128 lines
6.5 KiB
Vue
128 lines
6.5 KiB
Vue
<template>
|
||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-bind="$attrs">
|
||
<!-- 动态表单项 -->
|
||
<el-row :gutter="props.rowGutter || 0">
|
||
<template v-for="(item, index) in formItems" :key="index">
|
||
<el-col :span="props.colSpan || 24">
|
||
<el-form-item :label="item.label" :prop="item.prop" :required="item.required" :label-position="item.labelPosition || 'left'"
|
||
:label-width="item.labelWidth || '100px'" :class="item.class">
|
||
<!-- 输入框 -->
|
||
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]"
|
||
:placeholder="item.placeholder" :disabled="item.disabled" clearable />
|
||
<!-- 密码输入框 -->
|
||
<el-input v-else-if="item.type === 'password'" v-model="formData[item.prop]"
|
||
:placeholder="item.placeholder" :disabled="item.disabled" clearable show-password type="password" />
|
||
<!-- 下拉选择框 -->
|
||
<el-select v-else-if="item.type === 'select'" v-model="formData[item.prop]"
|
||
:placeholder="item.placeholder" :disabled="item.disabled" clearable style="width: 100%"
|
||
@change="handleItemChange(item.prop, formData[item.prop])">
|
||
<el-option v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
|
||
:label="option.label" :value="option.value">
|
||
<template v-if="option.icon" #label>
|
||
<span style="display: flex; align-items: center; gap: 8px;">
|
||
<el-icon v-if="option.icon">
|
||
<component :is="option.icon" />
|
||
</el-icon>
|
||
<span>{{ option.label }}</span>
|
||
</span>
|
||
</template>
|
||
</el-option>
|
||
</el-select>
|
||
|
||
<!-- 数字输入框 -->
|
||
<el-input-number v-else-if="item.type === 'number'" v-model="formData[item.prop]"
|
||
:min="item.min" :max="item.max" :placeholder="item.placeholder" :disabled="item.disabled"
|
||
style="width: 100%" />
|
||
|
||
<!-- 单选框组 -->
|
||
<el-radio-group v-else-if="item.type === 'radio'" v-model="formData[item.prop]">
|
||
<el-radio v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
|
||
:value="option.value">
|
||
{{ option.label }}
|
||
</el-radio>
|
||
</el-radio-group>
|
||
|
||
<!-- 复选框组 -->
|
||
<el-checkbox-group v-else-if="item.type === 'checkbox'" v-model="formData[item.prop]">
|
||
<el-checkbox v-for="option in props.optionsMap?.[item.prop] || []" :key="option.value"
|
||
:value="option.value">
|
||
{{ option.label }}
|
||
</el-checkbox>
|
||
</el-checkbox-group>
|
||
<el-switch v-else-if="item.type === 'switch'" v-model="formData[item.prop]" :active-value="1"
|
||
:inactive-value="2" />
|
||
<!-- 日期选择器(单个/范围) -->
|
||
<el-date-picker v-else-if="item.type === 'date'" v-model="formData[item.prop]"
|
||
:type="item.dateType || 'date'" :range-separator="item.rangeSeparator || '至'"
|
||
:start-placeholder="item.startPlaceholder || '开始时间'"
|
||
:end-placeholder="item.endPlaceholder || '结束时间'"
|
||
:value-format="item.valueFormat || 'YYYY-MM-DD'" :clearable="item.clearable ?? true"
|
||
:disabled-date="item.disabledDate" :locale="'zhCn'" />
|
||
<!-- 时间选择器(单个/范围) -->
|
||
<el-time-picker v-else-if="item.type === 'time'" v-model="formData[item.prop]"
|
||
:range-separator="item.rangeSeparator || '至'"
|
||
:start-placeholder="item.startPlaceholder || '开始时间'"
|
||
:end-placeholder="item.endPlaceholder || '结束时间'"
|
||
:value-format="item.valueFormat || 'HH:mm:ss'" is-range />
|
||
<!-- 自定义插槽 -->
|
||
<slot v-else :name="item.slotName" :formData="formData" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</template>
|
||
</el-row>
|
||
</el-form>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import type { Component } from 'vue'
|
||
|
||
interface FormItem {
|
||
type: 'input' | 'select' | 'number' | 'radio' | 'slot' | 'checkbox' | 'date' | 'switch' | 'edit' | 'password' | 'time' | 'upload'
|
||
label: string
|
||
prop: string
|
||
placeholder?: string
|
||
required?: boolean
|
||
disabled?: boolean
|
||
min?: number
|
||
max?: number
|
||
slotName?: string // 自定义插槽名称
|
||
labelPosition?: 'left' | 'right' | 'top'
|
||
labelWidth?: string
|
||
class?: string
|
||
dateType?: 'date' | 'daterange' | 'monthrange' | 'yearrange' | 'datetimerange'
|
||
timeType?: 'time' | 'timerange'
|
||
rangeSeparator?: string
|
||
startPlaceholder?: string
|
||
endPlaceholder?: string
|
||
valueFormat?: string
|
||
clearable?: boolean
|
||
disabledDate?: (time: Date) => boolean
|
||
}
|
||
|
||
interface Props {
|
||
formData: Record<string, any>
|
||
formRules: FormRules
|
||
formItems: FormItem[]
|
||
optionsMap?: Record<string, Array<{
|
||
label: string | undefined
|
||
value: string | number
|
||
icon?: Component
|
||
}>>
|
||
rowGutter?: number // 新增:全局栅格间距(默认0)
|
||
colSpan?: number // 新增:全局表单项列宽(默认24)
|
||
}
|
||
|
||
const formRef = ref<FormInstance>()
|
||
const props = defineProps<Props>()
|
||
const emit = defineEmits(['form-item-change'])
|
||
|
||
const handleItemChange = (field: string, value: any) => {
|
||
emit('form-item-change', field, value)
|
||
}
|
||
// 暴露表单验证方法
|
||
defineExpose({
|
||
validate: () => formRef.value?.validate(),
|
||
resetFields: () => formRef.value?.resetFields()
|
||
})
|
||
</script> |