refactor: 优化 GiForm、GiEditTable(同步 GiDemo 更新)

This commit is contained in:
2025-03-21 21:14:39 +08:00
parent 1d77d8c51a
commit 436cc6bdfc
7 changed files with 113 additions and 51 deletions

View File

@@ -45,15 +45,18 @@ import type { TableColumnData, TableData } from '@arco-design/web-vue'
import type { ColumnItem, Disabled } from './type' import type { ColumnItem, Disabled } from './type'
defineOptions({ name: 'GiEditTable', inheritAttrs: false }) defineOptions({ name: 'GiEditTable', inheritAttrs: false })
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
cellDisabled: false, cellDisabled: false,
}) })
/** 事件定义 */
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'tr-dblclick', value: { record: any, rowIndex: number }): void (e: 'tr-dblclick', value: { record: any, rowIndex: number }): void
(e: 'td-dblclick', value: { record: any, rowIndex: number, column: TableColumnData }): void (e: 'td-dblclick', value: { record: any, rowIndex: number, column: TableColumnData }): void
}>() }>()
/** 插槽定义 */
defineSlots<{ defineSlots<{
[propsName: string]: (props: { record: T, rowIndex: number, column: ColumnItem }) => void [propsName: string]: (props: { record: T, rowIndex: number, column: ColumnItem }) => void
}>() }>()
@@ -66,36 +69,61 @@ interface Props {
const attrs = useAttrs() const attrs = useAttrs()
/** 表单数据 */
const form = computed(() => ({ tableData: props.data })) const form = computed(() => ({ tableData: props.data }))
/** 表单实例 */
const formRef = useTemplateRef('formRef') const formRef = useTemplateRef('formRef')
/** 获取表头必填星号*样式 */
const headerCellClass = (col: ColumnItem) => { const headerCellClass = (col: ColumnItem) => {
return col.required ? 'gi_column_require' : '' return col.required ? 'gi_column_require' : ''
} }
const getComponentBindProps = (col: ColumnItem) => { /** 静态配置 */
// 组件默认配置映射表 const STATIC_PROPS = new Map<ColumnItem['type'], Partial<ColumnItem['props']>>([
const ConfigMap = new Map<ColumnItem['type'], Partial<Omit<ColumnItem['props'], 'placeholder'> & { placeholder?: string | string[] }>>([ ['input', { allowClear: true, maxLength: 20 }],
['input', { allowClear: true, placeholder: `请输入${col.title}`, maxLength: 50 }], ['textarea', { allowClear: false, maxLength: 200 }],
['input-number', { placeholder: `请输入${col.title}` }], ['input-tag', { allowClear: true }],
['textarea', { allowClear: false, placeholder: `请填写${col.title}`, maxLength: 200 }], ['mention', { allowClear: true }],
['input-tag', { allowClear: true, placeholder: `请输入${col.title}` }], ['select', { allowClear: true }],
['mention', { allowClear: true, placeholder: `请输入${col.title}` }], ['tree-select', { allowClear: true }],
['select', { allowClear: true, placeholder: `请选择${col.title}` }], ['cascader', { allowClear: true }],
['tree-select', { allowClear: true, placeholder: `请选择${col.title}` }], ['date-picker', { allowClear: true }],
['cascader', { allowClear: true, placeholder: `请选择${col.title}` }], ['time-picker', { allowClear: true }],
['radio-group', {}], ])
['checkbox-group', {}],
['date-picker', { allowClear: true, placeholder: '请选择日期' }], /** 获取组件默认占位 */
['time-picker', { allowClear: true, placeholder: '请选择时间' }], const getPlaceholder = (item: ColumnItem) => {
]) if (!item.type) return undefined
// 获取默认配置 if (['input', 'input-tag', 'mention'].includes(item.type)) {
const defaultProps = ConfigMap.get(col.type) || {} return `请输入${item.title}`
// 合并默认配置和自定义配置 }
return { ...defaultProps, ...col.props } if (['textarea'].includes(item.type)) {
return `请填写${item.title}`
}
if (['select', 'tree-select', 'cascader'].includes(item.type)) {
return `请选择${item.title}`
}
if (['date-picker'].includes(item.type)) {
return '请选择日期'
}
if (['time-picker'].includes(item.type)) {
return '请选择时间'
}
return undefined
} }
/** 获取组件默认配置 */
const getComponentBindProps = (col: ColumnItem) => {
return {
...STATIC_PROPS.get(col.type) || {},
placeholder: getPlaceholder(col),
...col.props,
}
}
/** 获取组件默认规则 */
const getRuleMessage = (col: ColumnItem) => { const getRuleMessage = (col: ColumnItem) => {
if (['input', 'input-number', 'input-tag', 'mention'].includes(col.type ?? '')) { if (['input', 'input-number', 'input-tag', 'mention'].includes(col.type ?? '')) {
return `请输入${col.title}` return `请输入${col.title}`
@@ -106,12 +134,14 @@ const getRuleMessage = (col: ColumnItem) => {
return `请选择${col.title}` return `请选择${col.title}`
} }
/** 判断是否禁用 */
const isDisabled: Props['cellDisabled'] = (p) => { const isDisabled: Props['cellDisabled'] = (p) => {
if (typeof props?.cellDisabled === 'boolean') return props.cellDisabled if (typeof props?.cellDisabled === 'boolean') return props.cellDisabled
if (typeof props?.cellDisabled === 'function') return props.cellDisabled(p) if (typeof props?.cellDisabled === 'function') return props.cellDisabled(p)
return false return false
} }
/** 暴露表单实例 */
defineExpose({ formRef }) defineExpose({ formRef })
</script> </script>

View File

@@ -29,7 +29,7 @@
<component <component
:is="`a-${item.type}`" v-else v-bind="getComponentBindProps(item)" :is="`a-${item.type}`" v-else v-bind="getComponentBindProps(item)"
:model-value="modelValue[item.field as keyof typeof modelValue]" :model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)" @update:model-value="updateValue($event, item.field)"
> >
<template v-for="(slotValue, slotKey) in item?.slots" :key="slotKey" #[slotKey]="scope"> <template v-for="(slotValue, slotKey) in item?.slots" :key="slotKey" #[slotKey]="scope">
<template v-if="typeof slotValue === 'string'">{{ slotValue }}</template> <template v-if="typeof slotValue === 'string'">{{ slotValue }}</template>
@@ -50,8 +50,9 @@
</a-grid-item> </a-grid-item>
</template> </template>
<a-grid-item <a-grid-item
v-if="props.search" v-bind="defaultGridItemProps" :span="defaultGridItemProps?.span" v-if="props.search" :key="!props.suffix ? String(collapsed) : undefined"
:suffix="props.search && props.suffix" v-bind="defaultGridItemProps" :span="defaultGridItemProps?.span"
:suffix="props.search && (props.suffix || (!props.suffix && collapsed))"
> >
<a-space wrap> <a-space wrap>
<slot name="suffix"> <slot name="suffix">
@@ -135,35 +136,66 @@ const defaultGridItemProps = computed(() => {
}) })
const formRef = useTemplateRef('formRef') const formRef = useTemplateRef('formRef')
/** 是否折叠 */
const collapsed = ref(props.defaultCollapsed) const collapsed = ref(props.defaultCollapsed)
/** 数据字典 */
const dicData: Record<string, any> = reactive({}) const dicData: Record<string, any> = reactive({})
// 组件的默认props配置 /** 静态配置 */
const STATIC_PROPS = new Map<ColumnItem['type'], Partial<ColumnItem['props']>>([
['input', { allowClear: true, maxLength: 255, showWordLimit: !props.search }],
['input-password', { allowClear: true, showWordLimit: !props.search }],
['textarea', { allowClear: false, maxLength: 200, showWordLimit: !props.search, autoSize: { minRows: 3, maxRows: 5 } }],
['input-tag', { allowClear: true }],
['mention', { allowClear: true }],
['select', { allowClear: true }],
['tree-select', { allowClear: true }],
['cascader', { allowClear: true }],
['date-picker', { allowClear: true }],
['time-picker', { allowClear: true }],
])
/** 获取组件默认占位 */
const getPlaceholder = (item: ColumnItem) => {
if (!item.type) return undefined
if (['input', 'input-number', 'input-password', 'textarea', 'input-tag', 'mention'].includes(item.type)) {
return `请输入${item.label}`
}
if (['select', 'tree-select', 'cascader'].includes(item.type)) {
return `请选择${item.label}`
}
if (['date-picker'].includes(item.type)) {
return '请选择日期'
}
if (['time-picker'].includes(item.type)) {
return '请选择时间'
}
return undefined
}
/** 获取选项数据 */
const getOptions = (item: ColumnItem): any[] | undefined => {
if (!item.type) return undefined
/** 需要选项数据的组件类型 */
const arr = ['select', 'tree-select', 'cascader', 'radio-group', 'checkbox-group']
if (arr.includes(item.type)) {
return dicData[item.field] || []
}
return undefined
}
/** 获取组件绑定属性 */
const getComponentBindProps = (item: ColumnItem) => { const getComponentBindProps = (item: ColumnItem) => {
// 组件默认配置映射表 return {
const ConfigMap = new Map<ColumnItem['type'], Partial<ColumnItem['props'] & { placeholder: string }>>([ ...STATIC_PROPS.get(item.type) || {},
['input', { allowClear: true, placeholder: `请输入${item.label}`, maxLength: 255, showWordLimit: !props.search }], placeholder: getPlaceholder(item),
['input-password', { placeholder: `请输入${item.label}`, showWordLimit: !props.search }], options: getOptions(item),
['input-number', { placeholder: `请输入${item.label}` }], ...item.props,
['textarea', { allowClear: false, placeholder: `请输入${item.label}`, maxLength: 200, showWordLimit: !props.search, autoSize: { minRows: 3, maxRows: 5 } }], }
['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) => { const updateValue = (value: any, field: string) => {
emit('update:modelValue', Object.assign(props.modelValue, { [field]: value })) emit('update:modelValue', Object.assign(props.modelValue, { [field]: value }))
} }

View File

@@ -30,7 +30,7 @@
<!-- 列拖拽排序区域 --> <!-- 列拖拽排序区域 -->
<div class="gi-table__draggable"> <div class="gi-table__draggable">
<VueDraggable v-model="localColumns" @end="handleDragEnd"> <VueDraggable v-model="localColumns" :animation="150" @end="handleDragEnd">
<div <div
v-for="item in localColumns" v-for="item in localColumns"
:key="item.key" :key="item.key"

View File

@@ -11,6 +11,7 @@ declare module 'vue' {
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default'] Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
CellCopy: typeof import('./../components/CellCopy/index.vue')['default'] CellCopy: typeof import('./../components/CellCopy/index.vue')['default']
Chart: typeof import('./../components/Chart/index.vue')['default'] Chart: typeof import('./../components/Chart/index.vue')['default']
ColumnSetting: typeof import('./../components/GiTable/src/components/ColumnSetting.vue')['default']
CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default'] CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default']
CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default'] CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default']
DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default'] DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default']
@@ -58,7 +59,6 @@ declare module 'vue' {
SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default'] SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default']
SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default'] SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default']
TextCopy: typeof import('./../components/TextCopy/index.vue')['default'] TextCopy: typeof import('./../components/TextCopy/index.vue')['default']
ToggleDark: typeof import('./../components/ToggleDark/index.vue')['default']
UserSelect: typeof import('./../components/UserSelect/index.vue')['default'] UserSelect: typeof import('./../components/UserSelect/index.vue')['default']
Verify: typeof import('./../components/Verify/index.vue')['default'] Verify: typeof import('./../components/Verify/index.vue')['default']
VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default'] VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default']

View File

@@ -72,7 +72,7 @@ const {
const columns: TableInstance['columns'] = [ const columns: TableInstance['columns'] = [
{ {
title: '序号', title: '序号',
width: 66, minWidth: 66,
align: 'center', align: 'center',
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize), render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
}, },

View File

@@ -83,7 +83,7 @@ const {
const columns: TableInstance['columns'] = [ const columns: TableInstance['columns'] = [
{ {
title: '序号', title: '序号',
width: 66, minWidth: 66,
align: 'center', align: 'center',
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize), render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
}, },

View File

@@ -40,7 +40,7 @@
<GiIconSelector v-model="form.icon" /> <GiIconSelector v-model="form.icon" />
</a-form-item> </a-form-item>
<a-form-item v-else label="权限标识" field="permission"> <a-form-item v-else label="权限标识" field="permission">
<a-input v-model.trim="form.permission" placeholder="system:user:add" allow-clear /> <a-input v-model.trim="form.permission" placeholder="system:user:add" :max-length="100" show-word-limit allow-clear />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
@@ -80,7 +80,7 @@
</a-col> </a-col>
<a-col v-bind="colProps"> <a-col v-bind="colProps">
<a-form-item v-if="form.type === 2" label="权限标识" field="permission"> <a-form-item v-if="form.type === 2" label="权限标识" field="permission">
<a-input v-model.trim="form.permission" placeholder="system:user:add" allow-clear /> <a-input v-model.trim="form.permission" placeholder="system:user:add" :max-length="100" show-word-limit allow-clear />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>