mirror of
https://gitee.com/bootx/dax-pay-ui.git
synced 2025-09-04 11:26:03 +00:00
feat(BasicForm): Improve ts types for BasicForm (#3426)
* fix(ApiCascader): Resolve api type conflict with labelField/valueField * chore: Improve ts types for BasicForm * fix(ApiCascader): Resolve API type error * chore: Resolve type:check error * chore: fix form type error * fix(ApiRadioGroup): Resolve api type conflict with labelField/valueField * fix(ApiTree): api type error * chore(demo): form basic page schemas use FormSchemaAll * chore: FormSchemaAll to FormSchema * fix: ComponentFormSchemaType * fix: Object literal may only specify known properties --------- Co-authored-by: invalid w <wangjuesix@gmail.com> Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
This commit is contained in:
@@ -6,4 +6,4 @@ enum Api {
|
||||
}
|
||||
|
||||
export const areaRecord = (data: AreaParams) =>
|
||||
defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data });
|
||||
defHttp.post<AreaModel[]>({ url: Api.AREA_RECORD, data });
|
||||
|
@@ -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<any>) => Promise<Option[]>>,
|
||||
type: Function as PropType<(arg?: any) => Promise<Option[]>>,
|
||||
default: null,
|
||||
},
|
||||
numberToString: propTypes.bool,
|
||||
|
@@ -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<OptionsItem[]>>,
|
||||
type: Function as PropType<(arg?: any) => Promise<OptionsItem[]>>,
|
||||
default: null,
|
||||
},
|
||||
params: {
|
||||
|
@@ -18,7 +18,7 @@
|
||||
defineOptions({ name: 'ApiTree' });
|
||||
|
||||
const props = defineProps({
|
||||
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
|
||||
api: { type: Function as PropType<(arg?: any) => Promise<Recordable<any>>> },
|
||||
params: { type: Object },
|
||||
immediate: { type: Boolean, default: true },
|
||||
resultField: { type: String, default: '' },
|
||||
|
@@ -26,7 +26,7 @@
|
||||
defineOptions({ name: 'ApiTreeSelect' });
|
||||
|
||||
const props = defineProps({
|
||||
api: { type: Function as PropType<(arg?: Recordable<any>) => Promise<Recordable<any>>> },
|
||||
api: { type: Function as PropType<(arg?: any) => Promise<Recordable<any>>> },
|
||||
params: { type: Object },
|
||||
immediate: { type: Boolean, default: true },
|
||||
async: { type: Boolean, default: false },
|
||||
|
@@ -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<T extends ComponentType = any> {
|
||||
// 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<T extends ComponentType = any> extends BaseFormSchema<T> {
|
||||
// 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 ComponentType = ComponentType> = T extends any
|
||||
? ComponentFormSchema<T>
|
||||
: never;
|
||||
|
||||
export type FormSchema = ComponentFormSchemaType | SlotFormSchema;
|
||||
|
||||
export type FormSchemaInner = Partial<ComponentFormSchema> &
|
||||
Partial<SlotFormSchema> &
|
||||
|
@@ -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<Tail, `${M}${F}${Uppercase<N>}`>
|
||||
: `${M}${T}`;
|
||||
|
||||
type MethodsNameTransform<T> = {
|
||||
[K in keyof T as K extends `on${string}` ? MethodsNameToCamelCase<K> : never]: T[K];
|
||||
};
|
||||
|
||||
type ExtractPropTypes<T extends Component> = T extends new (...args: any) => any
|
||||
? Omit<InstanceType<T>['$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<T = _CustomComponents> = {
|
||||
[K in keyof T]: T[K] & MethodsNameTransform<T[K]>;
|
||||
};
|
||||
|
||||
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<string, any>;
|
||||
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'];
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@
|
||||
},
|
||||
defaultValue: import.meta.env.MODE || 'development', // 当前环境
|
||||
required: true,
|
||||
component: 'Input',
|
||||
// component: 'Input',
|
||||
slot: 'api',
|
||||
},
|
||||
],
|
||||
|
@@ -35,7 +35,7 @@
|
||||
},
|
||||
{
|
||||
field: '0',
|
||||
component: 'Input',
|
||||
// component: 'Input',
|
||||
label: ' ',
|
||||
slot: 'add',
|
||||
},
|
||||
|
@@ -80,7 +80,7 @@
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
component: 'Input',
|
||||
// component: 'Input',
|
||||
label: '自定义Slot',
|
||||
slot: 'f3',
|
||||
colProps: {
|
||||
|
@@ -137,7 +137,7 @@
|
||||
componentProps: ({ formModel }) => {
|
||||
return {
|
||||
placeholder: '同步f2的值为f1',
|
||||
onChange: (e: ChangeEvent) => {
|
||||
onChange: (e) => {
|
||||
formModel.f2 = e.target.value;
|
||||
},
|
||||
};
|
||||
|
@@ -37,7 +37,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Drawer, Space } from 'ant-design-vue';
|
||||
import { BasicForm, FormSchema, useForm, type FormProps } from '@/components/Form';
|
||||
import { BasicForm, type FormSchema, useForm, type FormProps } from '@/components/Form';
|
||||
import { CollapseContainer } from '@/components/Container';
|
||||
import { PageWrapper } from '@/components/Page';
|
||||
import { areaRecord } from '@/api/demo/cascader';
|
||||
@@ -86,7 +86,7 @@
|
||||
colProps: { span: 8 },
|
||||
componentProps: {
|
||||
getPopupContainer: () => {
|
||||
return document.querySelector('.ant-form');
|
||||
return document.querySelector('.ant-form')!;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -97,7 +97,7 @@
|
||||
colProps: { span: 8 },
|
||||
componentProps: {
|
||||
getPopupContainer: () => {
|
||||
return document.querySelector('.ant-form');
|
||||
return document.querySelector('.ant-form')!;
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -147,7 +147,6 @@
|
||||
componentProps: {
|
||||
api: areaRecord,
|
||||
apiParamKey: 'parentCode',
|
||||
dataField: 'data',
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
initFetchParams: {
|
||||
@@ -166,7 +165,6 @@
|
||||
componentProps: {
|
||||
api: areaRecord,
|
||||
apiParamKey: 'parentCode',
|
||||
dataField: 'data',
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
initFetchParams: {
|
||||
@@ -360,7 +358,7 @@
|
||||
colProps: { span: 24 },
|
||||
componentProps: ({ formActionType }) => {
|
||||
return {
|
||||
onChange: async (val: boolean) => {
|
||||
onChange: (val) => {
|
||||
formActionType.updateSchema([
|
||||
{ field: 'showResetButton', componentProps: { disabled: !val } },
|
||||
{
|
||||
|
@@ -58,7 +58,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { type Recordable } from '@vben/types';
|
||||
import { computed, unref, ref } from 'vue';
|
||||
import { BasicForm, FormSchema, ApiSelect } from '@/components/Form';
|
||||
import { BasicForm, ApiSelect, FormSchema } from '@/components/Form';
|
||||
import { CollapseContainer } from '@/components/Container';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
import { PageWrapper } from '@/components/Page';
|
||||
@@ -308,8 +308,8 @@
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
onChange: (e, v) => {
|
||||
console.log('RadioButtonGroup====>:', e, v);
|
||||
onChange: (e) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -362,7 +362,7 @@
|
||||
component: 'BasicTitle',
|
||||
label: '标题区分',
|
||||
componentProps: {
|
||||
line: true,
|
||||
// line: true,
|
||||
span: true,
|
||||
},
|
||||
colProps: {
|
||||
@@ -441,7 +441,7 @@
|
||||
componentProps: {
|
||||
api: areaRecord,
|
||||
apiParamKey: 'parentCode',
|
||||
dataField: 'data',
|
||||
// dataField: 'data',
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
initFetchParams: {
|
||||
@@ -457,7 +457,7 @@
|
||||
},
|
||||
{
|
||||
field: 'field31',
|
||||
component: 'Input',
|
||||
// component: 'Input',
|
||||
label: '下拉本地搜索',
|
||||
helpMessage: ['ApiSelect组件', '远程数据源本地搜索', '只发起一次请求获取所有选项'],
|
||||
required: true,
|
||||
@@ -466,10 +466,13 @@
|
||||
span: 8,
|
||||
},
|
||||
defaultValue: '0',
|
||||
componentProps: {
|
||||
onOptionsChange() {},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'field32',
|
||||
component: 'Input',
|
||||
// component: 'Input',
|
||||
label: '下拉远程搜索',
|
||||
helpMessage: ['ApiSelect组件', '将关键词发送到接口进行远程搜索'],
|
||||
required: true,
|
||||
@@ -578,8 +581,8 @@
|
||||
// use id as value
|
||||
valueField: 'id',
|
||||
isBtn: true,
|
||||
onChange: (e, v) => {
|
||||
console.log('ApiRadioGroup====>:', e, v);
|
||||
onChange: (e) => {
|
||||
console.log('ApiRadioGroup====>:', e);
|
||||
},
|
||||
},
|
||||
colProps: {
|
||||
@@ -684,7 +687,7 @@
|
||||
},
|
||||
{
|
||||
field: 'selectA',
|
||||
component: 'Select',
|
||||
// component: 'Select',
|
||||
label: '互斥SelectA',
|
||||
slot: 'selectA',
|
||||
defaultValue: [],
|
||||
@@ -694,7 +697,7 @@
|
||||
},
|
||||
{
|
||||
field: 'selectB',
|
||||
component: 'Select',
|
||||
// component: 'Select',
|
||||
label: '互斥SelectB',
|
||||
slot: 'selectB',
|
||||
defaultValue: [],
|
||||
|
@@ -40,8 +40,8 @@ export const schemas: FormSchema[] = [
|
||||
colProps,
|
||||
subLabel: '( 选填 )',
|
||||
componentProps: {
|
||||
formatter: (value: string) => (value ? `${value}%` : ''),
|
||||
parser: (value: string) => value.replace('%', ''),
|
||||
formatter: (value: string | number) => (value ? `${value}%` : ''),
|
||||
parser: (value: string) => Number(value.replace('%', '')),
|
||||
placeholder: '请输入',
|
||||
},
|
||||
},
|
||||
|
@@ -230,7 +230,7 @@ export function getMergeHeaderColumns(): BasicColumn[] {
|
||||
];
|
||||
}
|
||||
export const getAdvanceSchema = (itemNumber = 6): FormSchema[] => {
|
||||
const arr: any = [];
|
||||
const arr: FormSchema[] = [];
|
||||
for (let index = 0; index < itemNumber; index++) {
|
||||
arr.push({
|
||||
field: `field${index}`,
|
||||
@@ -252,7 +252,7 @@ export function getFormConfig(): Partial<FormProps> {
|
||||
{
|
||||
field: `field11`,
|
||||
label: `Slot示例`,
|
||||
component: 'Select',
|
||||
// component: 'Select',
|
||||
slot: 'custom',
|
||||
colProps: {
|
||||
xl: 12,
|
||||
|
Reference in New Issue
Block a user