mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2026-01-14 06:57:09 +08:00
feat: 优化 GiForm(同步 GiDemo 更新)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user