diff --git a/src/api/demo/cascader.ts b/src/api/demo/cascader.ts index 65a1f480..198853d5 100644 --- a/src/api/demo/cascader.ts +++ b/src/api/demo/cascader.ts @@ -6,4 +6,4 @@ enum Api { } export const areaRecord = (data: AreaParams) => - defHttp.post({ url: Api.AREA_RECORD, data }); + defHttp.post({ url: Api.AREA_RECORD, data }); diff --git a/src/components/Form/src/components/ApiCascader.vue b/src/components/Form/src/components/ApiCascader.vue index 1842e5a8..04b423cd 100644 --- a/src/components/Form/src/components/ApiCascader.vue +++ b/src/components/Form/src/components/ApiCascader.vue @@ -31,11 +31,12 @@ import { useI18n } from '@/hooks/web/useI18n'; interface Option { - value: string; - label: string; + value?: string; + label?: string; loading?: boolean; isLeaf?: boolean; children?: Option[]; + [key: string]: any; } defineOptions({ name: 'ApiCascader' }); @@ -45,7 +46,7 @@ type: Array, }, api: { - type: Function as PropType<(arg?: Recordable) => Promise>, + type: Function as PropType<(arg?: any) => Promise>, default: null, }, numberToString: propTypes.bool, diff --git a/src/components/Form/src/components/ApiRadioGroup.vue b/src/components/Form/src/components/ApiRadioGroup.vue index 2775041e..3cdbb8c8 100644 --- a/src/components/Form/src/components/ApiRadioGroup.vue +++ b/src/components/Form/src/components/ApiRadioGroup.vue @@ -27,13 +27,18 @@ import { propTypes } from '@/utils/propTypes'; import { get, omit, isEqual } from 'lodash-es'; - type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean }; + type OptionsItem = { + label?: string; + value?: string | number | boolean; + disabled?: boolean; + [key: string]: any; + }; defineOptions({ name: 'ApiRadioGroup' }); const props = defineProps({ api: { - type: Function as PropType<(arg?: any | string) => Promise>, + type: Function as PropType<(arg?: any) => Promise>, default: null, }, params: { diff --git a/src/components/Form/src/components/ApiTree.vue b/src/components/Form/src/components/ApiTree.vue index 5a476b4b..d2b9597c 100644 --- a/src/components/Form/src/components/ApiTree.vue +++ b/src/components/Form/src/components/ApiTree.vue @@ -18,7 +18,7 @@ defineOptions({ name: 'ApiTree' }); const props = defineProps({ - api: { type: Function as PropType<(arg?: Recordable) => Promise>> }, + api: { type: Function as PropType<(arg?: any) => Promise>> }, params: { type: Object }, immediate: { type: Boolean, default: true }, resultField: { type: String, default: '' }, diff --git a/src/components/Form/src/components/ApiTreeSelect.vue b/src/components/Form/src/components/ApiTreeSelect.vue index de45477a..c4264553 100644 --- a/src/components/Form/src/components/ApiTreeSelect.vue +++ b/src/components/Form/src/components/ApiTreeSelect.vue @@ -26,7 +26,7 @@ defineOptions({ name: 'ApiTreeSelect' }); const props = defineProps({ - api: { type: Function as PropType<(arg?: Recordable) => Promise>> }, + api: { type: Function as PropType<(arg?: any) => Promise>> }, params: { type: Object }, immediate: { type: Boolean, default: true }, async: { type: Boolean, default: false }, diff --git a/src/components/Form/src/types/form.ts b/src/components/Form/src/types/form.ts index 0838de8b..7beabe2c 100644 --- a/src/components/Form/src/types/form.ts +++ b/src/components/Form/src/types/form.ts @@ -2,7 +2,7 @@ import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface'; import type { VNode, CSSProperties } from 'vue'; import type { ButtonProps as AntdButtonProps } from '@/components/Button'; import type { FormItem } from './formItem'; -import type { ColEx, ComponentType } from './'; +import type { ColEx, ComponentType, ComponentProps } from './'; import type { TableActionType } from '@/components/Table/src/types/table'; import type { RowProps } from 'ant-design-vue/lib/grid/Row'; @@ -130,7 +130,7 @@ export type RenderOpts = { [key: string]: any; }; -interface BaseFormSchema { +interface BaseFormSchema { // Field name field: string; // Extra Fields name[] @@ -161,8 +161,8 @@ interface BaseFormSchema { tableAction: TableActionType; formActionType: FormActionType; formModel: Recordable; - }) => Recordable) - | object; + }) => ComponentProps[T]) + | ComponentProps[T]; // Required required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); @@ -224,17 +224,23 @@ interface BaseFormSchema { dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]; } -export interface ComponentFormSchema extends BaseFormSchema { +export interface ComponentFormSchema extends BaseFormSchema { // render component - component: ComponentType; + component: T; + // fix: Object literal may only specify known properties, and 'slot' does not exist in type 'ComponentFormSchema'. + slot?: string; } export interface SlotFormSchema extends BaseFormSchema { - // Custom slot, in from-item + // Custom slot, in form-item slot: string; } -export type FormSchema = ComponentFormSchema | SlotFormSchema; +type ComponentFormSchemaType = T extends any + ? ComponentFormSchema + : never; + +export type FormSchema = ComponentFormSchemaType | SlotFormSchema; export type FormSchemaInner = Partial & Partial & diff --git a/src/components/Form/src/types/index.ts b/src/components/Form/src/types/index.ts index 5e19fb54..50d14007 100644 --- a/src/components/Form/src/types/index.ts +++ b/src/components/Form/src/types/index.ts @@ -1,3 +1,5 @@ +import type { Component, VNodeProps } from 'vue'; + type ColSpanType = number | string; export interface ColEx { style?: any; @@ -80,43 +82,95 @@ export interface ColEx { xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType; } -export type ComponentType = - | 'Input' - | 'InputGroup' - | 'InputPassword' - | 'InputSearch' - | 'InputTextArea' - | 'InputNumber' - | 'InputCountDown' - | 'Select' - | 'ApiSelect' - | 'TreeSelect' - | 'ApiTree' - | 'ApiTreeSelect' - | 'ApiRadioGroup' - | 'RadioButtonGroup' - | 'RadioGroup' - | 'Checkbox' - | 'CheckboxGroup' - | 'AutoComplete' - | 'ApiCascader' - | 'Cascader' - | 'DatePicker' - | 'MonthPicker' - | 'RangePicker' - | 'WeekPicker' - | 'TimePicker' - | 'TimeRangePicker' - | 'Switch' - | 'StrengthMeter' - | 'Upload' - | 'ImageUpload' - | 'IconPicker' - | 'Render' - | 'Slider' - | 'Rate' - | 'Divider' - | 'ApiTransfer' - | 'Transfer' - | 'CropperAvatar' - | 'BasicTitle'; +export type ComponentType = keyof ComponentProps; + +type MethodsNameToCamelCase< + T extends string, + M extends string = '', +> = T extends `${infer F}-${infer N}${infer Tail}` + ? MethodsNameToCamelCase}`> + : `${M}${T}`; + +type MethodsNameTransform = { + [K in keyof T as K extends `on${string}` ? MethodsNameToCamelCase : never]: T[K]; +}; + +type ExtractPropTypes = T extends new (...args: any) => any + ? Omit['$props'], keyof VNodeProps> + : never; + +interface _CustomComponents { + ApiSelect: ExtractPropTypes<(typeof import('../components/ApiSelect.vue'))['default']>; + ApiTree: ExtractPropTypes<(typeof import('../components/ApiTree.vue'))['default']>; + ApiTreeSelect: ExtractPropTypes<(typeof import('../components/ApiTreeSelect.vue'))['default']>; + ApiRadioGroup: ExtractPropTypes<(typeof import('../components/ApiRadioGroup.vue'))['default']>; + RadioButtonGroup: ExtractPropTypes< + (typeof import('../components/RadioButtonGroup.vue'))['default'] + >; + ApiCascader: ExtractPropTypes<(typeof import('../components/ApiCascader.vue'))['default']>; + StrengthMeter: ExtractPropTypes< + (typeof import('@/components/StrengthMeter/src/StrengthMeter.vue'))['default'] + >; + Upload: ExtractPropTypes<(typeof import('@/components/Upload/src/BasicUpload.vue'))['default']>; + ImageUpload: ExtractPropTypes< + (typeof import('@/components/Upload/src/components/ImageUpload.vue'))['default'] + >; + IconPicker: ExtractPropTypes<(typeof import('@/components/Icon/src/IconPicker.vue'))['default']>; + ApiTransfer: ExtractPropTypes<(typeof import('../components/ApiTransfer.vue'))['default']>; + CropperAvatar: ExtractPropTypes< + (typeof import('@/components/Cropper/src/CropperAvatar.vue'))['default'] + >; + BasicTitle: ExtractPropTypes<(typeof import('@/components/Basic/src/BasicTitle.vue'))['default']>; + InputCountDown: ExtractPropTypes< + (typeof import('@/components/CountDown/src/CountdownInput.vue'))['default'] + >; +} + +type CustomComponents = { + [K in keyof T]: T[K] & MethodsNameTransform; +}; + +export interface ComponentProps { + Input: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['default']>; + InputGroup: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputGroup']>; + InputPassword: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputPassword']>; + InputSearch: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['InputSearch']>; + InputTextArea: ExtractPropTypes<(typeof import('ant-design-vue/es/input'))['Textarea']>; + InputNumber: ExtractPropTypes<(typeof import('ant-design-vue/es/input-number'))['default']>; + InputCountDown: CustomComponents['InputCountDown'] & ComponentProps['Input']; + Select: ExtractPropTypes<(typeof import('ant-design-vue/es/select'))['default']>; + ApiSelect: CustomComponents['ApiSelect'] & ComponentProps['Select']; + TreeSelect: ExtractPropTypes<(typeof import('ant-design-vue/es/tree-select'))['default']>; + ApiTree: CustomComponents['ApiTree'] & + ExtractPropTypes<(typeof import('ant-design-vue/es/tree'))['default']>; + ApiTreeSelect: CustomComponents['ApiTreeSelect'] & ComponentProps['TreeSelect']; + ApiRadioGroup: CustomComponents['ApiRadioGroup'] & ComponentProps['RadioGroup']; + RadioButtonGroup: CustomComponents['RadioButtonGroup'] & ComponentProps['RadioGroup']; + RadioGroup: ExtractPropTypes<(typeof import('ant-design-vue/es/radio'))['RadioGroup']>; + Checkbox: ExtractPropTypes<(typeof import('ant-design-vue/es/checkbox'))['default']>; + CheckboxGroup: ExtractPropTypes<(typeof import('ant-design-vue/es/checkbox'))['CheckboxGroup']>; + AutoComplete: ExtractPropTypes<(typeof import('ant-design-vue/es/auto-complete'))['default']>; + ApiCascader: CustomComponents['ApiCascader'] & ComponentProps['Cascader']; + Cascader: ExtractPropTypes<(typeof import('ant-design-vue/es/cascader'))['default']>; + DatePicker: ExtractPropTypes<(typeof import('ant-design-vue/es/date-picker'))['default']>; + MonthPicker: ExtractPropTypes<(typeof import('ant-design-vue/es/date-picker'))['MonthPicker']>; + RangePicker: ExtractPropTypes<(typeof import('ant-design-vue/es/date-picker'))['RangePicker']>; + WeekPicker: ExtractPropTypes<(typeof import('ant-design-vue/es/date-picker'))['WeekPicker']>; + TimePicker: ExtractPropTypes<(typeof import('ant-design-vue/es/time-picker'))['TimePicker']>; + TimeRangePicker: ExtractPropTypes< + (typeof import('ant-design-vue/es/time-picker'))['TimeRangePicker'] + >; + Switch: ExtractPropTypes<(typeof import('ant-design-vue/es/switch'))['default']>; + StrengthMeter: CustomComponents['StrengthMeter'] & ComponentProps['InputPassword']; + Upload: CustomComponents['Upload']; + ImageUpload: CustomComponents['ImageUpload']; + IconPicker: CustomComponents['IconPicker']; + Render: Record; + Slider: ExtractPropTypes<(typeof import('ant-design-vue/es/slider'))['default']>; + Rate: ExtractPropTypes<(typeof import('ant-design-vue/es/rate'))['default']>; + Divider: ExtractPropTypes<(typeof import('ant-design-vue/es/divider'))['default']>; + ApiTransfer: CustomComponents['ApiTransfer'] & ComponentProps['Transfer']; + Transfer: ExtractPropTypes<(typeof import('ant-design-vue/es/transfer'))['default']>; + CropperAvatar: CustomComponents['CropperAvatar']; + BasicTitle: CustomComponents['BasicTitle']; +} diff --git a/src/layouts/default/header/components/ChangeApi/index.vue b/src/layouts/default/header/components/ChangeApi/index.vue index 8b5dfa4c..5cbe07dd 100644 --- a/src/layouts/default/header/components/ChangeApi/index.vue +++ b/src/layouts/default/header/components/ChangeApi/index.vue @@ -54,7 +54,7 @@ }, defaultValue: import.meta.env.MODE || 'development', // 当前环境 required: true, - component: 'Input', + // component: 'Input', slot: 'api', }, ], diff --git a/src/views/demo/form/AppendForm.vue b/src/views/demo/form/AppendForm.vue index 7b0e47ed..a7cd8887 100644 --- a/src/views/demo/form/AppendForm.vue +++ b/src/views/demo/form/AppendForm.vue @@ -35,7 +35,7 @@ }, { field: '0', - component: 'Input', + // component: 'Input', label: ' ', slot: 'add', }, diff --git a/src/views/demo/form/CustomerForm.vue b/src/views/demo/form/CustomerForm.vue index 797c91ec..c7f766be 100644 --- a/src/views/demo/form/CustomerForm.vue +++ b/src/views/demo/form/CustomerForm.vue @@ -80,7 +80,7 @@ }, { field: 'field3', - component: 'Input', + // component: 'Input', label: '自定义Slot', slot: 'f3', colProps: { diff --git a/src/views/demo/form/DynamicForm.vue b/src/views/demo/form/DynamicForm.vue index 2e9b9529..1f3dbacc 100644 --- a/src/views/demo/form/DynamicForm.vue +++ b/src/views/demo/form/DynamicForm.vue @@ -137,7 +137,7 @@ componentProps: ({ formModel }) => { return { placeholder: '同步f2的值为f1', - onChange: (e: ChangeEvent) => { + onChange: (e) => { formModel.f2 = e.target.value; }, }; diff --git a/src/views/demo/form/UseForm.vue b/src/views/demo/form/UseForm.vue index 8f434267..2a57f35c 100644 --- a/src/views/demo/form/UseForm.vue +++ b/src/views/demo/form/UseForm.vue @@ -37,7 +37,7 @@