<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 @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: ''
|
})
|
|
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) => {
|
state.title = type === 'add' ? '新增' : type === 'edit' ? '编辑' : type === 'rectify' ? '整改' : '查看'
|
|
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)
|
}
|
} catch (error) {
|
console.error('表单验证失败:', error)
|
ElMessage.error('请完善表单内容')
|
}
|
}
|
|
|
const closeDialog = () => {
|
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>
|