gohttpdUi/src/components/Form/src/Form.vue

373 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol, FormItemProp } from 'element-plus'
import { componentMap } from './helper/componentMap'
import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper'
import {
setTextPlaceholder,
setGridProp,
setComponentProps,
setItemComponentSlots,
initModel
} from './helper'
import { useRenderSelect } from './components/useRenderSelect'
import { useRenderRadio } from './components/useRenderRadio'
import { useRenderCheckbox } from './components/useRenderCheckbox'
import { useDesign } from '@/hooks/web/useDesign'
import { findIndex } from '@/utils'
import { set } from 'lodash-es'
import { FormProps } from './types'
import {
FormSchema,
FormSetProps,
ComponentNameEnum,
SelectComponentProps,
RadioGroupComponentProps,
CheckboxGroupComponentProps
} from './types'
const { renderSelectOptions } = useRenderSelect()
const { renderRadioOptions } = useRenderRadio()
const { renderCheckboxOptions } = useRenderCheckbox()
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('form')
export default defineComponent({
name: 'Form',
props: {
// 生成Form的布局结构数组
schema: {
type: Array as PropType<FormSchema[]>,
default: () => []
},
// 是否需要栅格布局
isCol: propTypes.bool.def(true),
// 表单数据对象
model: {
type: Object as PropType<Recordable>,
default: () => ({})
},
// 是否自动设置placeholder
autoSetPlaceholder: propTypes.bool.def(true),
// 是否自定义内容
isCustom: propTypes.bool.def(false),
// 表单label宽度
labelWidth: propTypes.oneOfType([String, Number]).def('auto')
},
emits: ['register', 'validate'],
setup(props, { slots, expose, emit }) {
// element form 实例
const elFormRef = ref<ComponentRef<typeof ElForm>>()
// useForm传入的props
const outsideProps = ref<FormProps>({})
const mergeProps = ref<FormProps>({})
const getProps = computed(() => {
const propsObj = { ...props }
Object.assign(propsObj, unref(mergeProps))
return propsObj
})
// 存储表单实例
const formComponents = ref({})
// 存储form-item实例
const formItemComponents = ref({})
// 表单数据
const formModel = ref<Recordable>({})
onMounted(() => {
emit('register', unref(elFormRef)?.$parent, unref(elFormRef))
})
// 对表单赋值
const setValues = (data: Recordable = {}) => {
formModel.value = Object.assign(unref(formModel), data)
}
const setProps = (props: FormProps = {}) => {
mergeProps.value = Object.assign(unref(mergeProps), props)
outsideProps.value = props
}
const delSchema = (field: string) => {
const { schema } = unref(getProps)
const index = findIndex(schema, (v: FormSchema) => v.field === field)
if (index > -1) {
schema.splice(index, 1)
}
}
const addSchema = (formSchema: FormSchema, index?: number) => {
const { schema } = unref(getProps)
if (index !== void 0) {
schema.splice(index, 0, formSchema)
return
}
schema.push(formSchema)
}
const setSchema = (schemaProps: FormSetProps[]) => {
const { schema } = unref(getProps)
for (const v of schema) {
for (const item of schemaProps) {
if (v.field === item.field) {
set(v, item.path, item.value)
}
}
}
}
const getOptions = async (fn: Function, item: FormSchema) => {
const options = await fn()
setSchema([
{
field: item.field,
path:
item.component === ComponentNameEnum.TREE_SELECT
? 'componentProps.data'
: 'componentProps.options',
value: options
}
])
}
/**
* @description: 获取表单组件实例
* @param filed 表单字段
*/
const getComponentExpose = (filed: string) => {
return unref(formComponents)[filed]
}
/**
* @description: 获取formItem实例
* @param filed 表单字段
*/
const getFormItemExpose = (filed: string) => {
return unref(formItemComponents)[filed]
}
const setComponentRefMap = (ref: any, filed: string) => {
formComponents.value[filed] = ref
}
const setFormItemRefMap = (ref: any, filed: string) => {
formItemComponents.value[filed] = ref
}
expose({
setValues,
formModel,
setProps,
delSchema,
addSchema,
setSchema,
getComponentExpose,
getFormItemExpose
})
// 监听表单结构化数组重新生成formModel
watch(
() => unref(getProps).schema,
(schema = []) => {
formModel.value = initModel(schema, unref(formModel))
},
{
immediate: true,
deep: true
}
)
// 渲染包裹标签,是否使用栅格布局
const renderWrap = () => {
const { isCol } = unref(getProps)
const content = isCol ? (
<ElRow gutter={20}>{renderFormItemWrap()}</ElRow>
) : (
renderFormItemWrap()
)
return content
}
// 是否要渲染el-col
const renderFormItemWrap = () => {
// hidden属性表示隐藏不做渲染
const { schema = [], isCol } = unref(getProps)
return schema
.filter((v) => !v.remove)
.map((item) => {
// 如果是 Divider 组件,需要自己占用一行
const isDivider = item.component === 'Divider'
const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>
return isDivider ? (
<Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>
) : isCol ? (
// 如果需要栅格,需要包裹 ElCol
<ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>
) : (
renderFormItem(item)
)
})
}
// 渲染formItem
const renderFormItem = (item: FormSchema) => {
// 如果有optionApi优先使用optionApi
if (item.optionApi) {
// 内部自动调用接口,不影响其他渲染
getOptions(item.optionApi, item)
}
const formItemSlots: Recordable = {
default: () => {
if (item?.formItemProps?.slots?.default) {
return item?.formItemProps?.slots?.default(formModel.value)
} else {
const Com = componentMap[item.component as string] as ReturnType<typeof defineComponent>
const { autoSetPlaceholder } = unref(getProps)
const componentSlots = (item?.componentProps as any)?.slots || {}
const slotsMap: Recordable = {
...setItemComponentSlots(componentSlots)
}
// // 如果是select组件并且没有自定义模板自动渲染options
if (item.component === ComponentNameEnum.SELECT) {
slotsMap.default = !componentSlots.default
? () => renderSelectOptions(item)
: () => {
return componentSlots.default(
unref((item?.componentProps as SelectComponentProps)?.options)
)
}
}
// 虚拟列表
if (item.component === ComponentNameEnum.SELECT_V2 && componentSlots.default) {
slotsMap.default = ({ item }) => {
return componentSlots.default(item)
}
}
// 单选框组和按钮样式
if (
item.component === ComponentNameEnum.RADIO_GROUP ||
item.component === ComponentNameEnum.RADIO_BUTTON
) {
slotsMap.default = !componentSlots.default
? () => renderRadioOptions(item)
: () => {
return componentSlots.default(
unref((item?.componentProps as CheckboxGroupComponentProps)?.options)
)
}
}
// 多选框组和按钮样式
if (
item.component === ComponentNameEnum.CHECKBOX_GROUP ||
item.component === ComponentNameEnum.CHECKBOX_BUTTON
) {
slotsMap.default = !componentSlots.default
? () => renderCheckboxOptions(item)
: () => {
return componentSlots.default(
unref((item?.componentProps as RadioGroupComponentProps)?.options)
)
}
}
const Comp = () => {
return (
<Com
vModel={formModel.value[item.field]}
ref={(el: any) => setComponentRefMap(el, item.field)}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={item.componentProps?.style || {}}
>
{{ ...slotsMap }}
</Com>
)
}
return <>{Comp()}</>
}
}
}
if (item?.formItemProps?.slots?.label) {
formItemSlots.label = (...args: any[]) => {
return (item?.formItemProps?.slots as any)?.label(...args)
}
}
if (item?.formItemProps?.slots?.error) {
formItemSlots.error = (...args: any[]) => {
return (item?.formItemProps?.slots as any)?.error(...args)
}
}
return (
<ElFormItem
v-show={!item.hidden}
ref={(el: any) => setFormItemRefMap(el, item.field)}
{...(item.formItemProps || {})}
prop={item.field}
label={item.label || ''}
>
{formItemSlots}
</ElFormItem>
)
}
// 过滤传入Form组件的属性
const getFormBindValue = () => {
// 避免在标签上出现多余的属性
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
const props = { ...unref(getProps) }
for (const key in props) {
if (delKeys.indexOf(key) !== -1) {
delete props[key]
}
}
return props
}
const onValidate = (prop: FormItemProp, isValid: boolean, message: string) => {
emit('validate', prop, isValid, message)
}
return () => (
<ElForm
ref={elFormRef}
{...getFormBindValue()}
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
class={prefixCls}
onValidate={onValidate}
>
{{
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
default: () => {
const { isCustom } = unref(getProps)
return isCustom ? getSlot(slots, 'default') : renderWrap()
}
}}
</ElForm>
)
}
})
</script>
<style lang="less" scoped>
.@{elNamespace}-form.@{namespace}-form .@{elNamespace}-row {
margin-right: 0 !important;
margin-left: 0 !important;
}
</style>