203 lines
4.9 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>
<template v-if="item.type === 'radio'">
<el-radio
v-model="modelValue[item.prop]"
v-for="opt in item.options"
:key="opt.value"
:value="opt.value"
v-bind="getProps(item)"
>
{{ opt.label }}
</el-radio>
</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';
import UploadImg from '/@/components/uploadImg/index.vue'
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: UploadImg,
cascader: 'el-cascader',
rate: 'el-rate',
slider: 'el-slider',
colorPicker: 'el-color-picker',
};
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': '结束日期',
};
}
if (item.type === 'radio') {
return {
...props,
size: props.size || 'default',
disabled: props.disabled || false,
type: props.button ? 'button' : props.type,
};
}
return props;
};
const handleSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate((valid) => {
const model = { ...props.modelValue };
for (const key in model) {
if (model[key] instanceof Date) {
model[key] = model[key].toLocaleString();
}
}
if (valid) emit('submit', model);
});
};
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>