wip: Form改造中

This commit is contained in:
kailong321200875 2023-04-28 14:02:27 +08:00
parent 1d0f4b4c39
commit 2261f49976
6 changed files with 517 additions and 88 deletions

View File

@ -0,0 +1,302 @@
<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper'
import {
setTextPlaceholder,
setGridProp,
setComponentProps,
setItemComponentSlots,
initModel,
setFormItemSlots
} 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 { Icon } from '@/components/Icon'
import { FormSchema, FormSetPropsType } from '@/types/form'
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'],
setup(props, { slots, expose, emit }) {
// element form
const elFormRef = ref<ComponentRef<typeof ElForm>>()
// useFormprops
const outsideProps = ref<FormProps>({})
const mergeProps = ref<FormProps>({})
const getProps = computed(() => {
const propsObj = { ...props }
Object.assign(propsObj, unref(mergeProps))
return propsObj
})
//
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: FormSetPropsType[]) => {
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 getElFormRef = (): ComponentRef<typeof ElForm> => {
return unref(elFormRef) as ComponentRef<typeof ElForm>
}
expose({
setValues,
formModel,
setProps,
delSchema,
addSchema,
setSchema,
getElFormRef
})
// 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.hidden)
.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) => {
// options
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
const componentSlots = (item?.componentProps as any)?.slots || {}
const slotsMap: Recordable = {
...setItemComponentSlots(unref(formModel), componentSlots)
}
if (
item?.component !== 'SelectV2' &&
item?.component !== 'Cascader' &&
item?.componentProps?.options
) {
slotsMap.default = () => renderOptions(item)
}
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
// labelMessage使
if (item?.labelMessage) {
formItemSlots.label = () => {
return (
<>
<span>{item.label}</span>
<ElTooltip placement="right" raw-content>
{{
content: () => <span v-html={item.labelMessage}></span>,
default: () => (
<Icon
icon="ep:warning"
size={16}
color="var(--el-color-primary)"
class="ml-2px relative top-1px"
></Icon>
)
}}
</ElTooltip>
</>
)
}
}
return (
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
{{
...formItemSlots,
default: () => {
const Com = componentMap[item.component as string] as ReturnType<
typeof defineComponent
>
const { autoSetPlaceholder } = unref(getProps)
return slots[item.field] ? (
getSlot(slots, item.field, formModel.value)
) : (
<Com
vModel={formModel.value[item.field]}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={item.componentProps?.style}
{...(notRenderOptions.includes(item?.component as string) &&
item?.componentProps?.options
? { options: item?.componentProps?.options || [] }
: {})}
>
{{ ...slotsMap }}
</Com>
)
}
}}
</ElFormItem>
)
}
// options
const renderOptions = (item: FormSchema) => {
switch (item.component) {
case 'Select':
const { renderSelectOptions } = useRenderSelect(slots)
return renderSelectOptions(item)
case 'Radio':
case 'RadioButton':
const { renderRadioOptions } = useRenderRadio()
return renderRadioOptions(item)
case 'Checkbox':
case 'CheckboxButton':
const { renderCheckboxOptions } = useRenderCheckbox()
return renderCheckboxOptions(item)
default:
break
}
}
// 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
}
return () => (
<ElForm
ref={elFormRef}
{...getFormBindValue()}
model={props.isCustom ? props.model : formModel}
class={prefixCls}
>
{{
//
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>

View File

@ -21,6 +21,7 @@ import { set } from 'lodash-es'
import { FormProps } from './types'
import { Icon } from '@/components/Icon'
import { FormSchema, FormSetPropsType } from '@/types/form'
import { ComponentNameEnum } from '@/types/components.d'
const { getPrefixCls } = useDesign()
@ -172,46 +173,55 @@ export default defineComponent({
// formItem
const renderFormItem = (item: FormSchema) => {
// options
const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
// const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']
const componentSlots = (item?.componentProps as any)?.slots || {}
const slotsMap: Recordable = {
...setItemComponentSlots(unref(formModel), item?.componentProps?.slots)
...setItemComponentSlots(unref(formModel), componentSlots)
}
if (
item?.component !== 'SelectV2' &&
item?.component !== 'Cascader' &&
item?.componentProps?.options
) {
slotsMap.default = () => renderOptions(item)
// selectoptions
if (item.component === ComponentNameEnum.SELECT) {
slotsMap.default = !componentSlots.default
? () => renderOptions(item)
: (option: any) => {
console.log(option)
return componentSlots.default(option)
}
}
// if (
// item?.component !== 'SelectV2' &&
// item?.component !== 'Cascader' &&
// item?.componentProps?.options
// ) {
// slotsMap.default = () => renderOptions(item)
// }
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
// const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
// labelMessage使
if (item?.labelMessage) {
formItemSlots.label = () => {
return (
<>
<span>{item.label}</span>
<ElTooltip placement="right" raw-content>
{{
content: () => <span v-html={item.labelMessage}></span>,
default: () => (
<Icon
icon="ep:warning"
size={16}
color="var(--el-color-primary)"
class="ml-2px relative top-1px"
></Icon>
)
}}
</ElTooltip>
</>
)
}
}
// if (item?.labelMessage) {
// formItemSlots.label = () => {
// return (
// <>
// <span>{item.label}</span>
// <ElTooltip placement="right" raw-content>
// {{
// content: () => <span v-html={item.labelMessage}></span>,
// default: () => (
// <Icon
// icon="ep:warning"
// size={16}
// color="var(--el-color-primary)"
// class="ml-2px relative top-1px"
// ></Icon>
// )
// }}
// </ElTooltip>
// </>
// )
// }
// }
return (
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
{{
...formItemSlots,
default: () => {
const Com = componentMap[item.component as string] as ReturnType<
typeof defineComponent
@ -227,10 +237,6 @@ export default defineComponent({
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
style={item.componentProps?.style}
{...(notRenderOptions.includes(item?.component as string) &&
item?.componentProps?.options
? { options: item?.componentProps?.options || [] }
: {})}
>
{{ ...slotsMap }}
</Com>
@ -244,7 +250,7 @@ export default defineComponent({
// options
const renderOptions = (item: FormSchema) => {
switch (item.component) {
case 'Select':
case ComponentNameEnum.SELECT:
const { renderSelectOptions } = useRenderSelect(slots)
return renderSelectOptions(item)
case 'Radio':

View File

@ -2,14 +2,15 @@ import { ElOption, ElOptionGroup } from 'element-plus'
import { getSlot } from '@/utils/tsxHelper'
import { Slots } from 'vue'
import { FormSchema } from '@/types/form'
import { ComponentOptions } from '@/types/components'
import { SelectComponentProps, SelectOption } from '@/types/components'
export const useRenderSelect = (slots: Slots) => {
// 渲染 select options
const renderSelectOptions = (item: FormSchema) => {
const componentsProps = item.componentProps as SelectComponentProps
// 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField
return item?.componentProps?.options?.map((option) => {
const labelAlias = componentsProps?.labelAlias
return componentsProps?.options?.map((option) => {
if (option?.options?.length) {
return (
<ElOptionGroup label={option[labelAlias || 'label']}>
@ -27,10 +28,11 @@ export const useRenderSelect = (slots: Slots) => {
}
// 渲染 select option item
const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => {
const renderSelectOptionItem = (item: FormSchema, option: SelectOption) => {
// 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField
const componentsProps = item.componentProps as SelectComponentProps
const labelAlias = componentsProps?.labelAlias
const valueAlias = componentsProps?.valueAlias
const { label, value, ...other } = option
@ -43,7 +45,7 @@ export const useRenderSelect = (slots: Slots) => {
{{
default: () =>
// option 插槽名规则,{field}-option
item?.componentProps?.optionsSlot
componentsProps?.optionsSlot
? getSlot(slots, `${item.field}-option`, { item: option })
: undefined
}}

View File

@ -78,6 +78,7 @@ export interface InputComponentProps {
prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
append?: JSX.Element | ((item: any, data: any) => JSX.Element)
}
style?: CSSProperties
}
export interface AutocompleteComponentProps {
@ -110,6 +111,7 @@ export interface AutocompleteComponentProps {
prepend?: JSX.Element | ((item: any, data: any) => JSX.Element)
append?: JSX.Element | ((item: any, data: any) => JSX.Element)
}
style?: CSSProperties
}
export interface InputNumberComponentProps {
@ -135,6 +137,102 @@ export interface InputNumberComponentProps {
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
}
style?: CSSProperties
}
interface SelectOption {
label?: string
disabled?: boolean
value?: any
key?: string | number
options?: SelectOption[]
[key: string]: any
}
export interface SelectComponentProps {
value?: Array | string | number | boolean | Object
multiple?: boolean
disabled?: boolean
valueKey?: string
size?: InputNumberProps['size']
clearable?: boolean
collapseTags?: boolean
collapseTagsTooltip?: number
multipleLimit?: number
name?: string
effect?: string
autocomplete?: string
placeholder?: string
filterable?: boolean
allowCreate?: boolean
filterMethod?: (query: string, item: any) => boolean
remote?: boolean
remoteMethod?: (query: string) => void
remoteShowSuffix?: boolean
loading?: boolean
loadingText?: string
noMatchText?: string
noDataText?: string
popperClass?: string
reserveKeyword?: boolean
defaultFirstOption?: boolean
popperAppendToBody?: boolean
teleported?: boolean
persistent?: boolean
automaticDropdown?: boolean
clearIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
fitInputWidth?: boolean
suffixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element)
tagType?: 'success' | 'info' | 'warning' | 'danger'
validateEvent?: boolean
placement?:
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end'
maxCollapseTags?: number
/**
* label别名
*/
labelAlias?: string
/**
* value别名
*/
valueAlias?: string
/**
* key别名
*/
keyAlias?: string
/**
* option是否禁用的统一拦截
*/
optionDisabled?: (optin: any, data: any) => boolean
on?: {
change?: (value: string | number | boolean | Object) => void
visibleChange?: (visible: boolean) => void
removeTag?: (tag: any) => void
clear?: () => void
blur?: (event: FocusEvent) => void
focus?: (event: FocusEvent) => void
}
slots?: {
default?: (item: any) => JSX.Element
prefix?: JSX.Element | ((item: any, data: any) => JSX.Element)
empty?: JSX.Element | ((item: any, data: any) => JSX.Element)
}
options?: SelectOption[]
style?: CSSProperties
}
export interface ColProps {
@ -149,7 +247,7 @@ export interface ColProps {
export interface ComponentOptions extends Recordable {
label?: string
value?: FormValueType
value?: any
disabled?: boolean
key?: string | number
children?: ComponentOptions[]

9
src/types/form.d.ts vendored
View File

@ -5,7 +5,8 @@ import {
ComponentName,
InputComponentProps,
AutocompleteComponentProps,
InputNumberComponentProps
InputNumberComponentProps,
SelectComponentProps
} from '@/types/components'
import { FormValueType, FormValueType } from '@/types/form'
import type { AxiosPromise } from 'axios'
@ -52,7 +53,11 @@ export interface FormSchema {
/**
* element-plus文档
*/
componentProps?: InputComponentProps | AutocompleteComponentProps | InputNumberComponentProps
componentProps?:
| InputComponentProps
| AutocompleteComponentProps
| InputNumberComponentProps
| SelectComponentProps
/**
* formItem组件属性element-plus文档

View File

@ -499,48 +499,64 @@ const schema = reactive<FormSchema[]>([
controlsPosition: 'right'
},
value: 10
},
{
field: 'field13',
label: t('formDemo.select'),
component: 'Divider'
},
{
field: 'field14',
label: t('formDemo.default'),
component: 'Select',
componentProps: {
optionDisabled: (item: any, data: any) => {
console.log(item, data)
return false
},
options: [
{
disabled: true,
label: 'option1',
value: '1'
},
{
label: 'option2',
value: '2'
}
]
}
},
{
field: 'field15',
label: t('formDemo.slot'),
component: 'Select',
componentProps: {
options: [
{
label: 'option1',
value: '1'
},
{
label: 'option2',
value: '2'
}
],
slots: {
default: (item) => {
console.log(item)
return (
<>
<span style="float: left">{item.label}</span>
<span style=" float: right; color: var(--el-text-color-secondary); font-size: 13px;">
{item.value}
</span>
</>
)
}
}
}
}
// {
// field: 'field13',
// label: t('formDemo.select'),
// component: 'Divider'
// },
// {
// field: 'field14',
// label: t('formDemo.default'),
// component: 'Select',
// componentProps: {
// options: [
// {
// disabled: true,
// label: 'option1',
// value: '1'
// },
// {
// label: 'option2',
// value: '2'
// }
// ]
// }
// },
// {
// field: 'field15',
// label: t('formDemo.slot'),
// component: 'Select',
// componentProps: {
// options: [
// {
// label: 'option1',
// value: '1'
// },
// {
// label: 'option2',
// value: '2'
// }
// ],
// optionsSlot: true
// }
// },
// {
// field: 'field16',
// label: t('formDemo.selectGroup'),