feat(功能用例): 脑图保存

This commit is contained in:
baiqi 2024-05-31 16:57:05 +08:00 committed by 刘瑞斌
parent 14dc8d1d56
commit 06d3a96ef5
15 changed files with 396 additions and 149 deletions

View File

@ -90,7 +90,7 @@ import type {
DeleteDependencyParams, DeleteDependencyParams,
DemandItem, DemandItem,
DragCase, DragCase,
FeatureCaseMinder, FeatureCaseMinderUpdateParams,
ImportExcelType, ImportExcelType,
ModulesTreeType, ModulesTreeType,
OperationFile, OperationFile,
@ -182,7 +182,7 @@ export function batchCopyToModules(data: BatchMoveOrCopyType) {
} }
// 保存脑图 // 保存脑图
export function saveCaseMinder(data: FeatureCaseMinder) { export function saveCaseMinder(data: FeatureCaseMinderUpdateParams) {
return MSR.post({ url: `${SaveCaseMinderUrl}`, data }); return MSR.post({ url: `${SaveCaseMinderUrl}`, data });
} }

View File

@ -63,6 +63,7 @@
loading: boolean; loading: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'initTemplate', id: string): void;
(e: 'cancel'): void; (e: 'cancel'): void;
}>(); }>();
@ -118,6 +119,7 @@
}); });
formRules.value = result.filter((e: any) => e); formRules.value = result.filter((e: any) => e);
baseInfoLoading.value = false; baseInfoLoading.value = false;
emit('initTemplate', id);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -129,6 +131,22 @@
}); });
const saveLoading = ref(false); const saveLoading = ref(false);
function makeParams() {
return {
...baseInfoForm.value,
id: props.activeCase.id,
projectId: appStore.currentProjectId,
caseEditType: props.activeCase.caseEditType,
customFields: formItem.value.map((item: any) => {
return {
fieldId: item.field,
value: Array.isArray(item.value) ? JSON.stringify(item.value) : item.value,
};
}),
};
}
function handleSave() { function handleSave() {
baseInfoFormRef.value?.validate((errors) => { baseInfoFormRef.value?.validate((errors) => {
if (!errors) { if (!errors) {
@ -136,20 +154,8 @@
if (valid === true) { if (valid === true) {
try { try {
saveLoading.value = true; saveLoading.value = true;
const data = {
...baseInfoForm.value,
id: props.activeCase.id,
projectId: appStore.currentProjectId,
caseEditType: props.activeCase.caseEditType,
customFields: formItem.value.map((item: any) => {
return {
fieldId: item.field,
value: Array.isArray(item.value) ? JSON.stringify(item.value) : item.value,
};
}),
};
await updateCaseRequest({ await updateCaseRequest({
request: data, request: makeParams(),
fileList: [], fileList: [],
}); });
const selectedNode: MinderJsonNode = window.minder.getSelectedNode(); const selectedNode: MinderJsonNode = window.minder.getSelectedNode();
@ -181,6 +187,10 @@
immediate: true, immediate: true,
} }
); );
defineExpose({
makeParams,
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -13,14 +13,18 @@
single-tag single-tag
tag-enable tag-enable
sequence-enable sequence-enable
@content-change="handleContentChange"
@node-select="handleNodeSelect" @node-select="handleNodeSelect"
@action="handleAction"
@save="handleMinderSave" @save="handleMinderSave"
> >
<template #extractTabContent> <template #extractTabContent>
<baseInfo <baseInfo
v-if="activeExtraKey === 'baseInfo'" v-if="activeExtraKey === 'baseInfo'"
ref="baseInfoRef"
:loading="baseInfoLoading" :loading="baseInfoLoading"
:active-case="activeCase" :active-case="activeCase"
@init-template="(id) => (templateId = id)"
@cancel="handleBaseInfoCancel" @cancel="handleBaseInfoCancel"
/> />
<attachment <attachment
@ -54,9 +58,16 @@
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { getGenerateId, mapTree } from '@/utils'; import { MinderEvent } from '@/store/modules/components/minder-editor/types';
import { filterTree, getGenerateId, mapTree } from '@/utils';
import {
FeatureCaseMinderEditType,
FeatureCaseMinderStepItem,
FeatureCaseMinderUpdateParams,
} from '@/models/caseManagement/featureCase';
import { TableQueryParams } from '@/models/common'; import { TableQueryParams } from '@/models/common';
import { MinderEventName } from '@/enums/minderEnum';
import { convertToFile, initFormCreate } from '@/views/case-management/caseManagementFeature/components/utils'; import { convertToFile, initFormCreate } from '@/views/case-management/caseManagementFeature/components/utils';
@ -72,7 +83,12 @@
const caseTag = t('common.case'); const caseTag = t('common.case');
const moduleTag = t('common.module'); const moduleTag = t('common.module');
const topTags = [moduleTag, caseTag]; const topTags = [moduleTag, caseTag];
const descTags = [t('ms.minders.stepDesc'), t('ms.minders.textDesc')]; const stepTag = t('ms.minders.stepDesc');
const textTag = t('ms.minders.textDesc');
const prerequisiteTag = t('ms.minders.precondition');
const remarkTag = t('common.remark');
const descTags = [stepTag, textTag];
const caseChildTags = [prerequisiteTag, stepTag, textTag, remarkTag];
const importJson = ref<MinderJson>({ const importJson = ref<MinderJson>({
root: {} as MinderJsonNode, root: {} as MinderJsonNode,
template: 'default', template: 'default',
@ -80,6 +96,14 @@
}); });
const caseTree = ref<MinderJsonNode[]>([]); const caseTree = ref<MinderJsonNode[]>([]);
const loading = ref(false); const loading = ref(false);
const tempMinderParams = ref<FeatureCaseMinderUpdateParams>({
projectId: appStore.currentProjectId,
versionId: '',
updateCaseList: [],
updateModuleList: [],
deleteResourceList: [],
});
const templateId = ref('');
/** /**
* 初始化用例模块树 * 初始化用例模块树
@ -99,6 +123,7 @@
resource: e.data?.id === 'fakeNode' ? [] : [moduleTag], resource: e.data?.id === 'fakeNode' ? [] : [moduleTag],
expandState: e.level === 1 ? 'expand' : 'collapse', expandState: e.level === 1 ? 'expand' : 'collapse',
count: props.modulesCount[e.id], count: props.modulesCount[e.id],
isNew: false,
}, },
children: children:
props.modulesCount[e.id] > 0 && !e.children?.length props.modulesCount[e.id] > 0 && !e.children?.length
@ -108,6 +133,7 @@
id: 'fakeNode', id: 'fakeNode',
text: 'fakeNode', text: 'fakeNode',
resource: ['fakeNode'], resource: ['fakeNode'],
isNew: false,
}, },
}, },
] ]
@ -140,11 +166,19 @@
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleId: props.moduleId === 'all' ? '' : props.moduleId, moduleId: props.moduleId === 'all' ? '' : props.moduleId,
}); });
importJson.value.root.children = res; importJson.value.root.children = mapTree(res, (node) => {
return {
...node,
data: {
...node.data,
isNew: false,
},
};
});
importJson.value.root.data = { importJson.value.root.data = {
id: props.moduleId === 'all' ? '' : props.moduleId, id: props.moduleId === 'all' ? '' : props.moduleId,
text: props.moduleName, text: props.moduleName,
resource: [t('common.module')], resource: [moduleTag],
}; };
window.minder.importJson(importJson.value); window.minder.importJson(importJson.value);
} catch (error) { } catch (error) {
@ -163,15 +197,83 @@
} }
}); });
async function handleMinderSave(data: any) { const baseInfoRef = ref<InstanceType<typeof baseInfo>>();
/**
* 解析用例节点信息
* @param node 用例节点
*/
function getCaseNodeInfo(node: MinderJsonNode) {
let textStep: MinderJsonNode | undefined;
let prerequisiteNode: MinderJsonNode | undefined;
let remarkNode: MinderJsonNode | undefined;
const stepNodes: MinderJsonNode[] = [];
node.children?.forEach((item) => {
if (item.data.resource?.includes(textTag)) {
textStep = item;
} else if (item.data.resource?.includes(stepTag)) {
stepNodes.push(item);
} else if (item.data.resource?.includes(prerequisiteTag)) {
prerequisiteNode = item;
} else if (item.data.resource?.includes(remarkTag)) {
remarkNode = item;
}
});
const steps: FeatureCaseMinderStepItem[] = stepNodes.map((child, i) => {
return {
id: child.data.id,
num: i,
desc: child.data.text,
result: child.children?.[0].data.text || '',
};
});
return {
prerequisite: prerequisiteNode?.data.text || '',
caseEditType: steps.length > 0 ? 'STEP' : ('TEXT' as FeatureCaseMinderEditType),
steps,
textDescription: textStep?.data.text || '',
expectedResult: textStep?.children?.[0]?.data.text || '',
description: remarkNode?.data.text || '',
};
}
/**
* 生成脑图保存的入参
*/
function makeMinderParams(): FeatureCaseMinderUpdateParams {
const fullJson: MinderJson = window.minder.exportJson();
filterTree(fullJson.root.children, (node) => {
if (node.data.isNew !== false || node.data.changed === true) {
if (node.data.resource?.includes(moduleTag)) {
tempMinderParams.value.updateModuleList.push({
id: node.data.id,
name: node.data.text,
parentId: node.parent?.data.id || '',
type: node.data.isNew ? 'ADD' : 'UPDATE',
});
} else if (node.data.resource?.includes(caseTag)) {
const caseNodeInfo = getCaseNodeInfo(node as MinderJsonNode);
tempMinderParams.value.updateCaseList.push({
id: node.data.id,
name: node.data.text,
moduleId: node.parent?.data.id || '',
type: node.data.isNew ? 'ADD' : 'UPDATE',
templateId: templateId.value,
tags: node.data.resource || [],
customFields: baseInfoRef.value?.makeParams().customFields || [],
...caseNodeInfo,
});
return false; //
}
}
return true;
});
return tempMinderParams.value;
}
async function handleMinderSave() {
try { try {
await saveCaseMinder({ await saveCaseMinder(makeMinderParams());
projectId: appStore.currentProjectId,
versionId: '',
updateCaseList: data,
updateModuleList: [],
deleteResourceList: [],
});
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -196,21 +298,24 @@
if (node.data?.resource?.some((e) => descTags.includes(e))) { if (node.data?.resource?.some((e) => descTags.includes(e))) {
// //
if ( if (
node.data.resource.includes(t('ms.minders.stepDesc')) && node.data.resource.includes(stepTag) &&
(node.parent?.children?.filter((e) => e.data?.resource?.includes(t('ms.minders.stepDesc'))) || []).length > 1 (node.parent?.children?.filter((e) => e.data?.resource?.includes(stepTag)) || []).length > 1
) { ) {
// //
return []; return [];
} }
return descTags.filter((tag) => !node.data?.resource?.includes(tag)); 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 ( if (
(!node.data?.resource || node.data.resource.length === 0) && (!node.data?.resource || node.data.resource.length === 0) &&
(!node.parent?.data?.resource || (!node.parent?.data?.resource ||
node.parent?.data?.resource.length === 0 || node.parent?.data?.resource.length === 0 ||
node.parent?.data?.resource?.some((e) => topTags.includes(e))) node.parent?.data?.resource?.some((e) => topTags.includes(e)))
) { ) {
//
// //
return node.children && return node.children &&
(node.children.some((e) => e.data?.resource?.includes(caseTag)) || (node.children.some((e) => e.data?.resource?.includes(caseTag)) ||
@ -242,24 +347,14 @@
parent: node, parent: node,
data: { data: {
id: getGenerateId(), id: getGenerateId(),
text: t('ms.minders.precondition'), text: prerequisiteTag,
resource: [t('ms.minders.precondition')], resource: [prerequisiteTag],
expandState: 'expand', expandState: 'expand',
isNew: true,
}, },
children: [], children: [],
}; };
const sibling = {
parent: child,
data: {
id: getGenerateId(),
text: '',
resource: [],
},
};
execInert(type, child.data); execInert(type, child.data);
nextTick(() => {
execInert('AppendChildNode', sibling.data);
});
} }
/** /**
@ -272,38 +367,15 @@
parent: node, parent: node,
data: { data: {
id: getGenerateId(), id: getGenerateId(),
text: t('common.remark'), text: remarkTag,
resource: [t('common.remark')], resource: [remarkTag],
isNew: true,
}, },
children: [], children: [],
}; };
execInert(type, child.data); execInert(type, child.data);
} }
// function insertTextDesc(node: MinderJsonNode, type: string) {
// const child = {
// parent: node,
// data: {
// id: getGenerateId(),
// text: t('ms.minders.textDesc'),
// resource: [t('ms.minders.textDesc')],
// },
// children: [],
// };
// const sibling = {
// parent: child,
// data: {
// id: getGenerateId(),
// text: t('ms.minders.stepExpect'),
// resource: [t('ms.minders.stepExpect')],
// },
// };
// execInert(type, {
// ...child,
// children: [sibling],
// });
// }
/** /**
* 插入步骤描述 * 插入步骤描述
* @param node 目标节点 * @param node 目标节点
@ -316,6 +388,7 @@
id: getGenerateId(), id: getGenerateId(),
text: t('ms.minders.stepDesc'), text: t('ms.minders.stepDesc'),
resource: [t('ms.minders.stepDesc')], resource: [t('ms.minders.stepDesc')],
isNew: true,
}, },
children: [], children: [],
}; };
@ -325,6 +398,7 @@
id: getGenerateId(), id: getGenerateId(),
text: t('ms.minders.stepExpect'), text: t('ms.minders.stepExpect'),
resource: [t('ms.minders.stepExpect')], resource: [t('ms.minders.stepExpect')],
isNew: true,
}, },
}; };
execInert(type, child.data); execInert(type, child.data);
@ -345,6 +419,7 @@
id: getGenerateId(), id: getGenerateId(),
text: t('ms.minders.stepExpect'), text: t('ms.minders.stepExpect'),
resource: [t('ms.minders.stepExpect')], resource: [t('ms.minders.stepExpect')],
isNew: true,
}, },
children: [], children: [],
}; };
@ -373,11 +448,11 @@
let hasRemark = false; let hasRemark = false;
for (let i = 0; i < node.children.length; i++) { for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]; const child = node.children[i];
if (child.data?.resource?.includes(t('ms.minders.precondition'))) { if (child.data?.resource?.includes(prerequisiteTag)) {
hasPreCondition = true; hasPreCondition = true;
} else if (child.data?.resource?.includes(t('ms.minders.textDesc'))) { } else if (child.data?.resource?.includes(textTag)) {
hasTextDesc = true; hasTextDesc = true;
} else if (child.data?.resource?.includes(t('common.remark'))) { } else if (child.data?.resource?.includes(remarkTag)) {
hasRemark = true; hasRemark = true;
} }
} }
@ -393,20 +468,16 @@
} }
} }
} else if ( } else if (
(node.data?.resource?.includes(t('ms.minders.stepDesc')) || (node.data?.resource?.includes(stepTag) || node.data?.resource?.includes(textTag)) &&
node.data?.resource?.includes(t('ms.minders.textDesc'))) &&
(!node.children || node.children.length === 0) (!node.children || node.children.length === 0)
) { ) {
// //
insertExpect(node, 'AppendChildNode'); insertExpect(node, 'AppendChildNode');
} else if (node.data?.resource?.includes(t('ms.minders.precondition'))) { } else if (node.data?.resource?.includes(prerequisiteTag) && (!node.children || node.children.length === 0)) {
// //
execInert('AppendChildNode'); execInert('AppendChildNode');
} }
break; break;
case 'AppendParentNode':
execInert('AppendParentNode');
break;
case 'AppendSiblingNode': case 'AppendSiblingNode':
if (node.parent?.data?.resource?.includes(caseTag) && node.parent?.children) { if (node.parent?.data?.resource?.includes(caseTag) && node.parent?.children) {
// //
@ -415,11 +486,11 @@
let hasRemark = false; let hasRemark = false;
for (let i = 0; i < node.parent.children.length; i++) { for (let i = 0; i < node.parent.children.length; i++) {
const sibling = node.parent.children[i]; const sibling = node.parent.children[i];
if (sibling.data?.resource?.includes(t('ms.minders.precondition'))) { if (sibling.data?.resource?.includes(prerequisiteTag)) {
hasPreCondition = true; hasPreCondition = true;
} else if (sibling.data?.resource?.includes(t('common.remark'))) { } else if (sibling.data?.resource?.includes(remarkTag)) {
hasRemark = true; hasRemark = true;
} else if (sibling.data?.resource?.includes(t('ms.minders.textDesc'))) { } else if (sibling.data?.resource?.includes(textTag)) {
hasTextDesc = true; hasTextDesc = true;
} }
} }
@ -460,10 +531,39 @@
*/ */
function afterTagEdit(node: MinderJsonNode, tag: string) { function afterTagEdit(node: MinderJsonNode, tag: string) {
if (tag === moduleTag && node.data) { if (tag === moduleTag && node.data) {
//
tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter(
(e) => e.id !== node.data.id
);
// tempMinderParams.value.updateModuleList.push({
// id: node.data.id,
// name: node.data.text,
// type: 'ADD',
// parentId: node.parent?.data.id || '',
// });
window.minder.execCommand('priority'); window.minder.execCommand('priority');
} else if (node.data.resource?.includes(caseTag)) {
//
tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter(
(e) => e.id !== node.data.id
);
// tempMinderParams.value.updateCaseList.push({
// id: node.data.id,
// name: node.data.text,
// moduleId: node.parent?.data.id || '',
// type: 'ADD',
// templateId: templateId.value,
// prerequisite: '',
// caseEditType: 'STEP',
// steps: [],
// textDescription: '',
// expectedResult: '',
// description: '',
// tags: [],
// customFields: [],
// });
} }
} }
const baseInfoLoading = ref(false); const baseInfoLoading = ref(false);
const formRules = ref<FormItem[]>([]); const formRules = ref<FormItem[]>([]);
@ -562,6 +662,24 @@
resetExtractInfo(); resetExtractInfo();
} }
function handleContentChange(node: MinderJsonNode) {
const { resource } = node.data;
//
if (
resource?.includes(prerequisiteTag) ||
resource?.includes(stepTag) ||
resource?.includes(textTag) ||
resource?.includes(remarkTag)
) {
if (node.parent) {
node.parent.data.changed = true;
}
} else if (node.parent?.parent?.data.resource?.includes(caseTag)) {
//
node.parent.parent.data.changed = true;
}
}
/** /**
* 处理脑图节点激活/点击 * 处理脑图节点激活/点击
* @param node 被激活/点击的节点 * @param node 被激活/点击的节点
@ -594,15 +712,33 @@
// TODO: // TODO:
res.forEach((e) => { res.forEach((e) => {
// //
const child = window.minder.createNode(e.data, node); const child = window.minder.createNode(
{
...e.data,
isNew: false,
},
node
);
child.render(); child.render();
e.children?.forEach((item) => { e.children?.forEach((item) => {
// // // //
const grandChild = window.minder.createNode(item.data, child); const grandChild = window.minder.createNode(
{
...item.data,
isNew: false,
},
child
);
grandChild.render(); grandChild.render();
item.children?.forEach((subItem) => { item.children?.forEach((subItem) => {
// //
const greatGrandChild = window.minder.createNode(subItem.data, grandChild); const greatGrandChild = window.minder.createNode(
{
...subItem.data,
isNew: false,
},
grandChild
);
greatGrandChild.render(); greatGrandChild.render();
}); });
}); });
@ -626,6 +762,37 @@
resetExtractInfo(); resetExtractInfo();
} }
} }
/**
* 处理脑图节点操作
* @param event 脑图事件对象
*/
function handleAction(event: MinderEvent) {
const { node, name } = event;
if (node) {
switch (name) {
case MinderEventName.DELETE_NODE:
tempMinderParams.value.deleteResourceList.push({
id: node.data.id,
type: node.data?.resource?.[0] || moduleTag,
});
if (node.data?.resource?.includes(caseTag)) {
//
tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter(
(e) => e.id !== node.data.id
);
} else if (node.data?.resource?.includes(moduleTag)) {
//
tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter(
(e) => e.id !== node.data.id
);
}
break;
default:
break;
}
}
}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -0,0 +1,46 @@
import { debounce } from 'lodash-es';
import useMinderStore from '@/store/modules/components/minder-editor/index';
import type { MinderEvent } from '@/store/modules/components/minder-editor/types';
import type { MinderJsonNode } from '../props';
export interface UseEventListenerProps {
handleContentChange?: (node: MinderJsonNode) => void;
handleSelectionChange?: (node: MinderJsonNode) => void;
handleMinderEvent?: (event: MinderEvent) => void;
}
export default function useEventListener(listener: UseEventListenerProps) {
const { minder } = window;
const minderStore = useMinderStore();
// 监听脑图节点内容变化
minder.on('contentchange', () => {
const node: MinderJsonNode = minder.getSelectedNode();
if (listener.handleContentChange) {
listener.handleContentChange(node);
}
});
// 监听脑图选中节点变化
minder.on(
'selectionchange',
debounce(() => {
const node: MinderJsonNode = minder.getSelectedNode();
if (listener.handleSelectionChange) {
listener.handleSelectionChange(node);
}
}, 300)
);
// 监听脑图自定义事件
watch(
() => minderStore.event.timestamp,
() => {
if (listener.handleMinderEvent) {
listener.handleMinderEvent(minderStore.event);
}
}
);
}

View File

@ -0,0 +1 @@
export default function useShortCut() {}

View File

@ -19,12 +19,6 @@
<div class="ml-[4px] text-[var(--color-text-4)]">( / )</div> <div class="ml-[4px] text-[var(--color-text-4)]">( / )</div>
</div> </div>
</a-doption> </a-doption>
<a-doption value="insetParent">
<div class="flex items-center">
<div>{{ t('minder.hotboxMenu.insetParent') }}</div>
<div class="ml-[4px] text-[var(--color-text-4)]">(Shift + Tab)</div>
</div>
</a-doption>
<a-doption value="insetSon"> <a-doption value="insetSon">
<div class="flex items-center"> <div class="flex items-center">
<div>{{ t('minder.hotboxMenu.insetSon') }}</div> <div>{{ t('minder.hotboxMenu.insetSon') }}</div>
@ -94,7 +88,15 @@
import { MinderEventName } from '@/enums/minderEnum'; import { MinderEventName } from '@/enums/minderEnum';
import { editMenuProps, insertProps, mainEditorProps, MinderJsonNode, priorityProps, tagProps } from '../props'; import {
editMenuProps,
insertProps,
mainEditorProps,
MinderJson,
MinderJsonNode,
priorityProps,
tagProps,
} from '../props';
import Editor from '../script/editor'; import Editor from '../script/editor';
import { markChangeNode, markDeleteNode } from '../script/tool/utils'; import { markChangeNode, markDeleteNode } from '../script/tool/utils';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
@ -102,11 +104,10 @@
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ ...editMenuProps, ...insertProps, ...mainEditorProps, ...tagProps, ...priorityProps }); const props = defineProps({ ...editMenuProps, ...insertProps, ...mainEditorProps, ...tagProps, ...priorityProps });
const emit = defineEmits({ const emit = defineEmits<{
afterMount: () => ({}), (e: 'afterMount'): void;
save: (json) => json, (e: 'save', json: MinderJson): void;
enterNode: (data) => data, }>();
});
const minderStore = useMinderStore(); const minderStore = useMinderStore();
const mec: Ref<HTMLDivElement | null> = ref(null); const mec: Ref<HTMLDivElement | null> = ref(null);
@ -192,7 +193,7 @@
'appendsiblingnode', 'appendsiblingnode',
]); ]);
if (selectNodes && !notChangeCommands.has(env.commandName.toLocaleLowerCase())) { if (selectNodes && !notChangeCommands.has(env.commandName.toLocaleLowerCase())) {
selectNodes.forEach((node: any) => { selectNodes.forEach((node: MinderJsonNode) => {
markChangeNode(node); markChangeNode(node);
}); });
} }
@ -233,6 +234,7 @@
innerImportJson.value.data.expandState = 'expand'; innerImportJson.value.data.expandState = 'expand';
window.minder.importJson(innerImportJson.value); window.minder.importJson(innerImportJson.value);
window.minder.execCommand('template', Object.keys(window.kityminder.Minder.getTemplateList())[minderStore.mold]); window.minder.execCommand('template', Object.keys(window.kityminder.Minder.getTemplateList())[minderStore.mold]);
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, node);
} }
watch( watch(
@ -280,27 +282,35 @@
} else { } else {
window.minder.execCommand('Collapse'); window.minder.execCommand('Collapse');
} }
minderStore.dispatchEvent(MinderEventName.EXPAND, undefined, undefined, selectedNode);
break; break;
case 'insetParent': case 'insetParent':
execInsertCommand('AppendParentNode'); execInsertCommand('AppendParentNode');
minderStore.dispatchEvent(MinderEventName.INSERT_PARENT, undefined, undefined, selectedNode);
break; break;
case 'insetSon': case 'insetSon':
execInsertCommand('AppendChildNode'); execInsertCommand('AppendChildNode');
minderStore.dispatchEvent(MinderEventName.INSERT_CHILD, undefined, undefined, selectedNode);
break; break;
case 'insetBrother': case 'insetBrother':
execInsertCommand('AppendSiblingNode'); execInsertCommand('AppendSiblingNode');
minderStore.dispatchEvent(MinderEventName.INSERT_SIBLING, undefined, undefined, selectedNode);
break; break;
case 'copy': case 'copy':
window.minder.execCommand('Copy'); window.minder.execCommand('Copy');
minderStore.dispatchEvent(MinderEventName.COPY_NODE, undefined, undefined, selectedNode);
break; break;
case 'cut': case 'cut':
window.minder.execCommand('Cut'); window.minder.execCommand('Cut');
minderStore.dispatchEvent(MinderEventName.CUT_NODE, undefined, undefined, selectedNode);
break; break;
case 'paste': case 'paste':
window.minder.execCommand('Paste'); window.minder.execCommand('Paste');
minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, selectedNode);
break; break;
case 'delete': case 'delete':
window.minder.execCommand('RemoveNode'); window.minder.execCommand('RemoveNode');
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, selectedNode);
break; break;
case 'enterNode': case 'enterNode':
switchNode(selectedNode.data); switchNode(selectedNode.data);

View File

@ -15,10 +15,15 @@
<script lang="ts" name="TagBox" setup> <script lang="ts" name="TagBox" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'; import { nextTick, onMounted, reactive, ref } from 'vue';
import useMinderStore from '@/store/modules/components/minder-editor';
import { MinderEventName } from '@/enums/minderEnum';
import { MinderJsonNode, tagProps } from '../../props'; import { MinderJsonNode, tagProps } from '../../props';
import { isDisableNode, isTagEnable } from '../../script/tool/utils'; import { isDisableNode, isTagEnable } from '../../script/tool/utils';
const props = defineProps(tagProps); const props = defineProps(tagProps);
const minderStore = useMinderStore();
let minder = reactive<any>({}); let minder = reactive<any>({});
const commandDisabled = ref(true); const commandDisabled = ref(true);
@ -94,6 +99,7 @@
} }
window.minder.execCommand('resource', origin); window.minder.execCommand('resource', origin);
const node: MinderJsonNode = minder.getSelectedNode(); const node: MinderJsonNode = minder.getSelectedNode();
minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, node);
if (props.replaceableTags) { if (props.replaceableTags) {
tagList.value = props.replaceableTags(node); tagList.value = props.replaceableTags(node);
} }

View File

@ -63,14 +63,14 @@
</template> </template>
<script lang="ts" name="minderEditor" setup> <script lang="ts" name="minderEditor" setup>
import { debounce } from 'lodash-es';
import MsTab from '@/components/pure/ms-tab/index.vue'; import MsTab from '@/components/pure/ms-tab/index.vue';
import minderHeader from './main/header.vue'; import minderHeader from './main/header.vue';
import mainEditor from './main/mainEditor.vue'; import mainEditor from './main/mainEditor.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { MinderEvent } from '@/store/modules/components/minder-editor/types';
import useEventListener from './hooks/useEventListener';
import { import {
delProps, delProps,
editMenuProps, editMenuProps,
@ -88,8 +88,10 @@
(e: 'moldChange', data: number): void; (e: 'moldChange', data: number): void;
(e: 'save', data: Record<string, any>): void; (e: 'save', data: Record<string, any>): void;
(e: 'afterMount'): void; (e: 'afterMount'): void;
(e: 'enterNode', data: any): void; (e: 'enterNode', data: MinderJsonNode): void;
(e: 'nodeSelect', data: any): void; (e: 'nodeSelect', data: MinderJsonNode): void;
(e: 'contentChange', data: MinderJsonNode): void;
(e: 'action', event: MinderEvent): void;
}>(); }>();
const props = defineProps({ const props = defineProps({
@ -133,18 +135,19 @@
} }
onMounted(() => { onMounted(() => {
nextTick(() => { useEventListener({
if (window.minder.on) { handleSelectionChange: () => {
window.minder.on( const selectedNode: MinderJsonNode = window.minder.getSelectedNode();
'selectionchange', if (Object.keys(window.minder).length > 0 && selectedNode) {
debounce(() => { emit('nodeSelect', selectedNode);
const selectedNode: MinderJsonNode = window.minder.getSelectedNode(); }
if (Object.keys(window.minder).length > 0 && selectedNode) { },
emit('nodeSelect', selectedNode); handleContentChange: (node: MinderJsonNode) => {
} emit('contentChange', node);
}, 300) },
); handleMinderEvent: (event) => {
} emit('action', event);
},
}); });
}); });
</script> </script>

View File

@ -16,6 +16,8 @@ export interface MinderJsonNodeData {
expandState?: 'collapse' | 'expand'; expandState?: 'collapse' | 'expand';
priority?: number; priority?: number;
// 前端渲染字段 // 前端渲染字段
isNew?: boolean; // 是否脑图新增节点,需要在初始化脑图数据时标记已存在节点为 false 以区分是否新增节点
changed?: boolean; // 脑图节点是否发生过变化
[key: string]: any; [key: string]: any;
} }
export interface MinderJsonNode { export interface MinderJsonNode {

View File

@ -1,3 +1,5 @@
import type { MinderJsonNode } from '../../props';
export function isDisableNode(minder: any) { export function isDisableNode(minder: any) {
let node; let node;
if (minder && minder.getSelectedNode) { if (minder && minder.getSelectedNode) {
@ -20,7 +22,7 @@ export function isDeleteDisableNode(minder: any) {
return false; return false;
} }
export function isTagEnableNode(node: any) { export function isTagEnableNode(node: MinderJsonNode) {
if (node && (node.data.tagEnable === true || node.data.allowDisabledTag === true)) { if (node && (node.data.tagEnable === true || node.data.allowDisabledTag === true)) {
return true; return true;
} }
@ -38,19 +40,13 @@ export function isTagEnable(minder: any) {
return false; return false;
} }
export function markChangeNode(node: any) { export function markChangeNode(node: MinderJsonNode) {
if (node && node.data) { if (node.data) {
// 修改的该节点标记为 contextChanged node.data.changed = true;
node.data.contextChanged = true;
while (node) {
// 该路径上的节点都标记为 changed
node.data.changed = true;
node = node.parent;
}
} }
} }
function markDelNode(node: any, deleteChild: any) { function markDelNode(node: MinderJsonNode, deleteChild: any) {
deleteChild.push(node.data); deleteChild.push(node.data);
if (node.children) { if (node.children) {
node.children.forEach((child: any) => { node.children.forEach((child: any) => {
@ -63,7 +59,7 @@ function markDelNode(node: any, deleteChild: any) {
export function markDeleteNode(minder: any) { export function markDeleteNode(minder: any) {
if (minder) { if (minder) {
const nodes = minder.getSelectedNodes(); const nodes = minder.getSelectedNodes();
nodes.forEach((node: any) => { nodes.forEach((node: MinderJsonNode) => {
if (node && node.parent) { if (node && node.parent) {
const pData = node.parent.data; const pData = node.parent.data;
if (!pData.deleteChild) { if (!pData.deleteChild) {
@ -118,7 +114,7 @@ export function resetNodes(nodes: any) {
} }
} }
export function isDisableForNode(node: any) { export function isDisableForNode(node: MinderJsonNode) {
if (node && node.data.disable === true) { if (node && node.data.disable === true) {
return true; return true;
} }

View File

@ -2,6 +2,14 @@ export enum MinderEventName {
'DELETE_NODE' = 'DELETE_NODE', // 删除节点 'DELETE_NODE' = 'DELETE_NODE', // 删除节点
'HOTBOX' = 'HOTBOX', // 热键菜单 'HOTBOX' = 'HOTBOX', // 热键菜单
'ENTER_NODE' = 'ENTER_NODE', // 进入节点 'ENTER_NODE' = 'ENTER_NODE', // 进入节点
'EXPAND' = 'EXPAND', // 展开节点
'INSERT_PARENT' = 'INSERT_PARENT', // 插入父节点
'INSERT_CHILD' = 'INSERT_CHILD', // 插入子节点
'INSERT_SIBLING' = 'INSERT_SIBLING', // 插入同级节点
'COPY_NODE' = 'COPY_NODE', // 复制节点
'PASTE_NODE' = 'PASTE_NODE', // 粘贴节点
'CUT_NODE' = 'CUT_NODE', // 剪切节点
'SET_TAG' = 'SET_TAG', // 设置节点标签
} }
export default {}; export default {};

View File

@ -365,7 +365,7 @@ export interface ContentTabsMap {
backupTabList: TabItemType[]; backupTabList: TabItemType[];
} }
// 脑图删除的模块/用例的集合 // 脑图删除的模块/用例的集合
export interface FeatureCaseMinderDeleteResourceList { export interface FeatureCaseMinderDeleteResourceItem {
id: string; id: string;
type: string; type: string;
} }
@ -374,7 +374,7 @@ export type FeatureCaseMinderActionType = 'ADD' | 'UPDATE';
// 脑图用例编辑模式 // 脑图用例编辑模式
export type FeatureCaseMinderEditType = 'STEP' | 'TEXT'; export type FeatureCaseMinderEditType = 'STEP' | 'TEXT';
// 脑图新增/修改的模块集合(只记录操作的节点,节点下的子节点不需要记录) // 脑图新增/修改的模块集合(只记录操作的节点,节点下的子节点不需要记录)
export interface FeatureCaseMinderUpdateModuleList { export interface FeatureCaseMinderUpdateModuleItem {
id: string; id: string;
name: string; name: string;
parentId: string; parentId: string;
@ -395,7 +395,7 @@ export interface FeatureCaseMinderStepItem {
result?: string; result?: string;
} }
// 脑图新增/修改的用例对象集合 // 脑图新增/修改的用例对象集合
export interface FeatureCaseMinderUpdateCaseList { export interface FeatureCaseMinderUpdateCaseItem {
id: string; // 用例id(新增的时候前端传UUid更新的时候必填) id: string; // 用例id(新增的时候前端传UUid更新的时候必填)
templateId: string; // 模板id templateId: string; // 模板id
type: FeatureCaseMinderActionType; type: FeatureCaseMinderActionType;
@ -403,7 +403,7 @@ export interface FeatureCaseMinderUpdateCaseList {
moduleId: string; moduleId: string;
moveMode?: MoveMode; // 移动方式(节点移动或新增时需要) moveMode?: MoveMode; // 移动方式(节点移动或新增时需要)
targetId?: string; targetId?: string;
prerequisite: string; prerequisite: string; // 前置条件
caseEditType: FeatureCaseMinderEditType; caseEditType: FeatureCaseMinderEditType;
steps: FeatureCaseMinderStepItem[]; steps: FeatureCaseMinderStepItem[];
textDescription: string; // 文本描述 textDescription: string; // 文本描述
@ -413,10 +413,10 @@ export interface FeatureCaseMinderUpdateCaseList {
customFields: CustomField[]; customFields: CustomField[];
} }
// 脑图 // 脑图
export interface FeatureCaseMinder { export interface FeatureCaseMinderUpdateParams {
projectId: string; projectId: string;
versionId?: string; versionId?: string;
updateCaseList: FeatureCaseMinderUpdateCaseList[]; updateCaseList: FeatureCaseMinderUpdateCaseItem[];
updateModuleList: FeatureCaseMinderUpdateModuleList[]; updateModuleList: FeatureCaseMinderUpdateModuleItem[];
deleteResourceList: FeatureCaseMinderDeleteResourceList[]; deleteResourceList: FeatureCaseMinderDeleteResourceItem[];
} }

View File

@ -16,14 +16,14 @@
@refresh="fetchData()" @refresh="fetchData()"
> >
<template #right> <template #right>
<!-- <a-radio-group v-model:model-value="showType" type="button" size="small" class="list-show-type"> <a-radio-group v-model:model-value="showType" type="button" size="small" class="list-show-type">
<a-radio value="list" class="show-type-icon !m-[2px]"> <a-radio value="list" class="show-type-icon !m-[2px]">
<MsIcon :size="14" type="icon-icon_view-list_outlined" /> <MsIcon :size="14" type="icon-icon_view-list_outlined" />
</a-radio> </a-radio>
<a-radio value="xMind" class="show-type-icon !m-[2px]"> <a-radio value="xMind" class="show-type-icon !m-[2px]">
<MsIcon :size="14" type="icon-icon_mindnote_outlined" /> <MsIcon :size="14" type="icon-icon_mindnote_outlined" />
</a-radio> </a-radio>
</a-radio-group> --> </a-radio-group>
</template> </template>
</MsAdvanceFilter> </MsAdvanceFilter>
<ms-base-table <ms-base-table

View File

@ -64,31 +64,29 @@
width: 400px; width: 400px;
height: 40px; height: 40px;
} }
.login-qrcode { .login-qrcode {
min-width: 480px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column;
margin-top: 24px; margin-top: 24px;
min-width: 480px;
flex-direction: column;
.qrcode { .qrcode {
display: flex; display: flex;
overflow: hidden;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: hidden;
border-radius: 8px; border-radius: 8px;
background: #fff; background: #ffffff;
} }
.title { .title {
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
margin: 0px 0 16px 0; align-items: center;
overflow: hidden; overflow: hidden;
margin: 0 0 16px;
font-size: 18px; font-size: 18px;
font-style: normal;
font-weight: 500; font-weight: 500;
font-style: normal;
line-height: 26px; line-height: 26px;
.ed-icon { .ed-icon {
margin-right: 8px; margin-right: 8px;

View File

@ -39,10 +39,10 @@
state: 'fit2cloud-wecom-qr', state: 'fit2cloud-wecom-qr',
redirect_type: WWLoginRedirectType.callback, redirect_type: WWLoginRedirectType.callback,
}, },
onCheckWeComLogin({ isWeComLogin }) { onCheckWeComLogin({ isWeComLogin }: any) {
console.log(isWeComLogin); console.log(isWeComLogin);
}, },
async onLoginSuccess({ code }) { async onLoginSuccess({ code }: any) {
const weComCallback = getWeComCallback(code); const weComCallback = getWeComCallback(code);
userStore.qrCodeLogin(await weComCallback); userStore.qrCodeLogin(await weComCallback);
Message.success(t('login.form.login.success')); Message.success(t('login.form.login.success'));
@ -71,7 +71,7 @@
}, },
}); });
}, },
onLoginFail(err) { onLoginFail(err: any) {
console.log(err); console.log(err);
}, },
}); });