177 lines
4.7 KiB
Vue
177 lines
4.7 KiB
Vue
<template>
|
|
<el-dialog
|
|
v-model="localVisible"
|
|
:title="title"
|
|
:width="width"
|
|
:close-on-click-modal="false"
|
|
@closed="handleCancel"
|
|
@open="handleOpen"
|
|
v-bind="$attrs"
|
|
>
|
|
<el-form
|
|
ref="formRef"
|
|
:model="modelValue"
|
|
:rules="rules"
|
|
:label-width="labelWidth"
|
|
:label-position="labelPosition"
|
|
>
|
|
<el-row :gutter="gutter">
|
|
<template v-for="(item, index) in fields" :key="item.prop">
|
|
<el-col :span="item.span||24">
|
|
<el-form-item :label="item.label" :prop="item.prop" :rules="item.rules">
|
|
<!-- 插槽优先 -->
|
|
<slot :name="item.prop" :item="item" :form="modelValue">
|
|
<component
|
|
:is="getComponent(item.type)"
|
|
v-model="modelValue[item.prop]"
|
|
v-bind="getProps(item)"
|
|
>
|
|
<!-- 下拉选项 -->
|
|
<template v-if="item.type === 'select'" #default>
|
|
<el-option
|
|
v-for="opt in item.options || []"
|
|
:key="opt.value"
|
|
:label="opt.label"
|
|
:value="opt.value"
|
|
/>
|
|
</template>
|
|
</component>
|
|
</slot>
|
|
</el-form-item>
|
|
</el-col>
|
|
</template>
|
|
</el-row>
|
|
</el-form>
|
|
|
|
<!-- 操作按钮 -->
|
|
<template #footer v-if="showFooter">
|
|
<slot name="footer">
|
|
<el-button @click="handleCancel">取消</el-button>
|
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
</slot>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, watch, onMounted } from 'vue';
|
|
import type { FormInstance, FormRules } from 'element-plus';
|
|
import {PopupFormField} from '../type'
|
|
|
|
defineOptions({ name: 'ModalForm' });
|
|
|
|
const props = defineProps<{
|
|
visible: boolean;
|
|
title?: string;
|
|
width?: string;
|
|
modelValue: Record<string, any>;
|
|
fields: PopupFormField[];
|
|
labelWidth?: string;
|
|
labelPosition?: 'left' | 'right' | 'top';
|
|
gutter?: number;
|
|
showFooter?: boolean;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:visible', val: boolean): void;
|
|
(e: 'update:modelValue', val: Record<string, any>): void;
|
|
(e: 'submit', val:any): void;
|
|
(e: 'cancel'): void;
|
|
(e: 'open'): void;
|
|
}>();
|
|
|
|
const formRef = ref<FormInstance>();
|
|
const localVisible = ref(props.visible);
|
|
|
|
watch(() => props.visible, val => localVisible.value = val);
|
|
watch(localVisible, val => emit('update:visible', val));
|
|
|
|
const labelWidth = computed(() => props.labelWidth || '100px');
|
|
const labelPosition = computed(() => props.labelPosition || 'right');
|
|
const gutter = computed(() => props.gutter || 10);
|
|
const showFooter = computed(() => props.showFooter !== false);
|
|
|
|
const rules = computed<FormRules>(() => {
|
|
const map: FormRules = {};
|
|
props.fields.forEach(field => {
|
|
if (field.rules) map[field.prop] = field.rules;
|
|
});
|
|
return map;
|
|
});
|
|
|
|
const componentMap: Record<string, any> = {
|
|
input: 'el-input',
|
|
select: 'el-select',
|
|
date: 'el-date-picker',
|
|
dateTime: 'el-date-picker',
|
|
daterange: 'el-date-picker',
|
|
datetimerange: 'el-date-picker',
|
|
switch: 'el-switch',
|
|
radio: 'el-radio-group',
|
|
checkbox: 'el-checkbox-group',
|
|
upload: 'el-upload',
|
|
cascader: 'el-cascader',
|
|
rate: 'el-rate',
|
|
slider: 'el-slider',
|
|
timePicker: 'el-time-picker',
|
|
inputNumber: 'el-input-number',
|
|
colorPicker: 'el-color-picker',
|
|
treeSelect: 'el-tree-select',
|
|
};
|
|
|
|
const getComponent = (type?: string) => componentMap[type || 'input'] || 'el-input';
|
|
|
|
const getProps = (item: PopupFormField) => {
|
|
const props = item.componentProps || {};
|
|
if (item.type === 'select') {
|
|
return { ...props, filterable: true };
|
|
}
|
|
if (item.type === 'daterange' || item.type === 'datetimerange') {
|
|
return {
|
|
...props,
|
|
type: item.type,
|
|
'value-format': 'YYYY-MM-DD',
|
|
'range-separator': '至',
|
|
'start-placeholder': '开始日期',
|
|
'end-placeholder': '结束日期',
|
|
};
|
|
}
|
|
return props;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!formRef.value) return;
|
|
await formRef.value.validate((valid) => {
|
|
if (valid) emit('submit', props.modelValue);
|
|
});
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
emit('cancel');
|
|
localVisible.value = false;
|
|
formRef.value?.resetFields();
|
|
};
|
|
|
|
const handleOpen = () => {
|
|
emit('open');
|
|
};
|
|
|
|
const loadOptions = async () => {
|
|
const loaded = new Set();
|
|
for (const field of props.fields) {
|
|
if (field.fetchOptions && !loaded.has(field.fetchOptions)) {
|
|
field.options = await field.fetchOptions();
|
|
loaded.add(field.fetchOptions);
|
|
}
|
|
}
|
|
};
|
|
|
|
onMounted(loadOptions);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.form-item-wrapper {
|
|
display: block;
|
|
}
|
|
</style>
|