wip(VForm): VForm component development
This commit is contained in:
@@ -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())
|
||||
|
||||
23
src/components/Form/src/components/useRenderCascader.tsx
Normal file
23
src/components/Form/src/components/useRenderCascader.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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 : ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user