<script lang="ts" setup>
|
import type { GenItem } from './types/genItem';
|
|
import { computed, nextTick, onMounted, provide, reactive, ref, toRefs, unref, watch } from 'vue';
|
|
import { useMessage, useRedo } from '@jnpf/hooks';
|
import { ScrollContainer } from '@jnpf/ui';
|
import { useModal } from '@jnpf/ui/modal';
|
import { buildBitUUID, isUrl } from '@jnpf/utils';
|
|
import { useDebounceFn } from '@vueuse/core';
|
import { cloneDeep } from 'lodash-es';
|
import draggable from 'vuedraggable';
|
|
import { getKitSelector } from '#/api/system/kit';
|
import emptyImg from '#/assets/images/emptyElement.png';
|
import { $t } from '#/locales';
|
import { useGeneratorStore } from '#/store';
|
|
import AiChatPopover from './components/AiChatPopover.vue';
|
import AiFieldModal from './components/AiFieldModal.vue';
|
import KitForm from './components/KitForm.vue';
|
import PreviewModal from './components/PreviewModal.vue';
|
import DraggableItem from './DraggableItem.vue';
|
import { formConf as defaultFormConf, inputComponents, layoutComponents, selectComponents, systemComponents } from './helper/componentMap';
|
import { noTableAllowList, noVModelList } from './helper/config';
|
import RightPanel from './RightPanel.vue';
|
|
interface State {
|
leftComponents: any[];
|
showType: string;
|
formConf: any;
|
drawingList: any[];
|
copyDrawingList: string;
|
activeId: any;
|
activeData: any;
|
activeItem: any;
|
activeTableItem: any;
|
showTip: boolean;
|
rowIndex: number;
|
colIndex: number;
|
rowData: any[];
|
colData: any[];
|
selectCell: any;
|
leftActiveKey: string[];
|
leftKitActiveKey: string[];
|
leftTabActiveKey: string;
|
kitList: any[];
|
errorList: any[];
|
}
|
interface ComType {
|
setTabActiveKey: (data) => any;
|
}
|
|
const props = defineProps(['conf', 'formInfo', 'dbType']);
|
const emit = defineEmits(['showValidatePopover']);
|
defineExpose({ getData, activeFormItemById, setTabActiveKey });
|
const { createMessage, createConfirm } = useMessage();
|
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
|
const [registerKitForm, { openModal: openKitFormModal }] = useModal();
|
const [registerAiFieldModal, { openModal: openAiFieldModal }] = useModal();
|
const generatorStore = useGeneratorStore();
|
const debounceShouldClone = useDebounceFn(shouldClone, 200);
|
const rightPanelRef = ref<Nullable<ComType>>(null);
|
let tempActiveData;
|
let tempActiveKitData;
|
let kitParent;
|
const state = reactive<State>({
|
leftComponents: [
|
{ id: '1', title: '基础控件', list: inputComponents },
|
{ id: '2', title: '高级控件', list: selectComponents },
|
{ id: '3', title: '系统控件', list: systemComponents },
|
{ id: '4', title: '布局控件', list: layoutComponents },
|
],
|
showType: 'pc',
|
formConf: cloneDeep(defaultFormConf),
|
drawingList: [],
|
copyDrawingList: '',
|
activeId: null,
|
activeData: {},
|
activeItem: {},
|
activeTableItem: {},
|
showTip: true,
|
rowIndex: 0,
|
colIndex: 0,
|
rowData: [],
|
colData: [],
|
selectCell: {
|
__config__: {
|
rowspan: 1,
|
colspan: 1,
|
},
|
},
|
leftActiveKey: ['1', '2', '3', '4'],
|
leftKitActiveKey: [],
|
leftTabActiveKey: '1',
|
kitList: [],
|
errorList: [],
|
});
|
const { leftComponents, leftActiveKey, leftKitActiveKey, leftTabActiveKey, showType, drawingList, formConf, activeId } = toRefs(state);
|
const { initRedo, addRecord, handleUndo, handleRedo, getCanUndo, getCanRedo } = useRedo();
|
|
const getRightPanelBind = computed(() => {
|
return {
|
activeData: state.activeData,
|
formConf: state.formConf,
|
drawingList: state.drawingList,
|
formInfo: props.formInfo,
|
dbType: props.dbType,
|
};
|
});
|
const mergeLeftColDisabled = computed(() => {
|
if (!state.colData.length) return true;
|
return state.colIndex <= 0 || state.colData[state.colIndex - 1].__config__.rowspan !== state.selectCell.__config__.rowspan;
|
});
|
const mergeRightColDisabled = computed(() => {
|
if (!state.colData.length) return true;
|
const rightColIndex = state.colIndex + state.selectCell.__config__.colspan;
|
return (
|
state.colIndex >= state.colData.length - 1 ||
|
rightColIndex > state.colData.length - 1 ||
|
state.colData[rightColIndex].__config__.rowspan !== state.selectCell.__config__.rowspan
|
);
|
});
|
const mergeWholeRowDisabled = computed(() => {
|
if (!state.selectCell.__config__ || !state.rowData.length) return true;
|
const rowDataChildren = state.rowData[state.rowIndex].__config__.children;
|
const startRowspan = rowDataChildren[0].__config__.rowspan;
|
let unmatchedFlag = false;
|
for (let i = 1; i < rowDataChildren.length; i++) {
|
if (rowDataChildren[i].__config__.rowspan !== startRowspan) {
|
unmatchedFlag = true;
|
break;
|
}
|
}
|
if (unmatchedFlag) return true;
|
return state.colData.length <= 1 || state.colData.length === state.selectCell.__config__.colspan;
|
});
|
const mergeAboveRowDisabled = computed(() => {
|
if (!state.rowData.length || state.rowIndex <= 0) return true;
|
return (
|
state.rowData[state.rowIndex - 1].__config__.children[state.colIndex].__config__.colspan !== state.selectCell.__config__.colspan ||
|
state.rowData[state.rowIndex - 1].__config__.children[state.colIndex].__config__.merged
|
);
|
});
|
const mergeBelowRowDisabled = computed(() => {
|
if (!state.rowData.length || state.rowIndex == state.rowData.length) return true;
|
const belowRowIndex = state.rowIndex + state.selectCell.__config__.rowspan;
|
return (
|
state.rowIndex >= state.rowData.length - 1 ||
|
belowRowIndex > state.rowData.length - 1 ||
|
state.rowData[belowRowIndex].__config__.children[state.colIndex].__config__.colspan !== state.selectCell.__config__.colspan ||
|
state.rowData[belowRowIndex].__config__.children[state.colIndex].__config__.merged
|
);
|
});
|
const mergeWholeColDisabled = computed(() => {
|
if (!state.rowData.length) return true;
|
const startColspan = state.rowData[0].__config__.children[state.colIndex].__config__.colspan;
|
let unmatchedFlag = false;
|
for (let i = 1; i < state.rowData.length; i++) {
|
if (state.rowData[i].__config__.children[state.colIndex].__config__.colspan !== startColspan) {
|
unmatchedFlag = true;
|
break;
|
}
|
}
|
if (unmatchedFlag) return true;
|
return state.rowData.length <= 1 || state.rowData.length === state.selectCell.__config__.rowspan;
|
});
|
const undoMergeRowDisabled = computed(() => {
|
return state.selectCell.__config__.merged || state.selectCell.__config__.colspan <= 1;
|
});
|
const undoMergeColDisabled = computed(() => {
|
return state.selectCell.__config__.merged || state.selectCell.__config__.rowspan <= 1;
|
});
|
const deleteWholeColDisabled = computed(() => {
|
if (!state.rowData.length) return true;
|
if (state.rowData[0].__config__.children[0].__config__.colspan === state.rowData[0].__config__.children.length) return true;
|
const startColspan = state.rowData[0].__config__.children[state.colIndex].__config__.colspan;
|
let unmatchedFlag = false;
|
for (let i = 1; i < state.rowData.length; i++) {
|
if (state.rowData[i].__config__.children[state.colIndex].__config__.colspan !== startColspan) {
|
unmatchedFlag = true;
|
break;
|
}
|
}
|
if (unmatchedFlag) return true;
|
return state.selectCell.__config__.colspan === state.colData.length;
|
});
|
const deleteWholeRowDisabled = computed(() => {
|
if (!state.rowData.length || state.rowData.length <= state.rowIndex) return true;
|
if (state.rowData[0].__config__.children[0].__config__.rowspan === state.rowData.length) return true;
|
// 整行所有单元格行高不一致不可删除!!
|
const startRowspan = state.rowData[state.rowIndex].__config__.children[0].__config__.rowspan;
|
let unmatchedFlag = false;
|
for (let i = 1; i < state.rowData[state.rowIndex].__config__.children.length; i++) {
|
if (state.rowData[state.rowIndex].__config__.children[i].__config__.rowspan !== startRowspan) {
|
unmatchedFlag = true;
|
break;
|
}
|
}
|
if (unmatchedFlag) return true;
|
return state.rowData.length === 1 || state.selectCell.__config__.rowspan === state.rowData.length;
|
});
|
|
provide('getShowType', () => state.showType);
|
provide('getDrawingList', () => state.drawingList);
|
|
watch(
|
() => state.drawingList,
|
(val) => {
|
if (val.length) return;
|
state.activeData = {};
|
state.activeId = null;
|
},
|
{ deep: true },
|
);
|
|
// 供父组件使用 获取表单JSON
|
function getData() {
|
return new Promise((resolve) => {
|
state.errorList = [];
|
// eslint-disable-next-line regexp/no-unused-capturing-group
|
const RegExp = /(^_([a-z0-9]_?)*$)|(^[a-z](_?[a-z0-9])*_?$)/i;
|
// 表单验证
|
if (!state.drawingList.length) setErrorList('表单内容不能为空');
|
// 表单属性验证
|
if (state.formConf.hasPrintBtn && !state.formConf.printId?.length) setErrorList('打印模板不能为空', 'formAttr', '表单属性');
|
if (state.formConf.useBusinessKey && !state.formConf.businessKeyList?.length) setErrorList('业务主键的规则设置中联合字段不能为空', 'formAttr', '表单属性');
|
if (!generatorStore.getHasTable && state.formConf.tableName && !RegExp.test(state.formConf.tableName)) {
|
setErrorList('只能由字母、数字、下划线组成,且以字母开头', 'formAttr', '表单属性');
|
}
|
// 控件验证
|
const loop = (list) => {
|
for (const e of list) {
|
const config = e.__config__;
|
const formId = config.formId;
|
const label = config.label;
|
if (config.layout === 'colFormItem' && (!noVModelList.includes(config.jnpfKey) || e.isStorage)) {
|
if (!e.__vModel__) setErrorList('控件字段不能为空', formId, label);
|
if (e.__vModel__ && !RegExp.test(e.__vModel__)) setErrorList('控件字段不规范', formId, label);
|
}
|
if (config.jnpfKey === 'billRule') {
|
if (config.ruleType === 2) {
|
if (config.ruleConfig?.type === 1) {
|
const reg = /^\d*[1-9]$/;
|
if (!config.ruleConfig?.digit) setErrorList('中位数不能为空', formId, label);
|
if (!config.ruleConfig?.startNumber) setErrorList('起始值不能为空', formId, label);
|
if (config.ruleConfig?.startNumber && !reg.test(config.ruleConfig?.startNumber)) setErrorList('起始值只能输入数字且不能为0', formId, label);
|
}
|
} else {
|
if (!config.rule) setErrorList('单据模板不能为空', formId, label);
|
}
|
}
|
if (config.jnpfKey === 'relationForm') {
|
if (!e.modelId) setErrorList('关联功能不能为空', formId, label);
|
if (!e.propsValue) setErrorList('存储字段不能为空', formId, label);
|
if (!e.relationField) setErrorList('回显字段不能为空', formId, label);
|
}
|
if (config.jnpfKey === 'popupSelect' || config.jnpfKey === 'popupTableSelect') {
|
if (!e.interfaceId) setErrorList('数据接口不能为空', formId, label);
|
if (!e.propsValue) setErrorList('存储字段不能为空', formId, label);
|
if (!e.relationField) setErrorList('回显字段不能为空', formId, label);
|
}
|
if (config.jnpfKey === 'autoComplete') {
|
if (!e.interfaceId) setErrorList('数据接口不能为空', formId, label);
|
if (!e.relationField) setErrorList('回显字段不能为空', formId, label);
|
}
|
if (config.layout === 'rowFormItem' && !config.children.length) setErrorList('控件中没有组件', '', label);
|
if (config.jnpfKey === 'uploadFile' || config.jnpfKey === 'uploadImg') {
|
if (((config.jnpfKey === 'uploadImg' && e.showType === 'button') || config.jnpfKey === 'uploadFile') && !e.buttonText) {
|
setErrorList('按钮文字不能为空', formId, label);
|
}
|
if (e.pathType === 'selfPath') {
|
if (!e.sortRule || !e.sortRule.length) setErrorList('自定义路径不能为空', formId, label);
|
if (e.sortRule?.includes('3')) {
|
if (e.folder) {
|
const regex = /^[a-z0-9][\w-]{0,99}$/i;
|
let flag = false;
|
const folderList = e.folder.split('/');
|
folder: for (const element of folderList) {
|
if (!element || !regex.test(element)) {
|
flag = true;
|
break folder;
|
}
|
}
|
if (flag) {
|
setErrorList('文件夹名不正确', formId, label);
|
}
|
} else {
|
setErrorList('文件夹名不能为空', formId, label);
|
}
|
}
|
}
|
}
|
if ((config.jnpfKey === 'link' || config.jnpfKey === 'iframe') && e.href && !isUrl(e.href)) setErrorList('请输入正确的链接地址', formId, label);
|
if (!generatorStore.getHasTable && config.jnpfKey === 'table' && config.tableName && !RegExp.test(config.tableName)) {
|
setErrorList('只能由字母、数字、下划线组成,且以字母开头', formId, label);
|
}
|
if (config && config.children && Array.isArray(config.children)) {
|
loop(config.children);
|
}
|
}
|
};
|
loop(state.drawingList);
|
assembleFormData();
|
resolve({ formData: state.formConf, errorList: state.errorList, target: 1 });
|
});
|
}
|
function setErrorList(errorInfo, id = '', title = '表单设计') {
|
state.errorList.push({ errorInfo, id, title });
|
}
|
function addComponent(item) {
|
if (item.__config__.dragDisabled) return;
|
const clone = cloneComponent(item);
|
(state.drawingList as any[]).push(clone);
|
activeFormItem(clone);
|
addLocalRecord(state.drawingList);
|
}
|
function cloneComponent(origin) {
|
const clone = cloneDeep(origin);
|
const config = clone.__config__;
|
config.span = state.formConf.span; // 生成代码时,会根据span做精简判断
|
createIdAndKey(clone);
|
tempActiveData = clone;
|
return tempActiveData;
|
}
|
function addKit(item) {
|
const kitTemp = getKitTemp(item.fields);
|
(state.drawingList as any[]).push(...kitTemp);
|
activeFormItem(kitTemp[0]);
|
addLocalRecord(state.drawingList);
|
}
|
function cloneKit(origin) {
|
tempActiveKitData = getKitTemp(origin.fields);
|
}
|
function getKitTemp(fields) {
|
let list: any = [];
|
const vModelList: any = [];
|
const loop = (data, parent?) => {
|
if (!data) return;
|
if (data.__config__ && data.__config__.children && Array.isArray(data.__config__.children)) {
|
loop(data.__config__.children, data);
|
}
|
if (Array.isArray(data)) return data.forEach((d) => loop(d, parent));
|
const uuid = buildBitUUID();
|
data.__config__.formId = `formItem${uuid}`;
|
if (data.__vModel__ && data.__config__.jnpfKey) {
|
const oldVal = data.__vModel__;
|
const newVal = toggleVmodelCase(`${data.__config__.jnpfKey}Field${uuid}`);
|
data.__vModel__ = !generatorStore.getHasTable || data.__config__.jnpfKey == 'table' ? newVal : '';
|
if (data.__config__.jnpfKey == 'table') data.__config__.tableName = '';
|
vModelList.push({ oldVal, newVal });
|
}
|
};
|
loop(fields);
|
let fieldsStr = JSON.stringify(fields);
|
for (const e of vModelList) {
|
fieldsStr = fieldsStr.replaceAll(e.oldVal, e.newVal);
|
}
|
list = JSON.parse(fieldsStr);
|
return list;
|
}
|
function createIdAndKey(item, parent: Nullable<GenItem> = null) {
|
const uuid = buildBitUUID();
|
const config = item.__config__;
|
config.formId = `formItem${uuid}`;
|
config.renderKey = Date.now(); // 改变renderKey后可以实现强制更新组件
|
if (config.layout === 'colFormItem') {
|
if (!noVModelList.includes(config.jnpfKey) || item.isStorage) {
|
item.__vModel__ = generatorStore.getHasTable ? '' : toggleVmodelCase(`${config.jnpfKey}Field${uuid}`);
|
}
|
if (parent?.__vModel__ && parent?.__config__.jnpfKey === 'table') {
|
item.__config__.parentVModel = parent.__vModel__;
|
}
|
} else if (config.layout === 'rowFormItem') {
|
if (config.jnpfKey === 'table') {
|
item.__vModel__ = toggleVmodelCase(`${config.jnpfKey}Field${uuid}`);
|
}
|
config.componentName = `${config.jnpfKey}${uuid}`;
|
!Array.isArray(config.children) && (config.children = []);
|
}
|
if (Array.isArray(config.children)) {
|
config.children = config.children.map((childItem) => createIdAndKey(childItem, item));
|
}
|
return item;
|
}
|
function toggleVmodelCase(str) {
|
const dbType = props.dbType || '';
|
if (dbType.toLowerCase() === 'Oracle'.toLowerCase() || dbType.toLowerCase() === 'DM'.toLowerCase()) {
|
return str.toUpperCase();
|
}
|
if (dbType.toLowerCase() === 'PostgreSQL'.toLowerCase() || dbType.toLowerCase() === 'KingBaseES'.toLowerCase()) {
|
return str.toLowerCase();
|
}
|
return str;
|
}
|
function activeFormItem(element) {
|
state.activeData = element;
|
state.activeId = element.__config__.formId;
|
}
|
function drawingItemCopy(item, parent, isActiveFormItem = true) {
|
let clone = cloneDeep(item);
|
clone = createIdAndKey(clone);
|
parent.push(clone);
|
isActiveFormItem && activeFormItem(clone);
|
addLocalRecord(state.drawingList);
|
}
|
function drawingItemDelete(index, parent) {
|
parent.splice(index, 1);
|
nextTick(() => {
|
const len = state.drawingList.length;
|
if (len) activeFormItem(state.drawingList[len - 1]);
|
addLocalRecord(state.drawingList);
|
});
|
}
|
function assembleFormData() {
|
state.formConf = {
|
...state.formConf,
|
fields: cloneDeep(state.drawingList),
|
};
|
}
|
function replaceDrawingList(data) {
|
state.drawingList = cloneDeep(data);
|
state.copyDrawingList = JSON.stringify(state.drawingList);
|
let boo = false;
|
const loop = (list) => {
|
for (const e of list) {
|
if (e.__config__.formId === state.activeId) {
|
activeFormItem(e);
|
boo = true;
|
}
|
const config = e.__config__;
|
if (config && config.children && Array.isArray(config.children)) {
|
loop(config.children);
|
}
|
}
|
};
|
loop(state.drawingList);
|
if (!boo) {
|
state.activeData = {};
|
state.activeId = null;
|
}
|
}
|
function onLeftKitEnd(obj) {
|
if (!tempActiveKitData?.length) return;
|
nextTick(() => {
|
state.showTip = true;
|
if (kitParent) {
|
kitParent.__config__.children.splice(obj.newIndex, 0, ...tempActiveKitData);
|
kitParent = null;
|
} else {
|
(state.drawingList as any[]).splice(obj.newIndex, 0, ...tempActiveKitData);
|
}
|
activeFormItem(tempActiveKitData[0]);
|
addLocalRecord(state.drawingList);
|
});
|
}
|
function onLeftEnd(obj) {
|
state.showTip = true;
|
if (obj.from !== obj.to) {
|
state.activeData = tempActiveData;
|
state.activeId = tempActiveData.__config__.formId;
|
}
|
if (obj.to.className.includes('table-wrapper')) {
|
(state.activeItem as GenItem).__config__.isSubTable = true;
|
(state.activeItem as GenItem).__config__.parentVModel = (state.activeTableItem as GenItem).__vModel__;
|
if (generatorStore.getHasTable) {
|
(state.activeItem as GenItem).__config__.relationTable = (state.activeTableItem as GenItem).__config__.tableName;
|
(state.activeItem as GenItem).__vModel__ = '';
|
}
|
}
|
addLocalRecord(state.drawingList);
|
}
|
function onCenterEnd(obj) {
|
state.showTip = true;
|
if (obj.from === obj.to) return addLocalRecord(state.drawingList);
|
if (obj.to.className.includes('table-wrapper')) {
|
(state.activeItem as GenItem).__config__.isSubTable = true;
|
(state.activeItem as GenItem).__config__.parentVModel = (state.activeTableItem as GenItem).__vModel__;
|
if (generatorStore.getHasTable) {
|
(state.activeItem as GenItem).__config__.relationTable = (state.activeTableItem as GenItem).__config__.tableName;
|
(state.activeItem as GenItem).__vModel__ = '';
|
}
|
}
|
addLocalRecord(state.drawingList);
|
}
|
function onTableEnd(obj) {
|
if (obj.from == obj.to) return addLocalRecord(state.drawingList);
|
if (obj.to.className.includes('table-wrapper')) {
|
(state.activeItem as GenItem).__config__.isSubTable = true;
|
(state.activeItem as GenItem).__config__.parentVModel = (state.activeTableItem as GenItem).__vModel__;
|
if (generatorStore.getHasTable) {
|
(state.activeItem as GenItem).__config__.relationTable = (state.activeTableItem as GenItem).__config__.tableName;
|
(state.activeItem as GenItem).__vModel__ = '';
|
}
|
} else {
|
if ((state.activeItem as GenItem).__config__) {
|
(state.activeItem as GenItem).__config__.isSubTable = false;
|
(state.activeItem as GenItem).__config__.parentVModel = '';
|
if (generatorStore.getHasTable) (state.activeItem as GenItem).__vModel__ = '';
|
}
|
}
|
addLocalRecord(state.drawingList);
|
}
|
// 阻止表格中嵌套行容器
|
function shouldClone(_to, _from, target, _event, conf) {
|
const targetConf = target._underlying_vm_;
|
if (conf.__config__.jnpfKey === 'table') {
|
if (!targetConf) return false;
|
if (noTableAllowList.includes(targetConf.__config__.jnpfKey) || state.leftTabActiveKey === '2') {
|
return false;
|
}
|
if (targetConf.__config__.layout === 'rowFormItem') return false;
|
if (generatorStore.getHasTable && !conf.__config__.tableName) {
|
if (state.showTip) {
|
createMessage.warning(`子表请先关联数据表`);
|
state.showTip = false;
|
}
|
return false;
|
}
|
state.activeItem = targetConf;
|
state.activeTableItem = conf;
|
}
|
if (!targetConf && state.leftTabActiveKey === '2') {
|
kitParent = conf;
|
return;
|
}
|
if (conf.__config__.jnpfKey === 'tableGridTd' && targetConf.__config__.jnpfKey === 'tableGrid') return false;
|
return true;
|
}
|
// 预览表单
|
function handlePreview() {
|
assembleFormData();
|
openPreviewModal(true, { formConf: unref(state.formConf) });
|
}
|
// 清空
|
function handleClear() {
|
createConfirm({
|
iconType: 'warning',
|
title: $t('common.tipTitle'),
|
content: $t('formGenerator.cleanComponentTip'),
|
onOk: () => {
|
state.drawingList = [];
|
addLocalRecord(state.drawingList);
|
},
|
});
|
}
|
function addLocalRecord(val) {
|
if (JSON.stringify(val) != state.copyDrawingList) {
|
state.copyDrawingList = JSON.stringify(val);
|
addRecord(val);
|
}
|
}
|
function onRelationChange() {
|
const loop = (list) => {
|
for (const element of list) {
|
const config = element.__config__;
|
if (element.relationField && element.relationField === element.__vModel__) {
|
element.showField = '';
|
}
|
if (config?.children && Array.isArray(config.children)) {
|
loop(config.children);
|
}
|
}
|
};
|
loop(state.drawingList);
|
}
|
function handleTableAddRow(element, insertPos, colIndex, aboveFlag) {
|
const row = element.__config__.children;
|
let rowIdx = aboveFlag ? insertPos : insertPos + 1;
|
if (!aboveFlag) {
|
// 继续向下寻找同列第一个未被合并的单元格
|
let tmpRowIdx = rowIdx;
|
let rowFoundFlag = false;
|
while (tmpRowIdx < row.length) {
|
if (row[tmpRowIdx].__config__.children[colIndex].__config__.merged) {
|
tmpRowIdx++;
|
} else {
|
rowIdx = tmpRowIdx;
|
rowFoundFlag = true;
|
break;
|
}
|
}
|
if (!rowFoundFlag) {
|
rowIdx = row.length;
|
}
|
}
|
const newRow = cloneDeep(row[insertPos]) || cloneDeep(row[row.length - 1]);
|
newRow.__config__.children.forEach((col) => {
|
col.__config__.formId = `formItem${buildBitUUID()}`;
|
col.__config__.merged = false;
|
col.__config__.colspan = 1;
|
col.__config__.rowspan = 1;
|
col.__config__.children = [];
|
});
|
newRow.__config__.formId = `formItem${buildBitUUID()}`;
|
newRow.__config__.jnpfKey = 'tableGridTr';
|
element.__config__.children.splice(rowIdx, 0, newRow);
|
let colNo = 0;
|
while (rowIdx < row.length - 1 && colNo < row[0].__config__.children.length) {
|
const cellOfNextRow = row[rowIdx + 1].__config__.children[colNo];
|
const rowMerged = cellOfNextRow.__config__.merged;
|
if (rowMerged) {
|
let unMergedCell: any = {};
|
let startRowIndex = null;
|
for (let i = rowIdx; i >= 0; i--) {
|
// 查找该行已合并的主单元格
|
if (!row[i].__config__.children[colNo].__config__.merged && row[i].__config__.children[colNo].__config__.rowspan > 1) {
|
startRowIndex = i;
|
unMergedCell = row[i].__config__.children[colNo];
|
break;
|
}
|
}
|
// 如果有符合条件的unMergedCell
|
const newRowspan = unMergedCell.__config__.rowspan + 1;
|
setPropsOfMergedRows(startRowIndex, unMergedCell.__config__.colspan, newRowspan, colNo);
|
colNo += unMergedCell.__config__.colspan;
|
} else {
|
colNo += cellOfNextRow.__config__.colspan || 1;
|
}
|
}
|
}
|
function handleTableAddCol(element, insertPos) {
|
const row = element.__config__.children;
|
const colIdx = insertPos === undefined ? row[0].__config__.children.length : insertPos; // 确定插入列位置
|
row.forEach((item) => {
|
const newCol = {
|
__config__: {
|
jnpfKey: 'tableGridTd',
|
merged: false,
|
colspan: 1,
|
rowspan: 1,
|
formId: `formItem${buildBitUUID()}`,
|
children: [],
|
backgroundColor: '',
|
},
|
};
|
item.__config__.children.splice(colIdx, 0, newCol);
|
});
|
}
|
function mergeTableCol(_element, type = 0) {
|
const mergedColIndex = type == 1 ? state.colIndex : state.colIndex + state.colData[state.colIndex].__config__.colspan;
|
const remainedColIndex = type == 1 ? state.colIndex - state.colData[state.colIndex - 1].__config__.colspan : state.colIndex;
|
const colChildren = state.colData[mergedColIndex].__config__.children;
|
const colChildren_ = state.colData[remainedColIndex].__config__.children;
|
state.colData[remainedColIndex].__config__.children = [...colChildren_, ...cloneDeep(colChildren)];
|
const newColspan = state.colData[mergedColIndex].__config__.colspan * 1 + state.colData[remainedColIndex].__config__.colspan * 1;
|
setPropsOfMergedCols(remainedColIndex, newColspan, state.selectCell.__config__.rowspan);
|
}
|
function mergeWholeCol() {
|
const childrenData = state.colData.filter((colItem) => {
|
return !colItem.merged && colItem.__config__.children?.length;
|
});
|
if (childrenData?.length) {
|
childrenData.map((o, i) => {
|
if (i == 0) state.colData[0].__config__.children = cloneDeep(o.__config__.children);
|
if (i != 0) state.colData[0].__config__.children.push(...cloneDeep(o.__config__.children));
|
return o;
|
});
|
}
|
setPropsOfMergedCols(0, state.colData.length, state.colData[state.colIndex].__config__.rowspan);
|
}
|
function mergeTableRow(type = 0) {
|
const mergedRowIndex = type == 1 ? state.rowIndex : state.rowIndex + state.selectCell.__config__.rowspan;
|
const remainedRowIndex = type == 1 ? state.rowIndex - 1 : state.rowIndex;
|
const childrenData = state.rowData[mergedRowIndex].__config__.children[state.colIndex].__config__.children;
|
const childrenData_ = state.rowData[remainedRowIndex].__config__.children[state.colIndex].__config__.children;
|
state.rowData[remainedRowIndex].__config__.children[state.colIndex].__config__.children = [...childrenData_, ...cloneDeep(childrenData)];
|
const newRowspan =
|
state.rowData[mergedRowIndex].__config__.children[state.colIndex].__config__.rowspan * 1 +
|
state.rowData[remainedRowIndex].__config__.children[state.colIndex].__config__.rowspan * 1;
|
setPropsOfMergedRows(remainedRowIndex, state.selectCell.__config__.colspan, newRowspan);
|
}
|
function mergeWholeRow() {
|
const childrenData: any[] = [];
|
state.rowData.forEach((o) => {
|
const tempCell = o.__config__.children[state.colIndex];
|
if (!o.__config__.merged && !!o.__config__.children && o.__config__.children.length) {
|
childrenData.push(tempCell);
|
}
|
});
|
const firstCellOfCol = state.rowData[0].__config__.children[state.colIndex];
|
if (childrenData && childrenData.length) {
|
childrenData.map((o, i) => {
|
if (i != 0) firstCellOfCol.__config__.children.push(...cloneDeep(o.__config__.children));
|
return o;
|
});
|
}
|
setPropsOfMergedRows(0, firstCellOfCol.__config__.colspan, state.rowData.length);
|
}
|
function undoMergeCol() {
|
setPropsOfSplitCol(state.colIndex, state.selectCell.__config__.colspan, state.selectCell.__config__.rowspan);
|
}
|
function undoMergeRow() {
|
setPropsOfSplitRow(state.colIndex, state.selectCell.__config__.colspan, state.selectCell.__config__.rowspan);
|
}
|
function deleteWholeCol() {
|
const startColspan = state.rowData[0].__config__.children[state.colIndex].__config__.colspan;
|
state.rowData.forEach((rItem) => {
|
rItem.__config__.children.splice(state.colIndex, startColspan);
|
});
|
}
|
function deleteWholeRow() {
|
const startRowspan = state.rowData[state.rowIndex].__config__.children[0].__config__.rowspan;
|
state.rowData.splice(state.rowIndex, startRowspan);
|
}
|
function setPropsOfMergedCols(startColIndex, newColspan, rowspan) {
|
for (let i = state.rowIndex; i < state.rowIndex + rowspan; i++) {
|
for (let j = startColIndex; j < startColIndex + newColspan; j++) {
|
if (i === state.rowIndex && j === startColIndex) {
|
state.rowData[i].__config__.children[j].__config__.colspan = newColspan;
|
continue;
|
}
|
state.rowData[i].__config__.children[j].__config__.merged = true;
|
state.rowData[i].__config__.children[j].__config__.colspan = newColspan;
|
state.rowData[i].__config__.children[j].__config__.children = [];
|
}
|
}
|
}
|
function setPropsOfMergedRows(startRowIndex, colspan, newRowspan, colIndex = 0) {
|
if (!colIndex) colIndex = state.colIndex;
|
for (let i = startRowIndex; i < startRowIndex + newRowspan; i++) {
|
for (let j = colIndex; j < colIndex + colspan; j++) {
|
if (i === startRowIndex && j === colIndex) {
|
state.rowData[i].__config__.children[j].__config__.rowspan = newRowspan;
|
continue;
|
}
|
state.rowData[i].__config__.children[j].__config__.merged = true;
|
state.rowData[i].__config__.children[j].__config__.rowspan = newRowspan;
|
state.rowData[i].__config__.children[j].__config__.children = [];
|
}
|
}
|
}
|
function setPropsOfSplitCol(startColIndex, colspan, rowspan) {
|
for (let i = state.rowIndex; i < state.rowIndex + rowspan; i++) {
|
for (let j = startColIndex; j < startColIndex + colspan; j++) {
|
state.rowData[i].__config__.children[j].__config__.merged = false;
|
state.rowData[i].__config__.children[j].__config__.rowspan = 1;
|
state.rowData[i].__config__.children[j].__config__.colspan = 1;
|
}
|
}
|
}
|
function setPropsOfSplitRow(startColIndex, colspan, rowspan) {
|
for (let i = state.rowIndex; i < state.rowIndex + rowspan; i++) {
|
for (let j = startColIndex; j < startColIndex + colspan; j++) {
|
state.rowData[i].__config__.children[j].__config__.merged = false;
|
state.rowData[i].__config__.children[j].__config__.rowspan = 1;
|
state.rowData[i].__config__.children[j].__config__.colspan = 1;
|
}
|
}
|
}
|
function handleTableSetting(e, element) {
|
switch (e) {
|
case '1': {
|
// 插入左侧列
|
handleTableAddCol(element, state.colIndex);
|
break;
|
}
|
case '2': {
|
// 插入右侧列
|
handleTableAddCol(element, state.colIndex + 1);
|
break;
|
}
|
case '3': {
|
// 插入上方行
|
handleTableAddRow(element, state.rowIndex, state.colIndex, true);
|
break;
|
}
|
case '4': {
|
// 插入下方行
|
handleTableAddRow(element, state.rowIndex, state.colIndex, false);
|
break;
|
}
|
case '5': {
|
// 向左合并
|
mergeTableCol(element, 1);
|
break;
|
}
|
case '6': {
|
// 向右合并
|
mergeTableCol(element);
|
break;
|
}
|
case '7': {
|
// 合并整行
|
mergeWholeCol();
|
break;
|
}
|
case '8': {
|
// 向上合并
|
mergeTableRow(1);
|
break;
|
}
|
case '9': {
|
// 向下合并
|
mergeTableRow();
|
break;
|
}
|
case '10': {
|
// 合并整列
|
mergeWholeRow();
|
break;
|
}
|
case '11': {
|
// 撤销行合并
|
undoMergeCol();
|
break;
|
}
|
case '12': {
|
// 撤销列合并
|
undoMergeRow();
|
break;
|
}
|
case '13': {
|
// 删除整列
|
deleteWholeCol();
|
break;
|
}
|
case '14': {
|
// 删除整行
|
deleteWholeRow();
|
break;
|
}
|
default: {
|
break;
|
}
|
}
|
resetData();
|
}
|
function resetData() {
|
state.rowIndex = 0;
|
state.colIndex = 0;
|
state.rowData = [];
|
state.colData = [];
|
state.selectCell = {
|
__config__: {
|
rowspan: 1,
|
colspan: 1,
|
},
|
};
|
}
|
function handleShowMenu(element, rowIndex, colIndex) {
|
state.rowIndex = rowIndex;
|
state.colIndex = colIndex;
|
state.rowData = element.__config__.children;
|
state.colData = state.rowData[rowIndex].__config__.children;
|
state.selectCell = state.colData[colIndex];
|
}
|
function initData() {
|
if (typeof props.conf === 'object' && props.conf !== null) {
|
state.drawingList = cloneDeep(props.conf.fields);
|
Object.assign(state.formConf, props.conf);
|
if (state.drawingList.length) activeFormItem(state.drawingList[0]);
|
} else {
|
state.drawingList = [];
|
}
|
addLocalRecord(state.drawingList);
|
}
|
function handleAddTableComponent(item, parent) {
|
drawingItemCopy(item, parent, false);
|
}
|
// 获取模板列表
|
function getKitList() {
|
getKitSelector().then((res) => {
|
const list = res.data;
|
for (const element of list) {
|
element.children = element.children.map((o) => {
|
const formData = o.formData ? JSON.parse(o.formData) : null;
|
return { ...o, fields: formData ? formData.fields : [] };
|
});
|
}
|
state.kitList = list;
|
state.leftKitActiveKey = list.map((o) => o.id);
|
});
|
}
|
// 另存为模板
|
function openKitForm() {
|
getData()
|
.then((res: any) => {
|
if (res.errorList?.length) {
|
emit('showValidatePopover', res.errorList);
|
return;
|
}
|
const formData = cloneDeep(defaultFormConf);
|
formData.fields = (res as any)?.formData?.fields || [];
|
const data = {
|
formData: JSON.stringify(formData),
|
fullName: props.formInfo?.fullName,
|
enCode: props.formInfo?.enCode + buildBitUUID(),
|
category: props.formInfo?.category,
|
};
|
openKitFormModal(true, data);
|
})
|
.catch((error) => {
|
error?.message && createMessage.warning(error.message);
|
});
|
}
|
function activeFormItemById(id) {
|
const loop = (list) => {
|
for (const e of list) {
|
if (e.__config__.formId === id) {
|
state.activeData = e;
|
state.activeId = e.__config__.formId;
|
break;
|
}
|
const config = e.__config__;
|
if (config && config.children && Array.isArray(config.children)) {
|
loop(config.children);
|
}
|
}
|
};
|
loop(state.drawingList);
|
}
|
function setTabActiveKey(data) {
|
unref(rightPanelRef)?.setTabActiveKey(data);
|
}
|
function handleAiConfirm(list) {
|
openAiFieldModal(true, { list });
|
}
|
function addComponentByAi(list) {
|
list.forEach((o) => addComponent(o));
|
}
|
|
onMounted(() => {
|
state.leftActiveKey = ['1', '2', '3', '4'];
|
initRedo();
|
initData();
|
state.leftTabActiveKey = '1';
|
if (!props.formInfo?.isKit) getKitList();
|
});
|
</script>
|
<template>
|
<div class="jnpf-basic-generator">
|
<div class="left-board common-board">
|
<a-tabs v-model:active-key="leftTabActiveKey" :tab-bar-gutter="10" class="average-tabs" v-if="!formInfo.isKit">
|
<a-tab-pane key="1" tab="控件" />
|
<a-tab-pane key="2" tab="模板" />
|
</a-tabs>
|
<ScrollContainer>
|
<a-collapse v-model:active-key="leftActiveKey" expand-icon-position="end" ghost v-show="leftTabActiveKey === '1'">
|
<a-collapse-panel :header="item.title" :key="item.id" v-for="item in leftComponents" class="components-list">
|
<draggable
|
class="components-draggable"
|
v-model="item.list"
|
item-key="__config__.jnpfKey"
|
filter=".disabled"
|
:sort="false"
|
draggable=".components-item"
|
:clone="cloneComponent"
|
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
|
@end="onLeftEnd">
|
<template #item="{ element }">
|
<div class="components-item" :class="{ disabled: element.__config__.dragDisabled }" @click="addComponent(element)">
|
<div class="components-body">
|
<i :class="element.__config__.tagIcon"></i>
|
{{ element.__config__.label }}
|
<BasicHelp text="不支持代码生成" v-if="['calculate', 'dateCalculate'].includes(element.__config__.jnpfKey)" />
|
</div>
|
</div>
|
</template>
|
</draggable>
|
</a-collapse-panel>
|
</a-collapse>
|
<a-collapse v-model:active-key="leftKitActiveKey" expand-icon-position="end" ghost v-show="leftTabActiveKey === '2'">
|
<a-collapse-panel :header="item.fullName" :key="item.id" v-for="item in state.kitList" class="components-list">
|
<draggable
|
class="components-draggable"
|
v-model="item.children"
|
item-key="id"
|
filter=".disabled"
|
:sort="false"
|
draggable=".components-item"
|
:clone="cloneKit"
|
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
|
@end="onLeftKitEnd">
|
<template #item="{ element }">
|
<div class="components-item" @click="addKit(element)">
|
<div class="components-body">
|
<i :class="element.icon"></i>
|
<span>{{ element.fullName }}</span>
|
</div>
|
</div>
|
</template>
|
</draggable>
|
</a-collapse-panel>
|
</a-collapse>
|
</ScrollContainer>
|
</div>
|
<div class="center-board common-board">
|
<div class="action-bar">
|
<div class="action-bar-left">
|
<div class="jnpf-device-switch">
|
<div class="jnpf-device-switch-item" :class="{ 'jnpf-device-switch-item--active': showType === 'pc' }" @click="state.showType = 'pc'">
|
<a-tooltip title="PC">
|
<i class="icon-ym icon-ym-pc"></i>
|
</a-tooltip>
|
</div>
|
<div class="jnpf-device-switch-item" :class="{ 'jnpf-device-switch-item--active': showType === 'app' }" @click="state.showType = 'app'">
|
<a-tooltip title="APP">
|
<i class="icon-ym icon-ym-mobile"></i>
|
</a-tooltip>
|
</div>
|
</div>
|
</div>
|
<div class="action-bar-right">
|
<a-tooltip :title="$t('common.undoText')">
|
<a-button type="text" class="action-bar-btn" :disabled="!getCanUndo" @click="handleUndo(replaceDrawingList)">
|
<i class="icon-ym icon-ym-undo"></i>
|
</a-button>
|
</a-tooltip>
|
<a-tooltip :title="$t('common.redoText')">
|
<a-button type="text" class="action-bar-btn" :disabled="!getCanRedo" @click="handleRedo(replaceDrawingList)">
|
<i class="icon-ym icon-ym-redo"></i>
|
</a-button>
|
</a-tooltip>
|
<a-divider type="vertical" class="action-bar-divider" />
|
<a-tooltip :title="$t('common.cleanText')">
|
<a-button type="text" class="action-bar-btn" @click="handleClear">
|
<i class="icon-ym icon-ym-clean"></i>
|
</a-button>
|
</a-tooltip>
|
<a-tooltip :title="$t('common.previewText')">
|
<a-button type="text" class="action-bar-btn" @click="handlePreview">
|
<i class="icon-ym icon-ym-video-play"></i>
|
</a-button>
|
</a-tooltip>
|
<AiChatPopover @confirm="handleAiConfirm" />
|
<a-tooltip title="存为模板" v-if="!formInfo.isKit">
|
<a-button type="text" class="action-bar-btn" @click="openKitForm">
|
<i class="icon-ym icon-ym-save"></i>
|
</a-button>
|
</a-tooltip>
|
</div>
|
</div>
|
<div class="center-board-main">
|
<ScrollContainer v-show="showType === 'pc'">
|
<a-row class="center-board-row" :gutter="formConf.gutter || 15">
|
<a-form
|
:colon="formConf.colon"
|
:size="formConf.size"
|
:layout="formConf.labelPosition === 'top' ? 'vertical' : 'horizontal'"
|
:label-align="formConf.labelPosition === 'right' ? 'right' : 'left'">
|
<draggable class="drawing-board" v-model="drawingList" :animation="300" group="componentsGroup" item-key="renderKey" @end="onCenterEnd">
|
<template #item="{ element, index }">
|
<DraggableItem
|
:key="element.renderKey"
|
:drawing-list="drawingList"
|
:element="element"
|
:index="index"
|
:active-id="activeId"
|
:form-conf="formConf"
|
@active-item="activeFormItem"
|
@copy-item="drawingItemCopy"
|
@delete-item="drawingItemDelete"
|
:merge-left-col-disabled="mergeLeftColDisabled"
|
:merge-right-col-disabled="mergeRightColDisabled"
|
:merge-whole-row-disabled="mergeWholeRowDisabled"
|
:merge-above-row-disabled="mergeAboveRowDisabled"
|
:merge-below-row-disabled="mergeBelowRowDisabled"
|
:merge-whole-col-disabled="mergeWholeColDisabled"
|
:undo-merge-row-disabled="undoMergeRowDisabled"
|
:undo-merge-col-disabled="undoMergeColDisabled"
|
:delete-whole-col-disabled="deleteWholeColDisabled"
|
:delete-whole-row-disabled="deleteWholeRowDisabled"
|
@add-row="handleTableAddRow"
|
@add-col="handleTableAddCol"
|
@handle-table-setting="handleTableSetting"
|
@handle-show-menu="handleShowMenu"
|
:put="shouldClone"
|
:end="onTableEnd" />
|
</template>
|
</draggable>
|
<div v-show="!drawingList.length" class="empty-info">
|
<img :src="emptyImg" class="empty-img" />
|
</div>
|
</a-form>
|
</a-row>
|
</ScrollContainer>
|
<div class="ipad" v-show="showType === 'app'">
|
<div class="outerIpad">
|
<div class="ipadBody">
|
<ScrollContainer>
|
<a-row class="center-board-row">
|
<a-form
|
:colon="false"
|
:size="formConf.size"
|
:layout="formConf.labelPosition === 'top' ? 'vertical' : 'horizontal'"
|
:label-align="formConf.labelPosition === 'right' ? 'right' : 'left'">
|
<draggable class="drawing-board" v-model="drawingList" :animation="300" group="componentsGroup" item-key="renderKey" @end="onCenterEnd">
|
<template #item="{ element, index }">
|
<DraggableItem
|
show-type="app"
|
:key="element.renderKey"
|
:drawing-list="drawingList"
|
:element="element"
|
:index="index"
|
:active-id="activeId"
|
:form-conf="formConf"
|
@active-item="activeFormItem"
|
@copy-item="drawingItemCopy"
|
@delete-item="drawingItemDelete"
|
:put="debounceShouldClone"
|
:end="onTableEnd" />
|
</template>
|
</draggable>
|
<div v-show="!drawingList.length" class="empty-info app-empty-info">
|
<img :src="emptyImg" class="empty-img" />
|
</div>
|
</a-form>
|
</a-row>
|
</ScrollContainer>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<RightPanel
|
ref="rightPanelRef"
|
v-bind="getRightPanelBind"
|
@relation-change="onRelationChange"
|
@add-table-component="handleAddTableComponent"
|
@active-form-item="activeFormItem" />
|
<PreviewModal @register="registerPreviewModal" :form-conf="formConf" />
|
<KitForm @register="registerKitForm" @reload="getKitList" />
|
<AiFieldModal @register="registerAiFieldModal" @reload="addComponentByAi" />
|
</div>
|
</template>
|