feat(脑图): 测试规划脑图收尾&功能用例脑图交互逻辑抽离&ms-tab组件增加切换拦截
This commit is contained in:
parent
ba26a0c878
commit
d759105e57
|
@ -1,63 +1,65 @@
|
||||||
<template>
|
<template>
|
||||||
<MsMinderEditor
|
<div class="h-full py-[16px]">
|
||||||
v-model:activeExtraKey="activeExtraKey"
|
<MsMinderEditor
|
||||||
v-model:extra-visible="extraVisible"
|
v-model:activeExtraKey="activeExtraKey"
|
||||||
v-model:loading="loading"
|
v-model:extra-visible="extraVisible"
|
||||||
v-model:import-json="importJson"
|
v-model:loading="loading"
|
||||||
:tags="[]"
|
v-model:import-json="importJson"
|
||||||
:replaceable-tags="replaceableTags"
|
:tags="[]"
|
||||||
:insert-node="insertNode"
|
:replaceable-tags="replaceableTags"
|
||||||
:priority-disable-check="priorityDisableCheck"
|
:insert-node="insertNode"
|
||||||
:after-tag-edit="afterTagEdit"
|
:priority-disable-check="priorityDisableCheck"
|
||||||
:extract-content-tab-list="extractContentTabList"
|
:after-tag-edit="afterTagEdit"
|
||||||
:can-show-enter-node="canShowEnterNode"
|
:extract-content-tab-list="extractContentTabList"
|
||||||
:insert-sibling-menus="insertSiblingMenus"
|
:can-show-enter-node="canShowEnterNode"
|
||||||
:insert-son-menus="insertSonMenus"
|
:insert-sibling-menus="insertSiblingMenus"
|
||||||
:can-show-paste-menu="!stopPaste()"
|
:insert-son-menus="insertSonMenus"
|
||||||
:can-show-more-menu="canShowMoreMenu()"
|
:can-show-paste-menu="!stopPaste()"
|
||||||
:can-show-priority-menu="canShowPriorityMenu()"
|
:can-show-more-menu="canShowMoreMenu()"
|
||||||
:priority-tooltip="t('caseManagement.caseReview.caseLevel')"
|
:can-show-priority-menu="canShowPriorityMenu()"
|
||||||
single-tag
|
:priority-tooltip="t('caseManagement.caseReview.caseLevel')"
|
||||||
tag-enable
|
single-tag
|
||||||
sequence-enable
|
tag-enable
|
||||||
@content-change="handleContentChange"
|
sequence-enable
|
||||||
@node-select="handleNodeSelect"
|
@content-change="handleContentChange"
|
||||||
@action="handleAction"
|
@node-select="handleNodeSelect"
|
||||||
@before-exec-command="handleBeforeExecCommand"
|
@action="handleAction"
|
||||||
@save="handleMinderSave"
|
@before-exec-command="handleBeforeExecCommand"
|
||||||
@float-menu-close="handleBaseInfoCancel"
|
@save="handleMinderSave"
|
||||||
>
|
@float-menu-close="handleBaseInfoCancel"
|
||||||
<template #extractMenu>
|
>
|
||||||
<a-tooltip v-if="showDetailMenu" :content="t('common.detail')">
|
<template #extractMenu>
|
||||||
<MsButton
|
<a-tooltip v-if="showDetailMenu" :content="t('common.detail')">
|
||||||
type="icon"
|
<MsButton
|
||||||
class="ms-minder-node-float-menu-icon-button"
|
type="icon"
|
||||||
:class="[extraVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
class="ms-minder-node-float-menu-icon-button"
|
||||||
@click="toggleDetail"
|
:class="[extraVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
||||||
>
|
@click="toggleDetail"
|
||||||
<MsIcon type="icon-icon_describe_outlined" class="text-[var(--color-text-4)]" />
|
>
|
||||||
</MsButton>
|
<MsIcon type="icon-icon_describe_outlined" class="text-[var(--color-text-4)]" />
|
||||||
</a-tooltip>
|
</MsButton>
|
||||||
</template>
|
</a-tooltip>
|
||||||
<template #extractTabContent>
|
</template>
|
||||||
<baseInfo
|
<template #extractTabContent>
|
||||||
v-if="activeExtraKey === 'baseInfo'"
|
<baseInfo
|
||||||
ref="baseInfoRef"
|
v-if="activeExtraKey === 'baseInfo'"
|
||||||
:loading="baseInfoLoading"
|
ref="baseInfoRef"
|
||||||
:active-case="activeCase"
|
:loading="baseInfoLoading"
|
||||||
@init-template="(id) => (templateId = id)"
|
:active-case="activeCase"
|
||||||
@cancel="handleBaseInfoCancel"
|
@init-template="(id) => (templateId = id)"
|
||||||
/>
|
@cancel="handleBaseInfoCancel"
|
||||||
<attachment
|
/>
|
||||||
v-else-if="activeExtraKey === 'attachment'"
|
<attachment
|
||||||
v-model:model-value="fileList"
|
v-else-if="activeExtraKey === 'attachment'"
|
||||||
:active-case="activeCase"
|
v-model:model-value="fileList"
|
||||||
@upload-success="initCaseDetail"
|
:active-case="activeCase"
|
||||||
/>
|
@upload-success="initCaseDetail"
|
||||||
<caseCommentList v-else-if="activeExtraKey === 'comments'" :active-case="activeCase" />
|
/>
|
||||||
<bugList v-else :active-case="activeCase" />
|
<caseCommentList v-else-if="activeExtraKey === 'comments'" :active-case="activeCase" />
|
||||||
</template>
|
<bugList v-else :active-case="activeCase" />
|
||||||
</MsMinderEditor>
|
</template>
|
||||||
|
</MsMinderEditor>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -66,13 +68,7 @@
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import { FormItem } from '@/components/pure/ms-form-create/types';
|
import { FormItem } from '@/components/pure/ms-form-create/types';
|
||||||
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
|
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
|
||||||
import type {
|
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
||||||
InsertMenuItem,
|
|
||||||
MinderEvent,
|
|
||||||
MinderJson,
|
|
||||||
MinderJsonNode,
|
|
||||||
MinderJsonNodeData,
|
|
||||||
} from '@/components/pure/ms-minder-editor/props';
|
|
||||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
import attachment from './attachment.vue';
|
import attachment from './attachment.vue';
|
||||||
import baseInfo from './basInfo.vue';
|
import baseInfo from './basInfo.vue';
|
||||||
|
@ -100,6 +96,7 @@
|
||||||
import { MoveMode, TableQueryParams } from '@/models/common';
|
import { MoveMode, TableQueryParams } from '@/models/common';
|
||||||
import { MinderEventName } from '@/enums/minderEnum';
|
import { MinderEventName } from '@/enums/minderEnum';
|
||||||
|
|
||||||
|
import useMinderBaseApi from './useMinderBaseApi';
|
||||||
import { convertToFile, initFormCreate } from '@/views/case-management/caseManagementFeature/components/utils';
|
import { convertToFile, initFormCreate } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -112,17 +109,26 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const minderStore = useMinderStore();
|
const minderStore = useMinderStore();
|
||||||
|
|
||||||
const caseTag = t('common.case');
|
const {
|
||||||
const moduleTag = t('common.module');
|
caseTag,
|
||||||
const topTags = [moduleTag, caseTag];
|
moduleTag,
|
||||||
const stepTag = t('ms.minders.stepDesc');
|
stepTag,
|
||||||
const textDescTag = t('ms.minders.textDesc');
|
textDescTag,
|
||||||
const prerequisiteTag = t('ms.minders.precondition');
|
prerequisiteTag,
|
||||||
const stepExpectTag = t('ms.minders.stepExpect');
|
remarkTag,
|
||||||
const remarkTag = t('ms.minders.remark');
|
caseOffspringTags,
|
||||||
const descTags = [stepTag, textDescTag];
|
insertSiblingMenus,
|
||||||
const caseChildTags = [prerequisiteTag, stepTag, textDescTag, remarkTag];
|
insertSonMenus,
|
||||||
const caseOffspringTags = [...caseChildTags, stepTag, stepExpectTag, textDescTag, remarkTag];
|
insertNode,
|
||||||
|
handleBeforeExecCommand,
|
||||||
|
stopPaste,
|
||||||
|
checkNodeCanShowMenu,
|
||||||
|
canShowMoreMenu,
|
||||||
|
canShowPriorityMenu,
|
||||||
|
handleContentChange,
|
||||||
|
replaceableTags,
|
||||||
|
priorityDisableCheck,
|
||||||
|
} = useMinderBaseApi();
|
||||||
const importJson = ref<MinderJson>({
|
const importJson = ref<MinderJson>({
|
||||||
root: {} as MinderJsonNode,
|
root: {} as MinderJsonNode,
|
||||||
template: 'default',
|
template: 'default',
|
||||||
|
@ -259,315 +265,6 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 已选中节点的可替换标签判断
|
|
||||||
* @param node 选中节点
|
|
||||||
*/
|
|
||||||
function replaceableTags(nodes: MinderJsonNode[]) {
|
|
||||||
if (nodes.length > 1) {
|
|
||||||
// 选中的节点大于 1 时
|
|
||||||
if (nodes.some((e) => (e.data?.resource || []).length > 0)) {
|
|
||||||
// 批量选中的节点已经打了标签,不可替换
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (nodes.every((e) => (e.data?.resource || []).length === 0)) {
|
|
||||||
// 批量选中的节点都没有打标签,可替换为模块标签
|
|
||||||
return [moduleTag];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const node = nodes[0];
|
|
||||||
if (
|
|
||||||
Object.keys(node.data || {}).length === 0 ||
|
|
||||||
node.data?.id === 'root' ||
|
|
||||||
(node.parent?.data?.resource || []).length === 0
|
|
||||||
) {
|
|
||||||
// 没有数据的节点、默认模块节点、父节点为文本节点的节点不可替换标签
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (node.data?.resource?.some((e) => topTags.includes(e))) {
|
|
||||||
// 选中节点属于顶级节点,可替换为除自身外的顶级标签
|
|
||||||
return !node.children || node.children.length === 0
|
|
||||||
? topTags.filter((tag) => !node.data?.resource?.includes(tag))
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
if (node.data?.resource?.some((e) => descTags.includes(e))) {
|
|
||||||
// 选中节点属于描述节点,可替换为除自身外的描述标签
|
|
||||||
if (
|
|
||||||
node.data.resource.includes(stepTag) &&
|
|
||||||
(node.parent?.children?.filter((e) => e.data?.resource?.includes(stepTag)) || []).length > 1
|
|
||||||
) {
|
|
||||||
// 如果当前节点是步骤描述,则需要判断是否有其他步骤描述节点,如果有,则不可替换为文本描述
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return descTags.filter((tag) => !node.data?.resource?.includes(tag));
|
|
||||||
}
|
|
||||||
if ((!node.data?.resource || node.data.resource.length === 0) && node.parent?.data?.resource?.includes(caseTag)) {
|
|
||||||
// 选中节点无标签,且父节点为用例节点,可替换用例下级标签
|
|
||||||
return caseChildTags;
|
|
||||||
}
|
|
||||||
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 [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行插入节点
|
|
||||||
* @param command 插入命令
|
|
||||||
* @param node 目标节点
|
|
||||||
*/
|
|
||||||
function execInert(command: string, node?: MinderJsonNodeData) {
|
|
||||||
if (window.minder.queryCommandState(command) !== -1) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入前置条件
|
|
||||||
* @param node 目标节点
|
|
||||||
* @param type 插入类型
|
|
||||||
*/
|
|
||||||
function inertPrecondition(node: MinderJsonNode, type: string) {
|
|
||||||
const child: MinderJsonNode = {
|
|
||||||
parent: node,
|
|
||||||
data: {
|
|
||||||
id: getGenerateId(),
|
|
||||||
text: prerequisiteTag,
|
|
||||||
resource: [prerequisiteTag],
|
|
||||||
expandState: 'expand',
|
|
||||||
isNew: true,
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
execInert(type, child.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入备注
|
|
||||||
* @param node 目标节点
|
|
||||||
* @param type 插入类型
|
|
||||||
*/
|
|
||||||
function insetRemark(node: MinderJsonNode, type: string) {
|
|
||||||
const child = {
|
|
||||||
parent: node,
|
|
||||||
data: {
|
|
||||||
id: getGenerateId(),
|
|
||||||
text: remarkTag,
|
|
||||||
resource: [remarkTag],
|
|
||||||
isNew: true,
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
execInert(type, child.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入步骤描述
|
|
||||||
* @param node 目标节点
|
|
||||||
* @param type 插入类型
|
|
||||||
*/
|
|
||||||
function insetStepDesc(node: MinderJsonNode, type: string) {
|
|
||||||
const child = {
|
|
||||||
parent: node,
|
|
||||||
data: {
|
|
||||||
id: getGenerateId(),
|
|
||||||
text: stepTag,
|
|
||||||
resource: [stepTag],
|
|
||||||
isNew: true,
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
const sibling = {
|
|
||||||
parent: child,
|
|
||||||
data: {
|
|
||||||
id: getGenerateId(),
|
|
||||||
text: stepExpectTag,
|
|
||||||
resource: [stepExpectTag],
|
|
||||||
isNew: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
execInert(type, child.data);
|
|
||||||
nextTick(() => {
|
|
||||||
execInert('AppendChildNode', sibling.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入预期结果
|
|
||||||
* @param node 目标节点
|
|
||||||
* @param type 插入类型
|
|
||||||
*/
|
|
||||||
function insertExpect(node: MinderJsonNode, type: string) {
|
|
||||||
const child = {
|
|
||||||
parent: node,
|
|
||||||
data: {
|
|
||||||
id: getGenerateId(),
|
|
||||||
text: stepExpectTag,
|
|
||||||
resource: [stepExpectTag],
|
|
||||||
isNew: true,
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
execInert(type, child.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入指定的节点
|
|
||||||
* @param type 插入类型
|
|
||||||
* @param value 节点类型
|
|
||||||
*/
|
|
||||||
function insertSpecifyNode(type: string, value: string) {
|
|
||||||
execInert(type, {
|
|
||||||
id: getGenerateId(),
|
|
||||||
text: value !== t('ms.minders.text') ? value : '',
|
|
||||||
resource: value !== t('ms.minders.text') ? [value] : [],
|
|
||||||
expandState: 'expand',
|
|
||||||
isNew: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插入节点
|
|
||||||
* @param node 目标节点
|
|
||||||
* @param type 插入类型
|
|
||||||
* @param value 插入值
|
|
||||||
*/
|
|
||||||
function insertNode(node: MinderJsonNode, type: string, value?: string) {
|
|
||||||
switch (type) {
|
|
||||||
case 'AppendChildNode':
|
|
||||||
if (value) {
|
|
||||||
insertSpecifyNode('AppendChildNode', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (node.data?.resource?.includes(moduleTag)) {
|
|
||||||
execInert('AppendChildNode');
|
|
||||||
} else if (node.data?.resource?.includes(caseTag)) {
|
|
||||||
// 给用例插入子节点
|
|
||||||
if (!node.children || node.children.length === 0) {
|
|
||||||
// 当前用例还没有子节点,默认添加一个前置条件
|
|
||||||
inertPrecondition(node, type);
|
|
||||||
} else if (node.children.length > 0) {
|
|
||||||
// 当前用例有子节点
|
|
||||||
let hasPreCondition = false;
|
|
||||||
let hasTextDesc = false;
|
|
||||||
let hasRemark = false;
|
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
|
||||||
const child = node.children[i];
|
|
||||||
if (child.data?.resource?.includes(prerequisiteTag)) {
|
|
||||||
hasPreCondition = true;
|
|
||||||
} else if (child.data?.resource?.includes(textDescTag)) {
|
|
||||||
hasTextDesc = true;
|
|
||||||
} else if (child.data?.resource?.includes(remarkTag)) {
|
|
||||||
hasRemark = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasPreCondition) {
|
|
||||||
// 没有前置条件,则默认添加一个前置条件
|
|
||||||
inertPrecondition(node, type);
|
|
||||||
} else if (!hasRemark) {
|
|
||||||
// 没有备注,则默认添加一个备注
|
|
||||||
insetRemark(node, type);
|
|
||||||
} else if (!hasTextDesc) {
|
|
||||||
// 没有文本描述,则默认添加一个步骤描述
|
|
||||||
insetStepDesc(node, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
(node.data?.resource?.includes(stepTag) || node.data?.resource?.includes(textDescTag)) &&
|
|
||||||
(!node.children || node.children.length === 0)
|
|
||||||
) {
|
|
||||||
// 当前节点是步骤描述或文本描述,且没有子节点,则默认添加一个预期结果
|
|
||||||
insertExpect(node, 'AppendChildNode');
|
|
||||||
} else if (node.data?.resource?.includes(prerequisiteTag) && (!node.children || node.children.length === 0)) {
|
|
||||||
// 当前节点是前置条件,则默认添加一个文本节点
|
|
||||||
execInert('AppendChildNode');
|
|
||||||
} else {
|
|
||||||
// 文本节点下可添加文本节点
|
|
||||||
execInert('AppendChildNode');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'AppendSiblingNode':
|
|
||||||
if (value) {
|
|
||||||
insertSpecifyNode('AppendSiblingNode', value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (node.parent?.data?.resource?.includes(caseTag) && node.parent?.children) {
|
|
||||||
// 当前节点的父节点是用例
|
|
||||||
let hasPreCondition = false;
|
|
||||||
let hasTextDesc = false;
|
|
||||||
let hasRemark = false;
|
|
||||||
for (let i = 0; i < node.parent.children.length; i++) {
|
|
||||||
const sibling = node.parent.children[i];
|
|
||||||
if (sibling.data?.resource?.includes(prerequisiteTag)) {
|
|
||||||
hasPreCondition = true;
|
|
||||||
} else if (sibling.data?.resource?.includes(remarkTag)) {
|
|
||||||
hasRemark = true;
|
|
||||||
} else if (sibling.data?.resource?.includes(textDescTag)) {
|
|
||||||
hasTextDesc = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasPreCondition) {
|
|
||||||
// 没有前置条件,则默认添加一个前置条件
|
|
||||||
inertPrecondition(node, type);
|
|
||||||
} else if (!hasRemark) {
|
|
||||||
// 没有备注,则默认添加一个备注
|
|
||||||
insetRemark(node, type);
|
|
||||||
} else if (!hasTextDesc) {
|
|
||||||
// 没有文本描述,则默认添加一个步骤描述
|
|
||||||
insetStepDesc(node, type);
|
|
||||||
}
|
|
||||||
} else if (node.parent?.data?.resource?.includes(moduleTag) || !node.parent?.data?.resource) {
|
|
||||||
// 当前节点的父节点是模块或没有标签,则默认添加一个文本节点
|
|
||||||
execInert('AppendSiblingNode');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查节点是否可打优先级
|
|
||||||
*/
|
|
||||||
function priorityDisableCheck(node: MinderJsonNode) {
|
|
||||||
if (node.data?.resource?.includes(caseTag)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseInfoLoading = ref(false);
|
const baseInfoLoading = ref(false);
|
||||||
|
|
||||||
const formRules = ref<FormItem[]>([]);
|
const formRules = ref<FormItem[]>([]);
|
||||||
|
@ -666,184 +363,6 @@
|
||||||
resetExtractInfo();
|
resetExtractInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleContentChange(node: MinderJsonNode) {
|
|
||||||
if (node?.data) {
|
|
||||||
const { resource } = node.data;
|
|
||||||
// 用例下的子节点更改,触发用例更改
|
|
||||||
if (
|
|
||||||
resource?.includes(prerequisiteTag) ||
|
|
||||||
resource?.includes(stepTag) ||
|
|
||||||
resource?.includes(textDescTag) ||
|
|
||||||
resource?.includes(remarkTag)
|
|
||||||
) {
|
|
||||||
if (node.parent?.data) {
|
|
||||||
node.parent.data.changed = true;
|
|
||||||
}
|
|
||||||
} else if (node.parent?.parent?.data?.resource?.includes(caseTag)) {
|
|
||||||
// 用例下子节点的子节点更改,触发用例更改
|
|
||||||
node.parent.parent.data.changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertSiblingMenus = ref<InsertMenuItem[]>([]);
|
|
||||||
const insertSonMenus = ref<InsertMenuItem[]>([]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测节点可展示的菜单项
|
|
||||||
* @param node 选中节点
|
|
||||||
*/
|
|
||||||
function checkNodeCanShowMenu(node: MinderJsonNode) {
|
|
||||||
const { data } = node;
|
|
||||||
if (data?.resource?.includes(moduleTag)) {
|
|
||||||
// 模块节点
|
|
||||||
if (data?.id === 'NONE' || node.type === 'root' || node.parent?.data?.id === 'NONE') {
|
|
||||||
// 脑图根节点、NONE虚拟节点、父节点为NONE的模块节点,不能插入同级节点
|
|
||||||
insertSiblingMenus.value = [];
|
|
||||||
if (data?.id === 'NONE') {
|
|
||||||
// NONE模块节点下只能插入模块节点
|
|
||||||
insertSonMenus.value = [
|
|
||||||
{
|
|
||||||
label: moduleTag,
|
|
||||||
value: moduleTag,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
if (node.parent?.data?.id === 'NONE') {
|
|
||||||
// 父节点为NONE的模块节点同级可以插入模块节点
|
|
||||||
insertSiblingMenus.value = [
|
|
||||||
{
|
|
||||||
label: moduleTag,
|
|
||||||
value: moduleTag,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
// 非 NONE模块节点下可以插入模块、用例、文本节点
|
|
||||||
insertSonMenus.value = [
|
|
||||||
{
|
|
||||||
label: moduleTag,
|
|
||||||
value: moduleTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: caseTag,
|
|
||||||
value: caseTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('ms.minders.text'),
|
|
||||||
value: t('ms.minders.text'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 正常模块节点同级可插入模块、用例、文本节点
|
|
||||||
insertSiblingMenus.value = [
|
|
||||||
{
|
|
||||||
label: moduleTag,
|
|
||||||
value: moduleTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: caseTag,
|
|
||||||
value: caseTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('ms.minders.text'),
|
|
||||||
value: t('ms.minders.text'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// 正常模块节点下可插入模块、用例、文本节点
|
|
||||||
insertSonMenus.value = [
|
|
||||||
{
|
|
||||||
label: moduleTag,
|
|
||||||
value: moduleTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: caseTag,
|
|
||||||
value: caseTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('ms.minders.text'),
|
|
||||||
value: t('ms.minders.text'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} else if (data?.resource?.includes(caseTag)) {
|
|
||||||
// 用例节点同级可插入模块、用例、文本节点
|
|
||||||
insertSiblingMenus.value = [
|
|
||||||
{
|
|
||||||
label: moduleTag,
|
|
||||||
value: moduleTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: caseTag,
|
|
||||||
value: caseTag,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('ms.minders.text'),
|
|
||||||
value: t('ms.minders.text'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
insertSonMenus.value = caseChildTags.map((tag) => ({
|
|
||||||
label: tag,
|
|
||||||
value: tag,
|
|
||||||
}));
|
|
||||||
if (node.children?.some((child) => child.data?.resource?.includes(stepTag))) {
|
|
||||||
// 用例节点下有步骤描述节点,不可插入文本描述节点
|
|
||||||
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== textDescTag);
|
|
||||||
} else if (node.children?.some((child) => child.data?.resource?.includes(textDescTag))) {
|
|
||||||
// 用例节点下有文本描述节点,不可插入步骤描述和文本描述节点
|
|
||||||
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== stepTag && e.value !== textDescTag);
|
|
||||||
}
|
|
||||||
if (node.children?.some((child) => child.data?.resource?.includes(prerequisiteTag))) {
|
|
||||||
// 用例节点下有前置条件节点,不可插入前置条件节点
|
|
||||||
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== prerequisiteTag);
|
|
||||||
}
|
|
||||||
if (node.children?.some((child) => child.data?.resource?.includes(remarkTag))) {
|
|
||||||
// 用例节点下有备注节点,不可插入备注节点
|
|
||||||
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== remarkTag);
|
|
||||||
}
|
|
||||||
} else if (data?.resource?.some((tag) => caseChildTags.includes(tag))) {
|
|
||||||
// 用例下的子节点
|
|
||||||
insertSiblingMenus.value = caseChildTags.map((tag) => ({
|
|
||||||
label: tag,
|
|
||||||
value: tag,
|
|
||||||
}));
|
|
||||||
if (node.parent?.children?.some((child) => child.data?.resource?.includes(stepTag))) {
|
|
||||||
// 用例节点下有步骤描述节点,不可插入文本描述节点
|
|
||||||
insertSiblingMenus.value = insertSiblingMenus.value.filter((e) => e.value !== textDescTag);
|
|
||||||
} else if (node.parent?.children?.some((child) => child.data?.resource?.includes(textDescTag))) {
|
|
||||||
// 用例节点下有文本描述节点,不可插入步骤描述和文本描述节点
|
|
||||||
insertSiblingMenus.value = insertSiblingMenus.value.filter(
|
|
||||||
(e) => e.value !== stepTag && e.value !== textDescTag
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (node.parent?.children?.some((child) => child.data?.resource?.includes(prerequisiteTag))) {
|
|
||||||
// 用例节点下有前置条件节点,不可插入前置条件节点
|
|
||||||
insertSiblingMenus.value = insertSiblingMenus.value.filter((e) => e.value !== prerequisiteTag);
|
|
||||||
}
|
|
||||||
if (node.parent?.children?.some((child) => child.data?.resource?.includes(remarkTag))) {
|
|
||||||
// 用例节点下有备注节点,不可插入备注节点
|
|
||||||
insertSiblingMenus.value = insertSiblingMenus.value.filter((e) => e.value !== remarkTag);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(data?.resource?.includes(textDescTag) || data?.resource?.includes(stepTag)) &&
|
|
||||||
(!node.children || node.children.length === 0)
|
|
||||||
) {
|
|
||||||
// 文本描述和步骤描述节点无子级时,子级可插入预期结果
|
|
||||||
insertSonMenus.value = [
|
|
||||||
{
|
|
||||||
label: stepExpectTag,
|
|
||||||
value: stepExpectTag,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
insertSonMenus.value = [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
insertSiblingMenus.value = [];
|
|
||||||
insertSonMenus.value = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换用例详情显示
|
* 切换用例详情显示
|
||||||
*/
|
*/
|
||||||
|
@ -1065,217 +584,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function canShowMoreMenu() {
|
|
||||||
if (window.minder) {
|
|
||||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
|
||||||
return node?.data?.id !== 'NONE';
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function canShowPriorityMenu() {
|
|
||||||
if (window.minder) {
|
|
||||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
|
||||||
return node?.data?.resource?.includes(caseTag);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否停止拖拽动作
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否停止粘贴动作
|
|
||||||
*/
|
|
||||||
function stopPaste() {
|
|
||||||
const nodes = minderStore.clipboard;
|
|
||||||
if (window.minder) {
|
|
||||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
|
||||||
if (!node) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (node.data?.resource?.includes(moduleTag)) {
|
|
||||||
// NONE 虚拟模块下,只能粘贴模块
|
|
||||||
if (node.data?.id === 'NONE' && nodes.every((e) => e.data?.resource?.includes(moduleTag))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 正常模块下,只能粘贴模块、用例、文本节点
|
|
||||||
if (
|
|
||||||
node.data?.id !== 'NONE' &&
|
|
||||||
nodes.some(
|
|
||||||
(e) => !e.data?.resource || e.data.resource.includes(moduleTag) || e.data.resource.includes(caseTag)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.data?.resource?.includes(caseTag)) {
|
|
||||||
if (nodes.every((e) => caseChildTags.some((item) => e.data?.resource?.includes(item)))) {
|
|
||||||
// 用例节点下只能粘贴用例子节点
|
|
||||||
if (
|
|
||||||
nodes.length >= 1 &&
|
|
||||||
nodes.every((e) => e.data?.resource?.includes(stepTag)) &&
|
|
||||||
!node.children?.some((child) => child.data?.resource?.includes(textDescTag))
|
|
||||||
) {
|
|
||||||
// 粘贴的节点数大于等于 1 时,只能是粘贴步骤描述节点,且当前用例下无文本描述节点
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (nodes.length === 1) {
|
|
||||||
// 粘贴的节点数是 1 时
|
|
||||||
if (
|
|
||||||
node.children?.every((child) => !child.data?.resource?.includes(prerequisiteTag)) &&
|
|
||||||
nodes[0].data?.resource?.includes(prerequisiteTag)
|
|
||||||
) {
|
|
||||||
// 用例下无前置条件且粘贴的节点是前置条件
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
node.children?.every((child) => !child.data?.resource?.includes(remarkTag)) &&
|
|
||||||
nodes[0].data?.resource?.includes(remarkTag)
|
|
||||||
) {
|
|
||||||
// 用例下无备注且粘贴的节点是备注
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
node.children?.every((child) => !child.data?.resource?.includes(textDescTag)) &&
|
|
||||||
nodes[0].data?.resource?.includes(textDescTag)
|
|
||||||
) {
|
|
||||||
// 用例下无文本描述且粘贴的节点是文本描述
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ([stepTag, textDescTag].some((tag) => node.data?.resource?.includes(tag))) {
|
|
||||||
// 用例下的文本描述和步骤描述节点
|
|
||||||
if (node.data?.resource?.includes(stepExpectTag)) {
|
|
||||||
// 粘贴的是期望结果节点
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 脑图命令执行前拦截
|
|
||||||
* @param event 命令执行事件
|
|
||||||
*/
|
|
||||||
function handleBeforeExecCommand(event: MinderEvent) {
|
|
||||||
if (event.commandName === 'movetoparent') {
|
|
||||||
// 拖拽到节点内拦截
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
} else if (event.commandName === 'paste') {
|
|
||||||
if (stopPaste()) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
} else if (event.commandName === 'cut') {
|
|
||||||
minderStore.clipboard.forEach((node) => {
|
|
||||||
if (node.parent && node.parent.data?.resource?.includes(caseTag)) {
|
|
||||||
// 用例子节点更改
|
|
||||||
node.parent.data.changed = true;
|
|
||||||
} else if (node.parent?.parent && node.parent.parent.data?.resource?.includes(caseTag)) {
|
|
||||||
// 用例孙子节点更改
|
|
||||||
node.parent.parent.data.changed = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成脑图保存的入参
|
* 生成脑图保存的入参
|
||||||
*/
|
*/
|
||||||
|
@ -1324,6 +632,11 @@
|
||||||
return tempMinderParams.value;
|
return tempMinderParams.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理脑图保存
|
||||||
|
* @param fullJson 脑图导出的完整数据
|
||||||
|
* @param callback 保存成功回调
|
||||||
|
*/
|
||||||
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
|
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
|
@ -0,0 +1,687 @@
|
||||||
|
import type {
|
||||||
|
InsertMenuItem,
|
||||||
|
MinderEvent,
|
||||||
|
MinderJsonNode,
|
||||||
|
MinderJsonNodeData,
|
||||||
|
} from '@/components/pure/ms-minder-editor/props';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||||
|
import { getGenerateId } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装用例脑图基础功能,包含菜单显隐判断、节点插入、节点替换、节点拖拽等
|
||||||
|
* @returns API 集合
|
||||||
|
*/
|
||||||
|
export default function useMinderBaseApi() {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const minderStore = useMinderStore();
|
||||||
|
|
||||||
|
const caseTag = t('common.case');
|
||||||
|
const moduleTag = t('common.module');
|
||||||
|
const topTags = [moduleTag, caseTag];
|
||||||
|
const stepTag = t('ms.minders.stepDesc');
|
||||||
|
const textDescTag = t('ms.minders.textDesc');
|
||||||
|
const prerequisiteTag = t('ms.minders.precondition');
|
||||||
|
const stepExpectTag = t('ms.minders.stepExpect');
|
||||||
|
const remarkTag = t('ms.minders.remark');
|
||||||
|
const descTags = [stepTag, textDescTag];
|
||||||
|
const caseChildTags = [prerequisiteTag, stepTag, textDescTag, remarkTag];
|
||||||
|
const caseOffspringTags = [...caseChildTags, stepTag, stepExpectTag, textDescTag, remarkTag];
|
||||||
|
|
||||||
|
const insertSiblingMenus = ref<InsertMenuItem[]>([]);
|
||||||
|
const insertSonMenus = ref<InsertMenuItem[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测节点可展示的菜单项
|
||||||
|
* @param node 选中节点
|
||||||
|
*/
|
||||||
|
function checkNodeCanShowMenu(node: MinderJsonNode) {
|
||||||
|
const { data } = node;
|
||||||
|
if (data?.resource?.includes(moduleTag)) {
|
||||||
|
// 模块节点
|
||||||
|
if (data?.id === 'NONE' || node.type === 'root' || node.parent?.data?.id === 'NONE') {
|
||||||
|
// 脑图根节点、NONE虚拟节点、父节点为NONE的模块节点,不能插入同级节点
|
||||||
|
insertSiblingMenus.value = [];
|
||||||
|
if (data?.id === 'NONE') {
|
||||||
|
// NONE模块节点下只能插入模块节点
|
||||||
|
insertSonMenus.value = [
|
||||||
|
{
|
||||||
|
label: moduleTag,
|
||||||
|
value: moduleTag,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
if (node.parent?.data?.id === 'NONE') {
|
||||||
|
// 父节点为NONE的模块节点同级可以插入模块节点
|
||||||
|
insertSiblingMenus.value = [
|
||||||
|
{
|
||||||
|
label: moduleTag,
|
||||||
|
value: moduleTag,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// 非 NONE模块节点下可以插入模块、用例、文本节点
|
||||||
|
insertSonMenus.value = [
|
||||||
|
{
|
||||||
|
label: moduleTag,
|
||||||
|
value: moduleTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: caseTag,
|
||||||
|
value: caseTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('ms.minders.text'),
|
||||||
|
value: t('ms.minders.text'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 正常模块节点同级可插入模块、用例、文本节点
|
||||||
|
insertSiblingMenus.value = [
|
||||||
|
{
|
||||||
|
label: moduleTag,
|
||||||
|
value: moduleTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: caseTag,
|
||||||
|
value: caseTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('ms.minders.text'),
|
||||||
|
value: t('ms.minders.text'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// 正常模块节点下可插入模块、用例、文本节点
|
||||||
|
insertSonMenus.value = [
|
||||||
|
{
|
||||||
|
label: moduleTag,
|
||||||
|
value: moduleTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: caseTag,
|
||||||
|
value: caseTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('ms.minders.text'),
|
||||||
|
value: t('ms.minders.text'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else if (data?.resource?.includes(caseTag)) {
|
||||||
|
// 用例节点同级可插入模块、用例、文本节点
|
||||||
|
insertSiblingMenus.value = [
|
||||||
|
{
|
||||||
|
label: moduleTag,
|
||||||
|
value: moduleTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: caseTag,
|
||||||
|
value: caseTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('ms.minders.text'),
|
||||||
|
value: t('ms.minders.text'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
insertSonMenus.value = caseChildTags.map((tag) => ({
|
||||||
|
label: tag,
|
||||||
|
value: tag,
|
||||||
|
}));
|
||||||
|
if (node.children?.some((child) => child.data?.resource?.includes(stepTag))) {
|
||||||
|
// 用例节点下有步骤描述节点,不可插入文本描述节点
|
||||||
|
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== textDescTag);
|
||||||
|
} else if (node.children?.some((child) => child.data?.resource?.includes(textDescTag))) {
|
||||||
|
// 用例节点下有文本描述节点,不可插入步骤描述和文本描述节点
|
||||||
|
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== stepTag && e.value !== textDescTag);
|
||||||
|
}
|
||||||
|
if (node.children?.some((child) => child.data?.resource?.includes(prerequisiteTag))) {
|
||||||
|
// 用例节点下有前置条件节点,不可插入前置条件节点
|
||||||
|
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== prerequisiteTag);
|
||||||
|
}
|
||||||
|
if (node.children?.some((child) => child.data?.resource?.includes(remarkTag))) {
|
||||||
|
// 用例节点下有备注节点,不可插入备注节点
|
||||||
|
insertSonMenus.value = insertSonMenus.value.filter((e) => e.value !== remarkTag);
|
||||||
|
}
|
||||||
|
} else if (data?.resource?.some((tag) => caseChildTags.includes(tag))) {
|
||||||
|
// 用例下的子节点
|
||||||
|
insertSiblingMenus.value = caseChildTags.map((tag) => ({
|
||||||
|
label: tag,
|
||||||
|
value: tag,
|
||||||
|
}));
|
||||||
|
if (node.parent?.children?.some((child) => child.data?.resource?.includes(stepTag))) {
|
||||||
|
// 用例节点下有步骤描述节点,不可插入文本描述节点
|
||||||
|
insertSiblingMenus.value = insertSiblingMenus.value.filter((e) => e.value !== textDescTag);
|
||||||
|
} else if (node.parent?.children?.some((child) => child.data?.resource?.includes(textDescTag))) {
|
||||||
|
// 用例节点下有文本描述节点,不可插入步骤描述和文本描述节点
|
||||||
|
insertSiblingMenus.value = insertSiblingMenus.value.filter(
|
||||||
|
(e) => e.value !== stepTag && e.value !== textDescTag
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (node.parent?.children?.some((child) => child.data?.resource?.includes(prerequisiteTag))) {
|
||||||
|
// 用例节点下有前置条件节点,不可插入前置条件节点
|
||||||
|
insertSiblingMenus.value = insertSiblingMenus.value.filter((e) => e.value !== prerequisiteTag);
|
||||||
|
}
|
||||||
|
if (node.parent?.children?.some((child) => child.data?.resource?.includes(remarkTag))) {
|
||||||
|
// 用例节点下有备注节点,不可插入备注节点
|
||||||
|
insertSiblingMenus.value = insertSiblingMenus.value.filter((e) => e.value !== remarkTag);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(data?.resource?.includes(textDescTag) || data?.resource?.includes(stepTag)) &&
|
||||||
|
(!node.children || node.children.length === 0)
|
||||||
|
) {
|
||||||
|
// 文本描述和步骤描述节点无子级时,子级可插入预期结果
|
||||||
|
insertSonMenus.value = [
|
||||||
|
{
|
||||||
|
label: stepExpectTag,
|
||||||
|
value: stepExpectTag,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
insertSonMenus.value = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insertSiblingMenus.value = [];
|
||||||
|
insertSonMenus.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可展示更多菜单
|
||||||
|
*/
|
||||||
|
function canShowMoreMenu() {
|
||||||
|
if (window.minder) {
|
||||||
|
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||||
|
// 选中节点不为虚拟根节点时,可展示更多菜单
|
||||||
|
return node?.data?.id !== 'NONE';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可展示优先级菜单
|
||||||
|
*/
|
||||||
|
function canShowPriorityMenu() {
|
||||||
|
if (window.minder) {
|
||||||
|
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||||
|
// 选中节点是用例节点时,可展示优先级菜单
|
||||||
|
return node?.data?.resource?.includes(caseTag);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已选中节点的可替换标签判断
|
||||||
|
* @param node 选中节点
|
||||||
|
*/
|
||||||
|
function replaceableTags(nodes: MinderJsonNode[]) {
|
||||||
|
if (nodes.length > 1) {
|
||||||
|
// 选中的节点大于 1 时
|
||||||
|
if (nodes.some((e) => (e.data?.resource || []).length > 0)) {
|
||||||
|
// 批量选中的节点已经打了标签,不可替换
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (nodes.every((e) => (e.data?.resource || []).length === 0)) {
|
||||||
|
// 批量选中的节点都没有打标签,可替换为模块标签
|
||||||
|
return [moduleTag];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const node = nodes[0];
|
||||||
|
if (
|
||||||
|
Object.keys(node.data || {}).length === 0 ||
|
||||||
|
node.data?.id === 'root' ||
|
||||||
|
(node.parent?.data?.resource || []).length === 0
|
||||||
|
) {
|
||||||
|
// 没有数据的节点、默认模块节点、父节点为文本节点的节点不可替换标签
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (node.data?.resource?.some((e) => topTags.includes(e))) {
|
||||||
|
// 选中节点属于顶级节点,可替换为除自身外的顶级标签
|
||||||
|
return !node.children || node.children.length === 0
|
||||||
|
? topTags.filter((tag) => !node.data?.resource?.includes(tag))
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
if (node.data?.resource?.some((e) => descTags.includes(e))) {
|
||||||
|
// 选中节点属于描述节点,可替换为除自身外的描述标签
|
||||||
|
if (
|
||||||
|
node.data.resource.includes(stepTag) &&
|
||||||
|
(node.parent?.children?.filter((e) => e.data?.resource?.includes(stepTag)) || []).length > 1
|
||||||
|
) {
|
||||||
|
// 如果当前节点是步骤描述,则需要判断是否有其他步骤描述节点,如果有,则不可替换为文本描述
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return descTags.filter((tag) => !node.data?.resource?.includes(tag));
|
||||||
|
}
|
||||||
|
if ((!node.data?.resource || node.data.resource.length === 0) && node.parent?.data?.resource?.includes(caseTag)) {
|
||||||
|
// 选中节点无标签,且父节点为用例节点,可替换用例下级标签
|
||||||
|
return caseChildTags;
|
||||||
|
}
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查节点是否可打优先级
|
||||||
|
*/
|
||||||
|
function priorityDisableCheck(node: MinderJsonNode) {
|
||||||
|
if (node.data?.resource?.includes(caseTag)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行插入节点
|
||||||
|
* @param command 插入命令
|
||||||
|
* @param node 目标节点
|
||||||
|
*/
|
||||||
|
function execInert(command: string, node?: MinderJsonNodeData) {
|
||||||
|
if (window.minder.queryCommandState(command) !== -1) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入指定的节点
|
||||||
|
* @param type 插入类型
|
||||||
|
* @param value 节点类型
|
||||||
|
*/
|
||||||
|
function insertSpecifyNode(type: string, value: string) {
|
||||||
|
execInert(type, {
|
||||||
|
id: getGenerateId(),
|
||||||
|
text: value !== t('ms.minders.text') ? value : '',
|
||||||
|
resource: value !== t('ms.minders.text') ? [value] : [],
|
||||||
|
expandState: 'expand',
|
||||||
|
isNew: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入步骤描述
|
||||||
|
*/
|
||||||
|
function insetStepDesc() {
|
||||||
|
insertSpecifyNode('AppendChildNode', stepTag);
|
||||||
|
nextTick(() => {
|
||||||
|
insertSpecifyNode('AppendChildNode', stepExpectTag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入节点
|
||||||
|
* @param node 目标节点
|
||||||
|
* @param type 插入类型
|
||||||
|
* @param value 插入值
|
||||||
|
*/
|
||||||
|
function insertNode(node: MinderJsonNode, type: string, value?: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'AppendChildNode':
|
||||||
|
if (value) {
|
||||||
|
insertSpecifyNode('AppendChildNode', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (node.data?.resource?.includes(moduleTag)) {
|
||||||
|
execInert('AppendChildNode');
|
||||||
|
} else if (node.data?.resource?.includes(caseTag)) {
|
||||||
|
// 给用例插入子节点
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
// 当前用例还没有子节点,默认添加一个前置条件
|
||||||
|
insertSpecifyNode('AppendChildNode', prerequisiteTag);
|
||||||
|
} else if (node.children.length > 0) {
|
||||||
|
// 当前用例有子节点
|
||||||
|
let hasPreCondition = false;
|
||||||
|
let hasTextDesc = false;
|
||||||
|
let hasRemark = false;
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
const child = node.children[i];
|
||||||
|
if (child.data?.resource?.includes(prerequisiteTag)) {
|
||||||
|
hasPreCondition = true;
|
||||||
|
} else if (child.data?.resource?.includes(textDescTag)) {
|
||||||
|
hasTextDesc = true;
|
||||||
|
} else if (child.data?.resource?.includes(remarkTag)) {
|
||||||
|
hasRemark = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasPreCondition) {
|
||||||
|
// 没有前置条件,则默认添加一个前置条件
|
||||||
|
insertSpecifyNode('AppendChildNode', prerequisiteTag);
|
||||||
|
} else if (!hasRemark) {
|
||||||
|
// 没有备注,则默认添加一个备注
|
||||||
|
insertSpecifyNode('AppendChildNode', remarkTag);
|
||||||
|
} else if (!hasTextDesc) {
|
||||||
|
// 没有文本描述,则默认添加一个步骤描述
|
||||||
|
insetStepDesc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(node.data?.resource?.includes(stepTag) || node.data?.resource?.includes(textDescTag)) &&
|
||||||
|
(!node.children || node.children.length === 0)
|
||||||
|
) {
|
||||||
|
// 当前节点是步骤描述或文本描述,且没有子节点,则默认添加一个预期结果
|
||||||
|
insertSpecifyNode('AppendChildNode', value || stepExpectTag);
|
||||||
|
} else if (node.data?.resource?.includes(prerequisiteTag) && (!node.children || node.children.length === 0)) {
|
||||||
|
// 当前节点是前置条件,则默认添加一个文本节点
|
||||||
|
execInert('AppendChildNode');
|
||||||
|
} else {
|
||||||
|
// 文本节点下可添加文本节点
|
||||||
|
execInert('AppendChildNode');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'AppendSiblingNode':
|
||||||
|
if (value) {
|
||||||
|
insertSpecifyNode('AppendSiblingNode', value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (node.parent?.data?.resource?.includes(caseTag) && node.parent?.children) {
|
||||||
|
// 当前节点的父节点是用例
|
||||||
|
let hasPreCondition = false;
|
||||||
|
let hasTextDesc = false;
|
||||||
|
let hasRemark = false;
|
||||||
|
for (let i = 0; i < node.parent.children.length; i++) {
|
||||||
|
const sibling = node.parent.children[i];
|
||||||
|
if (sibling.data?.resource?.includes(prerequisiteTag)) {
|
||||||
|
hasPreCondition = true;
|
||||||
|
} else if (sibling.data?.resource?.includes(remarkTag)) {
|
||||||
|
hasRemark = true;
|
||||||
|
} else if (sibling.data?.resource?.includes(textDescTag)) {
|
||||||
|
hasTextDesc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasPreCondition) {
|
||||||
|
// 没有前置条件,则默认添加一个前置条件
|
||||||
|
insertSpecifyNode('AppendSiblingNode', prerequisiteTag);
|
||||||
|
} else if (!hasRemark) {
|
||||||
|
// 没有备注,则默认添加一个备注
|
||||||
|
insertSpecifyNode('AppendSiblingNode', remarkTag);
|
||||||
|
} else if (!hasTextDesc) {
|
||||||
|
// 没有文本描述,则默认添加一个步骤描述
|
||||||
|
insetStepDesc();
|
||||||
|
}
|
||||||
|
} else if (node.parent?.data?.resource?.includes(moduleTag) || !node.parent?.data?.resource) {
|
||||||
|
// 当前节点的父节点是模块或没有标签,则默认添加一个文本节点
|
||||||
|
execInert('AppendSiblingNode');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否停止拖拽动作
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否停止粘贴动作
|
||||||
|
*/
|
||||||
|
function stopPaste() {
|
||||||
|
const nodes = minderStore.clipboard;
|
||||||
|
if (window.minder) {
|
||||||
|
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||||
|
if (!node) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (node.data?.resource?.includes(moduleTag)) {
|
||||||
|
// NONE 虚拟模块下,只能粘贴模块
|
||||||
|
if (node.data?.id === 'NONE' && nodes.every((e) => e.data?.resource?.includes(moduleTag))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 正常模块下,只能粘贴模块、用例、文本节点
|
||||||
|
if (
|
||||||
|
node.data?.id !== 'NONE' &&
|
||||||
|
nodes.some(
|
||||||
|
(e) => !e.data?.resource || e.data.resource.includes(moduleTag) || e.data.resource.includes(caseTag)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.data?.resource?.includes(caseTag)) {
|
||||||
|
if (nodes.every((e) => caseChildTags.some((item) => e.data?.resource?.includes(item)))) {
|
||||||
|
// 用例节点下只能粘贴用例子节点
|
||||||
|
if (
|
||||||
|
nodes.length >= 1 &&
|
||||||
|
nodes.every((e) => e.data?.resource?.includes(stepTag)) &&
|
||||||
|
!node.children?.some((child) => child.data?.resource?.includes(textDescTag))
|
||||||
|
) {
|
||||||
|
// 粘贴的节点数大于等于 1 时,只能是粘贴步骤描述节点,且当前用例下无文本描述节点
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nodes.length === 1) {
|
||||||
|
// 粘贴的节点数是 1 时
|
||||||
|
if (
|
||||||
|
node.children?.every((child) => !child.data?.resource?.includes(prerequisiteTag)) &&
|
||||||
|
nodes[0].data?.resource?.includes(prerequisiteTag)
|
||||||
|
) {
|
||||||
|
// 用例下无前置条件且粘贴的节点是前置条件
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node.children?.every((child) => !child.data?.resource?.includes(remarkTag)) &&
|
||||||
|
nodes[0].data?.resource?.includes(remarkTag)
|
||||||
|
) {
|
||||||
|
// 用例下无备注且粘贴的节点是备注
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node.children?.every((child) => !child.data?.resource?.includes(textDescTag)) &&
|
||||||
|
nodes[0].data?.resource?.includes(textDescTag)
|
||||||
|
) {
|
||||||
|
// 用例下无文本描述且粘贴的节点是文本描述
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([stepTag, textDescTag].some((tag) => node.data?.resource?.includes(tag))) {
|
||||||
|
// 用例下的文本描述和步骤描述节点
|
||||||
|
if (node.data?.resource?.includes(stepExpectTag)) {
|
||||||
|
// 粘贴的是期望结果节点
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脑图命令执行前拦截
|
||||||
|
* @param event 命令执行事件
|
||||||
|
*/
|
||||||
|
function handleBeforeExecCommand(event: MinderEvent) {
|
||||||
|
if (event.commandName === 'movetoparent') {
|
||||||
|
// 拖拽到节点内拦截
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
} else if (event.commandName === 'paste') {
|
||||||
|
if (stopPaste()) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
} else if (event.commandName === 'cut') {
|
||||||
|
minderStore.clipboard.forEach((node) => {
|
||||||
|
if (node.parent && node.parent.data?.resource?.includes(caseTag)) {
|
||||||
|
// 用例子节点更改
|
||||||
|
node.parent.data.changed = true;
|
||||||
|
} else if (node.parent?.parent && node.parent.parent.data?.resource?.includes(caseTag)) {
|
||||||
|
// 用例孙子节点更改
|
||||||
|
node.parent.parent.data.changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理节点文本变更
|
||||||
|
* @param node 变更节点
|
||||||
|
*/
|
||||||
|
function handleContentChange(node: MinderJsonNode) {
|
||||||
|
if (node?.data) {
|
||||||
|
const { resource } = node.data;
|
||||||
|
// 用例下的子节点更改,触发用例更改
|
||||||
|
if (
|
||||||
|
resource?.includes(prerequisiteTag) ||
|
||||||
|
resource?.includes(stepTag) ||
|
||||||
|
resource?.includes(textDescTag) ||
|
||||||
|
resource?.includes(remarkTag)
|
||||||
|
) {
|
||||||
|
if (node.parent?.data) {
|
||||||
|
node.parent.data.changed = true;
|
||||||
|
}
|
||||||
|
} else if (node.parent?.parent?.data?.resource?.includes(caseTag)) {
|
||||||
|
// 用例下子节点的子节点更改,触发用例更改
|
||||||
|
node.parent.parent.data.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
caseTag,
|
||||||
|
moduleTag,
|
||||||
|
topTags,
|
||||||
|
stepTag,
|
||||||
|
textDescTag,
|
||||||
|
prerequisiteTag,
|
||||||
|
stepExpectTag,
|
||||||
|
remarkTag,
|
||||||
|
descTags,
|
||||||
|
caseChildTags,
|
||||||
|
caseOffspringTags,
|
||||||
|
insertSiblingMenus,
|
||||||
|
insertSonMenus,
|
||||||
|
insertNode,
|
||||||
|
handleBeforeExecCommand,
|
||||||
|
stopPaste,
|
||||||
|
checkNodeCanShowMenu,
|
||||||
|
canShowMoreMenu,
|
||||||
|
canShowPriorityMenu,
|
||||||
|
handleContentChange,
|
||||||
|
replaceableTags,
|
||||||
|
priorityDisableCheck,
|
||||||
|
};
|
||||||
|
}
|
|
@ -622,14 +622,10 @@
|
||||||
}
|
}
|
||||||
for (let i = 0; i < dragNodes.length; i++) {
|
for (let i = 0; i < dragNodes.length; i++) {
|
||||||
const dragNode = (dragNodes as MinderJsonNode[])[i];
|
const dragNode = (dragNodes as MinderJsonNode[])[i];
|
||||||
if (dragNode.parent?.data?.id !== dropNode.parent?.data?.id) {
|
if (dragNode.parent?.data?.id !== dropNode.parent?.data?.id && dragNode.data?.level !== 2) {
|
||||||
// 不允许跨节点拖拽
|
// 不允许跨节点拖拽,只允许拖拽同一节点内的测试集排序
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (dragNode.data?.level === 2) {
|
|
||||||
// 只允许拖拽同一节点内的测试集排序
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,8 +213,15 @@
|
||||||
() => minderStore.getMinderUnsaved,
|
() => minderStore.getMinderUnsaved,
|
||||||
(val) => {
|
(val) => {
|
||||||
setIsSave(!val);
|
setIsSave(!val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
minderStore.setMinderUnsaved(false);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
@ -224,8 +231,6 @@
|
||||||
}
|
}
|
||||||
.ms-minder-container {
|
.ms-minder-container {
|
||||||
@apply relative h-full overflow-hidden !bg-white;
|
@apply relative h-full overflow-hidden !bg-white;
|
||||||
|
|
||||||
padding: 16px 0;
|
|
||||||
}
|
}
|
||||||
.ms-minder-dropdown {
|
.ms-minder-dropdown {
|
||||||
.arco-dropdown-list-wrapper {
|
.arco-dropdown-list-wrapper {
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<a-tabs
|
<a-tabs
|
||||||
v-if="props.mode === 'origin'"
|
v-if="props.mode === 'origin'"
|
||||||
v-model:active-key="innerActiveKey"
|
v-model:active-key="tempActiveKey"
|
||||||
:class="[props.class, props.noContent ? 'no-content' : '']"
|
:class="[props.class, props.noContent ? 'no-content' : '']"
|
||||||
|
@change="(val) => handleTabClick(val as string)"
|
||||||
>
|
>
|
||||||
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="item.label">
|
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="item.label">
|
||||||
<template v-if="props.showBadge" #title>
|
<template v-if="props.showBadge" #title>
|
||||||
<a-badge
|
<a-badge
|
||||||
v-if="props.getTextFunc(item.value) !== ''"
|
v-if="props.getTextFunc(item.value) !== ''"
|
||||||
:class="item.value === innerActiveKey ? 'active-badge' : ''"
|
:class="item.value === tempActiveKey ? 'active-badge' : ''"
|
||||||
:max-count="99"
|
:max-count="99"
|
||||||
:text="props.getTextFunc(item.value)"
|
:text="props.getTextFunc(item.value)"
|
||||||
>
|
>
|
||||||
|
@ -27,8 +28,8 @@
|
||||||
v-for="item of props.contentTabList"
|
v-for="item of props.contentTabList"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
class="ms-tab--button-item"
|
class="ms-tab--button-item"
|
||||||
:class="item.value === innerActiveKey ? 'ms-tab--button-item--active' : ''"
|
:class="item.value === tempActiveKey ? 'ms-tab--button-item--active' : ''"
|
||||||
@click="innerActiveKey = item.value"
|
@click="handleTabClick(item.value)"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,12 +40,12 @@
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
mode?: 'origin' | 'button';
|
mode?: 'origin' | 'button';
|
||||||
activeKey: string;
|
|
||||||
contentTabList: { label: string; value: string }[];
|
contentTabList: { label: string; value: string }[];
|
||||||
class?: string;
|
class?: string;
|
||||||
getTextFunc?: (value: any) => string;
|
getTextFunc?: (value: any) => string;
|
||||||
noContent?: boolean;
|
noContent?: boolean;
|
||||||
showBadge?: boolean;
|
showBadge?: boolean;
|
||||||
|
changeInterceptor?: (newVal: string, oldVal: string, done: () => void) => void;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
mode: 'origin',
|
mode: 'origin',
|
||||||
|
@ -54,9 +55,30 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const innerActiveKey = defineModel<string>('activeKey', {
|
// 实际值,用于最终确认修改的 tab 值
|
||||||
|
const activeKey = defineModel<string>('activeKey', {
|
||||||
default: '',
|
default: '',
|
||||||
});
|
});
|
||||||
|
// 临时值,用于组件内部变更,但未影响到实际值
|
||||||
|
const tempActiveKey = ref(activeKey.value);
|
||||||
|
|
||||||
|
function handleTabClick(value: string) {
|
||||||
|
if (value === activeKey.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.changeInterceptor) {
|
||||||
|
// 存在拦截器,则先将临时值重置为实际值(此时实际值是未变更之前的值),再执行拦截器
|
||||||
|
tempActiveKey.value = activeKey.value;
|
||||||
|
props.changeInterceptor(value, activeKey.value, () => {
|
||||||
|
// 拦截器成功回调=》修改实际值,也将临时值修改为实际值
|
||||||
|
activeKey.value = value;
|
||||||
|
tempActiveKey.value = value;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 不存在拦截器,直接修改实际值
|
||||||
|
activeKey.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -171,6 +171,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||||
import useUserStore from '@/store/modules/user';
|
import useUserStore from '@/store/modules/user';
|
||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
@ -184,6 +185,8 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const minderStore = useMinderStore();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const planId = ref(route.query.id as string);
|
const planId = ref(route.query.id as string);
|
||||||
const detail = ref<TestPlanDetail>({
|
const detail = ref<TestPlanDetail>({
|
||||||
|
@ -204,6 +207,7 @@
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// eslint-disable-next-line prefer-destructuring
|
||||||
countDetail.value = result[0];
|
countDetail.value = result[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,6 +267,7 @@
|
||||||
Message.success(t('common.batchArchiveSuccess'));
|
Message.success(t('common.batchArchiveSuccess'));
|
||||||
initDetail();
|
initDetail();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -403,6 +408,7 @@
|
||||||
try {
|
try {
|
||||||
testPlanTree.value = await getTestPlanModule({ projectId: appStore.currentProjectId });
|
testPlanTree.value = await getTestPlanModule({ projectId: appStore.currentProjectId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,8 +433,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeTabInterceptor(newVal: string, oldVal: string, done: () => void) {
|
function changeTabInterceptor(newVal: string, oldVal: string, done: () => void) {
|
||||||
console.log('changeTabInterceptor', newVal, oldVal);
|
if (oldVal === 'plan' && minderStore.minderUnsaved) {
|
||||||
if (oldVal === 'plan') {
|
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: t('common.tip'),
|
title: t('common.tip'),
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex h-full flex-col">
|
||||||
<div class="p-[16px]">
|
<MsNotRemind tip="testPlan.planTip" class="p-[16px]" type="info" visited-key="testPlanTip" />
|
||||||
<MsNotRemind tip="testPlan.planTip" type="info" visited-key="testPlanTip" />
|
<div class="flex-1 overflow-hidden p-[16px]">
|
||||||
</div>
|
|
||||||
<div class="flex-1 overflow-hidden px-[16px]">
|
|
||||||
<MsTestPlanMinder :plan-id="props.planId" />
|
<MsTestPlanMinder :plan-id="props.planId" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue