mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-11-11 12:57:10 +08:00
feat: 优化 GiForm(同步 GiDemo 更新)
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
<template>
|
||||
<div class="gi-edit-table">
|
||||
<a-form ref="formRef" :model="form">
|
||||
<a-form ref="formRef" scroll-to-first-error :model="form">
|
||||
<a-table :data="form.tableData" :bordered="{ cell: true }" :pagination="false" v-bind="attrs">
|
||||
<template #columns>
|
||||
<a-table-column
|
||||
v-for="col in props.columns" :key="col.dataIndex" :title="col.title"
|
||||
:data-index="col.dataIndex" :header-cell-class="headerCellClass(col)" v-bind="col.columnProps"
|
||||
v-for="col in props.columns" :key="col.dataIndex" :data-index="col.dataIndex"
|
||||
:header-cell-class="headerCellClass(col)" v-bind="col.columnProps" :title="col.title"
|
||||
>
|
||||
<template #title>
|
||||
<component :is="col?.columnProps?.title" v-if="typeof col?.columnProps?.title === 'function'"></component>
|
||||
<template v-else>{{ col?.columnProps?.title || col.title }}</template>
|
||||
</template>
|
||||
<template #cell="{ record, rowIndex, column }">
|
||||
<a-form-item
|
||||
:field="`tableData[${rowIndex}].${col.dataIndex}`" :label-col-style="{ display: 'none' }"
|
||||
@@ -25,21 +29,31 @@
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
<template #tr="{ record, rowIndex }">
|
||||
<tr class="gi-edit-table-tr" @dblclick="emit('tr-dblclick', { record, rowIndex })"></tr>
|
||||
</template>
|
||||
<template #td="{ record, column, rowIndex }">
|
||||
<td class="gi-edit-table-td" @dblclick="emit('td-dblclick', { record, column, rowIndex })"></td>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts' setup generic="T extends TableData">
|
||||
import type { TableData } from '@arco-design/web-vue'
|
||||
import type { TableColumnData, TableData } from '@arco-design/web-vue'
|
||||
import type { ColumnItem, Disabled } from './type'
|
||||
|
||||
defineOptions({ name: 'GiEditTable', inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
cellDisabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'tr-dblclick', value: { record: any, rowIndex: number }): void
|
||||
(e: 'td-dblclick', value: { record: any, rowIndex: number, column: TableColumnData }): void
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
[propsName: string]: (props: { record: T, rowIndex: number, column: ColumnItem }) => void
|
||||
}>()
|
||||
@@ -55,64 +69,41 @@ const attrs = useAttrs()
|
||||
const form = computed(() => ({ tableData: props.data }))
|
||||
|
||||
const formRef = useTemplateRef('formRef')
|
||||
|
||||
const headerCellClass = (col: ColumnItem) => {
|
||||
return col.required ? 'gi_column_require' : ''
|
||||
}
|
||||
|
||||
const getComponentBindProps = (col: ColumnItem) => {
|
||||
const obj: Partial<ColumnItem['props'] & { placeholder: string }> = {}
|
||||
if (col.type === 'input') {
|
||||
obj.allowClear = true
|
||||
obj.placeholder = `请输入${col.title}`
|
||||
obj.maxLength = 50
|
||||
}
|
||||
if (col.type === 'input-number') {
|
||||
obj.placeholder = `请输入${col.title}`
|
||||
}
|
||||
if (col.type === 'textarea') {
|
||||
obj.allowClear = true
|
||||
obj.placeholder = `填写${col.title}`
|
||||
obj.maxLength = 200
|
||||
}
|
||||
if (col.type === 'select') {
|
||||
obj.allowClear = true
|
||||
obj.placeholder = `请选择${col.title}`
|
||||
}
|
||||
if (col.type === 'cascader') {
|
||||
obj.allowClear = true
|
||||
obj.placeholder = `请选择${col.title}`
|
||||
}
|
||||
if (col.type === 'tree-select') {
|
||||
obj.allowClear = true
|
||||
obj.placeholder = `请选择${col.title}`
|
||||
}
|
||||
if (col.type === 'date-picker') {
|
||||
obj.placeholder = '请选择日期'
|
||||
}
|
||||
if (col.type === 'time-picker') {
|
||||
obj.allowClear = true
|
||||
obj.placeholder = `请选择时间`
|
||||
}
|
||||
return { ...obj, ...col.props }
|
||||
// 组件默认配置映射表
|
||||
const ConfigMap = new Map<ColumnItem['type'], Partial<Omit<ColumnItem['props'], 'placeholder'> & { placeholder?: string | string[] }>>([
|
||||
['input', { allowClear: true, placeholder: `请输入${col.title}`, maxLength: 50 }],
|
||||
['input-number', { placeholder: `请输入${col.title}` }],
|
||||
['textarea', { allowClear: false, placeholder: `请填写${col.title}`, maxLength: 200 }],
|
||||
['input-tag', { allowClear: true, placeholder: `请输入${col.title}` }],
|
||||
['mention', { allowClear: true, placeholder: `请输入${col.title}` }],
|
||||
['select', { allowClear: true, placeholder: `请选择${col.title}` }],
|
||||
['tree-select', { allowClear: true, placeholder: `请选择${col.title}` }],
|
||||
['cascader', { allowClear: true, placeholder: `请选择${col.title}` }],
|
||||
['radio-group', {}],
|
||||
['checkbox-group', {}],
|
||||
['date-picker', { allowClear: true, placeholder: '请选择日期' }],
|
||||
['time-picker', { allowClear: true, placeholder: '请选择时间' }],
|
||||
])
|
||||
// 获取默认配置
|
||||
const defaultProps = ConfigMap.get(col.type) || {}
|
||||
// 合并默认配置和自定义配置
|
||||
return { ...defaultProps, ...col.props }
|
||||
}
|
||||
|
||||
const getRuleMessage = (col: ColumnItem) => {
|
||||
if (['input', 'input-number'].includes(col.type ?? '')) {
|
||||
if (['input', 'input-number', 'input-tag', 'mention'].includes(col.type ?? '')) {
|
||||
return `请输入${col.title}`
|
||||
}
|
||||
if (['textarea'].includes(col.type ?? '')) {
|
||||
return `请填写${col.title}`
|
||||
}
|
||||
if (['select', 'cascader', 'tree-select'].includes(col.type ?? '')) {
|
||||
return `请选择${col.title}`
|
||||
}
|
||||
if (['date-picker'].includes(col.type ?? '')) {
|
||||
return `请选择日期`
|
||||
}
|
||||
if (['time-picker'].includes(col.type ?? '')) {
|
||||
return `请选择时间`
|
||||
}
|
||||
return ''
|
||||
return `请选择${col.title}`
|
||||
}
|
||||
|
||||
const isDisabled: Props['cellDisabled'] = (p) => {
|
||||
@@ -120,6 +111,7 @@ const isDisabled: Props['cellDisabled'] = (p) => {
|
||||
if (typeof props?.cellDisabled === 'function') return props.cellDisabled(p)
|
||||
return false
|
||||
}
|
||||
|
||||
defineExpose({ formRef })
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,49 +1,69 @@
|
||||
import type * as A from '@arco-design/web-vue'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export type ColumnItemType =
|
||||
| 'input'
|
||||
| 'input-number'
|
||||
| 'input-tag'
|
||||
| 'textarea'
|
||||
| 'select'
|
||||
| 'tree-select'
|
||||
| 'radio-group'
|
||||
| 'checkbox-group'
|
||||
| 'date-picker'
|
||||
| 'year-picker'
|
||||
| 'quarter-picker'
|
||||
| 'month-picker'
|
||||
| 'week-picker'
|
||||
| 'time-picker'
|
||||
| 'range-picker'
|
||||
| 'color-picker'
|
||||
| 'rate'
|
||||
| 'switch'
|
||||
| 'slider'
|
||||
| 'cascader'
|
||||
| 'upload'
|
||||
| 'auto-complete'
|
||||
| 'mention'
|
||||
| ''
|
||||
|
||||
export type ComponentProps =
|
||||
& A.InputInstance['$props']
|
||||
& A.InputNumberInstance['$props']
|
||||
& A.InputTagInstance['$props']
|
||||
& A.TextareaInstance['$props']
|
||||
& A.SelectInstance['$props']
|
||||
& A.TreeSelectInstance['$props']
|
||||
& A.RadioGroupInstance['$props']
|
||||
& A.CheckboxGroupInstance['$props']
|
||||
& A.DatePickerInstance['$props']
|
||||
& A.YearPickerInstance['$props']
|
||||
& A.QuarterPickerInstance['$props']
|
||||
& A.MonthPickerInstance['$props']
|
||||
& A.WeekPickerInstance['$props']
|
||||
& A.TimePickerInstance['$props']
|
||||
& A.RangePickerInstance['$props']
|
||||
& A.ColorPickerInstance['$props']
|
||||
& A.RateInstance['$props']
|
||||
& A.SwitchInstance['$props']
|
||||
& A.SliderInstance['$props']
|
||||
& A.CascaderInstance['$props']
|
||||
& A.UploadInstance['$props']
|
||||
& A.AutoCompleteInstance['$props']
|
||||
& A.MentionInstance['$props']
|
||||
|
||||
interface ColumnItemProps extends Partial<Omit<ComponentProps, 'placeholder'>> {
|
||||
placeholder?: string | string[]
|
||||
}
|
||||
|
||||
export interface ColumnItem {
|
||||
type?:
|
||||
| 'input'
|
||||
| 'select'
|
||||
| 'radio-group'
|
||||
| 'checkbox-group'
|
||||
| 'textarea'
|
||||
| 'date-picker'
|
||||
| 'year-picker'
|
||||
| 'quarter-picker'
|
||||
| 'week-picker'
|
||||
| 'range-picker'
|
||||
| 'month-picker'
|
||||
| 'time-picker'
|
||||
| 'color-picker'
|
||||
| 'input-number'
|
||||
| 'rate'
|
||||
| 'switch'
|
||||
| 'slider'
|
||||
| 'cascader'
|
||||
| 'tree-select'
|
||||
| 'upload'
|
||||
| ''
|
||||
type?: ColumnItemType
|
||||
title: string
|
||||
dataIndex: string
|
||||
required?: boolean
|
||||
rules?: A.FormItemInstance['$props']['rules'] // 表单校验规则
|
||||
props?:
|
||||
& A.InputInstance['$props']
|
||||
& A.SelectInstance['$props']
|
||||
& A.TextareaInstance['$props']
|
||||
& A.DatePickerInstance['$props']
|
||||
& A.TimePickerInstance['$props']
|
||||
& A.RadioGroupInstance['$props']
|
||||
& A.CheckboxGroupInstance['$props']
|
||||
& A.InputNumberInstance['$props']
|
||||
& A.RateInstance['$props']
|
||||
& A.SwitchInstance['$props']
|
||||
& A.SliderInstance['$props']
|
||||
& A.CascaderInstance['$props']
|
||||
& A.TreeSelectInstance['$props']
|
||||
& A.UploadInstance['$props']
|
||||
& A.AlertInstance['$props']
|
||||
columnProps?: A.TableColumnInstance['$props']
|
||||
props?: ColumnItemProps
|
||||
columnProps?: Partial<Omit<A.TableColumnInstance['$props'], 'title'>> & { title?: string | (() => VNode) }
|
||||
formItemProps?: A.FormItemInstance['$props']
|
||||
slotName?: string
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import GiForm from './src/GiForm.vue'
|
||||
import { useGiForm } from './src/hooks'
|
||||
|
||||
export type * from './src/type'
|
||||
export { GiForm, useGiForm }
|
||||
export { GiForm }
|
||||
export default GiForm
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { reactive } from 'vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { Columns, ColumnsItem, ColumnsItemPropsKey } from './type'
|
||||
|
||||
export function useGiForm(initValue: Columns) {
|
||||
const getInitValue = () => cloneDeep(initValue)
|
||||
|
||||
const columns = reactive(getInitValue())
|
||||
|
||||
const resetColumns = () => {
|
||||
Object.assign(columns, getInitValue())
|
||||
}
|
||||
|
||||
const setValue = <T>(field: string, key: keyof ColumnsItem, value: T) => {
|
||||
if (!columns.length) return
|
||||
const obj = columns.find((i) => i.field === field)
|
||||
if (obj) {
|
||||
obj[key] = value as never
|
||||
} else {
|
||||
Message.warning(`没有这个field属性值-${field},请检查!`)
|
||||
}
|
||||
}
|
||||
|
||||
const setPropsValue = <T>(field: string, key: ColumnsItemPropsKey, value: T) => {
|
||||
if (!columns.length) return
|
||||
const obj = columns.find((i) => i.field === field)
|
||||
if (obj) {
|
||||
if (!obj.props) {
|
||||
obj.props = {}
|
||||
}
|
||||
obj.props[key as keyof ColumnsItem['props']] = value as never
|
||||
} else {
|
||||
Message.warning(`没有这个field属性值-${field},请检查!`)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/** 配置项 */
|
||||
columns,
|
||||
/** 重置 columns */
|
||||
resetColumns,
|
||||
/** 设置 columns 某个对象属性的值 */
|
||||
setValue,
|
||||
/** 设置 columns.props 某个属性的值 */
|
||||
setPropsValue,
|
||||
}
|
||||
}
|
||||
@@ -1,124 +1,215 @@
|
||||
import type * as A from '@arco-design/web-vue'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export type FormType =
|
||||
export type ColumnItemType =
|
||||
| 'input'
|
||||
| 'input-password'
|
||||
| 'input-number'
|
||||
| 'input-tag'
|
||||
| 'textarea'
|
||||
| 'select'
|
||||
| 'tree-select'
|
||||
| 'radio-group'
|
||||
| 'checkbox-group'
|
||||
| 'textarea'
|
||||
| 'date-picker'
|
||||
| 'year-picker'
|
||||
| 'quarter-picker'
|
||||
| 'week-picker'
|
||||
| 'range-picker'
|
||||
| 'month-picker'
|
||||
| 'week-picker'
|
||||
| 'time-picker'
|
||||
| 'range-picker'
|
||||
| 'color-picker'
|
||||
| 'rate'
|
||||
| 'switch'
|
||||
| 'slider'
|
||||
| 'cascader'
|
||||
| 'tree-select'
|
||||
| 'upload'
|
||||
| 'auto-complete'
|
||||
| 'mention'
|
||||
| 'group-title'
|
||||
|
||||
export type ColumnsItemPropsKey =
|
||||
| keyof A.InputInstance['$props']
|
||||
| keyof A.InputPasswordInstance['$props']
|
||||
| keyof A.InputNumberInstance['$props']
|
||||
| keyof A.SelectInstance['$props']
|
||||
| keyof A.TextareaInstance['$props']
|
||||
| keyof A.DatePickerInstance['$props']
|
||||
| keyof A.TimePickerInstance['$props']
|
||||
| keyof A.YearPickerInstance['$props']
|
||||
| keyof A.MonthPickerInstance['$props']
|
||||
| keyof A.QuarterPickerInstance['$props']
|
||||
| keyof A.WeekPickerInstance['$props']
|
||||
| keyof A.RangePickerInstance['$props']
|
||||
| keyof A.RadioGroupInstance['$props']
|
||||
| keyof A.CheckboxGroupInstance['$props']
|
||||
| keyof A.ColorPickerInstance['$props']
|
||||
| keyof A.RateInstance['$props']
|
||||
| keyof A.SwitchInstance['$props']
|
||||
| keyof A.SliderInstance['$props']
|
||||
| keyof A.CascaderInstance['$props']
|
||||
| keyof A.TreeSelectInstance['$props']
|
||||
| keyof A.UploadInstance['$props']
|
||||
| keyof A.AlertInstance['$props']
|
||||
export type ComponentProps =
|
||||
& A.InputInstance['$props']
|
||||
& A.InputNumberInstance['$props']
|
||||
& A.InputTagInstance['$props']
|
||||
& A.TextareaInstance['$props']
|
||||
& A.SelectInstance['$props']
|
||||
& A.TreeSelectInstance['$props']
|
||||
& A.RadioGroupInstance['$props']
|
||||
& A.CheckboxGroupInstance['$props']
|
||||
& A.DatePickerInstance['$props']
|
||||
& A.YearPickerInstance['$props']
|
||||
& A.QuarterPickerInstance['$props']
|
||||
& A.MonthPickerInstance['$props']
|
||||
& A.WeekPickerInstance['$props']
|
||||
& A.TimePickerInstance['$props']
|
||||
& A.RangePickerInstance['$props']
|
||||
& A.ColorPickerInstance['$props']
|
||||
& A.RateInstance['$props']
|
||||
& A.SwitchInstance['$props']
|
||||
& A.SliderInstance['$props']
|
||||
& A.CascaderInstance['$props']
|
||||
& A.UploadInstance['$props']
|
||||
& A.AutoCompleteInstance['$props']
|
||||
& A.MentionInstance['$props']
|
||||
& A.AlertInstance['$props']
|
||||
|
||||
export type ColumnsItemHide<F> = boolean | ((form: F) => boolean)
|
||||
export type ColumnsItemShow<F> = boolean | ((form: F) => boolean)
|
||||
export type ColumnsItemDisabled<F> = boolean | ((form: F) => boolean)
|
||||
export type ColumnsItemRequest<F = any> = (form: F) => Promise<any>
|
||||
export type ColumnsItemFormat<T = any> = (
|
||||
res: T
|
||||
) =>
|
||||
| A.SelectInstance['$props']['options']
|
||||
| A.RadioGroupInstance['$props']['options']
|
||||
| A.CheckboxGroupInstance['$props']['options']
|
||||
| A.CascaderInstance['$props']['options']
|
||||
| A.TreeSelectInstance['$props']['data']
|
||||
interface ColumnItemProps extends Partial<Omit<ComponentProps, 'placeholder'>> {
|
||||
placeholder?: string | string[]
|
||||
}
|
||||
|
||||
export type ColumnsItemOptionsOrData =
|
||||
export type ColumnItemOptions =
|
||||
| A.SelectInstance['$props']['options']
|
||||
| A.RadioGroupInstance['$props']['options']
|
||||
| A.CheckboxGroupInstance['$props']['options']
|
||||
| A.CascaderInstance['$props']['options']
|
||||
| A.TreeSelectInstance['$props']['data']
|
||||
|
||||
export interface ColumnsItem<F = any> {
|
||||
type?: FormType // 类型
|
||||
export type ColumnItemData =
|
||||
| A.TreeSelectInstance['$props']['data']
|
||||
| A.AutoCompleteInstance['$props']['data']
|
||||
| A.MentionInstance['$props']['data']
|
||||
|
||||
interface AutoCompleteSlots {
|
||||
option: (e: { data: (string | number | A.SelectOptionData | A.SelectOptionGroup)[] }) => VNode
|
||||
footer: () => VNode
|
||||
}
|
||||
|
||||
interface CascaderSlots {
|
||||
'label': (e: { data: A.CascaderOption }) => VNode
|
||||
'prefix': () => VNode
|
||||
'arrow-icon': () => VNode
|
||||
'loading-icon': () => VNode
|
||||
'search-icon': () => VNode
|
||||
'empty': () => VNode
|
||||
'option': (e: { data: A.CascaderOption }) => VNode
|
||||
}
|
||||
|
||||
interface CheckboxGroupSlots {
|
||||
checkbox: (e: { checked: boolean, disabled: string }) => VNode
|
||||
label: (e: { data: A.CheckboxOption }) => VNode
|
||||
}
|
||||
|
||||
interface RadioGroupSlots {
|
||||
radio: (e: { checked: boolean, disabled: string }) => VNode
|
||||
label: (e: { data: any }) => VNode
|
||||
}
|
||||
|
||||
interface DatePickerSlots {
|
||||
'prefix': () => VNode
|
||||
'suffix-icon': () => VNode
|
||||
'icon-next-double': () => VNode
|
||||
'icon-prev-double': () => VNode
|
||||
'icon-next': () => VNode
|
||||
'icon-prev': () => VNode
|
||||
'cell': (e: { data: Date }) => VNode
|
||||
'extra': () => VNode
|
||||
}
|
||||
|
||||
interface InputSlots {
|
||||
append: (() => VNode) | string
|
||||
prepend: (() => VNode) | string
|
||||
suffix: (() => VNode) | string
|
||||
prefix: (() => VNode) | string
|
||||
}
|
||||
|
||||
interface InputNumberSlots {
|
||||
minus: (() => VNode) | string
|
||||
plus: (() => VNode) | string
|
||||
append: (() => VNode) | string
|
||||
prepend: (() => VNode) | string
|
||||
suffix: (() => VNode) | string
|
||||
}
|
||||
|
||||
interface InputTagSlots {
|
||||
tag: (e: { data: A.TagData }) => VNode
|
||||
prefix: (() => VNode) | string
|
||||
suffix: (() => VNode) | string
|
||||
}
|
||||
|
||||
interface RateSlots {
|
||||
character: (e: { index: number }) => VNode
|
||||
}
|
||||
|
||||
interface SelectSlots {
|
||||
'trigger': () => VNode
|
||||
'prefix': () => VNode
|
||||
'search-icon': () => VNode
|
||||
'loading-icon': () => VNode
|
||||
'arrow-icon': () => VNode
|
||||
'footer': () => VNode
|
||||
'header': () => VNode
|
||||
'label': (e: { data: A.SelectOptionData }) => VNode
|
||||
'option': (e: { data: A.SelectOptionData }) => VNode
|
||||
'empty': () => VNode
|
||||
}
|
||||
|
||||
interface SwitchSlots {
|
||||
'checked-icon': () => VNode
|
||||
'unchecked-icon': () => VNode
|
||||
'checked': () => VNode
|
||||
'unchecked': () => VNode
|
||||
}
|
||||
|
||||
interface TreeSelectSlots {
|
||||
'trigger': () => VNode
|
||||
'prefix': () => VNode
|
||||
'label': (e: { data: any }) => VNode
|
||||
'header': () => VNode
|
||||
'loader': () => VNode
|
||||
'empty': () => VNode
|
||||
'footer': () => VNode
|
||||
'tree-slot-extra': () => VNode
|
||||
'tree-slot-title': (e: { title: string }) => VNode
|
||||
'tree-slot-icon': (e: { node: A.TreeNodeData }) => VNode
|
||||
'tree-slot-switcher-icon': () => VNode
|
||||
}
|
||||
|
||||
interface MentionSlots {
|
||||
option: (e: { data: any }) => VNode
|
||||
}
|
||||
|
||||
export type ComponentSlots =
|
||||
& AutoCompleteSlots
|
||||
& CascaderSlots
|
||||
& CheckboxGroupSlots
|
||||
& RadioGroupSlots
|
||||
& DatePickerSlots
|
||||
& InputSlots
|
||||
& InputNumberSlots
|
||||
& InputTagSlots
|
||||
& RateSlots
|
||||
& SelectSlots
|
||||
& SwitchSlots
|
||||
& TreeSelectSlots
|
||||
& MentionSlots
|
||||
|
||||
export interface ColumnItemSlots extends Omit<ComponentSlots, 'label' | 'option'> {
|
||||
label?: (e: { data: A.CheckboxOption | A.SelectOptionData | A.CascaderOption }) => VNode
|
||||
option?: (e: { data: (string | number | A.SelectOptionData | A.SelectOptionGroup)[] | A.CascaderOption | A.SelectOptionData }) => VNode
|
||||
}
|
||||
|
||||
export type ColumnItemHide<F> = boolean | ((form: F) => boolean)
|
||||
export type ColumnItemShow<F> = boolean | ((form: F) => boolean)
|
||||
export type ColumnItemDisabled<F> = boolean | ((form: F) => boolean)
|
||||
export type ColumnItemRequest<F = any> = (form: F) => Promise<any>
|
||||
export type ColumnItemFormat<T = any> = (res: T) => ColumnItemOptions | ColumnItemData
|
||||
|
||||
export interface ColumnItem<F = any> {
|
||||
type?: ColumnItemType // 类型
|
||||
label?: A.FormItemInstance['label'] | (() => VNode) // 标签
|
||||
field: A.FormItemInstance['field'] // 字段(必须唯一)
|
||||
span?: A.GridItemProps['span']
|
||||
props?: ColumnItemProps
|
||||
gridItemProps?: A.GridItemProps
|
||||
formItemProps?: Omit<A.FormItemInstance['$props'], 'label' | 'field'> // a-form-item的props
|
||||
props?:
|
||||
& A.InputInstance['$props']
|
||||
& A.InputPasswordInstance['$props']
|
||||
& A.InputNumberInstance['$props']
|
||||
& A.SelectInstance['$props']
|
||||
& A.TextareaInstance['$props']
|
||||
& A.DatePickerInstance['$props']
|
||||
& A.TimePickerInstance['$props']
|
||||
& A.RadioGroupInstance['$props']
|
||||
& A.CheckboxGroupInstance['$props']
|
||||
& A.RateInstance['$props']
|
||||
& A.SwitchInstance['$props']
|
||||
& A.SliderInstance['$props']
|
||||
& A.CascaderInstance['$props']
|
||||
& A.TreeSelectInstance['$props']
|
||||
& A.UploadInstance['$props']
|
||||
& A.AlertInstance['$props']
|
||||
required?: boolean // 是否必填
|
||||
rules?: A.FormItemInstance['$props']['rules'] // 表单校验规则
|
||||
// 下拉列表|复选框组|单选框组|级联选择组件的options
|
||||
options?:
|
||||
| A.SelectInstance['$props']['options']
|
||||
| A.RadioGroupInstance['$props']['options']
|
||||
| A.CheckboxGroupInstance['$props']['options']
|
||||
| A.CascaderInstance['$props']['options']
|
||||
// 下拉树组件的data
|
||||
span?: A.GridItemProps['span']
|
||||
data?: A.TreeSelectInstance['$props']['data']
|
||||
show?: ColumnsItemShow<F> // 是否显示(优先级比hide高)
|
||||
hide?: ColumnsItemHide<F> // 是否隐藏
|
||||
disabled?: ColumnsItemDisabled<F> // 是否禁用
|
||||
request?: ColumnsItemRequest<F> // 接口请求api
|
||||
resultFormat?: ColumnsItemFormat // 结果集格式化
|
||||
hide?: ColumnItemHide<F> // 是否隐藏
|
||||
show?: ColumnItemShow<F> // 是否显示(优先级比hide高)
|
||||
disabled?: ColumnItemDisabled<F> // 是否禁用
|
||||
request?: ColumnItemRequest<F> // 接口请求api
|
||||
resultFormat?: ColumnItemFormat // 结果集格式化
|
||||
init?: boolean // 初始化请求
|
||||
cascader?: string[] // 级联的field字段列表
|
||||
slots?: Partial<Record<'prepend' | 'append' | 'suffix' | 'prefix', string | (() => VNode)>>
|
||||
slots?: Partial<ColumnItemSlots>
|
||||
formItemSlots?: Partial<Record<'help' | 'extra', string | (() => VNode)>>
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
form?: Omit<A.FormInstance['$props'], 'model'>
|
||||
grid?: A.GridProps
|
||||
gridItem?: A.GridItemProps
|
||||
btns?: { hide?: boolean, searchBtnText?: string }
|
||||
fold?: { enable?: boolean, index?: number, defaultCollapsed?: boolean }
|
||||
}
|
||||
|
||||
export type Columns<F = any> = ColumnsItem<F>[]
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div ref="rootRef" class="ca-split-panel" :class="{
|
||||
'is-vertical': vertical,
|
||||
'is-resizing': resizing,
|
||||
'is-collapse': isCollapse,
|
||||
'is-responsive': isResponsive,
|
||||
'is-mobile': isMobile,
|
||||
}" :style="customStyle">
|
||||
<div
|
||||
ref="rootRef" class="ca-split-panel" :class="{
|
||||
'is-vertical': vertical,
|
||||
'is-resizing': resizing,
|
||||
'is-collapse': isCollapse,
|
||||
'is-responsive': isResponsive,
|
||||
'is-mobile': isMobile,
|
||||
}" :style="customStyle"
|
||||
>
|
||||
<div class="container" :style="sideStyle">
|
||||
<div ref="sideRef" class="ca-split-panel__side">
|
||||
<div class="ca-split-panel__content">
|
||||
@@ -15,10 +17,12 @@
|
||||
<!-- 竖线和按钮 -->
|
||||
<div class="divider-container">
|
||||
<div v-show="!isCollapse" class="divider"></div>
|
||||
<div v-if="allowCollapse" class="ca-split-panel__collapse-trigger" :class="{
|
||||
'is-collapse': isCollapse,
|
||||
'is-mobile': isMobile,
|
||||
}" @click="toggleCollapse">
|
||||
<div
|
||||
v-if="allowCollapse" class="ca-split-panel__collapse-trigger" :class="{
|
||||
'is-collapse': isCollapse,
|
||||
'is-mobile': isMobile,
|
||||
}" @click="toggleCollapse"
|
||||
>
|
||||
<div class="ca-split-panel__collapse-trigger-icon">
|
||||
<IconRight v-if="isCollapse" size="20" />
|
||||
<IconLeft v-else size="20" />
|
||||
|
||||
Reference in New Issue
Block a user