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,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>

View File

@@ -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
}

View File

@@ -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

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;
}

View File

@@ -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,
}
}

View File

@@ -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>[]

View File

@@ -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" />