---初始化后台管理web页面项目

This commit is contained in:
2025-08-20 14:39:30 +08:00
parent ad49711a7e
commit 87545a8baf
2057 changed files with 282864 additions and 213 deletions

View 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 };

View File

View File

@ -0,0 +1,702 @@
<template>
<div ref="formWrap">
<Form ref="formRef" :label-col="getProps?.labelCol" :labelAlign="getProps?.labelAlign" :layout="getProps?.layout" :model="formModel" :wrapper-col="getProps?.wrapperCol" @keypress.enter="handleEnterPress">
<Row v-bind="getRow">
<template v-for="schema in getSchemas" :key="schema.field">
<div v-if="schema?.componentProps.respBreakLine" style="width: 100%; height: 1px"></div>
<Col v-if="getIfShow(schema, formModel[schema.field])" v-show="getIsShow(schema, formModel[schema.field])" :span="getColWidth(schema)">
<template v-if="showComponent(schema) && schema.type !== 'slot'">
<SimpleFormItem v-model:value="formModel[schema.field]" :form-api="formApi" :isWorkFlow="isWorkFlow" :refreshFieldObj="refreshFieldObj" :schema="schema" />
</template>
<template v-if="schema.type === 'slot'">
<slot :formModel="formModel" :name="schema.slotName" :schema="schema"></slot>
</template>
</Col>
</template>
</Row>
<div :style="{ textAlign: getProps.buttonLocation }">
<slot name="buttonBefore"></slot>
<a-button v-if="getProps.showSubmitButton" type="primary" @click="handleSubmit">
{{ t('提交') }}
</a-button>
<a-button v-if="getProps.showResetButton" style="margin-left: 10px" @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, debounce } from 'lodash-es';
import { computed, reactive, ref, provide, unref, nextTick, toRaw, createVNode, inject, onMounted, onUnmounted } 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 formWrap = ref<HTMLElement>();
const wrapWidth = ref(960);
const formData = inject('formData', { noInject: true });
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
},
// 自定义监听 用于以编码开发的形式替代json配置中的change事件
watches: {
type: Array
}
});
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
};
});
function getWrapSize() {
const rect = formWrap.value?.getBoundingClientRect();
wrapWidth.value = rect?.width || 960;
}
function getColWidth(schema: any) {
const compProps = schema.componentProps;
if (compProps?.responsive) {
if (compProps.respNewRow) {
return 24; // 响应式布局下独立成行
} else {
const wrapValue = wrapWidth.value;
if (wrapValue > import.meta.env.VITE_RESP_LG_WIDTH) {
return 8;
} else if (wrapValue > import.meta.env.VITE_RESP_MD_WIDTH) {
return 12;
} else {
return 24;
}
}
}
return schema.colProps?.span;
}
const debGetWrapSize = debounce(getWrapSize, 300);
onMounted(() => {
window.addEventListener('resize', debGetWrapSize);
getWrapSize();
nextTick(() => {
//添加隐藏组件
if (unref(getProps)?.hiddenComponent?.length) {
unref(getProps)?.hiddenComponent?.forEach((component) => {
formModel[component.bindField] = component.value;
});
}
});
});
onUnmounted(() => {
window.removeEventListener('resize', debGetWrapSize);
});
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;
const handler = schema.componentProps['events'][eventKey];
try {
if (typeof handler === 'string') {
const event = new Function('schema', 'formModel', 'formActionType', 'extParams', handler);
event(schema, formModel, formApi, { formData, allSchemas });
} else if (typeof handler === 'function') {
handler(schema, formModel, formApi, { formData, allSchemas });
}
} 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>

View File

@ -0,0 +1,703 @@
<template>
<div ref="formWrap">
<Form ref="formRef" :label-col="getProps?.labelCol" :labelAlign="getProps?.labelAlign" :layout="getProps?.layout" :model="formModel" :wrapper-col="getProps?.wrapperCol" @keypress.enter="handleEnterPress">
<Row v-bind="getRow">
<template v-for="schema in getSchemas" :key="schema.field">
<Col v-if="getIfShow(schema, formModel[schema.field])" v-show="getIsShow(schema, formModel[schema.field])" :span="getColWidth(schema)">
<div v-if="schema?.componentProps.respBreakLine" style="width: 100%; height: 1px"></div>
<template v-if="showComponent(schema) && schema.type !== 'slot'">
<SimpleFormItem v-model:value="formModel[schema.field]" :form-api="formApi" :isWorkFlow="isWorkFlow" :refreshFieldObj="refreshFieldObj" :schema="schema" />
</template>
<template v-if="schema.type === 'slot'">
<slot :formModel="formModel" :name="schema.slotName" :schema="schema"></slot>
</template>
</Col>
</template>
</Row>
<div :style="{ textAlign: getProps.buttonLocation }">
<slot name="buttonBefore"></slot>
<a-button v-if="getProps.showSubmitButton" type="primary" @click="handleSubmit">
{{ t('提交') }}
</a-button>
<a-button v-if="getProps.showResetButton" style="margin-left: 10px" @click="handleReset">
{{ t('重置') }}
</a-button>
<slot name="buttonAfter"></slot>
</div>
</Form>
</div>
</template>
<script lang="ts">
import { Form, FormInstance, Row, Col, Modal, message } from 'ant-design-vue';
import { cloneDeep, isArray, isBoolean, isFunction, isObject, isString, uniqBy, isNil, debounce } from 'lodash-es';
import { computed, reactive, ref, provide, unref, nextTick, toRaw, createVNode, inject, onMounted, onUnmounted } 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();
export default {
components: { Form, Row, Col, Modal, SimpleFormItem },
props: {
// 表单配置规则
formProps: {
type: Object as PropType<FormProps>
},
//表单数据
formModel: {
type: Object as PropType<Recordable>,
default: () => {}
},
//是否是工作流
isWorkFlow: {
type: Boolean,
default: false
},
//是否系统表单需要转驼峰,兼容旧系统表单
isCamelCase: {
type: Boolean,
default: false
},
// 自定义监听 用于以编码开发的形式替代json配置中的change事件
watches: {
type: Array
}
},
emits: ['submit'],
setup(props, { attrs, slots, emit, expose }) {
const formRef = ref<FormInstance>();
const formWrap = ref<HTMLElement>();
const wrapWidth = ref(960);
const propsRef = ref<Partial<FormProps>>({});
const schemaRef = ref<Nullable<FormSchema[]>>(null);
const formData = inject('formData', { noInject: true });
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
};
});
function getWrapSize() {
const rect = formWrap.value?.getBoundingClientRect();
wrapWidth.value = rect?.width || 960;
}
function getColWidth(schema: any) {
const compProps = schema.componentProps;
if (compProps?.responsive) {
if (compProps.respNewRow) {
return 24; // 响应式布局下独立成行
} else {
const wrapValue = wrapWidth.value;
if (wrapValue > import.meta.env.VITE_RESP_LG_WIDTH) {
return 8;
} else if (wrapValue > import.meta.env.VITE_RESP_MD_WIDTH) {
return 12;
} else {
return 24;
}
}
}
return schema.colProps?.span;
}
const debGetWrapSize = debounce(getWrapSize, 300);
onMounted(() => {
window.addEventListener('resize', debGetWrapSize);
getWrapSize();
nextTick(() => {
//添加隐藏组件
if (unref(getProps)?.hiddenComponent?.length) {
unref(getProps)?.hiddenComponent?.forEach((component) => {
formModel[component.bindField] = component.value;
});
}
});
});
onUnmounted(() => {
window.removeEventListener('resize', debGetWrapSize);
});
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;
const handler = schema.componentProps['events'][eventKey];
try {
if (typeof handler === 'string') {
const event = new Function('schema', 'formModel', 'formActionType', 'extParams', handler);
event(schema, formModel, formApi, { formData, allSchemas });
} else if (typeof handler === 'function') {
handler(schema, formModel, formApi, { formData, allSchemas });
}
} 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
};
//将表单方法 导出 给父组件使用。
expose({
...formApi
});
return {
getRow,
getProps,
getSchemas,
getIfShow,
getIsShow,
getColWidth,
showComponent,
formApi,
refreshFieldObj,
handleSubmit,
t,
handleReset,
handleEnterPress,
formRef,
formWrap,
formModel
};
}
};
</script>

View 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>

View File

@ -0,0 +1,449 @@
<template>
<div>
<!--如果是grid 组件 需要新增grid布局包裹-->
<template v-if="schema.component.includes('Grid')">
<Row v-show="getIsShow(schema)" :align="(schema.componentProps as any).align" :gutter="(schema.componentProps as any).gutter ?? 0" :justify="(schema.componentProps as any).justify" type="flex">
<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)" v-model:value="formModel![childSchema.field]" :form-api="formApi" :isWorkFlow="isWorkFlow" :label-col="labelCol" :refreshFieldObj="refreshFieldObj" :schema="childSchema" />
</template>
</Col>
</Row>
</template>
<!--如果是tab 组件 需要新增tab组件包裹-->
<template v-else-if="schema.component === 'Tab'">
<Tabs v-show="getIsShow(schema)" v-model:activeKey="activeKey" :size="(schema.componentProps as any).tabSize" :tabPosition="(schema.componentProps as any).tabPosition" :type="(schema.componentProps as any).type">
<TabPane v-for="(tab, tabIndex) in schema.children" :key="tabIndex" :forceRender="true" :tab="tab.name">
<template v-for="childSchema in tab.list" :key="childSchema.field">
<SimpleFormItem v-if="showComponent(childSchema)" v-model:value="formModel![childSchema.field]" :form-api="formApi" :isWorkFlow="isWorkFlow" :refreshFieldObj="refreshFieldObj" :schema="childSchema" />
</template>
</TabPane>
</Tabs>
</template>
<!--如果是子表单 组件 需要v-model:value="表单对象[字段名]"-->
<template v-else-if="schema.component.includes('Form')">
<FormItem
v-if="getShow(schema)"
v-show="getIsShow(schema)"
:key="schema.key"
:label="getComponentsProps.showLabel ? schema.label : ''"
:label-col="labelCol"
:labelAlign="formProps?.labelAlign"
:name="schema.field"
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<component :is="formComponent(schema)" v-model:value="formModel![schema.field]" :disabled="getDisable" :size="formProps?.size" v-bind="schema.componentProps" />
</FormItem>
</template>
<!--如果是子表单 组件 需要v-model:value="表单对象[字段名]"-->
<template v-else-if="schema.component.includes('OneForOne')">
<FormItem
v-if="getShow(schema)"
v-show="getIsShow(schema)"
:key="schema.key"
:label="getComponentsProps.showLabel ? schema.label : ''"
:label-col="labelCol"
:labelAlign="formProps?.labelAlign"
:name="schema.field"
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<component :is="componentMap.get(schema.component)" v-model:value="formModel![schema.field]" :disabled="getDisable" :refreshFieldObj="refreshFieldObj" :size="formProps?.size" v-bind="schema.componentProps" />
</FormItem>
</template>
<template v-else-if="schema.component === 'TableLayout'">
<TableLayoutPreview v-if="getShow(schema)" v-show="getIsShow(schema)" :element="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)" v-model:value="formModel![childSchema.field]" :form-api="formApi" :isWorkFlow="isWorkFlow" :refreshFieldObj="refreshFieldObj" :schema="childSchema" />
</template>
</div>
</div>
</template>
</TableLayoutPreview>
</template>
<!--如果是时间区间 组件 需要v-model:startField="表单对象[字段名]" v-model:endField="表单对象[字段名]"-->
<template v-else-if="schema.component.includes('Range')">
<FormItem
v-if="getShow(schema)"
v-show="getIsShow(schema)"
:key="schema.key"
:label="getComponentsProps.showLabel ? schema.label : ''"
:label-col="labelCol"
:name="schema.field"
:rules="rules"
:validateTrigger="['blur', 'change']"
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<template v-if="getDisable && readonlySupport(schema.component)">
<readonly :model="formModel" :schema="schema" />
<component
:is="componentMap.get(schema.component)"
v-show="false"
v-model:endField="schema.field.split(',')[1]"
v-model:startField="schema.field.split(',')[0]"
v-model:value="formModel![schema.field]"
:disabled="getDisable"
:size="formProps?.size"
v-bind="schema.componentProps"
/>
</template>
<template v-else>
<component
:is="componentMap.get(schema.component)"
v-model:endField="schema.field.split(',')[1]"
v-model:startField="schema.field.split(',')[0]"
v-model:value="formModel![schema.field]"
:disabled="getDisable"
:size="formProps?.size"
v-bind="schema.componentProps"
/>
</template>
</FormItem>
<!-- 因为Range会变为 开始时间 结束时间 2个属性给与表单数据 所以需要2个隐藏框绑定 starTime endTime -->
<FormItem v-show="false" :key="schema.key" :label="getComponentsProps.showLabel ? schema.label : ''" :name="schema.field.split(',')[0]">
<input type="hidden" />
</FormItem>
<FormItem v-show="false" :key="schema.key" :label="getComponentsProps.showLabel ? schema.label : ''" :name="schema.field.split(',')[1]">
<input type="hidden" />
</FormItem>
</template>
<!--如果checked 或者 switch组件 需要v-model:checked="表单对象[字段名]" "-->
<template v-else-if="checkedValueComponents.includes(schema.component)">
<FormItem v-if="getShow(schema)" v-show="getIsShow(schema)" :key="schema.key" :label="getComponentsProps.showLabel ? schema.label : ''" :label-col="labelCol" :name="schema.field" :rules="rules" :wrapperCol="itemLabelWidthProp.wrapperCol">
<component :is="componentMap.get(schema.component)" :key="refreshFieldObj[schema.field]" v-model:checked="formModel![schema.field]" :disabled="getDisable" :size="formProps?.size" v-bind="getComponentsProps" />
</FormItem>
</template>
<!--如果是card 组件 需要新增card组件包裹-->
<template v-else-if="schema.component.includes('Card')">
<CollapseContainer v-show="getIsShow(schema)" :bordered="false" :hasLeftBorder="true" :title="(schema.componentProps as any).title">
<template v-for="childSchema in schema.children![0].list" :key="childSchema.field">
<SimpleFormItem v-if="showComponent(childSchema)" v-model:value="formModel![childSchema.field]" :form-api="formApi" :isWorkFlow="isWorkFlow" :refreshFieldObj="refreshFieldObj" :schema="childSchema" />
</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"
:label="getComponentsProps.showLabel ? schema.label : ''"
:label-col="labelCol"
:name="schema.field"
:rules="rules"
:validateTrigger="['blur', 'change']"
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<component :is="componentMap.get(schema.component)" :key="refreshFieldObj[schema.field]" :disabled="getDisable" :size="formProps?.size" :value="schema.defaultValue" v-bind="getComponentsProps" />
</FormItem>
</template>
<template v-else>
<FormItem
v-if="getShow(schema)"
v-show="getIsShow(schema)"
:key="schema.key"
:label="getComponentsProps.showLabel ? schema.label : ''"
:label-col="labelCol"
:name="schema.field"
:rules="rules"
:validateTrigger="['blur', 'change']"
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<template v-if="getDisable && readonlySupport(schema.component)">
<readonly :schema="schema" :value="formModel![schema.field]" />
</template>
<template v-else>
<component :is="defaultComponent(schema)" :key="refreshFieldObj[schema.field]" v-model:value="formModel![schema.field]" :disabled="getDisable" :formData="formModel" :size="formProps?.size" v-bind="getComponentsProps" />
</template>
</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';
import Readonly from '/@/components/Form/src/components/Readonly.vue';
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
}
);
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']) {
const allSchemas = formProps.value.schemas;
for (const eventKey in componentProps['events']) {
try {
const fun = componentProps['events'][eventKey];
let event;
if (typeof fun === 'string') {
event = new Function('schema', 'formModel', 'formActionType', 'extParams', `${fun}`);
} else if (typeof fun === 'function') {
event = fun;
}
componentProps['on' + upperFirst(eventKey)] = function (value, selectedOptions) {
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, { value, selectedOptions, allSchemas: allSchemas });
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(() => {
// 列宽支持两种模式 labelWidthMode = flex为百分比宽度 fix 为定宽
const commonLabelCol = unref(itemLabelWidthProp).labelCol;
const itemLabelCol = unref(getComponentsProps).labelCol;
const { labelWidthMode, labelFixWidth, span } = props.schema.componentProps as any;
let labelCol: any = {};
if (labelWidthMode !== 'fix') {
labelCol.span = span || itemLabelCol?.span || commonLabelCol?.span;
} else {
labelCol.style = {
width: `${labelFixWidth || 120}px`
};
}
return 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 readonlySupport(name) {
return /^(Input|AutoCodeRule|DatePicker|Text|TimePicker|Range|RichTextEditor|TimeRangePicker|RangePicker|InputTextArea)$/.test(name);
}
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: 32px;
}
</style>
<style lang="less">
.ant-select-disabled {
.ant-select-selector {
border: none !important;
background-color: transparent !important;
padding-left: 0 !important;
}
.ant-select-selection-item {
color: rgb(0 0 0 / 85%) !important;
}
.ant-select-arrow {
display: none;
}
}
</style>

View File

@ -0,0 +1,259 @@
<script lang="ts">
import { Form, Col, Row, Tabs, TabPane, Divider } from 'ant-design-vue';
import { isBoolean, isFunction, upperFirst, cloneDeep } from 'lodash-es';
import { computed, 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';
import Readonly from '/@/components/Form/src/components/Readonly.vue';
export default {
components: {
Form,
Col,
Row,
Tabs,
TabPane,
Divider,
checkedValueComponents,
CollapseContainer,
TableLayoutPreview,
Readonly
},
props: {
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
}
},
setup(props, ctx) {
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
}
);
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 fun = componentProps['events'][eventKey];
let event;
if (typeof fun === 'string') {
event = new Function('schema', 'formModel', 'formActionType', 'extParams', `${fun}`);
} else if (typeof fun === 'function') {
event = fun;
}
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(() => {
// 列宽支持两种模式 labelWidthMode = flex为百分比宽度 fix 为定宽
const commonLabelCol = unref(itemLabelWidthProp).labelCol;
const itemLabelCol = unref(getComponentsProps).labelCol;
const { labelWidthMode, labelFixWidth, span } = props.schema.componentProps as any;
let labelCol: any = {};
if (labelWidthMode !== 'fix') {
labelCol.span = span || itemLabelCol?.span || commonLabelCol?.span;
} else {
labelCol.style = {
width: `${labelFixWidth || 120}px`
};
}
return 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
}
);
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 readonlySupport(name) {
return /^(Input|AutoCodeRule|DatePicker|Text|TimePicker|Range|RichTextEditor|TimeRangePicker|RangePicker|InputTextArea)$/.test(name);
}
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;
}
return {
getIsShow,
getShow,
formModel,
formProps,
showComponent,
activeKey,
getComponentsProps,
componentMap,
readonlySupport,
getDisable,
formComponent,
defaultComponent,
rules,
itemLabelWidthProp
};
}
};
</script>

View 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';