feat: 优化 GiForm(同步 GiDemo 更新)

This commit is contained in:
2025-02-27 22:44:04 +08:00
parent 15ae164eef
commit 47769f9ad8
32 changed files with 589 additions and 586 deletions

View File

@@ -1,15 +1,14 @@
<template>
<a-form ref="formRef" :auto-label-width="true" v-bind="options.form" :model="modelValue">
<a-grid class="w-full" :col-gap="8" v-bind="options.grid" :collapsed="collapsed">
<template v-for="(item, index) in columns" :key="item.field">
<a-form ref="formRef" v-bind="formProps" :model="modelValue" :size="props.size ?? 'large'" :layout="props.layout ?? (props.search ? 'inline' : 'horizontal')">
<a-grid class="w-full" :col-gap="8" v-bind="props.gridProps" :collapsed="collapsed">
<template v-for="item in columns" :key="item.field">
<a-grid-item
v-if="item.show !== undefined ? isShow(item) : !isHide(item)"
v-show="colVShow(index)"
v-bind="item.gridItemProps || props.options.gridItem"
:span="item.span || options.gridItem?.span"
v-bind="item.gridItemProps || defaultGridItemProps"
:span="item.span || item.gridItemProps?.span || defaultGridItemProps?.span"
>
<a-form-item
v-bind="item.formItemProps" :field="item.field" :rules="item.rules"
v-bind="item.formItemProps" :field="item.field" :rules="getFormItemRules(item)"
:disabled="isDisabled(item)"
>
<template #label>
@@ -32,31 +31,40 @@
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
>
<template v-for="(slotValue, slotKey) in item?.slots" :key="slotKey" #[slotKey]>
<template v-for="(slotValue, slotKey) in item?.slots" :key="slotKey" #[slotKey]="scope">
<template v-if="typeof slotValue === 'string'">{{ slotValue }}</template>
<component :is="slotValue" v-else></component>
<template v-else-if="slotValue">
<component :is="slotValue(scope)"></component>
</template>
</template>
</component>
</slot>
<slot v-else name="group-title">
<a-alert v-bind="item.props">{{ item.label }}</a-alert>
</slot>
<template v-for="(slotValue, slotKey) in item?.formItemSlots" :key="slotKey" #[slotKey]>
<template v-if="typeof slotValue === 'string'">{{ slotValue }}</template>
<component :is="slotValue" v-else></component>
</template>
</a-form-item>
</a-grid-item>
</template>
<a-grid-item v-if="!options.btns?.hide" :suffix="options.fold?.enable">
<a-space wrap :size="[8, 16]" style="flex-wrap: nowrap">
<a-grid-item
v-if="props.search" v-bind="defaultGridItemProps" :span="defaultGridItemProps?.span"
:suffix="props.search && props.suffix"
>
<a-space wrap>
<slot name="suffix">
<a-button type="primary" @click="emit('search')">
<template #icon><icon-search /></template>
<template #default>{{ options.btns?.searchBtnText || '搜索' }}</template>
<template #default>{{ props.searchBtnText }}</template>
</a-button>
<a-button @click="emit('reset')">
<template #icon><icon-refresh /></template>
<template #default>重置</template>
</a-button>
<a-button
v-if="options.fold?.enable" class="gi-form__fold-btn" type="text" size="mini"
v-if="!props.hideFoldBtn" class="gi-form__fold-btn" type="text" size="mini"
@click="collapsed = !collapsed"
>
<template #icon>
@@ -73,17 +81,42 @@
</template>
<script setup lang="ts">
import { cloneDeep } from 'lodash-es'
import type { ColumnsItem, Options } from './type'
import { cloneDeep, omit } from 'lodash-es'
import type { FormInstance, GridItemProps, GridProps } from '@arco-design/web-vue'
import type { ColumnItem } from './type'
interface Props {
modelValue: any
options?: Options
columns: ColumnsItem[]
layout?: FormInstance['layout']
size?: FormInstance['size']
labelColProps?: FormInstance['labelColProps']
wrapperColProps?: FormInstance['wrapperColProps']
labelAlign?: FormInstance['labelAlign']
disabled?: FormInstance['disabled']
rules?: FormInstance['rules']
autoLabelWidth?: FormInstance['autoLabelWidth']
id?: FormInstance['id']
scrollToFirstError?: FormInstance['scrollToFirstError']
// 额外自定义属性
columns: ColumnItem[]
gridProps?: GridProps
gridItemProps?: GridItemProps
search?: boolean // 搜索模式
defaultCollapsed?: boolean // 折叠按钮默认折叠
searchBtnText?: string // 搜索按钮文字
hideFoldBtn?: boolean // 隐藏展开收起按钮,在表单项少的时候手动隐藏
suffix?: boolean
}
const props = withDefaults(defineProps<Props>(), {
options: () => ({}),
autoLabelWidth: true,
scrollToFirstError: true,
defaultCollapsed: false,
search: false,
gridItemProps: { span: { xs: 24, sm: 12, xxl: 8 } },
searchBtnText: '搜索',
hideFoldBtn: false,
suffix: true,
})
const emit = defineEmits<{
@@ -92,68 +125,57 @@ const emit = defineEmits<{
(e: 'reset'): void
}>()
const options = computed(() => ({
grid: { cols: 1 },
gridItem: { span: { xs: 2, sm: 1 } },
...props.options,
}
))
const formProps = computed(() => {
const baseProps = omit(props, ['columns', 'gridProps', 'gridItemProps', 'search', 'defaultCollapsed', 'searchBtnText', 'hideFoldBtn', 'suffix', 'layout'])
return { ...baseProps }
})
const defaultGridItemProps = computed(() => {
return props.gridItemProps
})
const formRef = useTemplateRef('formRef')
const collapsed = ref(props.options.fold?.defaultCollapsed ?? false)
const collapsed = ref(props.defaultCollapsed)
const dicData: Record<string, any> = reactive({})
// col组件的显示隐藏
const colVShow = (index: number) => {
return index <= (props.options.fold?.index || 0) || (index >= (props.options.fold?.index || 0) && !collapsed.value)
}
// 组件的默认props配置
const getComponentBindProps = (item: ColumnsItem) => {
const obj: Partial<ColumnsItem['props'] & { placeholder: string }> = {}
switch (item.type) {
case 'input':
case 'input-password':
case 'input-number':
obj.allowClear = true
obj.placeholder = `请输入${item.label}`
break
case 'textarea':
obj.placeholder = `输入${item.label}`
obj.maxLength = 200
break
case 'select':
case 'cascader':
obj.allowClear = true
obj.placeholder = `请选择${item.label}`
obj.options = dicData[item.field] || item.options
break
case 'tree-select':
obj.allowClear = true
obj.placeholder = `请选择${item.label}`
obj.data = dicData[item.field] || item.data
break
case 'radio-group':
case 'checkbox-group':
obj.options = dicData[item.field] || item.options
break
case 'date-picker':
obj.placeholder = '请选择日期'
break
case 'time-picker':
obj.allowClear = true
obj.placeholder = `请选择时间`
break
}
return { ...obj, ...item.props }
const getComponentBindProps = (item: ColumnItem) => {
// 组件默认配置映射表
const ConfigMap = new Map<ColumnItem['type'], Partial<ColumnItem['props'] & { placeholder: string }>>([
['input', { allowClear: true, placeholder: `请输入${item.label}`, maxLength: 20 }],
['input-number', { placeholder: `请输入${item.label}` }],
['textarea', { allowClear: false, placeholder: `请输入${item.label}`, maxLength: 200 }],
['input-tag', { allowClear: true, placeholder: `请输入${item.label}` }],
['mention', { allowClear: true, placeholder: `请输入${item.label}` }],
['select', { allowClear: true, placeholder: `请选择${item.label}`, options: dicData[item.field] || [] }],
['tree-select', { allowClear: true, placeholder: `请选择${item.label}` }],
['cascader', { allowClear: true, placeholder: `选择${item.label}`, options: dicData[item.field] || [] }],
['radio-group', { options: dicData[item.field] || [] }],
['checkbox-group', { options: dicData[item.field] || [] }],
['date-picker', { allowClear: true, placeholder: '请选择日期' }],
['time-picker', { allowClear: true, placeholder: '请选择时间' }],
])
// 获取默认配置
const defaultProps = ConfigMap.get(item.type) || {}
// 合并默认配置和自定义配置
return { ...defaultProps, ...item.props }
}
/** 表单数据更新 */
const valueChange = (value: any, field: string) => {
emit('update:modelValue', Object.assign(props.modelValue, { [field]: value }))
}
/** 表单项校验规则 */
const getFormItemRules = (item: ColumnItem) => {
if (item.required) {
return [{ required: true, message: `${item.label}为必填项` }, ...(Array.isArray(item.rules) ? item.rules : [])]
}
return item.rules
}
/** 显示表单项 */
const isShow = (item: ColumnsItem) => {
const isShow = (item: ColumnItem) => {
if (typeof item.show === 'boolean') return item.show
if (typeof item.show === 'function') {
return item.show(props.modelValue)
@@ -161,7 +183,7 @@ const isShow = (item: ColumnsItem) => {
}
/** 隐藏表单项 */
const isHide = (item: ColumnsItem) => {
const isHide = (item: ColumnItem) => {
if (item.hide === undefined) return false
if (typeof item.hide === 'boolean') return item.hide
if (typeof item.hide === 'function') {
@@ -170,7 +192,7 @@ const isHide = (item: ColumnsItem) => {
}
/** 禁用表单项 */
const isDisabled = (item: ColumnsItem) => {
const isDisabled = (item: ColumnItem) => {
if (item.disabled === undefined) return false
if (typeof item.disabled === 'boolean') return item.disabled
if (typeof item.disabled === 'function') {
@@ -189,7 +211,7 @@ props.columns.forEach((item) => {
// 先找出有级联的项
// 如果这个字段改变了值那么就找出它的cascader属性对应的字段项去请求里面的request
const hasCascaderColumns: ColumnsItem[] = []
const hasCascaderColumns: ColumnItem[] = []
props.columns.forEach((item) => {
const arr = hasCascaderColumns.map((i) => i.field)
if (item.cascader?.length && !arr.includes(item.field)) {
@@ -211,12 +233,12 @@ watch(cloneForm as any, (newVal, oldVal) => {
i.request(props.modelValue).then((res) => {
dicData[i.field] = i.resultFormat ? i.resultFormat(res) : res.data
if (!dicData[i.field].map((i: any) => i.value).includes(props.modelValue[i.field])) {
emit('update:modelValue', Object.assign(props.modelValue, { [i.field]: '' }))
emit('update:modelValue', Object.assign(props.modelValue, { [i.field]: Array.isArray(props.modelValue[i.field]) ? [] : '' }))
}
})
} else if (i.request && !newVal[item.field]) {
dicData[i.field] = []
emit('update:modelValue', Object.assign(props.modelValue, { [i.field]: '' }))
emit('update:modelValue', Object.assign(props.modelValue, { [i.field]: Array.isArray(props.modelValue[i.field]) ? [] : '' }))
}
})
}
@@ -226,10 +248,7 @@ watch(cloneForm as any, (newVal, oldVal) => {
defineExpose({ formRef })
</script>
<style scoped lang="scss">
:deep(.arco-form-item-layout-inline) {
margin-right: 0;
}
<style lang="scss" scoped>
.gi-form__fold-btn {
padding: 0 5px;
}