增加配置项

值变更回调事件增加传参
移动端不绑表字段报错修正
子组件将formModel传给父组件
This commit is contained in:
yaoyn
2024-06-11 17:09:39 +08:00
parent 1e4b1c3452
commit 832c0f94d8
7 changed files with 823 additions and 818 deletions

View File

@ -16,6 +16,8 @@ VITE_DROP_CONSOLE = false
# 接口地址
# 如果没有跨域问题,直接在这里配置即可
VITE_GLOB_API_URL=http://10.133.96.105:8077
# 报表系统地址
VITE_GLOB_REPORT_URL=http://10.133.96.105:3100
# 文件上传接口 可选
VITE_GLOB_UPLOAD_URL = /system/oss/upload
@ -31,3 +33,6 @@ VITE_GLOB_PRINT_BASE_URL = http://114.116.210.204:3300
# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
VITE_GLOB_API_URL_PREFIX =
# 屏蔽通知消息的轮询
VITE_DISABLE_NEWS=false

View File

@ -259,7 +259,7 @@
const val = Array.isArray(value) ? value.join(',') : value;
emitData.value = args;
emit('update:value', val);
emit('change', val);
emit('change', val, args);
selectedValue.value = props.value === undefined ? val : (((typeof props.value === 'string' && !!props.value ? props.value?.split(',') : props.value) || undefined) as any);
updateSepTextField(Array.isArray(value) ? value : [value]);
}

View File

@ -1,12 +1,16 @@
<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">
<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)">
<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" />
<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>
@ -30,21 +34,21 @@
</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';
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();
@ -53,13 +57,13 @@
const wrapWidth = ref(960);
const formData = inject('formData', { noInject: true });
const propsRef = ref<Partial<FormProps>>({});
const propsRef = ref<Partial<FormProps>>({});
const schemaRef = ref<Nullable<FormSchema[]>>(null);
const schemaRef = ref<Nullable<FormSchema[]>>(null);
const emit = defineEmits(['submit']);
const emit = defineEmits(['submit']);
const props = defineProps({
const props = defineProps({
// 表单配置规则
formProps: {
type: Object as PropType<FormProps>
@ -67,7 +71,7 @@
//表单数据
formModel: {
type: Object as PropType<Recordable>,
default: () => {}
default: () => { }
},
//是否是工作流
isWorkFlow: {
@ -83,33 +87,33 @@
watches: {
type: Array
}
});
});
const getSchemas = computed<FormSchema[]>(() => {
const getSchemas = computed<FormSchema[]>(() => {
return (unref(getProps).schemas as any) || unref(schemaRef);
});
});
// Get the basic configuration of the form
const getProps = computed((): FormProps => {
// 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 => {
// 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() {
function getWrapSize() {
const rect = formWrap.value?.getBoundingClientRect();
wrapWidth.value = rect?.width || 960;
}
}
function getColWidth(schema: any) {
function getColWidth(schema: any) {
const compProps = schema.componentProps;
if (compProps?.responsive) {
if (compProps.respNewRow) {
@ -126,11 +130,11 @@
}
}
return schema.colProps?.span;
}
}
const debGetWrapSize = debounce(getWrapSize, 300);
const debGetWrapSize = debounce(getWrapSize, 300);
onMounted(() => {
onMounted(() => {
window.addEventListener('resize', debGetWrapSize);
getWrapSize();
nextTick(() => {
@ -141,17 +145,17 @@
});
}
});
});
});
onUnmounted(() => {
onUnmounted(() => {
window.removeEventListener('resize', debGetWrapSize);
});
});
function showComponent(schema) {
function showComponent(schema) {
return props.isWorkFlow ? !noShowWorkFlowComponents.includes(schema.type) : !noShowGenerateComponents.includes(schema.type);
}
}
function getIsShow(schema: FormSchema, itemValue: any): boolean {
function getIsShow(schema: FormSchema, itemValue: any): boolean {
const { show } = schema;
const { showAdvancedButton } = getProps.value;
const itemIsAdvanced = showAdvancedButton ? (isBoolean(schema.isAdvanced) ? schema.isAdvanced : true) : true;
@ -172,9 +176,9 @@
isShow = isShow && itemIsAdvanced;
return isShow;
}
}
function getIfShow(schema: FormSchema, itemValue: any): boolean {
function getIfShow(schema: FormSchema, itemValue: any): boolean {
const { ifShow } = schema;
let isIfShow = true;
@ -191,15 +195,16 @@
});
}
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 () => {
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);
formData.formModel=formModel;
const handleSubmit = async () => {
try {
const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) {
@ -208,18 +213,18 @@
}
const res = await formRef.value!.validate();
emit('submit', res);
} catch (error) {}
};
} catch (error) { }
};
const handleReset = () => {
const handleReset = () => {
resetFields();
};
};
const refreshFieldObj = ref<object>({});
const refreshFieldObj = ref<object>({});
getComponent(getSchemas.value);
getComponent(getSchemas.value);
function getComponent(component) {
function getComponent(component) {
const layoutComponents = ['tab', 'grid', 'card'];
component?.map((info) => {
if (layoutComponents.includes(info.type!)) {
@ -248,9 +253,9 @@
setComponentDefault(info);
}
});
}
}
function setComponentDefault(item) {
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;
@ -280,12 +285,12 @@
return;
}
}
}
}
/**
/**
* 回车提交表单
*/
function handleEnterPress(e: KeyboardEvent) {
function handleEnterPress(e: KeyboardEvent) {
const { autoSubmitOnEnter } = unref(getProps);
if (!autoSubmitOnEnter) return;
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
@ -294,21 +299,21 @@
handleSubmit();
}
}
}
}
//调用表单验证
const validate = async (nameList?: NamePath[]): Promise<any> => formRef.value?.validate(nameList);
//调用表单验证
const validate = async (nameList?: NamePath[]): Promise<any> => formRef.value?.validate(nameList);
//清除表单验证
const clearValidate = async (name?: string | string[]): Promise<any> => formRef.value?.clearValidate(name);
//清除表单验证
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 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 validateFields = async (nameList?: NamePath[] | string, options?: ValidateOptions): Promise<any> => formRef.value?.validateFields(nameList, options);
const findSchema = (schemaArr, key) => {
const findSchema = (schemaArr, key) => {
let schema;
const formListComponent = ['tab', 'grid', 'card'];
schemaArr?.some((info) => {
@ -336,14 +341,14 @@
}
});
return schema;
};
};
const setDefaultValue = async (): Promise<void> => {
const setDefaultValue = async (): Promise<void> => {
getComponent(getSchemas.value);
};
};
// 重置表单所有数据
const resetFields = async (): Promise<void> => {
// 重置表单所有数据
const resetFields = async (): Promise<void> => {
Object.keys(formModel).forEach((key) => {
//没有绑定字段的组件不处理
if (!key) return;
@ -389,15 +394,15 @@
}
});
nextTick(() => clearValidate());
};
};
// 更改formProps
const setProps = async (formProps: Partial<FormProps>): Promise<void> => {
// 更改formProps
const setProps = async (formProps: Partial<FormProps>): Promise<void> => {
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
};
};
//设定某个字段值 慎用 建议直接页面直接操作formModel数据
const setFieldsValue = async (values: Recordable): Promise<void> => {
//设定某个字段值 慎用 建议直接页面直接操作formModel数据
const setFieldsValue = async (values: Recordable): Promise<void> => {
Object.keys(values).forEach((key) => {
if (!isNil(key)) {
formModel[key] = values[key];
@ -405,14 +410,14 @@
});
executeEvent(getSchemas.value);
};
};
/**
/**
* 执行schema中 所有change事件
* 修改bug #5090
* 为了保证表单赋值触发所有组件的change事件
*/
const executeEvent = (allSchemas: FormSchema[]) => {
const executeEvent = (allSchemas: FormSchema[]) => {
for (const schema of allSchemas) {
//如果是这几个组件 需要查询子级
if (['Card', 'Tab', 'Grid'].includes(schema.component)) {
@ -473,15 +478,15 @@
// }
// }
// }
};
};
//获取表单值 慎用 建议直接页面直接操作formModel数据
const getFieldsValue = (): Recordable => {
//获取表单值 慎用 建议直接页面直接操作formModel数据
const getFieldsValue = (): Recordable => {
return toRaw(unref(formModel));
};
};
//更新schema
const updateSchema = async (data: Partial<FormSchema> | Partial<FormSchema>[]): Promise<void> => {
//更新schema
const updateSchema = async (data: Partial<FormSchema> | Partial<FormSchema>[]): Promise<void> => {
let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) {
updateData.push(data as FormSchema);
@ -514,8 +519,8 @@
});
schemaRef.value = uniqBy(schema, 'field');
};
const deepEachChild = (childs: GridComponentProps[] | TabComponentProps[] | CardComponentProps[], thisItem: FormSchema) => {
};
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)) {
@ -529,10 +534,10 @@
}
});
});
};
};
//重置schema
const resetSchema = async (data: Partial<FormSchema> | Partial<FormSchema>[]): Promise<void> => {
//重置schema
const resetSchema = async (data: Partial<FormSchema> | Partial<FormSchema>[]): Promise<void> => {
let updateData: Partial<FormSchema>[] = [];
if (isObject(data)) {
updateData.push(data as FormSchema);
@ -548,9 +553,9 @@
}
schemaRef.value = updateData as FormSchema[];
};
//移除schema
const removeSchemaByFiled = async (fields: string | string[]): Promise<void> => {
};
//移除schema
const removeSchemaByFiled = async (fields: string | string[]): Promise<void> => {
const schemaList: FormSchema[] = cloneDeep(unref(getSchemas));
if (!fields) {
return;
@ -570,9 +575,9 @@
}
}
schemaRef.value = schemaList;
};
//追加schema
const appendSchemaByField = async (schema: FormSchema, prefixField: string | undefined, first?: boolean | undefined): Promise<void> => {
};
//追加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);
@ -586,10 +591,10 @@
}
schemaRef.value = schemaList;
};
};
// 提交
const submit = async (e?: Event): Promise<void> => {
// 提交
const submit = async (e?: Event): Promise<void> => {
e && e.preventDefault();
const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) {
@ -604,9 +609,9 @@
} catch (error: any) {
throw new Error(error);
}
};
};
const showModal = async (modal: ModalFuncProps) => {
const showModal = async (modal: ModalFuncProps) => {
Modal.confirm({
title: modal.title,
icon: createVNode(ExclamationCircleOutlined),
@ -615,15 +620,15 @@
onCancel: modal.onCancel,
...modal
});
};
};
const regTest = async (regular: regTestProps) => {
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) => {
const httpRequest = async (request: requestProps) => {
if (request.requestType.toLowerCase() === 'get') {
return defHttp[request.requestType](
{
@ -645,17 +650,17 @@
}
);
}
};
};
const refreshAPI = (field: string) => {
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?) => {
const changeStyle = (schema, style, field?) => {
if (field) {
const changeSchema = unref(getSchemas).filter((item) => {
if (props.isCamelCase) {
@ -667,9 +672,9 @@
schema = changeSchema[0];
}
schema.componentProps.style = { ...schema.componentProps.style, ...style };
};
};
const formApi: FormActionType = {
const formApi: FormActionType = {
submit,
validate,
clearValidate,
@ -689,8 +694,8 @@
refreshAPI,
changeStyle,
setDefaultValue
};
};
//将表单方法 导出 给父组件使用。
defineExpose<FormActionType>(formApi);
//将表单方法 导出 给父组件使用。
defineExpose<FormActionType>(formApi);
</script>

View File

@ -84,7 +84,17 @@
:wrapperCol="itemLabelWidthProp.wrapperCol"
>
<template v-if="getDisable && readonlySupport(schema.component)">
<readonly :schema="schema" :model="formModel" />
<readonly :schema="schema" :model="formModel"/>
<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
@ -303,13 +313,13 @@
} else if (typeof fun === 'function') {
event = fun;
}
componentProps['on' + upperFirst(eventKey)] = function () {
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, { formData });
event(props.schema, isCamelCase ? cloneFormModel : formModel, props.formApi, { formData, value, selectedOptions });
if (isCamelCase) {
for (let item in formModel) {

View File

@ -22,21 +22,10 @@ export function getAppEnvConfig() {
(import.meta.env as unknown as GlobEnvConfig)
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;
const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_UPLOAD_PREVIEW,
VITE_GLOB_OUT_LINK_URL,
VITE_GLOB_PRINT_BASE_URL,
} = ENV;
const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL, VITE_GLOB_APP_SHORT_NAME, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_PREVIEW, VITE_GLOB_OUT_LINK_URL, VITE_GLOB_REPORT_URL, VITE_GLOB_PRINT_BASE_URL } = ENV;
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`,
);
warn(`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`);
}
return {
@ -47,7 +36,8 @@ export function getAppEnvConfig() {
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_UPLOAD_PREVIEW,
VITE_GLOB_OUT_LINK_URL,
VITE_GLOB_PRINT_BASE_URL,
VITE_GLOB_REPORT_URL,
VITE_GLOB_PRINT_BASE_URL
};
}

View File

@ -498,7 +498,8 @@
}
// TODO 这里继续写各组件自己特有的一些验证
if (!component.bindTable) {
if ((component.type == 'input' && !component.options!.isSave && !component.bindTable) ||
(component.type !== 'input' && !component.bindTable)) {
return t(`{name}未绑定表`, { name: component.label });
}

10
types/config.d.ts vendored
View File

@ -1,12 +1,5 @@
import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum';
import {
ContentEnum,
PermissionModeEnum,
ThemeEnum,
RouterTransitionEnum,
SettingButtonPositionEnum,
SessionTimeoutProcessingEnum,
} from '/@/enums/appEnum';
import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum, SettingButtonPositionEnum, SessionTimeoutProcessingEnum } from '/@/enums/appEnum';
import { CacheTypeEnum } from '/@/enums/cacheEnum';
@ -166,6 +159,7 @@ export interface GlobEnvConfig {
VITE_GLOB_PRINT_BASE_URL?: string;
//file preview
VITE_GLOB_UPLOAD_PREVIEW?: string;
VITE_GLOB_REPORT_URL: string;
}
export interface LogoConfig {