Просмотр исходного кода

feat: Schema 中 componentProps 随注册组件联动类型提示

dullathanol 2 месяцев назад
Родитель
Сommit
794b103aa9

+ 96 - 0
apps/web-antd/src/adapter/form-schema.ts

@@ -0,0 +1,96 @@
+import type {
+  AutoCompleteProps,
+  ButtonProps,
+  CascaderProps,
+  CheckboxGroupProps,
+  CheckboxProps,
+  DatePickerProps,
+  DividerProps,
+  InputNumberProps,
+  InputProps,
+  MentionsProps,
+  RadioGroupProps,
+  RadioProps,
+  RateProps,
+  SelectProps,
+  SpaceProps,
+  SwitchProps,
+  TextAreaProps,
+  TimePickerProps,
+  TreeSelectProps,
+  UploadProps,
+} from 'ant-design-vue';
+import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
+
+import type { Component } from 'vue';
+
+import type {
+  ApiComponentSharedProps,
+  VbenFormSchema as CoreFormSchema,
+  IconPickerProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+/**
+ * 对象形式:使用适配器里为各 `component` 声明的 Props 类型 `P`;
+ * 与 `Record<string, any>` 相交是为了满足核心库 `MaybeComponentProps` 的索引签名。
+ * 函数形式:通过联合 `CoreFormSchema['componentProps']`,与表单核心对动态 `componentProps` 的约定保持一致。
+ */
+type SchemaComponentProps<P> =
+  | CoreFormSchema<ComponentType>['componentProps']
+  | (P & Record<string, any>);
+
+/**
+ * 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
+ */
+interface FormSchemaComponentPropsMap {
+  ApiCascader: ApiComponentSharedProps & CascaderProps;
+  ApiSelect: ApiComponentSharedProps & SelectProps;
+  ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
+  AutoComplete: AutoCompleteProps;
+  Cascader: CascaderProps;
+  Checkbox: CheckboxProps;
+  CheckboxGroup: CheckboxGroupProps;
+  DatePicker: DatePickerProps;
+  DefaultButton: ButtonProps;
+  Divider: DividerProps;
+  IconPicker: IconPickerProps;
+  Input: InputProps;
+  InputNumber: InputNumberProps;
+  InputPassword: InputProps;
+  Mentions: MentionsProps;
+  PrimaryButton: ButtonProps;
+  Radio: RadioProps;
+  RadioGroup: RadioGroupProps;
+  RangePicker: RangePickerProps;
+  Rate: RateProps;
+  Select: SelectProps;
+  Space: SpaceProps;
+  Switch: SwitchProps;
+  Textarea: TextAreaProps;
+  TimePicker: TimePickerProps;
+  TreeSelect: TreeSelectProps;
+  Upload: UploadProps;
+}
+
+type BaseSchema = Omit<
+  CoreFormSchema<ComponentType>,
+  'component' | 'componentProps'
+>;
+
+type RegisteredName = keyof FormSchemaComponentPropsMap;
+
+type DiscriminatedFormSchema = {
+  [K in RegisteredName]: BaseSchema & {
+    component: K;
+    componentProps?: SchemaComponentProps<FormSchemaComponentPropsMap[K]>;
+  };
+}[RegisteredName];
+
+type FallbackFormSchema = BaseSchema & {
+  component: Component | Exclude<ComponentType, RegisteredName>;
+  componentProps?: CoreFormSchema<ComponentType>['componentProps'];
+};
+
+export type VbenFormSchema = DiscriminatedFormSchema | FallbackFormSchema;

+ 10 - 7
apps/web-antd/src/adapter/form.ts

@@ -1,9 +1,7 @@
-import type {
-  VbenFormSchema as FormSchema,
-  VbenFormProps,
-} from '@vben/common-ui';
+import type { VbenFormProps as CoreFormProps } from '@vben/common-ui';
 
 import type { ComponentType } from './component';
+import type { VbenFormSchema } from './form-schema';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -41,9 +39,14 @@ async function initSetupVbenForm() {
   });
 }
 
-const useVbenForm = useForm<ComponentType>;
+type VbenFormProps = Omit<CoreFormProps<ComponentType>, 'schema'> & {
+  schema?: VbenFormSchema[];
+};
+
+function useVbenForm(options: VbenFormProps) {
+  return useForm<ComponentType>(options as CoreFormProps<ComponentType>);
+}
 
 export { initSetupVbenForm, useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<ComponentType>;
-export type { VbenFormProps };
+export type { VbenFormProps, VbenFormSchema };

+ 96 - 0
apps/web-antdv-next/src/adapter/form-schema.ts

@@ -0,0 +1,96 @@
+import type {
+  AutoCompleteProps,
+  ButtonProps,
+  CascaderProps,
+  CheckboxGroupProps,
+  CheckboxProps,
+  DatePickerProps,
+  DividerProps,
+  InputNumberProps,
+  InputProps,
+  MentionsProps,
+  RadioGroupProps,
+  RadioProps,
+  RangePickerProps,
+  RateProps,
+  SelectProps,
+  SpaceProps,
+  SwitchProps,
+  TextAreaProps,
+  TimePickerProps,
+  TreeSelectProps,
+  UploadProps,
+} from 'antdv-next';
+
+import type { Component } from 'vue';
+
+import type {
+  ApiComponentSharedProps,
+  VbenFormSchema as CoreFormSchema,
+  IconPickerProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+/**
+ * 对象形式:使用适配器里为各 `component` 声明的 Props 类型 `P`;
+ * 与 `Record<string, any>` 相交是为了满足核心库 `MaybeComponentProps` 的索引签名。
+ * 函数形式:通过联合 `CoreFormSchema['componentProps']`,与表单核心对动态 `componentProps` 的约定保持一致。
+ */
+type SchemaComponentProps<P> =
+  | CoreFormSchema<ComponentType>['componentProps']
+  | (P & Record<string, any>);
+
+/**
+ * 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
+ */
+interface FormSchemaComponentPropsMap {
+  ApiCascader: ApiComponentSharedProps & CascaderProps;
+  ApiSelect: ApiComponentSharedProps & SelectProps;
+  ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
+  AutoComplete: AutoCompleteProps;
+  Cascader: CascaderProps;
+  Checkbox: CheckboxProps;
+  CheckboxGroup: CheckboxGroupProps;
+  DatePicker: DatePickerProps;
+  DefaultButton: ButtonProps;
+  Divider: DividerProps;
+  IconPicker: IconPickerProps;
+  Input: InputProps;
+  InputNumber: InputNumberProps;
+  InputPassword: InputProps;
+  Mentions: MentionsProps;
+  PrimaryButton: ButtonProps;
+  Radio: RadioProps;
+  RadioGroup: RadioGroupProps;
+  RangePicker: RangePickerProps;
+  Rate: RateProps;
+  Select: SelectProps;
+  Space: SpaceProps;
+  Switch: SwitchProps;
+  Textarea: TextAreaProps;
+  TimePicker: TimePickerProps;
+  TreeSelect: TreeSelectProps;
+  Upload: UploadProps;
+}
+
+type BaseSchema = Omit<
+  CoreFormSchema<ComponentType>,
+  'component' | 'componentProps'
+>;
+
+type RegisteredName = keyof FormSchemaComponentPropsMap;
+
+type DiscriminatedFormSchema = {
+  [K in RegisteredName]: BaseSchema & {
+    component: K;
+    componentProps?: SchemaComponentProps<FormSchemaComponentPropsMap[K]>;
+  };
+}[RegisteredName];
+
+type FallbackFormSchema = BaseSchema & {
+  component: Component | Exclude<ComponentType, RegisteredName>;
+  componentProps?: CoreFormSchema<ComponentType>['componentProps'];
+};
+
+export type VbenFormSchema = DiscriminatedFormSchema | FallbackFormSchema;

+ 10 - 7
apps/web-antdv-next/src/adapter/form.ts

@@ -1,9 +1,7 @@
-import type {
-  VbenFormSchema as FormSchema,
-  VbenFormProps,
-} from '@vben/common-ui';
+import type { VbenFormProps as CoreFormProps } from '@vben/common-ui';
 
 import type { ComponentType } from './component';
+import type { VbenFormSchema } from './form-schema';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -41,9 +39,14 @@ async function initSetupVbenForm() {
   });
 }
 
-const useVbenForm = useForm<ComponentType>;
+type VbenFormProps = Omit<CoreFormProps<ComponentType>, 'schema'> & {
+  schema?: VbenFormSchema[];
+};
+
+function useVbenForm(options: VbenFormProps) {
+  return useForm<ComponentType>(options as CoreFormProps<ComponentType>);
+}
 
 export { initSetupVbenForm, useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<ComponentType>;
-export type { VbenFormProps };
+export type { VbenFormProps, VbenFormSchema };

+ 80 - 0
apps/web-ele/src/adapter/form-schema.ts

@@ -0,0 +1,80 @@
+import type {
+  CheckboxGroupProps,
+  CheckboxProps,
+  DatePickerProps,
+  DividerProps,
+  ElTimePicker,
+  ElTreeSelect,
+  InputNumberProps,
+  InputProps,
+  RadioGroupProps,
+  SelectV2Props,
+  SpaceProps,
+  SwitchProps,
+  UploadProps,
+} from 'element-plus';
+
+import type { Component } from 'vue';
+
+import type {
+  ApiComponentSharedProps,
+  VbenFormSchema as CoreFormSchema,
+  IconPickerProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+type ElTreeSelectSchemaProps = InstanceType<typeof ElTreeSelect>['$props'];
+type ElTimePickerSchemaProps = InstanceType<typeof ElTimePicker>['$props'];
+
+/**
+ * 对象形式:使用适配器里为各 `component` 声明的 Props 类型 `P`;
+ * 与 `Record<string, any>` 相交是为了满足核心库 `MaybeComponentProps` 的索引签名。
+ * 函数形式:通过联合 `CoreFormSchema['componentProps']`,与表单核心对动态 `componentProps` 的约定保持一致。
+ */
+type SchemaComponentProps<P> =
+  | CoreFormSchema<ComponentType>['componentProps']
+  | (P & Record<string, any>);
+
+/**
+ * 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
+ */
+interface FormSchemaComponentPropsMap {
+  ApiSelect: ApiComponentSharedProps & SelectV2Props;
+  ApiTreeSelect: ApiComponentSharedProps & ElTreeSelectSchemaProps;
+  Checkbox: CheckboxProps;
+  CheckboxGroup: CheckboxGroupProps;
+  DatePicker: DatePickerProps;
+  Divider: DividerProps;
+  IconPicker: IconPickerProps;
+  Input: InputProps;
+  InputNumber: InputNumberProps;
+  RadioGroup: RadioGroupProps;
+  Select: SelectV2Props;
+  Space: SpaceProps;
+  Switch: SwitchProps;
+  TimePicker: ElTimePickerSchemaProps;
+  TreeSelect: ElTreeSelectSchemaProps;
+  Upload: UploadProps;
+}
+
+type BaseSchema = Omit<
+  CoreFormSchema<ComponentType>,
+  'component' | 'componentProps'
+>;
+
+type RegisteredName = keyof FormSchemaComponentPropsMap;
+
+type DiscriminatedFormSchema = {
+  [K in RegisteredName]: BaseSchema & {
+    component: K;
+    componentProps?: SchemaComponentProps<FormSchemaComponentPropsMap[K]>;
+  };
+}[RegisteredName];
+
+type FallbackFormSchema = BaseSchema & {
+  component: Component | Exclude<ComponentType, RegisteredName>;
+  componentProps?: CoreFormSchema<ComponentType>['componentProps'];
+};
+
+export type VbenFormSchema = DiscriminatedFormSchema | FallbackFormSchema;

+ 10 - 7
apps/web-ele/src/adapter/form.ts

@@ -1,9 +1,7 @@
-import type {
-  VbenFormSchema as FormSchema,
-  VbenFormProps,
-} from '@vben/common-ui';
+import type { VbenFormProps as CoreFormProps } from '@vben/common-ui';
 
 import type { ComponentType } from './component';
+import type { VbenFormSchema } from './form-schema';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -33,9 +31,14 @@ async function initSetupVbenForm() {
   });
 }
 
-const useVbenForm = useForm<ComponentType>;
+type VbenFormProps = Omit<CoreFormProps<ComponentType>, 'schema'> & {
+  schema?: VbenFormSchema[];
+};
+
+function useVbenForm(options: VbenFormProps) {
+  return useForm<ComponentType>(options as CoreFormProps<ComponentType>);
+}
 
 export { initSetupVbenForm, useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<ComponentType>;
-export type { VbenFormProps };
+export type { VbenFormProps, VbenFormSchema };

+ 77 - 0
apps/web-naive/src/adapter/form-schema.ts

@@ -0,0 +1,77 @@
+import type {
+  CheckboxGroupProps,
+  CheckboxProps,
+  DatePickerProps,
+  DividerProps,
+  InputNumberProps,
+  InputProps,
+  RadioGroupProps,
+  SelectProps,
+  SpaceProps,
+  SwitchProps,
+  TimePickerProps,
+  TreeSelectProps,
+  UploadProps,
+} from 'naive-ui';
+
+import type { Component } from 'vue';
+
+import type {
+  ApiComponentSharedProps,
+  VbenFormSchema as CoreFormSchema,
+  IconPickerProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+/**
+ * 对象形式:使用适配器里为各 `component` 声明的 Props 类型 `P`;
+ * 与 `Record<string, any>` 相交是为了满足核心库 `MaybeComponentProps` 的索引签名。
+ * 函数形式:通过联合 `CoreFormSchema['componentProps']`,与表单核心对动态 `componentProps` 的约定保持一致。
+ */
+type SchemaComponentProps<P> =
+  | CoreFormSchema<ComponentType>['componentProps']
+  | (P & Record<string, any>);
+
+/**
+ * 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
+ */
+interface FormSchemaComponentPropsMap {
+  ApiSelect: ApiComponentSharedProps & SelectProps;
+  ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
+  Checkbox: CheckboxProps;
+  CheckboxGroup: CheckboxGroupProps;
+  DatePicker: DatePickerProps;
+  Divider: DividerProps;
+  IconPicker: IconPickerProps;
+  Input: InputProps;
+  InputNumber: InputNumberProps;
+  RadioGroup: RadioGroupProps;
+  Select: SelectProps;
+  Space: SpaceProps;
+  Switch: SwitchProps;
+  TimePicker: TimePickerProps;
+  TreeSelect: TreeSelectProps;
+  Upload: UploadProps;
+}
+
+type BaseSchema = Omit<
+  CoreFormSchema<ComponentType>,
+  'component' | 'componentProps'
+>;
+
+type RegisteredName = keyof FormSchemaComponentPropsMap;
+
+type DiscriminatedFormSchema = {
+  [K in RegisteredName]: BaseSchema & {
+    component: K;
+    componentProps?: SchemaComponentProps<FormSchemaComponentPropsMap[K]>;
+  };
+}[RegisteredName];
+
+type FallbackFormSchema = BaseSchema & {
+  component: Component | Exclude<ComponentType, RegisteredName>;
+  componentProps?: CoreFormSchema<ComponentType>['componentProps'];
+};
+
+export type VbenFormSchema = DiscriminatedFormSchema | FallbackFormSchema;

+ 10 - 7
apps/web-naive/src/adapter/form.ts

@@ -1,9 +1,7 @@
-import type {
-  VbenFormSchema as FormSchema,
-  VbenFormProps,
-} from '@vben/common-ui';
+import type { VbenFormProps as CoreFormProps } from '@vben/common-ui';
 
 import type { ComponentType } from './component';
+import type { VbenFormSchema } from './form-schema';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -37,9 +35,14 @@ async function initSetupVbenForm() {
   });
 }
 
-const useVbenForm = useForm<ComponentType>;
+type VbenFormProps = Omit<CoreFormProps<ComponentType>, 'schema'> & {
+  schema?: VbenFormSchema[];
+};
+
+function useVbenForm(options: VbenFormProps) {
+  return useForm<ComponentType>(options as CoreFormProps<ComponentType>);
+}
 
 export { initSetupVbenForm, useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<ComponentType>;
-export type { VbenFormProps };
+export type { VbenFormProps, VbenFormSchema };

+ 90 - 0
apps/web-tdesign/src/adapter/form-schema.ts

@@ -0,0 +1,90 @@
+import type {
+  AutoCompleteProps,
+  ButtonProps,
+  CheckboxGroupProps,
+  CheckboxProps,
+  DatePickerProps,
+  DateRangePickerProps,
+  DividerProps,
+  InputNumberProps,
+  InputProps,
+  RadioGroupProps,
+  RadioProps,
+  SelectProps,
+  SpaceProps,
+  SwitchProps,
+  TextareaProps,
+  TimePickerProps,
+  TreeSelectProps,
+} from 'tdesign-vue-next';
+import type { TdRateProps } from 'tdesign-vue-next/es/rate/type';
+import type { UploadProps } from 'tdesign-vue-next/es/upload/types';
+
+import type { Component } from 'vue';
+
+import type {
+  ApiComponentSharedProps,
+  VbenFormSchema as CoreFormSchema,
+  IconPickerProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+/**
+ * 对象形式:使用适配器里为各 `component` 声明的 Props 类型 `P`;
+ * 与 `Record<string, any>` 相交是为了满足核心库 `MaybeComponentProps` 的索引签名。
+ * 函数形式:通过联合 `CoreFormSchema['componentProps']`,与表单核心对动态 `componentProps` 的约定保持一致。
+ */
+type SchemaComponentProps<P> =
+  | CoreFormSchema<ComponentType>['componentProps']
+  | (P & Record<string, any>);
+
+/**
+ * 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
+ */
+interface FormSchemaComponentPropsMap {
+  ApiSelect: ApiComponentSharedProps & SelectProps;
+  ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
+  AutoComplete: AutoCompleteProps;
+  Checkbox: CheckboxProps;
+  CheckboxGroup: CheckboxGroupProps;
+  DatePicker: DatePickerProps;
+  DefaultButton: ButtonProps;
+  Divider: DividerProps;
+  IconPicker: IconPickerProps;
+  Input: InputProps;
+  InputNumber: InputNumberProps;
+  PrimaryButton: ButtonProps;
+  Radio: RadioProps;
+  RadioGroup: RadioGroupProps;
+  RangePicker: DateRangePickerProps;
+  Rate: TdRateProps;
+  Select: SelectProps;
+  Space: SpaceProps;
+  Switch: SwitchProps;
+  Textarea: TextareaProps;
+  TimePicker: TimePickerProps;
+  TreeSelect: TreeSelectProps;
+  Upload: UploadProps;
+}
+
+type BaseSchema = Omit<
+  CoreFormSchema<ComponentType>,
+  'component' | 'componentProps'
+>;
+
+type RegisteredName = keyof FormSchemaComponentPropsMap;
+
+type DiscriminatedFormSchema = {
+  [K in RegisteredName]: BaseSchema & {
+    component: K;
+    componentProps?: SchemaComponentProps<FormSchemaComponentPropsMap[K]>;
+  };
+}[RegisteredName];
+
+type FallbackFormSchema = BaseSchema & {
+  component: Component | Exclude<ComponentType, RegisteredName>;
+  componentProps?: CoreFormSchema<ComponentType>['componentProps'];
+};
+
+export type VbenFormSchema = DiscriminatedFormSchema | FallbackFormSchema;

+ 10 - 7
apps/web-tdesign/src/adapter/form.ts

@@ -1,9 +1,7 @@
-import type {
-  VbenFormSchema as FormSchema,
-  VbenFormProps,
-} from '@vben/common-ui';
+import type { VbenFormProps as CoreFormProps } from '@vben/common-ui';
 
 import type { ComponentType } from './component';
+import type { VbenFormSchema } from './form-schema';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -41,9 +39,14 @@ async function initSetupVbenForm() {
   });
 }
 
-const useVbenForm = useForm<ComponentType>;
+type VbenFormProps = Omit<CoreFormProps<ComponentType>, 'schema'> & {
+  schema?: VbenFormSchema[];
+};
+
+function useVbenForm(options: VbenFormProps) {
+  return useForm<ComponentType>(options as CoreFormProps<ComponentType>);
+}
 
 export { initSetupVbenForm, useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<ComponentType>;
-export type { VbenFormProps };
+export type { VbenFormProps, VbenFormSchema };

+ 5 - 67
packages/effects/common-ui/src/components/api-component/api-component.vue

@@ -1,7 +1,8 @@
 <script lang="ts" setup>
-import type { Component } from 'vue';
-
-import type { AnyPromiseFunction } from '@vben/types';
+import type {
+  ApiComponentProps,
+  ApiComponentOptionsItem as OptionsItem,
+} from './types';
 
 import { computed, nextTick, ref, unref, useAttrs, watch } from 'vue';
 
@@ -11,72 +12,9 @@ import { cloneDeep, get, isEqual, isFunction } from '@vben-core/shared/utils';
 
 import { objectOmit } from '@vueuse/core';
 
-type OptionsItem = {
-  [name: string]: any;
-  children?: OptionsItem[];
-  disabled?: boolean;
-  label?: string;
-  value?: string;
-};
-
-interface Props {
-  /** 组件 */
-  component: Component;
-  /** 是否将value从数字转为string */
-  numberToString?: boolean;
-  /** 获取options数据的函数 */
-  api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
-  /** 传递给api的参数 */
-  params?: Record<string, any>;
-  /** 从api返回的结果中提取options数组的字段名 */
-  resultField?: string;
-  /** label字段名 */
-  labelField?: string;
-  /** children字段名,需要层级数据的组件可用 */
-  childrenField?: string;
-  /** value字段名 */
-  valueField?: string;
-  /** disabled字段名 */
-  disabledField?: string;
-  /** 组件接收options数据的属性名 */
-  optionsPropName?: string;
-  /** 是否立即调用api */
-  immediate?: boolean;
-  /** 每次`visibleEvent`事件发生时都重新请求数据 */
-  alwaysLoad?: boolean;
-  /** 在api请求之前的回调函数 */
-  beforeFetch?: AnyPromiseFunction<any, any>;
-  /** 在api请求之前的判断是否允许请求的回调函数 */
-  shouldFetch?: AnyPromiseFunction<any, boolean>;
-  /** 在api请求之后的回调函数 */
-  afterFetch?: AnyPromiseFunction<any, any>;
-  /** 直接传入选项数据,也作为api返回空数据时的后备数据 */
-  options?: OptionsItem[];
-  /** 组件的插槽名称,用来显示一个"加载中"的图标 */
-  loadingSlot?: string;
-  /** 触发api请求的事件名 */
-  visibleEvent?: string;
-  /** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
-  modelPropName?: string;
-  /**
-   * 自动选择
-   * - `first`:自动选择第一个选项
-   * - `last`:自动选择最后一个选项
-   * - `one`: 当请求的结果只有一个选项时,自动选择该选项
-   * - 函数:自定义选择逻辑,函数的参数为请求的结果数组,返回值为选择的选项
-   * - false:不自动选择(默认)
-   */
-  autoSelect?:
-    | 'first'
-    | 'last'
-    | 'one'
-    | ((item: OptionsItem[]) => OptionsItem)
-    | false;
-}
-
 defineOptions({ name: 'ApiComponent', inheritAttrs: false });
 
-const props = withDefaults(defineProps<Props>(), {
+const props = withDefaults(defineProps<ApiComponentProps>(), {
   labelField: 'label',
   valueField: 'value',
   disabledField: 'disabled',

+ 5 - 0
packages/effects/common-ui/src/components/api-component/index.ts

@@ -1 +1,6 @@
 export { default as ApiComponent } from './api-component.vue';
+export type {
+  ApiComponentOptionsItem,
+  ApiComponentProps,
+  ApiComponentSharedProps,
+} from './types';

+ 68 - 0
packages/effects/common-ui/src/components/api-component/types.ts

@@ -0,0 +1,68 @@
+import type { Component } from 'vue';
+
+import type { AnyPromiseFunction } from '@vben/types';
+
+export type ApiComponentOptionsItem = {
+  [name: string]: any;
+  children?: ApiComponentOptionsItem[];
+  disabled?: boolean;
+  label?: string;
+  value?: string;
+};
+
+export interface ApiComponentProps {
+  /** 组件 */
+  component: Component;
+  /** 是否将value从数字转为string */
+  numberToString?: boolean;
+  /** 获取options数据的函数 */
+  api?: (arg?: any) => Promise<ApiComponentOptionsItem[] | Record<string, any>>;
+  /** 传递给api的参数 */
+  params?: Record<string, any>;
+  /** 从api返回的结果中提取options数组的字段名 */
+  resultField?: string;
+  /** label字段名 */
+  labelField?: string;
+  /** children字段名,需要层级数据的组件可用 */
+  childrenField?: string;
+  /** value字段名 */
+  valueField?: string;
+  /** disabled字段名 */
+  disabledField?: string;
+  /** 组件接收options数据的属性名 */
+  optionsPropName?: string;
+  /** 是否立即调用api */
+  immediate?: boolean;
+  /** 每次`visibleEvent`事件发生时都重新请求数据 */
+  alwaysLoad?: boolean;
+  /** 在api请求之前的回调函数 */
+  beforeFetch?: AnyPromiseFunction<any, any>;
+  /** 在api请求之前的判断是否允许请求的回调函数 */
+  shouldFetch?: AnyPromiseFunction<any, boolean>;
+  /** 在api请求之后的回调函数 */
+  afterFetch?: AnyPromiseFunction<any, any>;
+  /** 直接传入选项数据,也作为api返回空数据时的后备数据 */
+  options?: ApiComponentOptionsItem[];
+  /** 组件的插槽名称,用来显示一个"加载中"的图标 */
+  loadingSlot?: string;
+  /** 触发api请求的事件名 */
+  visibleEvent?: string;
+  /** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
+  modelPropName?: string;
+  /**
+   * 自动选择
+   * - `first`:自动选择第一个选项
+   * - `last`:自动选择最后一个选项
+   * - `one`: 当请求的结果只有一个选项时,自动选择该选项
+   * - 函数:自定义选择逻辑,函数的参数为请求的结果数组,返回值为选择的选项
+   * - false:不自动选择(默认)
+   */
+  autoSelect?:
+    | 'first'
+    | 'last'
+    | 'one'
+    | ((item: ApiComponentOptionsItem[]) => ApiComponentOptionsItem)
+    | false;
+}
+
+export type ApiComponentSharedProps = Omit<ApiComponentProps, 'component'>;

+ 2 - 23
packages/effects/common-ui/src/components/icon-picker/icon-picker.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { VNode } from 'vue';
+import type { IconPickerProps } from './types';
 
 import { computed, ref, useAttrs, watch, watchEffect } from 'vue';
 
@@ -28,28 +28,7 @@ import { objectOmit, refDebounced, watchDebounced } from '@vueuse/core';
 
 import { fetchIconsData } from './icons';
 
-interface Props {
-  pageSize?: number;
-  /** 图标集的名字 */
-  prefix?: string;
-  /** 是否自动请求API以获得图标集的数据.提供prefix时有效 */
-  autoFetchApi?: boolean;
-  /**
-   * 图标列表
-   */
-  icons?: string[];
-  /** Input组件 */
-  inputComponent?: VNode;
-  /** 图标插槽名,预览图标将被渲染到此插槽中 */
-  iconSlot?: string;
-  /** input组件的值属性名称 */
-  modelValueProp?: string;
-  /** 图标样式 */
-  iconClass?: string;
-  type?: 'icon' | 'input';
-}
-
-const props = withDefaults(defineProps<Props>(), {
+const props = withDefaults(defineProps<IconPickerProps>(), {
   prefix: 'ant-design',
   pageSize: 36,
   icons: () => [],

+ 1 - 0
packages/effects/common-ui/src/components/icon-picker/index.ts

@@ -1 +1,2 @@
 export { default as IconPicker } from './icon-picker.vue';
+export type { IconPickerProps } from './types';

+ 22 - 0
packages/effects/common-ui/src/components/icon-picker/types.ts

@@ -0,0 +1,22 @@
+import type { VNode } from 'vue';
+
+export interface IconPickerProps {
+  pageSize?: number;
+  /** 图标集的名字 */
+  prefix?: string;
+  /** 是否自动请求API以获得图标集的数据.提供prefix时有效 */
+  autoFetchApi?: boolean;
+  /**
+   * 图标列表
+   */
+  icons?: string[];
+  /** Input组件 */
+  inputComponent?: VNode;
+  /** 图标插槽名,预览图标将被渲染到此插槽中 */
+  iconSlot?: string;
+  /** input组件的值属性名称 */
+  modelValueProp?: string;
+  /** 图标样式 */
+  iconClass?: string;
+  type?: 'icon' | 'input';
+}

+ 96 - 0
playground/src/adapter/form-schema.ts

@@ -0,0 +1,96 @@
+import type {
+  AutoCompleteProps,
+  ButtonProps,
+  CascaderProps,
+  CheckboxGroupProps,
+  CheckboxProps,
+  DatePickerProps,
+  DividerProps,
+  InputNumberProps,
+  InputProps,
+  MentionsProps,
+  RadioGroupProps,
+  RadioProps,
+  RateProps,
+  SelectProps,
+  SpaceProps,
+  SwitchProps,
+  TextAreaProps,
+  TimePickerProps,
+  TreeSelectProps,
+  UploadProps,
+} from 'ant-design-vue';
+import type { RangePickerProps } from 'ant-design-vue/es/date-picker';
+
+import type { Component } from 'vue';
+
+import type {
+  ApiComponentSharedProps,
+  VbenFormSchema as CoreFormSchema,
+  IconPickerProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+/**
+ * 对象形式:使用适配器里为各 `component` 声明的 Props 类型 `P`;
+ * 与 `Record<string, any>` 相交是为了满足核心库 `MaybeComponentProps` 的索引签名。
+ * 函数形式:通过联合 `CoreFormSchema['componentProps']`,与表单核心对动态 `componentProps` 的约定保持一致。
+ */
+type SchemaComponentProps<P> =
+  | CoreFormSchema<ComponentType>['componentProps']
+  | (P & Record<string, any>);
+
+/**
+ * 与 {@link ComponentType} 中注册的组件名一一对应,便于 Schema 上 `component` + `componentProps` 联动提示
+ */
+interface FormSchemaComponentPropsMap {
+  ApiCascader: ApiComponentSharedProps & CascaderProps;
+  ApiSelect: ApiComponentSharedProps & SelectProps;
+  ApiTreeSelect: ApiComponentSharedProps & TreeSelectProps;
+  AutoComplete: AutoCompleteProps;
+  Cascader: CascaderProps;
+  Checkbox: CheckboxProps;
+  CheckboxGroup: CheckboxGroupProps;
+  DatePicker: DatePickerProps;
+  DefaultButton: ButtonProps;
+  Divider: DividerProps;
+  IconPicker: IconPickerProps;
+  Input: InputProps;
+  InputNumber: InputNumberProps;
+  InputPassword: InputProps;
+  Mentions: MentionsProps;
+  PrimaryButton: ButtonProps;
+  Radio: RadioProps;
+  RadioGroup: RadioGroupProps;
+  RangePicker: RangePickerProps;
+  Rate: RateProps;
+  Select: SelectProps;
+  Space: SpaceProps;
+  Switch: SwitchProps;
+  Textarea: TextAreaProps;
+  TimePicker: TimePickerProps;
+  TreeSelect: TreeSelectProps;
+  Upload: UploadProps;
+}
+
+type BaseSchema = Omit<
+  CoreFormSchema<ComponentType>,
+  'component' | 'componentProps'
+>;
+
+type RegisteredName = keyof FormSchemaComponentPropsMap;
+
+type DiscriminatedFormSchema = {
+  [K in RegisteredName]: BaseSchema & {
+    component: K;
+    componentProps?: SchemaComponentProps<FormSchemaComponentPropsMap[K]>;
+  };
+}[RegisteredName];
+
+type FallbackFormSchema = BaseSchema & {
+  component: Component | Exclude<ComponentType, RegisteredName>;
+  componentProps?: CoreFormSchema<ComponentType>['componentProps'];
+};
+
+export type VbenFormSchema = DiscriminatedFormSchema | FallbackFormSchema;

+ 10 - 7
playground/src/adapter/form.ts

@@ -1,9 +1,7 @@
-import type {
-  VbenFormSchema as FormSchema,
-  VbenFormProps,
-} from '@vben/common-ui';
+import type { VbenFormProps as CoreFormProps } from '@vben/common-ui';
 
 import type { ComponentType } from './component';
+import type { VbenFormSchema } from './form-schema';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -40,8 +38,13 @@ async function initSetupVbenForm() {
   });
 }
 
-const useVbenForm = useForm<ComponentType>;
+type VbenFormProps = Omit<CoreFormProps<ComponentType>, 'schema'> & {
+  schema?: VbenFormSchema[];
+};
+
+function useVbenForm(options: VbenFormProps) {
+  return useForm<ComponentType>(options as CoreFormProps<ComponentType>);
+}
 
 export { initSetupVbenForm, useVbenForm, z };
-export type VbenFormSchema = FormSchema<ComponentType>;
-export type { VbenFormProps };
+export type { VbenFormProps, VbenFormSchema };