2025-06-19 17:32:00 +08:00

554 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>