<template>
|
<div class="dynamic-form">
|
<el-dialog
|
v-model="dialogVisible"
|
:title="state.title"
|
width="50%"
|
:before-close="closeDialog"
|
:close-on-press-escape="false"
|
:close-on-click-modal="false"
|
>
|
<el-dialog v-model="state.dialogVisible">
|
<el-image style="width: 100%" :src="state.dialogImageUrl" />
|
</el-dialog>
|
<el-form
|
:model="state.formData"
|
size="default"
|
ref="formRef"
|
:rules="state.formRules"
|
label-width="160px"
|
>
|
<template v-for="(item, index) in config.formItems" :key="index">
|
<el-divider v-if="item.type === 'divider'" content-position="left">{{
|
item.content
|
}}</el-divider>
|
<el-form-item
|
v-else
|
:label="item.label + (item.label.endsWith(':') ? '' : ':')"
|
:prop="item.prop"
|
:rules="item.rules"
|
>
|
<!-- 文件上传组件 -->
|
<el-upload
|
v-if="item.type === 'upload'"
|
:action="state.uploadUrl"
|
:headers="state.header"
|
method="post"
|
:on-success="
|
(res, uploadFile) =>
|
handleUploadSuccess(res, uploadFile, item.prop)
|
"
|
:on-exceed="showTip"
|
:limit="item.limit || 1"
|
v-model:file-list="state.fileLists[item.prop]"
|
:before-upload="(rawFile) => beforeUpload(rawFile, item)"
|
:on-remove="
|
(file, uploadFiles) =>
|
handleRemove(file, uploadFiles, item.prop)
|
"
|
:accept="item.accept || '.doc,.docx,.pdf,.jpg,.jpeg,.png'"
|
:disabled="state.title === '查看' || item.disabled"
|
:list-type="item.uploadType == 'img' ? 'picture-card' : ''"
|
:on-preview="handlePictureCardPreview"
|
>
|
<el-icon v-if="item.uploadType == 'img'"><Plus /></el-icon>
|
<el-button v-else type="primary">点击上传</el-button>
|
<template #tip>
|
<div class="el-upload__tip" v-if="item.tip">
|
{{ item.tip }}
|
</div>
|
<div class="el-upload__tip" v-else>
|
支持上传{{
|
item.accept || ".doc,.docx,.pdf,.jpg,.jpeg,.png"
|
}}格式, 尺寸小于{{ item.maxSize || 5 }}M,最多可上传{{
|
item.limit || 1
|
}}份
|
</div>
|
</template>
|
</el-upload>
|
|
<!-- 多选选择框组件 -->
|
<el-select
|
v-else-if="item.type === 'select' && item.multiple"
|
v-model="state.formData[item.prop]"
|
:placeholder="item.placeholder || '请选择'"
|
:disabled="state.title === '查看' || item.disabled"
|
:clearable="item.clearable !== false"
|
:filterable="item.filterable"
|
:multiple="item.multiple"
|
:multiple-limit="item.multipleLimit"
|
:collapse-tags="item.collapseTags"
|
:collapse-tags-tooltip="item.collapseTagsTooltip"
|
:style="item.style || 'width: 100%'"
|
@change="
|
item.changeEvent
|
? handleEvent(item.changeEvent, state.formData[item.prop])
|
: null
|
"
|
>
|
<el-option
|
v-for="option in getOptions(item.options)"
|
:key="option.value"
|
:label="option.label"
|
:value="option.value"
|
/>
|
</el-select>
|
|
<!-- 单选选择框组件 -->
|
<el-select
|
v-else-if="item.type === 'select'"
|
v-model="state.formData[item.prop]"
|
:placeholder="item.placeholder || '请选择'"
|
:disabled="state.title === '查看' || item.disabled"
|
:clearable="item.clearable !== false"
|
:filterable="item.filterable"
|
:style="item.style || 'width: 100%'"
|
@change="
|
item.changeEvent
|
? handleEvent(item.changeEvent, state.formData[item.prop])
|
: null
|
"
|
>
|
<el-option
|
v-for="option in getOptions(item.options)"
|
:key="option.value"
|
:label="option.label"
|
:value="option.value"
|
/>
|
</el-select>
|
|
<!-- 级联选择组件 -->
|
<el-cascader
|
v-else-if="item.type === 'cascader'"
|
v-model="state.formData[item.prop]"
|
:options="getCascaderOptions(item)"
|
:props="item.props || {}"
|
:placeholder="item.placeholder || '请选择'"
|
:disabled="state.title === '查看' || item.disabled"
|
:clearable="item.clearable !== false"
|
:filterable="item.filterable"
|
:show-all-levels="item.showAllLevels !== false"
|
:style="item.style || 'width: 100%'"
|
@change="
|
item.changeEvent
|
? handleEvent(item.changeEvent, state.formData[item.prop])
|
: null
|
"
|
/>
|
|
<!-- 日期选择器组件 -->
|
<el-date-picker
|
v-else-if="item.type === 'date'"
|
v-model="state.formData[item.prop]"
|
:type="item.dateType || 'date'"
|
:placeholder="item.placeholder || '请选择日期'"
|
:disabled="state.title === '查看' || item.disabled"
|
:value-format="item.valueFormat || 'YYYY-MM-DD'"
|
:style="item.style || 'width: 100%'"
|
/>
|
|
<!-- 文本域组件 -->
|
<el-input
|
v-else-if="item.type === 'textarea'"
|
v-model="state.formData[item.prop]"
|
type="textarea"
|
:placeholder="item.placeholder || '请输入'"
|
:disabled="state.title === '查看' || item.disabled"
|
:readonly="state.title === '查看'"
|
:autosize="item.autosize || { minRows: 2 }"
|
:style="item.style || 'width: 100%'"
|
/>
|
|
<!-- 默认文本输入框组件 -->
|
<el-input
|
v-else
|
v-model="state.formData[item.prop]"
|
:placeholder="item.placeholder || '请输入'"
|
:disabled="state.title === '查看' || item.disabled"
|
:readonly="state.title === '查看'"
|
:style="item.style || 'width: 100%'"
|
:type="item.inputType || 'text'"
|
/>
|
</el-form-item>
|
</template>
|
</el-form>
|
|
<template #footer v-if="state.title !== '查看'">
|
<span class="dialog-footer">
|
<el-button
|
type="primary"
|
@click="onSave"
|
v-if="state.hasSave"
|
size="default"
|
v-preReClick
|
>暂存</el-button
|
>
|
<el-button @click="closeDialog" size="default">取 消</el-button>
|
<el-button
|
type="primary"
|
@click="onSubmit"
|
size="default"
|
v-preReClick
|
>确认</el-button
|
>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { reactive, ref, nextTick } from "vue";
|
import { ElMessage } from "element-plus";
|
import { getToken } from "@/utils/auth";
|
|
// 定义props
|
const props = defineProps({
|
config: {
|
type: Object,
|
required: true,
|
default: () => ({}),
|
},
|
// API相关函数
|
api: {
|
type: Object,
|
default: () => ({}),
|
},
|
// 数据加载函数
|
dataLoader: {
|
type: Object,
|
default: () => ({}),
|
},
|
// 文件上传相关配置
|
uploadConfig: {
|
type: Object,
|
default: () => ({
|
uploadUrl: import.meta.env.VITE_APP_BASE_API + "/common/upload",
|
deleteUrl: "/common/deleteFile",
|
}),
|
},
|
});
|
|
const emit = defineEmits(["submit", "close", "update:modelValue"]);
|
|
const dialogVisible = ref(false);
|
const formRef = ref();
|
|
const state = reactive({
|
title: "",
|
formData: {},
|
formRules: {},
|
uploadUrl: props.uploadConfig.uploadUrl,
|
header: {
|
Authorization: getToken(),
|
},
|
fileLists: {},
|
companyList: [],
|
deptList: [],
|
userList: [],
|
dialogVisible: false,
|
dialogImageUrl: "",
|
hasSave: false,
|
});
|
|
const initFormData = () => {
|
const formData = {};
|
state.fileLists = {};
|
|
props.config.formItems?.forEach((item) => {
|
if (item.type === "select" && item.multiple) {
|
formData[item.prop] = Array.isArray(item.defaultValue)
|
? item.defaultValue
|
: [];
|
} else {
|
formData[item.prop] =
|
item.defaultValue !== undefined ? item.defaultValue : null;
|
}
|
|
if (item.type === "upload") {
|
state.fileLists[item.prop] = [];
|
}
|
});
|
|
const formRules = {};
|
props.config.formItems?.forEach((item) => {
|
if (item.rules) {
|
formRules[item.prop] = item.rules;
|
}
|
|
if (item.type === "upload" && item.required) {
|
formRules[item.prop] = formRules[item.prop] || [];
|
formRules[item.prop].push({
|
required: true,
|
validator: (rule, value, callback) => {
|
if (state.fileLists[item.prop]?.length === 0) {
|
callback(new Error(`请上传${item.label}`));
|
} else {
|
callback();
|
}
|
},
|
trigger: "blur",
|
});
|
}
|
|
if (item.type === "select" && item.multiple && item.required) {
|
formRules[item.prop] = formRules[item.prop] || [];
|
formRules[item.prop].push({
|
validator: (rule, value, callback) => {
|
if (!value || value.length === 0) {
|
callback(new Error(`请选择${item.label}`));
|
} else {
|
callback();
|
}
|
},
|
trigger: "change",
|
});
|
}
|
});
|
|
state.formData = formData;
|
state.formRules = formRules;
|
};
|
|
const getOptions = (options) => {
|
if (!options) return [];
|
|
if (typeof options === "function") {
|
return options();
|
}
|
|
if (Array.isArray(options)) {
|
return options;
|
}
|
|
return [];
|
};
|
|
const getCascaderOptions = (item) => {
|
if (!item.options) return [];
|
|
if (typeof item.options === "function") {
|
return item.options();
|
}
|
|
if (Array.isArray(item.options)) {
|
return item.options;
|
}
|
|
if (
|
item.options &&
|
typeof item.options === "object" &&
|
"value" in item.options
|
) {
|
return item.options.value;
|
}
|
|
return [];
|
};
|
|
const beforeUpload = (rawFile, item) => {
|
const maxSize = item.maxSize || 5;
|
if (rawFile.size / 1024 / 1024 > maxSize) {
|
ElMessage.warning(`文件大小不能超过${maxSize}M`);
|
return false;
|
}
|
return true;
|
};
|
|
const handleUploadSuccess = (res, uploadFile, prop) => {
|
if (res.code === 200) {
|
ElMessage.success("上传成功");
|
} else {
|
state.fileLists[prop] = state.fileLists[prop].filter(
|
(file) => file.uid !== uploadFile.uid,
|
);
|
ElMessage.warning(res.message || "文件上传失败");
|
}
|
state.formData[prop] = state.fileLists[prop]
|
.filter((i) => i.status == "success")
|
.map((item) => {
|
return item.response.fileName;
|
})
|
.join(",");
|
};
|
|
const handleRemove = async (file, uploadFiles, prop) => {
|
try {
|
if (file.uid) {
|
state.fileLists[prop] = state.fileLists[prop].filter(
|
(f) => f.uid !== file.uid,
|
);
|
} else {
|
state.fileLists[prop] = state.fileLists[prop].filter(
|
(f) => f.url !== file.url,
|
);
|
}
|
state.formData[prop] = state.fileLists[prop]
|
.filter((i) => i.status == "success")
|
.map((item) => {
|
return item.response.fileName;
|
})
|
.join(",");
|
ElMessage.success("文件已删除");
|
} catch (error) {
|
console.error("删除文件时出错:", error);
|
ElMessage.warning("文件删除失败");
|
}
|
};
|
|
const handlePictureCardPreview = (uploadFile) => {
|
state.dialogImageUrl = uploadFile.url;
|
state.dialogVisible = true;
|
};
|
|
const showTip = () => {
|
ElMessage.warning("超出文件上传数量");
|
};
|
|
const handleEvent = async (eventName, value) => {
|
try {
|
if (
|
eventName === "getUserListByRole" &&
|
props.dataLoader.getUserListByRole
|
) {
|
const userList = await props.dataLoader.getUserListByRole(value);
|
state.userList = userList;
|
const userItem = props.config.formItems.find(
|
(item) => item.prop === "reformUserId",
|
);
|
if (userItem) {
|
userItem.options = () =>
|
userList.map((user) => ({
|
value: user.userId,
|
label: user.nickName,
|
}));
|
state.formData.reformUserId = null;
|
await nextTick();
|
}
|
}
|
} catch (error) {
|
console.error("事件处理失败:", error);
|
ElMessage.error("数据加载失败");
|
}
|
};
|
|
const openDialog = async (type, initialData, hasSave) => {
|
state.title =
|
type === "add"
|
? "新增"
|
: type === "edit"
|
? "编辑"
|
: type === "rectify"
|
? "整改"
|
: "查看";
|
state.hasSave = hasSave
|
initFormData();
|
|
try {
|
if (props.dataLoader.loadInitialData) {
|
const initialData = await props.dataLoader.loadInitialData(id);
|
Object.assign(state, initialData);
|
}
|
} catch (error) {
|
console.error("无初始数据:", error);
|
}
|
|
if (initialData) {
|
Object.keys(state.formData).forEach((key) => {
|
if (key in initialData) {
|
const item = props.config.formItems.find((i) => i.prop === key);
|
if (item && item.type === "select" && item.multiple) {
|
state.formData[key] = Array.isArray(initialData[key])
|
? initialData[key]
|
: initialData[key]
|
? [initialData[key]]
|
: [];
|
} else {
|
state.formData[key] = initialData[key];
|
}
|
}
|
});
|
state.formData.id = initialData.id || null;
|
|
props.config.formItems?.forEach((item) => {
|
if (item.type === "upload" && initialData[item.prop]) {
|
state.fileLists[item.prop] = [
|
{
|
url: initialData[item.prop],
|
name: item.label || "文件",
|
},
|
];
|
state.fileLists[item.prop] = initialData[item.prop]
|
.split(",")
|
.map((i) => {
|
return {
|
url: import.meta.env.VITE_APP_BASE_API + i,
|
status: "success",
|
name: item.label || "文件",
|
response: {
|
fileName: i,
|
},
|
};
|
});
|
}
|
});
|
}
|
dialogVisible.value = true;
|
await nextTick()
|
};
|
|
// 提交表单
|
const onSubmit = async () => {
|
try {
|
const valid = await formRef.value.validate();
|
if (valid) {
|
const submitData = { ...state.formData };
|
|
props.config.formItems?.forEach((item) => {
|
if (
|
item.type === "select" &&
|
item.multiple &&
|
Array.isArray(submitData[item.prop]) &&
|
submitData[item.prop].length === 0
|
) {
|
submitData[item.prop] = null;
|
}
|
});
|
|
emit("submit", submitData, state.title);
|
}else{
|
ElMessage.error("请完善表单内容");
|
}
|
} catch (error) {
|
console.error("表单验证失败:", error);
|
ElMessage.error("请完善表单内容");
|
}
|
};
|
|
const onSave = async () => {
|
const submitData = { ...state.formData };
|
props.config.formItems?.forEach((item) => {
|
if (
|
item.type === "select" &&
|
item.multiple &&
|
Array.isArray(submitData[item.prop]) &&
|
submitData[item.prop].length === 0
|
) {
|
submitData[item.prop] = null;
|
}
|
});
|
emit("onSave", submitData, state.title);
|
};
|
|
const closeDialog = () => {
|
if (formRef.value) {
|
formRef.value.clearValidate();
|
formRef.value.resetFields();
|
}
|
dialogVisible.value = false;
|
};
|
|
defineExpose({
|
openDialog,
|
closeDialog,
|
});
|
|
initFormData();
|
</script>
|
|
<style scoped lang="scss">
|
.dynamic-form {
|
:deep(.el-form .el-form-item__label) {
|
font-size: 15px;
|
}
|
}
|
|
.el-upload__tip {
|
font-size: 12px;
|
color: #606266;
|
margin-top: 7px;
|
}
|
|
:deep(.el-select__tags) {
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
</style>
|