初始版本提交
This commit is contained in:
94
src/components/SimpleForm/componentMap.ts
Normal file
94
src/components/SimpleForm/componentMap.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import type { Component } from 'vue';
|
||||
import type { ComponentType } from './types/index';
|
||||
|
||||
/**
|
||||
* Component list, register here to setting it in the form
|
||||
*/
|
||||
import {
|
||||
Input,
|
||||
Select,
|
||||
Radio,
|
||||
Checkbox,
|
||||
AutoComplete,
|
||||
Cascader,
|
||||
DatePicker,
|
||||
InputNumber,
|
||||
Switch,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Slider,
|
||||
Rate,
|
||||
Divider,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import ApiRadioGroup from './components/ApiRadioGroup.vue';
|
||||
import RadioButtonGroup from './components/RadioButtonGroup.vue';
|
||||
import ApiSelect from './components/ApiSelect.vue';
|
||||
import ApiTree from './components/ApiTree.vue';
|
||||
import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
||||
import ApiCascader from './components/ApiCascader.vue';
|
||||
import SelectDepartment from './components/SelectDepartment.vue';
|
||||
import SelectUser from './components/SelectUser.vue';
|
||||
import CommonInfo from './components/CommonInfo.vue';
|
||||
import SelectArea from './components/SelectArea.vue';
|
||||
// import SelectArea from './components/SelectArea.vue';
|
||||
|
||||
import { BasicUpload } from '/@/components/Upload';
|
||||
import { StrengthMeter } from '/@/components/StrengthMeter';
|
||||
import { IconPicker } from '/@/components/Icon';
|
||||
import { CountdownInput } from '/@/components/CountDown';
|
||||
import { ChildTable } from '/@/components/ChildTable';
|
||||
import { Text } from '/@/components/Text';
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('Input', Input);
|
||||
componentMap.set('InputGroup', Input.Group);
|
||||
componentMap.set('InputPassword', Input.Password);
|
||||
componentMap.set('InputSearch', Input.Search);
|
||||
componentMap.set('InputTextArea', Input.TextArea);
|
||||
componentMap.set('InputNumber', InputNumber);
|
||||
componentMap.set('AutoComplete', AutoComplete);
|
||||
|
||||
componentMap.set('Select', Select);
|
||||
componentMap.set('ApiSelect', ApiSelect);
|
||||
componentMap.set('ApiTree', ApiTree);
|
||||
componentMap.set('TreeSelect', TreeSelect);
|
||||
componentMap.set('ApiTreeSelect', ApiTreeSelect);
|
||||
componentMap.set('ApiRadioGroup', ApiRadioGroup);
|
||||
componentMap.set('Switch', Switch);
|
||||
componentMap.set('RadioButtonGroup', RadioButtonGroup);
|
||||
componentMap.set('RadioGroup', Radio.Group);
|
||||
componentMap.set('Checkbox', Checkbox);
|
||||
componentMap.set('CheckboxGroup', Checkbox.Group);
|
||||
componentMap.set('ApiCascader', ApiCascader);
|
||||
componentMap.set('Cascader', Cascader);
|
||||
componentMap.set('Slider', Slider);
|
||||
componentMap.set('Rate', Rate);
|
||||
componentMap.set('Dept', SelectDepartment);
|
||||
componentMap.set('User', SelectUser);
|
||||
componentMap.set('Info', CommonInfo);
|
||||
componentMap.set('Area', SelectArea);
|
||||
|
||||
componentMap.set('DatePicker', DatePicker);
|
||||
componentMap.set('MonthPicker', DatePicker.MonthPicker);
|
||||
componentMap.set('RangePicker', DatePicker.RangePicker);
|
||||
componentMap.set('WeekPicker', DatePicker.WeekPicker);
|
||||
componentMap.set('TimePicker', TimePicker);
|
||||
componentMap.set('StrengthMeter', StrengthMeter);
|
||||
componentMap.set('IconPicker', IconPicker);
|
||||
componentMap.set('InputCountDown', CountdownInput);
|
||||
componentMap.set('ChildTable', ChildTable);
|
||||
componentMap.set('Upload', BasicUpload);
|
||||
componentMap.set('Divider', Divider);
|
||||
componentMap.set('Text', Text);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap };
|
||||
0
src/components/SimpleForm/hooks/useForm.ts
Normal file
0
src/components/SimpleForm/hooks/useForm.ts
Normal file
0
src/components/SimpleForm/index.ts
Normal file
0
src/components/SimpleForm/index.ts
Normal file
758
src/components/SimpleForm/src/SimpleForm.vue
Normal file
758
src/components/SimpleForm/src/SimpleForm.vue
Normal file
@ -0,0 +1,758 @@
|
||||
<template>
|
||||
<div>
|
||||
<Form
|
||||
ref="formRef"
|
||||
:model="formModel"
|
||||
:layout="getProps?.layout"
|
||||
:label-col="getProps?.labelCol"
|
||||
:wrapper-col="getProps?.wrapperCol"
|
||||
:labelAlign="getProps?.labelAlign"
|
||||
@keypress.enter="handleEnterPress"
|
||||
>
|
||||
<Row v-bind="getRow">
|
||||
<template v-for="schema in getSchemas" :key="schema.field">
|
||||
<Col
|
||||
:span="schema.colProps?.span"
|
||||
v-show="getIsShow(schema, formModel[schema.field])"
|
||||
v-if="getIfShow(schema, formModel[schema.field])"
|
||||
>
|
||||
<template v-if="showComponent(schema)">
|
||||
<SimpleFormItem
|
||||
:refreshFieldObj="refreshFieldObj"
|
||||
:schema="schema"
|
||||
:form-api="formApi"
|
||||
:isWorkFlow="isWorkFlow"
|
||||
v-model:value="formModel[schema.field]"
|
||||
/>
|
||||
</template>
|
||||
</Col>
|
||||
</template>
|
||||
</Row>
|
||||
|
||||
<div :style="{ textAlign: getProps.buttonLocation }">
|
||||
<slot name="buttonBefore"></slot>
|
||||
|
||||
<a-button type="primary" v-if="getProps.showSubmitButton" @click="handleSubmit">
|
||||
{{ t('提交') }}
|
||||
</a-button>
|
||||
<a-button style="margin-left: 10px" v-if="getProps.showResetButton" @click="handleReset">
|
||||
{{ t('重置') }}
|
||||
</a-button>
|
||||
<slot name="buttonAfter"></slot>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Form, FormInstance, Row, Col, Modal, message } from 'ant-design-vue';
|
||||
import {
|
||||
cloneDeep,
|
||||
isArray,
|
||||
isBoolean,
|
||||
isFunction,
|
||||
isObject,
|
||||
isString,
|
||||
uniqBy,
|
||||
isNil,
|
||||
} from 'lodash-es';
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
provide,
|
||||
unref,
|
||||
nextTick,
|
||||
toRaw,
|
||||
createVNode,
|
||||
inject,
|
||||
onMounted,
|
||||
} from 'vue';
|
||||
import {
|
||||
CardComponentProps,
|
||||
FormActionType,
|
||||
FormProps,
|
||||
FormSchema,
|
||||
GridComponentProps,
|
||||
TabComponentProps,
|
||||
regTestProps,
|
||||
requestProps,
|
||||
} from '../../Form/src/types/form';
|
||||
import SimpleFormItem from './components/SimpleFormItem.vue';
|
||||
import { NamePath, ValidateOptions } from 'ant-design-vue/lib/form/interface';
|
||||
import {
|
||||
arrayValueComponents,
|
||||
defaultValueComponents,
|
||||
staticDataComponents,
|
||||
noFieldComponent,
|
||||
noDefaultValueComponents,
|
||||
noShowWorkFlowComponents,
|
||||
noShowGenerateComponents,
|
||||
} from '../../Form/src/helper';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { ModalFuncProps } from 'ant-design-vue/lib/modal/Modal';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { needDicDefaultValue } from '../../Designer/src/types';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { camelCaseString } from '/@/utils/event/design';
|
||||
const { t } = useI18n();
|
||||
const { notification } = useMessage();
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const propsRef = ref<Partial<FormProps>>({});
|
||||
|
||||
const schemaRef = ref<Nullable<FormSchema[]>>(null);
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
|
||||
const props = defineProps({
|
||||
// 表单配置规则
|
||||
formProps: {
|
||||
type: Object as PropType<FormProps>,
|
||||
},
|
||||
//表单数据
|
||||
formModel: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => {},
|
||||
},
|
||||
//是否是工作流
|
||||
isWorkFlow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//是否系统表单需要转驼峰,兼容旧系统表单
|
||||
isCamelCase: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const getSchemas = computed<FormSchema[]>(() => {
|
||||
return (unref(getProps).schemas as any) || unref(schemaRef);
|
||||
});
|
||||
|
||||
// Get the basic configuration of the form
|
||||
const getProps = computed((): FormProps => {
|
||||
return { ...(props.formProps as FormProps), ...unref(propsRef) } as FormProps;
|
||||
});
|
||||
|
||||
// Get uniform row style and Row configuration for the entire form
|
||||
const getRow = computed((): Recordable => {
|
||||
const { baseRowStyle = {}, rowProps } = props.formProps!;
|
||||
|
||||
return {
|
||||
style: baseRowStyle,
|
||||
...rowProps,
|
||||
};
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
//添加隐藏组件
|
||||
if (unref(getProps)?.hiddenComponent?.length) {
|
||||
unref(getProps)?.hiddenComponent?.forEach((component) => {
|
||||
formModel[component.bindField] = component.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function showComponent(schema) {
|
||||
return props.isWorkFlow
|
||||
? !noShowWorkFlowComponents.includes(schema.type)
|
||||
: !noShowGenerateComponents.includes(schema.type);
|
||||
}
|
||||
|
||||
function getIsShow(schema: FormSchema, itemValue: any): boolean {
|
||||
const { show } = schema;
|
||||
const { showAdvancedButton } = getProps.value;
|
||||
const itemIsAdvanced = showAdvancedButton
|
||||
? isBoolean(schema.isAdvanced)
|
||||
? schema.isAdvanced
|
||||
: true
|
||||
: true;
|
||||
|
||||
let isShow = true;
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show;
|
||||
}
|
||||
if (isFunction(show)) {
|
||||
isShow = show({
|
||||
values: itemValue,
|
||||
model: formModel!,
|
||||
schema: schema,
|
||||
field: schema.field,
|
||||
});
|
||||
}
|
||||
|
||||
isShow = isShow && itemIsAdvanced;
|
||||
return isShow;
|
||||
}
|
||||
|
||||
function getIfShow(schema: FormSchema, itemValue: any): boolean {
|
||||
const { ifShow } = schema;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow({
|
||||
values: itemValue,
|
||||
model: formModel!,
|
||||
schema: schema,
|
||||
field: schema.field,
|
||||
});
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const formModel = reactive<Recordable>(props.formModel);
|
||||
const isCustom = inject<boolean>('isCustomForm', false);
|
||||
provide('formModel', formModel);
|
||||
provide('formProps', getProps);
|
||||
provide('isCustomForm', isCustom || props.isWorkFlow);
|
||||
provide('isCamelCase', props.isCamelCase);
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const { submitFunc } = unref(getProps);
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc();
|
||||
return;
|
||||
}
|
||||
const res = await formRef.value!.validate();
|
||||
emit('submit', res);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
resetFields();
|
||||
};
|
||||
|
||||
const refreshFieldObj = ref<object>({});
|
||||
|
||||
getComponent(getSchemas.value);
|
||||
|
||||
function getComponent(component) {
|
||||
const layoutComponents = ['tab', 'grid', 'card'];
|
||||
component?.map((info) => {
|
||||
if (layoutComponents.includes(info.type!)) {
|
||||
info.children?.map((childInfo) => {
|
||||
childInfo.list.map((com) => {
|
||||
if (layoutComponents.includes(com.type) || com.type === 'table-layout') {
|
||||
getComponent(childInfo.list);
|
||||
} else {
|
||||
setComponentDefault(com);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (info.type === 'table-layout') {
|
||||
info.children?.map((childInfo) => {
|
||||
childInfo.list.map((com) => {
|
||||
com.children.map((item) => {
|
||||
if (layoutComponents.includes(item.type) || item.type === 'table-layout') {
|
||||
getComponent(com.children);
|
||||
} else {
|
||||
setComponentDefault(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setComponentDefault(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setComponentDefault(item) {
|
||||
if (
|
||||
(staticDataComponents.includes(item.component) &&
|
||||
(item.componentProps as any)?.datasourceType === 'staticData') ||
|
||||
(needDicDefaultValue.includes(item.type) &&
|
||||
(item.componentProps as any)?.datasourceType === 'dic')
|
||||
) {
|
||||
let { defaultSelect } = item.componentProps as any;
|
||||
formModel[item.field] = defaultSelect;
|
||||
return;
|
||||
}
|
||||
let { defaultValue } = item;
|
||||
if (!!item.field && !noDefaultValueComponents.includes(item.component)) {
|
||||
if (item.component === 'OneForOne') {
|
||||
if (item.componentProps.childSchemas?.length) {
|
||||
const singleFormObj = {};
|
||||
item.componentProps.childSchemas.map((singleCom) => {
|
||||
if (
|
||||
(staticDataComponents.includes(singleCom.component) &&
|
||||
(singleCom.componentProps as any)?.datasourceType === 'staticData') ||
|
||||
(needDicDefaultValue.includes(singleCom.type) &&
|
||||
(singleCom.componentProps as any)?.datasourceType === 'dic')
|
||||
) {
|
||||
let { defaultSelect } = singleCom.componentProps as any;
|
||||
singleFormObj[singleCom.field] = defaultSelect;
|
||||
} else {
|
||||
singleFormObj[singleCom.field] = singleCom.defaultValue;
|
||||
}
|
||||
});
|
||||
formModel[item.field] = [singleFormObj];
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
formModel[item.field] = item.component === 'SubForm' ? [] : defaultValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回车提交表单
|
||||
*/
|
||||
function handleEnterPress(e: KeyboardEvent) {
|
||||
const { autoSubmitOnEnter } = unref(getProps);
|
||||
if (!autoSubmitOnEnter) return;
|
||||
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
|
||||
const target: HTMLElement = e.target as HTMLElement;
|
||||
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
|
||||
handleSubmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//调用表单验证
|
||||
const validate = async (nameList?: NamePath[]): Promise<any> => formRef.value?.validate(nameList);
|
||||
|
||||
//清除表单验证
|
||||
const clearValidate = async (name?: string | string[]): Promise<any> =>
|
||||
formRef.value?.clearValidate(name);
|
||||
|
||||
//跳到某个字段
|
||||
const scrollToField = async (name: NamePath, options?: ScrollOptions): Promise<any> =>
|
||||
formRef.value?.scrollToField(name, options);
|
||||
|
||||
//验证某个字段
|
||||
const validateFields = async (
|
||||
nameList?: NamePath[] | string,
|
||||
options?: ValidateOptions,
|
||||
): Promise<any> => formRef.value?.validateFields(nameList, options);
|
||||
|
||||
const findSchema = (schemaArr, key) => {
|
||||
let schema;
|
||||
const formListComponent = ['tab', 'grid', 'card'];
|
||||
schemaArr?.some((info) => {
|
||||
if (formListComponent.includes(info.type!)) {
|
||||
const hasComponent = info.children.some((childInfo) => {
|
||||
schema = childInfo.list.find((com) => com.field === key);
|
||||
if (!!schema) return true;
|
||||
schema = findSchema(childInfo.list, key);
|
||||
return !!schema;
|
||||
});
|
||||
return !!hasComponent;
|
||||
} else if (info.type === 'table-layout') {
|
||||
const hasComponent = info.children.some((childInfo) => {
|
||||
return childInfo.list.some((chil) => {
|
||||
schema = chil.children.find((com) => com.field === key);
|
||||
if (!!schema) return true;
|
||||
schema = findSchema(chil.children, key);
|
||||
return !!schema;
|
||||
});
|
||||
});
|
||||
return !!hasComponent;
|
||||
} else {
|
||||
schema = info.field === key ? info : null;
|
||||
return !!schema;
|
||||
}
|
||||
});
|
||||
return schema;
|
||||
};
|
||||
|
||||
const setDefaultValue = async (): Promise<void> => {
|
||||
getComponent(getSchemas.value);
|
||||
};
|
||||
|
||||
// 重置表单所有数据
|
||||
const resetFields = async (): Promise<void> => {
|
||||
Object.keys(formModel).forEach((key) => {
|
||||
//没有绑定字段的组件不处理
|
||||
if (!key) return;
|
||||
const schema = findSchema(unref(getSchemas), key);
|
||||
|
||||
const isInput = schema?.component && defaultValueComponents.includes(schema.component);
|
||||
const isSubForm = schema?.component && arrayValueComponents.includes(schema.component);
|
||||
const isRange = schema?.component && schema?.component.includes('Range');
|
||||
const isStatic =
|
||||
schema?.component &&
|
||||
staticDataComponents.includes(schema.component) &&
|
||||
schema?.componentProps.datasourceType === 'staticData';
|
||||
const isDic =
|
||||
schema?.type &&
|
||||
needDicDefaultValue.includes(schema.type) &&
|
||||
schema?.componentProps.datasourceType === 'dic';
|
||||
|
||||
if (isSubForm) {
|
||||
if (schema.component === 'OneForOne') {
|
||||
if (schema.componentProps.childSchemas?.length) {
|
||||
const singleFormObj = {};
|
||||
schema.componentProps.childSchemas.map((singleCom) => {
|
||||
if (
|
||||
(staticDataComponents.includes(singleCom.component) &&
|
||||
singleCom?.componentProps.datasourceType === 'staticData') ||
|
||||
(needDicDefaultValue.includes(singleCom.type) &&
|
||||
singleCom?.componentProps.datasourceType === 'dic')
|
||||
) {
|
||||
singleFormObj[singleCom.field] = singleCom?.componentProps.defaultSelect;
|
||||
} else {
|
||||
singleFormObj[singleCom.field] = singleCom.defaultValue;
|
||||
}
|
||||
});
|
||||
formModel[key] = [singleFormObj];
|
||||
}
|
||||
} else {
|
||||
formModel[key] = [];
|
||||
}
|
||||
} else if (isInput) {
|
||||
formModel[key] = schema?.defaultValue || '';
|
||||
} else if (isRange) {
|
||||
const startTimeKey = key.split(',')[0];
|
||||
const endTimeKey = key.split(',')[1];
|
||||
formModel[key] = schema?.defaultValue || [];
|
||||
formModel[startTimeKey] =
|
||||
schema?.defaultValue && schema?.defaultValue.length > 0 && schema?.defaultValue[0];
|
||||
formModel[endTimeKey] =
|
||||
schema?.defaultValue && schema?.defaultValue.length > 0 && schema?.defaultValue[1];
|
||||
} else if (isStatic || isDic) {
|
||||
formModel[key] = schema?.componentProps.defaultSelect;
|
||||
} else {
|
||||
formModel[key] =
|
||||
schema?.defaultValue || schema?.defaultValue === 0 ? schema?.defaultValue : '';
|
||||
}
|
||||
});
|
||||
nextTick(() => clearValidate());
|
||||
};
|
||||
|
||||
// 更改formProps
|
||||
const setProps = async (formProps: Partial<FormProps>): Promise<void> => {
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
||||
};
|
||||
|
||||
//设定某个字段值 慎用 建议直接页面直接操作formModel数据
|
||||
const setFieldsValue = async (values: Recordable): Promise<void> => {
|
||||
Object.keys(values).forEach((key) => {
|
||||
if (!isNil(key)) {
|
||||
formModel[key] = values[key];
|
||||
}
|
||||
});
|
||||
|
||||
executeEvent(getSchemas.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行schema中 所有change事件
|
||||
* 修改bug #5090
|
||||
* 为了保证表单赋值触发所有组件的change事件
|
||||
*/
|
||||
const executeEvent = (allSchemas: FormSchema[]) => {
|
||||
for (const schema of allSchemas) {
|
||||
//如果是这几个组件 需要查询子级
|
||||
if (['Card', 'Tab', 'Grid'].includes(schema.component)) {
|
||||
if (schema.component === 'Tab') {
|
||||
for (const child of schema.children as TabComponentProps[]) {
|
||||
executeEvent(child.list);
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.component === 'Card') {
|
||||
for (const child of schema.children as CardComponentProps[]) {
|
||||
executeEvent(child.list);
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.component === 'Grid') {
|
||||
for (const child of schema.children as GridComponentProps[]) {
|
||||
executeEvent(child.list);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (schema.componentProps?.['events']) {
|
||||
for (const eventKey in schema.componentProps['events']) {
|
||||
if (eventKey !== 'change') return;
|
||||
try {
|
||||
const event = new Function(
|
||||
'schema',
|
||||
'formModel',
|
||||
'formActionType',
|
||||
`${schema.componentProps['events'][eventKey]}`,
|
||||
);
|
||||
event(schema, formModel, formApi);
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
notification.error({
|
||||
message: 'Tip',
|
||||
description: '触发事件填写有误!',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (componentProps['events']) {
|
||||
// for (const eventKey in componentProps['events']) {
|
||||
// try {
|
||||
// const event = new Function(
|
||||
// 'schema',
|
||||
// 'formModel',
|
||||
// 'formActionType',
|
||||
// `${componentProps['events'][eventKey]}`,
|
||||
// );
|
||||
// componentProps['on' + upperFirst(eventKey)] = function () {
|
||||
// event(props.schema, formModel, props.formApi);
|
||||
// };
|
||||
// } catch (error) {
|
||||
// console.log('error', error);
|
||||
// notification.error({
|
||||
// message: 'Tip',
|
||||
// description: '触发事件填写有误!',
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
//获取表单值 慎用 建议直接页面直接操作formModel数据
|
||||
const getFieldsValue = (): Recordable => {
|
||||
return toRaw(unref(formModel));
|
||||
};
|
||||
|
||||
//更新schema
|
||||
const updateSchema = async (data: Partial<FormSchema> | Partial<FormSchema>[]): Promise<void> => {
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data];
|
||||
}
|
||||
const hasField = updateData.every(
|
||||
(item) =>
|
||||
noFieldComponent.includes(item.component!) || (Reflect.has(item, 'field') && item.field),
|
||||
);
|
||||
if (!hasField) {
|
||||
throw new Error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field',
|
||||
);
|
||||
}
|
||||
const schema: FormSchema[] = [];
|
||||
updateData.forEach((item: FormSchema) => {
|
||||
unref(getSchemas).forEach((val: FormSchema) => {
|
||||
//如果当前组件是布局组件 需要去child 里面更新
|
||||
if (['Card', 'Tab', 'Grid'].includes(val.component)) {
|
||||
deepEachChild(val.children!, item);
|
||||
} else {
|
||||
if (val.key && item.key && val.key === item.key) {
|
||||
const newSchema = deepMerge(val, item);
|
||||
schema.push(newSchema as FormSchema);
|
||||
} else if (val.field === item.field) {
|
||||
const newSchema = deepMerge(val, item);
|
||||
schema.push(newSchema as FormSchema);
|
||||
} else {
|
||||
schema.push(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
schemaRef.value = uniqBy(schema, 'field');
|
||||
};
|
||||
const deepEachChild = (
|
||||
childs: GridComponentProps[] | TabComponentProps[] | CardComponentProps[],
|
||||
thisItem: FormSchema,
|
||||
) => {
|
||||
childs?.forEach((child: GridComponentProps | CardComponentProps | TabComponentProps) => {
|
||||
child.list.forEach((it: FormSchema) => {
|
||||
if (['Card', 'Tab', 'Grid'].includes(it.component)) {
|
||||
deepEachChild(it.children!, thisItem);
|
||||
}
|
||||
|
||||
if (thisItem.key && it.key && thisItem.key === it.key) {
|
||||
it = deepMerge(it, thisItem);
|
||||
} else if (thisItem.field === it.field) {
|
||||
it = deepMerge(it, thisItem);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//重置schema
|
||||
const resetSchema = async (data: Partial<FormSchema> | Partial<FormSchema>[]): Promise<void> => {
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data];
|
||||
}
|
||||
|
||||
const hasField = updateData.every(
|
||||
(item) =>
|
||||
noFieldComponent.includes(item.component!) || (Reflect.has(item, 'field') && item.field),
|
||||
);
|
||||
if (!hasField) {
|
||||
throw new Error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
schemaRef.value = updateData as FormSchema[];
|
||||
};
|
||||
//移除schema
|
||||
const removeSchemaByFiled = async (fields: string | string[]): Promise<void> => {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchemas));
|
||||
if (!fields) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fieldList: string[] = isString(fields) ? [fields] : fields;
|
||||
if (isString(fields)) {
|
||||
fieldList = [fields];
|
||||
}
|
||||
for (const field of fieldList) {
|
||||
if (isString(field)) {
|
||||
const index = schemaList.findIndex((schema) => schema.field === field);
|
||||
if (index !== -1) {
|
||||
delete formModel[field];
|
||||
schemaList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
schemaRef.value = schemaList;
|
||||
};
|
||||
//追加schema
|
||||
const appendSchemaByField = async (
|
||||
schema: FormSchema,
|
||||
prefixField: string | undefined,
|
||||
first?: boolean | undefined,
|
||||
): Promise<void> => {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchemas));
|
||||
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField);
|
||||
if (!prefixField || index === -1 || first) {
|
||||
first ? schemaList.unshift(schema) : schemaList.push(schema);
|
||||
schemaRef.value = schemaList;
|
||||
return;
|
||||
}
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index + 1, 0, schema);
|
||||
}
|
||||
|
||||
schemaRef.value = schemaList;
|
||||
};
|
||||
|
||||
// 提交
|
||||
const submit = async (e?: Event): Promise<void> => {
|
||||
e && e.preventDefault();
|
||||
const { submitFunc } = unref(getProps);
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc();
|
||||
return;
|
||||
}
|
||||
const formEl = unref(formRef);
|
||||
if (!formEl) return;
|
||||
try {
|
||||
const values = await validate();
|
||||
emit('submit', values);
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const showModal = async (modal: ModalFuncProps) => {
|
||||
Modal.confirm({
|
||||
title: modal.title,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: createVNode('div', { style: 'color:red;' }, modal.content),
|
||||
onOk: modal.onOk,
|
||||
onCancel: modal.onCancel,
|
||||
...modal,
|
||||
});
|
||||
};
|
||||
|
||||
const regTest = async (regular: regTestProps) => {
|
||||
const regExpression = regular.regExpression;
|
||||
const testRes = regExpression.test(regular.testValue);
|
||||
testRes ? message.success(regular.successMessage) : message.error(regular.errorMessage);
|
||||
};
|
||||
|
||||
const httpRequest = async (request: requestProps) => {
|
||||
if (request.requestType.toLowerCase() === 'get') {
|
||||
return defHttp[request.requestType](
|
||||
{
|
||||
url: request.requestUrl,
|
||||
params: request.params,
|
||||
},
|
||||
{
|
||||
errorMessageMode: request.errorMessageMode || 'none',
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return defHttp[request.requestType](
|
||||
{
|
||||
url: request.requestUrl,
|
||||
data: request.params,
|
||||
},
|
||||
{
|
||||
errorMessageMode: request.errorMessageMode || 'none',
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAPI = (field: string) => {
|
||||
if (!field) return;
|
||||
if (!Object.keys(unref(refreshFieldObj)).includes(field)) {
|
||||
unref(refreshFieldObj)[field] = 0;
|
||||
}
|
||||
unref(refreshFieldObj)[field]++;
|
||||
};
|
||||
|
||||
const changeStyle = (schema, style, field?) => {
|
||||
if (field) {
|
||||
const changeSchema = unref(getSchemas).filter((item) => {
|
||||
if (props.isCamelCase) {
|
||||
return camelCaseString(item.field) === field;
|
||||
} else {
|
||||
return item.field === field;
|
||||
}
|
||||
});
|
||||
schema = changeSchema[0];
|
||||
}
|
||||
schema.componentProps.style = { ...schema.componentProps.style, ...style };
|
||||
};
|
||||
|
||||
const formApi: FormActionType = {
|
||||
submit,
|
||||
validate,
|
||||
clearValidate,
|
||||
scrollToField,
|
||||
validateFields,
|
||||
resetFields,
|
||||
setProps,
|
||||
updateSchema,
|
||||
setFieldsValue,
|
||||
getFieldsValue,
|
||||
removeSchemaByFiled,
|
||||
appendSchemaByField,
|
||||
resetSchema,
|
||||
showModal,
|
||||
regTest,
|
||||
httpRequest,
|
||||
refreshAPI,
|
||||
changeStyle,
|
||||
setDefaultValue,
|
||||
};
|
||||
|
||||
//将表单方法 导出 给父组件使用。
|
||||
defineExpose<FormActionType>(formApi);
|
||||
</script>
|
||||
87
src/components/SimpleForm/src/components/SimpleFormGrid.vue
Normal file
87
src/components/SimpleForm/src/components/SimpleFormGrid.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div>
|
||||
<Col v-for="(col, colIndex) in children" :key="colIndex" :span="col.span">
|
||||
<template v-for="(schema, key) in col.list" :key="key">
|
||||
<template v-if="schema.component.includes('Range')">
|
||||
<SimpleFormItem
|
||||
:schema="schema"
|
||||
:value="
|
||||
convertToDayjs(
|
||||
formModel[spiltRangeDateField(schema.field)[0]],
|
||||
formModel[spiltRangeDateField(schema.field)[1]],
|
||||
)
|
||||
"
|
||||
:form-props="formProps"
|
||||
v-model:value="formModel[schema.field]"
|
||||
v-model:startTime="formModel[spiltRangeDateField(schema.field)[0]]"
|
||||
v-model:endTime="formModel[spiltRangeDateField(schema.field)[1]]"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<SimpleFormItem
|
||||
:schema="schema"
|
||||
:form-props="formProps"
|
||||
v-model:value="formModel[schema.field]"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Col>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Col } from 'ant-design-vue';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { trim } from 'lodash-es';
|
||||
import { reactive, watch } from 'vue';
|
||||
import SimpleFormItem from './SimpleFormItem.vue';
|
||||
import { FormSchema, GridComponentProps, FormProps } from '/@/components/Form';
|
||||
|
||||
const props = defineProps({
|
||||
// 表单配置规则
|
||||
config: {
|
||||
type: Object as PropType<FormSchema>,
|
||||
default: () => {},
|
||||
},
|
||||
//整个表单配置
|
||||
formProps: {
|
||||
type: Object as PropType<FormProps>,
|
||||
default: () => ({}),
|
||||
},
|
||||
//子组件
|
||||
children: {
|
||||
type: Array as PropType<GridComponentProps[]>,
|
||||
default: () => [],
|
||||
},
|
||||
//表单数据
|
||||
formModel: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const formModel = reactive<Recordable>(props.formModel);
|
||||
|
||||
/**
|
||||
* 当有range组件 schema的field格式为 startTime,endTime 默认逗号隔开 开始时间在前 结束时间在后
|
||||
*/
|
||||
const spiltRangeDateField = (field: string): string[] => {
|
||||
return field.split(',').map(trim);
|
||||
};
|
||||
|
||||
const convertToDayjs = (
|
||||
startTime: string,
|
||||
endTime: string,
|
||||
format = 'YYYY-MM-DD',
|
||||
): [Dayjs, Dayjs] => {
|
||||
return [dayjs(startTime, format), dayjs(endTime, format)];
|
||||
};
|
||||
|
||||
const emit = defineEmits(['update:formModel']);
|
||||
|
||||
watch(
|
||||
() => formModel,
|
||||
() => {
|
||||
emit('update:formModel', formModel);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
539
src/components/SimpleForm/src/components/SimpleFormItem.vue
Normal file
539
src/components/SimpleForm/src/components/SimpleFormItem.vue
Normal file
@ -0,0 +1,539 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--如果是grid 组件 需要新增grid布局包裹-->
|
||||
<template v-if="schema.component.includes('Grid')">
|
||||
<Row
|
||||
type="flex"
|
||||
:gutter="(schema.componentProps as any ).gutter ?? 0"
|
||||
:justify="(schema.componentProps as any ).justify"
|
||||
:align="(schema.componentProps as any ).align"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<Col v-for="(col, colIndex) in schema.children" :key="colIndex" :span="col.span">
|
||||
<template v-for="childSchema in col.list" :key="childSchema.field">
|
||||
<SimpleFormItem
|
||||
v-if="showComponent(childSchema)"
|
||||
:refreshFieldObj="refreshFieldObj"
|
||||
:form-api="formApi"
|
||||
:schema="childSchema"
|
||||
v-model:value="formModel![childSchema.field]"
|
||||
:label-col="labelCol"
|
||||
:isWorkFlow="isWorkFlow"
|
||||
/>
|
||||
</template>
|
||||
</Col>
|
||||
</Row>
|
||||
</template>
|
||||
<!--如果是tab 组件 需要新增tab组件包裹-->
|
||||
<template v-else-if="schema.component === 'Tab'">
|
||||
<Tabs
|
||||
v-model:activeKey="activeKey"
|
||||
:tabPosition="(schema.componentProps as any ).tabPosition"
|
||||
:type="(schema.componentProps as any ).type"
|
||||
:size="(schema.componentProps as any ).tabSize"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<TabPane
|
||||
v-for="(tab, tabIndex) in schema.children"
|
||||
:tab="tab.name"
|
||||
:forceRender="true"
|
||||
:key="tabIndex"
|
||||
>
|
||||
<template v-for="childSchema in tab.list" :key="childSchema.field">
|
||||
<SimpleFormItem
|
||||
v-if="showComponent(childSchema)"
|
||||
:refreshFieldObj="refreshFieldObj"
|
||||
:form-api="formApi"
|
||||
:schema="childSchema"
|
||||
:isWorkFlow="isWorkFlow"
|
||||
v-model:value="formModel![childSchema.field]"
|
||||
/>
|
||||
</template>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</template>
|
||||
<!--如果是子表单 组件 需要v-model:value="表单对象[字段名]"-->
|
||||
<template v-else-if="schema.component.includes('Form')">
|
||||
<FormItem
|
||||
:key="schema.key"
|
||||
:name="schema.field"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
:label-col="labelCol"
|
||||
:labelAlign="formProps?.labelAlign"
|
||||
:wrapperCol="itemLabelWidthProp.wrapperCol"
|
||||
v-if="getShow(schema)"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<component
|
||||
:disabled="getDisable"
|
||||
:is="formComponent(schema)"
|
||||
:size="formProps?.size"
|
||||
v-bind="schema.componentProps"
|
||||
v-model:value="formModel![schema.field]"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
<!--如果是子表单 组件 需要v-model:value="表单对象[字段名]"-->
|
||||
<template v-else-if="schema.component.includes('OneForOne')">
|
||||
<FormItem
|
||||
:key="schema.key"
|
||||
:name="schema.field"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
:label-col="labelCol"
|
||||
:labelAlign="formProps?.labelAlign"
|
||||
:wrapperCol="itemLabelWidthProp.wrapperCol"
|
||||
v-if="getShow(schema)"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<component
|
||||
:disabled="getDisable"
|
||||
:refreshFieldObj="refreshFieldObj"
|
||||
:is="componentMap.get(schema.component)"
|
||||
:size="formProps?.size"
|
||||
v-bind="schema.componentProps"
|
||||
v-model:value="formModel![schema.field]"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
<template v-else-if="schema.component === 'TableLayout'">
|
||||
<TableLayoutPreview :element="schema" v-if="getShow(schema)" v-show="getIsShow(schema)">
|
||||
<template #tdElement="{ tdElement }">
|
||||
<div class="h-full">
|
||||
<div
|
||||
:style="{
|
||||
height: tdElement.height ? tdElement.height + 'px' : '',
|
||||
minHeight: (tdElement.height || '42') + 'px',
|
||||
overflow: 'hidden',
|
||||
padding: '10px',
|
||||
}"
|
||||
>
|
||||
<template v-for="childSchema in tdElement.children" :key="childSchema.field">
|
||||
<SimpleFormItem
|
||||
v-if="showComponent(childSchema)"
|
||||
:refreshFieldObj="refreshFieldObj"
|
||||
:form-api="formApi"
|
||||
:schema="childSchema"
|
||||
:isWorkFlow="isWorkFlow"
|
||||
v-model:value="formModel![childSchema.field]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</TableLayoutPreview>
|
||||
</template>
|
||||
<!--如果是时间区间 组件 需要v-model:startField="表单对象[字段名]" v-model:endField="表单对象[字段名]"-->
|
||||
<template v-else-if="schema.component.includes('Range')">
|
||||
<FormItem
|
||||
:key="schema.key"
|
||||
:name="schema.field"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
:label-col="labelCol"
|
||||
:wrapperCol="itemLabelWidthProp.wrapperCol"
|
||||
:validateTrigger="['blur', 'change']"
|
||||
:rules="rules"
|
||||
v-if="getShow(schema)"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<component
|
||||
:disabled="getDisable"
|
||||
:is="componentMap.get(schema.component)"
|
||||
:size="formProps?.size"
|
||||
v-bind="schema.componentProps"
|
||||
v-model:startField="schema.field.split(',')[0]"
|
||||
v-model:endField="schema.field.split(',')[1]"
|
||||
v-model:value="formModel![schema.field]"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- 因为Range会变为 开始时间 和 结束时间 2个属性给与表单数据 所以需要2个隐藏框绑定 starTime 和 endTime -->
|
||||
<FormItem
|
||||
:key="schema.key"
|
||||
v-show="false"
|
||||
:name="schema.field.split(',')[0]"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
>
|
||||
<input type="hidden" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
:key="schema.key"
|
||||
v-show="false"
|
||||
:name="schema.field.split(',')[1]"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
>
|
||||
<input type="hidden" />
|
||||
</FormItem>
|
||||
</template>
|
||||
<!--如果checked 或者 switch组件 需要v-model:checked="表单对象[字段名]" "-->
|
||||
<template v-else-if="checkedValueComponents.includes(schema.component)">
|
||||
<FormItem
|
||||
:key="schema.key"
|
||||
:name="schema.field"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
:label-col="labelCol"
|
||||
:wrapperCol="itemLabelWidthProp.wrapperCol"
|
||||
:rules="rules"
|
||||
v-if="getShow(schema)"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<component
|
||||
:key="refreshFieldObj[schema.field]"
|
||||
:is="componentMap.get(schema.component)"
|
||||
:disabled="getDisable"
|
||||
:size="formProps?.size"
|
||||
v-bind="getComponentsProps"
|
||||
v-model:checked="formModel![schema.field]"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
<!--如果是card 组件 需要新增card组件包裹-->
|
||||
<template v-else-if="schema.component.includes('Card')">
|
||||
<CollapseContainer
|
||||
:title="(schema.componentProps as any ).title"
|
||||
:bordered="false"
|
||||
:hasLeftBorder="true"
|
||||
v-show="getIsShow(schema)"
|
||||
>
|
||||
<template v-for="childSchema in schema.children![0].list" :key="childSchema.field">
|
||||
<SimpleFormItem
|
||||
v-if="showComponent(childSchema)"
|
||||
:refreshFieldObj="refreshFieldObj"
|
||||
:form-api="formApi"
|
||||
:schema="childSchema"
|
||||
:isWorkFlow="isWorkFlow"
|
||||
v-model:value="formModel![childSchema.field]"
|
||||
/>
|
||||
</template>
|
||||
</CollapseContainer>
|
||||
</template>
|
||||
<!--如果是 分割线 组件 需要新增divider组件包裹-->
|
||||
<template v-else-if="schema.component.includes('Divider')">
|
||||
<FormItem v-show="getIsShow(schema)">
|
||||
<Divider v-bind="getComponentsProps"> {{ getComponentsProps.text }}</Divider>
|
||||
</FormItem>
|
||||
</template>
|
||||
<!--如果是 意见簿 组件 需要使用defaultValue直接赋值 -->
|
||||
<template v-else-if="schema.component.includes('Opinion')">
|
||||
<FormItem
|
||||
v-if="getShow(schema)"
|
||||
v-show="getIsShow(schema)"
|
||||
:key="schema.key"
|
||||
:name="schema.field"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
:label-col="labelCol"
|
||||
:wrapperCol="itemLabelWidthProp.wrapperCol"
|
||||
:rules="rules"
|
||||
:validateTrigger="['blur', 'change']"
|
||||
>
|
||||
<component
|
||||
:is="componentMap.get(schema.component)"
|
||||
:key="refreshFieldObj[schema.field]"
|
||||
:disabled="getDisable"
|
||||
:size="formProps?.size"
|
||||
v-bind="getComponentsProps"
|
||||
:value="schema.defaultValue"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
<template v-else>
|
||||
<FormItem
|
||||
v-if="getShow(schema)"
|
||||
v-show="getIsShow(schema)"
|
||||
:key="schema.key"
|
||||
:name="schema.field"
|
||||
:label="getComponentsProps.showLabel ? schema.label : ''"
|
||||
:label-col="labelCol"
|
||||
:wrapperCol="itemLabelWidthProp.wrapperCol"
|
||||
:rules="rules"
|
||||
:validateTrigger="['blur', 'change']"
|
||||
>
|
||||
<component
|
||||
:is="defaultComponent(schema)"
|
||||
:key="refreshFieldObj[schema.field]"
|
||||
:disabled="getDisable"
|
||||
:size="formProps?.size"
|
||||
v-bind="getComponentsProps"
|
||||
v-model:value="formModel![schema.field]"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Form, Col, Row, Tabs, TabPane, Divider } from 'ant-design-vue';
|
||||
import { isBoolean, isFunction, upperFirst, cloneDeep } from 'lodash-es';
|
||||
import { computed, onMounted, unref, inject, Ref, watch, ref } from 'vue';
|
||||
import { componentMap } from '/@/components/Form/src/componentMap';
|
||||
import { checkedValueComponents } from '/@/components/Form/src/helper';
|
||||
import { useItemLabelWidth } from '/@/components/Form/src/hooks/useLabelWidth';
|
||||
import { FormActionType, FormProps, FormSchema } from '/@/components/Form/src/types/form';
|
||||
import { CollapseContainer } from '/@/components/Container';
|
||||
import {
|
||||
noShowWorkFlowComponents,
|
||||
noShowGenerateComponents,
|
||||
} from '/@/components/Form/src/helper';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import TableLayoutPreview from '/@/components/Form/src/components/TableLayoutPreview.vue';
|
||||
import { camelCaseString } from '/@/utils/event/design';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const props = defineProps({
|
||||
// 表单配置规则
|
||||
schema: {
|
||||
type: Object as PropType<FormSchema>,
|
||||
default: () => {},
|
||||
},
|
||||
value: [Object, String, Number, Boolean, Array],
|
||||
formApi: {
|
||||
type: Object as PropType<FormActionType>,
|
||||
},
|
||||
//刷新api使用
|
||||
refreshFieldObj: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
//是否是工作流
|
||||
isWorkFlow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const formModel = inject<Recordable>('formModel');
|
||||
const formProps = inject<Ref<FormProps>>('formProps');
|
||||
const tabActiveKey = inject<Ref<number>>('tabActiveKey', ref(0));
|
||||
const activeKey = ref<number>(0);
|
||||
const isCamelCase = inject<boolean>('isCamelCase', false);
|
||||
watch(
|
||||
() => tabActiveKey?.value,
|
||||
(val) => {
|
||||
if (props.isWorkFlow) activeKey.value = val!;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => activeKey?.value,
|
||||
(val) => {
|
||||
if (props.isWorkFlow) tabActiveKey.value = val!;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => props.value,
|
||||
// (val) => {
|
||||
// if (!val) return;
|
||||
// let { componentProps = {} } = props.schema;
|
||||
// if (componentProps['events']) {
|
||||
// for (const eventKey in componentProps['events']) {
|
||||
// try {
|
||||
// const event = new Function(
|
||||
// 'schema',
|
||||
// 'formModel',
|
||||
// 'formActionType',
|
||||
// `${componentProps['events'][eventKey]}`,
|
||||
// );
|
||||
// event(props.schema, formModel, props.formApi);
|
||||
// } catch (error) {
|
||||
// notification.error({
|
||||
// message: 'Tip',
|
||||
// description: '触发事件填写有误!',
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// immediate: true,
|
||||
// },
|
||||
// );
|
||||
|
||||
const { notification } = useMessage();
|
||||
const getSchema = computed(() => {
|
||||
return props.schema as FormSchema;
|
||||
});
|
||||
|
||||
const getDisable = computed(() => {
|
||||
const { disabled: globDisabled } = formProps!.value;
|
||||
const { dynamicDisabled } = getSchema.value;
|
||||
const { disabled: itemDisabled = false } = unref(getComponentsProps);
|
||||
let disabled = !!globDisabled || itemDisabled;
|
||||
if (isBoolean(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled;
|
||||
}
|
||||
if (isFunction(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled({
|
||||
values: formModel![getSchema.value.field],
|
||||
model: formModel!,
|
||||
schema: unref(getSchema),
|
||||
field: unref(getSchema).field,
|
||||
});
|
||||
}
|
||||
return disabled;
|
||||
});
|
||||
|
||||
const getComponentsProps = computed(() => {
|
||||
let { componentProps = {} } = props.schema;
|
||||
|
||||
if (isFunction(componentProps)) {
|
||||
componentProps =
|
||||
componentProps({ schema: props.schema, formModel, formActionType: props.formApi }) ?? {};
|
||||
} else {
|
||||
if (componentProps['events']) {
|
||||
for (const eventKey in componentProps['events']) {
|
||||
try {
|
||||
const event = new Function(
|
||||
'schema',
|
||||
'formModel',
|
||||
'formActionType',
|
||||
`${componentProps['events'][eventKey]}`,
|
||||
);
|
||||
componentProps['on' + upperFirst(eventKey)] = function () {
|
||||
let cloneFormModel = cloneDeep(formModel);
|
||||
for (let item in cloneFormModel) {
|
||||
let field = camelCaseString(item);
|
||||
if (field) cloneFormModel[field] = cloneFormModel[item];
|
||||
}
|
||||
event(props.schema, isCamelCase ? cloneFormModel : formModel, props.formApi);
|
||||
|
||||
if (isCamelCase) {
|
||||
for (let item in formModel) {
|
||||
let field = camelCaseString(item);
|
||||
if (cloneFormModel && field && cloneFormModel[field] !== undefined) {
|
||||
formModel[item] = cloneFormModel[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
notification.error({
|
||||
message: 'Tip',
|
||||
description: '触发事件填写有误!',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isBoolean(props.schema.dynamicDisabled)) {
|
||||
componentProps['disabled'] = props.schema.dynamicDisabled;
|
||||
}
|
||||
if (isBoolean(props.schema.required)) {
|
||||
componentProps['required'] = props.schema.required;
|
||||
}
|
||||
|
||||
return componentProps as Recordable;
|
||||
});
|
||||
|
||||
const labelCol = computed(() => {
|
||||
return unref(getComponentsProps).span
|
||||
? { span: unref(getComponentsProps).span }
|
||||
: unref(itemLabelWidthProp).labelCol;
|
||||
});
|
||||
|
||||
const rules = computed(() => {
|
||||
const requiredRule = {
|
||||
required: unref(getComponentsProps).required || false,
|
||||
message: `${props.schema.label}是必填项`,
|
||||
};
|
||||
const rulesList = cloneDeep(unref(getComponentsProps).rules);
|
||||
if (!rulesList) return [requiredRule];
|
||||
rulesList?.map((item) => (item.pattern = eval(item.pattern)));
|
||||
return [...rulesList, requiredRule];
|
||||
});
|
||||
|
||||
//根据labelwidth 生成labelCol
|
||||
const itemLabelWidthProp = useItemLabelWidth(getSchema, formProps!);
|
||||
watch(
|
||||
() => formModel,
|
||||
() => {
|
||||
// console.log('formitem watch!!!!!!!!');
|
||||
//填值以后需要手动校验的组件
|
||||
const validateComponents = ['User', 'RichTextEditor', 'Upload', 'SelectMap'];
|
||||
if (validateComponents.includes(props.schema.component) && formModel![props.schema.field]) {
|
||||
setTimeout(() => {
|
||||
props.formApi?.validateFields([props.schema.field]);
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
// console.log('onMounted');
|
||||
// if (
|
||||
// staticDataComponents.includes(props.schema.component) &&
|
||||
// (props.schema.componentProps as any)?.datasourceType === 'staticData'
|
||||
// ) {
|
||||
// let { defaultSelect } = props.schema.componentProps as any;
|
||||
// emit('update:value', defaultSelect);
|
||||
// console.log('update:value1', defaultSelect);
|
||||
// } else {
|
||||
// let { defaultValue } = props.schema;
|
||||
// if (!!props.schema.field && !noDefaultValueComponents.includes(props.schema.component)) {
|
||||
// console.log('update:value', props.schema.component === 'SubForm' ? [] : defaultValue);
|
||||
// emit('update:value', defaultValue);
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
const formComponent = (schema) => {
|
||||
return componentMap.get(
|
||||
['caseErpApplyDetailList', 'case_erp_apply_detailList', 'CASE_ERP_APPLY_DETAILList'].includes(
|
||||
schema.field,
|
||||
)
|
||||
? 'ErpApply'
|
||||
: schema.component,
|
||||
);
|
||||
};
|
||||
|
||||
const defaultComponent = (schema) => {
|
||||
return componentMap.get(
|
||||
schema.key === 'ac18952da41b45c9a66ffba3e42b7f3d'
|
||||
? 'ErpUpload'
|
||||
: schema.key === 'b3ba87573cf0466d951bc63fd4df1c78'
|
||||
? 'ErpCheck'
|
||||
: schema.component,
|
||||
);
|
||||
};
|
||||
|
||||
function showComponent(schema) {
|
||||
return props.isWorkFlow
|
||||
? !noShowWorkFlowComponents.includes(schema.type)
|
||||
: !noShowGenerateComponents.includes(schema.type);
|
||||
}
|
||||
function getShow(schema: FormSchema): boolean {
|
||||
const { show } = schema;
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isIfShow = show;
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
function getIsShow(schema: FormSchema): boolean {
|
||||
const { componentProps, show } = schema as any;
|
||||
let isShow = true;
|
||||
if (isBoolean(componentProps?.isShow)) {
|
||||
isShow = componentProps?.isShow;
|
||||
}
|
||||
if (isBoolean(show)) {
|
||||
isShow = show;
|
||||
}
|
||||
return isShow;
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item-label > label) {
|
||||
white-space: normal;
|
||||
display: inline;
|
||||
line-height: 28px;
|
||||
}
|
||||
</style>
|
||||
251
src/components/SimpleForm/types/index.ts
Normal file
251
src/components/SimpleForm/types/index.ts
Normal file
@ -0,0 +1,251 @@
|
||||
import { Rule } from 'ant-design-vue/lib/form';
|
||||
import { VNode } from 'vue';
|
||||
|
||||
type ColSpanType = number | string;
|
||||
|
||||
// export interface FormApi {
|
||||
// setFieldsValue: <T>(values: T) => Promise<void>;
|
||||
// resetFields: () => Promise<void>;
|
||||
// getFieldsValue: () => Recordable;
|
||||
// clearValidate: (name?: string | string[]) => Promise<void>;
|
||||
// updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
// resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
// setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||
// removeSchemaByFiled: (field: string | string[]) => Promise<void>;
|
||||
// appendSchemaByField: (
|
||||
// schema: FormSchema,
|
||||
// prefixField: string | undefined,
|
||||
// first?: boolean | undefined,
|
||||
// ) => Promise<void>;
|
||||
// validateFields: (nameList?: NamePath[]) => Promise<any>;
|
||||
// validate: (nameList?: NamePath[]) => Promise<any>;
|
||||
// scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
|
||||
// }
|
||||
export interface ColEx {
|
||||
style?: any;
|
||||
/**
|
||||
* raster number of cells to occupy, 0 corresponds to display: none
|
||||
* @default none (0)
|
||||
* @type ColSpanType
|
||||
*/
|
||||
span?: ColSpanType;
|
||||
|
||||
/**
|
||||
* raster order, used in flex layout mode
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
order?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the layout fill of flex
|
||||
* @default none
|
||||
* @type ColSpanType
|
||||
*/
|
||||
flex?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells to offset Col from the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
offset?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the right
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
push?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
pull?: ColSpanType;
|
||||
|
||||
/**
|
||||
* <576px and also default setting, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥576px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥768px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥992px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥1200px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥1600px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
}
|
||||
|
||||
export interface RenderCallbackParams {
|
||||
schema: FormSchema;
|
||||
values: Recordable;
|
||||
model: Recordable;
|
||||
field: string;
|
||||
}
|
||||
export interface HelpComponentProps {
|
||||
maxWidth: string;
|
||||
// Whether to display the serial number
|
||||
showIndex: boolean;
|
||||
// Text list
|
||||
text: any;
|
||||
// colour
|
||||
color: string;
|
||||
// font size
|
||||
fontSize: string;
|
||||
icon: string;
|
||||
absolute: boolean;
|
||||
// Positioning
|
||||
position: any;
|
||||
}
|
||||
|
||||
export interface GridComponentProps {
|
||||
span: number;
|
||||
name?: string;
|
||||
list: FormSchema[];
|
||||
}
|
||||
|
||||
export interface TabComponentProps {
|
||||
span: number;
|
||||
name?: string;
|
||||
list: FormSchema[];
|
||||
}
|
||||
|
||||
export interface FormSchema {
|
||||
// 字段名
|
||||
field: string;
|
||||
// 表单标题
|
||||
label: string | VNode;
|
||||
// Auxiliary text
|
||||
subLabel?: string;
|
||||
// Help text on the right side of the text
|
||||
helpMessage?:
|
||||
| string
|
||||
| string[]
|
||||
| ((renderCallbackParams: RenderCallbackParams) => string | string[]);
|
||||
// BaseHelp component props
|
||||
helpComponentProps?: Partial<HelpComponentProps>;
|
||||
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
|
||||
labelWidth?: string | number;
|
||||
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
|
||||
disabledLabelWidth?: boolean;
|
||||
// render component
|
||||
component: ComponentType;
|
||||
// grid layout children
|
||||
children?: GridComponentProps[] | TabComponentProps[];
|
||||
// Component parameters
|
||||
componentProps?: any;
|
||||
// Required
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
|
||||
|
||||
// Validation rules
|
||||
rules?: Rule[];
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>;
|
||||
|
||||
// 默认值
|
||||
defaultValue?: any;
|
||||
isAdvanced?: boolean;
|
||||
|
||||
// Matching details components
|
||||
span?: number;
|
||||
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
// Render the content in the form-item tag
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
// Rendering col content requires outer wrapper form-item
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
renderComponentContent?:
|
||||
| ((renderCallbackParams: RenderCallbackParams) => any)
|
||||
| VNode
|
||||
| VNode[]
|
||||
| string;
|
||||
|
||||
// Custom slot, in from-item
|
||||
slot?: string;
|
||||
|
||||
// Custom slot, similar to renderColContent
|
||||
colSlot?: string;
|
||||
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
|
||||
}
|
||||
|
||||
export type ComponentType =
|
||||
| 'Input'
|
||||
| 'InputGroup'
|
||||
| 'InputPassword'
|
||||
| 'InputSearch'
|
||||
| 'InputTextArea'
|
||||
| 'InputNumber'
|
||||
| 'InputCountDown'
|
||||
| 'Select'
|
||||
| 'ApiSelect'
|
||||
| 'TreeSelect'
|
||||
| 'ApiTree'
|
||||
| 'ApiTreeSelect'
|
||||
| 'ApiRadioGroup'
|
||||
| 'RadioButtonGroup'
|
||||
| 'RadioGroup'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'AutoComplete'
|
||||
| 'ApiCascader'
|
||||
| 'Cascader'
|
||||
| 'DatePicker'
|
||||
| 'MonthPicker'
|
||||
| 'RangePicker'
|
||||
| 'WeekPicker'
|
||||
| 'TimePicker'
|
||||
| 'Switch'
|
||||
| 'StrengthMeter'
|
||||
| 'Upload'
|
||||
| 'IconPicker'
|
||||
| 'Render'
|
||||
| 'Slider'
|
||||
| 'Rate'
|
||||
| 'Grid'
|
||||
| 'Tab'
|
||||
| 'ChildTable'
|
||||
| 'Divider'
|
||||
| 'Dept'
|
||||
| 'User'
|
||||
| 'Info'
|
||||
| 'Area'
|
||||
| 'Text';
|
||||
Reference in New Issue
Block a user