提交 1d45617e 编写于 作者: V vben

refactor(form): enhanced form customization and dynamic capabilities

上级 3f78b5aa
......@@ -4,6 +4,7 @@
- 重构 hook,引入 `@vueuse`,删除其中已有的`hook`,优化现有的 hook
- `useEvent` 更名->`useEventListener`
- 表单`ComponentType`删除 `SelectOptGroup`,`SelectOption`,`Transfer`,`Radio`,四个类型。修改`RadioButtonGroup`组件
### ✨ Features
......@@ -12,10 +13,15 @@
- 新增菜单及顶栏颜色选择配色
- 增加示例结果页
### ⚡ Wip
- 上传组件(未完成,测试中...)
### ⚡ Performance Improvements
- 优化 settingDrawer 代码
- 优化多标签页切换速度
- 增加表单自定义及动态能力
### 🐛 Bug Fixes
......@@ -23,6 +29,7 @@
- 修复登录过期后重新登录未跳转原来页面的
- 修复 window 系统动态引入错误
- 修复页面类型错误
- 修复表单 switch 和 checkBox 单独使用报错
## 2.0.0-rc.9 (2020-11-9)
......
......@@ -5,6 +5,7 @@
<template v-for="schema in getSchema" :key="schema.field">
<FormItem
:tableAction="tableAction"
:formActionType="formActionType"
:schema="schema"
:formProps="getProps"
:allDefaultValues="defaultValueRef"
......@@ -164,7 +165,7 @@
propsRef.value = mergeProps;
}
const methods: Partial<FormActionType> = {
const formActionType: Partial<FormActionType> = {
getFieldsValue,
setFieldsValue,
resetFields,
......@@ -179,7 +180,7 @@
onMounted(() => {
initDefault();
emit('register', methods);
emit('register', formActionType);
});
return {
......@@ -191,7 +192,8 @@
getProps,
formElRef,
getSchema,
...methods,
formActionType,
...formActionType,
};
},
});
......
import type { PropType } from 'vue';
import type { FormProps } from './types/form';
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';
......@@ -41,6 +41,9 @@ export default defineComponent({
tableAction: {
type: Object as PropType<TableActionType>,
},
formActionType: {
type: Object as PropType<FormActionType>,
},
},
setup(props, { slots }) {
const itemLabelWidthRef = useItemLabelWidth(toRef(props, 'schema'), toRef(props, 'formProps'));
......@@ -61,12 +64,12 @@ export default defineComponent({
});
const getComponentsPropsRef = computed(() => {
const { schema, tableAction, formModel } = props;
const { schema, tableAction, formModel, formActionType } = props;
const { componentProps = {} } = schema;
if (!isFunction(componentProps)) {
return componentProps;
}
return componentProps({ schema, tableAction, formModel }) || {};
return componentProps({ schema, tableAction, formModel, formActionType }) || {};
});
const getDisableRef = computed(() => {
......@@ -179,17 +182,27 @@ export default defineComponent({
}
function renderComponent() {
const { renderComponentContent, component, field, changeEvent = 'change' } = props.schema;
const {
renderComponentContent,
component,
field,
changeEvent = 'change',
valueField,
} = props.schema;
const isCheck = component && ['Switch'].includes(component);
const isCheck = component && ['Switch', 'Checkbox'].includes(component);
const eventKey = `on${upperFirst(changeEvent)}`;
const on = {
[eventKey]: (e: any) => {
if (propsData[eventKey]) {
propsData[eventKey](e);
}
(props.formModel as any)[field] = e && e.target ? e.target.value : e;
const target = e ? e.target : null;
const value = target ? (isCheck ? target.checked : target.value) : e;
(props.formModel as any)[field] = value;
},
};
const Comp = componentMap.get(component);
......@@ -215,17 +228,20 @@ export default defineComponent({
propsData.codeField = field;
propsData.formValues = unref(getValuesRef);
const bindValue = {
[isCheck ? 'checked' : 'value']: handleValue(component, field),
[valueField || (isCheck ? 'checked' : 'value')]: handleValue(component, field),
};
if (!renderComponentContent) {
return <Comp {...propsData} {...on} {...bindValue} />;
}
const compSlot = isFunction(renderComponentContent)
? { ...renderComponentContent(unref(getValuesRef)) }
: {
default: () => renderComponentContent,
};
return (
<Comp {...propsData} {...on} {...bindValue}>
{{
...renderComponentContent(unref(getValuesRef)),
}}
{compSlot}
</Comp>
);
}
......@@ -249,7 +265,7 @@ export default defineComponent({
const { colon } = props.formProps;
const getContent = () => {
return slot
? getSlot(slots, slot)
? getSlot(slots, slot, unref(getValuesRef))
: render
? render(unref(getValuesRef))
: renderComponent();
......@@ -276,7 +292,7 @@ export default defineComponent({
const { isIfShow, isShow } = getShow();
const getContent = () => {
return colSlot
? getSlot(slots, colSlot)
? getSlot(slots, colSlot, unref(getValuesRef))
: renderColContent
? renderColContent(unref(getValuesRef))
: renderItem();
......
......@@ -14,8 +14,8 @@ import {
Switch,
TimePicker,
TreeSelect,
Transfer,
} from 'ant-design-vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue';
import { ComponentType } from './types/index';
......@@ -30,13 +30,13 @@ componentMap.set('InputNumber', InputNumber);
componentMap.set('AutoComplete', AutoComplete);
componentMap.set('Select', Select);
componentMap.set('SelectOptGroup', Select.OptGroup);
componentMap.set('SelectOption', Select.Option);
// componentMap.set('SelectOptGroup', Select.OptGroup);
// componentMap.set('SelectOption', Select.Option);
componentMap.set('TreeSelect', TreeSelect);
componentMap.set('Transfer', Transfer);
componentMap.set('Radio', Radio);
// componentMap.set('Transfer', Transfer);
// componentMap.set('Radio', Radio);
componentMap.set('Switch', Switch);
componentMap.set('RadioButton', Radio.Button);
componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('RadioGroup', Radio.Group);
componentMap.set('Checkbox', Checkbox);
componentMap.set('CheckboxGroup', Checkbox.Group);
......
<template>
<RadioGroup v-bind="$attrs" v-model:value="valueRef" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton :value="item.value"> {{ item.label }} </RadioButton>
</template>
</RadioGroup>
</template>
<script lang="ts">
import { defineComponent, ref, PropType, watch, unref, computed } from 'vue';
import { Radio } from 'ant-design-vue';
import {} from 'ant-design-vue/es/radio/Group';
import { isString } from '/@/utils/is';
type OptionsItem = { label: string; value: string; disabled?: boolean };
type RadioItem = string | OptionsItem;
export default defineComponent({
name: 'RadioButtonGroup',
components: {
RadioGroup: Radio.Group,
RadioButton: Radio.Button,
},
props: {
value: {
type: String as PropType<string>,
},
options: {
type: Array as PropType<RadioItem[]>,
default: () => [],
},
},
setup(props, { emit }) {
const valueRef = ref('');
watch(
() => props.value,
(v = '') => {
valueRef.value = v;
},
{ immediate: true }
);
watch(
() => unref(valueRef),
() => {
emit('change', valueRef.value);
},
{ immediate: true }
);
const getOptions = computed((): OptionsItem[] => {
const { options } = props;
if (!options || options.length === 0) return [];
const isStringArr = options.some((item) => isString(item));
if (!isStringArr) return options as OptionsItem[];
return options.map((item) => ({ label: item, value: item })) as OptionsItem[];
});
return { valueRef, getOptions };
},
});
</script>
......@@ -7,6 +7,10 @@ import { TableActionType } from '../../../Table/src/types/table';
export type FieldMapToTime = [string, [string, string], string?][];
export type Rule = RuleObject & {
trigger?: 'blur' | 'change' | ['change', 'blur'];
};
export interface RenderCallbackParams {
schema: FormSchema;
values: any;
......@@ -98,7 +102,10 @@ export interface FormProps {
export interface FormSchema {
// 字段名
field: string;
// 内部值更改触发的事件名,默认 change
changeEvent?: string;
// v-model绑定的变量名 默认 value
valueField?: string;
// 标签名
label: string;
// 文本右侧帮助文本
......@@ -113,13 +120,18 @@ export interface FormSchema {
component: ComponentType;
// 组件参数
componentProps?:
| ((opt: { schema: FormSchema; tableAction: TableActionType; formModel: any }) => any)
| ((opt: {
schema: FormSchema;
tableAction: TableActionType;
formActionType: FormActionType;
formModel: any;
}) => any)
| object;
// 必填
required?: boolean;
// 校验规则
rules?: RuleObject[];
rules?: Rule[];
// 校验信息是否加入label
rulesMessageJoinLabel?: boolean;
......@@ -146,7 +158,11 @@ export interface FormSchema {
// 渲染 col内容,需要外层包裹 form-item
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
renderComponentContent?: (renderCallbackParams: RenderCallbackParams) => any;
renderComponentContent?:
| ((renderCallbackParams: RenderCallbackParams) => any)
| VNode
| VNode[]
| string;
// 自定义slot, 在 from-item内
slot?: string;
......@@ -156,7 +172,7 @@ export interface FormSchema {
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => RuleObject[];
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
}
export interface HelpComponentProps {
maxWidth: string;
......
......@@ -93,8 +93,8 @@ export type ComponentType =
| 'SelectOption'
| 'TreeSelect'
| 'Transfer'
| 'Radio'
| 'RadioButton'
// | 'Radio'
| 'RadioButtonGroup'
| 'RadioGroup'
| 'Checkbox'
| 'CheckboxGroup'
......
......@@ -87,7 +87,7 @@ export interface GetColumnsParams {
export type SizeType = 'default' | 'middle' | 'small' | 'large';
export interface TableActionType {
reload: (opt?: FetchParams) => Promise<void>;
// reload: (opt?: FetchParams) => Promise<void>;
getSelectRows: () => any[];
clearSelectedRowKeys: () => void;
getSelectRowKeys: () => string[];
......
......@@ -38,10 +38,10 @@ const menu: MenuModule = {
path: 'strength-meter',
name: '密码强度组件',
},
{
path: 'upload',
name: '上传组件',
},
// {
// path: 'upload',
// name: '上传组件',
// },
{
path: 'scroll',
name: '滚动组件',
......
......@@ -4,10 +4,18 @@ const menu: MenuModule = {
menu: {
path: '/form',
name: 'Form',
tag: {
type: 'warn',
dot: true,
},
children: [
{
path: 'basic',
name: '基础表单',
tag: {
type: 'warn',
content: 'updated',
},
},
{
path: 'useForm',
......@@ -24,14 +32,26 @@ const menu: MenuModule = {
{
path: 'ruleForm',
name: '表单校验',
tag: {
type: 'warn',
content: 'updated',
},
},
{
path: 'dynamicForm',
name: '动态表单',
tag: {
type: 'warn',
content: 'updated',
},
},
{
path: 'customerForm',
name: '自定义组件',
tag: {
type: 'warn',
content: 'updated',
},
},
],
},
......
<template>
<div class="m-4">
<CollapseContainer title="自定义表单">
<BasicForm @register="register" @submit="handleSubmit" />
<BasicForm @register="register" @submit="handleSubmit">
<template #f3="{ model, field }">
<a-input v-model:value="model[field]" placeholder="自定义slot" />
</template>
</BasicForm>
</CollapseContainer>
</div>
</template>
......@@ -15,7 +19,7 @@
{
field: 'field1',
component: 'Input',
label: '字段1',
label: 'render方式',
colProps: {
span: 8,
},
......@@ -33,7 +37,7 @@
{
field: 'field2',
component: 'Input',
label: '字段2',
label: 'render组件slot',
colProps: {
span: 8,
},
......@@ -44,6 +48,16 @@
};
},
},
{
field: 'field3',
component: 'Input',
label: '自定义Slot',
slot: 'f3',
colProps: {
span: 8,
},
rules: [{ required: true }],
},
];
export default defineComponent({
components: { BasicForm, CollapseContainer },
......
......@@ -9,6 +9,10 @@
<CollapseContainer title="动态表单示例,动态根据表单内其他值改变">
<BasicForm @register="register" />
</CollapseContainer>
<CollapseContainer class="mt-5" title="componentProps动态改变">
<BasicForm @register="register1" />
</CollapseContainer>
</div>
</template>
<script lang="ts">
......@@ -120,6 +124,58 @@
},
];
const schemas1: FormSchema[] = [
{
field: 'f1',
component: 'Input',
label: 'F1',
colProps: {
span: 12,
},
labelWidth: 200,
componentProps: ({ formModel }) => {
return {
placeholder: '同步f2的值为f1',
onChange: (e: ChangeEvent) => {
formModel.f2 = e.target.value;
},
};
},
},
{
field: 'f2',
component: 'Input',
label: 'F2',
colProps: {
span: 12,
},
labelWidth: 200,
componentProps: { disabled: true },
},
{
field: 'f3',
component: 'Input',
label: 'F3',
colProps: {
span: 12,
},
labelWidth: 200,
// @ts-ignore
componentProps: ({ formActionType, tableAction }) => {
return {
placeholder: '值改变时执行查询,查看控制台',
onChange: async () => {
const { validate } = formActionType;
// tableAction只适用于在表格内开启表单的例子
// const { reload } = tableAction;
const res = await validate();
console.log(res);
},
};
},
},
];
export default defineComponent({
components: { BasicForm, CollapseContainer },
setup() {
......@@ -133,6 +189,13 @@
span: 24,
},
});
const [register1] = useForm({
labelWidth: 120,
schemas: schemas1,
actionColOptions: {
span: 24,
},
});
function changeLabel3() {
updateSchema({
field: 'field3',
......@@ -170,6 +233,7 @@
}
return {
register,
register1,
schemas,
setProps,
changeLabel3,
......
......@@ -65,7 +65,33 @@
},
],
},
rules: [{ required: true, message: '请输入aa' }],
rules: [
{
required: true,
message: '请输入aa',
},
],
},
{
field: 'field44',
component: 'Input',
label: '自定义校验',
colProps: {
span: 8,
},
rules: [
{
required: true,
// @ts-ignore
validator: async (rule, value) => {
if (value === '1') {
return Promise.reject('值不能为1');
}
return Promise.resolve();
},
trigger: 'blur',
},
],
},
{
field: 'field5',
......
......@@ -11,10 +11,11 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent, ref } from 'vue';
import { BasicForm, FormSchema } from '/@/components/Form/index';
import { CollapseContainer } from '/@/components/Container/index';
import { useMessage } from '/@/hooks/web/useMessage';
const schemas: FormSchema[] = [
{
field: 'field1',
......@@ -23,8 +24,11 @@
colProps: {
span: 8,
},
defaultValue: '111',
componentProps: () => {
// componentProps:{},
// can func
componentProps: ({ schema, formModel }) => {
console.log('form:', schema);
console.log('formModel:', formModel);
return {
placeholder: '自定义placeholder',
onChange: (e: any) => {
......@@ -32,14 +36,26 @@
},
};
},
renderComponentContent: () => {
return {
prefix: () => 'pSlot',
suffix: () => 'sSlot',
};
},
},
{
field: 'field2',
component: 'Input',
label: '字段2',
defaultValue: '111',
colProps: {
span: 8,
},
componentProps: {
onChange: (e: any) => {
console.log(e);
},
},
},
{
field: 'field3',
......@@ -111,17 +127,100 @@
],
},
},
{
field: 'field8',
component: 'Checkbox',
label: '字段8',
colProps: {
span: 8,
},
renderComponentContent: 'Check',
},
{
field: 'field9',
component: 'Switch',
label: '字段9',
colProps: {
span: 8,
},
},
{
field: 'field10',
component: 'RadioButtonGroup',
label: '字段10',
colProps: {
span: 8,
},
componentProps: {
options: [
{
label: '选项1',
value: '1',
},
{
label: '选项2',
value: '2',
},
],
},
},
{
field: 'field11',
component: 'Cascader',
label: '字段11',
colProps: {
span: 8,
},
componentProps: {
options: [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
],
},
},
];
export default defineComponent({
components: { BasicForm, CollapseContainer },
setup() {
const check = ref(null);
const { createMessage } = useMessage();
return {
schemas,
handleSubmit: (values: any) => {
createMessage.success('click search,values:' + JSON.stringify(values));
},
check,
};
},
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册