初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

View File

@ -0,0 +1,12 @@
export { default as MenuConfigStep } from './src/MenuConfigStep.vue';
export { default as StructureConfigStep } from './src/StructureConfigStep.vue';
export { default as ViewDesignStep } from './src/ViewDesignStep.vue';
export { default as FormDesignStep } from './src/FormDesignStep.vue';
export { default as FormEventStep } from './src/FormEventStep.vue';
export { default as TableConfigStep } from './src/TableConfigStep.vue';
export { default as SelectDatabase } from './src/components/SelectDatabase.vue';
export { default as TableNameModal } from './src/components/TableNameModal.vue';

View File

@ -0,0 +1,597 @@
<template>
<div style="height: 100%">
<DesignForm ref="designFormRef" />
<FormPreviewDrawer @register="registerDrawer" />
</div>
</template>
<script lang="ts" setup>
import { DesignForm, noHaveTableAndField, remoteComponents } from '/@/components/Designer';
import FormPreviewDrawer from './components/FormPreviewDrawer.vue';
import { inject, ref, Ref, watch } from 'vue';
import { useDrawer } from '/@/components/Drawer';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { TableFieldConfig } from '/@/model/generator/tableStructureConfig';
import { useMessage } from '/@/hooks/web/useMessage';
import { ComponentOptionModel, FormJson } from '/@/model/generator/codeGenerator';
import { unionWith, cloneDeep } from 'lodash-es';
import {
noHaveField,
shortTextComponents,
longTextComponents,
integerComponents,
decimalsComponents,
dateTimeComponents,
timeComponents,
} from '/@/components/Designer';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const generatorConfig = inject<GeneratorConfig>('generatorConfig');
const designType = inject<string>('designType');
const isCustomForm = inject<boolean>('isCustomForm', false);
const widgetForm = inject<any>('widgetForm');
const { notification } = useMessage();
const current = inject<Ref<number>>('current') as Ref<number>;
const designFormRef = ref();
const rangeComponents = ['time-range', 'date-range'];
watch(
() => current.value,
() => {
if (designType == 'data' || (isCustomForm && designType == 'template')) return;
designFormRef.value.setWidgetFormSelect(widgetForm.value);
},
);
//注册抽屉 获取外部操作抽屉得方法
const [registerDrawer] = useDrawer();
const setStructureConfig = () => {
//新增时的配置信息
const addStructureConfig = cloneDeep(generatorConfig!.tableStructureConfigs || []);
generatorConfig!.tableStructureConfigs = [];
let tableFieldConfigs = [] as TableFieldConfig[];
getTableStructure(generatorConfig?.formJson.list, tableFieldConfigs);
if (generatorConfig?.formJson.hiddenComponent?.length) {
generatorConfig?.formJson.hiddenComponent.map((item) => {
if (
!generatorConfig?.tableStructureConfigs?.length ||
!generatorConfig?.tableStructureConfigs[0]?.isMain
) {
generatorConfig?.tableStructureConfigs?.unshift({
tableName: item.bindTable,
tableComment: '',
isMain: true,
tableFieldConfigs: [],
});
}
if (generatorConfig?.tableStructureConfigs?.length) {
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.push({
key: item.key,
fieldName: item.bindField.substr(0, 30),
fieldLength: 500,
fieldType: 0,
fieldComment: t('隐藏组件'),
});
}
});
}
let isDataAuth = isCustomForm
? generatorConfig?.isDataAuth
: generatorConfig?.outputConfig.isDataAuth;
//自定义表单界面优先、简易模板开启了数据权限则新增rule_user_id字段
if (isDataAuth) {
const hasRuleUserField =
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.find(
(x) => x.key === 'rule_user_id',
);
if (hasRuleUserField) return;
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.push({
key: 'rule_user_id',
fieldName: 'rule_user_id',
fieldLength: 500,
fieldType: 7,
fieldComment: t('数据权限所属人ID'),
});
} else {
if (generatorConfig?.tableStructureConfigs?.length) {
generatorConfig.tableStructureConfigs[0].tableFieldConfigs =
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.filter(
(x) => x.key !== 'rule_user_id',
);
}
}
if (designType === 'code' && addStructureConfig.length) {
//编辑回显
addStructureConfig.forEach((addConfig) => {
generatorConfig!.tableStructureConfigs!.forEach((config) => {
if (addConfig.tableName === config.tableName) {
config.tableComment = addConfig.tableComment;
addConfig.tableFieldConfigs?.forEach((subAddConfig) => {
config.tableFieldConfigs?.forEach((subConfig) => {
if (subAddConfig.fieldName === subConfig.fieldName) {
subConfig.fieldType = subAddConfig.fieldType;
subConfig.fieldLength = subAddConfig.fieldLength;
subConfig.fieldComment = subAddConfig.fieldComment;
}
});
});
}
});
});
}
};
const getTableStructure = (list, tableFieldConfigs) => {
list?.map((item) => {
if (['tab', 'grid', 'card'].includes(item.type)) {
for (const child of item.layout!) {
getTableStructure(child.list, tableFieldConfigs);
}
} else if (item.type === 'table-layout') {
for (const child of item.layout!) {
for (const el of child.list) {
getTableStructure(el.children, tableFieldConfigs);
}
}
} else if (item.type === 'form' || item.type === 'one-for-one') {
let subTableFieldConfigs = [] as TableFieldConfig[];
generatorConfig?.tableStructureConfigs?.push({
key: item.key,
tableName: item.bindTable,
tableComment: '',
isMain: false,
tableFieldConfigs: subTableFieldConfigs,
});
if (item.type === 'form') {
item.children?.map((subItem) => {
if (!noHaveField.includes(subItem.type) || rangeComponents.includes(subItem.type)) {
setTableFieldConfigs(subItem, subTableFieldConfigs);
}
});
} else {
getSimpleTableStructure(item.children, subTableFieldConfigs);
}
} else if (
(!noHaveField.includes(item.type) && item.type !== 'input') ||
rangeComponents.includes(item.type) ||
(item.type == 'input' && !item.options.isSave)
) {
if (
!generatorConfig?.tableStructureConfigs?.length ||
!generatorConfig?.tableStructureConfigs[0].isMain
) {
generatorConfig?.tableStructureConfigs?.unshift({
tableName: item.bindTable,
tableComment: '',
isMain: true,
tableFieldConfigs,
});
}
if (generatorConfig?.tableStructureConfigs?.length) {
setTableFieldConfigs(item, generatorConfig?.tableStructureConfigs[0].tableFieldConfigs);
}
}
});
};
const getSimpleTableStructure = (list, tableFieldConfigs) => {
list?.map((item) => {
if (['tab', 'grid', 'card'].includes(item.type)) {
for (const child of item.layout!) {
getSimpleTableStructure(child.list, tableFieldConfigs);
}
} else if (!noHaveField.includes(item.type) || rangeComponents.includes(item.type)) {
setTableFieldConfigs(item, tableFieldConfigs);
}
});
};
const setTableFieldConfigs = (item, tableFieldConfigs) => {
if (rangeComponents.includes(item.type)) {
tableFieldConfigs.push({
key: item.key,
fieldStartName: item.bindStartTime,
fieldName: item.bindStartTime.substr(0, 30),
fieldLength: !getFieldType(item.type) ? 500 : null,
fieldType: getFieldType(item.type),
fieldComment: t(`{types}开始时间`, { types: item.label }),
});
tableFieldConfigs.push({
key: item.key,
fieldEndName: item.bindEndTime,
fieldName: item.bindEndTime.substr(0, 30),
fieldLength: !getFieldType(item.type) ? 500 : null,
fieldType: getFieldType(item.type),
fieldComment: t(`{types}结束时间`, { types: item.label }),
});
} else if (item.type === 'info' && item.options.infoType === 2) {
//信息体组件 选择当前时间情况
tableFieldConfigs.push({
key: item.key,
fieldName: item.bindField.substr(0, 30),
fieldLength: null,
fieldType: 5,
fieldComment: item.label!,
});
} else {
if (item.options.isSave && item.type == 'input') return;
tableFieldConfigs.push({
key: item.key,
fieldName: item.bindField.substr(0, 30),
fieldLength: !getFieldType(item.type) ? 500 : null,
fieldType: getFieldType(item.type),
fieldComment: item.label!,
});
}
};
const getFieldType = (type) => {
switch (type) {
case shortTextComponents.find((x) => x === type):
return 0;
case longTextComponents.find((x) => x === type):
return 1;
case integerComponents.find((x) => x === type):
return 2;
case decimalsComponents.find((x) => x === type):
return 3;
case dateTimeComponents.find((x) => x === type):
return 5;
case timeComponents.find((x) => x === type):
return 8;
default:
return 0;
}
};
//验证当前步骤的数据
const validateStep = async (): Promise<boolean> => {
if (designType !== 'data') {
setStructureConfig();
}
const formJson = designFormRef.value.getJson() as FormJson;
//formJson 是否为空 或者 一个组件都没有
if (!formJson || formJson.list.length === 0) {
notification.error({
message: t('提示'),
description: t('表单设计不能为空!'),
}); //提示消息
return false;
}
const { tableConfigs } = generatorConfig as GeneratorConfig;
const mainTableName = tableConfigs?.find((x) => x.isMain)?.tableName;
//先判断所有非子表组件 是否包含主表字段 如果一个主表字段都没有 提示
const getMainComponent = (list?) => {
if (!list) return [];
let mainComponents = [] as Recordable[];
for (const item of list) {
if (['tab', 'grid', 'card'].includes(item.type)) {
for (const child of item.layout!) {
mainComponents = unionWith(mainComponents, getMainComponent(child.list));
}
} else if (item.type === 'table-layout') {
for (const child of item.layout!) {
for (const el of child.list) {
mainComponents = unionWith(mainComponents, getMainComponent(el.children));
}
}
} else {
if (item.bindTable === mainTableName) {
mainComponents.push(item);
}
}
}
return mainComponents;
};
//获取子表中的字段
const getSubField = (list, subTableFieldList = {}) => {
for (const item of list) {
if (['tab', 'grid', 'card'].includes(item.type)) {
for (const child of item.layout!) {
getSubField(child.list, subTableFieldList);
}
}
if (item.type === 'form' || item.type === 'one-for-one') {
if (!item.children.length) return;
if (!subTableFieldList[item.bindTable]) subTableFieldList[item.bindTable] = [];
item.children.forEach((x) => {
if (['tab', 'grid', 'card'].includes(x.type)) {
for (const child of x.layout!) {
getSubField(child.list, subTableFieldList);
}
} else if (rangeComponents.includes(x.type)) {
subTableFieldList[item.bindTable].push(x.bindStartTime);
subTableFieldList[item.bindTable].push(x.bindEndTime);
} else {
if (x.bindField) subTableFieldList[item.bindTable].push(x.bindField);
}
});
} else if (item.isSingleFormChild) {
if (rangeComponents.includes(item.type)) {
subTableFieldList[item.bindTable].push(item.bindStartTime);
subTableFieldList[item.bindTable].push(item.bindEndTime);
} else {
if (item.bindField) subTableFieldList[item.bindTable].push(item.bindField);
}
}
}
return subTableFieldList;
};
//判断子表的同一表中 是否有重复字段
const subTableHasSameField = () => {
const subTableFieldList = getSubField(formJson.list);
const hasSameFieldList: boolean[] = [];
if (!Object.keys(subTableFieldList!).length) return false;
for (let key in subTableFieldList) {
const res = subTableFieldList[key].length === new Set(subTableFieldList[key]).size;
hasSameFieldList.push(res);
}
return hasSameFieldList.some((x) => !x);
};
//主表中的所有字段
const mainTableFieldList: string[] = [];
getMainComponent(formJson.list).forEach((item) => {
if (rangeComponents.includes(item.type)) {
mainTableFieldList.push(item.bindStartTime);
mainTableFieldList.push(item.bindEndTime);
} else {
mainTableFieldList.push(item.bindField);
}
});
//界面优先、简易模板 判断子表里面是否有组件
const hasSubFormComponents = generatorConfig?.tableStructureConfigs
?.filter((x) => !x.isMain)
.every((x) => !!x.tableFieldConfigs.length);
if (!getMainComponent(formJson.list).length && designType === 'data') {
notification.error({
message: t('提示'),
description: t('表单设计未绑定一个主表字段,最少得包含一个主表字段!'),
}); //提示消息
return false;
}
if (
generatorConfig?.tableStructureConfigs &&
!generatorConfig?.tableStructureConfigs[0]?.isMain &&
designType !== 'data'
) {
notification.error({
message: t('提示'),
description: t('表单设计未添加生成主表字段的组件,请先添加后再进行下一步。'),
}); //提示消息
return false;
}
//判断是否多表 但是没有子表单组件
if (
(designType === 'data' &&
tableConfigs!.length > 1 &&
sumSubFormComponent(formJson.list) !== tableConfigs!.length - 1) ||
(designType !== 'data' && !hasSubFormComponents)
) {
notification.error({
message: t('提示'),
description: t('有子表未绑定组件!'),
}); //提示消息
return false;
}
if (designType === 'data' && mainTableFieldList.length > new Set(mainTableFieldList).size) {
notification.error({
message: t('提示'),
description: t('主表中有组件绑定相同字段!'),
}); //提示消息
return false;
}
if (designType === 'data' && subTableHasSameField()) {
notification.error({
message: t('提示'),
description: t('子表中有组件绑定相同字段!'),
}); //提示消息
return false;
}
//判断隐藏组件是否填写完整
if (formJson.hiddenComponent?.length) {
const isCompelete = formJson.hiddenComponent.every((com) => {
return Object.values(com).every((val) => val !== '');
});
if (!isCompelete) {
notification.error({
message: t('提示'),
description: t('隐藏组件需填写完整'),
}); //提示消息
return false;
}
}
//一个个组件遍历
const message = validateComponent(formJson.list);
if (message) {
notification.error({
message: t('提示'),
description: message,
}); //提示消息
return false;
}
return true;
};
const validateComponent = (list: ComponentOptionModel[]) => {
for (const component of list) {
//布局组件需要递归子集
if (['card', 'tab', 'form', 'grid', 'one-for-one'].includes(component.type)) {
//如果是子表单 默认取 children中的组件 其他的 都是取layout中的组件
if (component.type === 'form' || component.type === 'one-for-one') {
if (!component.bindTable) {
return t(`{name}(子表单)未绑定表`, { name: component.label });
}
if (!component.children || component.children.length === 0) {
return t(`{name}(子表单)的子组件不能为空`, { name: component.label });
}
//如果子组件有错误 直接返回顶级
const errorMsg = validateComponent(component.children);
if (errorMsg) {
return errorMsg;
}
} else {
if (!component.layout || component.layout.length === 0) {
return t(`{name}组件布局不能为空`, { name: component.label });
}
for (const item of component.layout) {
const errorMsg = validateComponent(item.list);
if (errorMsg) {
return errorMsg;
}
}
}
}
if (component.type === 'table-layout') {
for (const item of component.layout!) {
for (const list of item.list) {
if (list.children && list.children.length > 0) {
const errorMsg = validateComponent(list.children);
if (errorMsg) {
return errorMsg;
}
}
}
}
}
if (
noHaveTableAndField.includes(component.type) ||
component.type === 'form' ||
component.type === 'one-for-one'
) {
//如果是不需要绑定字段的 默认跳过验证
continue;
}
//如果是时间区间组件 必须要绑定2个字段 一个开始时间 一个结束时间
if (component.type === 'range') {
if (!component.bindStartTime) {
return t(`{name}未绑定开始时间字段`, { name: component.label });
}
if (!component.bindEndTime) {
return t(`{name}未绑定结束时间字段`, { name: component.label });
}
}
//级联组件 必须选择级联配置
if (component.type === 'cascader' && !component.options!.apiConfig.apiId) {
return t(`{name}未选择级联配置`, { name: component.label });
}
//如果是远程组件并且是 数据源 或者 数据字典 方式
if (
remoteComponents.includes(component.type) &&
component.options!.datasourceType !== 'staticData'
) {
if (component.options!.datasourceType === 'dic') {
if (!component.options!.itemId) {
return t(`{name}未选择数据字典`, { name: component.label });
}
if (!component.options!.dicOptions?.length && component.type === 'associate-popup') {
return t(`{name}未进行联想配置`, { name: component.label });
}
if (!component.options!.dicOptions?.length && component.type === 'multiple-popup') {
return t(`{name}未进行显示配置`, { name: component.label });
}
}
if (component.options!.datasourceType === 'api') {
if (!component.options!.apiConfig.apiId) {
return t(`{name}未选择API`, { name: component.label });
}
if (!component.options!.apiConfig.outputParams && component.type === 'associate-popup') {
return t(`{name}未进行联想配置`, { name: component.label });
}
if (!component.options!.apiConfig.outputParams && component.type === 'multiple-popup') {
return t(`{name}未进行显示配置`, { name: component.label });
}
}
if (component.options!.datasourceType === 'datasource') {
if (!component.options!.sourceId) {
return t(`{name}未选择数据源`, { name: component.label });
}
if (!component.options!.labelField) {
return t(`{name}未选择数据源显示字段`, { name: component.label });
}
if (!component.options!.valueField) {
return t(`{name}未选择数据源保存字段`, { name: component.label });
}
}
}
// TODO 这里继续写各组件自己特有的一些验证
if (
(component.type == 'input' && !component.options!.isSave && !component.bindTable) ||
(component.type !== 'input' && !component.bindTable)
) {
return t(`{name}未绑定表`, { name: component.label });
}
if (!component.bindField && !component.type.includes('range')) {
return t(`{name}未绑定字段`, { name: component.label });
}
if (
component.type.includes('range') &&
(!component.bindStartTime || !component.bindEndTime)
) {
return t(`{name}未绑定开始字段或结束字段`, { name: component.label });
}
if (component.type === 'auto-code' && !component.options!.autoCodeRule) {
return t(`{name}未选择编码规则`, { name: component.label });
}
}
return '';
};
//遍历所有组件 是否有子表单
const sumSubFormComponent = (list: ComponentOptionModel[]): number => {
let totalSubForm = 0;
for (const component of list) {
if (
(component.type === 'form' || component.type === 'one-for-one') &&
!!component.bindTable &&
component.children?.length
) {
totalSubForm++;
}
//布局组件需要递归子集
if (['card', 'tab', 'grid'].includes(component.type)) {
//如果是子表单 默认取 children中的组件 其他的 都是取layout中的组件
if (!component.layout || component.layout.length === 0) {
continue;
}
for (const item of component.layout) {
totalSubForm += sumSubFormComponent(item.list);
}
}
if (component.type === 'table-layout') {
for (const item of component.layout!) {
for (const list of item.list!) {
if (list.children && list.children.length > 0) {
totalSubForm += sumSubFormComponent(list.children);
}
}
}
}
}
return totalSubForm;
};
defineExpose({ validateStep });
</script>

View File

@ -0,0 +1,263 @@
<template>
<div style="height: 100%">
<div class="event-box">
<div>
<EventArea
:columnList="generatorConfig!.formEventConfig[0]"
:index="0"
@delete-event="deleteEvent"
@click-node="clickNode"
/>
<AddEvent :lineHeight="`${lineHeight[0]}px`" @click="addEvent(0)" />
</div>
<div class="box-second">
<EventArea
:columnList="generatorConfig!.formEventConfig[1]"
:index="1"
@delete-event="deleteEvent"
@click-node="clickNode"
/>
<AddEvent :lineHeight="`${lineHeight[1]}px`" @click="addEvent(1)" />
</div>
<div class="box-third">
<EventArea
:columnList="generatorConfig!.formEventConfig[2]"
:index="2"
@delete-event="deleteEvent"
@click-node="clickNode"
/>
<AddEvent :lineHeight="`${lineHeight[2]}px`" @click="addEvent(2)" />
</div>
<div class="box-fourth">
<EventArea
:columnList="generatorConfig!.formEventConfig[3]"
:index="3"
@delete-event="deleteEvent"
@click-node="clickNode"
/>
<AddEvent :isLast="true" @click="addEvent(3)" />
<EventArea
:columnList="generatorConfig!.formEventConfig[4]"
:index="4"
@click-node="clickNode"
/>
</div>
</div>
<NodeEvent v-model:columnList="generatorConfig!.formEventConfig" :selectedNode="selectedNode" />
</div>
</template>
<script lang="ts" setup>
import { ref, Ref, reactive, inject, watch } from 'vue';
import EventArea from './components/EventArea.vue';
import NodeEvent from './components/NodeEvent.vue';
import AddEvent from './components/AddEvent.vue';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import {
FormEventColumnConfig,
FormEventStyleConfig,
selectedNodeConfig,
} from '/@/model/generator/formEventConfig';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import { changeCompsApiConfig, changeEventApiConfig, getMainTable } from '/@/utils/event/design';
import { cloneDeep } from 'lodash-es';
const { t } = useI18n();
const { notification } = useMessage();
const current = inject<Ref<number>>('current');
const isCustomForm = inject<boolean>('isCustomForm', false);
const designType = inject<string>('designType');
const widgetForm = inject<any>('widgetForm');
const fromMobile = inject<Boolean>('fromMobile', false);
const generatorConfig = inject<GeneratorConfig>('generatorConfig');
let lineHeight = reactive<number[]>([]);
let column = reactive<FormEventColumnConfig>({
0: [
{
type: 'circle',
color: '#2774ff',
text: t('开始节点'),
icon: '#icon-kaishi',
bgcColor: '#D8E5FF',
isUserDefined: false,
},
{
color: '#F6AB01',
icon: '#icon-chushihua',
text: t('初始化表单'),
bgcColor: '#f9f5ea',
isUserDefined: false,
nodeInfo: {
processEvent: [],
},
},
],
1: [
{
color: '#B36EDB',
icon: '#icon-shujufenxi',
text: t('获取表单数据'),
detail: t('(新增无此操作)'),
bgcColor: '#F8F2FC',
isUserDefined: false,
nodeInfo: {
processEvent: [],
},
},
],
2: [
{
color: '#F8625C',
icon: '#icon-jiazai',
text: t('加载表单'),
bgcColor: '#FFF1F1',
isUserDefined: false,
nodeInfo: {
processEvent: [],
},
},
],
3: [
{
color: '#6C6AE0',
icon: '#icon-jsontijiao',
text: t('提交表单'),
bgcColor: '#F5F4FF',
isUserDefined: false,
nodeInfo: {
processEvent: [],
},
},
],
4: [
{
type: 'circle',
color: '#F8625C',
text: t('结束节点'),
icon: '#icon-jieshuzhiliao',
bgcColor: '#FFD6D6',
isLast: true,
isUserDefined: false,
},
],
});
watch(
() => current?.value,
(val) => {
//自定义表单当前步骤为2 或 代码生成器 数据优先当前步骤为2 或 代码生成器 界面优先、简易模板 当前步骤为1
if (
(isCustomForm && val === 2) ||
(!isCustomForm &&
((designType === 'data' && val === 2) || (designType !== 'data' && val === 1)))
) {
if (
((isCustomForm && designType == 'code') || (!isCustomForm && designType !== 'data')) &&
generatorConfig!.tableStructureConfigs
) {
let tableFieldConfigs = getMainTable(generatorConfig!.tableStructureConfigs);
changeCompsApiConfig(generatorConfig!.formJson.list, designType, tableFieldConfigs);
changeEventApiConfig(generatorConfig!.formEventConfig, designType, tableFieldConfigs);
widgetForm.value.list = cloneDeep(generatorConfig!.formJson.list);
}
if (
generatorConfig?.formEventConfig &&
Object.keys(generatorConfig!.formEventConfig)?.length
) {
column = generatorConfig!.formEventConfig!;
lineHeight[0] = 135 + (column[0].length - 2) * 158;
lineHeight[1] = 50 + (column[1].length - 1) * 158;
lineHeight[2] = 50 + (column[2].length - 1) * 158;
} else {
generatorConfig!.formEventConfig = column;
lineHeight[0] = 135;
lineHeight[1] = 50;
lineHeight[2] = 50;
}
}
},
);
const selectedNode = ref<selectedNodeConfig>();
const addEvent = (index) => {
if (fromMobile) return;
const columnInfo: FormEventStyleConfig = {
color: '#2774FF',
icon: '#icon-yonghu-xianxing',
text: t('用户自定义节点'),
bgcColor: '#F5F8FA',
isUserDefined: true,
nodeInfo: {
processEvent: [],
},
};
column[index].push(columnInfo);
if (index === 3) return;
lineHeight[index] += 158;
};
const deleteEvent = ({ index, columnIndex }) => {
if (fromMobile) return;
column[columnIndex].splice(index, 1);
lineHeight[columnIndex] -= 158;
};
const clickNode = (selected) => {
for (let key in column) {
column[key].map((item) => {
item.isClick = false;
});
}
column[selected.columnIndex][selected.index]['isClick'] = true;
selectedNode.value = selected;
};
//验证当前步骤的数据
const validateStep = async (): Promise<boolean> => {
const hasNodeName = Object.values(generatorConfig!.formEventConfig).every((config) => {
return config.every((item) => {
return !!item.text;
});
});
if (!hasNodeName) {
notification.error({
message: t('提示'),
description: t('节点名称不能为空!'),
});
return false;
}
return true;
};
defineExpose({ validateStep });
</script>
<style lang="less" scoped>
.event-box {
position: relative;
padding: 10px 50px;
font-size: 13px;
width: v-bind("!!selectedNode ? 'calc(100% - 410px)' : '100%'");
height: 100%;
overflow: auto;
.box-second {
position: absolute;
top: 60px;
left: 310px;
}
.box-third {
position: absolute;
top: 120px;
left: 570px;
}
.box-fourth {
position: absolute;
top: 185px;
left: 830px;
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="step1-form">
<BasicForm @register="register" />
</div>
</template>
<script lang="ts" setup>
import { inject, onMounted } from 'vue';
import { FormSchema, useForm, BasicForm } from '/@/components/Form';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const generatorConfig = inject<GeneratorConfig>('generatorConfig') as GeneratorConfig;
const formSchema: FormSchema[] = [
{
field: 'code',
label: t('菜单编号'),
required: true,
title: t('菜单信息'),
component: 'Input',
componentProps: {
placeholder: t('请输入菜单编号'),
},
colProps: { span: 12 },
},
{
field: 'name',
label: t('菜单名称'),
required: true,
component: 'Input',
componentProps: {
placeholder: t('请输入菜单名称'),
},
colProps: { span: 12 },
},
{
field: 'parentId',
label: t('上级菜单'),
component: 'MenuSelect',
required: process.env.NODE_ENV === 'production',
componentProps: {
placeholder: t('请选择上级菜单'),
},
colProps: { span: 12 },
},
{
field: 'sortCode',
label: t('排序'),
required: true,
component: 'InputNumber',
componentProps: {
placeholder: t('请输入排序号'),
min: 0,
},
colProps: { span: 12 },
},
{
field: 'icon',
label: t('图标'),
required: true,
component: 'IconPicker',
componentProps: {
placeholder: t('请选择图标'),
},
colProps: { span: 12 },
},
{
field: 'remark',
label: t('备注'),
component: 'InputTextArea',
componentProps: {
placeholder: t('请填写备注'),
},
colProps: { span: 24 },
},
];
const [register, { validate, getFieldsValue, setFieldsValue }] = useForm({
labelWidth: 100,
schemas: formSchema,
showActionButtonGroup: false,
});
onMounted(() => {
setFieldsValue(generatorConfig.menuConfig);
});
//验证当前步骤的数据
const validateStep = async (): Promise<boolean> => {
try {
const formData = await validate();
setData(formData);
} catch (error) {
return false;
}
return true;
};
function getFormData() {
const formData = getFieldsValue();
setData(formData);
}
function setData(formData) {
generatorConfig.menuConfig!.code = formData.code;
generatorConfig.menuConfig!.name = formData.name;
generatorConfig.menuConfig!.parentId = formData.parentId;
generatorConfig.menuConfig!.remark = formData.remark;
generatorConfig.menuConfig!.sortCode = formData.sortCode;
generatorConfig.menuConfig!.icon = formData.icon;
}
defineExpose({ validateStep, getFormData });
</script>
<style lang="less" scoped>
:deep(.ant-input-number) {
width: 100%;
}
</style>

View File

@ -0,0 +1,794 @@
<template>
<div class="step1">
<div class="step1-form" v-if="!isCustomForm">
<BasicForm @register="register" />
</div>
<Divider v-if="designType === 'code' && !isFormGenerator && !isCustomForm" />
<template v-if="designType === 'code' && !isFormGenerator">
<div v-for="(tableConfig, index) in generatorConfig?.tableStructureConfigs" :key="index">
<a-table
:columns="tableColumns"
:data-source="[tableConfig]"
:pagination="false"
:defaultExpandAllRows="true"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'tableName'">
<a-input
v-model:value="record.tableName"
:placeholder="t('请填写数据表名称')"
@change="changeComponentName(record, 'table')"
@blur="handleBlur(record)"
/>
</template>
<template v-else-if="column.dataIndex === 'isMain'">
<span>
<a-tag :color="record.isMain ? 'blue' : 'orange'">
{{ record.isMain ? t('主表') : t('附表') }}
</a-tag>
</span>
</template>
<template v-else-if="column.dataIndex === 'tableComment'">
<a-input v-model:value="record.tableComment" :placeholder="t('请填写备注')" />
</template>
</template>
<template #expandedRowRender>
<a-table
:columns="fieldColumns"
:data-source="tableConfig.tableFieldConfigs"
:pagination="false"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'fieldName'">
<a-input
v-model:value="record.fieldName"
:placeholder="t(`请填写{title}`, { title: column.title })"
@change="changeComponentName(record, 'field')"
:disabled="record.key === 'rule_user_id'"
/>
</template>
<template v-else-if="column.dataIndex === 'fieldLength'">
<a-input
v-model:value.number="record.fieldLength"
:placeholder="t('请填写字段长度')"
:disabled="record.fieldType !== 0"
/>
</template>
<template v-else-if="column.dataIndex === 'fieldComment'">
<a-input
v-model:value.number="record.fieldComment"
:placeholder="t('请填写备注')"
:disabled="record.key === 'rule_user_id'"
/>
</template>
<template v-else-if="column.dataIndex === 'fieldType'">
<a-select
v-model:value="record.fieldType"
style="width: 100%"
:placeholder="t('请选择数据格式')"
:disabled="record.key === 'rule_user_id'"
allowClear
@change="(value) => handleTypeChange(value, record)"
>
<a-select-option
:value="type.value"
v-for="(type, idx) in fieldTypeList"
:key="idx"
>
{{ type.label }}
</a-select-option>
</a-select>
</template>
</template>
</a-table>
</template>
</a-table>
</div>
</template>
<TableNameModal @register="registerTableName" @success="handleEditSuccess" />
</div>
</template>
<script lang="ts" setup>
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
import { Divider } from 'ant-design-vue';
import { TableNameModal } from '/@/components/CreateCodeStep';
import { onMounted, inject, computed, ref, Ref } from 'vue';
import { debounce, cloneDeep, random, snakeCase } from 'lodash-es';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { useMessage } from '/@/hooks/web/useMessage';
import { validateTableName } from '/@/api/system/generator';
import { getAuthList } from '/@/api/system/authorize';
import { useI18n } from '/@/hooks/web/useI18n';
import { useModal } from '/@/components/Modal';
const { t } = useI18n();
const [registerTableName, { openModal, closeModal }] = useModal();
const validateTable = ref(false);
const emit = defineEmits(['validateTable']);
const { notification } = useMessage();
const dataAuthPlaceholder = computed(() => {
return generatorConfig!.outputConfig!.isDataAuth
? t('请选择已有通用数据权限')
: t('请先启用数据权限');
});
const dataAuthHelpMessage = `
1.启用数据权限会判断主表是否包含RuleUserlD字段如果存在则不进行表结构修改如果不存在则会对主表进行字段添加。
2.RuleUserlD主要用来控制每一条记录的权限所属人新增时默认将当前登录认作为权限所属人。
3.在表单设计中会添加“批量设置权限所属人”功能启用后,拥有该按钮权限的人员可以设置每一条记录的权限所属人。
`;
const formSchemaCode: FormSchema[] = [
{
field: 'className',
label: t('功能名称'),
required: true,
component: 'Input',
title: t('基本信息'),
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能名称'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.className = val.target.value;
const { outputConfig, tableStructureConfigs } = generatorConfig!;
tableStructureConfigs?.map((item) => {
const name = snakeCase(outputConfig!.className!);
const tableName = isFieldUpper.value ? name.toUpperCase() : name;
item.tableName = item.isMain
? tableName
: isFieldUpper.value
? `${tableName}_CHILD_${random(1000, 9999)}`
: `${tableName}_child_${random(1000, 9999)}`;
changeComponentName(item, 'table');
});
}, 200),
},
},
{
field: 'comment',
label: t('功能描述'),
required: true,
component: 'Input',
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能描述'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.comment = val.target.value;
}, 200),
},
},
{
field: 'outputArea',
label: t('功能模块'),
component: 'DicSelect',
required: true,
componentProps: {
placeholder: t('请选择功能模块'),
itemId: '1419276800524423333',
onChange: debounce((_, obj) => {
if (obj) {
generatorConfig!.outputConfig!.outputArea = obj.id;
generatorConfig!.outputConfig!.outputValue = obj.value;
}
}, 200),
},
colProps: { span: 12 },
},
{
field: 'remarks',
label: t('备注'),
component: 'Input',
required: false,
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写备注'),
onChange: debounce((val) => {
generatorConfig!.outputConfig!.remarks = val.target.value;
generatorConfig!.tableStructureConfigs?.map(
(item) => (item.tableComment = item.isMain ? val.target.value : item.tableComment),
);
}, 200),
},
},
{
field: 'isDataAuth',
label: t('数据权限'),
component: 'Switch',
required: false,
colProps: { span: 12 },
helpMessage: dataAuthHelpMessage,
helpComponentProps: { maxWidth: '400px' },
componentProps: {
checkedValue: true,
unCheckedValue: false,
onChange: (val) => {
if (!val) {
setFieldsValue({ dataAuthList: [] });
generatorConfig!.outputConfig!.dataAuthList = [];
if (generatorConfig?.tableStructureConfigs?.length) {
generatorConfig.tableStructureConfigs[0].tableFieldConfigs =
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.filter(
(x) => x.key !== 'rule_user_id',
);
}
} else {
const hasRuleUserField =
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.find(
(x) => x.key === 'rule_user_id',
);
if (hasRuleUserField) return;
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.push({
key: 'rule_user_id',
fieldName: 'rule_user_id',
fieldLength: 500,
fieldType: 7,
fieldComment: t('数据权限所属人ID'),
});
}
generatorConfig!.outputConfig!.isDataAuth = val;
},
},
},
{
field: 'dataAuthList',
label: t('权限选择'),
component: 'ApiSelect',
required: false,
colProps: { span: 12 },
componentProps: {
mode: 'multiple',
placeholder: dataAuthPlaceholder,
api: getAuthList,
labelField: 'name',
valueField: 'id',
getPopupContainer: () => document.body,
onChange: (val) => {
generatorConfig!.outputConfig!.dataAuthList = val;
},
},
dynamicDisabled: ({ values }) => {
return !values.isDataAuth;
},
},
];
const formSchemaTemplate: FormSchema[] = [
{
field: 'className',
label: t('功能名称'),
required: true,
component: 'Input',
title: t('基本信息'),
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能名称'),
onChange: (val: ChangeEvent) => {
generatorConfig!.outputConfig!.className = val.target.value;
const { outputConfig, tableStructureConfigs } = generatorConfig!;
tableStructureConfigs?.map((item) => {
const name = snakeCase(outputConfig!.className!);
const tableName = isFieldUpper.value ? name.toUpperCase() : name;
item.tableName = item.isMain
? tableName
: isFieldUpper.value
? `${tableName}_CHILD_${random(1000, 9999)}`
: `${tableName}_child_${random(1000, 9999)}`;
changeComponentName(item, 'table');
});
},
},
},
{
field: 'comment',
label: t('功能描述'),
required: true,
component: 'Input',
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能描述'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.comment = val.target.value;
}, 200),
},
},
{
field: 'outputArea',
label: t('功能模块'),
component: 'DicSelect',
required: true,
componentProps: {
placeholder: t('请选择功能模块'),
itemId: '1419276800524423333',
onChange: debounce((_, obj) => {
if (obj) {
generatorConfig!.outputConfig!.outputArea = obj.id;
generatorConfig!.outputConfig!.outputValue = obj.value;
}
}, 200),
},
colProps: { span: 12 },
},
{
field: 'remarks',
label: t('备注'),
component: 'Input',
required: false,
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写备注'),
onChange: debounce((val) => {
generatorConfig!.outputConfig!.remarks = val.target.value;
generatorConfig!.tableStructureConfigs?.map(
(item) => (item.tableComment = item.isMain ? val.target.value : item.tableComment),
);
}, 200),
},
},
{
field: 'isDataAuth',
label: t('数据权限'),
component: 'Switch',
required: false,
colProps: { span: 12 },
helpMessage: dataAuthHelpMessage,
helpComponentProps: { maxWidth: '400px' },
componentProps: {
checkedValue: true,
unCheckedValue: false,
onChange: (val) => {
if (!val) {
setFieldsValue({ dataAuthList: [] });
generatorConfig!.outputConfig!.dataAuthList = [];
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.map(
(x) => x.key !== 'rule_user_id',
);
} else {
const hasRuleUserField =
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.find(
(x) => x.key === 'rule_user_id',
);
if (hasRuleUserField) return;
generatorConfig?.tableStructureConfigs?.length &&
generatorConfig?.tableStructureConfigs[0].tableFieldConfigs.push({
key: 'rule_user_id',
fieldName: 'rule_user_id',
fieldLength: 500,
fieldType: 7,
fieldComment: t('数据权限所属人ID'),
});
}
generatorConfig!.outputConfig!.isDataAuth = val;
},
},
},
{
field: 'dataAuthList',
label: t('权限选择'),
component: 'ApiSelect',
required: false,
colProps: { span: 12 },
componentProps: {
mode: 'multiple',
placeholder: dataAuthPlaceholder,
api: getAuthList,
labelField: 'name',
valueField: 'id',
getPopupContainer: () => document.body,
onChange: (val) => {
generatorConfig!.outputConfig!.dataAuthList = val;
},
},
dynamicDisabled: ({ values }) => {
return !values.isDataAuth;
},
},
];
const generatorSchema: FormSchema[] = [
{
field: 'className',
label: t('功能名称'),
required: true,
component: 'Input',
title: t('基本信息'),
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能名称'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.className = val.target.value;
}, 200),
},
},
{
field: 'comment',
label: t('功能描述'),
required: true,
component: 'Input',
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能描述'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.comment = val.target.value;
}, 200),
},
},
{
field: 'outputArea',
label: t('功能模块'),
component: 'DicSelect',
required: true,
componentProps: {
placeholder: t('请选择功能模块'),
itemId: '1419276800524423333',
onChange: debounce((_, obj) => {
if (obj) {
generatorConfig!.outputConfig!.outputArea = obj.id;
generatorConfig!.outputConfig!.outputValue = obj.value;
}
}, 200),
},
colProps: { span: 12 },
},
{
field: 'remarks',
label: t('备注'),
component: 'Input',
required: false,
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写备注'),
onChange: debounce((val) => {
generatorConfig!.outputConfig!.remarks = val.target.value;
}, 200),
},
},
];
const tableColumns = [
{
title: t('数据表名称'),
dataIndex: 'tableName',
},
{
title: t('数据表类别'),
dataIndex: 'isMain',
align: 'center',
width: 200,
},
{
title: t('备注'),
dataIndex: 'tableComment',
align: 'center',
},
];
const fieldColumns = [
{
title: t('字段名'),
dataIndex: 'fieldName',
width: 500,
},
{
title: t('字段长度'),
dataIndex: 'fieldLength',
align: 'center',
},
{
title: t('数据格式'),
dataIndex: 'fieldType',
align: 'center',
},
{
title: t('备注'),
dataIndex: 'fieldComment',
},
];
const fieldTypeList = [
{
label: t('短文本'),
value: 0,
},
{
label: t('长文本(适用于多行文本等组件)'),
value: 1,
},
{
label: t('整数'),
value: 2,
},
{
label: t('小数'),
value: 3,
},
{
label: t('日期'),
value: 4,
},
{
label: t('日期时间'),
value: 5,
},
{
label: t('外键'),
value: 6,
},
{
label: t('长整数'),
value: 7,
},
{
label: t('时间'),
value: 8,
},
];
const generatorConfig = inject<GeneratorConfig>('generatorConfig');
const widgetForm = inject<any>('widgetForm');
let mainTableName = inject<any>('mainTableName', '');
const designType = inject<string>('designType', '');
const isCustomForm = inject<boolean>('isCustomForm', false);
const isFieldUpper = inject<Ref<boolean>>('isFieldUpper', ref(false));
const props = defineProps({
//是否是自定义表单生成代码
isFormGenerator: {
type: Boolean,
default: false,
},
//是否是编辑状态
isUpdate: {
type: Boolean,
default: false,
},
//新增时的表名集合
beforeTableNames: {
type: Array,
default: () => [],
},
});
const [register, { validate, setFieldsValue, clearValidate }] = useForm({
labelWidth: 100,
schemas: props.isFormGenerator
? generatorSchema
: designType === 'code'
? formSchemaCode
: formSchemaTemplate,
showActionButtonGroup: false,
});
onMounted(() => {
const { outputConfig } = generatorConfig!;
if (
!isCustomForm &&
(outputConfig?.className || outputConfig?.comment || outputConfig?.outputArea)
) {
setFieldsValue({
className: outputConfig?.className,
comment: outputConfig?.comment,
outputArea: outputConfig?.outputArea,
isDataAuth: outputConfig?.isDataAuth,
dataAuthList: outputConfig?.dataAuthList,
});
clearValidate();
}
});
const changeComponentInfo = (list, type, fieldKey, name, tableKey, rangField?) => {
const isTable = type === 'table';
if (generatorConfig?.formJson.hiddenComponent?.length) {
generatorConfig?.formJson.hiddenComponent?.map((component: any) => {
if (component.key === fieldKey) {
isTable ? (component.bindTable = name) : (component.bindField = name);
}
});
widgetForm!.value.hiddenComponent = cloneDeep(generatorConfig?.formJson.hiddenComponent);
}
list?.map((component: any) => {
if (component.type === 'form' || component.type === 'one-for-one') {
if (isTable && component.key === tableKey) {
component.bindTable = name;
}
if (component.children.length) {
component.children.map((subComponent: any) => {
if (['tab', 'grid', 'card'].includes(subComponent.type)) {
subComponent.bindTable = name;
for (const subChild of subComponent.layout!) {
changeComponentInfo(subChild.list, type, fieldKey, name, tableKey, rangField);
}
} else if (subComponent.key === fieldKey) {
isTable ? (subComponent.bindTable = name) : (subComponent.bindField = name);
}
});
}
} else if (component.key === fieldKey) {
//修改主表名
mainTableName!.value = name;
if (['time-range', 'date-range'].includes(component.type)) {
isTable ? (component.bindTable = name) : (component[rangField] = name);
} else {
isTable ? (component.bindTable = name) : (component.bindField = name);
}
} else if (['tab', 'grid', 'card'].includes(component.type)) {
for (const child of component.layout!) {
changeComponentInfo(child.list, type, fieldKey, name, tableKey, rangField);
}
} else if (component.type == 'table-layout') {
for (const child of component.layout!) {
for (const el of child.list!) {
changeComponentInfo(el.children, type, fieldKey, name, tableKey, rangField);
}
}
}
});
widgetForm!.value.list = cloneDeep(generatorConfig!.formJson.list); //表单设计页面绑定表和字段需要同步修改
};
const changeComponentName = (record, type) => {
const rangField = record.fieldStartName
? 'bindStartTime'
: record.fieldEndName
? 'bindEndTime'
: '';
if (type === 'table') {
const name = snakeCase(record.tableName);
record.tableFieldConfigs.forEach((item) => {
changeComponentInfo(
generatorConfig?.formJson.list,
'table',
item.key,
isFieldUpper.value ? name.toUpperCase() : name,
record.key,
);
});
} else {
changeComponentInfo(
generatorConfig?.formJson.list,
'field',
record.key,
record.fieldName,
'',
rangField,
);
}
};
const handleBlur = (record) => {
const name = snakeCase(record.tableName);
record.tableName = isFieldUpper.value ? name.toUpperCase() : name;
};
const handleTypeChange = (value, record) => {
if (value !== 0) {
record.fieldLength = null;
} else {
record.fieldLength = 500;
}
};
//验证当前步骤的数据
const validateStep = async (): Promise<boolean> => {
try {
const { tableStructureConfigs, outputConfig } = generatorConfig!;
if (!isCustomForm) {
await validate();
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(outputConfig!.className!)) {
notification.error({
message: t('提示'),
description: t('功能名称只能是数字和字母组成,必须以英文字母开头'),
});
return false;
}
}
if (props.isFormGenerator) return true;
const tableNames: string[] = tableStructureConfigs!.map((x) => x.tableName);
const reg = /^[a-zA-Z0-9_]*$/;
//判断表名是否符合要求
const isTableNotSuccess = tableStructureConfigs?.some((config: any) => {
return !reg.test(config.tableName);
});
if (isTableNotSuccess) {
notification.error({
message: t('提示'),
description: '表名只能包括字母、数字、下划线',
}); //提示消息
return false;
}
//判断字段名是否符合要求
const isFieldNotSuccess = tableStructureConfigs?.some((config: any) => {
const isNotPassValidation = config.tableFieldConfigs.some((fieldConfig: any) => {
return !reg.test(fieldConfig.fieldName) || fieldConfig.fieldName.length > 30;
});
return isNotPassValidation;
});
if (isFieldNotSuccess) {
notification.error({
message: t('提示'),
description:
'字段名必须以小写字母开头只能包括小写字母、数字、下划线并且不能超过30个字符',
}); //提示消息
return false;
}
const notHasRepeatName = tableStructureConfigs?.every((config) => {
const nameArr = config.tableFieldConfigs.map((x) => x.fieldName);
return [...new Set(nameArr)].length === nameArr.length;
});
if (!notHasRepeatName) {
notification.error({
message: t('提示'),
description: t('同一个表内,不能有相同字段名'),
}); //提示消息
return false;
}
const testTableNames = tableNames.filter((x) => {
return !props.beforeTableNames.includes(x);
});
const params = {
id: designType === 'code' ? generatorConfig!.databaseId! : 'master',
tableNames: props.isUpdate ? testTableNames.toString() : tableNames.toString(),
};
if (!props.isUpdate || (!props.isUpdate && testTableNames.length)) {
await validateTableName(params);
}
if (props.isUpdate) {
if (!validateTable.value) {
openModal();
return false;
}
}
} catch (error) {
return false;
}
validateTable.value = false;
return true;
};
async function handleEditSuccess() {
validateTable.value = true;
closeModal();
emit('validateTable');
}
defineExpose({ validateStep });
</script>
<style lang="less" scoped>
.step1 {
h3 {
margin: 0 0 12px;
font-size: 16px;
line-height: 32px;
color: @text-color;
}
h4 {
margin: 0 0 4px;
font-size: 14px;
line-height: 22px;
color: @text-color;
}
p {
color: @text-color;
}
}
.pay-select {
width: 20%;
}
.pay-input {
width: 70%;
}
:deep(.ant-tag-orange),
:deep(.ant-tag-blue) {
background: #fff;
padding: 3px 12px;
font-size: 13px;
}
:deep(.ant-spin-nested-loading) {
overflow: hidden; //去掉表格嵌套子表滚动条
}
</style>

View File

@ -0,0 +1,542 @@
<template>
<div class="step1">
<div class="step1-form">
<BasicForm @register="register" />
</div>
<Divider />
<p>{{ t('添加数据库表(请先选择数据库-第一个选择的为主库)') }}</p>
<div>
<a-table :columns="columns" :data-source="generatorConfig!.tableConfigs" :pagination="false">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'order'">
<span>
{{ index + 1 }}
</span>
</template>
<template v-if="column.key === 'isMain'">
<span>
<a-tag :color="record.isMain ? 'blue' : 'orange'">
{{ record.isMain ? t('主表') : t('附表') }}
</a-tag>
</span>
</template>
<template v-else-if="column.key === 'relationField'">
<template v-if="index > 0">
<Select
style="width: 200px"
v-model:value="record[column.key]"
:placeholder="t('请选择附表关联主表字段')"
show-search
>
<SelectOption
v-for="(name, idx) in selectOptions[record.tableName]"
:key="idx"
:value="name"
>
{{ name }}
</SelectOption>
</Select>
</template>
</template>
<template v-else-if="column.key === 'relationTableField'">
<template v-if="index > 0">
<Select
style="width: 200px"
v-model:value="record[column.key]"
:placeholder="t('请选择主表字段')"
show-search
>
<SelectOption
v-for="(name, idx) in selectOptions[mainTableName]"
:key="idx"
:value="name"
>
{{ name }}
</SelectOption>
</Select>
</template>
</template>
<template v-else-if="column.key === 'action'">
<DeleteTwoTone two-tone-color="#ff8080" @click="remove(index)" />
</template>
</template>
</a-table>
<a-button type="dashed" block @click="add">
<PlusOutlined />
{{ t('新增') }}
</a-button>
</div>
<SelectDatabase @register="registerModal" @success="handleSelectSuccess" />
</div>
</template>
<script lang="ts" setup>
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
import { getDatabaselinkMultiTableColumns } from '/@/api/system/databaselink';
import { getAuthList } from '/@/api/system/authorize';
import { Divider } from 'ant-design-vue';
import { PlusOutlined, DeleteTwoTone } from '@ant-design/icons-vue';
import { useModal } from '/@/components/Modal';
import SelectDatabase from './components/SelectDatabase.vue';
import { computed, inject, nextTick, onMounted, Ref, ref, toRaw } from 'vue';
import { Select } from 'ant-design-vue';
import { debounce, uniqBy } from 'lodash-es';
import { TableConfig } from '/@/model/generator/tableConfig';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { useMessage } from '/@/hooks/web/useMessage';
import { TableInfo, FieldInfo } from '/@/components/Designer';
import { JavaTypeConvertTsType } from '/@/utils/helper/designHelper';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const SelectOption = Select.Option;
const { notification } = useMessage();
const generatorConfig = inject<GeneratorConfig>('generatorConfig');
const curDataBase = ref();
const dataAuthPlaceholder = computed(() => {
return generatorConfig!.outputConfig!.isDataAuth
? t('请选择已有通用数据权限')
: t('请先启用数据权限');
});
const dataAuthHelpMessage = `
1.启用数据权限会判断主表是否包含RuleUserlD字段如果存在则不进行表结构修改如果不存在则会对主表进行字段添加。
2.RuleUserlD主要用来控制每一条记录的权限所属人新增时默认将当前登录认作为权限所属人。
3.在表单设计中会添加“批量设置权限所属人”功能启用后,拥有该按钮权限的人员可以设置每一条记录的权限所属人。
`;
const formSchema: FormSchema[] = [
{
field: 'className',
label: t('功能名称'),
required: true,
component: 'Input',
title: t('基本信息'),
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能名称'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.className = val.target.value;
}, 200),
},
},
{
field: 'comment',
label: t('功能描述'),
required: true,
component: 'Input',
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写功能描述'),
onChange: debounce((val: ChangeEvent) => {
generatorConfig!.outputConfig!.comment = val.target.value;
}, 200),
},
},
{
field: 'outputArea',
label: t('功能模块'),
component: 'DicSelect',
required: true,
componentProps: {
placeholder: t('请选择功能模块'),
itemId: '1419276800524423333',
onChange: debounce((_, obj) => {
if (obj) {
generatorConfig!.outputConfig!.outputArea = obj.id;
generatorConfig!.outputConfig!.outputValue = obj.value;
}
}, 200),
},
colProps: { span: 12 },
},
{
field: 'remarks',
label: t('备注'),
component: 'Input',
required: false,
colProps: { span: 12 },
componentProps: {
placeholder: t('请填写备注'),
onChange: debounce((val) => {
generatorConfig!.outputConfig!.remarks = val.target.value;
}, 200),
},
},
{
field: 'isDataAuth',
label: t('数据权限'),
component: 'Switch',
required: false,
colProps: { span: 12 },
helpMessage: dataAuthHelpMessage,
helpComponentProps: { maxWidth: '400px' },
componentProps: {
checkedValue: true,
unCheckedValue: false,
onChange: (val) => {
if (!val) {
setFieldsValue({ dataAuthList: [] });
generatorConfig!.outputConfig!.dataAuthList = [];
}
generatorConfig!.outputConfig!.isDataAuth = val;
},
},
},
{
field: 'dataAuthList',
label: t('权限选择'),
component: 'ApiSelect',
required: false,
colProps: { span: 12 },
componentProps: {
mode: 'multiple',
placeholder: dataAuthPlaceholder,
api: getAuthList,
labelField: 'name',
valueField: 'id',
getPopupContainer: () => document.body,
onChange: (val) => {
generatorConfig!.outputConfig!.dataAuthList = val;
},
},
dynamicDisabled: ({ values }) => {
return !values.isDataAuth;
},
},
{
field: 'databaseId',
label: t('数据库'),
component: 'DbSelect',
required: true,
title: t('数据库信息'),
colProps: { span: 24 },
componentProps: {
placeholder: t('请选择数据库'),
onChange: debounce((val) => {
generatorConfig!.databaseId = val;
if (curDataBase.value && curDataBase.value !== val) {
generatorConfig!.tableConfigs = [];
}
curDataBase.value = val;
}, 200),
},
},
];
const columns = [
{
title: t('序号'),
dataIndex: 'order',
key: 'order',
width: 80,
align: 'center',
},
{
title: t('数据表类别'),
dataIndex: 'isMain',
key: 'isMain',
width: 120,
},
{
title: t('数据表名称'),
dataIndex: 'tableName',
key: 'tableName',
},
{
title: t('关联字段'),
dataIndex: 'relationField',
key: 'relationField',
},
{
title: t('关联表字段'),
key: 'relationTableField',
dataIndex: 'relationTableField',
},
{
title: t('操作'),
key: 'action',
align: 'center',
},
];
const [registerModal, { openModal }] = useModal();
const [register, { validate, getFieldsValue, setFieldsValue, clearValidate }] = useForm({
labelWidth: 100,
schemas: formSchema,
showActionButtonGroup: false,
});
const selectOptions = ref({});
const selectTableName = computed(() =>
generatorConfig!.tableConfigs!.map((item) => item.tableName),
);
const mainTableName = computed(
() => generatorConfig!.tableConfigs!.find((item) => item.isMain)!.tableName,
);
defineEmits(['register']);
const tableInfo = inject<Ref<TableInfo[]>>('tableInfo', ref([]));
onMounted(() => {
const { tableConfigs, databaseId, outputConfig } = generatorConfig!;
setFieldsValue({
className: outputConfig?.className,
comment: outputConfig?.comment,
outputArea: outputConfig?.outputArea,
databaseId: databaseId,
isDataAuth: outputConfig?.isDataAuth || false,
dataAuthList: outputConfig?.dataAuthList || [],
});
if (tableConfigs && tableConfigs.length > 0) {
getDatabaselinkMultiTableColumns({
id: databaseId,
tableNames: selectTableName.value.join(','),
}).then((result) => {
for (const key in result) {
const columnInfo = result[key];
//如果已经写入过的表格 不再添加
if (!tableInfo?.value.find((x) => x.name === key)) {
const fields = columnInfo.map((field) => {
const filedInfo: FieldInfo = {
name: field.column,
length: field.dataLength,
type: JavaTypeConvertTsType(field.dataType),
isPk: field.primaryKey,
isNullable: field.nullable,
};
return filedInfo;
});
tableInfo?.value.push({
name: key,
isMain: generatorConfig!.tableConfigs!.find((x) => x.tableName === key)
?.isMain as boolean,
fields: fields,
});
}
selectOptions.value[key] = columnInfo.map((x) => x.column);
}
});
}
nextTick(() => {
clearValidate();
});
});
const add = async () => {
try {
const values = await validate();
openModal(true, { databaseId: values.databaseId, selectTableName: selectTableName.value });
} catch (error) {}
};
const handleSelectSuccess = (selectRows: TableConfig[]) => {
if (generatorConfig?.tableConfigs && generatorConfig?.tableConfigs.length === 0) {
tableInfo.value!.length = 0;
generatorConfig!.tableConfigs = [...toRaw(selectRows)];
} else {
generatorConfig!.tableConfigs = uniqBy(
generatorConfig!.tableConfigs!.concat([...selectRows]),
'tableName',
);
}
const formData = getFieldsValue();
getDatabaselinkMultiTableColumns({
id: formData.databaseId,
tableNames: selectTableName.value.join(','),
}).then((result) => {
for (const key in result) {
const columnInfo = result[key];
//如果已经写入过的表格 不再添加
if (!tableInfo?.value.find((x) => x.name === key)) {
const fields = columnInfo.map((field) => {
const filedInfo: FieldInfo = {
name: field.column,
length: field.dataLength,
type: JavaTypeConvertTsType(field.dataType),
isPk: field.primaryKey,
isNullable: field.nullable,
};
return filedInfo;
});
tableInfo?.value.push({
name: key,
isMain: generatorConfig!.tableConfigs!.find((x) => x.tableName === key)
?.isMain as boolean,
fields: fields,
});
const thisTableConfig = generatorConfig!.tableConfigs!.find((x) => x.tableName === key);
const col = columnInfo.find((x) => x.primaryKey);
thisTableConfig!.pkField = col.column;
thisTableConfig!.pkType = col.dataType;
}
selectOptions.value[key] = columnInfo.map((x) => x.column);
}
});
};
const remove = (index) => {
// TODO 这里删除 可能会引起 表单设计步骤所有的设计 出错。3个解决方案
//1 删除后提示用户 表单设计全部清空!
//2 删除之后根据所删除的表名 去表单设计Json匹配 所绑定的组件 全部将绑定表置空!
//3 表单设计后 不允许删除表配置!
//如果删除的数据是主表 并且还有其他表 默认顺序 找下一张表 将其设为主表
if (generatorConfig!.tableConfigs![index].isMain && generatorConfig!.tableConfigs!.length > 1) {
const nextObj = generatorConfig!.tableConfigs![index + 1];
nextObj.isMain = true;
nextObj.relationField = '';
nextObj.relationTableField = '';
const nextTable = tableInfo!.value![index + 1];
nextTable.isMain = true;
generatorConfig!.tableConfigs!.splice(index, 1);
tableInfo?.value.splice(index, 1);
} else {
generatorConfig!.tableConfigs!.splice(index, 1);
tableInfo?.value.splice(index, 1);
}
};
//验证当前步骤的数据
const validateStep = async (): Promise<boolean> => {
try {
await validate();
const { tableConfigs, outputConfig } = generatorConfig as GeneratorConfig;
//判断tableconfig 是否为空 或者 一条数据都没有
if (!tableConfigs || tableConfigs!.length === 0) {
notification.error({
message: t('提示'),
description: t('数据表配置不能为空!'),
}); //提示消息
return false;
}
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(outputConfig.className!)) {
notification.error({
message: t('提示'),
description: t('功能名称只能是数字和字母组成,必须以英文字母开头'),
}); //提示消息
return false;
}
for (const config of tableConfigs!) {
//如果是主表 可以不需要关联字段等
if (config.isMain) {
if (!config.tableName) {
notification.error({
message: t('提示'),
description: t('主表表名未能配置成功!'),
}); //提示消息
return false;
}
} else {
//子表需要验证关联字段 已经关联表 是否选择好
if (!config.tableName) {
notification.error({
message: t('提示'),
description: t('子表表名未能配置成功!'),
}); //提示消息
return false;
}
if (!config.relationField) {
notification.error({
message: t('提示'),
description: t(`{name} 表 关联字段未选中`, { name: config.tableName }),
}); //提示消息
return false;
}
if (!config.relationTableField) {
notification.error({
message: t('提示'),
description: t(`{name} 表 关联表字段未选中`, { name: config.tableName }),
}); //提示消息
return false;
}
}
}
} catch (error) {
return false;
}
return true;
};
function editFieldsValue() {
const { databaseId, outputConfig } = generatorConfig!;
setFieldsValue({
className: outputConfig?.className,
comment: outputConfig?.comment,
outputArea: outputConfig?.outputArea,
databaseId: databaseId,
isDataAuth: outputConfig?.isDataAuth || false,
dataAuthList: outputConfig?.dataAuthList || [],
});
}
defineExpose({ validateStep, setFieldsValue, editFieldsValue });
</script>
<style lang="less" scoped>
.step1 {
// &-form {
// width: 450px;
// margin: 0 auto;
// }
h3 {
margin: 0 0 12px;
font-size: 16px;
line-height: 32px;
color: @text-color;
}
h4 {
margin: 0 0 4px;
font-size: 14px;
line-height: 22px;
color: @text-color;
}
p {
color: @text-color;
}
}
.pay-select {
width: 20%;
}
.pay-input {
width: 70%;
}
:deep(.ant-tag-orange),
:deep(.ant-tag-blue) {
background: #fff;
padding: 3px 12px;
font-size: 13px;
}
:deep(.ant-tooltip-inner) {
white-space: pre-wrap !important;
}
:deep(.ant-table-tbody > tr > td) {
padding: 16px 8px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
<template>
<div class="add-box">
<svg class="icon add-icon" aria-hidden="true">
<use xlink:href="#icon-add" />
</svg>
<div class="dashed-arrow add-arrow" v-if="isLast"></div>
<div class="bottom-arrow" v-else>
<div class="top-arrow" :style="{ left: `-${lineHeight}`, width: lineHeight }"> </div>
</div>
</div>
</template>
<script lang="ts" setup>
const props = defineProps({
lineHeight: String,
isLast: {
type: Boolean,
default: false,
},
});
</script>
<style lang="less" scoped>
.add-box {
position: relative;
left: 40px;
width: 80px;
height: 80px;
margin-top: 65px;
background: #e7faf3;
display: flex;
justify-content: center;
align-items: center;
transform: rotate(45deg);
cursor: pointer;
&::after {
content: '';
background: #fff;
height: 10px;
width: 10px;
position: absolute;
top: v-bind("props.isLast ? 'calc(100% - 10px)' : '0'");
left: calc(100% - 10px);
border: 2px solid #00d37e;
border-radius: 50%;
}
.dashed-arrow {
height: 40px;
position: absolute;
bottom: -40px;
left: calc(50% - 1px);
border-left: 2px dashed #d9d9d9;
&::after {
content: '';
position: absolute;
left: -7px;
bottom: -14px;
height: 0;
width: 0;
border: 6px solid #000;
border-color: #d9d9d9 transparent transparent;
}
}
.add-icon {
font-size: 30px;
background-color: #fff;
padding: 5px;
border-radius: 50%;
transform: rotate(45deg);
fill: #00d37e;
}
.bottom-arrow {
height: 58px;
width: 45px;
position: absolute;
left: 64px;
top: -64px;
transform: rotate(45deg);
border-top: 2px dashed #d9d9d9;
border-right: 2px dashed #d9d9d9;
border-radius: 0 10%;
.top-arrow {
height: 48px;
position: absolute;
top: -48px;
border-bottom: 2px dashed #d9d9d9;
border-left: 2px dashed #d9d9d9;
border-radius: 0 10%;
&::after {
content: '';
position: absolute;
left: -7px;
top: -14px;
height: 0;
width: 0;
border: 6px solid #000;
border-color: transparent transparent #d9d9d9;
}
}
}
.add-arrow {
left: 116%;
transform: rotate(-45deg);
top: 92%;
}
}
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentcolor;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,183 @@
<template>
<div v-for="(item, idx) in columnList" :key="idx" @click="eventClick(idx)">
<div
:style="{
'--bgcColor': item.bgcColor,
'--color': item.color,
marginTop: item.isLast ? '68px' : 0,
}"
:class="['circle-box', { dot: !item.isLast }]"
v-if="item.type === 'circle'"
>
<svg class="icon circle-icon" aria-hidden="true">
<use :xlink:href="item.icon" />
</svg>
<div class="dashed-arrow" v-if="!item.isLast"> </div>
</div>
<div
:style="{ background: item.bgcColor, '--color': item.color }"
:class="[{ 'area-outline': item.isClick }, 'area-box']"
v-else
>
<svg class="icon svg-icon" aria-hidden="true">
<use :xlink:href="item.icon" />
</svg>
<p>{{ item.text }}</p>
<p v-if="item.detail" style="font-size: 12px">{{ item.detail }}</p>
<svg
class="icon del-icon"
aria-hidden="true"
v-if="item.isUserDefined"
@click.stop="delEvent(idx)"
>
<use xlink:href="#icon-shanchu" />
</svg>
<div class="dashed-arrow"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { FormEventStyleConfig } from '../../../../model/generator/formEventConfig';
const props = defineProps({
index: Number,
isLast: {
type: Boolean,
default: false,
},
columnList: {
type: Array as PropType<FormEventStyleConfig[]>,
},
});
const emits = defineEmits(['deleteEvent', 'clickNode']);
const eventClick = (index) => {
const clickNode = {
index,
columnIndex: props.index,
};
emits('clickNode', clickNode);
};
const delEvent = (index) => {
const clickNode = {
index,
columnIndex: props.index,
};
emits('deleteEvent', clickNode);
};
</script>
<style lang="less" scoped>
.circle-box {
position: relative;
left: 50px;
width: 60px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
background: var(--bgcColor);
margin-bottom: 54px;
border-radius: 50%;
cursor: pointer;
&.dot::after {
content: '';
background: #fff;
height: 10px;
width: 10px;
position: absolute;
bottom: -5px;
left: calc(50% - 5px);
border: 2px solid var(--color);
border-radius: 50%;
}
.circle-icon {
font-size: 32px;
fill: var(--color);
}
}
.area-box {
position: relative;
width: 135px;
height: 90px;
padding: 10px 0 0 15px;
// margin-bottom: 65px;
margin-top: 48px;
border-radius: 6px 6px 6px 6px;
border: 5px solid #fff;
box-sizing: content-box;
cursor: pointer;
// background: v-bind(bgcColor);
&::after {
content: '';
background: #fff;
height: 10px;
width: 10px;
position: absolute;
bottom: -5px;
left: calc(50% - 5px);
border: 2px solid var(--color);
border-radius: 50%;
}
.svg-icon {
font-size: 22px;
background: #fff;
padding: 8px;
margin-bottom: 8px;
box-sizing: content-box;
border-radius: 50%;
fill: var(--color);
}
p {
margin-bottom: 0;
}
.del-icon {
position: absolute;
right: 10px;
font-size: 14px;
fill: #2774ff;
}
}
.area-outline {
outline: 2px dashed #2774ff !important;
}
.dashed-arrow {
height: 40px;
position: absolute;
bottom: -40px;
left: calc(50% - 1px);
border-left: 2px dashed #d9d9d9;
&::after {
content: '';
position: absolute;
left: -7px;
bottom: -14px;
height: 0px;
width: 0px;
border: 6px solid #000;
border-top-color: #d9d9d9;
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
}
}
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div>
<BasicDrawer
v-bind="$attrs"
@register="registerDrawer"
:isDetail="true"
@close="handleCloser"
:title="t('表单预览1')"
>
<div>
<BasicForm
@register="registerForm"
autoFocusFirstItem
:labelWidth="100"
:schemas="schemas"
:actionColOptions="{ span: 24 }"
:submitButtonOptions="{ text: t('提交') }"
@submit="handleSubmit"
@reset="handleReset"
/>
</div>
<template #titleToolbar> {{ t('重置') }} </template>
</BasicDrawer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { BasicForm, FormProps, FormSchema, useForm } from '/@/components/Form/index';
import { useI18n } from '/@/hooks/web/useI18n';
import { buildOption } from '/@/utils/helper/designHelper';
// import { buildCode } from '/@/utils/helper/generatorHelper';
const formProps = ref<FormProps>();
const schemas: FormSchema[] = [];
// [
// {
// field: 'radio_1527c74b77dd43ceb8daceb35e22d657',
// label: '单选框组',
// component: 'ApiRadioGroup',
// colProps: {
// span: 24,
// },
// rules: [
// {
// trigger: 'blur',
// enum: '',
// message: '',
// pattern: '',
// required: false,
// type: 'any',
// },
// ],
// required: false,
// defaultValue: '',
// componentProps: {
// api: getMenuList,
// params: {
// count: 2,
// },
// resultField: 'data',
// // use name as label
// labelField: 'name',
// // use id as value
// valueField: 'id',
// isBtn: false,
// },
// },
// ];
//使用表单钩子 注册表单 获取到 操作表单的方法
const [registerForm, { setProps, resetFields, validate }] = useForm();
//使用抽屉内部钩子 获取到 操作抽屉的方法
const [registerDrawer] = useDrawerInner((option) => {
formProps.value = buildOption(option);
// const codes = buildCode(generatorConfig, tableInfo, formProps.value.schemas as FormSchema[]);
// console.log(JSON.stringify(formProps.value), '@@@@@@@@@@', codes);
setProps(formProps.value);
});
//提交方法
const handleSubmit = async (formData: Recordable) => {
//提交之后将按钮设置为loading 不可操作状态
try {
const values = await validate();
console.log(values);
} finally {
console.log(formData);
}
};
const handleReset = () => {};
//关闭方法
const handleCloser = () => {
resetFields(); //重置表单
// clearValidate(); //清空验证
};
const { t } = useI18n();
</script>

View File

@ -0,0 +1,299 @@
<template>
<div class="node-box" v-if="node?.nodeInfo">
<div class="node-title">{{ node.text }}</div>
<a-tabs v-model:activeKey="nodeKey">
<a-tab-pane key="1" :tab="t('基本信息')">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item :label="t('节点名称')" required>
<a-input v-model:value="node.text" :disabled="!node.isUserDefined" />
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" :tab="t('过程处理')" v-if="node.isUserDefined">
<div class="process-top">
<div class="process-title">{{ t('过程处理事件列表') }}</div>
<a-button type="primary" @click="addProcess" :disabled="fromMobile">
{{ t('添加') }}
</a-button>
</div>
<a-table :columns="columns" :data-source="node.nodeInfo.processEvent" :pagination="false">
<template #headerCell="{ column }">
<template v-if="column.key === 'sort'">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fangxiang1" />
</svg>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'sort'">
<svg class="icon draggable-icon" aria-hidden="true" style="cursor: move">
<use xlink:href="#icon-paixu" />
</svg>
</template>
<template v-if="column.key === 'operateType'">
<a-select v-model:value="record[column.dataIndex]" :disabled="fromMobile">
<a-select-option value="api">{{ t('执行API') }}</a-select-option>
<a-select-option value="liteflow">{{ t('规则引擎') }}</a-select-option>
<a-select-option value="js">JS脚本</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'operateConfig'">
<a-input
v-if="record.operateType === 'api'"
v-model:value="record[column.dataIndex].path"
:disabled="fromMobile"
@click="showConfig(index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input>
<a-input
v-model:value="record.showValue"
v-else-if="record.operateType === 'js'"
@click="showJSConfig(index)"
>
<template #suffix>
<Icon icon="ant-design:ellipsis-outlined" />
</template>
</a-input>
<a-select
v-else-if="record.operateType === 'liteflow'"
v-model:value="record[column.dataIndex]"
:options="liteFlowOptions"
:disabled="fromMobile"
:field-names="{ label: 'chainName', value: 'id' }"
/>
</template>
<template v-if="column.key === 'action'">
<DeleteTwoTone
two-tone-color="#ff8080"
@click="deleteEvent(index)"
:style="{ cursor: fromMobile ? 'not-allowed' : 'pointer' }"
/>
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
<ApiConfig
v-if="apiConfigDialog"
:title="t('API配置')"
v-model:apiConfigDialog="apiConfigDialog"
v-model:apiConfig="node.nodeInfo.processEvent[configIndex].operateConfig"
/>
<ScriptConfig
@register="registerModal"
@success="submitConfig"
:disabled="fromMobile ? true : false"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick, inject, Ref } from 'vue';
import { DeleteTwoTone } from '@ant-design/icons-vue';
import { Icon } from '/@/components/Icon';
import { ApiConfig } from '/@/components/ApiConfig';
import { getLiteflowList } from '/@/api/liteflow';
import { FormEventColumnConfig, selectedNodeConfig } from '/@/model/generator/formEventConfig';
import Sortable from 'sortablejs';
import { useI18n } from '/@/hooks/web/useI18n';
import { useModal } from '/@/components/Modal';
import ScriptConfig from '/@/components/Designer/src/components/componentProperty/settings/ScriptConfig.vue';
import { noHaveTableAndField } from '/@/components/Designer/src/types';
const { t } = useI18n();
const nodeKey = ref<string>('1');
const props = defineProps({
columnList: {
type: Object as PropType<FormEventColumnConfig>,
default: () => {},
},
selectedNode: {
type: Object as PropType<selectedNodeConfig>,
},
});
const node = ref<any>();
const configIndex = ref(0);
const apiConfigDialog = ref(false);
const liteFlowOptions = ref();
const widgetForm = inject<Ref>('widgetForm');
const fromMobile = inject<Boolean>('fromMobile', false);
const [registerModal, { openModal }] = useModal();
const columns = ref([
{
dataIndex: 'sort',
key: 'sort',
},
{
title: t('操作类别'),
dataIndex: 'operateType',
key: 'operateType',
width: '35%',
align: 'center',
},
{
title: t('操作配置'),
dataIndex: 'operateConfig',
key: 'operateConfig',
width: '50%',
align: 'center',
},
{
title: t('操作'),
dataIndex: 'action',
key: 'action',
width: '25%',
align: 'center',
},
]);
watch(
() => props.selectedNode,
(val) => {
if (val) {
node.value = props.columnList[val.columnIndex][val.index];
if (!node.value.isUserDefined) nodeKey.value = '1';
}
},
{
deep: true,
},
);
watch(
() => node.value,
(val) => {
if (val.nodeInfo?.processEvent && val.nodeInfo?.processEvent.length) {
nextTick(() => {
const tbody: any = document.querySelector('.node-box .ant-table-tbody');
Sortable.create(tbody, {
handle: '.draggable-icon',
});
});
delete val.nodeInfo.processEvent[configIndex.value]?.operateConfig?.script;
}
if (val.isUserDefined && !Array.isArray(liteFlowOptions.value)) getList();
},
{ deep: true },
);
const showConfig = (index) => {
configIndex.value = index;
apiConfigDialog.value = true;
};
const getMainTableComponents = (list, mainTableComponent) => {
const rangeComponents = ['time-range', 'date-range'];
list.forEach((item) => {
if (['tab', 'grid', 'card'].includes(item.type)) {
for (const child of item.layout!) {
getMainTableComponents(child.list, mainTableComponent);
}
} else if (rangeComponents.includes(item.type)) {
//时间范围、日期范围需改为两个字段
mainTableComponent.push({
label: t(`{name}开始时间`, { name: item.label }),
bindField: item.bindStartTime,
});
mainTableComponent.push({
label: t(`{name}结束时间`, { name: item.label }),
bindField: item.bindEndTime,
});
} else if (item.type !== 'form' && !noHaveTableAndField.includes(item.type)) {
//除去不绑定表和字段的组件 以及表格组件
mainTableComponent.push(item);
}
});
};
const showJSConfig = (index) => {
console.log('widgetForm', widgetForm?.value, node.value);
const mainTableComponent = ref<any[]>([]);
getMainTableComponents(widgetForm?.value.list, mainTableComponent.value);
const operateConfig = node.value?.nodeInfo.processEvent[index].operateConfig;
const content = typeof operateConfig !== 'string' ? '' : operateConfig;
openModal(true, {
content,
list: mainTableComponent.value,
index,
});
};
const submitConfig = (_, script, index) => {
node.value.nodeInfo.processEvent[index].operateConfig = script;
node.value.nodeInfo.processEvent[index].showValue = script ? '已配置' : '';
};
const addProcess = () => {
node.value.nodeInfo.processEvent.push({
operateType: 'api',
operateConfig: {},
});
};
const deleteEvent = (index) => {
if (fromMobile) return;
node.value.nodeInfo.processEvent.splice(index, 1);
};
const getList = async () => {
liteFlowOptions.value = (await getLiteflowList()) || [];
};
</script>
<style lang="less" scoped>
.node-box {
position: absolute;
top: 0;
right: 0;
box-shadow: -7px -1px 7px #dadcde;
padding: 20px 30px 20px 20px;
height: 100%;
width: 410px;
.node-title {
line-height: 20px;
margin-bottom: 10px;
padding-left: 6px;
border-left: 6px solid #5e95ff;
}
:deep(.ant-form-item) {
margin-bottom: 10px;
}
.process-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.process-title {
line-height: 18px;
padding-left: 6px;
border-left: 6px solid #5e95ff;
}
}
:deep(.ant-select) {
width: 100%;
}
}
:deep(.ant-table-cell) {
padding: 10px !important;
}
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentcolor;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,144 @@
<template>
<BasicModal
wrap-class-name="fixedHeight"
@register="registerModal"
:title="t('选择数据库')"
v-bind="$attrs"
width="800px"
:fixedHeight="true"
@ok="handlerClick"
>
<BasicTable @register="registerTable" />
</BasicModal>
</template>
<script lang="ts" setup>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useTable, FormSchema, BasicColumn, BasicTable } from '/@/components/Table';
import { getDatabaselinkTable } from '/@/api/system/databaselink';
import { ref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { TableConfig } from '/@/model/generator/tableConfig';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const searchFormSchema: FormSchema[] = [
{
field: 'tableName',
label: t('表名'),
component: 'Input',
},
];
const columns: BasicColumn[] = [
{
title: t('表名'),
dataIndex: 'tableName',
width: 200,
},
{
title: t('备注'),
dataIndex: 'tableComment',
width: 200,
},
];
const databaseId = ref('');
const selectedKeys = ref<string[] | number[]>([]);
const emit = defineEmits(['success', 'register']);
const { createMessage } = useMessage();
const selectTableName = ref<string[]>([]);
const formConfig = {
rowProps: {
gutter: 16,
},
schemas: searchFormSchema,
showResetButton: false,
};
const [registerTable, { getSelectRows, setSelectedRowKeys, reload }] = useTable({
title: t('数据表列表'),
api: getDatabaselinkTable,
striped: false,
rowKey: 'tableName',
columns,
formConfig,
beforeFetch: (params) => {
//发送请求默认新增 左边树结构所选机构id
return { ...params, id: databaseId.value };
},
afterFetch: () => {
//搜索完成后 将选中项重新赋值给SelectedRowKeys
setSelectedRowKeys(selectedKeys.value);
},
rowSelection: {
type: 'checkbox',
getCheckboxProps: (record) => ({
disabled: selectTableName.value.includes(record.tableName),
}),
onChange: (selectedRowKeys) => {
//搜索后会把原有选中项清空 所以需要进行存储
selectedKeys.value = selectedRowKeys;
},
},
isPaginateByDataSource: true,
useSearchForm: true,
showTableSetting: true,
tableSetting: {
size: false,
setting: false,
},
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
selectTableName.value = data.selectTableName;
setModalProps({ confirmLoading: false, canFullscreen: false });
setSelectedRowKeys(data.selectTableName);
databaseId.value = data.databaseId;
reload({ searchInfo: { id: databaseId.value } });
});
const handlerClick = () => {
const selectRows = getSelectRows() as TableConfig[];
if (selectRows.length === 0) {
createMessage.error(t('至少需要选择一个数据表!'));
return;
}
selectRows.map((item, index) => {
item.order = index + 1;
if (index === 0) {
item.isMain = true;
} else {
item.isMain = false;
}
});
emit('success', selectRows);
closeModal();
};
</script>
<style scoped>
:deep(.ant-pagination) {
margin-top: 15px;
}
:deep(.ant-table-wrapper .ant-table-title) {
padding-bottom: 0 !important;
}
:deep(.ant-table) {
height: calc(100% - 44px);
}
:deep(.ant-table-container) {
height: calc(100% - 102px);
}
:deep(.ant-modal) {
top: 20px;
}
:deep(.ant-table-wrapper) {
padding-top: 0;
padding-bottom: 0;
}
</style>

View File

@ -0,0 +1,204 @@
<template>
<BasicModal
@register="registerModal"
title="数据表处理"
:width="1400"
:destroyOnClose="true"
v-bind="$attrs"
@ok="handleSuccess"
>
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'status'">
<span v-if="record.status === 1" class="text-blue-500">表结构相同</span>
<span v-else-if="record.status === 2" class="text-orange-500">表结构不同</span>
<span v-else-if="record.status === 3">数据库不存在该表按新增处理</span>
</template>
<template v-if="column.dataIndex === 'operator'">
<span v-if="record.status === 3">无需操作</span>
<a-radio-group
v-model:value="record.operator"
name="radioGroup"
@change="handleOperator(record)"
v-else
>
<a-radio :value="1" v-if="record.status !== 2">沿用旧表</a-radio>
<a-radio :value="2">创建新表重新生成表名</a-radio>
<a-radio :value="3">
覆盖旧表
<span class="text-red-500" v-if="record.status === 1">此操作会清空旧表数据</span>
<span class="text-red-500" v-else>
此操作会删除旧表然后以相同表名按新结构进行生成
</span>
</a-radio>
</a-radio-group>
</template>
<template v-if="column.dataIndex === 'newTableName'">
<a-input
v-if="record.operator === 2"
v-model:value="record.newTableName"
placeholder="请输入表名"
/>
</template>
</template>
</BasicTable>
</BasicModal>
</template>
<script lang="ts" setup>
import { inject } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
import { GeneratorConfig } from '/@/model/generator/generatorConfig';
import { validateTable, validateTableName } from '/@/api/system/generator';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
const { notification } = useMessage();
const { t } = useI18n();
const emits = defineEmits(['success', 'register']);
const generatorConfig = inject<GeneratorConfig>('generatorConfig');
const columns: BasicColumn[] = [
{
title: '数据库名称',
dataIndex: 'databaseName',
width: 100,
align: 'center',
},
{
title: '数据表名',
dataIndex: 'tableName',
width: 120,
align: 'center',
},
{
title: '表结构配置比对结果',
dataIndex: 'status',
width: 120,
align: 'center',
},
{
title: '操作',
dataIndex: 'operator',
width: 400,
align: 'center',
},
{
title: '新表表名',
dataIndex: 'newTableName',
width: 80,
align: 'center',
},
];
const [registerModal] = useModalInner();
const [registerTable, { getDataSource }] = useTable({
api: validateTable,
beforeFetch: () => {
return {
databaseId: generatorConfig?.databaseId,
tableStructureConfigs: generatorConfig?.tableStructureConfigs,
};
},
afterFetch: (data) => {
if (Array.isArray(data) && data.length) {
data.map((item) => {
const operator = item.status === 3 ? 4 : item.status === 2 ? 2 : 1;
item.operator = operator;
item.newTableName = '';
});
}
},
columns,
striped: false,
pagination: false,
showIndexColumn: false,
});
const handleOperator = (record) => {
if (record.operator !== 2) record.newTableName = '';
};
const handleSuccess = async () => {
const newTableNameArr: string[] = [];
const hasNoTableName = getDataSource().some((info) => {
return info.operator === 2 && !info.newTableName;
});
if (hasNoTableName) {
notification.error({
message: t('提示'),
description: '新表表名不能为空,否则无法创建数据表',
});
return;
}
getDataSource().map((item) => {
if (item.operator === 2) {
newTableNameArr.push(item.newTableName);
}
});
const reg = /^[a-zA-Z0-9_]*$/;
//判断表名是否符合要求
const isTableNotSuccess = newTableNameArr?.some((tableName) => !reg.test(tableName));
if (isTableNotSuccess) {
notification.error({
message: t('提示'),
description: '表名只能包括字母、数字、下划线',
});
return;
}
await validateTableName({
id: generatorConfig!.databaseId!,
tableNames: newTableNameArr.toString(),
});
generatorConfig?.tableStructureConfigs?.forEach((item) => {
getDataSource().forEach((data) => {
if (item.tableName === data.tableName) {
item.operator = data.operator;
if (data.operator === 2) {
item.tableName = data.newTableName;
item.tableFieldConfigs.forEach((field) => {
changeTableName(generatorConfig?.formJson.list, field.key, item.tableName, item.key);
});
}
}
});
});
emits('success');
};
const changeTableName = (list, fieldKey, tableName, tableKey) => {
if (generatorConfig?.formJson.hiddenComponent?.length) {
generatorConfig?.formJson.hiddenComponent?.map((component: any) => {
if (component.key === fieldKey) {
component.bindTable = tableName;
}
});
}
list?.map((component: any) => {
if (component.type === 'form' || component.type === 'one-for-one') {
if (component.key === tableKey) {
component.bindTable = tableName;
}
if (component.children.length) {
component.children.map((subComponent: any) => {
if (subComponent.key === fieldKey) {
subComponent.bindTable = tableName;
}
});
}
} else if (component.key === fieldKey) {
component.bindTable = tableName;
} else if (['tab', 'grid', 'card'].includes(component.type)) {
for (const child of component.layout!) {
changeTableName(child.list, fieldKey, tableName, tableKey);
}
}
});
};
</script>
<style lang="less" scoped>
* {
font-size: 12px;
}
</style>