first commit

This commit is contained in:
2024-04-08 21:34:02 +08:00
commit a41a7f32ab
223 changed files with 44629 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
import GiForm from './src/GiForm.vue'
import { useGiForm } from './src/hooks'
export type * from './src/type'
export { GiForm, useGiForm }
export default GiForm

View File

@@ -0,0 +1,239 @@
<template>
<a-form ref="formRef" v-bind="options.form" :model="modelValue" size="large" :auto-label-width="true">
<a-row :gutter="14" v-bind="options.row" class="w-full">
<template v-for="(item, index) in columns" :key="item.field">
<a-col
v-if="!isHide(item.hide)"
:span="item.span || 12"
v-bind="item.col || item.span ? item.col : options.col"
v-show="index <= (options.fold?.index || 0) || (index >= (options.fold?.index || 0) && !collapsed)"
>
<a-form-item v-bind="item.item" :label="item.label" :field="item.field" :rules="item.rules">
<slot :name="item.field">
<template v-if="item.type === 'input'">
<a-input
:placeholder="`请输入${item.label}`"
v-bind="(item.props as A.InputInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-input>
</template>
<template v-if="item.type === 'input-number'">
<a-input-number
:placeholder="`请输入${item.label}`"
v-bind="(item.props as A.InputNumberInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-input-number>
</template>
<template v-if="item.type === 'textarea'">
<a-textarea
:placeholder="`请输入${item.label}`"
:show-word-limit="true"
v-bind="(item.props as A.TextareaInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-textarea>
</template>
<template v-if="item.type === 'select'">
<a-select
:placeholder="`请选择${item.label}`"
v-bind="(item.props as A.SelectInstance['$props'])"
:options="dicData[item.field] || (item.options as A.SelectInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-select>
</template>
<template v-if="item.type === 'cascader'">
<a-cascader
:placeholder="`请选择${item.label}`"
v-bind="(item.props as A.CascaderInstance['$props'])"
:options="dicData[item.field] || (item.options as A.CascaderInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
<template v-if="item.type === 'tree-select'">
<a-tree-select
:placeholder="`请选择${item.label}`"
v-bind="(item.props as A.TreeSelectInstance['$props'])"
:data="dicData[item.field] || (item.data as A.TreeSelectInstance['$props']['data'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
>
</a-tree-select>
</template>
<template v-if="item.type === 'radio-group'">
<a-radio-group
v-bind="(item.props as A.RadioGroupInstance['$props'])"
:options="dicData[item.field] || (item.options as A.RadioGroupInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-radio-group>
</template>
<template v-if="item.type === 'checkbox-group'">
<a-checkbox-group
v-bind="(item.props as A.CheckboxGroupInstance['$props'])"
:options="dicData[item.field] || (item.options as A.CheckboxGroupInstance['$props']['options'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-checkbox-group>
</template>
<template v-if="item.type === 'date-picker'">
<a-date-picker
:placeholder="`请选择日期`"
v-bind="(item.props as A.DatePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
></a-date-picker>
</template>
<template v-if="item.type === 'time-picker'">
<a-time-picker
:placeholder="`请选择时间`"
v-bind="(item.props as A.TimePickerInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
>
</a-time-picker>
</template>
<template v-if="item.type === 'rate'">
<a-rate
v-bind="(item.props as A.RateInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
<template v-if="item.type === 'switch'">
<a-switch
v-bind="(item.props as A.SwitchInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
<template v-if="item.type === 'slider'">
<a-slider
v-bind="(item.props as A.SliderInstance['$props'])"
:model-value="modelValue[item.field as keyof typeof modelValue]"
@update:model-value="valueChange($event, item.field)"
/>
</template>
</slot>
</a-form-item>
</a-col>
</template>
<a-col :span="options.btns?.span || 12" v-bind="options.btns?.col" v-if="!options.btns?.hide">
<a-space wrap>
<slot name="suffix">
<a-button type="primary" @click="emit('search')">
<template #icon><icon-search /></template>
<template #default>{{ options.btns?.searchBtnText || '查询' }}</template>
</a-button>
<a-button @click="emit('reset')">重置</a-button>
<a-button v-if="options.fold?.enable" type="text" size="mini" @click="collapsed = !collapsed">
<template #icon>
<icon-up v-if="!collapsed" />
<icon-down v-else />
</template>
<template #default>{{ collapsed ? '展开' : '收起' }}</template>
</a-button>
</slot>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
<script setup lang="ts">
import type { Options, Columns, ColumnsItemHide, ColumnsItem } from './type'
import type * as A from '@arco-design/web-vue'
import _ from 'lodash'
interface Props {
modelValue: any
options: Options
columns: Columns
}
const props = withDefaults(defineProps<Props>(), {})
const emit = defineEmits<{
(e: 'update:modelValue', value: any): void
(e: 'search'): void
(e: 'reset'): void
}>()
const valueChange = (value: any, field: string) => {
emit('update:modelValue', Object.assign(props.modelValue, { [field]: value }))
}
const collapsed = ref(props.options.fold?.defaultCollapsed ?? false)
const formRef = ref<A.FormInstance>()
defineExpose({ formRef })
const isHide = (hide?: ColumnsItemHide<boolean | object>) => {
if (hide === undefined) return false
if (typeof hide === 'boolean') return hide
if (typeof hide === 'function') {
return hide(props.modelValue)
}
}
const dicData: Record<string, any> = reactive({})
props.columns.forEach((item) => {
if (item.request && typeof item.request === 'function' && item?.init) {
item.request(props.modelValue).then((res) => {
dicData[item.field] = item.resultFormat ? item.resultFormat(res) : res.data
})
}
})
// 先找出有级联的项
// 如果这个字段改变了值那么就找出它的cascader属性对应的字段项去请求里面的request
const hasCascaderColumns: ColumnsItem[] = []
props.columns.forEach((item) => {
const arr = hasCascaderColumns.map((i) => i.field)
if (item.cascader?.length && !arr.includes(item.field)) {
hasCascaderColumns.push(item)
}
})
// 要深克隆,否则无法监听新旧值变化
const cloneForm = computed(() => _.cloneDeep(props.modelValue))
watch(cloneForm as any, (newVal, oldVal) => {
hasCascaderColumns.forEach((item) => {
if (newVal[item.field] !== oldVal[item.field]) {
const arr = props.columns.filter((a) => {
return item?.cascader?.includes(a.field)
})
arr.forEach((i) => {
if (i.request && Boolean(newVal[item.field])) {
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]: '' }))
}
})
} else if (i.request && !newVal[item.field]) {
dicData[i.field] = []
emit('update:modelValue', Object.assign(props.modelValue, { [i.field]: '' }))
}
})
}
})
})
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,48 @@
import { reactive } from 'vue'
import _ from 'lodash'
import type { Columns, ColumnsItem, ColumnsItemPropsKey } from './type'
import { Message } from '@arco-design/web-vue'
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

@@ -0,0 +1,96 @@
import type * as A from '@arco-design/web-vue'
export type FormType =
| 'input'
| 'select'
| 'radio-group'
| 'checkbox-group'
| 'textarea'
| 'date-picker'
| 'time-picker'
| 'input-number'
| 'rate'
| 'switch'
| 'slider'
| 'cascader'
| 'tree-select'
export type ColumnsItemPropsKey =
| keyof A.InputInstance['$props']
| keyof A.SelectInstance['$props']
| keyof A.TextareaInstance['$props']
| keyof A.DatePickerInstance['$props']
| keyof A.TimePickerInstance['$props']
| keyof A.RadioGroupInstance['$props']
| keyof A.CheckboxGroupInstance['$props']
| keyof A.InputNumberInstance['$props']
| keyof A.RateInstance['$props']
| keyof A.SwitchInstance['$props']
| keyof A.SliderInstance['$props']
| keyof A.CascaderInstance['$props']
| keyof A.TreeSelectInstance['$props']
export type ColumnsItemHide<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']
export type ColumnsItemOptionsOrData =
| 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 // 类型
label: A.FormItemInstance['label'] // 标签
field: A.FormItemInstance['field'] // 字段(必须唯一)
span?: number // 栅格占位格数
col?: A.ColProps // a-col的props, 响应式布局, 优先级大于span
item?: Omit<A.FormItemInstance['$props'], 'label' | 'field'> // a-form-item的props
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']
rules?: A.FormItemInstance['$props']['rules'] // 表单校验规则
// 下拉列表|复选框组|单选框组|级联选择组件的options
options?:
| A.SelectInstance['$props']['options']
| A.RadioGroupInstance['$props']['options']
| A.CheckboxGroupInstance['$props']['options']
| A.CascaderInstance['$props']['options']
// 下拉树组件的data
data?: A.TreeSelectInstance['$props']['data']
hide?: ColumnsItemHide<F> // 是否隐藏
request?: ColumnsItemRequest<F> // 接口请求api
resultFormat?: ColumnsItemFormat // 结果集格式化
init?: boolean // 初始化请求
cascader?: string[] // 级联的field字段列表
}
export interface Options {
form: Omit<A.FormInstance['$props'], 'model'>
row?: Partial<typeof import('@arco-design/web-vue')['Row']['__defaults']>
col?: A.ColProps
btns?: { hide?: boolean; span?: number; col?: A.ColProps; searchBtnText?: string }
fold?: { enable?: boolean; index?: number; defaultCollapsed?: boolean }
}
export type Columns<F = any> = ColumnsItem<F>[]