mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 00:26:20 +08:00
perf(form): improve the form function
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
- 新增主框架外页面示例
|
||||
- `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。
|
||||
- 新增面包屑导航示例
|
||||
- form: 新增`suffix`属性,用于配置后缀内容
|
||||
- form: 新增远程下拉`ApiSelect`及示例
|
||||
- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
|
24
mock/demo/select-demo.ts
Normal file
24
mock/demo/select-demo.ts
Normal file
@@ -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[];
|
@@ -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
src/api/demo/model/optionsModel.ts
Normal file
11
src/api/demo/model/optionsModel.ts
Normal file
@@ -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
src/api/demo/select.ts
Normal file
16
src/api/demo/select.ts
Normal file
@@ -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',
|
||||
});
|
||||
}
|
@@ -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>
|
||||
|
@@ -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
src/components/Form/src/components/ApiSelect.vue
Normal file
89
src/components/Form/src/components/ApiSelect.vue
Normal file
@@ -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>
|
@@ -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
|
||||
|
@@ -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,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
src/components/Form/src/hooks/useAutoFocus.ts
Normal file
34
src/components/Form/src/hooks/useAutoFocus.ts
Normal file
@@ -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();
|
||||
});
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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>>,
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -89,6 +89,7 @@ export type ComponentType =
|
||||
| 'InputNumber'
|
||||
| 'InputCountDown'
|
||||
| 'Select'
|
||||
| 'ApiSelect'
|
||||
| 'SelectOptGroup'
|
||||
| 'SelectOption'
|
||||
| 'TreeSelect'
|
||||
|
@@ -48,37 +48,6 @@
|
||||
color: @primary-color !important;
|
||||
}
|
||||
|
||||
// =================================
|
||||
// ==============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==============
|
||||
// =================================
|
||||
|
@@ -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...',
|
||||
};
|
||||
|
@@ -8,4 +8,6 @@ export default {
|
||||
choose: '请选择',
|
||||
|
||||
maxTip: '字符数应小于{0}位',
|
||||
|
||||
apiSelectNotFound: '请等待数据加载完成...',
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -170,7 +170,7 @@
|
||||
}
|
||||
function setFormValues() {
|
||||
setFieldsValue({
|
||||
field1: '1111',
|
||||
field1: 1111,
|
||||
field5: ['1'],
|
||||
field7: '1',
|
||||
});
|
||||
|
@@ -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',
|
||||
|
34
yarn.lock
34
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"
|
||||
|
||||
|
Reference in New Issue
Block a user