wip(VForm): VForm component development

This commit is contained in:
kailong321200875
2021-12-18 20:09:05 +08:00
parent d9d64f3931
commit 28be932fd2
10 changed files with 526 additions and 81 deletions

View File

@@ -1,11 +1,18 @@
<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref } from 'vue'
import { PropType, defineComponent, ref, computed, unref, reactive, watch } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol } from 'element-plus'
import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper'
import { setTextPlaceholder, setGridProp, setComponentProps, setItemComponentSlots } from './helper'
import {
setTextPlaceholder,
setGridProp,
setComponentProps,
setItemComponentSlots,
setModel
} from './helper'
import { useRenderSelect } from './components/useRenderSelect'
import { useRenderCascader } from './components/useRenderCascader'
export default defineComponent({
name: 'VForm',
@@ -31,9 +38,24 @@ export default defineComponent({
labelWidth: propTypes.oneOfType([String, Number]).def(130)
},
setup(props, { slots }) {
// element form 实例
const formRef = ref<ComponentRef<typeof ElForm>>()
const getProps = computed(() => props)
const { schema, isCol, isCustom, autoSetPlaceholder } = unref(getProps)
// 表单数据
const formModel = reactive<Recordable>({})
// 监听表单结构化数组重新生成formModel
watch(
() => schema,
(schema) => {
setModel(schema, formModel)
},
{
immediate: true,
deep: true
}
)
// 渲染包裹标签,是否使用栅格布局
function renderWrap() {
@@ -67,20 +89,28 @@ export default defineComponent({
// 渲染formItem
function renderFormItem(item: VFormSchema) {
// 单独给只有options属性的组件做判断
const notRenderOptions = ['SelectV2', 'Cascader']
return (
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label}>
{() => {
const Com = componentMap[item.component as string] as ReturnType<typeof defineComponent>
return (
<Com
vModel={formModel[item.field]}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item.componentProps)}
// 单独给SelectV2做判断
options={item.component === 'SelectV2' ? item.options || [] : undefined}
options={
notRenderOptions.includes(item?.component as string)
? item.options || []
: undefined
}
>
{{
default: () =>
item.options && item.component !== 'SelectV2' ? renderOptions(item) : undefined,
default: (data: Recordable) =>
item.options && item?.component !== 'SelectV2'
? renderOptions(item, data)
: undefined,
...setItemComponentSlots(slots, item?.componentProps?.slots, item.field)
}}
</Com>
@@ -91,11 +121,14 @@ export default defineComponent({
}
// 渲染options
function renderOptions(item: VFormSchema) {
function renderOptions(item: VFormSchema, data: Recordable) {
switch (item.component) {
case 'Select':
const { renderSelectOptions } = useRenderSelect(slots)
return renderSelectOptions(item)
case 'Cascader':
const { useRenderCascaderOptions } = useRenderCascader(slots)
return useRenderCascaderOptions(item, data)
default:
break
}
@@ -104,7 +137,7 @@ export default defineComponent({
// 过滤传入Form组件的属性
function getFormBindValue() {
// 避免在标签上出现多余的属性
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom']
const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']
const props = { ...unref(getProps) }
for (const key in props) {
if (delKeys.indexOf(key) !== -1) {
@@ -115,7 +148,7 @@ export default defineComponent({
}
return () => (
<ElForm ref={formRef} {...getFormBindValue()}>
<ElForm ref={formRef} {...getFormBindValue()} model={formModel}>
{{
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
default: () => (isCustom ? getSlot(slots, 'default') : renderWrap())

View File

@@ -0,0 +1,23 @@
import { Slots } from 'vue'
import { getSlot } from '@/utils/tsxHelper'
// 这个可能是element-plus的BUG需要这么处理才能渲染出来。
export function useRenderCascader(slots: Slots) {
function useRenderCascaderOptions(item: VFormSchema, data: Recordable) {
return (
<span>
{{
default: () => {
return item?.componentProps?.slots?.default
? getSlot(slots, `${item.field}-default`, data)
: data?.data[item?.optionsField?.labelField || 'label']
}
}}
</span>
)
}
return {
useRenderCascaderOptions
}
}

View File

@@ -1,7 +1,6 @@
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
import { shallowRef } from 'vue'
import { isFunction } from '@/utils/is'
import { unref } from 'vue'
import { Slots } from 'vue'
import { getSlot } from '@/utils/tsxHelper'
@@ -71,27 +70,35 @@ export function setGridProp(col: ColProps = {}): ColProps {
return colProps
}
type ComponentPropsModel = {
clearable: boolean
} & Recordable
/**
*
* @param props 传入的组件属性
* @returns 默认添加 clearable 属性
*/
export function setComponentProps(props: Recordable = {}): ComponentPropsModel {
for (const key in props) {
// 如果传入的是组件,需要让其失去响应式,避免不必要的性能开销
// 这样判断好像还不太合理。后续看看没有更合理的判断方法
if (props[key]?.render && isFunction(props[key]?.render)) {
props[key] = shallowRef(props[key]?.render())
}
}
const componentProps: ComponentPropsModel = {
export function setComponentProps(props: Recordable = {}): Recordable {
const propsObj = unref(props)
// for (const key in propsObj) {
// // 如果传入的是组件,需要让其失去响应式,避免不必要的性能开销
// // 这样判断好像还不太合理。后续看看没有更合理的判断方法
// if (propsObj[key]?.render && isFunction(propsObj[key]?.render)) {
// propsObj[key] = shallowRef(propsObj[key]?.render())
// }
// // if (key === 'icon') {
// // propsObj[key] = [...propsObj[key]]
// // }
// }
const componentProps: Recordable = {
clearable: true,
...props
...propsObj
}
// componentProps.icons
// ? (componentProps.icons = (componentProps.icons as Recordable[]).map((v) => {
// return shallowRef(v?.render()?.value)
// }))
// : undefined
// 需要删除额外的属性
delete componentProps?.slots
console.log(componentProps)
return componentProps
}
@@ -118,4 +125,21 @@ export function setItemComponentSlots(
return slotObj
}
export function setModel() {}
/**
*
* @param schema Form表单结构化数组
* @param formModel FormMoel
* @description 生成对应的formModel
*/
export function setModel(schema: VFormSchema[], formModel: Recordable) {
schema.map((v) => {
// 如果是hidden就删除对应的值
if (v.hidden) {
delete formModel[v.field]
} else {
const hasField = Reflect.has(formModel, v.field)
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
formModel[v.field] = hasField ? formModel[v.field] : v.value !== void 0 ? v.value : ''
}
})
}