From 99328a50e55708f73aab5a17d2051162bbc5e70a Mon Sep 17 00:00:00 2001 From: baiqi Date: Tue, 4 Jun 2024 19:45:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=84=91=E5=9B=BE):=20=E8=84=91=E5=9B=BE?= =?UTF-8?q?=E6=8B=96=E6=8B=BD=E6=8B=A6=E6=88=AA&=E7=A9=BA=E7=99=BD?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E4=BF=9D=E5=AD=98&=E9=83=A8=E5=88=86=20bug?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ms-minders/featureCaseMinder/index.vue | 275 ++++++++++++++---- .../business/ms-minders/locale/en-US.ts | 1 + .../business/ms-minders/locale/zh-CN.ts | 1 + .../hooks/useMinderEventListener.ts | 3 +- .../pure/ms-minder-editor/main/mainEditor.vue | 123 ++++---- .../ms-minder-editor/menu/edit/editDel.vue | 12 +- .../ms-minder-editor/menu/edit/tagBox.vue | 14 +- .../components/pure/ms-minder-editor/props.ts | 36 +-- .../ms-minder-editor/script/tool/utils.ts | 38 +-- .../src/models/caseManagement/featureCase.ts | 11 +- .../modules/components/minder-editor/index.ts | 13 +- .../modules/components/minder-editor/types.ts | 2 +- frontend/src/utils/index.ts | 4 +- .../views/api-test/components/paramTable.vue | 10 +- 14 files changed, 361 insertions(+), 182 deletions(-) diff --git a/frontend/src/components/business/ms-minders/featureCaseMinder/index.vue b/frontend/src/components/business/ms-minders/featureCaseMinder/index.vue index 5959a5bd25..97cb7e97cf 100644 --- a/frontend/src/components/business/ms-minders/featureCaseMinder/index.vue +++ b/frontend/src/components/business/ms-minders/featureCaseMinder/index.vue @@ -94,9 +94,11 @@ const stepTag = t('ms.minders.stepDesc'); const textTag = t('ms.minders.textDesc'); const prerequisiteTag = t('ms.minders.precondition'); - const remarkTag = t('common.remark'); + const stepExpectTag = t('ms.minders.stepExpect'); + const remarkTag = t('ms.minders.remark'); const descTags = [stepTag, textTag]; const caseChildTags = [prerequisiteTag, stepTag, textTag, remarkTag]; + const caseOffspringTags = [...caseChildTags, stepTag, stepExpectTag, textTag, remarkTag]; const importJson = ref({ root: {} as MinderJsonNode, template: 'default', @@ -110,6 +112,7 @@ updateCaseList: [], updateModuleList: [], deleteResourceList: [], + additionalNodeList: [], }); const templateId = ref(''); @@ -153,6 +156,7 @@ id: 'NONE', text: t('ms.minders.allModule'), resource: [moduleTag], + disabled: true, }, }; window.minder.importJson(importJson.value); @@ -217,31 +221,31 @@ let remarkNode: MinderJsonNode | undefined; // 备注 const stepNodes: MinderJsonNode[] = []; // 步骤描述 node.children?.forEach((item) => { - if (item.data.resource?.includes(textTag)) { + if (item.data?.resource?.includes(textTag)) { textStep = item; - } else if (item.data.resource?.includes(stepTag)) { + } else if (item.data?.resource?.includes(stepTag)) { stepNodes.push(item); - } else if (item.data.resource?.includes(prerequisiteTag)) { + } else if (item.data?.resource?.includes(prerequisiteTag)) { prerequisiteNode = item; - } else if (item.data.resource?.includes(remarkTag)) { + } else if (item.data?.resource?.includes(remarkTag)) { remarkNode = item; } }); const steps: FeatureCaseMinderStepItem[] = stepNodes.map((child, i) => { return { - id: child.data.id, + id: child.data?.id || getGenerateId(), num: i, - desc: child.data.text, - result: child.children?.[0].data.text || '', + desc: child.data?.text || '', + result: child.children?.[0].data?.text || '', }; }); return { - prerequisite: prerequisiteNode?.data.text || '', + prerequisite: prerequisiteNode?.data?.text || '', caseEditType: steps.length > 0 ? 'STEP' : ('TEXT' as FeatureCaseMinderEditType), steps: JSON.stringify(steps), - textDescription: textStep?.data.text || '', - expectedResult: textStep?.children?.[0]?.data.text || '', - description: remarkNode?.data.text || '', + textDescription: textStep?.data?.text || '', + expectedResult: textStep?.children?.[0]?.data?.text || '', + description: remarkNode?.data?.text || '', }; } @@ -250,15 +254,14 @@ * @param node 节点 * @param parent 父节点 */ - function getNodeMoveInfo(node: MinderJsonNode, parent?: MinderJsonNode): { moveMode: MoveMode; targetId?: string } { - const nodeIndex = parent?.children?.findIndex((e) => e.data.id === node.data.id); - const moveMode = nodeIndex === 0 ? 'BEFORE' : 'AFTER'; + function getNodeMoveInfo(nodeIndex: number, parent?: MinderJsonNode): { moveMode: MoveMode; targetId?: string } { + const moveMode = nodeIndex === 0 ? 'BEFORE' : 'AFTER'; // 除了第一个以外,其他都是在目标节点后面插入 return { moveMode, targetId: moveMode === 'BEFORE' - ? parent?.children?.[1]?.data.id - : parent?.children?.[(nodeIndex || parent.children.length - 1) - 1]?.data.id, + ? parent?.children?.[1]?.data?.id + : parent?.children?.[(nodeIndex || parent.children.length - 1) - 1]?.data?.id, }; } @@ -267,17 +270,19 @@ */ function makeMinderParams(): FeatureCaseMinderUpdateParams { const fullJson: MinderJson = window.minder.exportJson(); - filterTree(fullJson.root.children, (node, parent) => { + filterTree(fullJson.root.children, (node, nodeIndex, parent) => { if (node.data.isNew !== false || node.data.changed === true) { if (node.data.resource?.includes(moduleTag)) { + // 处理模块节点 tempMinderParams.value.updateModuleList.push({ id: node.data.id, name: node.data.text, parentId: parent?.data.id || 'NONE', type: node.data.isNew !== false ? 'ADD' : 'UPDATE', - ...getNodeMoveInfo(node as MinderJsonNode, parent as MinderJsonNode), + ...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode), }); } else if (node.data.resource?.includes(caseTag)) { + // 处理用例节点 const caseNodeInfo = getCaseNodeInfo(node as MinderJsonNode); const caseBaseInfo = baseInfoRef.value?.makeParams(); tempMinderParams.value.updateCaseList.push({ @@ -288,10 +293,19 @@ tags: caseBaseInfo?.tags || [], customFields: caseBaseInfo?.customFields || [], name: caseBaseInfo?.name || node.data.text, - ...getNodeMoveInfo(node as MinderJsonNode, parent as MinderJsonNode), + ...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode), ...caseNodeInfo, }); return false; // 用例的子孙节点已经处理过,跳过 + } else if (!node.data.resource) { + // 处理文本节点 + tempMinderParams.value.additionalNodeList.push({ + id: node.data.id, + parentId: parent?.data.id || 'NONE', + type: node.data.isNew !== false ? 'ADD' : 'UPDATE', + name: node.data.text, + ...getNodeMoveInfo(nodeIndex, parent as MinderJsonNode), + }); } } return true; @@ -316,7 +330,7 @@ * 已选中节点的可替换标签判断 * @param node 选中节点 */ - function replaceableTags(node: MinderJsonNode, nodes: MinderJsonNode[]) { + function replaceableTags(nodes: MinderJsonNode[]) { if (nodes.length > 1) { // 选中的节点大于 1 时 if (nodes.some((e) => (e.data?.resource || []).length > 0)) { @@ -328,10 +342,11 @@ return [moduleTag]; } } + const node = nodes[0]; if ( Object.keys(node.data || {}).length === 0 || node.data?.id === 'root' || - (node.parent?.data.resource || []).length === 0 + (node.parent?.data?.resource || []).length === 0 ) { // 没有数据的节点、默认模块节点、父节点为文本节点的节点不可替换标签 return []; @@ -357,18 +372,21 @@ // 选中节点无标签,且父节点为用例节点,可替换用例下级标签 return caseChildTags; } - if ( - (!node.data?.resource || node.data.resource.length === 0) && - (!node.parent?.data?.resource || - node.parent?.data?.resource.length === 0 || - node.parent?.data?.resource?.some((e) => topTags.includes(e))) - ) { - // 如果选中节点子级含有用例节点或模块节点,则不可将选中节点标记为用例 - return node.children && - (node.children.some((e) => e.data?.resource?.includes(caseTag)) || - node.children.some((e) => e.data?.resource?.includes(moduleTag))) - ? topTags.filter((e) => e !== caseTag) - : topTags; + if ((!node.data?.resource || node.data.resource.length === 0) && node.parent?.data?.resource?.includes(moduleTag)) { + // 选中节点是文本节点、选中节点的父节点是模块节点 + if ( + (node.children && + (node.children.some((e) => e.data?.resource?.includes(caseTag)) || + node.children.some((e) => e.data?.resource?.includes(moduleTag)))) || + node.parent?.data?.id === 'NONE' + ) { + // 如果选中节点子级含有用例节点或模块节点,或者选中节点的父节点是根节点 NONE,只能将节点标记为模块节点 + return [moduleTag]; + } + if (!node.children || node.children.length === 0) { + // 如果选中节点无子级,可标记为用例节点或模块节点 + return topTags; + } } return []; } @@ -383,7 +401,22 @@ window.minder.execCommand(command, node); nextTick(() => { const newNode: MinderJsonNode = window.minder.getSelectedNode(); + if (!newNode.data) { + newNode.data = { + id: getGenerateId(), + text: '', + }; + } newNode.data.isNew = true; // 新建的节点标记为新建 + if (newNode.data?.resource?.some((e) => caseOffspringTags.includes(e))) { + // 用例子孙节点更新,标记用例节点变化 + if (newNode.parent?.data?.resource?.includes(caseTag)) { + newNode.parent.data.changed = true; + } else if (newNode.parent?.parent?.data?.resource?.includes(caseTag)) { + // 期望结果是第三层节点 + newNode.parent.parent.data.changed = true; + } + } }); } } @@ -437,8 +470,8 @@ parent: node, data: { id: getGenerateId(), - text: t('ms.minders.stepDesc'), - resource: [t('ms.minders.stepDesc')], + text: stepTag, + resource: [stepTag], isNew: true, }, children: [], @@ -447,8 +480,8 @@ parent: child, data: { id: getGenerateId(), - text: t('ms.minders.stepExpect'), - resource: [t('ms.minders.stepExpect')], + text: stepExpectTag, + resource: [stepExpectTag], isNew: true, }, }; @@ -468,8 +501,8 @@ parent: node, data: { id: getGenerateId(), - text: t('ms.minders.stepExpect'), - resource: [t('ms.minders.stepExpect')], + text: stepExpectTag, + resource: [stepExpectTag], isNew: true, }, children: [], @@ -587,14 +620,22 @@ if (tag === moduleTag && node.data) { // 排除是从用例节点切换到模块节点的数据 tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter( - (e) => e.id !== node.data.id + (e) => e.id !== node.data?.id ); window.minder.execCommand('priority'); - } else if (node.data.resource?.includes(caseTag)) { + } else if (node.data?.resource?.includes(caseTag)) { // 排除是从模块节点切换到用例节点的数据 tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter( - (e) => e.id !== node.data.id + (e) => e.id !== node.data?.id ); + } else if (node.data?.resource?.some((e) => caseOffspringTags.includes(e))) { + // 用例子孙节点更新,标记用例节点变化 + if (node.parent?.data?.resource?.includes(caseTag)) { + node.parent.data.changed = true; + } else if (node.parent?.parent?.data?.resource?.includes(caseTag)) { + // 期望结果是第三层节点 + node.parent.parent.data.changed = true; + } } } const baseInfoLoading = ref(false); @@ -792,9 +833,12 @@ node.expand(); node.renderTree(); window.minder.layout(); + window.minder.execCommand('camera', node, 600); if (node.data) { node.data.isLoaded = true; } + // 加载完用例数据后,更新当前importJson数据 + importJson.value = window.minder.exportJson(); } catch (error) { // eslint-disable-next-line no-console console.log(error); @@ -812,25 +856,33 @@ * @param event 脑图事件对象 */ function handleAction(event: MinderCustomEvent) { - const { node, name } = event; - if (node) { + const { nodes, name } = event; + if (nodes && nodes.length > 0) { switch (name) { case MinderEventName.DELETE_NODE: - tempMinderParams.value.deleteResourceList.push({ - id: node.data.id, - type: node.data?.resource?.[0] || moduleTag, + // TODO:循环优化 + nodes.forEach((node) => { + tempMinderParams.value.deleteResourceList.push({ + id: node.data?.id || getGenerateId(), + type: node.data?.resource?.[0] || moduleTag, + }); + if (node.data?.resource?.includes(caseTag)) { + // 删除用例节点 + tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter( + (e) => e.id !== node.data?.id + ); + } else if (node.data?.resource?.includes(moduleTag)) { + // 删除模块节点 + tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter( + (e) => e.id !== node.data?.id + ); + } else if (!node.data?.resource) { + // 删除文本节点 + tempMinderParams.value.additionalNodeList = tempMinderParams.value.additionalNodeList.filter( + (e) => e.id !== node.data?.id + ); + } }); - if (node.data?.resource?.includes(caseTag)) { - // 删除用例节点 - tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter( - (e) => e.id !== node.data.id - ); - } else if (node.data?.resource?.includes(moduleTag)) { - // 删除模块节点 - tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter( - (e) => e.id !== node.data.id - ); - } break; default: break; @@ -838,10 +890,111 @@ } } + /** + * 是否停止拖拽动作 + * @param dragNode 拖动节点 + * @param dropNode 目标节点 + * @param mode 拖拽模式 + */ + function stopDrag( + dragNodes: MinderJsonNode | MinderJsonNode[], + dropNode: MinderJsonNode, + mode: 'movetoparent' | 'arrange' + ) { + if (!Array.isArray(dragNodes)) { + dragNodes = [dragNodes]; + } + for (let i = 0; i < dragNodes.length; i++) { + const dragNode = (dragNodes as MinderJsonNode[])[i]; + if (mode === 'movetoparent') { + // 拖拽到目标节点内 + if (dragNode.data?.resource?.includes(caseTag) && dropNode.data?.id === 'NONE') { + // 用例不能拖拽到根模块节点内 + return true; + } + if ( + (dragNode.data?.resource?.includes(moduleTag) || dragNode.data?.resource?.includes(caseTag)) && + dropNode.data?.resource?.includes(moduleTag) + ) { + // 模块、用例只能拖拽到模块节点内 + if (dragNode.data) { + dragNode.data.changed = true; + } + return false; + } + if (!dragNode.data?.resource && (dropNode.data?.resource?.includes(moduleTag) || !dropNode.data?.resource)) { + // 文本节点只能拖拽到模块、文本节点内 + if (dragNode.data) { + dragNode.data.changed = true; + } + return false; + } + if ( + dragNode.data?.resource?.some((e) => caseChildTags.includes(e)) && + dropNode.data?.resource?.includes(caseTag) && + dragNode.parent?.data?.id === dropNode.data?.id + ) { + // 一个用例下的子节点只能拖拽到它自身内 + if (dragNode.parent?.data) { + dragNode.parent.data.changed = true; + } + return false; + } + } else if (mode === 'arrange') { + // 拖拽到目标节点前后 + if ( + (dragNode.data?.resource?.includes(moduleTag) || + dragNode.data?.resource?.includes(caseTag) || + !dragNode.data?.resource) && + (dropNode.data?.resource?.includes(moduleTag) || + dropNode.data?.resource?.includes(caseTag) || + !dropNode.data?.resource) + ) { + if (dragNode.data) { + dragNode.data.changed = true; + } + // 模块、用例、文本节点只能拖拽到模块、用例、文本节点前后 + return false; + } + if (dragNode.data?.resource?.includes(stepTag) && dropNode.data?.resource?.includes(stepTag)) { + if (dragNode.parent?.data) { + dragNode.parent.data.changed = true; + } + // 用例节点下的步骤节点之间拖拽排序 + return false; + } + } + } + return true; + } + + /** + * 脑图命令执行前拦截 + * @param event 命令执行事件 + */ function handleBeforeExecCommand(event: MinderEvent) { if (event.commandName === 'movetoparent') { - // TODO:拖拽拦截 - event.stopPropagation(); + // 拖拽到节点内拦截 + if (stopDrag(event.commandArgs[0] as MinderJsonNode, event.commandArgs[1] as MinderJsonNode, 'movetoparent')) { + event.stopPropagation(); + } + } else if (event.commandName === 'arrange') { + // 拖拽排序拦截 + const dragNodes: MinderJsonNode[] = window.minder.getSelectedNodes(); + let dropNode: MinderJsonNode; + if (dragNodes[0].parent?.children?.[event.commandArgs[0] as number]) { + // 释放到目标节点后 + dropNode = dragNodes[0].parent?.children?.[event.commandArgs[0] as number]; + } else if (dragNodes[0].parent?.children?.[(event.commandArgs[0] as number) - 1]) { + // 释放到目标节点前 + dropNode = dragNodes[0].parent?.children?.[(event.commandArgs[0] as number) - 1]; + } else { + // 释放到最后一个节点 + dropNode = dragNodes[dragNodes.length - 1]; + } + if (stopDrag(dragNodes, dropNode, 'arrange')) { + event.stopPropagation(); + } } } diff --git a/frontend/src/components/business/ms-minders/locale/en-US.ts b/frontend/src/components/business/ms-minders/locale/en-US.ts index 1e00bb908b..1578743352 100644 --- a/frontend/src/components/business/ms-minders/locale/en-US.ts +++ b/frontend/src/components/business/ms-minders/locale/en-US.ts @@ -4,6 +4,7 @@ export default { 'ms.minders.stepDesc': 'Step Description', 'ms.minders.stepExpect': 'Expected Result', 'ms.minders.textDesc': 'Text Description', + 'ms.minders.remark': 'Remark Information', 'ms.minders.caseName': 'Test Case Name', 'ms.minders.caseNameNotNull': 'Test Case Name cannot be empty', 'ms.minders.commentTotal': '{num} Comments in Total', diff --git a/frontend/src/components/business/ms-minders/locale/zh-CN.ts b/frontend/src/components/business/ms-minders/locale/zh-CN.ts index 2258f49b68..dd3dfcef8b 100644 --- a/frontend/src/components/business/ms-minders/locale/zh-CN.ts +++ b/frontend/src/components/business/ms-minders/locale/zh-CN.ts @@ -4,6 +4,7 @@ export default { 'ms.minders.stepDesc': '步骤描述', 'ms.minders.stepExpect': '预期结果', 'ms.minders.textDesc': '文本描述', + 'ms.minders.remark': '备注信息', 'ms.minders.caseName': '用例名称', 'ms.minders.caseNameNotNull': '用例名称不能为空', 'ms.minders.commentTotal': '共 {num} 评论', diff --git a/frontend/src/components/pure/ms-minder-editor/hooks/useMinderEventListener.ts b/frontend/src/components/pure/ms-minder-editor/hooks/useMinderEventListener.ts index 125905da48..dbabfc849a 100644 --- a/frontend/src/components/pure/ms-minder-editor/hooks/useMinderEventListener.ts +++ b/frontend/src/components/pure/ms-minder-editor/hooks/useMinderEventListener.ts @@ -44,7 +44,8 @@ export default function useEventListener(listener: UseEventListenerProps) { // console.log('dragFinish', minder.history); // }); - minder.on('beforeExecCommand', (e: any) => { + // 监听脑图执行命令前(可通过e.stopPropagation拦截命令执行) + minder.on('beforeExecCommand', (e: MinderEvent) => { if (listener.handleBeforeExecCommand) { listener.handleBeforeExecCommand(e); } diff --git a/frontend/src/components/pure/ms-minder-editor/main/mainEditor.vue b/frontend/src/components/pure/ms-minder-editor/main/mainEditor.vue index 3eff72c0f1..abd1086e01 100644 --- a/frontend/src/components/pure/ms-minder-editor/main/mainEditor.vue +++ b/frontend/src/components/pure/ms-minder-editor/main/mainEditor.vue @@ -84,7 +84,7 @@ import { useI18n } from '@/hooks/useI18n'; import useMinderStore from '@/store/modules/components/minder-editor'; - import { findNodePathByKey } from '@/utils'; + import { findNodePathByKey, getGenerateId } from '@/utils'; import { MinderEventName } from '@/enums/minderEnum'; @@ -94,6 +94,7 @@ mainEditorProps, MinderJson, MinderJsonNode, + MinderJsonNodeData, priorityProps, tagProps, } from '../props'; @@ -214,14 +215,6 @@ init(); }); - watch( - () => props.importJson, - (val) => { - innerImportJson.value = val; - window.minder.importJson(val); - } - ); - const menuVisible = ref(false); const menuPopupOffset = ref([0, 0]); @@ -229,12 +222,17 @@ * 切换脑图展示的节点层级 * @param node 切换的节点 */ - function switchNode(node: MinderJsonNode) { - innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.data?.id, 'data', 'id')); + function switchNode(node: MinderJsonNode | MinderJsonNodeData) { + if (node.data) { + innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.data.id, 'data', 'id')); + } else { + innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.id, 'data', 'id')); + } innerImportJson.value.data.expandState = 'expand'; window.minder.importJson(innerImportJson.value); - window.minder.execCommand('template', Object.keys(window.kityminder.Minder.getTemplateList())[minderStore.mold]); - minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, node); + setTimeout(() => { + window.minder.execCommand('camera', window.minder.getRoot(), 600); + }, 100); // TODO:暂未知渲染时机,临时延迟解决 } watch( @@ -248,8 +246,8 @@ ]; menuVisible.value = true; } - if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.node) { - switchNode(minderStore.event.node); + if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.nodes) { + switchNode(minderStore.event.nodes[0]); } } ); @@ -268,6 +266,12 @@ window.minder.execCommand(command); nextTick(() => { const newNode: MinderJsonNode = window.minder.getSelectedNode(); + if (!newNode.data) { + newNode.data = { + id: getGenerateId(), + text: '', + }; + } newNode.data.isNew = true; // 新建的节点标记为新建 }); } @@ -278,49 +282,52 @@ * @param val 选择的菜单项 */ function handleMinderMenuSelect(val: string | number | Record | undefined) { - const selectedNode = window.minder.getSelectedNode(); - switch (val) { - case 'expand': - if (selectedNode.data.expandState === 'collapse') { - window.minder.execCommand('Expand'); - } else { - window.minder.execCommand('Collapse'); - } - minderStore.dispatchEvent(MinderEventName.EXPAND, undefined, undefined, selectedNode); - break; - case 'insetParent': - execInsertCommand('AppendParentNode'); - minderStore.dispatchEvent(MinderEventName.INSERT_PARENT, undefined, undefined, selectedNode); - break; - case 'insetSon': - execInsertCommand('AppendChildNode'); - minderStore.dispatchEvent(MinderEventName.INSERT_CHILD, undefined, undefined, selectedNode); - break; - case 'insetBrother': - execInsertCommand('AppendSiblingNode'); - minderStore.dispatchEvent(MinderEventName.INSERT_SIBLING, undefined, undefined, selectedNode); - break; - case 'copy': - window.minder.execCommand('Copy'); - minderStore.dispatchEvent(MinderEventName.COPY_NODE, undefined, undefined, selectedNode); - break; - case 'cut': - window.minder.execCommand('Cut'); - minderStore.dispatchEvent(MinderEventName.CUT_NODE, undefined, undefined, selectedNode); - break; - case 'paste': - window.minder.execCommand('Paste'); - minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, selectedNode); - break; - case 'delete': - window.minder.execCommand('RemoveNode'); - minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, selectedNode); - break; - case 'enterNode': - switchNode(selectedNode.data); - break; - default: - break; + const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes(); + if (selectedNodes.length > 0) { + switch (val) { + case 'expand': + if (selectedNodes.some((node) => node.data?.expandState === 'collapse')) { + window.minder.execCommand('Expand'); + } else { + window.minder.execCommand('Collapse'); + } + minderStore.dispatchEvent(MinderEventName.EXPAND, undefined, undefined, selectedNodes); + break; + case 'insetParent': + execInsertCommand('AppendParentNode'); + minderStore.dispatchEvent(MinderEventName.INSERT_PARENT, undefined, undefined, selectedNodes); + break; + case 'insetSon': + execInsertCommand('AppendChildNode'); + minderStore.dispatchEvent(MinderEventName.INSERT_CHILD, undefined, undefined, selectedNodes); + break; + case 'insetBrother': + execInsertCommand('AppendSiblingNode'); + minderStore.dispatchEvent(MinderEventName.INSERT_SIBLING, undefined, undefined, selectedNodes); + break; + case 'copy': + window.minder.execCommand('Copy'); + minderStore.dispatchEvent(MinderEventName.COPY_NODE, undefined, undefined, selectedNodes); + break; + case 'cut': + window.minder.execCommand('Cut'); + minderStore.dispatchEvent(MinderEventName.CUT_NODE, undefined, undefined, selectedNodes); + break; + case 'paste': + window.minder.execCommand('Paste'); + minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, selectedNodes); + break; + case 'delete': + window.minder.execCommand('RemoveNode'); + minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, selectedNodes); + break; + case 'enterNode': + switchNode(selectedNodes[0]); + minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, [selectedNodes[0]]); + break; + default: + break; + } } } diff --git a/frontend/src/components/pure/ms-minder-editor/menu/edit/editDel.vue b/frontend/src/components/pure/ms-minder-editor/menu/edit/editDel.vue index d73d5d24b1..6f23c0e007 100644 --- a/frontend/src/components/pure/ms-minder-editor/menu/edit/editDel.vue +++ b/frontend/src/components/pure/ms-minder-editor/menu/edit/editDel.vue @@ -24,7 +24,7 @@ import { MinderEventName } from '@/enums/minderEnum'; - import { delProps } from '../../props'; + import { delProps, MinderJsonNode } from '../../props'; import { isDeleteDisableNode } from '../../script/tool/utils'; const minderStore = useMinderStore(); @@ -59,19 +59,19 @@ if (removeNodeDisabled.value || !minder.queryCommandState || !minder.execCommand) { return; } - const node = minder.getSelectedNode(); + const nodes: MinderJsonNode[] = minder.getSelectedNodes(); let position: MinderNodePosition | undefined; - if (node) { + if (nodes.length > 0) { if (props.delConfirm) { - props.delConfirm(node); + props.delConfirm(nodes); return; } - const box = node.getRenderBox(); + const box = nodes[0].getRenderBox(); position = { x: box.cx, y: box.cy, }; - minderStore.dispatchEvent(MinderEventName.DELETE_NODE, position, node.rc.node, node.data); + minderStore.dispatchEvent(MinderEventName.DELETE_NODE, position, nodes[0].rc.node, nodes); } minder.forceRemoveNode(); } diff --git a/frontend/src/components/pure/ms-minder-editor/menu/edit/tagBox.vue b/frontend/src/components/pure/ms-minder-editor/menu/edit/tagBox.vue index b2982b34f7..6a6c1eb148 100644 --- a/frontend/src/components/pure/ms-minder-editor/menu/edit/tagBox.vue +++ b/frontend/src/components/pure/ms-minder-editor/menu/edit/tagBox.vue @@ -47,11 +47,10 @@ minder.on('selectionchange', () => { commandDisabled.value = isDisable(); const nodes: MinderJsonNode[] = window.minder.getSelectedNodes(); - const node: MinderJsonNode = minder.getSelectedNode(); if (commandDisabled.value) { tagList.value = []; } else if (props.replaceableTags) { - tagList.value = props.replaceableTags(node, nodes); + tagList.value = props.replaceableTags(nodes); } else { tagList.value = []; } @@ -70,8 +69,8 @@ return; } if (props.tagEditCheck) { - const node: MinderJsonNode = minder.getSelectedNode(); - if (!props.tagEditCheck(node, resourceName)) { + const nodes: MinderJsonNode[] = minder.getSelectedNodes(); + if (!props.tagEditCheck(nodes, resourceName)) { return; } } @@ -100,13 +99,12 @@ } window.minder.execCommand('resource', origin); const nodes: MinderJsonNode[] = window.minder.getSelectedNodes(); - const node: MinderJsonNode = minder.getSelectedNode(); - minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, node); + minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, nodes); if (props.replaceableTags) { - tagList.value = props.replaceableTags(node, nodes); + tagList.value = props.replaceableTags(nodes); } if (props.afterTagEdit) { - props.afterTagEdit(node, resourceName); + props.afterTagEdit(nodes, resourceName); } } diff --git a/frontend/src/components/pure/ms-minder-editor/props.ts b/frontend/src/components/pure/ms-minder-editor/props.ts index e5d7f0285b..d95f9f5892 100644 --- a/frontend/src/components/pure/ms-minder-editor/props.ts +++ b/frontend/src/components/pure/ms-minder-editor/props.ts @@ -6,19 +6,6 @@ import type { MoveMode } from '@/models/common'; import type { PropType } from 'vue'; -export interface MinderClass { - stopPropagation: () => void; // 阻止事件冒泡 - stopPropagationImmediately: () => void; // 立即阻止事件冒泡 - [key: string]: any; // TODO: 其他事件属性 -} -// TODO:脑图事件类型补充 -export interface MinderEvent extends MinderClass { - command: any; - commandArgs: Record[]; - commandName: string; - minder: any; - type: string; -} export interface MinderIconButtonItem { icon: string; tooltip: string; @@ -35,10 +22,11 @@ export interface MinderJsonNodeData { changed?: boolean; // 脑图节点是否发生过变化 moveMode?: MoveMode; // 移动方式(节点移动或新增时需要) targetId?: string; // 目标节点 id(节点移动或新增时需要) + disabled?: boolean; // 是否禁用 [key: string]: any; } export interface MinderJsonNode { - data: MinderJsonNodeData; + data?: MinderJsonNodeData; parent?: MinderJsonNode; children?: MinderJsonNode[]; [key: string]: any; // minder 内置字段 @@ -49,6 +37,20 @@ export interface MinderJson { template: string; treePath: Record[]; } +// 脑图类 +export interface MinderClass { + stopPropagation: () => void; // 阻止事件冒泡 + stopPropagationImmediately: () => void; // 立即阻止事件冒泡 + [key: string]: any; // TODO: 其他事件属性 +} +// TODO:脑图事件类型补充 +export interface MinderEvent extends MinderClass { + command: any; + commandArgs: ((Record & MinderJsonNode) | (Record & MinderJsonNode)[] | number)[]; + commandName: string; + minder: any; + type: string; +} export const mainEditorProps = { importJson: { @@ -122,10 +124,10 @@ export const tagProps = { type: Boolean, default: false, }, - replaceableTags: Function as PropType<(node: MinderJsonNode, nodes: MinderJsonNode[]) => string[]>, + replaceableTags: Function as PropType<(nodes: MinderJsonNode[]) => string[]>, tagDisableCheck: Function, - tagEditCheck: Function as PropType<(node: MinderJsonNode, tag: string) => boolean>, - afterTagEdit: Function as PropType<(node: MinderJsonNode, tag: string) => void>, + tagEditCheck: Function as PropType<(nodes: MinderJsonNode[], tag: string) => boolean>, + afterTagEdit: Function as PropType<(nodes: MinderJsonNode[], tag: string) => void>, }; export const insertProps = { diff --git a/frontend/src/components/pure/ms-minder-editor/script/tool/utils.ts b/frontend/src/components/pure/ms-minder-editor/script/tool/utils.ts index 6b225bc057..6243b3e9f1 100644 --- a/frontend/src/components/pure/ms-minder-editor/script/tool/utils.ts +++ b/frontend/src/components/pure/ms-minder-editor/script/tool/utils.ts @@ -1,41 +1,41 @@ import type { MinderJsonNode } from '../../props'; export function isDisableNode(minder: any) { - let node; + let node: MinderJsonNode; if (minder && minder.getSelectedNode) { node = minder.getSelectedNode(); - } - if (node && node.data.disable === true) { - return true; + if (node && node.data?.disabled === true) { + return true; + } } return false; } export function isDeleteDisableNode(minder: any) { - let node; + let node: MinderJsonNode; if (minder && minder.getSelectedNode) { node = minder.getSelectedNode(); - } - if (node && node.data.disable === true && !node.data.allowDelete) { - return true; + if (node && node.data?.disabled === true && !node.data.allowDelete) { + return true; + } } return false; } export function isTagEnableNode(node: MinderJsonNode) { - if (node && (node.data.tagEnable === true || node.data.allowDisabledTag === true)) { + if (node.data && (node.data.tagEnable === true || node.data.allowDisabledTag === true)) { return true; } return false; } export function isTagEnable(minder: any) { - let node; + let node: MinderJsonNode; if (minder && minder.getSelectedNode) { node = minder.getSelectedNode(); - } - if (isTagEnableNode(node)) { - return true; + if (isTagEnableNode(node)) { + return true; + } } return false; } @@ -46,10 +46,10 @@ export function markChangeNode(node: MinderJsonNode) { } } -function markDelNode(node: MinderJsonNode, deleteChild: any) { +function markDelNode(node: MinderJsonNode, deleteChild: MinderJsonNode) { deleteChild.push(node.data); if (node.children) { - node.children.forEach((child: any) => { + node.children.forEach((child: MinderJsonNode) => { markDelNode(child, deleteChild); }); } @@ -58,9 +58,9 @@ function markDelNode(node: MinderJsonNode, deleteChild: any) { // 在父节点记录删除的节点 export function markDeleteNode(minder: any) { if (minder) { - const nodes = minder.getSelectedNodes(); + const nodes: MinderJsonNode[] = minder.getSelectedNodes(); nodes.forEach((node: MinderJsonNode) => { - if (node && node.parent) { + if (node.parent?.data) { const pData = node.parent.data; if (!pData.deleteChild) { pData.deleteChild = []; @@ -101,7 +101,7 @@ export function setPriorityView(priorityStartWithZero: boolean, priorityPrefix: * 将节点及其子节点id置为null,changed 标记为true * @param node */ -export function resetNodes(nodes: any) { +export function resetNodes(nodes: MinderJsonNode[]) { if (nodes) { nodes.forEach((item: any) => { if (item.data) { @@ -115,7 +115,7 @@ export function resetNodes(nodes: any) { } export function isDisableForNode(node: MinderJsonNode) { - if (node && node.data.disable === true) { + if (node && node.data?.disabled === true) { return true; } return false; diff --git a/frontend/src/models/caseManagement/featureCase.ts b/frontend/src/models/caseManagement/featureCase.ts index aae9720169..da6fbae055 100644 --- a/frontend/src/models/caseManagement/featureCase.ts +++ b/frontend/src/models/caseManagement/featureCase.ts @@ -382,7 +382,15 @@ export interface FeatureCaseMinderUpdateModuleItem { moveMode?: MoveMode; targetId?: string; } - +// 脑图新增/修改的文本节点集合 +export interface FeatureCaseMinderUpdateTextNodeItem { + id: string; + name: string; + parentId: string; + type: FeatureCaseMinderActionType; + moveMode?: MoveMode; + targetId?: string; +} export interface CustomField { fieldId: string; value: string; @@ -419,4 +427,5 @@ export interface FeatureCaseMinderUpdateParams { updateCaseList: FeatureCaseMinderUpdateCaseItem[]; updateModuleList: FeatureCaseMinderUpdateModuleItem[]; deleteResourceList: FeatureCaseMinderDeleteResourceItem[]; + additionalNodeList: FeatureCaseMinderUpdateTextNodeItem[]; } diff --git a/frontend/src/store/modules/components/minder-editor/index.ts b/frontend/src/store/modules/components/minder-editor/index.ts index d32803f980..3132ec14ee 100644 --- a/frontend/src/store/modules/components/minder-editor/index.ts +++ b/frontend/src/store/modules/components/minder-editor/index.ts @@ -17,7 +17,7 @@ const useMinderStore = defineStore('minder', { y: 0, }, nodeDom: undefined, - node: undefined, + nodes: undefined, }, mold: 0, }), @@ -27,15 +27,20 @@ const useMinderStore = defineStore('minder', { * @param name 事件名称 * @param position 触发事件的节点/鼠标位置 * @param nodeDom 节点 DOM - * @param node 节点 + * @param nodes 节点集合 */ - dispatchEvent(name: MinderEventName, position?: MinderNodePosition, nodeDom?: HTMLElement, node?: MinderJsonNode) { + dispatchEvent( + name: MinderEventName, + position?: MinderNodePosition, + nodeDom?: HTMLElement, + nodes?: MinderJsonNode[] + ) { this.event = { name, timestamp: Date.now(), nodePosition: position, nodeDom, - node, + nodes, }; }, setMold(val: number) { diff --git a/frontend/src/store/modules/components/minder-editor/types.ts b/frontend/src/store/modules/components/minder-editor/types.ts index e7ba45cb4a..76db8706a1 100644 --- a/frontend/src/store/modules/components/minder-editor/types.ts +++ b/frontend/src/store/modules/components/minder-editor/types.ts @@ -12,7 +12,7 @@ export interface MinderCustomEvent { timestamp: number; nodePosition?: MinderNodePosition; nodeDom?: HTMLElement; - node?: MinderJsonNode; + nodes?: MinderJsonNode[]; } export interface MinderState { diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index e3fc5c5092..294a6fcd0f 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -281,7 +281,7 @@ export function mapTree( */ export function filterTree( tree: TreeNode | TreeNode[] | T | T[], - filterFn: (node: TreeNode, parent?: TreeNode | null) => boolean, + filterFn: (node: TreeNode, nodeIndex: number, parent?: TreeNode | null) => boolean, customChildrenKey = 'children', parentNode: TreeNode | null = null ): TreeNode[] { @@ -292,7 +292,7 @@ export function filterTree( for (let i = 0; i < tree.length; i++) { const node = (tree as TreeNode[])[i]; // 如果节点满足过滤条件,则保留该节点,并递归过滤子节点 - if (filterFn(node, parentNode)) { + if (filterFn(node, i, parentNode)) { const newNode = cloneDeep(node); if (node[customChildrenKey] && node[customChildrenKey].length > 0) { // 递归过滤子节点,并将过滤后的子节点添加到当前节点中 diff --git a/frontend/src/views/api-test/components/paramTable.vue b/frontend/src/views/api-test/components/paramTable.vue index 63471ef9bb..0195e6f94e 100644 --- a/frontend/src/views/api-test/components/paramTable.vue +++ b/frontend/src/views/api-test/components/paramTable.vue @@ -855,6 +855,7 @@ /** 环境管理-环境组 end */ + const defaultLineData = ref(cloneDeep(props.defaultParamItem)); /** * 当表格输入框变化时,给参数表格添加一行数据行 * @param val 输入值 @@ -875,7 +876,7 @@ const nextLine = { id, enable: true, // 是否勾选 - ...cloneDeep(props.defaultParamItem), // 深拷贝,避免有嵌套引用类型,数据隔离 + ...cloneDeep(defaultLineData.value), // 深拷贝,避免有嵌套引用类型,数据隔离 } as any; selectColumnKeys.forEach((key) => { // 如果是更改了下拉框导致添加新的一列,需要将更改后的下拉框的值应用到下一行(产品为了方便统一输入参数类型) @@ -894,6 +895,7 @@ } }); paramsData.value.push(nextLine); + defaultLineData.value = cloneDeep(nextLine); } emitChange('addTableLine', isInit); handleMustContainColChange(true); @@ -910,7 +912,7 @@ // 批量添加过来的数据最后一行会是 undefined hasNoIdItem = true; return { - ...cloneDeep(props.defaultParamItem), + ...cloneDeep(defaultLineData.value), id: getGenerateId(), }; } @@ -927,7 +929,7 @@ if ( (!props.disabledExceptParam || !props.disabledParamValue) && hasNoIdItem && - !filterKeyValParams(arr, props.defaultParamItem, !props.selectable).lastDataIsDefault && + !filterKeyValParams(arr, defaultLineData.value, !props.selectable).lastDataIsDefault && !props.isTreeTable ) { addTableLine(arr.length - 1, false, true); @@ -939,7 +941,7 @@ { id, // 默认给时间戳 id,若 props.defaultParamItem 有 id,则覆盖 enable: true, // 是否勾选 - ...cloneDeep(props.defaultParamItem), + ...cloneDeep(defaultLineData.value), }, ] as any[]; emitChange('watch props.params', true);