feat(脑图): 测试规划脑图收尾&功能用例脑图交互逻辑抽离&ms-tab组件增加切换拦截

This commit is contained in:
baiqi 2024-06-13 11:03:02 +08:00 committed by 刘瑞斌
parent ba26a0c878
commit d759105e57
7 changed files with 821 additions and 795 deletions

View File

@ -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') {
// NONENONE
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;

View File

@ -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,
};
}

View File

@ -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;
} }

View File

@ -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 {

View File

@ -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>

View File

@ -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'),

View File

@ -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>