554 lines
17 KiB
Vue
554 lines
17 KiB
Vue
<script setup lang="ts">
|
||
import DictSelect from '@/components/DictSelect/index.vue';
|
||
import DictCheckbox from '@/components/DictCheckBox/index.vue';
|
||
import DictRadio from '@/components/DictRadio/index.vue';
|
||
import RemoteSelect from '@/components/RemoteSelect/index.vue';
|
||
import UploadImage from '@/components/UploadImage/index.vue';
|
||
import UploadFiles from '@/components/UploadFiles/index.vue';
|
||
import type { FormGridConfig } from './types';
|
||
import { computed, ref, watch } from 'vue';
|
||
import { ElMessage } from 'element-plus';
|
||
|
||
const props = defineProps<{
|
||
modelValue: any;
|
||
configs: Array<FormGridConfig>;
|
||
labelWidth?: string;
|
||
labelPosition?: 'left' | 'right' | 'top';
|
||
disabled?: boolean;
|
||
formName?: string;
|
||
type?: 'text' | 'form';
|
||
}>();
|
||
|
||
const Emits = defineEmits(['update:modelValue']);
|
||
|
||
const formValue = computed({
|
||
get: () => {
|
||
return props.modelValue || {};
|
||
},
|
||
set: nv => {
|
||
Emits('update:modelValue', nv);
|
||
}
|
||
});
|
||
const rules = ref({} as any);
|
||
const formRef = ref();
|
||
|
||
/**
|
||
* 选择类型
|
||
*/
|
||
const selectType = [
|
||
'select',
|
||
'remoteSelect',
|
||
'dictSelect',
|
||
'dictCheckbox',
|
||
'dictRadio',
|
||
'checkbox',
|
||
'radio',
|
||
'date',
|
||
'datetime',
|
||
'datetimerange',
|
||
'daterange',
|
||
'year',
|
||
'yearrange',
|
||
'month',
|
||
'monthrange',
|
||
'week',
|
||
'weekrange'
|
||
];
|
||
|
||
watch(
|
||
() => props.configs,
|
||
newConfigs => {
|
||
rules.value = {};
|
||
newConfigs.forEach(item => {
|
||
if (item.required) {
|
||
rules.value[item.key] = item.rules || {
|
||
required: true,
|
||
message:
|
||
item.requiredText ||
|
||
item.placeholder ||
|
||
(item.type && selectType.includes(item.type)
|
||
? '请选择'
|
||
: '请输入') + item.name,
|
||
trigger: ['blur', 'change']
|
||
};
|
||
}
|
||
});
|
||
|
||
if (formRef.value) {
|
||
// formRef.value.resetFields();
|
||
|
||
formRef.value.clearValidate();
|
||
}
|
||
},
|
||
{
|
||
immediate: true,
|
||
deep: true
|
||
}
|
||
);
|
||
|
||
const formValidate = () => {
|
||
return new Promise((resolve, reject) => {
|
||
if (typeof formValue.value === 'object') {
|
||
for (const key in formValue.value) {
|
||
if (typeof formValue.value[key] === 'string') {
|
||
formValue.value[key] = formValue.value[key].trim();
|
||
}
|
||
}
|
||
}
|
||
|
||
formRef.value?.validate((valid, fields) => {
|
||
if (valid) {
|
||
resolve(formValue);
|
||
} else {
|
||
console.error(valid, fields);
|
||
if (typeof fields === 'object') {
|
||
const errorMessages = [] as Array<string>;
|
||
for (const key in fields) {
|
||
if (Object.prototype.hasOwnProperty.call(fields, key)) {
|
||
const rules = fields[key];
|
||
if (rules instanceof Array) {
|
||
rules.forEach(rule => {
|
||
errorMessages.push(rule.message);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
ElMessage.warning(errorMessages.join('\n'));
|
||
} else {
|
||
ElMessage.warning({
|
||
message: '请完善' + (props.formName || '') + '表单'
|
||
});
|
||
}
|
||
|
||
reject(valid);
|
||
}
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 时间类型
|
||
*/
|
||
const timeType = [
|
||
'date',
|
||
'datetime',
|
||
'daterange',
|
||
'year',
|
||
'month',
|
||
'week',
|
||
'datetimerange'
|
||
];
|
||
|
||
/**
|
||
* 时间格式化
|
||
*/
|
||
const timeFormat = {
|
||
date: 'YYYY-MM-DD',
|
||
datetime: 'YYYY-MM-DD HH:mm:ss',
|
||
daterange: 'YYYY-MM-DD',
|
||
year: 'YYYY',
|
||
month: 'MM',
|
||
datetimerange: 'YYYY-MM-DD HH:mm:ss'
|
||
};
|
||
|
||
const formReset = () => {
|
||
formRef.value?.resetFields();
|
||
};
|
||
|
||
const formItemRefs = {} as any;
|
||
const setFormItemRef = (itemKey, itemRef) => {
|
||
formItemRefs[itemKey] = itemRef;
|
||
};
|
||
|
||
/**
|
||
* 重新校验表单
|
||
* @param itemKey
|
||
*/
|
||
const reValidateKey = (itemKey: string) => {
|
||
if (formItemRefs[itemKey]) {
|
||
formItemRefs[itemKey].clearValidate();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 字典类型组件
|
||
*/
|
||
const dictTextType = [
|
||
'select',
|
||
'remoteSelect',
|
||
'dictSelect',
|
||
'dictCheckbox',
|
||
'dictRadio'
|
||
];
|
||
|
||
const localOptionType = ['select', 'radio', 'checkbox'];
|
||
|
||
/**
|
||
* 特殊组件
|
||
*/
|
||
const ignoreType = ['uploadImage', 'uploadFiles'];
|
||
|
||
defineExpose({
|
||
formValidate,
|
||
formReset
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<el-form
|
||
ref="formRef"
|
||
:model="formValue"
|
||
:rules="rules"
|
||
:label-width="labelWidth"
|
||
:label-position="labelPosition"
|
||
:class="{
|
||
'data-form-grid__text': type === 'text'
|
||
}"
|
||
scroll-to-error
|
||
:validate-on-rule-change="false"
|
||
@submit.prevent
|
||
>
|
||
<el-row :gutter="24">
|
||
<el-col
|
||
v-for="item in configs"
|
||
:key="item.key"
|
||
v-auth="item.auth"
|
||
:sm="24"
|
||
:md="
|
||
item.mdSpan ||
|
||
(item.type === 'title'
|
||
? 24
|
||
: item.span && item.span > 12
|
||
? item.span
|
||
: 12)
|
||
"
|
||
:lg="item.span || 24"
|
||
>
|
||
<template v-if="!item.hidden">
|
||
<template v-if="item.type === 'null' || item.type === 'slot'">
|
||
<div v-auth="item.auth">
|
||
<slot v-if="item.type === 'slot'" :name="item.key"></slot>
|
||
</div>
|
||
</template>
|
||
<template v-else-if="item.type === 'title'">
|
||
<div
|
||
v-auth="item.auth"
|
||
class="mb-4 text-primary-light-color font-bold flex items-center"
|
||
:style="{
|
||
marginBottom:
|
||
type === 'text' || item.disabledText ? '8px' : undefined
|
||
}"
|
||
>
|
||
{{ item.name }}
|
||
<slot
|
||
v-if="item.options && item.options[0].hasSuffix"
|
||
:name="item.key"
|
||
></slot>
|
||
</div>
|
||
</template>
|
||
<el-form-item
|
||
v-else
|
||
:ref="el => setFormItemRef(item.key, el)"
|
||
v-auth="item.auth"
|
||
:label="item.name + ':'"
|
||
:prop="item.key"
|
||
:class="{
|
||
'data-form-grid__text-item': type === 'text' || item.disabledText
|
||
}"
|
||
:label-width="item.labelWidth"
|
||
>
|
||
<slot :name="item.key" :value-key="item.key">
|
||
<template
|
||
v-if="
|
||
(type !== 'text' && !item.disabledText) ||
|
||
ignoreType.includes(item.type as any)
|
||
"
|
||
>
|
||
<template v-if="item.type === 'checkbox' && item.options">
|
||
<el-checkbox-group
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
>
|
||
<el-checkbox
|
||
v-for="option in item.options"
|
||
:key="option.value"
|
||
:label="option.value"
|
||
>{{ option.label }}</el-checkbox
|
||
>
|
||
</el-checkbox-group>
|
||
</template>
|
||
<template v-else-if="item.type === 'radio' && item.options">
|
||
<el-radio-group
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
>
|
||
<el-radio
|
||
v-for="option in item.options"
|
||
:key="option.value"
|
||
:label="option.value"
|
||
>{{ option.label }}</el-radio
|
||
>
|
||
</el-radio-group>
|
||
</template>
|
||
<template
|
||
v-else-if="
|
||
item.type === 'dictRadio' &&
|
||
item.options &&
|
||
item.options[0].dictCode
|
||
"
|
||
>
|
||
<DictRadio
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:dict-code="item.options[0].dictCode"
|
||
:text-key="item.options[0].label"
|
||
:value-key="item.options[0].value"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></DictRadio>
|
||
</template>
|
||
<template
|
||
v-else-if="
|
||
item.type === 'dictCheckbox' &&
|
||
item.options &&
|
||
item.options[0].dictCode
|
||
"
|
||
>
|
||
<DictCheckbox
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:dict-code="item.options[0].dictCode"
|
||
:text-key="item.options[0].label"
|
||
:value-key="item.options[0].value"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></DictCheckbox>
|
||
</template>
|
||
<template v-else-if="item.type === 'select' && item.options">
|
||
<el-select
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请选择' + item.name
|
||
"
|
||
class="w-full"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
>
|
||
<el-option
|
||
v-for="option in item.options"
|
||
:key="option.value"
|
||
:label="option.label"
|
||
:value="option.value"
|
||
></el-option>
|
||
</el-select>
|
||
</template>
|
||
<template
|
||
v-else-if="
|
||
item.type === 'dictSelect' &&
|
||
item.options &&
|
||
item.options[0].dictCode
|
||
"
|
||
>
|
||
<DictSelect
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:dict-code="item.options[0].dictCode"
|
||
:text-key="item.options[0].label"
|
||
:value-key="item.options[0].value"
|
||
:disabled="disabled || item.disabledText"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请选择' + item.name
|
||
"
|
||
class="w-full"
|
||
clearable
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></DictSelect>
|
||
</template>
|
||
<template
|
||
v-else-if="item.type === 'remoteSelect' && item.options"
|
||
>
|
||
<RemoteSelect
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:url="item.options[0].url"
|
||
:option-label="item.options[0].label"
|
||
:option-value="item.options[0].value"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请选择' + item.name
|
||
"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></RemoteSelect>
|
||
</template>
|
||
<template
|
||
v-else-if="item.type === 'uploadImage' && item.options"
|
||
>
|
||
<UploadImage
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:url="item.options[0].url || ''"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
@change="reValidateKey(item.key)"
|
||
v-on="item.eventOptions || {}"
|
||
></UploadImage>
|
||
</template>
|
||
<template
|
||
v-else-if="item.type === 'uploadFiles' && item.options"
|
||
>
|
||
<UploadFiles
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:url="item.options[0].url || ''"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
:accept="item.accept || ''"
|
||
@change="reValidateKey(item.key)"
|
||
v-on="item.eventOptions || {}"
|
||
></UploadFiles>
|
||
</template>
|
||
<template v-else-if="item.type && timeType.includes(item.type)">
|
||
<el-date-picker
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:type="item.type"
|
||
class="w-full"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请选择日期'
|
||
"
|
||
:format="timeFormat[item.type] || 'YYYY-MM-DD'"
|
||
:value-format="timeFormat[item.type] || 'YYYY-MM-DD'"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
/>
|
||
</template>
|
||
<template v-else-if="item.type === 'textarea'">
|
||
<el-input
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
type="textarea"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请输入' + item.name
|
||
"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></el-input>
|
||
</template>
|
||
<template v-else-if="item.type === 'numberInput'">
|
||
<el-input
|
||
v-model.number="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
type="number"
|
||
style="width: 100%"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请输入' + item.name
|
||
"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></el-input>
|
||
</template>
|
||
<el-input
|
||
v-else
|
||
v-model="formValue[item.key]"
|
||
v-auth="item.auth"
|
||
:type="item.type !== 'input' ? item.type : undefined"
|
||
:placeholder="
|
||
item.placeholder ? item.placeholder : '请输入' + item.name
|
||
"
|
||
:disabled="disabled || item.disabledText"
|
||
v-bind="item.componentOptions"
|
||
v-on="item.eventOptions || {}"
|
||
></el-input>
|
||
</template>
|
||
<template v-else>
|
||
<div
|
||
v-if="dictTextType.includes(item.type as any)"
|
||
v-auth="item.auth"
|
||
class="text-[#888]"
|
||
style="white-space: break-spaces"
|
||
>
|
||
{{
|
||
typeof formValue[
|
||
item.dictTextName
|
||
? item.dictTextName
|
||
: `${item.key}_dictText`
|
||
] === 'undefined' || formValue[item.key] === null
|
||
? '-'
|
||
: formValue[
|
||
item.dictTextName
|
||
? item.dictTextName
|
||
: `${item.key}_dictText`
|
||
]
|
||
}}
|
||
</div>
|
||
|
||
<div
|
||
v-else-if="
|
||
item.type &&
|
||
localOptionType.includes(item.type) &&
|
||
item.options
|
||
"
|
||
>
|
||
{{
|
||
typeof formValue[item.key] === 'undefined'
|
||
? '-'
|
||
: item.options.find(opItem => {
|
||
return opItem.value === formValue[item.key];
|
||
})?.label
|
||
}}
|
||
</div>
|
||
|
||
<div
|
||
v-else
|
||
v-auth="item.auth"
|
||
class="text-[#888]"
|
||
style="white-space: break-spaces"
|
||
>
|
||
{{
|
||
typeof formValue[item.key] === 'undefined' ||
|
||
formValue[item.key] === null
|
||
? '-'
|
||
: formValue[item.key] +
|
||
(item.componentOptions &&
|
||
item.componentOptions.suffixIcon
|
||
? ' ' + item.componentOptions.suffixIcon.render()
|
||
: '')
|
||
}}
|
||
</div>
|
||
</template>
|
||
</slot>
|
||
</el-form-item>
|
||
</template>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</template>
|
||
|
||
<style scoped>
|
||
:deep(.el-date-editor.el-input) {
|
||
width: 100%;
|
||
}
|
||
|
||
.data-form-grid__text :deep(.el-form-item) {
|
||
margin-bottom: 8px;
|
||
}
|
||
.data-form-grid__text-item {
|
||
margin-bottom: 8px;
|
||
}
|
||
</style>
|