| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- <template>
- <el-form
- ref="formRef"
- :model="formData"
- :rules="formRules"
- :label-width="labelWidth"
- :label-position="labelPosition"
- :size="size"
- :disabled="disabled"
- :inline="inline"
- v-bind="$attrs"
- >
- <!-- 内联表单 -->
- <template v-if="inline">
- <el-form-item
- v-for="(item, index) in formConfig"
- :key="index"
- :label="item.label"
- :prop="item.name"
- :required="item.req"
- :rules="getItemRules(item)"
- >
- <!-- 输入框 -->
- <el-input
- v-if="item.type === 'text'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请输入${item.label}`"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :maxlength="item.maxlength"
- :show-word-limit="item.showWordLimit"
- v-bind="item.props"
- />
- <!-- 文本域 -->
- <el-input
- v-else-if="item.type === 'textarea'"
- v-model="formData[item.name]"
- type="textarea"
- :placeholder="item.placeholder || `请输入${item.label}`"
- :rows="item.rows || 3"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :maxlength="item.maxlength"
- :show-word-limit="item.showWordLimit"
- v-bind="item.props"
- />
- <!-- 数字输入框 -->
- <el-input-number
- v-else-if="item.type === 'number'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请输入${item.label}`"
- :min="item.min"
- :max="item.max"
- :step="item.step"
- :precision="item.precision"
- :disabled="item.disabled"
- :controls="item.controls !== false"
- v-bind="item.props"
- />
- <!-- 选择器 -->
- <el-select
- v-else-if="item.type === 'select'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请选择${item.label}`"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :multiple="item.multiple"
- :filterable="item.filterable"
- :remote="item.remote"
- :remote-method="item.remoteMethod"
- :loading="item.loading"
- v-bind="item.props"
- :style="inline ? 'width: 180px' : 'width: 100%'"
- >
- <el-option
- v-for="option in getOptions(item)"
- :key="getOptionValue(option, item)"
- :label="getOptionLabel(option, item)"
- :value="getOptionValue(option, item)"
- />
- </el-select>
- <!-- 日期选择器 -->
- <el-date-picker
- v-else-if="item.type === 'date'"
- v-model="formData[item.name]"
- :type="item.dateType || 'date'"
- :placeholder="item.placeholder || `请选择${item.label}`"
- :format="item.format"
- :value-format="item.valueFormat"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :show-time="item.showTime"
- :range-separator="item.rangeSeparator || '至'"
- :start-placeholder="item.startPlaceholder"
- :end-placeholder="item.endPlaceholder"
- v-bind="item.props"
- style="width: 180px"
- />
- <!-- 时间选择器 -->
- <el-time-picker
- v-else-if="item.type === 'time'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请选择${item.label}`"
- :format="item.format"
- :value-format="item.valueFormat"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- v-bind="item.props"
- style="width: 180px"
- />
- <!-- 开关 -->
- <el-switch
- v-else-if="item.type === 'switch'"
- v-model="formData[item.name]"
- :disabled="item.disabled"
- :active-text="item.activeText"
- :inactive-text="item.inactiveText"
- :active-value="item.activeValue"
- :inactive-value="item.inactiveValue"
- v-bind="item.props"
- />
- <!-- 单选框组 -->
- <el-radio-group
- v-else-if="item.type === 'radio'"
- v-model="formData[item.name]"
- :disabled="item.disabled"
- v-bind="item.props"
- >
- <el-radio
- v-for="option in getOptions(item)"
- :key="getOptionValue(option, item)"
- :label="getOptionValue(option, item)"
- >
- {{ getOptionLabel(option, item) }}
- </el-radio>
- </el-radio-group>
- <!-- 复选框组 -->
- <el-checkbox-group
- v-else-if="item.type === 'checkbox'"
- v-model="formData[item.name]"
- :disabled="item.disabled"
- v-bind="item.props"
- >
- <el-checkbox
- v-for="option in getOptions(item)"
- :key="getOptionValue(option, item)"
- :label="getOptionValue(option, item)"
- >
- {{ getOptionLabel(option, item) }}
- </el-checkbox>
- </el-checkbox-group>
- <!-- 自定义插槽 -->
- <slot
- v-else-if="item.type === 'slot'"
- :name="item.slotName || item.name"
- :item="item"
- :value="formData[item.name]"
- :formData="formData"
- />
- <!-- 范围选择器 -->
- <div
- v-else-if="item.type === 'range'"
- class="range-input"
- style="display: flex; align-items: center; gap: 10px"
- >
- <el-input
- v-model="formData[item.beginField || 'begin']"
- :placeholder="item.startPlaceholder || '开始值'"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- style="flex: 1"
- />
- <span style="color: #999; font-size: 14px">至</span>
- <el-input
- v-model="formData[item.endField || 'end']"
- :placeholder="item.endPlaceholder || '结束值'"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- style="flex: 1"
- />
- </div>
- <!-- 默认文本显示 -->
- <span v-else>{{ formData[item.name] }}</span>
- </el-form-item>
- </template>
- <!-- 栅格布局表单 -->
- <el-row v-else :gutter="gutter">
- <el-col
- v-for="(item, index) in formConfig"
- :key="index"
- :span="item.span || 4"
- :xs="item.xs || 12"
- :sm="item.sm || 8"
- :md="item.md || 6"
- :lg="item.lg || 4"
- :xl="item.xl || 4"
- >
- <el-form-item
- :label="item.label"
- :prop="item.name"
- :required="item.req"
- :rules="getItemRules(item)"
- >
- <!-- 输入框 -->
- <el-input
- v-if="item.type === 'text'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请输入${item.label}`"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :maxlength="item.maxlength"
- :show-word-limit="item.showWordLimit"
- v-bind="item.props"
- />
- <!-- 文本域 -->
- <el-input
- v-else-if="item.type === 'textarea'"
- v-model="formData[item.name]"
- type="textarea"
- :placeholder="item.placeholder || `请输入${item.label}`"
- :rows="item.rows || 3"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :maxlength="item.maxlength"
- :show-word-limit="item.showWordLimit"
- v-bind="item.props"
- />
- <!-- 数字输入框 -->
- <el-input-number
- v-else-if="item.type === 'number'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请输入${item.label}`"
- :min="item.min"
- :max="item.max"
- :step="item.step"
- :precision="item.precision"
- :disabled="item.disabled"
- :controls="item.controls !== false"
- v-bind="item.props"
- style="width: 100%"
- />
- <!-- 选择器 -->
- <el-select
- v-else-if="item.type === 'select'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请选择${item.label}`"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :multiple="item.multiple"
- :filterable="item.filterable"
- :remote="item.remote"
- :remote-method="item.remoteMethod"
- :loading="item.loading"
- v-bind="item.props"
- style="width: 100%"
- >
- <el-option
- v-for="option in getOptions(item)"
- :key="getOptionValue(option, item)"
- :label="getOptionLabel(option, item)"
- :value="getOptionValue(option, item)"
- />
- </el-select>
- <!-- 日期选择器 -->
- <el-date-picker
- v-else-if="item.type === 'date'"
- v-model="formData[item.name]"
- :type="item.dateType || 'date'"
- :placeholder="item.placeholder || `请选择${item.label}`"
- :format="item.format"
- :value-format="item.valueFormat"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- :show-time="item.showTime"
- :range-separator="item.rangeSeparator || '至'"
- :start-placeholder="item.startPlaceholder"
- :end-placeholder="item.endPlaceholder"
- v-bind="item.props"
- style="width: 100%"
- />
- <!-- 时间选择器 -->
- <el-time-picker
- v-else-if="item.type === 'time'"
- v-model="formData[item.name]"
- :placeholder="item.placeholder || `请选择${item.label}`"
- :format="item.format"
- :value-format="item.valueFormat"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- v-bind="item.props"
- style="width: 100%"
- />
- <!-- 开关 -->
- <el-switch
- v-else-if="item.type === 'switch'"
- v-model="formData[item.name]"
- :disabled="item.disabled"
- :active-text="item.activeText"
- :inactive-text="item.inactiveText"
- :active-value="item.activeValue"
- :inactive-value="item.inactiveValue"
- v-bind="item.props"
- />
- <!-- 单选框组 -->
- <el-radio-group
- v-else-if="item.type === 'radio'"
- v-model="formData[item.name]"
- :disabled="item.disabled"
- v-bind="item.props"
- >
- <el-radio
- v-for="option in getOptions(item)"
- :key="getOptionValue(option, item)"
- :label="getOptionValue(option, item)"
- >
- {{ getOptionLabel(option, item) }}
- </el-radio>
- </el-radio-group>
- <!-- 复选框组 -->
- <el-checkbox-group
- v-else-if="item.type === 'checkbox'"
- v-model="formData[item.name]"
- :disabled="item.disabled"
- v-bind="item.props"
- >
- <el-checkbox
- v-for="option in getOptions(item)"
- :key="getOptionValue(option, item)"
- :label="getOptionValue(option, item)"
- >
- {{ getOptionLabel(option, item) }}
- </el-checkbox>
- </el-checkbox-group>
- <!-- 自定义插槽 -->
- <slot
- v-else-if="item.type === 'slot'"
- :name="item.slotName || item.name"
- :item="item"
- :value="formData[item.name]"
- :formData="formData"
- />
- <!-- 范围选择器 -->
- <div
- v-else-if="item.type === 'range'"
- class="range-input"
- style="display: flex; align-items: center; gap: 10px"
- >
- <el-input
- v-model="formData[item.beginField || 'begin']"
- :placeholder="item.startPlaceholder || '开始值'"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- style="flex: 1"
- />
- <span style="color: #999; font-size: 14px">至</span>
- <el-input
- v-model="formData[item.endField || 'end']"
- :placeholder="item.endPlaceholder || '结束值'"
- :clearable="item.clearable !== false"
- :disabled="item.disabled"
- style="flex: 1"
- />
- </div>
- <!-- 默认文本显示 -->
- <span v-else>{{ formData[item.name] }}</span>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- </template>
- <script setup>
- import { ref, reactive, watch, computed, onMounted } from "vue";
- const props = defineProps({
- // 表单配置
- config: {
- type: Array,
- default: () => [],
- },
- // 表单数据
- modelValue: {
- type: Object,
- default: () => ({}),
- },
- // 标签宽度
- labelWidth: {
- type: String,
- default: "120px",
- },
- // 标签位置
- labelPosition: {
- type: String,
- default: "right",
- },
- // 尺寸
- size: {
- type: String,
- default: "default",
- },
- // 是否禁用
- disabled: {
- type: Boolean,
- default: false,
- },
- // 栅格间距
- gutter: {
- type: Number,
- default: 20,
- },
- // 是否内联表单
- inline: {
- type: Boolean,
- default: false,
- },
- });
- const emit = defineEmits(["update:modelValue", "change", "validate"]);
- const formRef = ref();
- const formData = reactive({});
- const formRules = reactive({});
- // 监听父组件传入的modelValue变化,同步到formData
- watch(
- () => props.modelValue,
- (newValue) => {
- if (newValue) {
- Object.assign(formData, newValue);
- }
- },
- { immediate: true, deep: true }
- );
- // 监听formData变化,同步回父组件
- watch(
- formData,
- (newData) => {
- emit("update:modelValue", { ...newData });
- },
- { deep: true }
- );
- // 计算属性
- const formConfig = computed(() => props.config);
- // 初始化表单数据
- const initFormData = () => {
- formConfig.value.forEach((item) => {
- if (formData[item.name] === undefined) {
- formData[item.name] = item.value || getDefaultValue(item.type);
- }
- });
- };
- // 获取默认值
- const getDefaultValue = (type) => {
- const defaults = {
- text: "",
- textarea: "",
- number: null,
- select: null,
- date: null,
- time: null,
- switch: false,
- radio: null,
- checkbox: [],
- };
- return defaults[type] || "";
- };
- // 初始化表单验证规则
- const initFormRules = () => {
- formConfig.value.forEach((item) => {
- if (item.req) {
- formRules[item.name] = [
- { required: true, message: `请输入${item.label}`, trigger: "blur" },
- ];
- }
- if (item.rules) {
- formRules[item.name] = formRules[item.name] || [];
- formRules[item.name].push(...item.rules);
- }
- });
- };
- // 获取表单项验证规则
- const getItemRules = (item) => {
- const rules = [];
- if (item.req) {
- rules.push({
- required: true,
- message: `请输入${item.label}`,
- trigger: "blur",
- });
- }
- if (item.rules) {
- rules.push(...item.rules);
- }
- return rules;
- };
- // 获取选项数据
- const getOptions = (item) => {
- return item.option || [];
- };
- // 获取选项标签
- const getOptionLabel = (option, item) => {
- if (typeof option === "string") return option;
- return option[item.optionLabel] || option.label || option.name || option;
- };
- // 获取选项值
- const getOptionValue = (option, item) => {
- if (typeof option === "string") return option;
- return option[item.optionValue] || option.value || option.id || option;
- };
- // 监听外部数据变化
- watch(
- () => props.modelValue,
- (newVal) => {
- Object.assign(formData, newVal);
- },
- { deep: true, immediate: true }
- );
- // 监听表单数据变化
- watch(
- formData,
- (newVal) => {
- emit("update:modelValue", { ...newVal });
- emit("change", { ...newVal });
- },
- { deep: true }
- );
- // 表单验证
- const validate = (callback) => {
- return formRef.value.validate(callback);
- };
- // 重置表单
- const resetFields = () => {
- formRef.value.resetFields();
- // 重置为初始值
- formConfig.value.forEach((item) => {
- formData[item.name] = item.value || getDefaultValue(item.type);
- });
- };
- // 清除验证
- const clearValidate = (props) => {
- formRef.value.clearValidate(props);
- };
- // 暴露方法
- defineExpose({
- validate,
- resetFields,
- clearValidate,
- formRef,
- });
- // 初始化
- onMounted(() => {
- initFormData();
- initFormRules();
- });
- </script>
- <style scoped>
- .el-form-item {
- margin-bottom: 18px;
- }
- </style>
|