123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- <template>
- <el-form v-bind="ElFormProps" ref="formRef" class="base-form" @submit.prevent>
- <template v-for="formGroup in formGroups" :key="formGroup.groupKey">
- <component :is="props.transition ? ElCollapseTransition : LessRenderComponent">
- <custom-block v-show="!formGroup.hidden" :title="formGroup.groupTitle">
- <template v-for="row in formGroup.rows" :key="row.rowKey">
- <component :is="props.transition ? ElCollapseTransition : LessRenderComponent">
- <el-row v-show="!row.rowProp.hidden" v-bind="row.rowProp">
- <template v-for="col in row.cols" :key="col.colKey">
- <el-col v-show="!col.hidden" v-bind="col.colProp">
- <el-form-item
- v-bind="
- omit(col, ['colProp', 'slot', 'rowKey', 'colIndex', 'slotType', 'slotName', 'itemDescription'])
- "
- >
- <template
- v-for="[usedItemSlotName, itemSlotName] in getSlotKeys(col, 'item', ['label', 'error'])"
- #[itemSlotName]
- >
- <slot
- :name="usedItemSlotName"
- v-bind="{ item: col, model: props.model, index: col.colIndex }"
- ></slot>
- </template>
- <template #default>
- <slot
- :name="getSlotKeys(col, 'item', ['default'])[0]?.[0]"
- v-bind="{ item: col, model: props.model, index: col.colIndex }"
- >
- <component
- :is="FormItemComponentMap[col.slotType]"
- v-if="col.slotType"
- v-bind="Object.assign({}, FormItemComponentDefault[col.slotType], col.slot)"
- v-model="getFieldValue(props.model, col.prop).value"
- >
- <template
- v-for="[usedSlotName, slotName] in getSlotKeys(
- col,
- col.slotType,
- FormItemComponentSlots[col.slotType]
- )"
- #[slotName]
- >
- <slot
- :name="usedSlotName"
- v-bind="{ item: col, model: props.model, index: col.colIndex }"
- ></slot>
- </template>
- </component>
- </slot>
- </template>
- </el-form-item>
- </el-col>
- <el-row v-if="col.itemDescription?.description || getSlotKeys(col, 'item', ['description'])[0]?.[0]">
- <slot
- :name="getSlotKeys(col, 'item', ['description'])[0]?.[0]"
- v-bind="{ item: col, model: props.model, index: col.colIndex }"
- >
- <el-form-item label-width="0">
- <span
- v-if="col.itemDescription?.description"
- :key="getSlotKeys(col, 'item', ['description'])[0]?.[0]"
- class="inline-flex items-center form-item-description"
- :class="{ 'required-asterisk': col.itemDescription?.requiredAsterisk !== false }"
- >
- {{ col.itemDescription?.description }}
- </span>
- </el-form-item>
- </slot>
- </el-row>
- </template>
- </el-row>
- </component>
- </template>
- </custom-block>
- </component>
- </template>
- <slot></slot>
- </el-form>
- </template>
- <script setup lang="tsx" name="EpForm">
- import { computed, useSlots, shallowRef, markRaw, defineComponent } from 'vue'
- import {
- ElForm,
- ElFormItem,
- ElInput,
- ElInputNumber,
- ElAutocomplete,
- ElSwitch,
- ElRadio,
- ElDatePicker,
- ElRow,
- ElCol,
- ElCollapseTransition,
- } from 'element-plus'
- import CustomBlock from '@/components/common/CustomBlock.vue'
- import BaseSelect from '@/components/element/BaseSelect.vue'
- import BaseCheckBox from '@/components/element/BaseCheckBox.vue'
- import BaseRadio from '@/components/element/BaseRadio.vue'
- import { get, set, omit } from 'lodash-es'
- import type { EpFormItem, BaseFormProp, FormSupportComponentMap, FormGroup, EpFormRows } from 'global-type'
- const LessRenderComponent = defineComponent({
- name: 'LessRender',
- render() {
- const slots = useSlots()
- return slots?.default?.()
- },
- })
- interface BaseEpForm extends BaseFormProp {
- model: Record<string, unknown>
- rules?: Required<BaseFormProp>['rules']
- items?: EpFormItem[]
- rows?: EpFormRows
- groups?: FormGroup[]
- transition?: boolean
- }
- type RequiredGroup = Required<ExtractArrayValue<Required<BaseEpForm>['groups']>>
- type FormatterFormItem = AssignKeys<Required<BaseEpForm>['items'][0], { colIndex: number; colKey: string | number }>
- interface FormatterFormGroup extends Omit<RequiredGroup, 'rowKeys'> {
- rows: {
- rowKey: string | number
- rowProp: Omit<ExtractRecordValue<Required<BaseEpForm>['rows']>, 'rowKey'>
- cols: FormatterFormItem[]
- }[]
- }
- const FormItemComponentMap = markRaw<FormSupportComponentMap>({
- autocomplete: ElAutocomplete,
- input: ElInput,
- inputNumber: ElInputNumber,
- checkbox: BaseCheckBox,
- radio: BaseRadio,
- select: BaseSelect,
- date: ElDatePicker,
- dateTime: ElDatePicker,
- switch: ElSwitch,
- })
- const FormItemComponentSlots = markRaw<Record<keyof FormSupportComponentMap, string[]>>({
- autocomplete: ['prefix', 'suffix', 'prepend', 'append', 'default'],
- input: ['prefix', 'suffix', 'prepend', 'append'],
- inputNumber: [],
- checkbox: ['default'],
- radio: ['default'],
- select: ['prefix', 'empty', 'default'],
- switch: [],
- date: ['range-separator', 'default'],
- dateTime: ['range-separator', 'default'],
- })
- const FormItemComponentDefault = markRaw<Partial<Record<keyof FormSupportComponentMap, EpFormItem['slot']>>>({
- inputNumber: {
- controls: false,
- },
- })
- const props = defineProps<BaseEpForm>()
- const ElFormProps = computed(() => {
- const { items, groups, rows, ...formAttr } = props
- return formAttr
- })
- const formGroups = computed<FormatterFormGroup[]>(() => {
- const rows = props.items?.reduce((rowMap, formItem, index) => {
- const itemRowKey = formItem.rowKey || 'row-' + (index + 1)
- rowMap[itemRowKey] ??= { rowKey: itemRowKey, rowProp: props.rows?.[itemRowKey] || {}, groupUsed: false, cols: [] }
- rowMap[itemRowKey].cols.push({ ...formItem, colKey: `col-${rowMap[itemRowKey].cols.length}`, colIndex: index })
- return rowMap
- }, {} as Record<string | number, FormatterFormGroup['rows'][0] & { groupUsed: boolean }>)
- const groups = props.groups?.map((groupConfig, index) => {
- let groupRows: FormatterFormGroup['rows'] = []
- groupConfig.rowKeys?.forEach((rowKey) => {
- if (rows?.[rowKey]) {
- rows[rowKey].groupUsed = true
- groupRows.push(rows[rowKey])
- }
- })
- const groupInfo: FormatterFormGroup = {
- hidden: groupConfig.hidden ?? false,
- groupKey: groupConfig.groupKey || 'group-' + (index + 1),
- groupTitle: groupConfig.groupTitle || '',
- rows: groupRows,
- }
- return groupInfo
- })
- return (groups || []).concat({
- rows: Object.values(rows || {}).filter((row) => !row.groupUsed),
- groupKey: 'default-group',
- groupTitle: '',
- hidden: false,
- })
- })
- const slots = useSlots()
- function getSlotKeys(formItem: FormatterFormItem, itemName: string, slotNames: string[]) {
- const tag = (formItem.slotName || formItem.prop || formItem.colIndex) ?? undefined
- const slotKey = slotNames.map((slotName) => [
- ['form', itemName, tag, slotName === 'default' ? '' : slotName].filter((t) => t !== '').join('-'),
- slotName,
- ])
- return slotKey.filter(([slotName]) => typeof slots[slotName] === 'function')
- }
- function getFieldValue(prop: Record<string, any>, key?: string | string[]) {
- return {
- get value() {
- return key ? get(prop, key) : undefined
- },
- set value(val: any) {
- key ? set(prop, key, val) : void 0
- },
- }
- }
- const formRef = shallowRef<InstanceType<typeof ElForm>>()
- defineExpose({ formRef })
- </script>
- <style scoped lang="scss">
- .base-form {
- :deep(.el-select) {
- width: 100%;
- }
- &.el-form--inline :deep(.el-form-item) {
- align-items: center;
- margin-bottom: 0;
- .el-form-item__content {
- word-break: break-all;
- }
- }
- .form-item-description {
- margin-left: 40px;
- color: $FormItemDescriptionColor;
- font-size: $SmallFont;
- &.required-asterisk:before {
- content: '*';
- color: $DangerColor;
- margin-right: 0.285em;
- }
- }
- }
- </style>
|