diff --git a/src/components/Form/src/Form copy.vue b/src/components/Form/src/Form copy.vue new file mode 100644 index 0000000..94fe848 --- /dev/null +++ b/src/components/Form/src/Form copy.vue @@ -0,0 +1,302 @@ + + + diff --git a/src/components/Form/src/Form.vue b/src/components/Form/src/Form.vue index 5686668..0ce1af2 100644 --- a/src/components/Form/src/Form.vue +++ b/src/components/Form/src/Form.vue @@ -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) + // 如果是select组件,并且没有自定义模板,自动渲染options + 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 ( - <> - {item.label} - - {{ - content: () => , - default: () => ( - - ) - }} - - - ) - } - } + // if (item?.labelMessage) { + // formItemSlots.label = () => { + // return ( + // <> + // {item.label} + // + // {{ + // content: () => , + // default: () => ( + // + // ) + // }} + // + // + // ) + // } + // } return ( {{ - ...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 }} @@ -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': diff --git a/src/components/Form/src/components/useRenderSelect.tsx b/src/components/Form/src/components/useRenderSelect.tsx index e4b5a26..9421c0e 100644 --- a/src/components/Form/src/components/useRenderSelect.tsx +++ b/src/components/Form/src/components/useRenderSelect.tsx @@ -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 ( @@ -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 }} diff --git a/src/components/Form/src/helper.ts b/src/components/Form/src/helper.ts index f58b7f1..f8c3a64 100644 --- a/src/components/Form/src/helper.ts +++ b/src/components/Form/src/helper.ts @@ -1,5 +1,5 @@ import { useI18n } from '@/hooks/web/useI18n' -import type { Slots } from 'vue' +import { unref, type Slots } from 'vue' import { getSlot } from '@/utils/tsxHelper' import { PlaceholderMoel } from './types' import { FormSchema } from '@/types/form' @@ -74,12 +74,14 @@ export const setGridProp = (col: ColProps = {}): ColProps => { */ export const setComponentProps = (item: FormSchema): Recordable => { // const notNeedClearable = ['ColorPicker'] - const componentProps = { + const componentProps: Recordable = { clearable: true, ...item.componentProps } // 需要删除额外的属性 - delete componentProps?.slots + if (componentProps.slots) { + delete componentProps.slots + } return componentProps } @@ -93,8 +95,8 @@ export const setItemComponentSlots = (formModel: any, slotsProps: Recordable = { for (const key in slotsProps) { if (slotsProps[key]) { if (isFunction(slotsProps[key])) { - slotObj[key] = () => { - return slotsProps[key]?.(formModel) + slotObj[key] = (item: any) => { + return slotsProps[key]?.(unref(item?.item) || undefined, formModel) } } else { slotObj[key] = () => { diff --git a/src/locales/en.ts b/src/locales/en.ts index 6f097b6..26a9a02 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -214,7 +214,9 @@ export default { default: 'Default', icon: 'Icon', mixed: 'Mixed', + password: 'Password', textarea: 'Textarea', + remoteSearch: 'Remote search', slot: 'Slot', position: 'Position', autocomplete: 'Autocomplete', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 6fa0531..165e268 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -214,7 +214,9 @@ export default { default: '默认', icon: '图标', mixed: '复合型', + password: '密码框', textarea: '多行文本', + remoteSearch: '远程搜索', slot: '插槽', position: '位置', autocomplete: '自动补全', diff --git a/src/types/components.d.ts b/src/types/components.d.ts index fbc7c49..e132fad 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -1,5 +1,5 @@ import { CSSProperties } from 'vue' -import { InputProps } from 'element-plus' +import { InputProps, AutocompleteProps, InputNumberProps } from 'element-plus' export enum ComponentNameEnum { RADIO = 'Radio', @@ -25,6 +25,16 @@ export enum ComponentNameEnum { EDITOR = 'Editor' } +type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K + ? K extends string + ? K extends `${infer A}_${infer B}` + ? `${Capitalize>}${Capitalize>}` + : Capitalize> + : never + : never + +export type ComponentName = CamelCaseComponentName + export interface InputComponentProps { value?: string | number maxlength?: number | string @@ -37,8 +47,8 @@ export interface InputComponentProps { showPassword?: boolean disabled?: boolean size?: InputProps['size'] - prefixIcon?: string | JSX.Element | ((data: T | any) => string | JSX.Element) - suffixIcon?: string | JSX.Element | ((data: T | any) => string | JSX.Element) + prefixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element) + suffixIcon?: string | JSX.Element | ((item: any, data: any) => string | JSX.Element) type?: InputProps['type'] rows?: number autosize?: boolean | { Pows?: numer; maxRows?: number } @@ -63,22 +73,167 @@ export interface InputComponentProps { input?: (value: string | number) => void } slots?: { - prefix?: JSX.Element | ((data: T | any) => JSX.Element) - suffix?: JSX.Element | ((data: T | any) => JSX.Element) - prepend?: JSX.Element | ((data: T | any) => JSX.Element) - append?: JSX.Element | ((data: T | any) => JSX.Element) + prefix?: JSX.Element | ((item: any, data: any) => JSX.Element) + suffix?: JSX.Element | ((item: any, data: any) => JSX.Element) + prepend?: JSX.Element | ((item: any, data: any) => JSX.Element) + append?: JSX.Element | ((item: any, data: any) => JSX.Element) } + style?: CSSProperties } -type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K - ? K extends string - ? K extends `${infer A}_${infer B}` - ? `${Capitalize>}${Capitalize>}` - : Capitalize> - : never - : never +export interface AutocompleteComponentProps { + value?: string + placeholder?: string + clearable?: boolean + disabled?: boolean + valueKey?: string + debounce?: number + placement?: AutocompleteProps['placement'] + fetchSuggestions?: (queryString: string, callback: (data: string[]) => void) => void + triggerOnFocus?: boolean + selectWhenUnmatched?: boolean + name?: string + label?: string + hideLoading?: boolean + popperClass?: string + popperAppendToBody?: boolean + teleported?: boolean + highlightFirstItem?: boolean + fitInputWidth?: boolean + on?: { + select?: (item: any) => void + change?: (value: string | number) => void + } + slots?: { + default?: JSX.Element | ((item: any, data: any) => JSX.Element) + prefix?: JSX.Element | ((item: any, data: any) => JSX.Element) + suffix?: JSX.Element | ((item: any, data: any) => JSX.Element) + prepend?: JSX.Element | ((item: any, data: any) => JSX.Element) + append?: JSX.Element | ((item: any, data: any) => JSX.Element) + } + style?: CSSProperties +} -export type ComponentName = CamelCaseComponentName +export interface InputNumberComponentProps { + value?: number + min?: number + max?: number + step?: number + stepStrictly?: boolean + precision?: number + size?: InputNumberProps['size'] + readonly?: boolean + disabled?: boolean + controls?: boolean + controlsPosition?: InputNumberProps['controlsPosition'] + name?: string + label?: string + placeholder?: string + id?: string + valueOnClear?: number | null | 'min' | 'max' + validateEvent?: boolean + on?: { + change?: (currentValue: number | undefined, oldValue: number | undefined) => void + 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 { span?: number @@ -92,7 +247,7 @@ export interface ColProps { export interface ComponentOptions extends Recordable { label?: string - value?: FormValueType + value?: any disabled?: boolean key?: string | number children?: ComponentOptions[] diff --git a/src/types/form.d.ts b/src/types/form.d.ts index ee38385..5530c99 100644 --- a/src/types/form.d.ts +++ b/src/types/form.d.ts @@ -3,8 +3,10 @@ import { ColProps, ComponentProps, ComponentName, - ComponentNameEnum, - InputComponentProps + InputComponentProps, + AutocompleteComponentProps, + InputNumberComponentProps, + SelectComponentProps } from '@/types/components' import { FormValueType, FormValueType } from '@/types/form' import type { AxiosPromise } from 'axios' @@ -28,29 +30,57 @@ export type FormItemProps = { } export interface FormSchema { - // 唯一值 + /** + * 唯一标识 + */ field: string - // 标题 - label?: string - // 提示 - labelMessage?: string - // col组件属性 - colProps?: ColProps - // 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档 - // componentProps?: { slots?: Recordable } & ComponentProps /** - * 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档 + * 标题 + */ + label?: string + + /** + * 提示信息 + */ + labelMessage?: string + + /** + * col组件属性 + */ + colProps?: ColProps + + /** + * 表单组件属性,具体可以查看element-plus文档 + */ + componentProps?: + | InputComponentProps + | AutocompleteComponentProps + | InputNumberComponentProps + | SelectComponentProps + + /** + * formItem组件属性,具体可以查看element-plus文档 */ - componentProps?: InputComponentProps - // formItem组件属性 formItemProps?: FormItemProps - // 渲染的组件 + + /** + * 渲染的组件名称 + */ component?: ComponentName - // 初始值 + + /** + * 初始值 + */ value?: FormValueType - // 是否隐藏 + + /** + * 是否隐藏 + */ hidden?: boolean - // 远程加载下拉项 + + /** + * @returns 远程加载下拉项 + */ api?: () => AxiosPromise } diff --git a/src/views/Components/Form/DefaultForm.vue b/src/views/Components/Form/DefaultForm.vue index 7415bb3..3902705 100644 --- a/src/views/Components/Form/DefaultForm.vue +++ b/src/views/Components/Form/DefaultForm.vue @@ -1,4 +1,4 @@ -