提交 ac1a3695 作者: vben

perf(form): improve the form function

上级 4ff1c408
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
- 新增主框架外页面示例 - 新增主框架外页面示例
- `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。 - `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。
- 新增面包屑导航示例 - 新增面包屑导航示例
- form: 新增`suffix`属性,用于配置后缀内容
- form: 新增远程下拉`ApiSelect`及示例
- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
### 🐛 Bug Fixes ### 🐛 Bug Fixes
......
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 @@ ...@@ -22,7 +22,7 @@
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.0-rc.4", "@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", "ant-design-vue": "^2.0.0-rc.5",
"apexcharts": "^3.23.0", "apexcharts": "^3.23.0",
"axios": "^0.21.1", "axios": "^0.21.1",
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"sortablejs": "^1.12.0", "sortablejs": "^1.12.0",
"vditor": "^3.7.3", "vditor": "^3.7.4",
"vue": "^3.0.4", "vue": "^3.0.4",
"vue-i18n": "9.0.0-beta.14", "vue-i18n": "9.0.0-beta.14",
"vue-router": "^4.0.1", "vue-router": "^4.0.1",
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^11.0.0", "@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^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", "@ls-lint/ls-lint": "^1.9.2",
"@purge-icons/generated": "^0.4.1", "@purge-icons/generated": "^0.4.1",
"@types/echarts": "^4.9.3", "@types/echarts": "^4.9.3",
......
import { BasicFetchResult } from '/@/api/model/baseModel';
export interface DemoOptionsItem {
label: string;
value: string;
}
/**
* @description: Request list return value
*/
export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem[]>;
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',
});
}
<template> <template>
<Form v-bind="{ ...$attrs, ...$props }" ref="formElRef" :model="formModel"> <Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
<Row :class="getProps.compact ? 'compact-form-row' : ''" :style="getRowWrapStyle"> <Row :style="getRowWrapStyle">
<slot name="formHeader" /> <slot name="formHeader" />
<template v-for="schema in getSchema" :key="schema.field"> <template v-for="schema in getSchema" :key="schema.field">
<FormItem <FormItem
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
</FormItem> </FormItem>
</template> </template>
<!-- -->
<FormAction <FormAction
v-bind="{ ...getProps, ...advanceState }" v-bind="{ ...getProps, ...advanceState }"
@toggle-advanced="handleToggleAdvanced" @toggle-advanced="handleToggleAdvanced"
...@@ -46,8 +45,10 @@ ...@@ -46,8 +45,10 @@
import useAdvanced from './hooks/useAdvanced'; import useAdvanced from './hooks/useAdvanced';
import { useFormEvents } from './hooks/useFormEvents'; import { useFormEvents } from './hooks/useFormEvents';
import { createFormContext } from './hooks/useFormContext'; import { createFormContext } from './hooks/useFormContext';
import { useAutoFocus } from './hooks/useAutoFocus';
import { basicProps } from './props'; import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({ export default defineComponent({
name: 'BasicForm', name: 'BasicForm',
...@@ -71,6 +72,8 @@ ...@@ -71,6 +72,8 @@
const schemaRef = ref<Nullable<FormSchema[]>>(null); const schemaRef = ref<Nullable<FormSchema[]>>(null);
const formElRef = ref<Nullable<FormActionType>>(null); const formElRef = ref<Nullable<FormActionType>>(null);
const { prefixCls } = useDesign('basic-form');
// Get the basic configuration of the form // Get the basic configuration of the form
const getProps = computed( const getProps = computed(
(): FormProps => { (): FormProps => {
...@@ -78,6 +81,15 @@ ...@@ -78,6 +81,15 @@
} }
); );
const getFormClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--compact`]: unref(getProps).compact,
},
];
});
// Get uniform row style // Get uniform row style
const getRowWrapStyle = computed( const getRowWrapStyle = computed(
(): CSSProperties => { (): CSSProperties => {
...@@ -115,7 +127,7 @@ ...@@ -115,7 +127,7 @@
defaultValueRef, defaultValueRef,
}); });
const { transformDateFunc, fieldMapToTime } = toRefs(props); const { transformDateFunc, fieldMapToTime, autoFocusFirstItem } = toRefs(props);
const { handleFormValues, initDefault } = useFormValues({ const { handleFormValues, initDefault } = useFormValues({
transformDateFuncRef: transformDateFunc, transformDateFuncRef: transformDateFunc,
...@@ -125,6 +137,13 @@ ...@@ -125,6 +137,13 @@
formModel, formModel,
}); });
useAutoFocus({
getSchema,
autoFocusFirstItem,
isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>,
});
const { const {
handleSubmit, handleSubmit,
setFieldsValue, setFieldsValue,
...@@ -217,8 +236,51 @@ ...@@ -217,8 +236,51 @@
getSchema, getSchema,
formActionType, formActionType,
setFormModel, setFormModel,
prefixCls,
getFormClass,
...formActionType, ...formActionType,
}; };
}, },
}); });
</script> </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 { ...@@ -19,6 +19,7 @@ import {
} from 'ant-design-vue'; } from 'ant-design-vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue'; import RadioButtonGroup from './components/RadioButtonGroup.vue';
import ApiSelect from './components/ApiSelect.vue';
import { BasicUpload } from '/@/components/Upload'; import { BasicUpload } from '/@/components/Upload';
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>();
...@@ -32,6 +33,7 @@ componentMap.set('InputNumber', InputNumber); ...@@ -32,6 +33,7 @@ componentMap.set('InputNumber', InputNumber);
componentMap.set('AutoComplete', AutoComplete); componentMap.set('AutoComplete', AutoComplete);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect);
// componentMap.set('SelectOptGroup', Select.OptGroup); // componentMap.set('SelectOptGroup', Select.OptGroup);
// componentMap.set('SelectOption', Select.Option); // componentMap.set('SelectOption', Select.Option);
componentMap.set('TreeSelect', TreeSelect); componentMap.set('TreeSelect', TreeSelect);
......
<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'; ...@@ -3,7 +3,6 @@ import type { FormActionType, FormProps } from '../types/form';
import type { FormSchema } from '../types/form'; import type { FormSchema } from '../types/form';
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { TableActionType } from '/@/components/Table'; import type { TableActionType } from '/@/components/Table';
import type { ComponentType } from '../types';
import { defineComponent, computed, unref, toRefs } from 'vue'; import { defineComponent, computed, unref, toRefs } from 'vue';
import { Form, Col } from 'ant-design-vue'; import { Form, Col } from 'ant-design-vue';
...@@ -16,7 +15,6 @@ import { createPlaceholderMessage, setComponentRuleType } from '../helper'; ...@@ -16,7 +15,6 @@ import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { upperFirst, cloneDeep } from 'lodash-es'; import { upperFirst, cloneDeep } from 'lodash-es';
import { useItemLabelWidth } from '../hooks/useLabelWidth'; import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { isNumber } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
...@@ -81,7 +79,7 @@ export default defineComponent({ ...@@ -81,7 +79,7 @@ export default defineComponent({
if (!isFunction(componentProps)) { if (!isFunction(componentProps)) {
return componentProps; return componentProps;
} }
return componentProps({ schema, tableAction, formModel, formActionType }) || {}; return componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
}); });
const getDisable = computed(() => { const getDisable = computed(() => {
...@@ -99,7 +97,7 @@ export default defineComponent({ ...@@ -99,7 +97,7 @@ export default defineComponent({
return disabled; return disabled;
}); });
function getShow() { const getShow = computed(() => {
const { show, ifShow } = props.schema; const { show, ifShow } = props.schema;
const { showAdvancedButton } = props.formProps; const { showAdvancedButton } = props.formProps;
const itemIsAdvanced = showAdvancedButton const itemIsAdvanced = showAdvancedButton
...@@ -124,7 +122,7 @@ export default defineComponent({ ...@@ -124,7 +122,7 @@ export default defineComponent({
} }
isShow = isShow && itemIsAdvanced; isShow = isShow && itemIsAdvanced;
return { isShow, isIfShow }; return { isShow, isIfShow };
} });
function handleRules(): ValidationRule[] { function handleRules(): ValidationRule[] {
const { const {
...@@ -171,7 +169,7 @@ export default defineComponent({ ...@@ -171,7 +169,7 @@ export default defineComponent({
} }
} }
// 最大输入长度规则校验 // Maximum input length rule check
const characterInx = rules.findIndex((val) => val.max); const characterInx = rules.findIndex((val) => val.max);
if (characterInx !== -1 && !rules[characterInx].validator) { if (characterInx !== -1 && !rules[characterInx].validator) {
rules[characterInx].message = rules[characterInx].message =
...@@ -180,20 +178,6 @@ export default defineComponent({ ...@@ -180,20 +178,6 @@ export default defineComponent({
return rules; 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() { function renderComponent() {
const { const {
renderComponentContent, renderComponentContent,
...@@ -217,7 +201,6 @@ export default defineComponent({ ...@@ -217,7 +201,6 @@ export default defineComponent({
const value = target ? (isCheck ? target.checked : target.value) : e; const value = target ? (isCheck ? target.checked : target.value) : e;
props.setFormModel(field, value); props.setFormModel(field, value);
// props.formModel[field] = value;
}, },
}; };
const Comp = componentMap.get(component) as typeof defineComponent; const Comp = componentMap.get(component) as typeof defineComponent;
...@@ -233,7 +216,7 @@ export default defineComponent({ ...@@ -233,7 +216,7 @@ export default defineComponent({
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder; const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
let placeholder; let placeholder;
// RangePicker place为数组 // RangePicker place is an array
if (isCreatePlaceholder && component !== 'RangePicker' && component) { if (isCreatePlaceholder && component !== 'RangePicker' && component) {
placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component); placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
} }
...@@ -242,7 +225,7 @@ export default defineComponent({ ...@@ -242,7 +225,7 @@ export default defineComponent({
propsData.formValues = unref(getValues); propsData.formValues = unref(getValues);
const bindValue: Recordable = { const bindValue: Recordable = {
[valueField || (isCheck ? 'checked' : 'value')]: handleValue(component, field), [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
}; };
const compAttr: Recordable = { const compAttr: Recordable = {
...@@ -284,7 +267,7 @@ export default defineComponent({ ...@@ -284,7 +267,7 @@ export default defineComponent({
} }
function renderItem() { function renderItem() {
const { itemProps, slot, render, field } = props.schema; const { itemProps, slot, render, field, suffix } = props.schema;
const { labelCol, wrapperCol } = unref(itemLabelWidthProp); const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
const { colon } = props.formProps; const { colon } = props.formProps;
...@@ -296,17 +279,27 @@ export default defineComponent({ ...@@ -296,17 +279,27 @@ export default defineComponent({
: renderComponent(); : renderComponent();
}; };
const showSuffix = !!suffix;
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
return ( return (
<Form.Item <Form.Item
name={field} name={field}
colon={colon} colon={colon}
class={{ 'suffix-item': showSuffix }}
{...(itemProps as Recordable)} {...(itemProps as Recordable)}
label={renderLabelHelpMessage()} label={renderLabelHelpMessage()}
rules={handleRules()} rules={handleRules()}
labelCol={labelCol} labelCol={labelCol}
wrapperCol={wrapperCol} wrapperCol={wrapperCol}
> >
{() => getContent()} {() => (
<>
{getContent()}
{showSuffix && <span class="suffix">{getSuffix}</span>}
</>
)}
</Form.Item> </Form.Item>
); );
} }
...@@ -317,7 +310,7 @@ export default defineComponent({ ...@@ -317,7 +310,7 @@ export default defineComponent({
const { baseColProps = {} } = props.formProps; const { baseColProps = {} } = props.formProps;
const realColProps = { ...baseColProps, ...colProps }; const realColProps = { ...baseColProps, ...colProps };
const { isIfShow, isShow } = getShow(); const { isIfShow, isShow } = unref(getShow);
const getContent = () => { const getContent = () => {
return colSlot return colSlot
......
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { ComponentType } from './types/index'; import type { ComponentType } from './types/index';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { isNumber } from '/@/utils/is';
const { t } = useI18n(); const { t } = useI18n();
...@@ -41,6 +42,14 @@ export function setComponentRuleType(rule: ValidationRule, component: ComponentT ...@@ -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;
}
/** /**
* 时间字段 * 时间字段
*/ */
......
import type { ColEx } from '../types'; import type { ColEx } from '../types';
import type { AdvanceState } from '../types/hooks'; 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 type { FormProps, FormSchema } from '../types/form';
import { computed, unref, watch } from 'vue'; import { computed, unref, watch } from 'vue';
......
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'; ...@@ -6,7 +6,7 @@ import { unref, toRaw } from 'vue';
import { isArray, isFunction, isObject, isString } from '/@/utils/is'; import { isArray, isFunction, isObject, isString } from '/@/utils/is';
import { deepMerge, unique } from '/@/utils'; import { deepMerge, unique } from '/@/utils';
import { dateItemType } from '../helper'; import { dateItemType, handleInputNumberValue } from '../helper';
import moment from 'moment'; import moment from 'moment';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { error } from '/@/utils/log'; import { error } from '/@/utils/log';
...@@ -49,29 +49,32 @@ export function useFormEvents({ ...@@ -49,29 +49,32 @@ export function useFormEvents({
/** /**
* @description: Set form value * @description: Set form value
*/ */
async function setFieldsValue(values: any): Promise<void> { async function setFieldsValue(values: Recordable): Promise<void> {
const fields = unref(getSchema) const fields = unref(getSchema)
.map((item) => item.field) .map((item) => item.field)
.filter(Boolean); .filter(Boolean);
const validKeys: string[] = []; const validKeys: string[] = [];
Object.keys(values).forEach((key) => { 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 // 0| '' is allow
if (element !== undefined && element !== null && fields.includes(key)) { if (value !== undefined && value !== null && fields.includes(key)) {
// time type // time type
if (itemIsDateType(key)) { if (itemIsDateType(key)) {
if (Array.isArray(element)) { if (Array.isArray(value)) {
const arr: any[] = []; const arr: moment.Moment[] = [];
for (const ele of element) { for (const ele of value) {
arr.push(moment(ele)); arr.push(moment(ele));
} }
formModel[key] = arr; formModel[key] = arr;
} else { } else {
formModel[key] = moment(element); formModel[key] = moment(value);
} }
} else { } else {
formModel[key] = element; formModel[key] = value;
} }
validKeys.push(key); validKeys.push(key);
} }
......
...@@ -65,6 +65,8 @@ export const basicProps = { ...@@ -65,6 +65,8 @@ export const basicProps = {
actionColOptions: Object as PropType<Partial<ColEx>>, actionColOptions: Object as PropType<Partial<ColEx>>,
// 显示重置按钮 // 显示重置按钮
showResetButton: propTypes.bool.def(true), showResetButton: propTypes.bool.def(true),
// 是否聚焦第一个输入框,只在第一个表单项为input的时候作用
autoFocusFirstItem: propTypes.bool,
// 重置按钮配置 // 重置按钮配置
resetButtonOptions: Object as PropType<Partial<ButtonProps>>, resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
......
...@@ -82,6 +82,8 @@ export interface FormProps { ...@@ -82,6 +82,8 @@ export interface FormProps {
rulesMessageJoinLabel?: boolean; rulesMessageJoinLabel?: boolean;
// Whether to show collapse and expand buttons // Whether to show collapse and expand buttons
showAdvancedButton?: boolean; 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 // Automatically collapse over the specified number of rows
autoAdvancedLine?: number; autoAdvancedLine?: number;
// Whether to show the operation button // Whether to show the operation button
...@@ -139,6 +141,8 @@ export interface FormSchema { ...@@ -139,6 +141,8 @@ export interface FormSchema {
// Required // Required
required?: boolean; required?: boolean;
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
// Validation rules // Validation rules
rules?: Rule[]; rules?: Rule[];
// Check whether the information is added to the label // Check whether the information is added to the label
......
...@@ -89,6 +89,7 @@ export type ComponentType = ...@@ -89,6 +89,7 @@ export type ComponentType =
| 'InputNumber' | 'InputNumber'
| 'InputCountDown' | 'InputCountDown'
| 'Select' | 'Select'
| 'ApiSelect'
| 'SelectOptGroup' | 'SelectOptGroup'
| 'SelectOption' | 'SelectOption'
| 'TreeSelect' | 'TreeSelect'
......
...@@ -49,37 +49,6 @@ ...@@ -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============== // ==============empty==============
// ================================= // =================================
.ant-empty-image { .ant-empty-image {
......
...@@ -8,4 +8,6 @@ export default { ...@@ -8,4 +8,6 @@ export default {
choose: 'Please Choose ', choose: 'Please Choose ',
maxTip: 'The number of characters should be less than {0}', maxTip: 'The number of characters should be less than {0}',
apiSelectNotFound: 'Wait for data loading to complete...',
}; };
...@@ -8,4 +8,6 @@ export default { ...@@ -8,4 +8,6 @@ export default {
choose: '请选择', choose: '请选择',
maxTip: '字符数应小于{0}位', maxTip: '字符数应小于{0}位',
apiSelectNotFound: '请等待数据加载完成...',
}; };
...@@ -105,28 +105,29 @@ const transform: AxiosTransform = { ...@@ -105,28 +105,29 @@ const transform: AxiosTransform = {
if (apiUrl && isString(apiUrl)) { if (apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`; config.url = `${apiUrl}${config.url}`;
} }
const params = config.params || {};
if (config.method?.toUpperCase() === RequestEnum.GET) { if (config.method?.toUpperCase() === RequestEnum.GET) {
if (!isString(config.params)) { if (!isString(params)) {
config.data = { config.data = {
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。 // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
params: Object.assign(config.params || {}, createNow(joinTime, false)), params: Object.assign(params || {}, createNow(joinTime, false)),
}; };
} else { } else {
// 兼容restful风格 // 兼容restful风格
config.url = config.url + config.params + `${createNow(joinTime, true)}`; config.url = config.url + params + `${createNow(joinTime, true)}`;
config.params = undefined; config.params = undefined;
} }
} else { } else {
if (!isString(config.params)) { if (!isString(params)) {
formatDate && formatRequestDate(config.params); formatDate && formatRequestDate(params);
config.data = config.params; config.data = params;
config.params = undefined; config.params = undefined;
if (joinParamsToUrl) { if (joinParamsToUrl) {
config.url = setObjToUrlParams(config.url as string, config.data); config.url = setObjToUrlParams(config.url as string, config.data);
} }
} else { } else {
// 兼容restful风格 // 兼容restful风格
config.url = config.url + config.params; config.url = config.url + params;
config.params = undefined; config.params = undefined;
} }
} }
......
...@@ -170,7 +170,7 @@ ...@@ -170,7 +170,7 @@
} }
function setFormValues() { function setFormValues() {
setFieldsValue({ setFieldsValue({
field1: '1111', field1: 1111,
field5: ['1'], field5: ['1'],
field7: '1', field7: '1',
}); });
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<div class="m-4"> <div class="m-4">
<CollapseContainer title="基础示例"> <CollapseContainer title="基础示例">
<BasicForm <BasicForm
autoFocusFirstItem
:labelWidth="100" :labelWidth="100"
:schemas="schemas" :schemas="schemas"
:actionColOptions="{ span: 24 }" :actionColOptions="{ span: 24 }"
...@@ -16,11 +17,13 @@ ...@@ -16,11 +17,13 @@
import { CollapseContainer } from '/@/components/Container/index'; import { CollapseContainer } from '/@/components/Container/index';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { optionsListApi } from '/@/api/demo/select';
const schemas: FormSchema[] = [ const schemas: FormSchema[] = [
{ {
field: 'field1', field: 'field1',
component: 'Input', component: 'Input',
label: '字段1', label: '字段1',
colProps: { colProps: {
span: 8, span: 8,
}, },
...@@ -46,7 +49,7 @@ ...@@ -46,7 +49,7 @@
{ {
field: 'field2', field: 'field2',
component: 'Input', component: 'Input',
label: '字段2', label: '带后缀',
defaultValue: '111', defaultValue: '111',
colProps: { colProps: {
span: 8, span: 8,
...@@ -56,6 +59,7 @@ ...@@ -56,6 +59,7 @@
console.log(e); console.log(e);
}, },
}, },
suffix: '天',
}, },
{ {
field: 'field3', field: 'field3',
...@@ -208,6 +212,19 @@ ...@@ -208,6 +212,19 @@
], ],
}, },
}, },
{
field: 'field30',
component: 'ApiSelect',
label: '远程下拉',
required: true,
componentProps: {
api: optionsListApi,
},
colProps: {
span: 8,
},
},
{ {
field: 'field20', field: 'field20',
component: 'InputNumber', component: 'InputNumber',
......
...@@ -1076,10 +1076,10 @@ ...@@ -1076,10 +1076,10 @@
resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.4.tgz#46098fb544a4eb3af724219e4955c9022801835e" resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.4.tgz#46098fb544a4eb3af724219e4955c9022801835e"
integrity sha512-YCSECbeXKFJEIVkKgKMjUzJ439ysufmL/a31B1j7dCvnHaBWsX9J4XehhJgg/aTy3yvhHaVhI6xt1kSMZP799A== integrity sha512-YCSECbeXKFJEIVkKgKMjUzJ439ysufmL/a31B1j7dCvnHaBWsX9J4XehhJgg/aTy3yvhHaVhI6xt1kSMZP799A==
"@iconify/json@^1.1.276": "@iconify/json@^1.1.277":
version "1.1.276" version "1.1.277"
resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.276.tgz#c8d51751abc84cc73a466f55bc2f352686451786" resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.277.tgz#e11e01833b05845ce1afc5ad61759804f6ed2eb2"
integrity sha512-Ra/mGT+n38vhi/i1cjsPYOmSR2d6rNIXZ+OsrIWp9J35zAPQ93sSTQMpTyxZdLu3QxU0vYwtcaC7h/Y1/3H3wg== integrity sha512-66n4lsv57iRwtcb2Q8ax8iasVLzFz9VWcqtgobHVrvyfsVqf8hSldJELnTl/gtqayqa35pT4mHEpdfsqt1mnLA==
"@intlify/core-base@9.0.0-beta.14": "@intlify/core-base@9.0.0-beta.14":
version "9.0.0-beta.14" version "9.0.0-beta.14"
...@@ -1831,18 +1831,18 @@ ...@@ -1831,18 +1831,18 @@
vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-textdocument "^1.0.1"
vscode-uri "^2.1.2" vscode-uri "^2.1.2"
"@vueuse/core@^4.0.0": "@vueuse/core@^4.0.1":
version "4.0.0" version "4.0.1"
resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0.tgz#5bea3eaa848e3b3e00427f5053fb98e7e4834b0f" resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.1.tgz#be90fd09de0264dbe61c571b5967334ca94d8cb2"
integrity sha512-BBkqriC2j9SH/LuHCggS2MP7VSwBfGkTB9qQh1lzadodk2TnM1JHwM76f3G0hCGqqhEF7ab8Xs+1M1PlvuEQYA== integrity sha512-bC6H/ES9aFnzp6rT3W3d5j/CqB8mN1UrvBj1RO639QMwxPbJ5/JDjDD4HHtOdIZfA82d6p2Ijbv4Y04mXmkHng==
dependencies: dependencies:
"@vueuse/shared" "4.0.0" "@vueuse/shared" "4.0.1"
vue-demi latest vue-demi latest
"@vueuse/shared@4.0.0": "@vueuse/shared@4.0.1":
version "4.0.0" version "4.0.1"
resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0.tgz#d495b8fd2f28a453ef0fccae175ca848a4a84bb0" resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.1.tgz#28750d34400cd0cabf2576342c5ee7471b0e27bd"
integrity sha512-8tn1BpnaMJU2LqFyFzzN6Dvmc1uDsSlb3Neli5bwwb9f+rcASpuOS3nAWAY6/rIODZP1iwXDNCL4rNFR3YxYtQ== integrity sha512-7SQ1OqUPiuOSe5OFGIn5NvawZ7mfID5V4AwsHwpMAQn22Ex73az6TFE1N/6fL4rZBx6wLrkPfVO9v7vSsOkvlg==
dependencies: dependencies:
vue-demi latest vue-demi latest
...@@ -8039,10 +8039,10 @@ vary@^1.1.2: ...@@ -8039,10 +8039,10 @@ vary@^1.1.2:
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
vditor@^3.7.3: vditor@^3.7.4:
version "3.7.3" version "3.7.4"
resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.3.tgz#6f7bdee7dca758985b29be1533ed952178f0aac4" resolved "https://registry.npmjs.org/vditor/-/vditor-3.7.4.tgz#e2ec46f009e99d4ef1804d4ef355d44be7efb9a3"
integrity sha512-2EHwAc9l+HOo6dcScSJDPmVTsVuEqHK2ucZwAHgvctpua3pMz/CAGMHgPoyB5X1Pju7yrLfsESHZh8V6Ndh6rg== integrity sha512-NfpXCoiVEeaORwGPNaxVDQGHs6Sib2RlI+slSFc5eXV8pFfYM639O6iOLjG2Ks+lN7nM9SsmpcGXwnQ0/S90xA==
dependencies: dependencies:
diff-match-patch "^1.0.5" diff-match-patch "^1.0.5"
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论