Browse Source

perf(form): improve the form function

vben 4 years ago
parent
commit
ac1a369502

+ 3 - 0
CHANGELOG.zh_CN.md

@@ -8,6 +8,9 @@
 - 新增主框架外页面示例
 - `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。
 - 新增面包屑导航示例
+- form: 新增`suffix`属性,用于配置后缀内容
+- form: 新增远程下拉`ApiSelect`及示例
+- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
 
 ### 🐛 Bug Fixes
 

+ 24 - 0
mock/demo/select-demo.ts

@@ -0,0 +1,24 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { resultSuccess } from '../_util';
+
+const demoList = (() => {
+  const result: any[] = [];
+  for (let index = 0; index < 20; index++) {
+    result.push({
+      label: `选项${index}`,
+      value: `${index}`,
+    });
+  }
+  return result;
+})();
+
+export default [
+  {
+    url: '/api/select/getDemoOptions',
+    timeout: 4000,
+    method: 'get',
+    response: ({ query }) => {
+      return resultSuccess(demoList);
+    },
+  },
+] as MockMethod[];

+ 3 - 3
package.json

@@ -22,7 +22,7 @@
   },
   "dependencies": {
     "@iconify/iconify": "^2.0.0-rc.4",
-    "@vueuse/core": "^4.0.0",
+    "@vueuse/core": "^4.0.1",
     "ant-design-vue": "^2.0.0-rc.5",
     "apexcharts": "^3.23.0",
     "axios": "^0.21.1",
@@ -35,7 +35,7 @@
     "path-to-regexp": "^6.2.0",
     "qrcode": "^1.4.4",
     "sortablejs": "^1.12.0",
-    "vditor": "^3.7.3",
+    "vditor": "^3.7.4",
     "vue": "^3.0.4",
     "vue-i18n": "9.0.0-beta.14",
     "vue-router": "^4.0.1",
@@ -48,7 +48,7 @@
   "devDependencies": {
     "@commitlint/cli": "^11.0.0",
     "@commitlint/config-conventional": "^11.0.0",
-    "@iconify/json": "^1.1.276",
+    "@iconify/json": "^1.1.277",
     "@ls-lint/ls-lint": "^1.9.2",
     "@purge-icons/generated": "^0.4.1",
     "@types/echarts": "^4.9.3",

+ 11 - 0
src/api/demo/model/optionsModel.ts

@@ -0,0 +1,11 @@
+import { BasicFetchResult } from '/@/api/model/baseModel';
+
+export interface DemoOptionsItem {
+  label: string;
+  value: string;
+}
+
+/**
+ * @description: Request list return value
+ */
+export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem[]>;

+ 16 - 0
src/api/demo/select.ts

@@ -0,0 +1,16 @@
+import { defHttp } from '/@/utils/http/axios';
+import { DemoOptionsGetResultModel } from './model/optionsModel';
+
+enum Api {
+  OPTIONS_LIST = '/select/getDemoOptions',
+}
+
+/**
+ * @description: Get sample options value
+ */
+export function optionsListApi() {
+  return defHttp.request<DemoOptionsGetResultModel>({
+    url: Api.OPTIONS_LIST,
+    method: 'GET',
+  });
+}

+ 66 - 4
src/components/Form/src/BasicForm.vue

@@ -1,6 +1,6 @@
 <template>
-  <Form v-bind="{ ...$attrs, ...$props }" ref="formElRef" :model="formModel">
-    <Row :class="getProps.compact ? 'compact-form-row' : ''" :style="getRowWrapStyle">
+  <Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
+    <Row :style="getRowWrapStyle">
       <slot name="formHeader" />
       <template v-for="schema in getSchema" :key="schema.field">
         <FormItem
@@ -18,7 +18,6 @@
         </FormItem>
       </template>
 
-      <!--  -->
       <FormAction
         v-bind="{ ...getProps, ...advanceState }"
         @toggle-advanced="handleToggleAdvanced"
@@ -46,8 +45,10 @@
   import useAdvanced from './hooks/useAdvanced';
   import { useFormEvents } from './hooks/useFormEvents';
   import { createFormContext } from './hooks/useFormContext';
+  import { useAutoFocus } from './hooks/useAutoFocus';
 
   import { basicProps } from './props';
+  import { useDesign } from '/@/hooks/web/useDesign';
 
   export default defineComponent({
     name: 'BasicForm',
@@ -71,6 +72,8 @@
       const schemaRef = ref<Nullable<FormSchema[]>>(null);
       const formElRef = ref<Nullable<FormActionType>>(null);
 
+      const { prefixCls } = useDesign('basic-form');
+
       // Get the basic configuration of the form
       const getProps = computed(
         (): FormProps => {
@@ -78,6 +81,15 @@
         }
       );
 
+      const getFormClass = computed(() => {
+        return [
+          prefixCls,
+          {
+            [`${prefixCls}--compact`]: unref(getProps).compact,
+          },
+        ];
+      });
+
       // Get uniform row style
       const getRowWrapStyle = computed(
         (): CSSProperties => {
@@ -115,7 +127,7 @@
         defaultValueRef,
       });
 
-      const { transformDateFunc, fieldMapToTime } = toRefs(props);
+      const { transformDateFunc, fieldMapToTime, autoFocusFirstItem } = toRefs(props);
 
       const { handleFormValues, initDefault } = useFormValues({
         transformDateFuncRef: transformDateFunc,
@@ -125,6 +137,13 @@
         formModel,
       });
 
+      useAutoFocus({
+        getSchema,
+        autoFocusFirstItem,
+        isInitedDefault: isInitedDefaultRef,
+        formElRef: formElRef as Ref<FormActionType>,
+      });
+
       const {
         handleSubmit,
         setFieldsValue,
@@ -217,8 +236,51 @@
         getSchema,
         formActionType,
         setFormModel,
+        prefixCls,
+        getFormClass,
         ...formActionType,
       };
     },
   });
 </script>
+<style lang="less">
+  @import (reference) '../../../design/index.less';
+  @prefix-cls: ~'@{namespace}-basic-form';
+
+  .@{prefix-cls} {
+    .ant-form-item {
+      &-label label::after {
+        margin: 0 6px 0 2px;
+      }
+
+      &-with-help {
+        margin-bottom: 0;
+      }
+
+      &:not(.ant-form-item-with-help) {
+        margin-bottom: 20px;
+      }
+
+      &.suffix-item {
+        .ant-form-item-children {
+          display: flex;
+        }
+
+        .suffix {
+          display: inline-block;
+          padding-left: 6px;
+        }
+      }
+    }
+
+    .ant-form-explain {
+      font-size: 14px;
+    }
+
+    &--compact {
+      .ant-form-item {
+        margin-bottom: 8px;
+      }
+    }
+  }
+</style>

+ 2 - 0
src/components/Form/src/componentMap.ts

@@ -19,6 +19,7 @@ import {
 } from 'ant-design-vue';
 
 import RadioButtonGroup from './components/RadioButtonGroup.vue';
+import ApiSelect from './components/ApiSelect.vue';
 import { BasicUpload } from '/@/components/Upload';
 
 const componentMap = new Map<ComponentType, Component>();
@@ -32,6 +33,7 @@ componentMap.set('InputNumber', InputNumber);
 componentMap.set('AutoComplete', AutoComplete);
 
 componentMap.set('Select', Select);
+componentMap.set('ApiSelect', ApiSelect);
 // componentMap.set('SelectOptGroup', Select.OptGroup);
 // componentMap.set('SelectOption', Select.Option);
 componentMap.set('TreeSelect', TreeSelect);

+ 89 - 0
src/components/Form/src/components/ApiSelect.vue

@@ -0,0 +1,89 @@
+<template>
+  <Select v-bind="attrs" :options="options" v-model:value="state">
+    <template #[item]="data" v-for="item in Object.keys($slots)">
+      <slot :name="item" v-bind="data" />
+    </template>
+    <template #suffixIcon v-if="loading">
+      <LoadingOutlined spin />
+    </template>
+    <template #notFoundContent v-if="loading">
+      <span>
+        <LoadingOutlined spin class="mr-1" />
+        {{ t('component.form.apiSelectNotFound') }}
+      </span>
+    </template>
+  </Select>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, watchEffect } from 'vue';
+  import { Select } from 'ant-design-vue';
+  import { isFunction } from '/@/utils/is';
+  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
+  import { useAttrs } from '/@/hooks/core/useAttrs';
+  import { get } from 'lodash-es';
+
+  import { LoadingOutlined } from '@ant-design/icons-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+
+  type OptionsItem = { label: string; value: string; disabled?: boolean };
+
+  export default defineComponent({
+    name: 'RadioButtonGroup',
+    components: {
+      Select,
+      LoadingOutlined,
+    },
+    props: {
+      value: {
+        type: String as PropType<string>,
+      },
+      api: {
+        type: Function as PropType<(arg: Recordable) => Promise<OptionsItem[]>>,
+        default: null,
+      },
+      params: {
+        type: Object as PropType<Recordable>,
+        default: () => {},
+      },
+      resultField: {
+        type: String as PropType<string>,
+        default: '',
+      },
+    },
+    setup(props) {
+      const options = ref<OptionsItem[]>([]);
+      const loading = ref(false);
+      const attrs = useAttrs();
+      const { t } = useI18n();
+
+      // Embedded in the form, just use the hook binding to perform form verification
+      const [state] = useRuleFormItem(props);
+
+      watchEffect(() => {
+        fetch();
+      });
+
+      async function fetch() {
+        const api = props.api;
+        if (!api || !isFunction(api)) return;
+
+        try {
+          loading.value = true;
+          const res = await api(props.params);
+          if (Array.isArray(res)) {
+            options.value = res;
+            return;
+          }
+          if (props.resultField) {
+            options.value = get(res, props.resultField) || [];
+          }
+        } catch (error) {
+          console.warn(error);
+        } finally {
+          loading.value = false;
+        }
+      }
+      return { state, attrs, options, loading, t };
+    },
+  });
+</script>

+ 19 - 26
src/components/Form/src/components/FormItem.tsx

@@ -3,7 +3,6 @@ import type { FormActionType, FormProps } from '../types/form';
 import type { FormSchema } from '../types/form';
 import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
 import type { TableActionType } from '/@/components/Table';
-import type { ComponentType } from '../types';
 
 import { defineComponent, computed, unref, toRefs } from 'vue';
 import { Form, Col } from 'ant-design-vue';
@@ -16,7 +15,6 @@ import { createPlaceholderMessage, setComponentRuleType } from '../helper';
 import { upperFirst, cloneDeep } from 'lodash-es';
 
 import { useItemLabelWidth } from '../hooks/useLabelWidth';
-import { isNumber } from '/@/utils/is';
 import { useI18n } from '/@/hooks/web/useI18n';
 
 export default defineComponent({
@@ -81,7 +79,7 @@ export default defineComponent({
       if (!isFunction(componentProps)) {
         return componentProps;
       }
-      return componentProps({ schema, tableAction, formModel, formActionType }) || {};
+      return componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
     });
 
     const getDisable = computed(() => {
@@ -99,7 +97,7 @@ export default defineComponent({
       return disabled;
     });
 
-    function getShow() {
+    const getShow = computed(() => {
       const { show, ifShow } = props.schema;
       const { showAdvancedButton } = props.formProps;
       const itemIsAdvanced = showAdvancedButton
@@ -124,7 +122,7 @@ export default defineComponent({
       }
       isShow = isShow && itemIsAdvanced;
       return { isShow, isIfShow };
-    }
+    });
 
     function handleRules(): ValidationRule[] {
       const {
@@ -171,7 +169,7 @@ export default defineComponent({
         }
       }
 
-      // 最大输入长度规则校验
+      // Maximum input length rule check
       const characterInx = rules.findIndex((val) => val.max);
       if (characterInx !== -1 && !rules[characterInx].validator) {
         rules[characterInx].message =
@@ -180,20 +178,6 @@ export default defineComponent({
       return rules;
     }
 
-    function handleValue(component: ComponentType, field: string) {
-      const val = props.formModel[field];
-      if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
-        if (val && isNumber(val)) {
-          props.setFormModel(field, `${val}`);
-
-          // props.formModel[field] = `${val}`;
-          return `${val}`;
-        }
-        return val;
-      }
-      return val;
-    }
-
     function renderComponent() {
       const {
         renderComponentContent,
@@ -217,7 +201,6 @@ export default defineComponent({
 
           const value = target ? (isCheck ? target.checked : target.value) : e;
           props.setFormModel(field, value);
-          // props.formModel[field] = value;
         },
       };
       const Comp = componentMap.get(component) as typeof defineComponent;
@@ -233,7 +216,7 @@ export default defineComponent({
 
       const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
       let placeholder;
-      // RangePicker place为数组
+      // RangePicker place is an array
       if (isCreatePlaceholder && component !== 'RangePicker' && component) {
         placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
       }
@@ -242,7 +225,7 @@ export default defineComponent({
       propsData.formValues = unref(getValues);
 
       const bindValue: Recordable = {
-        [valueField || (isCheck ? 'checked' : 'value')]: handleValue(component, field),
+        [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
       };
 
       const compAttr: Recordable = {
@@ -284,7 +267,7 @@ export default defineComponent({
     }
 
     function renderItem() {
-      const { itemProps, slot, render, field } = props.schema;
+      const { itemProps, slot, render, field, suffix } = props.schema;
       const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
       const { colon } = props.formProps;
 
@@ -296,17 +279,27 @@ export default defineComponent({
           : renderComponent();
       };
 
+      const showSuffix = !!suffix;
+
+      const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
+
       return (
         <Form.Item
           name={field}
           colon={colon}
+          class={{ 'suffix-item': showSuffix }}
           {...(itemProps as Recordable)}
           label={renderLabelHelpMessage()}
           rules={handleRules()}
           labelCol={labelCol}
           wrapperCol={wrapperCol}
         >
-          {() => getContent()}
+          {() => (
+            <>
+              {getContent()}
+              {showSuffix && <span class="suffix">{getSuffix}</span>}
+            </>
+          )}
         </Form.Item>
       );
     }
@@ -317,7 +310,7 @@ export default defineComponent({
       const { baseColProps = {} } = props.formProps;
 
       const realColProps = { ...baseColProps, ...colProps };
-      const { isIfShow, isShow } = getShow();
+      const { isIfShow, isShow } = unref(getShow);
 
       const getContent = () => {
         return colSlot

+ 9 - 0
src/components/Form/src/helper.ts

@@ -1,6 +1,7 @@
 import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
 import type { ComponentType } from './types/index';
 import { useI18n } from '/@/hooks/web/useI18n';
+import { isNumber } from '/@/utils/is';
 
 const { t } = useI18n();
 
@@ -41,6 +42,14 @@ export function setComponentRuleType(rule: ValidationRule, component: ComponentT
   }
 }
 
+export function handleInputNumberValue(component?: ComponentType, val: any) {
+  if (!component) return val;
+  if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
+    return val && isNumber(val) ? `${val}` : val;
+  }
+  return val;
+}
+
 /**
  * 时间字段
  */

+ 1 - 1
src/components/Form/src/hooks/useAdvanced.ts

@@ -1,6 +1,6 @@
 import type { ColEx } from '../types';
 import type { AdvanceState } from '../types/hooks';
-import { ComputedRef, Ref } from 'vue';
+import type { ComputedRef, Ref } from 'vue';
 import type { FormProps, FormSchema } from '../types/form';
 
 import { computed, unref, watch } from 'vue';

+ 34 - 0
src/components/Form/src/hooks/useAutoFocus.ts

@@ -0,0 +1,34 @@
+import type { ComputedRef, Ref } from 'vue';
+import type { FormSchema, FormActionType } from '../types/form';
+
+import { unref, nextTick, watchEffect } from 'vue';
+
+interface UseAutoFocusContext {
+  getSchema: ComputedRef<FormSchema[]>;
+  autoFocusFirstItem: Ref<boolean>;
+  isInitedDefault: Ref<boolean>;
+  formElRef: Ref<FormActionType>;
+}
+export async function useAutoFocus({
+  getSchema,
+  autoFocusFirstItem,
+  formElRef,
+  isInitedDefault,
+}: UseAutoFocusContext) {
+  watchEffect(async () => {
+    if (unref(isInitedDefault) || !unref(autoFocusFirstItem)) return;
+    await nextTick();
+    const schemas = unref(getSchema);
+    const formEl = unref(formElRef);
+    const el = (formEl as any)?.$el as HTMLElement;
+    if (!formEl || !el || !schemas || schemas.length === 0) return;
+
+    const firstItem = schemas[0];
+    // Only open when the first form item is input type
+    if (!firstItem.component.includes('Input')) return;
+
+    const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
+    if (!inputEl) return;
+    inputEl?.focus();
+  });
+}

+ 12 - 9
src/components/Form/src/hooks/useFormEvents.ts

@@ -6,7 +6,7 @@ import { unref, toRaw } from 'vue';
 
 import { isArray, isFunction, isObject, isString } from '/@/utils/is';
 import { deepMerge, unique } from '/@/utils';
-import { dateItemType } from '../helper';
+import { dateItemType, handleInputNumberValue } from '../helper';
 import moment from 'moment';
 import { cloneDeep } from 'lodash-es';
 import { error } from '/@/utils/log';
@@ -49,29 +49,32 @@ export function useFormEvents({
   /**
    * @description: Set form value
    */
-  async function setFieldsValue(values: any): Promise<void> {
+  async function setFieldsValue(values: Recordable): Promise<void> {
     const fields = unref(getSchema)
       .map((item) => item.field)
       .filter(Boolean);
 
     const validKeys: string[] = [];
     Object.keys(values).forEach((key) => {
-      const element = values[key];
+      const schema = unref(getSchema).find((item) => item.field === key);
+      let value = values[key];
+
+      value = handleInputNumberValue(schema?.component, value);
       // 0| '' is allow
-      if (element !== undefined && element !== null && fields.includes(key)) {
+      if (value !== undefined && value !== null && fields.includes(key)) {
         // time type
         if (itemIsDateType(key)) {
-          if (Array.isArray(element)) {
-            const arr: any[] = [];
-            for (const ele of element) {
+          if (Array.isArray(value)) {
+            const arr: moment.Moment[] = [];
+            for (const ele of value) {
               arr.push(moment(ele));
             }
             formModel[key] = arr;
           } else {
-            formModel[key] = moment(element);
+            formModel[key] = moment(value);
           }
         } else {
-          formModel[key] = element;
+          formModel[key] = value;
         }
         validKeys.push(key);
       }

+ 2 - 0
src/components/Form/src/props.ts

@@ -65,6 +65,8 @@ export const basicProps = {
   actionColOptions: Object as PropType<Partial<ColEx>>,
   // 显示重置按钮
   showResetButton: propTypes.bool.def(true),
+  // 是否聚焦第一个输入框,只在第一个表单项为input的时候作用
+  autoFocusFirstItem: propTypes.bool,
   // 重置按钮配置
   resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
 

+ 4 - 0
src/components/Form/src/types/form.ts

@@ -82,6 +82,8 @@ export interface FormProps {
   rulesMessageJoinLabel?: boolean;
   // Whether to show collapse and expand buttons
   showAdvancedButton?: boolean;
+  // Whether to focus on the first input box, only works when the first form item is input
+  autoFocusFirstItem?: boolean;
   // Automatically collapse over the specified number of rows
   autoAdvancedLine?: number;
   // Whether to show the operation button
@@ -139,6 +141,8 @@ export interface FormSchema {
   // Required
   required?: boolean;
 
+  suffix?: string | number | ((values: RenderCallbackParams) => string | number);
+
   // Validation rules
   rules?: Rule[];
   // Check whether the information is added to the label

+ 1 - 0
src/components/Form/src/types/index.ts

@@ -89,6 +89,7 @@ export type ComponentType =
   | 'InputNumber'
   | 'InputCountDown'
   | 'Select'
+  | 'ApiSelect'
   | 'SelectOptGroup'
   | 'SelectOption'
   | 'TreeSelect'

+ 0 - 31
src/design/ant/index.less

@@ -49,37 +49,6 @@
 }
 
 // =================================
-// ==============form===============
-// =================================
-.ant-form-item.deltag .ant-form-item-required::before {
-  content: '';
-}
-
-.ant-form-item-with-help {
-  margin-bottom: 0;
-}
-
-.ant-form-item {
-  &-label label::after {
-    margin: 0 6px 0 2px;
-  }
-}
-
-.ant-form-item:not(.ant-form-item-with-help) {
-  margin-bottom: 20px;
-}
-
-.ant-form-explain {
-  font-size: 14px;
-}
-
-.compact-form-row {
-  .ant-form-item {
-    margin-bottom: 8px;
-  }
-}
-
-// =================================
 // ==============empty==============
 // =================================
 .ant-empty-image {

+ 2 - 0
src/locales/lang/en/component/form.ts

@@ -8,4 +8,6 @@ export default {
   choose: 'Please Choose ',
 
   maxTip: 'The number of characters should be less than {0}',
+
+  apiSelectNotFound: 'Wait for data loading to complete...',
 };

+ 2 - 0
src/locales/lang/zh_CN/component/form.ts

@@ -8,4 +8,6 @@ export default {
   choose: '请选择',
 
   maxTip: '字符数应小于{0}位',
+
+  apiSelectNotFound: '请等待数据加载完成...',
 };

+ 8 - 7
src/utils/http/axios/index.ts

@@ -105,28 +105,29 @@ const transform: AxiosTransform = {
     if (apiUrl && isString(apiUrl)) {
       config.url = `${apiUrl}${config.url}`;
     }
+    const params = config.params || {};
     if (config.method?.toUpperCase() === RequestEnum.GET) {
-      if (!isString(config.params)) {
+      if (!isString(params)) {
         config.data = {
           // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
-          params: Object.assign(config.params || {}, createNow(joinTime, false)),
+          params: Object.assign(params || {}, createNow(joinTime, false)),
         };
       } else {
         // 兼容restful风格
-        config.url = config.url + config.params + `${createNow(joinTime, true)}`;
+        config.url = config.url + params + `${createNow(joinTime, true)}`;
         config.params = undefined;
       }
     } else {
-      if (!isString(config.params)) {
-        formatDate && formatRequestDate(config.params);
-        config.data = config.params;
+      if (!isString(params)) {
+        formatDate && formatRequestDate(params);
+        config.data = params;
         config.params = undefined;
         if (joinParamsToUrl) {
           config.url = setObjToUrlParams(config.url as string, config.data);
         }
       } else {
         // 兼容restful风格
-        config.url = config.url + config.params;
+        config.url = config.url + params;
         config.params = undefined;
       }
     }

+ 1 - 1
src/views/demo/form/RuleForm.vue

@@ -170,7 +170,7 @@
       }
       function setFormValues() {
         setFieldsValue({
-          field1: '1111',
+          field1: 1111,
           field5: ['1'],
           field7: '1',
         });

+ 18 - 1
src/views/demo/form/index.vue

@@ -2,6 +2,7 @@
   <div class="m-4">
     <CollapseContainer title="基础示例">
       <BasicForm
+        autoFocusFirstItem
         :labelWidth="100"
         :schemas="schemas"
         :actionColOptions="{ span: 24 }"
@@ -16,11 +17,13 @@
   import { CollapseContainer } from '/@/components/Container/index';
   import { useMessage } from '/@/hooks/web/useMessage';
 
+  import { optionsListApi } from '/@/api/demo/select';
   const schemas: FormSchema[] = [
     {
       field: 'field1',
       component: 'Input',
       label: '字段1',
+
       colProps: {
         span: 8,
       },
@@ -46,7 +49,7 @@
     {
       field: 'field2',
       component: 'Input',
-      label: '字段2',
+      label: '带后缀',
       defaultValue: '111',
       colProps: {
         span: 8,
@@ -56,6 +59,7 @@
           console.log(e);
         },
       },
+      suffix: '天',
     },
     {
       field: 'field3',
@@ -208,6 +212,19 @@
         ],
       },
     },
+
+    {
+      field: 'field30',
+      component: 'ApiSelect',
+      label: '远程下拉',
+      required: true,
+      componentProps: {
+        api: optionsListApi,
+      },
+      colProps: {
+        span: 8,
+      },
+    },
     {
       field: 'field20',
       component: 'InputNumber',

+ 17 - 17
yarn.lock

@@ -1076,10 +1076,10 @@
   resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.4.tgz#46098fb544a4eb3af724219e4955c9022801835e"
   integrity sha512-YCSECbeXKFJEIVkKgKMjUzJ439ysufmL/a31B1j7dCvnHaBWsX9J4XehhJgg/aTy3yvhHaVhI6xt1kSMZP799A==
 
-"@iconify/json@^1.1.276":
-  version "1.1.276"
-  resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.276.tgz#c8d51751abc84cc73a466f55bc2f352686451786"
-  integrity sha512-Ra/mGT+n38vhi/i1cjsPYOmSR2d6rNIXZ+OsrIWp9J35zAPQ93sSTQMpTyxZdLu3QxU0vYwtcaC7h/Y1/3H3wg==
+"@iconify/json@^1.1.277":
+  version "1.1.277"
+  resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.277.tgz#e11e01833b05845ce1afc5ad61759804f6ed2eb2"
+  integrity sha512-66n4lsv57iRwtcb2Q8ax8iasVLzFz9VWcqtgobHVrvyfsVqf8hSldJELnTl/gtqayqa35pT4mHEpdfsqt1mnLA==
 
 "@intlify/core-base@9.0.0-beta.14":
   version "9.0.0-beta.14"
@@ -1831,18 +1831,18 @@
     vscode-languageserver-textdocument "^1.0.1"
     vscode-uri "^2.1.2"
 
-"@vueuse/core@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0.tgz#5bea3eaa848e3b3e00427f5053fb98e7e4834b0f"
-  integrity sha512-BBkqriC2j9SH/LuHCggS2MP7VSwBfGkTB9qQh1lzadodk2TnM1JHwM76f3G0hCGqqhEF7ab8Xs+1M1PlvuEQYA==
+"@vueuse/core@^4.0.1":
+  version "4.0.1"
+  resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.1.tgz#be90fd09de0264dbe61c571b5967334ca94d8cb2"
+  integrity sha512-bC6H/ES9aFnzp6rT3W3d5j/CqB8mN1UrvBj1RO639QMwxPbJ5/JDjDD4HHtOdIZfA82d6p2Ijbv4Y04mXmkHng==
   dependencies:
-    "@vueuse/shared" "4.0.0"
+    "@vueuse/shared" "4.0.1"
     vue-demi latest
 
-"@vueuse/shared@4.0.0":
-  version "4.0.0"
-  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0.tgz#d495b8fd2f28a453ef0fccae175ca848a4a84bb0"
-  integrity sha512-8tn1BpnaMJU2LqFyFzzN6Dvmc1uDsSlb3Neli5bwwb9f+rcASpuOS3nAWAY6/rIODZP1iwXDNCL4rNFR3YxYtQ==
+"@vueuse/shared@4.0.1":
+  version "4.0.1"
+  resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.1.tgz#28750d34400cd0cabf2576342c5ee7471b0e27bd"
+  integrity sha512-7SQ1OqUPiuOSe5OFGIn5NvawZ7mfID5V4AwsHwpMAQn22Ex73az6TFE1N/6fL4rZBx6wLrkPfVO9v7vSsOkvlg==
   dependencies:
     vue-demi latest
 
@@ -8039,10 +8039,10 @@ vary@^1.1.2:
   resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
 
-vditor@^3.7.3:
-  version "3.7.3"
-  resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.3.tgz#6f7bdee7dca758985b29be1533ed952178f0aac4"
-  integrity sha512-2EHwAc9l+HOo6dcScSJDPmVTsVuEqHK2ucZwAHgvctpua3pMz/CAGMHgPoyB5X1Pju7yrLfsESHZh8V6Ndh6rg==
+vditor@^3.7.4:
+  version "3.7.4"
+  resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.4.tgz#e2ec46f009e99d4ef1804d4ef355d44be7efb9a3"
+  integrity sha512-NfpXCoiVEeaORwGPNaxVDQGHs6Sib2RlI+slSFc5eXV8pFfYM639O6iOLjG2Ks+lN7nM9SsmpcGXwnQ0/S90xA==
   dependencies:
     diff-match-patch "^1.0.5"