提交 84c9d78f 编写于 作者: V vben

refactor(form): code optimization and reconstruction

上级 e2333642
## Wip ## Wip
### ✨ Features
- 表单组件现在支持直接传入 model 直接进行 set 操作,参考**组件->弹窗扩展->打开弹窗并传递数据**
- modal 的 useModalInner 现在支持传入回调函数,用于接收外部`transferModalData`传进来的值,
- 用于处理打开弹窗对表单等组件的设置值。参考**组件->弹窗扩展->打开弹窗并传递数据**
- `receiveModalDataRef`这个值暂时保留。尽量少用。后续可能会删除。
### ✨ Refactor
- 表单代码优化重构
### 🎫 Chores ### 🎫 Chores
- 添加部分注释 - 添加部分注释
...@@ -10,6 +22,7 @@ ...@@ -10,6 +22,7 @@
- 修复本地代理 post 接口到 https 地址超时错误 - 修复本地代理 post 接口到 https 地址超时错误
- 修复 modal 在不显示 footer 的时候全屏高度计算问题 - 修复 modal 在不显示 footer 的时候全屏高度计算问题
- 修复表单重置未删除校验信息错误
## 2.0.0-rc.6 (2020-10-28) ## 2.0.0-rc.6 (2020-10-28)
......
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
<script lang="ts"> <script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form'; import type { FormActionType, FormProps, FormSchema } from './types/form';
import type { Form as FormType, ValidateFields } from 'ant-design-vue/types/form/form'; import type { Form as FormType, ValidateFields } from 'ant-design-vue/types/form/form';
import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue';
import { import {
defineComponent, defineComponent,
...@@ -32,27 +34,22 @@ ...@@ -32,27 +34,22 @@
ref, ref,
computed, computed,
unref, unref,
toRaw,
watch,
toRef, toRef,
onMounted, onMounted,
watchEffect,
} from 'vue'; } from 'vue';
import { Form, Row } from 'ant-design-vue'; import { Form, Row } from 'ant-design-vue';
import FormItem from './FormItem'; import FormItem from './FormItem';
import { basicProps } from './props'; import { basicProps } from './props';
import { deepMerge, unique } from '/@/utils'; import { deepMerge } from '/@/utils';
import FormAction from './FormAction'; import FormAction from './FormAction';
import { dateItemType } from './helper'; import { dateItemType } from './helper';
import moment from 'moment'; import moment from 'moment';
import { isArray, isBoolean, isFunction, isNumber, isObject, isString } from '/@/utils/is';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
// import { useThrottle } from '/@/hooks/core/useThrottle';
import { useFormValues } from './hooks/useFormValues'; import { useFormValues } from './hooks/useFormValues';
import type { ColEx } from './types'; import useAdvanced from './hooks/useAdvanced';
import { NamePath } from 'ant-design-vue/types/form/form-item'; import { useFormAction } from './hooks/useFormAction';
const BASIC_COL_LEN = 24;
export default defineComponent({ export default defineComponent({
name: 'BasicForm', name: 'BasicForm',
...@@ -61,13 +58,20 @@ ...@@ -61,13 +58,20 @@
props: basicProps, props: basicProps,
emits: ['advanced-change', 'reset', 'submit', 'register'], emits: ['advanced-change', 'reset', 'submit', 'register'],
setup(props, { emit }) { setup(props, { emit }) {
let formModel = reactive({}); const formModel = reactive({});
const advanceState = reactive({
const actionState = reactive({
resetAction: {},
submitAction: {},
});
const advanceState = reactive<AdvanceState>({
isAdvanced: true, isAdvanced: true,
hideAdvanceBtn: false, hideAdvanceBtn: false,
isLoad: false, isLoad: false,
actionSpan: 6, actionSpan: 6,
}); });
const defaultValueRef = ref<any>({}); const defaultValueRef = ref<any>({});
const propsRef = ref<Partial<FormProps>>({}); const propsRef = ref<Partial<FormProps>>({});
const schemaRef = ref<FormSchema[] | null>(null); const schemaRef = ref<FormSchema[] | null>(null);
...@@ -78,50 +82,24 @@ ...@@ -78,50 +82,24 @@
return deepMerge(cloneDeep(props), unref(propsRef)); return deepMerge(cloneDeep(props), unref(propsRef));
} }
); );
// 获取表单基本配置 // 获取表单基本配置
const getProps = computed( const getProps = computed(
(): FormProps => { (): FormProps => {
const resetAction = {
onClick: resetFields,
};
const submitAction = {
onClick: handleSubmit,
};
return { return {
...unref(getMergePropsRef), ...unref(getMergePropsRef),
resetButtonOptions: deepMerge( resetButtonOptions: deepMerge(
resetAction, actionState.resetAction,
unref(getMergePropsRef).resetButtonOptions || {} unref(getMergePropsRef).resetButtonOptions || {}
) as any, ),
submitButtonOptions: deepMerge( submitButtonOptions: deepMerge(
submitAction, actionState.submitAction,
unref(getMergePropsRef).submitButtonOptions || {} unref(getMergePropsRef).submitButtonOptions || {}
) as any, ),
}; };
} }
); );
const getActionPropsRef = computed(() => {
const {
resetButtonOptions,
submitButtonOptions,
showActionButtonGroup,
showResetButton,
showSubmitButton,
showAdvancedButton,
actionColOptions,
} = unref(getProps);
return {
resetButtonOptions,
submitButtonOptions,
show: showActionButtonGroup,
showResetButton,
showSubmitButton,
showAdvancedButton,
actionColOptions,
};
});
const getSchema = computed((): FormSchema[] => { const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any); const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
for (const schema of schemas) { for (const schema of schemas) {
...@@ -133,305 +111,51 @@ ...@@ -133,305 +111,51 @@
return schemas as FormSchema[]; return schemas as FormSchema[];
}); });
const getEmptySpanRef = computed((): number => { const { getActionPropsRef, handleToggleAdvanced } = useAdvanced({
if (!advanceState.isAdvanced) { advanceState,
return 0; emit,
} getMergePropsRef,
const emptySpan = unref(getMergePropsRef).emptySpan || 0; getProps,
getSchema,
if (isNumber(emptySpan)) { formModel,
return emptySpan; defaultValueRef,
}
if (isObject(emptySpan)) {
const { span = 0 } = emptySpan;
const screen = unref(screenRef) as string;
const screenSpan = (emptySpan as any)[screen.toLowerCase()];
return screenSpan || span || 0;
}
return 0;
}); });
const { realWidthRef, screenEnum, screenRef } = useBreakpoint(); const { handleFormValues, initDefault } = useFormValues({
// const [throttleUpdateAdvanced] = useThrottle(updateAdvanced, 30, { immediate: true }); transformDateFuncRef: toRef(props, 'transformDateFunc') as Ref<Fn<any>>,
watch( fieldMapToTimeRef: toRef(props, 'fieldMapToTime'),
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)], defaultValueRef,
() => { getSchema,
const { showAdvancedButton } = unref(getProps); formModel,
if (showAdvancedButton) { });
updateAdvanced();
}
},
{ immediate: true }
);
function initDefault() {
const schemas = unref(getSchema);
const obj: any = {};
schemas.forEach((item) => {
if (item.defaultValue) {
obj[item.field] = item.defaultValue;
(formModel as any)[item.field] = item.defaultValue;
}
});
defaultValueRef.value = obj;
}
function updateAdvanced() {
let itemColSum = 0;
let realItemColSum = 0;
for (const schema of unref(getSchema)) {
const { show, colProps } = schema;
let isShow = true;
if (isBoolean(show)) {
isShow = show;
}
if (isFunction(show)) {
isShow = show({
schema: schema,
model: formModel,
field: schema.field,
values: {
...unref(defaultValueRef),
...formModel,
},
});
}
if (isShow && colProps) {
const { itemColSum: sum, isAdvanced } = getAdvanced(colProps, itemColSum);
itemColSum = sum || 0;
if (isAdvanced) {
realItemColSum = itemColSum;
}
schema.isAdvanced = isAdvanced;
}
}
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpanRef);
getAdvanced(
unref(getActionPropsRef).actionColOptions || { span: BASIC_COL_LEN },
itemColSum,
true
);
emit('advanced-change');
}
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
const width = unref(realWidthRef);
const mdWidth =
parseInt(itemCol.md as string) ||
parseInt(itemCol.xs as string) ||
parseInt(itemCol.sm as string) ||
(itemCol.span as number) ||
BASIC_COL_LEN;
const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
if (width <= screenEnum.LG) {
itemColSum += mdWidth;
} else if (width < screenEnum.XL) {
itemColSum += lgWidth;
} else if (width < screenEnum.XXL) {
itemColSum += xlWidth;
} else {
itemColSum += xxlWidth;
}
if (isLastAction) {
advanceState.hideAdvanceBtn = false;
if (itemColSum <= BASIC_COL_LEN * 2) {
// 小于等于2行时,不显示收起展开按钮
advanceState.hideAdvanceBtn = true;
advanceState.isAdvanced = true;
} else if (
itemColSum > BASIC_COL_LEN * 2 &&
itemColSum <= BASIC_COL_LEN * (props.autoAdvancedLine || 3)
) {
advanceState.hideAdvanceBtn = false;
// 大于3行默认收起
} else if (!advanceState.isLoad) {
advanceState.isLoad = true;
advanceState.isAdvanced = !advanceState.isAdvanced;
}
return { isAdvanced: advanceState.isAdvanced, itemColSum };
}
if (itemColSum > BASIC_COL_LEN) {
return { isAdvanced: advanceState.isAdvanced, itemColSum };
} else {
// 第一行始终显示
return { isAdvanced: true, itemColSum };
}
}
async function resetFields(): Promise<any> {
const { resetFunc, submitOnReset } = unref(getProps);
resetFunc && isFunction(resetFunc) && (await resetFunc());
const formEl = unref(formElRef);
if (!formEl) return;
Object.keys(formModel).forEach((key) => {
(formModel as any)[key] = defaultValueRef.value[key];
});
// const values = formEl.resetFields();
emit('reset', toRaw(formModel));
// return values;
submitOnReset && handleSubmit();
}
/**
* @description: 设置表单值
*/
async function setFieldsValue(values: any): Promise<void> {
const fields = unref(getSchema)
.map((item) => item.field)
.filter(Boolean);
const formEl = unref(formElRef);
Object.keys(values).forEach((key) => {
const element = values[key];
if (fields.includes(key) && element !== undefined && element !== null) {
// 时间
if (itemIsDateType(key)) {
if (Array.isArray(element)) {
const arr: any[] = [];
for (const ele of element) {
arr.push(moment(ele));
}
(formModel as any)[key] = arr;
} else {
(formModel as any)[key] = moment(element);
}
} else {
(formModel as any)[key] = element;
}
if (formEl) {
formEl.validateFields([key]);
}
}
});
}
/**
* @description: 表单提交
*/
async function handleSubmit(e?: Event): Promise<void> {
e && e.preventDefault();
const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) {
await submitFunc();
return;
}
const formEl = unref(formElRef);
if (!formEl) return;
try {
const values = await formEl.validate();
const res = handleFormValues(values);
emit('submit', res);
} catch (error) {}
}
/**
* @description: 根据字段名删除
*/
function removeSchemaByFiled(fields: string | string[]): void {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
if (!fields) {
return;
}
let fieldList: string[] = fields as string[];
if (isString(fields)) {
fieldList = [fields];
}
for (const field of fieldList) {
_removeSchemaByFiled(field, schemaList);
}
schemaRef.value = schemaList as any;
}
/**
* @description: 根据字段名删除
*/
function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void {
if (isString(field)) {
const index = schemaList.findIndex((schema) => schema.field === field);
if (index !== -1) {
schemaList.splice(index, 1);
}
}
}
/**
* @description: 往某个字段后面插入,如果没有插入最后一个
*/
function appendSchemaByField(schema: FormSchema, prefixField?: string) {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
const index = schemaList.findIndex((schema) => schema.field === prefixField);
const hasInList = schemaList.find((item) => item.field === schema.field);
if (hasInList) {
return;
}
if (!prefixField || index === -1) {
schemaList.push(schema);
schemaRef.value = schemaList as any;
return;
}
if (index !== -1) {
schemaList.splice(index + 1, 0, schema);
}
schemaRef.value = schemaList as any;
}
function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) {
updateData.push(data as FormSchema);
}
if (isArray(data)) {
updateData = [...data];
}
const hasField = updateData.every((item) => Reflect.has(item, 'field') && item.field);
if (!hasField) {
throw new Error('Must pass in the `field` field!');
}
const schema: FormSchema[] = [];
updateData.forEach((item) => {
unref(getSchema).forEach((val) => {
if (val.field === item.field) {
const newScheam = deepMerge(val, item);
schema.push(newScheam as FormSchema);
} else {
schema.push(val);
}
});
});
schemaRef.value = unique(schema, 'field') as any;
}
function handleToggleAdvanced() {
advanceState.isAdvanced = !advanceState.isAdvanced;
}
const handleFormValues = useFormValues(
toRef(props, 'transformDateFunc'),
toRef(props, 'fieldMapToTime')
);
function getFieldsValue(): any { const {
const formEl = unref(formElRef); // handleSubmit,
if (!formEl) return; setFieldsValue,
return handleFormValues(toRaw(unref(formModel))); clearValidate,
} validate,
validateFields,
getFieldsValue,
updateSchema,
appendSchemaByField,
removeSchemaByFiled,
resetFields,
} = useFormAction({
emit,
getProps,
formModel,
getSchema,
defaultValueRef,
formElRef: formElRef as any,
schemaRef: schemaRef as any,
handleFormValues,
actionState,
});
/** watchEffect(() => {
* @description: 是否是时间 if (!unref(getMergePropsRef).model) return;
*/ setFieldsValue(unref(getMergePropsRef).model);
function itemIsDateType(key: string) { });
return unref(getSchema).some((item) => {
return item.field === key ? dateItemType.includes(item.component!) : false;
});
}
/** /**
* @description:设置表单 * @description:设置表单
...@@ -441,21 +165,6 @@ ...@@ -441,21 +165,6 @@
propsRef.value = mergeProps; propsRef.value = mergeProps;
} }
function validateFields(nameList?: NamePath[] | undefined) {
if (!formElRef.value) return;
return formElRef.value.validateFields(nameList);
}
function validate(nameList?: NamePath[] | undefined) {
if (!formElRef.value) return;
return formElRef.value.validate(nameList);
}
function clearValidate(name: string | string[]) {
if (!formElRef.value) return;
formElRef.value.clearValidate(name);
}
const methods: Partial<FormActionType> = { const methods: Partial<FormActionType> = {
getFieldsValue, getFieldsValue,
setFieldsValue, setFieldsValue,
......
...@@ -57,6 +57,7 @@ export default defineComponent({ ...@@ -57,6 +57,7 @@ export default defineComponent({
...props.resetButtonOptions, ...props.resetButtonOptions,
}; };
}); });
const getSubmitBtnOptionsRef = computed(() => { const getSubmitBtnOptionsRef = computed(() => {
return { return {
text: '查询', text: '查询',
...@@ -80,10 +81,12 @@ export default defineComponent({ ...@@ -80,10 +81,12 @@ export default defineComponent({
function toggleAdvanced() { function toggleAdvanced() {
emit('toggle-advanced'); emit('toggle-advanced');
} }
return () => { return () => {
if (!props.show) { if (!props.show) {
return; return;
} }
const { const {
showAdvancedButton, showAdvancedButton,
hideAdvanceBtn, hideAdvanceBtn,
...@@ -91,50 +94,49 @@ export default defineComponent({ ...@@ -91,50 +94,49 @@ export default defineComponent({
showResetButton, showResetButton,
showSubmitButton, showSubmitButton,
} = props; } = props;
return ( return (
<> <Col {...unref(actionColOpt)} style={{ textAlign: 'right' }}>
<Col {...unref(actionColOpt)} style={{ textAlign: 'right' }}> {() => (
{() => ( <Form.Item>
<Form.Item> {() => (
{() => ( <>
<> {getSlot(slots, 'advanceBefore')}
{getSlot(slots, 'advanceBefore')} {showAdvancedButton && !hideAdvanceBtn && (
{showAdvancedButton && !hideAdvanceBtn && ( <Button type="default" class="mr-2" onClick={toggleAdvanced}>
<Button type="default" class="mr-2" onClick={toggleAdvanced}> {() => (
{() => ( <>
<> {isAdvanced ? '收起' : '展开'}
{isAdvanced ? '收起' : '展开'} {isAdvanced ? (
{isAdvanced ? ( <UpOutlined class="advanced-icon" />
<UpOutlined class="advanced-icon" /> ) : (
) : ( <DownOutlined class="advanced-icon" />
<DownOutlined class="advanced-icon" /> )}
)} </>
</> )}
)} </Button>
</Button> )}
)}
{getSlot(slots, 'resetBefore')} {getSlot(slots, 'resetBefore')}
{showResetButton && ( {showResetButton && (
<Button type="default" class="mr-2" {...unref(getResetBtnOptionsRef)}> <Button type="default" class="mr-2" {...unref(getResetBtnOptionsRef)}>
{() => unref(getResetBtnOptionsRef).text} {() => unref(getResetBtnOptionsRef).text}
</Button> </Button>
)} )}
{getSlot(slots, 'submitBefore')} {getSlot(slots, 'submitBefore')}
{showSubmitButton && ( {showSubmitButton && (
<Button type="primary" {...unref(getSubmitBtnOptionsRef)}> <Button type="primary" {...unref(getSubmitBtnOptionsRef)}>
{() => unref(getSubmitBtnOptionsRef).text} {() => unref(getSubmitBtnOptionsRef).text}
</Button> </Button>
)} )}
{getSlot(slots, 'submitAfter')} {getSlot(slots, 'submitAfter')}
</> </>
)} )}
</Form.Item> </Form.Item>
)} )}
</Col> </Col>
</>
); );
}; };
}, },
......
import type { ValidationRule } from 'ant-design-vue/types/form/form';
import type { PropType } from 'vue';
import type { FormProps } from './types/form';
import type { FormSchema } from './types/form';
import { defineComponent, computed, unref, toRef } from 'vue'; import { defineComponent, computed, unref, toRef } from 'vue';
import { Form, Col } from 'ant-design-vue'; import { Form, Col } from 'ant-design-vue';
import { componentMap } from './componentMap'; import { componentMap } from './componentMap';
import { BasicHelp } from '/@/components/Basic';
import type { PropType } from 'vue';
import type { FormProps } from './types/form';
import type { FormSchema } from './types/form';
import { isBoolean, isFunction } from '/@/utils/is'; import { isBoolean, isFunction } from '/@/utils/is';
import { useItemLabelWidth } from './hooks/useLabelWidth';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { BasicHelp } from '/@/components/Basic';
import { createPlaceholderMessage } from './helper'; import { createPlaceholderMessage } from './helper';
import { upperFirst, cloneDeep } from 'lodash-es'; import { upperFirst, cloneDeep } from 'lodash-es';
import { ValidationRule } from 'ant-design-vue/types/form/form';
import { useItemLabelWidth } from './hooks/useLabelWidth';
export default defineComponent({ export default defineComponent({
name: 'BasicFormItem', name: 'BasicFormItem',
inheritAttrs: false, inheritAttrs: false,
...@@ -50,6 +53,7 @@ export default defineComponent({ ...@@ -50,6 +53,7 @@ export default defineComponent({
schema: schema, schema: schema,
}; };
}); });
const getShowRef = computed(() => { const getShowRef = computed(() => {
const { show, ifShow, isAdvanced } = props.schema; const { show, ifShow, isAdvanced } = props.schema;
const { showAdvancedButton } = props.formProps; const { showAdvancedButton } = props.formProps;
...@@ -226,6 +230,7 @@ export default defineComponent({ ...@@ -226,6 +230,7 @@ export default defineComponent({
</span> </span>
); );
} }
function renderItem() { function renderItem() {
const { itemProps, slot, render, field } = props.schema; const { itemProps, slot, render, field } = props.schema;
const { labelCol, wrapperCol } = unref(itemLabelWidthRef); const { labelCol, wrapperCol } = unref(itemLabelWidthRef);
...@@ -255,11 +260,8 @@ export default defineComponent({ ...@@ -255,11 +260,8 @@ export default defineComponent({
const { colProps = {}, colSlot, renderColContent, component } = props.schema; const { colProps = {}, colSlot, renderColContent, component } = props.schema;
if (!componentMap.has(component)) return null; if (!componentMap.has(component)) return null;
const { baseColProps = {} } = props.formProps; const { baseColProps = {} } = props.formProps;
const realColProps = { ...baseColProps, ...colProps }; const realColProps = { ...baseColProps, ...colProps };
const { isIfShow, isShow } = unref(getShowRef); const { isIfShow, isShow } = unref(getShowRef);
const getContent = () => { const getContent = () => {
return colSlot return colSlot
? getSlot(slots, colSlot) ? getSlot(slots, colSlot)
......
import { ComponentType } from './types/index'; import type { ComponentType } from './types/index';
/** /**
* @description: 生成placeholder * @description: 生成placeholder
*/ */
...@@ -21,9 +22,11 @@ export function createPlaceholderMessage(component: ComponentType) { ...@@ -21,9 +22,11 @@ export function createPlaceholderMessage(component: ComponentType) {
} }
return ''; return '';
} }
function genType() { function genType() {
return ['DatePicker', 'MonthPicker', 'RangePicker', 'WeekPicker', 'TimePicker']; return ['DatePicker', 'MonthPicker', 'RangePicker', 'WeekPicker', 'TimePicker'];
} }
/** /**
* 时间字段 * 时间字段
*/ */
......
import type { ColEx } from '../types';
import type { AdvanceState } from '../types/hooks';
import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import { computed, unref, watch } from 'vue';
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
const BASIC_COL_LEN = 24;
interface UseAdvancedContext {
advanceState: AdvanceState;
emit: EmitType;
getMergePropsRef: ComputedRef<FormProps>;
getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]>;
formModel: any;
defaultValueRef: Ref<any>;
}
export default function ({
advanceState,
emit,
getMergePropsRef,
getProps,
getSchema,
formModel,
defaultValueRef,
}: UseAdvancedContext) {
const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
const getEmptySpanRef = computed((): number => {
if (!advanceState.isAdvanced) {
return 0;
}
const emptySpan = unref(getMergePropsRef).emptySpan || 0;
if (isNumber(emptySpan)) {
return emptySpan;
}
if (isObject(emptySpan)) {
const { span = 0 } = emptySpan;
const screen = unref(screenRef) as string;
const screenSpan = (emptySpan as any)[screen.toLowerCase()];
return screenSpan || span || 0;
}
return 0;
});
const getActionPropsRef = computed(() => {
const {
resetButtonOptions,
submitButtonOptions,
showActionButtonGroup,
showResetButton,
showSubmitButton,
showAdvancedButton,
actionColOptions,
} = unref(getProps);
return {
resetButtonOptions,
submitButtonOptions,
show: showActionButtonGroup,
showResetButton,
showSubmitButton,
showAdvancedButton,
actionColOptions,
};
});
watch(
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
() => {
const { showAdvancedButton } = unref(getProps);
if (showAdvancedButton) {
updateAdvanced();
}
},
{ immediate: true }
);
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
const width = unref(realWidthRef);
const mdWidth =
parseInt(itemCol.md as string) ||
parseInt(itemCol.xs as string) ||
parseInt(itemCol.sm as string) ||
(itemCol.span as number) ||
BASIC_COL_LEN;
const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
if (width <= screenEnum.LG) {
itemColSum += mdWidth;
} else if (width < screenEnum.XL) {
itemColSum += lgWidth;
} else if (width < screenEnum.XXL) {
itemColSum += xlWidth;
} else {
itemColSum += xxlWidth;
}
if (isLastAction) {
advanceState.hideAdvanceBtn = false;
if (itemColSum <= BASIC_COL_LEN * 2) {
// 小于等于2行时,不显示收起展开按钮
advanceState.hideAdvanceBtn = true;
advanceState.isAdvanced = true;
} else if (
itemColSum > BASIC_COL_LEN * 2 &&
itemColSum <= BASIC_COL_LEN * (unref(getMergePropsRef).autoAdvancedLine || 3)
) {
advanceState.hideAdvanceBtn = false;
// 大于3行默认收起
} else if (!advanceState.isLoad) {
advanceState.isLoad = true;
advanceState.isAdvanced = !advanceState.isAdvanced;
}
return { isAdvanced: advanceState.isAdvanced, itemColSum };
}
if (itemColSum > BASIC_COL_LEN) {
return { isAdvanced: advanceState.isAdvanced, itemColSum };
} else {
// 第一行始终显示
return { isAdvanced: true, itemColSum };
}
}
function updateAdvanced() {
let itemColSum = 0;
let realItemColSum = 0;
for (const schema of unref(getSchema)) {
const { show, colProps } = schema;
let isShow = true;
if (isBoolean(show)) {
isShow = show;
}
if (isFunction(show)) {
isShow = show({
schema: schema,
model: formModel,
field: schema.field,
values: {
...unref(defaultValueRef),
...formModel,
},
});
}
if (isShow && colProps) {
const { itemColSum: sum, isAdvanced } = getAdvanced(colProps, itemColSum);
itemColSum = sum || 0;
if (isAdvanced) {
realItemColSum = itemColSum;
}
schema.isAdvanced = isAdvanced;
}
}
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpanRef);
getAdvanced(
unref(getActionPropsRef).actionColOptions || { span: BASIC_COL_LEN },
itemColSum,
true
);
emit('advanced-change');
}
function handleToggleAdvanced() {
advanceState.isAdvanced = !advanceState.isAdvanced;
}
return { getActionPropsRef, handleToggleAdvanced };
}
import type { ComponentType } from '../types/index';
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
import { add, del } from '../componentMap'; import { add, del } from '../componentMap';
import { ComponentType } from '../types/index';
export function useComponentRegister(compName: ComponentType, comp: any) { export function useComponentRegister(compName: ComponentType, comp: any) {
add(compName, comp); add(compName, comp);
tryOnUnmounted(() => { tryOnUnmounted(() => {
......
import { ref, onUnmounted, unref } from 'vue'; import { ref, onUnmounted, unref } from 'vue';
import { isInSetup } from '/@/utils/helper/vueHelper'; import { isInSetup } from '/@/utils/helper/vueHelper';
import { isProdMode } from '/@/utils/env';
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'; import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
import { isProdMode } from '/@/utils/env';
import type { NamePath } from 'ant-design-vue/types/form/form-item'; import type { NamePath } from 'ant-design-vue/types/form/form-item';
import type { ValidateFields } from 'ant-design-vue/types/form/form'; import type { ValidateFields } from 'ant-design-vue/types/form/form';
...@@ -11,6 +11,7 @@ export function useForm(props?: Partial<FormProps>): UseFormReturnType { ...@@ -11,6 +11,7 @@ export function useForm(props?: Partial<FormProps>): UseFormReturnType {
isInSetup(); isInSetup();
const formRef = ref<FormActionType | null>(null); const formRef = ref<FormActionType | null>(null);
const loadedRef = ref<boolean | null>(false); const loadedRef = ref<boolean | null>(false);
function getForm() { function getForm() {
const form = unref(formRef); const form = unref(formRef);
if (!form) { if (!form) {
......
import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema } from '../types/form';
import type { Form as FormType } from 'ant-design-vue/types/form/form';
import type { NamePath } from 'ant-design-vue/types/form/form-item';
import { unref, toRaw } from 'vue';
import { isArray, isFunction, isObject, isString } from '/@/utils/is';
import { deepMerge, unique } from '/@/utils';
import { dateItemType } from '../helper';
import moment from 'moment';
import { cloneDeep } from 'lodash-es';
interface UseFormActionContext {
emit: EmitType;
getProps: ComputedRef<FormProps>;
getSchema: ComputedRef<FormSchema[]>;
formModel: any;
defaultValueRef: Ref<any>;
formElRef: Ref<FormType>;
schemaRef: Ref<FormSchema[]>;
handleFormValues: Fn;
actionState: {
resetAction: any;
submitAction: any;
};
}
export function useFormAction({
emit,
getProps,
formModel,
getSchema,
defaultValueRef,
formElRef,
schemaRef,
handleFormValues,
actionState,
}: UseFormActionContext) {
async function resetFields(): Promise<any> {
const { resetFunc, submitOnReset } = unref(getProps);
resetFunc && isFunction(resetFunc) && (await resetFunc());
const formEl = unref(formElRef);
if (!formEl) return;
Object.keys(formModel).forEach((key) => {
(formModel as any)[key] = defaultValueRef.value[key];
});
// @ts-ignore
// TODO 官方组件库类型定义错误,可以不传参数
formEl.clearValidate();
emit('reset', toRaw(formModel));
// return values;
submitOnReset && handleSubmit();
}
/**
* @description: 设置表单值
*/
async function setFieldsValue(values: any): Promise<void> {
const fields = unref(getSchema)
.map((item) => item.field)
.filter(Boolean);
const formEl = unref(formElRef);
Object.keys(values).forEach((key) => {
const element = values[key];
if (fields.includes(key) && element !== undefined && element !== null) {
// 时间
if (itemIsDateType(key)) {
if (Array.isArray(element)) {
const arr: any[] = [];
for (const ele of element) {
arr.push(moment(ele));
}
(formModel as any)[key] = arr;
} else {
(formModel as any)[key] = moment(element);
}
} else {
(formModel as any)[key] = element;
}
if (formEl) {
formEl.validateFields([key]);
}
}
});
}
/**
* @description: 根据字段名删除
*/
function removeSchemaByFiled(fields: string | string[]): void {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
if (!fields) {
return;
}
let fieldList: string[] = fields as string[];
if (isString(fields)) {
fieldList = [fields];
}
for (const field of fieldList) {
_removeSchemaByFiled(field, schemaList);
}
schemaRef.value = schemaList as any;
}
/**
* @description: 根据字段名删除
*/
function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void {
if (isString(field)) {
const index = schemaList.findIndex((schema) => schema.field === field);
if (index !== -1) {
schemaList.splice(index, 1);
}
}
}
/**
* @description: 往某个字段后面插入,如果没有插入最后一个
*/
function appendSchemaByField(schema: FormSchema, prefixField?: string) {
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
const index = schemaList.findIndex((schema) => schema.field === prefixField);
const hasInList = schemaList.find((item) => item.field === schema.field);
if (hasInList) {
return;
}
if (!prefixField || index === -1) {
schemaList.push(schema);
schemaRef.value = schemaList as any;
return;
}
if (index !== -1) {
schemaList.splice(index + 1, 0, schema);
}
schemaRef.value = schemaList as any;
}
function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) {
updateData.push(data as FormSchema);
}
if (isArray(data)) {
updateData = [...data];
}
const hasField = updateData.every((item) => Reflect.has(item, 'field') && item.field);
if (!hasField) {
throw new Error('Must pass in the `field` field!');
}
const schema: FormSchema[] = [];
updateData.forEach((item) => {
unref(getSchema).forEach((val) => {
if (val.field === item.field) {
const newScheam = deepMerge(val, item);
schema.push(newScheam as FormSchema);
} else {
schema.push(val);
}
});
});
schemaRef.value = unique(schema, 'field') as any;
}
function getFieldsValue(): any {
const formEl = unref(formElRef);
if (!formEl) return;
return handleFormValues(toRaw(unref(formModel)));
}
/**
* @description: 是否是时间
*/
function itemIsDateType(key: string) {
return unref(getSchema).some((item) => {
return item.field === key ? dateItemType.includes(item.component!) : false;
});
}
function validateFields(nameList?: NamePath[] | undefined) {
if (!formElRef.value) return;
return formElRef.value.validateFields(nameList);
}
function validate(nameList?: NamePath[] | undefined) {
if (!formElRef.value) return;
return formElRef.value.validate(nameList);
}
function clearValidate(name: string | string[]) {
if (!formElRef.value) return;
formElRef.value.clearValidate(name);
}
/**
* @description: 表单提交
*/
async function handleSubmit(e?: Event): Promise<void> {
e && e.preventDefault();
const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) {
await submitFunc();
return;
}
const formEl = unref(formElRef);
if (!formEl) return;
try {
const values = await formEl.validate();
const res = handleFormValues(values);
emit('submit', res);
} catch (error) {}
}
actionState.resetAction = {
onClick: resetFields,
};
actionState.submitAction = {
onClick: handleSubmit,
};
return {
handleSubmit,
clearValidate,
validate,
validateFields,
getFieldsValue,
updateSchema,
appendSchemaByField,
removeSchemaByFiled,
resetFields,
setFieldsValue,
};
}
import { isArray, isFunction, isObject, isString } from '/@/utils/is'; import { isArray, isFunction, isObject, isString } from '/@/utils/is';
import moment from 'moment'; import moment from 'moment';
import { unref } from 'vue'; import { unref } from 'vue';
import type { Ref } from 'vue'; import type { Ref, ComputedRef } from 'vue';
import type { FieldMapToTime } from '../types/form'; import type { FieldMapToTime, FormSchema } from '../types/form';
export function useFormValues( interface UseFormValuesContext {
transformDateFuncRef: Ref<Fn>, transformDateFuncRef: Ref<Fn>;
fieldMapToTimeRef: Ref<FieldMapToTime> fieldMapToTimeRef: Ref<FieldMapToTime>;
) { defaultValueRef: Ref<any>;
getSchema: ComputedRef<FormSchema[]>;
formModel: any;
}
export function useFormValues({
transformDateFuncRef,
fieldMapToTimeRef,
defaultValueRef,
getSchema,
formModel,
}: UseFormValuesContext) {
// 处理表单值 // 处理表单值
function handleFormValues(values: any) { function handleFormValues(values: any) {
if (!isObject(values)) { if (!isObject(values)) {
...@@ -35,6 +45,7 @@ export function useFormValues( ...@@ -35,6 +45,7 @@ export function useFormValues(
} }
return handleRangeTimeValue(resMap); return handleRangeTimeValue(resMap);
} }
/** /**
* @description: 处理时间区间参数 * @description: 处理时间区间参数
*/ */
...@@ -58,5 +69,18 @@ export function useFormValues( ...@@ -58,5 +69,18 @@ export function useFormValues(
return values; return values;
} }
return handleFormValues;
function initDefault() {
const schemas = unref(getSchema);
const obj: any = {};
schemas.forEach((item) => {
if (item.defaultValue) {
obj[item.field] = item.defaultValue;
(formModel as any)[item.field] = item.defaultValue;
}
});
defaultValueRef.value = obj;
}
return { handleFormValues, initDefault };
} }
...@@ -3,6 +3,10 @@ import type { PropType } from 'vue'; ...@@ -3,6 +3,10 @@ import type { PropType } from 'vue';
import type { ColEx } from './types'; import type { ColEx } from './types';
export const basicProps = { export const basicProps = {
model: {
type: Object as PropType<any>,
default: {},
},
// 标签宽度 固定宽度 // 标签宽度 固定宽度
labelWidth: { labelWidth: {
type: [Number, String] as PropType<number | string>, type: [Number, String] as PropType<number | string>,
......
...@@ -35,6 +35,8 @@ export type RegisterFn = (formInstance: FormActionType) => void; ...@@ -35,6 +35,8 @@ export type RegisterFn = (formInstance: FormActionType) => void;
export type UseFormReturnType = [RegisterFn, FormActionType]; export type UseFormReturnType = [RegisterFn, FormActionType];
export interface FormProps { export interface FormProps {
// 表单值
model?: any;
// 整个表单所有项宽度 // 整个表单所有项宽度
labelWidth?: number | string; labelWidth?: number | string;
// 重置时提交 // 重置时提交
......
export interface AdvanceState {
isAdvanced: boolean;
hideAdvanceBtn: boolean;
isLoad: boolean;
actionSpan: number;
}
...@@ -15,13 +15,12 @@ import { ...@@ -15,13 +15,12 @@ import {
import { Spin } from 'ant-design-vue'; import { Spin } from 'ant-design-vue';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
// import { useTimeout } from '/@/hooks/core/useTimeout'; import { useTimeout } from '/@/hooks/core/useTimeout';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { useElResize } from '/@/hooks/event/useElResize'; import { useElResize } from '/@/hooks/event/useElResize';
export default defineComponent({ export default defineComponent({
name: 'ModalWrapper', name: 'ModalWrapper',
emits: ['heightChange', 'getExtHeight'],
props: { props: {
loading: { loading: {
type: Boolean as PropType<boolean>, type: Boolean as PropType<boolean>,
...@@ -52,6 +51,7 @@ export default defineComponent({ ...@@ -52,6 +51,7 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
emits: ['heightChange', 'getExtHeight'],
setup(props: ModalWrapperProps, { slots, emit }) { setup(props: ModalWrapperProps, { slots, emit }) {
const wrapperRef = ref<HTMLElement | null>(null); const wrapperRef = ref<HTMLElement | null>(null);
const spinRef = ref<any>(null); const spinRef = ref<any>(null);
...@@ -66,7 +66,7 @@ export default defineComponent({ ...@@ -66,7 +66,7 @@ export default defineComponent({
}); });
// 重试次数 // 重试次数
// let tryCount = 0; let tryCount = 0;
let stopElResizeFn: Fn = () => {}; let stopElResizeFn: Fn = () => {};
watchEffect(() => { watchEffect(() => {
...@@ -123,17 +123,17 @@ export default defineComponent({ ...@@ -123,17 +123,17 @@ export default defineComponent({
} }
await nextTick(); await nextTick();
const spinEl = unref(spinRef); const spinEl = unref(spinRef);
// if (!spinEl) { if (!spinEl) {
// useTimeout(() => { useTimeout(() => {
// // retry // retry
// if (tryCount < 3) { if (tryCount < 3) {
// setModalHeight(); setModalHeight();
// } }
// tryCount++; tryCount++;
// }, 10); }, 10);
// return; return;
// } }
// tryCount = 0; tryCount = 0;
const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement; const spinContainerEl = spinEl.$el.querySelector('.ant-spin-container') as HTMLElement;
if (!spinContainerEl) return; if (!spinContainerEl) return;
...@@ -142,7 +142,7 @@ export default defineComponent({ ...@@ -142,7 +142,7 @@ export default defineComponent({
if (props.fullScreen) { if (props.fullScreen) {
realHeightRef.value = realHeightRef.value =
window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 26; window.innerHeight - props.modalFooterHeight - props.modalHeaderHeight - 6;
} else { } else {
realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30; realHeightRef.value = realHeight > maxHeight ? maxHeight : realHeight + 16 + 30;
} }
......
...@@ -5,8 +5,9 @@ import type { ...@@ -5,8 +5,9 @@ import type {
ReturnMethods, ReturnMethods,
UseModalInnerReturnType, UseModalInnerReturnType,
} from './types'; } from './types';
import { ref, onUnmounted, unref, getCurrentInstance, reactive, computed } from 'vue'; import { ref, onUnmounted, unref, getCurrentInstance, reactive, computed, watchEffect } from 'vue';
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is';
const dataTransferRef = reactive<any>({}); const dataTransferRef = reactive<any>({});
/** /**
...@@ -58,7 +59,7 @@ export function useModal(): UseModalReturnType { ...@@ -58,7 +59,7 @@ export function useModal(): UseModalReturnType {
return [register, methods]; return [register, methods];
} }
export const useModalInner = (): UseModalInnerReturnType => { export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
const modalInstanceRef = ref<ModalMethods | null>(null); const modalInstanceRef = ref<ModalMethods | null>(null);
const currentInstall = getCurrentInstance(); const currentInstall = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
...@@ -81,6 +82,13 @@ export const useModalInner = (): UseModalInnerReturnType => { ...@@ -81,6 +82,13 @@ export const useModalInner = (): UseModalInnerReturnType => {
currentInstall.emit('register', modalInstance); currentInstall.emit('register', modalInstance);
}; };
watchEffect(() => {
const data = dataTransferRef[unref(uidRef)];
if (!data) return;
if (!callbackFn || !isFunction(callbackFn)) return;
callbackFn(data);
});
return [ return [
register, register,
{ {
......
<template> <template>
<BasicModal v-bind="$attrs" @register="register" title="Modal Title"> <BasicModal v-bind="$attrs" @register="register" title="Modal Title">
<p class="h-20">外部传递数据: {{ receiveModalDataRef }}</p> <p class="h-20">外部传递数据: {{ receiveModalDataRef }}</p>
<BasicForm @register="registerForm" :model="model" />
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, nextTick, ref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
const schemas: FormSchema[] = [
{
field: 'field1',
component: 'Input',
label: '字段1',
colProps: {
span: 12,
},
defaultValue: '111',
},
{
field: 'field2',
component: 'Input',
label: '字段2',
colProps: {
span: 12,
},
},
];
export default defineComponent({ export default defineComponent({
components: { BasicModal }, components: { BasicModal, BasicForm },
setup() { setup() {
const [register, { receiveModalDataRef }] = useModalInner(); const modelRef = ref({});
return { register, receiveModalDataRef }; const [
registerForm,
{
// setFieldsValue,
// setProps
},
] = useForm({
labelWidth: 120,
schemas,
showActionButtonGroup: false,
actionColOptions: {
span: 24,
},
});
const [register, { receiveModalDataRef }] = useModalInner((data) => {
nextTick(() => {
// 方式1
// setFieldsValue({
// field2: data.data,
// field1: data.info,
// });
// 方式2
modelRef.value = { field2: data.data, field1: data.info };
// setProps({
// model:{ field2: data.data, field1: data.info }
// })
});
});
return { register, receiveModalDataRef, schemas, registerForm, model: modelRef };
}, },
}); });
</script> </script>
...@@ -48,6 +48,12 @@ ...@@ -48,6 +48,12 @@
data: 'content', data: 'content',
info: 'Info', info: 'Info',
}); });
// setTimeout(() => {
// transferModalData({
// data: 'content1',
// info: 'Info1',
// });
// }, 3000);
openModal4(true); openModal4(true);
} }
function openModalLoading() { function openModalLoading() {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册