Commit ac1a36950259844822c6300a00710b040dfc2640

Authored by vben
1 parent 4ff1c408

perf(form): improve the form function

CHANGELOG.zh_CN.md
... ... @@ -8,6 +8,9 @@
8 8 - 新增主框架外页面示例
9 9 - `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。
10 10 - 新增面包屑导航示例
  11 +- form: 新增`suffix`属性,用于配置后缀内容
  12 +- form: 新增远程下拉`ApiSelect`及示例
  13 +- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
11 14  
12 15 ### 🐛 Bug Fixes
13 16  
... ...
mock/demo/select-demo.ts 0 → 100644
  1 +import { MockMethod } from 'vite-plugin-mock';
  2 +import { resultSuccess } from '../_util';
  3 +
  4 +const demoList = (() => {
  5 + const result: any[] = [];
  6 + for (let index = 0; index < 20; index++) {
  7 + result.push({
  8 + label: `选项${index}`,
  9 + value: `${index}`,
  10 + });
  11 + }
  12 + return result;
  13 +})();
  14 +
  15 +export default [
  16 + {
  17 + url: '/api/select/getDemoOptions',
  18 + timeout: 4000,
  19 + method: 'get',
  20 + response: ({ query }) => {
  21 + return resultSuccess(demoList);
  22 + },
  23 + },
  24 +] as MockMethod[];
... ...
package.json
... ... @@ -22,7 +22,7 @@
22 22 },
23 23 "dependencies": {
24 24 "@iconify/iconify": "^2.0.0-rc.4",
25   - "@vueuse/core": "^4.0.0",
  25 + "@vueuse/core": "^4.0.1",
26 26 "ant-design-vue": "^2.0.0-rc.5",
27 27 "apexcharts": "^3.23.0",
28 28 "axios": "^0.21.1",
... ... @@ -35,7 +35,7 @@
35 35 "path-to-regexp": "^6.2.0",
36 36 "qrcode": "^1.4.4",
37 37 "sortablejs": "^1.12.0",
38   - "vditor": "^3.7.3",
  38 + "vditor": "^3.7.4",
39 39 "vue": "^3.0.4",
40 40 "vue-i18n": "9.0.0-beta.14",
41 41 "vue-router": "^4.0.1",
... ... @@ -48,7 +48,7 @@
48 48 "devDependencies": {
49 49 "@commitlint/cli": "^11.0.0",
50 50 "@commitlint/config-conventional": "^11.0.0",
51   - "@iconify/json": "^1.1.276",
  51 + "@iconify/json": "^1.1.277",
52 52 "@ls-lint/ls-lint": "^1.9.2",
53 53 "@purge-icons/generated": "^0.4.1",
54 54 "@types/echarts": "^4.9.3",
... ...
src/api/demo/model/optionsModel.ts 0 → 100644
  1 +import { BasicFetchResult } from '/@/api/model/baseModel';
  2 +
  3 +export interface DemoOptionsItem {
  4 + label: string;
  5 + value: string;
  6 +}
  7 +
  8 +/**
  9 + * @description: Request list return value
  10 + */
  11 +export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem[]>;
... ...
src/api/demo/select.ts 0 → 100644
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { DemoOptionsGetResultModel } from './model/optionsModel';
  3 +
  4 +enum Api {
  5 + OPTIONS_LIST = '/select/getDemoOptions',
  6 +}
  7 +
  8 +/**
  9 + * @description: Get sample options value
  10 + */
  11 +export function optionsListApi() {
  12 + return defHttp.request<DemoOptionsGetResultModel>({
  13 + url: Api.OPTIONS_LIST,
  14 + method: 'GET',
  15 + });
  16 +}
... ...
src/components/Form/src/BasicForm.vue
1 1 <template>
2   - <Form v-bind="{ ...$attrs, ...$props }" ref="formElRef" :model="formModel">
3   - <Row :class="getProps.compact ? 'compact-form-row' : ''" :style="getRowWrapStyle">
  2 + <Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
  3 + <Row :style="getRowWrapStyle">
4 4 <slot name="formHeader" />
5 5 <template v-for="schema in getSchema" :key="schema.field">
6 6 <FormItem
... ... @@ -18,7 +18,6 @@
18 18 </FormItem>
19 19 </template>
20 20  
21   - <!-- -->
22 21 <FormAction
23 22 v-bind="{ ...getProps, ...advanceState }"
24 23 @toggle-advanced="handleToggleAdvanced"
... ... @@ -46,8 +45,10 @@
46 45 import useAdvanced from './hooks/useAdvanced';
47 46 import { useFormEvents } from './hooks/useFormEvents';
48 47 import { createFormContext } from './hooks/useFormContext';
  48 + import { useAutoFocus } from './hooks/useAutoFocus';
49 49  
50 50 import { basicProps } from './props';
  51 + import { useDesign } from '/@/hooks/web/useDesign';
51 52  
52 53 export default defineComponent({
53 54 name: 'BasicForm',
... ... @@ -71,6 +72,8 @@
71 72 const schemaRef = ref<Nullable<FormSchema[]>>(null);
72 73 const formElRef = ref<Nullable<FormActionType>>(null);
73 74  
  75 + const { prefixCls } = useDesign('basic-form');
  76 +
74 77 // Get the basic configuration of the form
75 78 const getProps = computed(
76 79 (): FormProps => {
... ... @@ -78,6 +81,15 @@
78 81 }
79 82 );
80 83  
  84 + const getFormClass = computed(() => {
  85 + return [
  86 + prefixCls,
  87 + {
  88 + [`${prefixCls}--compact`]: unref(getProps).compact,
  89 + },
  90 + ];
  91 + });
  92 +
81 93 // Get uniform row style
82 94 const getRowWrapStyle = computed(
83 95 (): CSSProperties => {
... ... @@ -115,7 +127,7 @@
115 127 defaultValueRef,
116 128 });
117 129  
118   - const { transformDateFunc, fieldMapToTime } = toRefs(props);
  130 + const { transformDateFunc, fieldMapToTime, autoFocusFirstItem } = toRefs(props);
119 131  
120 132 const { handleFormValues, initDefault } = useFormValues({
121 133 transformDateFuncRef: transformDateFunc,
... ... @@ -125,6 +137,13 @@
125 137 formModel,
126 138 });
127 139  
  140 + useAutoFocus({
  141 + getSchema,
  142 + autoFocusFirstItem,
  143 + isInitedDefault: isInitedDefaultRef,
  144 + formElRef: formElRef as Ref<FormActionType>,
  145 + });
  146 +
128 147 const {
129 148 handleSubmit,
130 149 setFieldsValue,
... ... @@ -217,8 +236,51 @@
217 236 getSchema,
218 237 formActionType,
219 238 setFormModel,
  239 + prefixCls,
  240 + getFormClass,
220 241 ...formActionType,
221 242 };
222 243 },
223 244 });
224 245 </script>
  246 +<style lang="less">
  247 + @import (reference) '../../../design/index.less';
  248 + @prefix-cls: ~'@{namespace}-basic-form';
  249 +
  250 + .@{prefix-cls} {
  251 + .ant-form-item {
  252 + &-label label::after {
  253 + margin: 0 6px 0 2px;
  254 + }
  255 +
  256 + &-with-help {
  257 + margin-bottom: 0;
  258 + }
  259 +
  260 + &:not(.ant-form-item-with-help) {
  261 + margin-bottom: 20px;
  262 + }
  263 +
  264 + &.suffix-item {
  265 + .ant-form-item-children {
  266 + display: flex;
  267 + }
  268 +
  269 + .suffix {
  270 + display: inline-block;
  271 + padding-left: 6px;
  272 + }
  273 + }
  274 + }
  275 +
  276 + .ant-form-explain {
  277 + font-size: 14px;
  278 + }
  279 +
  280 + &--compact {
  281 + .ant-form-item {
  282 + margin-bottom: 8px;
  283 + }
  284 + }
  285 + }
  286 +</style>
... ...
src/components/Form/src/componentMap.ts
... ... @@ -19,6 +19,7 @@ import {
19 19 } from 'ant-design-vue';
20 20  
21 21 import RadioButtonGroup from './components/RadioButtonGroup.vue';
  22 +import ApiSelect from './components/ApiSelect.vue';
22 23 import { BasicUpload } from '/@/components/Upload';
23 24  
24 25 const componentMap = new Map<ComponentType, Component>();
... ... @@ -32,6 +33,7 @@ componentMap.set(&#39;InputNumber&#39;, InputNumber);
32 33 componentMap.set('AutoComplete', AutoComplete);
33 34  
34 35 componentMap.set('Select', Select);
  36 +componentMap.set('ApiSelect', ApiSelect);
35 37 // componentMap.set('SelectOptGroup', Select.OptGroup);
36 38 // componentMap.set('SelectOption', Select.Option);
37 39 componentMap.set('TreeSelect', TreeSelect);
... ...
src/components/Form/src/components/ApiSelect.vue 0 → 100644
  1 +<template>
  2 + <Select v-bind="attrs" :options="options" v-model:value="state">
  3 + <template #[item]="data" v-for="item in Object.keys($slots)">
  4 + <slot :name="item" v-bind="data" />
  5 + </template>
  6 + <template #suffixIcon v-if="loading">
  7 + <LoadingOutlined spin />
  8 + </template>
  9 + <template #notFoundContent v-if="loading">
  10 + <span>
  11 + <LoadingOutlined spin class="mr-1" />
  12 + {{ t('component.form.apiSelectNotFound') }}
  13 + </span>
  14 + </template>
  15 + </Select>
  16 +</template>
  17 +<script lang="ts">
  18 + import { defineComponent, PropType, ref, watchEffect } from 'vue';
  19 + import { Select } from 'ant-design-vue';
  20 + import { isFunction } from '/@/utils/is';
  21 + import { useRuleFormItem } from '/@/hooks/component/useFormItem';
  22 + import { useAttrs } from '/@/hooks/core/useAttrs';
  23 + import { get } from 'lodash-es';
  24 +
  25 + import { LoadingOutlined } from '@ant-design/icons-vue';
  26 + import { useI18n } from '/@/hooks/web/useI18n';
  27 +
  28 + type OptionsItem = { label: string; value: string; disabled?: boolean };
  29 +
  30 + export default defineComponent({
  31 + name: 'RadioButtonGroup',
  32 + components: {
  33 + Select,
  34 + LoadingOutlined,
  35 + },
  36 + props: {
  37 + value: {
  38 + type: String as PropType<string>,
  39 + },
  40 + api: {
  41 + type: Function as PropType<(arg: Recordable) => Promise<OptionsItem[]>>,
  42 + default: null,
  43 + },
  44 + params: {
  45 + type: Object as PropType<Recordable>,
  46 + default: () => {},
  47 + },
  48 + resultField: {
  49 + type: String as PropType<string>,
  50 + default: '',
  51 + },
  52 + },
  53 + setup(props) {
  54 + const options = ref<OptionsItem[]>([]);
  55 + const loading = ref(false);
  56 + const attrs = useAttrs();
  57 + const { t } = useI18n();
  58 +
  59 + // Embedded in the form, just use the hook binding to perform form verification
  60 + const [state] = useRuleFormItem(props);
  61 +
  62 + watchEffect(() => {
  63 + fetch();
  64 + });
  65 +
  66 + async function fetch() {
  67 + const api = props.api;
  68 + if (!api || !isFunction(api)) return;
  69 +
  70 + try {
  71 + loading.value = true;
  72 + const res = await api(props.params);
  73 + if (Array.isArray(res)) {
  74 + options.value = res;
  75 + return;
  76 + }
  77 + if (props.resultField) {
  78 + options.value = get(res, props.resultField) || [];
  79 + }
  80 + } catch (error) {
  81 + console.warn(error);
  82 + } finally {
  83 + loading.value = false;
  84 + }
  85 + }
  86 + return { state, attrs, options, loading, t };
  87 + },
  88 + });
  89 +</script>
... ...
src/components/Form/src/components/FormItem.tsx
... ... @@ -3,7 +3,6 @@ import type { FormActionType, FormProps } from &#39;../types/form&#39;;
3 3 import type { FormSchema } from '../types/form';
4 4 import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
5 5 import type { TableActionType } from '/@/components/Table';
6   -import type { ComponentType } from '../types';
7 6  
8 7 import { defineComponent, computed, unref, toRefs } from 'vue';
9 8 import { Form, Col } from 'ant-design-vue';
... ... @@ -16,7 +15,6 @@ import { createPlaceholderMessage, setComponentRuleType } from &#39;../helper&#39;;
16 15 import { upperFirst, cloneDeep } from 'lodash-es';
17 16  
18 17 import { useItemLabelWidth } from '../hooks/useLabelWidth';
19   -import { isNumber } from '/@/utils/is';
20 18 import { useI18n } from '/@/hooks/web/useI18n';
21 19  
22 20 export default defineComponent({
... ... @@ -81,7 +79,7 @@ export default defineComponent({
81 79 if (!isFunction(componentProps)) {
82 80 return componentProps;
83 81 }
84   - return componentProps({ schema, tableAction, formModel, formActionType }) || {};
  82 + return componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
85 83 });
86 84  
87 85 const getDisable = computed(() => {
... ... @@ -99,7 +97,7 @@ export default defineComponent({
99 97 return disabled;
100 98 });
101 99  
102   - function getShow() {
  100 + const getShow = computed(() => {
103 101 const { show, ifShow } = props.schema;
104 102 const { showAdvancedButton } = props.formProps;
105 103 const itemIsAdvanced = showAdvancedButton
... ... @@ -124,7 +122,7 @@ export default defineComponent({
124 122 }
125 123 isShow = isShow && itemIsAdvanced;
126 124 return { isShow, isIfShow };
127   - }
  125 + });
128 126  
129 127 function handleRules(): ValidationRule[] {
130 128 const {
... ... @@ -171,7 +169,7 @@ export default defineComponent({
171 169 }
172 170 }
173 171  
174   - // 最大输入长度规则校验
  172 + // Maximum input length rule check
175 173 const characterInx = rules.findIndex((val) => val.max);
176 174 if (characterInx !== -1 && !rules[characterInx].validator) {
177 175 rules[characterInx].message =
... ... @@ -180,20 +178,6 @@ export default defineComponent({
180 178 return rules;
181 179 }
182 180  
183   - function handleValue(component: ComponentType, field: string) {
184   - const val = props.formModel[field];
185   - if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
186   - if (val && isNumber(val)) {
187   - props.setFormModel(field, `${val}`);
188   -
189   - // props.formModel[field] = `${val}`;
190   - return `${val}`;
191   - }
192   - return val;
193   - }
194   - return val;
195   - }
196   -
197 181 function renderComponent() {
198 182 const {
199 183 renderComponentContent,
... ... @@ -217,7 +201,6 @@ export default defineComponent({
217 201  
218 202 const value = target ? (isCheck ? target.checked : target.value) : e;
219 203 props.setFormModel(field, value);
220   - // props.formModel[field] = value;
221 204 },
222 205 };
223 206 const Comp = componentMap.get(component) as typeof defineComponent;
... ... @@ -233,7 +216,7 @@ export default defineComponent({
233 216  
234 217 const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
235 218 let placeholder;
236   - // RangePicker place为数组
  219 + // RangePicker place is an array
237 220 if (isCreatePlaceholder && component !== 'RangePicker' && component) {
238 221 placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
239 222 }
... ... @@ -242,7 +225,7 @@ export default defineComponent({
242 225 propsData.formValues = unref(getValues);
243 226  
244 227 const bindValue: Recordable = {
245   - [valueField || (isCheck ? 'checked' : 'value')]: handleValue(component, field),
  228 + [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
246 229 };
247 230  
248 231 const compAttr: Recordable = {
... ... @@ -284,7 +267,7 @@ export default defineComponent({
284 267 }
285 268  
286 269 function renderItem() {
287   - const { itemProps, slot, render, field } = props.schema;
  270 + const { itemProps, slot, render, field, suffix } = props.schema;
288 271 const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
289 272 const { colon } = props.formProps;
290 273  
... ... @@ -296,17 +279,27 @@ export default defineComponent({
296 279 : renderComponent();
297 280 };
298 281  
  282 + const showSuffix = !!suffix;
  283 +
  284 + const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
  285 +
299 286 return (
300 287 <Form.Item
301 288 name={field}
302 289 colon={colon}
  290 + class={{ 'suffix-item': showSuffix }}
303 291 {...(itemProps as Recordable)}
304 292 label={renderLabelHelpMessage()}
305 293 rules={handleRules()}
306 294 labelCol={labelCol}
307 295 wrapperCol={wrapperCol}
308 296 >
309   - {() => getContent()}
  297 + {() => (
  298 + <>
  299 + {getContent()}
  300 + {showSuffix && <span class="suffix">{getSuffix}</span>}
  301 + </>
  302 + )}
310 303 </Form.Item>
311 304 );
312 305 }
... ... @@ -317,7 +310,7 @@ export default defineComponent({
317 310 const { baseColProps = {} } = props.formProps;
318 311  
319 312 const realColProps = { ...baseColProps, ...colProps };
320   - const { isIfShow, isShow } = getShow();
  313 + const { isIfShow, isShow } = unref(getShow);
321 314  
322 315 const getContent = () => {
323 316 return colSlot
... ...
src/components/Form/src/helper.ts
1 1 import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
2 2 import type { ComponentType } from './types/index';
3 3 import { useI18n } from '/@/hooks/web/useI18n';
  4 +import { isNumber } from '/@/utils/is';
4 5  
5 6 const { t } = useI18n();
6 7  
... ... @@ -41,6 +42,14 @@ export function setComponentRuleType(rule: ValidationRule, component: ComponentT
41 42 }
42 43 }
43 44  
  45 +export function handleInputNumberValue(component?: ComponentType, val: any) {
  46 + if (!component) return val;
  47 + if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
  48 + return val && isNumber(val) ? `${val}` : val;
  49 + }
  50 + return val;
  51 +}
  52 +
44 53 /**
45 54 * 时间字段
46 55 */
... ...
src/components/Form/src/hooks/useAdvanced.ts
1 1 import type { ColEx } from '../types';
2 2 import type { AdvanceState } from '../types/hooks';
3   -import { ComputedRef, Ref } from 'vue';
  3 +import type { ComputedRef, Ref } from 'vue';
4 4 import type { FormProps, FormSchema } from '../types/form';
5 5  
6 6 import { computed, unref, watch } from 'vue';
... ...
src/components/Form/src/hooks/useAutoFocus.ts 0 → 100644
  1 +import type { ComputedRef, Ref } from 'vue';
  2 +import type { FormSchema, FormActionType } from '../types/form';
  3 +
  4 +import { unref, nextTick, watchEffect } from 'vue';
  5 +
  6 +interface UseAutoFocusContext {
  7 + getSchema: ComputedRef<FormSchema[]>;
  8 + autoFocusFirstItem: Ref<boolean>;
  9 + isInitedDefault: Ref<boolean>;
  10 + formElRef: Ref<FormActionType>;
  11 +}
  12 +export async function useAutoFocus({
  13 + getSchema,
  14 + autoFocusFirstItem,
  15 + formElRef,
  16 + isInitedDefault,
  17 +}: UseAutoFocusContext) {
  18 + watchEffect(async () => {
  19 + if (unref(isInitedDefault) || !unref(autoFocusFirstItem)) return;
  20 + await nextTick();
  21 + const schemas = unref(getSchema);
  22 + const formEl = unref(formElRef);
  23 + const el = (formEl as any)?.$el as HTMLElement;
  24 + if (!formEl || !el || !schemas || schemas.length === 0) return;
  25 +
  26 + const firstItem = schemas[0];
  27 + // Only open when the first form item is input type
  28 + if (!firstItem.component.includes('Input')) return;
  29 +
  30 + const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
  31 + if (!inputEl) return;
  32 + inputEl?.focus();
  33 + });
  34 +}
... ...
src/components/Form/src/hooks/useFormEvents.ts
... ... @@ -6,7 +6,7 @@ import { unref, toRaw } from &#39;vue&#39;;
6 6  
7 7 import { isArray, isFunction, isObject, isString } from '/@/utils/is';
8 8 import { deepMerge, unique } from '/@/utils';
9   -import { dateItemType } from '../helper';
  9 +import { dateItemType, handleInputNumberValue } from '../helper';
10 10 import moment from 'moment';
11 11 import { cloneDeep } from 'lodash-es';
12 12 import { error } from '/@/utils/log';
... ... @@ -49,29 +49,32 @@ export function useFormEvents({
49 49 /**
50 50 * @description: Set form value
51 51 */
52   - async function setFieldsValue(values: any): Promise<void> {
  52 + async function setFieldsValue(values: Recordable): Promise<void> {
53 53 const fields = unref(getSchema)
54 54 .map((item) => item.field)
55 55 .filter(Boolean);
56 56  
57 57 const validKeys: string[] = [];
58 58 Object.keys(values).forEach((key) => {
59   - const element = values[key];
  59 + const schema = unref(getSchema).find((item) => item.field === key);
  60 + let value = values[key];
  61 +
  62 + value = handleInputNumberValue(schema?.component, value);
60 63 // 0| '' is allow
61   - if (element !== undefined && element !== null && fields.includes(key)) {
  64 + if (value !== undefined && value !== null && fields.includes(key)) {
62 65 // time type
63 66 if (itemIsDateType(key)) {
64   - if (Array.isArray(element)) {
65   - const arr: any[] = [];
66   - for (const ele of element) {
  67 + if (Array.isArray(value)) {
  68 + const arr: moment.Moment[] = [];
  69 + for (const ele of value) {
67 70 arr.push(moment(ele));
68 71 }
69 72 formModel[key] = arr;
70 73 } else {
71   - formModel[key] = moment(element);
  74 + formModel[key] = moment(value);
72 75 }
73 76 } else {
74   - formModel[key] = element;
  77 + formModel[key] = value;
75 78 }
76 79 validKeys.push(key);
77 80 }
... ...
src/components/Form/src/props.ts
... ... @@ -65,6 +65,8 @@ export const basicProps = {
65 65 actionColOptions: Object as PropType<Partial<ColEx>>,
66 66 // 显示重置按钮
67 67 showResetButton: propTypes.bool.def(true),
  68 + // 是否聚焦第一个输入框,只在第一个表单项为input的时候作用
  69 + autoFocusFirstItem: propTypes.bool,
68 70 // 重置按钮配置
69 71 resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
70 72  
... ...
src/components/Form/src/types/form.ts
... ... @@ -82,6 +82,8 @@ export interface FormProps {
82 82 rulesMessageJoinLabel?: boolean;
83 83 // Whether to show collapse and expand buttons
84 84 showAdvancedButton?: boolean;
  85 + // Whether to focus on the first input box, only works when the first form item is input
  86 + autoFocusFirstItem?: boolean;
85 87 // Automatically collapse over the specified number of rows
86 88 autoAdvancedLine?: number;
87 89 // Whether to show the operation button
... ... @@ -139,6 +141,8 @@ export interface FormSchema {
139 141 // Required
140 142 required?: boolean;
141 143  
  144 + suffix?: string | number | ((values: RenderCallbackParams) => string | number);
  145 +
142 146 // Validation rules
143 147 rules?: Rule[];
144 148 // Check whether the information is added to the label
... ...
src/components/Form/src/types/index.ts
... ... @@ -89,6 +89,7 @@ export type ComponentType =
89 89 | 'InputNumber'
90 90 | 'InputCountDown'
91 91 | 'Select'
  92 + | 'ApiSelect'
92 93 | 'SelectOptGroup'
93 94 | 'SelectOption'
94 95 | 'TreeSelect'
... ...
src/design/ant/index.less
... ... @@ -49,37 +49,6 @@
49 49 }
50 50  
51 51 // =================================
52   -// ==============form===============
53   -// =================================
54   -.ant-form-item.deltag .ant-form-item-required::before {
55   - content: '';
56   -}
57   -
58   -.ant-form-item-with-help {
59   - margin-bottom: 0;
60   -}
61   -
62   -.ant-form-item {
63   - &-label label::after {
64   - margin: 0 6px 0 2px;
65   - }
66   -}
67   -
68   -.ant-form-item:not(.ant-form-item-with-help) {
69   - margin-bottom: 20px;
70   -}
71   -
72   -.ant-form-explain {
73   - font-size: 14px;
74   -}
75   -
76   -.compact-form-row {
77   - .ant-form-item {
78   - margin-bottom: 8px;
79   - }
80   -}
81   -
82   -// =================================
83 52 // ==============empty==============
84 53 // =================================
85 54 .ant-empty-image {
... ...
src/locales/lang/en/component/form.ts
... ... @@ -8,4 +8,6 @@ export default {
8 8 choose: 'Please Choose ',
9 9  
10 10 maxTip: 'The number of characters should be less than {0}',
  11 +
  12 + apiSelectNotFound: 'Wait for data loading to complete...',
11 13 };
... ...
src/locales/lang/zh_CN/component/form.ts
... ... @@ -8,4 +8,6 @@ export default {
8 8 choose: '请选择',
9 9  
10 10 maxTip: '字符数应小于{0}位',
  11 +
  12 + apiSelectNotFound: '请等待数据加载完成...',
11 13 };
... ...
src/utils/http/axios/index.ts
... ... @@ -105,28 +105,29 @@ const transform: AxiosTransform = {
105 105 if (apiUrl && isString(apiUrl)) {
106 106 config.url = `${apiUrl}${config.url}`;
107 107 }
  108 + const params = config.params || {};
108 109 if (config.method?.toUpperCase() === RequestEnum.GET) {
109   - if (!isString(config.params)) {
  110 + if (!isString(params)) {
110 111 config.data = {
111 112 // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
112   - params: Object.assign(config.params || {}, createNow(joinTime, false)),
  113 + params: Object.assign(params || {}, createNow(joinTime, false)),
113 114 };
114 115 } else {
115 116 // 兼容restful风格
116   - config.url = config.url + config.params + `${createNow(joinTime, true)}`;
  117 + config.url = config.url + params + `${createNow(joinTime, true)}`;
117 118 config.params = undefined;
118 119 }
119 120 } else {
120   - if (!isString(config.params)) {
121   - formatDate && formatRequestDate(config.params);
122   - config.data = config.params;
  121 + if (!isString(params)) {
  122 + formatDate && formatRequestDate(params);
  123 + config.data = params;
123 124 config.params = undefined;
124 125 if (joinParamsToUrl) {
125 126 config.url = setObjToUrlParams(config.url as string, config.data);
126 127 }
127 128 } else {
128 129 // 兼容restful风格
129   - config.url = config.url + config.params;
  130 + config.url = config.url + params;
130 131 config.params = undefined;
131 132 }
132 133 }
... ...
src/views/demo/form/RuleForm.vue
... ... @@ -170,7 +170,7 @@
170 170 }
171 171 function setFormValues() {
172 172 setFieldsValue({
173   - field1: '1111',
  173 + field1: 1111,
174 174 field5: ['1'],
175 175 field7: '1',
176 176 });
... ...
src/views/demo/form/index.vue
... ... @@ -2,6 +2,7 @@
2 2 <div class="m-4">
3 3 <CollapseContainer title="基础示例">
4 4 <BasicForm
  5 + autoFocusFirstItem
5 6 :labelWidth="100"
6 7 :schemas="schemas"
7 8 :actionColOptions="{ span: 24 }"
... ... @@ -16,11 +17,13 @@
16 17 import { CollapseContainer } from '/@/components/Container/index';
17 18 import { useMessage } from '/@/hooks/web/useMessage';
18 19  
  20 + import { optionsListApi } from '/@/api/demo/select';
19 21 const schemas: FormSchema[] = [
20 22 {
21 23 field: 'field1',
22 24 component: 'Input',
23 25 label: '字段1',
  26 +
24 27 colProps: {
25 28 span: 8,
26 29 },
... ... @@ -46,7 +49,7 @@
46 49 {
47 50 field: 'field2',
48 51 component: 'Input',
49   - label: '字段2',
  52 + label: '带后缀',
50 53 defaultValue: '111',
51 54 colProps: {
52 55 span: 8,
... ... @@ -56,6 +59,7 @@
56 59 console.log(e);
57 60 },
58 61 },
  62 + suffix: '天',
59 63 },
60 64 {
61 65 field: 'field3',
... ... @@ -208,6 +212,19 @@
208 212 ],
209 213 },
210 214 },
  215 +
  216 + {
  217 + field: 'field30',
  218 + component: 'ApiSelect',
  219 + label: '远程下拉',
  220 + required: true,
  221 + componentProps: {
  222 + api: optionsListApi,
  223 + },
  224 + colProps: {
  225 + span: 8,
  226 + },
  227 + },
211 228 {
212 229 field: 'field20',
213 230 component: 'InputNumber',
... ...
yarn.lock
... ... @@ -1076,10 +1076,10 @@
1076 1076 resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.4.tgz#46098fb544a4eb3af724219e4955c9022801835e"
1077 1077 integrity sha512-YCSECbeXKFJEIVkKgKMjUzJ439ysufmL/a31B1j7dCvnHaBWsX9J4XehhJgg/aTy3yvhHaVhI6xt1kSMZP799A==
1078 1078  
1079   -"@iconify/json@^1.1.276":
1080   - version "1.1.276"
1081   - resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.276.tgz#c8d51751abc84cc73a466f55bc2f352686451786"
1082   - integrity sha512-Ra/mGT+n38vhi/i1cjsPYOmSR2d6rNIXZ+OsrIWp9J35zAPQ93sSTQMpTyxZdLu3QxU0vYwtcaC7h/Y1/3H3wg==
  1079 +"@iconify/json@^1.1.277":
  1080 + version "1.1.277"
  1081 + resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.277.tgz#e11e01833b05845ce1afc5ad61759804f6ed2eb2"
  1082 + integrity sha512-66n4lsv57iRwtcb2Q8ax8iasVLzFz9VWcqtgobHVrvyfsVqf8hSldJELnTl/gtqayqa35pT4mHEpdfsqt1mnLA==
1083 1083  
1084 1084 "@intlify/core-base@9.0.0-beta.14":
1085 1085 version "9.0.0-beta.14"
... ... @@ -1831,18 +1831,18 @@
1831 1831 vscode-languageserver-textdocument "^1.0.1"
1832 1832 vscode-uri "^2.1.2"
1833 1833  
1834   -"@vueuse/core@^4.0.0":
1835   - version "4.0.0"
1836   - resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0.tgz#5bea3eaa848e3b3e00427f5053fb98e7e4834b0f"
1837   - integrity sha512-BBkqriC2j9SH/LuHCggS2MP7VSwBfGkTB9qQh1lzadodk2TnM1JHwM76f3G0hCGqqhEF7ab8Xs+1M1PlvuEQYA==
  1834 +"@vueuse/core@^4.0.1":
  1835 + version "4.0.1"
  1836 + resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.1.tgz#be90fd09de0264dbe61c571b5967334ca94d8cb2"
  1837 + integrity sha512-bC6H/ES9aFnzp6rT3W3d5j/CqB8mN1UrvBj1RO639QMwxPbJ5/JDjDD4HHtOdIZfA82d6p2Ijbv4Y04mXmkHng==
1838 1838 dependencies:
1839   - "@vueuse/shared" "4.0.0"
  1839 + "@vueuse/shared" "4.0.1"
1840 1840 vue-demi latest
1841 1841  
1842   -"@vueuse/shared@4.0.0":
1843   - version "4.0.0"
1844   - resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0.tgz#d495b8fd2f28a453ef0fccae175ca848a4a84bb0"
1845   - integrity sha512-8tn1BpnaMJU2LqFyFzzN6Dvmc1uDsSlb3Neli5bwwb9f+rcASpuOS3nAWAY6/rIODZP1iwXDNCL4rNFR3YxYtQ==
  1842 +"@vueuse/shared@4.0.1":
  1843 + version "4.0.1"
  1844 + resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.1.tgz#28750d34400cd0cabf2576342c5ee7471b0e27bd"
  1845 + integrity sha512-7SQ1OqUPiuOSe5OFGIn5NvawZ7mfID5V4AwsHwpMAQn22Ex73az6TFE1N/6fL4rZBx6wLrkPfVO9v7vSsOkvlg==
1846 1846 dependencies:
1847 1847 vue-demi latest
1848 1848  
... ... @@ -8039,10 +8039,10 @@ vary@^1.1.2:
8039 8039 resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
8040 8040 integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
8041 8041  
8042   -vditor@^3.7.3:
8043   - version "3.7.3"
8044   - resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.3.tgz#6f7bdee7dca758985b29be1533ed952178f0aac4"
8045   - integrity sha512-2EHwAc9l+HOo6dcScSJDPmVTsVuEqHK2ucZwAHgvctpua3pMz/CAGMHgPoyB5X1Pju7yrLfsESHZh8V6Ndh6rg==
  8042 +vditor@^3.7.4:
  8043 + version "3.7.4"
  8044 + resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.4.tgz#e2ec46f009e99d4ef1804d4ef355d44be7efb9a3"
  8045 + integrity sha512-NfpXCoiVEeaORwGPNaxVDQGHs6Sib2RlI+slSFc5eXV8pFfYM639O6iOLjG2Ks+lN7nM9SsmpcGXwnQ0/S90xA==
8046 8046 dependencies:
8047 8047 diff-match-patch "^1.0.5"
8048 8048  
... ...