feat(脑图): 脑图节点菜单&空白节点&用例评审未评审数量
This commit is contained in:
parent
3fe1bc68dd
commit
8ae4c91500
|
@ -42,6 +42,7 @@ import {
|
|||
GetAssociationPublicCasePageUrl,
|
||||
GetAssociationPublicModuleTreeUrl,
|
||||
GetCaseListUrl,
|
||||
GetCaseMinderTreeUrl,
|
||||
GetCaseMinderUrl,
|
||||
GetCaseModulesCountUrl,
|
||||
GetCaseModuleTreeUrl,
|
||||
|
@ -191,6 +192,11 @@ export function getCaseMinder(data: { projectId: string; moduleId: string }) {
|
|||
return MSR.post<MinderJsonNode[]>({ url: `${GetCaseMinderUrl}`, data });
|
||||
}
|
||||
|
||||
// 获取脑图模块树(包含文本节点)
|
||||
export function getCaseMinderTree(data: { projectId: string; moduleId: string }) {
|
||||
return MSR.post<MinderJsonNode[]>({ url: `${GetCaseMinderTreeUrl}`, data });
|
||||
}
|
||||
|
||||
// 回收站
|
||||
|
||||
// 回收站用例分页表
|
||||
|
|
|
@ -28,6 +28,7 @@ export const GetSearchCustomFieldsUrl = '/functional/case/custom/field';
|
|||
export const GetAssociatedFilePageUrl = '/attachment/page';
|
||||
export const SaveCaseMinderUrl = '/functional/mind/case/edit'; // 保存用例脑图
|
||||
export const GetCaseMinderUrl = '/functional/mind/case/list'; // 获取脑图数据
|
||||
export const GetCaseMinderTreeUrl = '/functional/mind/case/tree'; // 获取脑图模块树(含文本节点)
|
||||
|
||||
// 获取模块树
|
||||
export const GetCaseModuleTreeUrl = '/functional/case/module/tree';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@font-face {
|
||||
font-family: iconfont; /* Project id 3462279 */
|
||||
src: url('iconfont.woff2?t=1717669877554') format('woff2'), url('iconfont.woff?t=1717669877554') format('woff'),
|
||||
url('iconfont.ttf?t=1717669877554') format('truetype'), url('iconfont.svg?t=1717669877554#iconfont') format('svg');
|
||||
src: url('iconfont.woff2?t=1717664244652') format('woff2'), url('iconfont.woff?t=1717664244652') format('woff'),
|
||||
url('iconfont.ttf?t=1717664244652') format('truetype'), url('iconfont.svg?t=1717664244652#iconfont') format('svg');
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 16px;
|
||||
|
|
|
@ -469,6 +469,10 @@
|
|||
|
||||
/** radio **/
|
||||
.arco-radio-group-button {
|
||||
.arco-radio-button-content {
|
||||
@apply break-keep;
|
||||
}
|
||||
|
||||
background-color: var(--color-text-n8);
|
||||
.arco-radio-button {
|
||||
@apply bg-transparent;
|
||||
|
|
|
@ -3,13 +3,20 @@
|
|||
v-model:activeExtraKey="activeExtraKey"
|
||||
v-model:extra-visible="extraVisible"
|
||||
v-model:loading="loading"
|
||||
v-model:import-json="importJson"
|
||||
:tags="[]"
|
||||
:import-json="importJson"
|
||||
:replaceable-tags="replaceableTags"
|
||||
:insert-node="insertNode"
|
||||
:priority-disable-check="priorityDisableCheck"
|
||||
:after-tag-edit="afterTagEdit"
|
||||
:extract-content-tab-list="extractContentTabList"
|
||||
:can-show-enter-node="canShowEnterNode"
|
||||
:insert-sibling-menus="insertSiblingMenus"
|
||||
:insert-son-menus="insertSonMenus"
|
||||
:can-show-paste-menu="!stopPaste()"
|
||||
:can-show-more-menu="canShowMoreMenu()"
|
||||
:can-show-priority-menu="canShowPriorityMenu()"
|
||||
:priority-tooltip="t('caseManagement.caseReview.caseLevel')"
|
||||
single-tag
|
||||
tag-enable
|
||||
sequence-enable
|
||||
|
@ -19,6 +26,18 @@
|
|||
@before-exec-command="handleBeforeExecCommand"
|
||||
@save="handleMinderSave"
|
||||
>
|
||||
<template #extractMenu>
|
||||
<a-tooltip v-if="showDetailMenu" :content="t('common.detail')">
|
||||
<MsButton
|
||||
type="icon"
|
||||
class="ms-minder-node-float-menu-icon-button"
|
||||
: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>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #extractTabContent>
|
||||
<baseInfo
|
||||
v-if="activeExtraKey === 'baseInfo'"
|
||||
|
@ -43,9 +62,11 @@
|
|||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import { FormItem } from '@/components/pure/ms-form-create/types';
|
||||
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
|
||||
import type {
|
||||
InsertMenuItem,
|
||||
MinderEvent,
|
||||
MinderJson,
|
||||
MinderJsonNode,
|
||||
|
@ -61,11 +82,12 @@
|
|||
checkFileIsUpdateRequest,
|
||||
getCaseDetail,
|
||||
getCaseMinder,
|
||||
getCaseModuleTree,
|
||||
getCaseMinderTree,
|
||||
saveCaseMinder,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
|
||||
import { filterTree, getGenerateId, mapTree } from '@/utils';
|
||||
|
||||
|
@ -87,18 +109,19 @@
|
|||
|
||||
const appStore = useAppStore();
|
||||
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 textTag = t('ms.minders.textDesc');
|
||||
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, textTag];
|
||||
const caseChildTags = [prerequisiteTag, stepTag, textTag, remarkTag];
|
||||
const caseOffspringTags = [...caseChildTags, stepTag, stepExpectTag, textTag, remarkTag];
|
||||
const descTags = [stepTag, textDescTag];
|
||||
const caseChildTags = [prerequisiteTag, stepTag, textDescTag, remarkTag];
|
||||
const caseOffspringTags = [...caseChildTags, stepTag, stepExpectTag, textDescTag, remarkTag];
|
||||
const importJson = ref<MinderJson>({
|
||||
root: {} as MinderJsonNode,
|
||||
template: 'default',
|
||||
|
@ -122,16 +145,16 @@
|
|||
async function initCaseTree() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getCaseModuleTree({
|
||||
const res = await getCaseMinderTree({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: props.moduleId === 'NONE' ? '' : props.moduleId,
|
||||
moduleId: props.moduleId === 'all' ? '' : props.moduleId,
|
||||
});
|
||||
caseTree.value = mapTree<MinderJsonNode>(res, (e) => ({
|
||||
...e,
|
||||
data: {
|
||||
id: e.id,
|
||||
text: e.name,
|
||||
resource: e.data?.id === 'fakeNode' ? [] : [moduleTag],
|
||||
resource: props.modulesCount[e.id] !== undefined ? [moduleTag] : e.data?.resource,
|
||||
expandState: e.level === 1 ? 'expand' : 'collapse',
|
||||
count: props.modulesCount[e.id],
|
||||
isNew: false,
|
||||
|
@ -160,39 +183,13 @@
|
|||
},
|
||||
};
|
||||
window.minder.importJson(importJson.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模块下脑图数据
|
||||
*/
|
||||
async function initMinder() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getCaseMinder({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: props.moduleId === 'all' ? '' : props.moduleId,
|
||||
});
|
||||
importJson.value.root.children = mapTree(res, (node) => {
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isNew: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
importJson.value.root.data = {
|
||||
id: props.moduleId === 'all' ? '' : props.moduleId,
|
||||
text: props.moduleName,
|
||||
resource: [moduleTag],
|
||||
};
|
||||
window.minder.importJson(importJson.value);
|
||||
if (props.moduleId !== 'all') {
|
||||
nextTick(() => {
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
|
||||
window.minder.getNodeById(props.moduleId),
|
||||
]);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -202,11 +199,7 @@
|
|||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.moduleId === 'all') {
|
||||
initCaseTree();
|
||||
} else {
|
||||
initMinder();
|
||||
}
|
||||
initCaseTree();
|
||||
});
|
||||
|
||||
const baseInfoRef = ref<InstanceType<typeof baseInfo>>();
|
||||
|
@ -221,7 +214,7 @@
|
|||
let remarkNode: MinderJsonNode | undefined; // 备注
|
||||
const stepNodes: MinderJsonNode[] = []; // 步骤描述
|
||||
node.children?.forEach((item) => {
|
||||
if (item.data?.resource?.includes(textTag)) {
|
||||
if (item.data?.resource?.includes(textDescTag)) {
|
||||
textStep = item;
|
||||
} else if (item.data?.resource?.includes(stepTag)) {
|
||||
stepNodes.push(item);
|
||||
|
@ -268,8 +261,7 @@
|
|||
/**
|
||||
* 生成脑图保存的入参
|
||||
*/
|
||||
function makeMinderParams(): FeatureCaseMinderUpdateParams {
|
||||
const fullJson: MinderJson = window.minder.exportJson();
|
||||
function makeMinderParams(fullJson: MinderJson): FeatureCaseMinderUpdateParams {
|
||||
filterTree(fullJson.root.children, (node, nodeIndex, parent) => {
|
||||
if (node.data.isNew !== false || node.data.changed === true) {
|
||||
if (node.data.resource?.includes(moduleTag)) {
|
||||
|
@ -297,7 +289,7 @@
|
|||
...caseNodeInfo,
|
||||
});
|
||||
return false; // 用例的子孙节点已经处理过,跳过
|
||||
} else if (!node.data.resource) {
|
||||
} else if (!node.data.resource || node.data.resource.length === 0) {
|
||||
// 处理文本节点
|
||||
tempMinderParams.value.additionalNodeList.push({
|
||||
id: node.data.id,
|
||||
|
@ -308,16 +300,19 @@
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return tempMinderParams.value;
|
||||
}
|
||||
|
||||
async function handleMinderSave() {
|
||||
async function handleMinderSave(fullJson: MinderJson, callback: () => void) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await saveCaseMinder(makeMinderParams());
|
||||
await saveCaseMinder(makeMinderParams(fullJson));
|
||||
Message.success(t('common.saveSuccess'));
|
||||
initCaseTree();
|
||||
callback();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -510,14 +505,34 @@
|
|||
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) {
|
||||
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)) {
|
||||
|
@ -534,7 +549,7 @@
|
|||
const child = node.children[i];
|
||||
if (child.data?.resource?.includes(prerequisiteTag)) {
|
||||
hasPreCondition = true;
|
||||
} else if (child.data?.resource?.includes(textTag)) {
|
||||
} else if (child.data?.resource?.includes(textDescTag)) {
|
||||
hasTextDesc = true;
|
||||
} else if (child.data?.resource?.includes(remarkTag)) {
|
||||
hasRemark = true;
|
||||
|
@ -552,7 +567,7 @@
|
|||
}
|
||||
}
|
||||
} else if (
|
||||
(node.data?.resource?.includes(stepTag) || node.data?.resource?.includes(textTag)) &&
|
||||
(node.data?.resource?.includes(stepTag) || node.data?.resource?.includes(textDescTag)) &&
|
||||
(!node.children || node.children.length === 0)
|
||||
) {
|
||||
// 当前节点是步骤描述或文本描述,且没有子节点,则默认添加一个预期结果
|
||||
|
@ -566,6 +581,10 @@
|
|||
}
|
||||
break;
|
||||
case 'AppendSiblingNode':
|
||||
if (value) {
|
||||
insertSpecifyNode('AppendSiblingNode', value);
|
||||
break;
|
||||
}
|
||||
if (node.parent?.data?.resource?.includes(caseTag) && node.parent?.children) {
|
||||
// 当前节点的父节点是用例
|
||||
let hasPreCondition = false;
|
||||
|
@ -577,7 +596,7 @@
|
|||
hasPreCondition = true;
|
||||
} else if (sibling.data?.resource?.includes(remarkTag)) {
|
||||
hasRemark = true;
|
||||
} else if (sibling.data?.resource?.includes(textTag)) {
|
||||
} else if (sibling.data?.resource?.includes(textDescTag)) {
|
||||
hasTextDesc = true;
|
||||
}
|
||||
}
|
||||
|
@ -611,33 +630,6 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签编辑后,如果将标签修改为模块,则删除已添加的优先级
|
||||
* @param node 选中节点
|
||||
* @param tag 更改后的标签
|
||||
*/
|
||||
function afterTagEdit(node: MinderJsonNode, tag: string) {
|
||||
if (tag === moduleTag && node.data) {
|
||||
// 排除是从用例节点切换到模块节点的数据
|
||||
tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter(
|
||||
(e) => e.id !== node.data?.id
|
||||
);
|
||||
window.minder.execCommand('priority');
|
||||
} else if (node.data?.resource?.includes(caseTag)) {
|
||||
// 排除是从模块节点切换到用例节点的数据
|
||||
tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter(
|
||||
(e) => e.id !== node.data?.id
|
||||
);
|
||||
} else if (node.data?.resource?.some((e) => caseOffspringTags.includes(e))) {
|
||||
// 用例子孙节点更新,标记用例节点变化
|
||||
if (node.parent?.data?.resource?.includes(caseTag)) {
|
||||
node.parent.data.changed = true;
|
||||
} else if (node.parent?.parent?.data?.resource?.includes(caseTag)) {
|
||||
// 期望结果是第三层节点
|
||||
node.parent.parent.data.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const baseInfoLoading = ref(false);
|
||||
|
||||
const formRules = ref<FormItem[]>([]);
|
||||
|
@ -743,7 +735,7 @@
|
|||
if (
|
||||
resource?.includes(prerequisiteTag) ||
|
||||
resource?.includes(stepTag) ||
|
||||
resource?.includes(textTag) ||
|
||||
resource?.includes(textDescTag) ||
|
||||
resource?.includes(remarkTag)
|
||||
) {
|
||||
if (node.parent?.data) {
|
||||
|
@ -756,29 +748,193 @@
|
|||
}
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
const showDetailMenu = ref(false);
|
||||
const canShowEnterNode = ref(false);
|
||||
/**
|
||||
* 处理脑图节点激活/点击
|
||||
* @param node 被激活/点击的节点
|
||||
*/
|
||||
async function handleNodeSelect(node: MinderJsonNode) {
|
||||
checkNodeCanShowMenu(node);
|
||||
const { data } = node;
|
||||
if (
|
||||
data?.resource?.includes(moduleTag) &&
|
||||
(node.children || []).length > 0 &&
|
||||
node.type !== 'root' &&
|
||||
!data.isNew
|
||||
) {
|
||||
// 模块节点且有子节点且非根节点且非新建节点,可展示进入节点菜单
|
||||
canShowEnterNode.value = true;
|
||||
} else {
|
||||
canShowEnterNode.value = false;
|
||||
}
|
||||
if (data?.resource && data.resource.includes(caseTag)) {
|
||||
extraVisible.value = true;
|
||||
activeExtraKey.value = 'baseInfo';
|
||||
resetExtractInfo();
|
||||
if (data.isNew === false) {
|
||||
// 非新用例节点才能加载详情
|
||||
initCaseDetail(data);
|
||||
} else {
|
||||
activeCase.value = {
|
||||
id: data.id,
|
||||
name: data.text,
|
||||
isNew: true,
|
||||
};
|
||||
}
|
||||
// 用例节点才展示详情按钮
|
||||
showDetailMenu.value = true;
|
||||
} else if (data?.resource?.includes(moduleTag) && data.count > 0 && data.isLoaded !== true) {
|
||||
// 模块节点且有用例且未加载过用例数据
|
||||
try {
|
||||
loading.value = true;
|
||||
showDetailMenu.value = false;
|
||||
extraVisible.value = false;
|
||||
const res = await getCaseMinder({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: data.id,
|
||||
|
@ -846,11 +1002,85 @@
|
|||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
// 文本节点或已加载过用例数据的模块节点
|
||||
extraVisible.value = false;
|
||||
showDetailMenu.value = false;
|
||||
resetExtractInfo();
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.expand();
|
||||
node.renderTree();
|
||||
window.minder.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换用例详情显示
|
||||
*/
|
||||
async function toggleDetail() {
|
||||
extraVisible.value = !extraVisible.value;
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
const { data } = node;
|
||||
if (extraVisible.value) {
|
||||
if (data?.resource && data.resource.includes(caseTag)) {
|
||||
activeExtraKey.value = 'baseInfo';
|
||||
resetExtractInfo();
|
||||
if (data.isNew === false) {
|
||||
// 非新用例节点才能加载详情
|
||||
initCaseDetail(data);
|
||||
} else {
|
||||
activeCase.value = {
|
||||
id: data.id,
|
||||
name: data.text,
|
||||
isNew: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签编辑后,如果将标签修改为模块,则删除已添加的优先级
|
||||
* @param node 选中节点
|
||||
* @param tag 更改后的标签
|
||||
*/
|
||||
function afterTagEdit(nodes: MinderJsonNode[], tag: string) {
|
||||
nodes.forEach((node, index) => {
|
||||
if (tag === moduleTag && node.data) {
|
||||
// 排除是从用例节点切换到模块节点的数据
|
||||
tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter(
|
||||
(e) => e.id !== node.data?.id
|
||||
);
|
||||
node.data.isNew = true;
|
||||
window.minder.execCommand('priority');
|
||||
if (index === nodes.length - 1) {
|
||||
nextTick(() => {
|
||||
handleNodeSelect(node);
|
||||
});
|
||||
}
|
||||
} else if (node.data?.resource?.includes(caseTag)) {
|
||||
// 排除是从模块节点切换到用例节点的数据
|
||||
tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter(
|
||||
(e) => e.id !== node.data?.id
|
||||
);
|
||||
node.data.isNew = true;
|
||||
if (index === nodes.length - 1) {
|
||||
nextTick(() => {
|
||||
handleNodeSelect(node);
|
||||
});
|
||||
}
|
||||
} else if (node.data?.resource?.some((e) => caseOffspringTags.includes(e))) {
|
||||
// 用例子孙节点更新,标记用例节点变化
|
||||
if (node.parent?.data?.resource?.includes(caseTag)) {
|
||||
node.parent.data.changed = true;
|
||||
} else if (node.parent?.parent?.data?.resource?.includes(caseTag)) {
|
||||
// 期望结果是第三层节点
|
||||
node.parent.parent.data.changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理脑图节点操作
|
||||
* @param event 脑图事件对象
|
||||
|
@ -860,12 +1090,16 @@
|
|||
if (nodes && nodes.length > 0) {
|
||||
switch (name) {
|
||||
case MinderEventName.DELETE_NODE:
|
||||
case MinderEventName.CUT_NODE:
|
||||
// TODO:循环优化
|
||||
nodes.forEach((node) => {
|
||||
tempMinderParams.value.deleteResourceList.push({
|
||||
id: node.data?.id || getGenerateId(),
|
||||
type: node.data?.resource?.[0] || moduleTag,
|
||||
});
|
||||
if (!caseOffspringTags.some((e) => node.data?.resource?.includes(e))) {
|
||||
// 非用例下的子孙节点的移除,才加入删除资源队列
|
||||
tempMinderParams.value.deleteResourceList.push({
|
||||
id: node.data?.id || getGenerateId(),
|
||||
type: node.data?.resource?.[0] || moduleTag,
|
||||
});
|
||||
}
|
||||
if (node.data?.resource?.includes(caseTag)) {
|
||||
// 删除用例节点
|
||||
tempMinderParams.value.updateCaseList = tempMinderParams.value.updateCaseList.filter(
|
||||
|
@ -890,6 +1124,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
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 拖动节点
|
||||
|
@ -968,6 +1218,79 @@
|
|||
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 命令执行事件
|
||||
|
@ -995,6 +1318,20 @@
|
|||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -8,4 +8,5 @@ export default {
|
|||
'ms.minders.caseName': 'Test Case Name',
|
||||
'ms.minders.caseNameNotNull': 'Test Case Name cannot be empty',
|
||||
'ms.minders.commentTotal': '{num} Comments in Total',
|
||||
'ms.minders.text': 'Text',
|
||||
};
|
||||
|
|
|
@ -8,4 +8,5 @@ export default {
|
|||
'ms.minders.caseName': '用例名称',
|
||||
'ms.minders.caseNameNotNull': '用例名称不能为空',
|
||||
'ms.minders.commentTotal': '共 {num} 评论',
|
||||
'ms.minders.text': '文本',
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface UseEventListenerProps {
|
|||
handleSelectionChange?: (node: MinderJsonNode) => void;
|
||||
handleMinderEvent?: (event: MinderCustomEvent) => void;
|
||||
handleBeforeExecCommand?: (event: MinderEvent) => void;
|
||||
handleViewChange?: (event: MinderEvent) => void;
|
||||
}
|
||||
|
||||
export default function useEventListener(listener: UseEventListenerProps) {
|
||||
|
@ -35,15 +36,6 @@ export default function useEventListener(listener: UseEventListenerProps) {
|
|||
}, 300)
|
||||
);
|
||||
|
||||
// minder.on('dragStart', () => {
|
||||
// const node: MinderJsonNode = minder.getSelectedNode();
|
||||
// console.log('dragStart', node);
|
||||
// });
|
||||
|
||||
// minder.on('dragFinish', () => {
|
||||
// console.log('dragFinish', minder.history);
|
||||
// });
|
||||
|
||||
// 监听脑图执行命令前(可通过e.stopPropagation拦截命令执行)
|
||||
minder.on('beforeExecCommand', (e: MinderEvent) => {
|
||||
if (listener.handleBeforeExecCommand) {
|
||||
|
@ -51,6 +43,15 @@ export default function useEventListener(listener: UseEventListenerProps) {
|
|||
}
|
||||
});
|
||||
|
||||
minder.on(
|
||||
'viewchange',
|
||||
debounce((e: MinderEvent) => {
|
||||
if (listener.handleViewChange) {
|
||||
listener.handleViewChange(e);
|
||||
}
|
||||
}, 300)
|
||||
);
|
||||
|
||||
// 监听脑图自定义事件
|
||||
watch(
|
||||
() => minderStore.event.timestamp,
|
||||
|
|
|
@ -101,8 +101,8 @@ export default {
|
|||
hotboxMenu: {
|
||||
expand: 'Expand/Collapse',
|
||||
insetParent: 'Insert one level up',
|
||||
insetSon: 'Insert next level',
|
||||
insetBrother: 'Insert sibling',
|
||||
insetSon: 'Add next level',
|
||||
insetBrother: 'Add sibling',
|
||||
copy: 'Copy',
|
||||
cut: 'Cut',
|
||||
paste: 'Paste',
|
||||
|
@ -110,5 +110,6 @@ export default {
|
|||
enterNode: 'Enter the current node',
|
||||
},
|
||||
loading: 'Mind map loading...',
|
||||
unSavedEnterNodeTip: 'There are currently unsaved changes, please save before entering the node',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -95,8 +95,8 @@ export default {
|
|||
hotboxMenu: {
|
||||
expand: '展开/收起',
|
||||
insetParent: '插入上一级',
|
||||
insetSon: '插入下一级',
|
||||
insetBrother: '插入同级',
|
||||
insetSon: '添加子级',
|
||||
insetBrother: '添加同级',
|
||||
copy: '复制',
|
||||
cut: '剪切',
|
||||
paste: '粘贴',
|
||||
|
@ -104,5 +104,6 @@ export default {
|
|||
enterNode: '进入当前节点',
|
||||
},
|
||||
loading: '脑图加载中...',
|
||||
unSavedEnterNodeTip: '当前有未保存的改动,请先保存后再进入节点',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<!-- <div class="ms-minder-editor-header">
|
||||
<div class="ms-minder-editor-header">
|
||||
<a-tooltip v-for="item of props.iconButtons" :key="item.eventTag" :content="t(item.tooltip)">
|
||||
<MsButton type="icon" class="ms-minder-editor-header-icon-button" @click="emit('click', item.eventTag)">
|
||||
<MsIcon :type="item.icon" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-divider v-if="props.iconButtons?.length" direction="vertical" :margin="8"></a-divider>
|
||||
<a-divider v-if="props.iconButtons?.length" direction="vertical" :margin="0"></a-divider>
|
||||
<a-tooltip :content="isFullScreen ? t('common.offFullScreen') : t('common.fullScreen')">
|
||||
<MsButton v-if="isFullScreen" type="icon" class="ms-minder-editor-header-icon-button" @click="toggleFullScreen">
|
||||
<MsIcon type="icon-icon_off_screen" class="text-[var(--color-text-4)]" />
|
||||
|
@ -14,89 +14,68 @@
|
|||
<MsIcon type="icon-icon_full_screen_one" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
</div> -->
|
||||
<div class="mind-tab-panel">
|
||||
<editMenu
|
||||
:minder="minder"
|
||||
:move-enable="props.moveEnable"
|
||||
:move-confirm="props.moveConfirm"
|
||||
:sequence-enable="props.sequenceEnable"
|
||||
:tag-enable="props.tagEnable"
|
||||
:progress-enable="props.progressEnable"
|
||||
:priority-count="props.priorityCount"
|
||||
:priority-prefix="props.priorityPrefix"
|
||||
:tag-edit-check="props.tagEditCheck"
|
||||
:tag-disable-check="props.tagDisableCheck"
|
||||
:priority-disable-check="props.priorityDisableCheck"
|
||||
:priority-start-with-zero="props.priorityStartWithZero"
|
||||
:tags="props.tags"
|
||||
:distinct-tags="props.distinctTags"
|
||||
:del-confirm="props.delConfirm"
|
||||
:replaceable-tags="props.replaceableTags"
|
||||
:single-tag="props.singleTag"
|
||||
:insert-node="props.insertNode"
|
||||
:after-tag-edit="props.afterTagEdit"
|
||||
/>
|
||||
<a-button
|
||||
type="outline"
|
||||
:disabled="props.disabled"
|
||||
class="px-[8px] py-[2px] text-[12px]"
|
||||
size="small"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('minder.main.main.save') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
// import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import editMenu from '../menu/edit/editMenu.vue';
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import { delProps, editMenuProps, insertProps, moleProps, priorityProps, tagProps, viewMenuProps } from '../props';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps({
|
||||
...editMenuProps,
|
||||
...insertProps,
|
||||
...moleProps,
|
||||
...priorityProps,
|
||||
...tagProps,
|
||||
...delProps,
|
||||
...viewMenuProps,
|
||||
minder: null,
|
||||
import { MinderIconButtonItem } from '../props';
|
||||
|
||||
const props = defineProps<{
|
||||
iconButtons?: MinderIconButtonItem[];
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', eventTag: string): void;
|
||||
(e: 'save'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const containerRef = ref<Element | null>(null);
|
||||
const { toggleFullScreen, isFullScreen } = useFullScreen(containerRef);
|
||||
|
||||
onMounted(() => {
|
||||
containerRef.value = document.querySelector('.ms-minder-editor-container');
|
||||
});
|
||||
// import useFullScreen from '@/hooks/useFullScreen';
|
||||
// import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
// import { headerProps } from '../props';
|
||||
|
||||
// const props = defineProps({
|
||||
// ...headerProps,
|
||||
// });
|
||||
// const emit = defineEmits<{
|
||||
// (e: 'click', eventTag: string): void;
|
||||
// }>();
|
||||
|
||||
// const { t } = useI18n();
|
||||
|
||||
// const { toggleFullScreen, isFullScreen } = useFullScreen(document.querySelector('.ms-minder-editor-container'));
|
||||
function save() {
|
||||
emit('save');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../style/header.less';
|
||||
.mind_tab-content {
|
||||
.tab-icons {
|
||||
background-image: url('@/assets/images/minder/icons.png');
|
||||
background-repeat: no-repeat;
|
||||
.ms-minder-editor-header {
|
||||
@apply absolute z-10 flex items-center bg-white;
|
||||
|
||||
top: 16px;
|
||||
right: 4px;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--border-radius-small);
|
||||
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
|
||||
.ms-minder-editor-header-icon-button {
|
||||
@apply !mr-0;
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1)) !important;
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-4)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .ms-minder-editor-header {
|
||||
// @apply absolute z-10 flex items-center bg-white;
|
||||
|
||||
// top: 24px;
|
||||
// right: 0;
|
||||
// padding: 4px 8px;
|
||||
// border-radius: var(--border-radius-small);
|
||||
// box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
|
||||
// .ms-minder-editor-header-icon-button {
|
||||
// &:hover {
|
||||
// background-color: rgb(var(--primary-1)) !important;
|
||||
// .arco-icon {
|
||||
// color: rgb(var(--primary-4)) !important;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
|
|
@ -1,68 +1,7 @@
|
|||
<template>
|
||||
<div ref="mec" class="minder-container">
|
||||
<a-button type="primary" :disabled="props.disabled" class="save-btn bottom-[30px] right-[30px]" @click="save">
|
||||
{{ t('minder.main.main.save') }}
|
||||
</a-button>
|
||||
<div ref="mec" class="ms-minder-container">
|
||||
<minderHeader :icon-buttons="props.iconButtons" @save="save" />
|
||||
<Navigator />
|
||||
<a-dropdown
|
||||
v-model:popup-visible="menuVisible"
|
||||
class="minder-dropdown"
|
||||
position="bl"
|
||||
:popup-translate="menuPopupOffset"
|
||||
@select="handleMinderMenuSelect"
|
||||
>
|
||||
<span></span>
|
||||
<template #content>
|
||||
<a-doption value="expand">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.expand') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">( / )</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="insetSon">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.insetSon') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Tab)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="insetBrother">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.insetBrother') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Enter)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="copy">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.copy') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl + C)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="cut">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.cut') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl + X)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="paste">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.paste') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl + V)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="delete">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.delete') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Backspace)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="enterNode">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.enterNode') }}</div>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl+ Enter)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<div
|
||||
v-if="innerImportJson.treePath?.length > 1"
|
||||
class="absolute left-[50%] top-[24px] z-50 translate-x-[-50%] bg-white p-[8px]"
|
||||
|
@ -73,23 +12,30 @@
|
|||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
<nodeFloatMenu v-bind="props">
|
||||
<template #extractMenu>
|
||||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
</nodeFloatMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="minderContainer" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import nodeFloatMenu from '../menu/nodeFloatMenu.vue';
|
||||
import minderHeader from './header.vue';
|
||||
import Navigator from './navigator.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
import { findNodePathByKey, getGenerateId } from '@/utils';
|
||||
import { findNodePathByKey, replaceNodeInTree } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import {
|
||||
editMenuProps,
|
||||
floatMenuProps,
|
||||
headerProps,
|
||||
insertProps,
|
||||
mainEditorProps,
|
||||
MinderJson,
|
||||
|
@ -102,21 +48,32 @@
|
|||
import { markChangeNode, markDeleteNode } from '../script/tool/utils';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({ ...editMenuProps, ...insertProps, ...mainEditorProps, ...tagProps, ...priorityProps });
|
||||
|
||||
const props = defineProps({
|
||||
...headerProps,
|
||||
...floatMenuProps,
|
||||
...editMenuProps,
|
||||
...insertProps,
|
||||
...mainEditorProps,
|
||||
...tagProps,
|
||||
...priorityProps,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
(e: 'afterMount'): void;
|
||||
(e: 'save', json: MinderJson): void;
|
||||
}>();
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
const mec: Ref<HTMLDivElement | null> = ref(null);
|
||||
const innerImportJson = ref<any>({});
|
||||
const importJson = defineModel<MinderJson>('importJson', {
|
||||
required: true,
|
||||
});
|
||||
const innerImportJson = ref<MinderJson>({
|
||||
root: {},
|
||||
template: 'default',
|
||||
treePath: [],
|
||||
});
|
||||
const minderUnsaved = ref(false);
|
||||
|
||||
function save() {
|
||||
emit('save', window.minder.exportJson());
|
||||
}
|
||||
function handlePriorityButton() {
|
||||
const { priorityPrefix } = props;
|
||||
const { priorityStartWithZero } = props;
|
||||
|
@ -162,8 +119,8 @@
|
|||
moveEnable: props.moveEnable,
|
||||
});
|
||||
const { editor } = window;
|
||||
if (Object.keys(props.importJson || {}).length > 0) {
|
||||
editor.minder.importJson(props.importJson);
|
||||
if (Object.keys(importJson.value || {}).length > 0) {
|
||||
editor.minder.importJson(importJson.value);
|
||||
}
|
||||
window.km = editor.minder;
|
||||
window.minder = window.km;
|
||||
|
@ -189,11 +146,10 @@
|
|||
'zoom',
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
'append',
|
||||
'appendchildnode',
|
||||
'appendsiblingnode',
|
||||
]);
|
||||
if (selectNodes && !notChangeCommands.has(env.commandName.toLocaleLowerCase())) {
|
||||
minderUnsaved.value = true;
|
||||
minderStore.dispatchEvent(MinderEventName.MINDER_CHANGED);
|
||||
selectNodes.forEach((node: MinderJsonNode) => {
|
||||
markChangeNode(node);
|
||||
});
|
||||
|
@ -223,15 +179,25 @@
|
|||
* @param node 切换的节点
|
||||
*/
|
||||
function switchNode(node: MinderJsonNode | MinderJsonNodeData) {
|
||||
if (node.data) {
|
||||
innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.data.id, 'data', 'id'));
|
||||
} else {
|
||||
innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.id, 'data', 'id'));
|
||||
if (minderUnsaved.value) {
|
||||
// 切换前,如果脑图未保存,先把更改的节点信息同步一次
|
||||
replaceNodeInTree(
|
||||
[importJson.value.root],
|
||||
innerImportJson.value.root.data?.id || '',
|
||||
window.minder.exportJson()?.root,
|
||||
'data',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
if (node.data) {
|
||||
innerImportJson.value = findNodePathByKey([importJson.value.root], node.data.id, 'data', 'id') as MinderJson;
|
||||
} else {
|
||||
innerImportJson.value = findNodePathByKey([importJson.value.root], node.id, 'data', 'id') as MinderJson;
|
||||
}
|
||||
innerImportJson.value.data.expandState = 'expand';
|
||||
window.minder.importJson(innerImportJson.value);
|
||||
setTimeout(() => {
|
||||
window.minder.execCommand('camera', window.minder.getRoot(), 600);
|
||||
window.minder.select(window.minder.getRoot());
|
||||
window.minder.execCommand('camera', window.minder.getRoot());
|
||||
}, 100); // TODO:暂未知渲染时机,临时延迟解决
|
||||
}
|
||||
|
||||
|
@ -252,83 +218,23 @@
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 执行插入
|
||||
* @param command 插入命令
|
||||
*/
|
||||
function execInsertCommand(command: string) {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (props.insertNode) {
|
||||
props.insertNode(node, command);
|
||||
return;
|
||||
}
|
||||
if (window.minder.queryCommandState(command) !== -1) {
|
||||
window.minder.execCommand(command);
|
||||
nextTick(() => {
|
||||
const newNode: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (!newNode.data) {
|
||||
newNode.data = {
|
||||
id: getGenerateId(),
|
||||
text: '',
|
||||
};
|
||||
}
|
||||
newNode.data.isNew = true; // 新建的节点标记为新建
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理快捷菜单选择
|
||||
* @param val 选择的菜单项
|
||||
*/
|
||||
function handleMinderMenuSelect(val: string | number | Record<string, any> | undefined) {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (selectedNodes.length > 0) {
|
||||
switch (val) {
|
||||
case 'expand':
|
||||
if (selectedNodes.some((node) => node.data?.expandState === 'collapse')) {
|
||||
window.minder.execCommand('Expand');
|
||||
} else {
|
||||
window.minder.execCommand('Collapse');
|
||||
}
|
||||
minderStore.dispatchEvent(MinderEventName.EXPAND, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'insetParent':
|
||||
execInsertCommand('AppendParentNode');
|
||||
minderStore.dispatchEvent(MinderEventName.INSERT_PARENT, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'insetSon':
|
||||
execInsertCommand('AppendChildNode');
|
||||
minderStore.dispatchEvent(MinderEventName.INSERT_CHILD, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'insetBrother':
|
||||
execInsertCommand('AppendSiblingNode');
|
||||
minderStore.dispatchEvent(MinderEventName.INSERT_SIBLING, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'copy':
|
||||
window.minder.execCommand('Copy');
|
||||
minderStore.dispatchEvent(MinderEventName.COPY_NODE, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'cut':
|
||||
window.minder.execCommand('Cut');
|
||||
minderStore.dispatchEvent(MinderEventName.CUT_NODE, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'paste':
|
||||
window.minder.execCommand('Paste');
|
||||
minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'delete':
|
||||
window.minder.execCommand('RemoveNode');
|
||||
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'enterNode':
|
||||
switchNode(selectedNodes[0]);
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, [selectedNodes[0]]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
function save() {
|
||||
let data = importJson.value;
|
||||
if (innerImportJson.value.treePath?.length > 1) {
|
||||
replaceNodeInTree(
|
||||
[importJson.value.root],
|
||||
innerImportJson.value.root.data?.id || '',
|
||||
window.minder.exportJson()?.root,
|
||||
'data',
|
||||
'id'
|
||||
);
|
||||
} else {
|
||||
data = window.minder.exportJson();
|
||||
}
|
||||
emit('save', data, () => {
|
||||
minderUnsaved.value = false;
|
||||
menuVisible.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -337,12 +243,13 @@
|
|||
.save-btn {
|
||||
@apply !absolute;
|
||||
}
|
||||
.minder-container {
|
||||
@apply relative !bg-white;
|
||||
.ms-minder-container {
|
||||
@apply relative overflow-hidden !bg-white;
|
||||
|
||||
padding: 16px 0;
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
.minder-dropdown {
|
||||
.ms-minder-dropdown {
|
||||
.arco-dropdown-list-wrapper {
|
||||
max-height: none;
|
||||
}
|
||||
|
|
|
@ -60,18 +60,13 @@
|
|||
return;
|
||||
}
|
||||
const nodes: MinderJsonNode[] = minder.getSelectedNodes();
|
||||
let position: MinderNodePosition | undefined;
|
||||
if (nodes.length > 0) {
|
||||
if (props.delConfirm) {
|
||||
props.delConfirm(nodes);
|
||||
return;
|
||||
}
|
||||
const box = nodes[0].getRenderBox();
|
||||
position = {
|
||||
x: box.cx,
|
||||
y: box.cy,
|
||||
};
|
||||
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, position, nodes[0].rc.node, nodes);
|
||||
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, box, nodes[0].rc.node, nodes);
|
||||
}
|
||||
minder.forceRemoveNode();
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
}
|
||||
window.minder.execCommand('resource', origin);
|
||||
const nodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, nodes);
|
||||
minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, undefined, nodes);
|
||||
if (props.replaceableTags) {
|
||||
tagList.value = props.replaceableTags(nodes);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,420 @@
|
|||
<template>
|
||||
<a-trigger
|
||||
v-model:popup-visible="menuVisible"
|
||||
class="ms-minder-node-float-menu"
|
||||
position="bl"
|
||||
:popup-translate="menuPopupOffset"
|
||||
trigger="click"
|
||||
:click-outside-to-close="false"
|
||||
popup-container=".ms-minder-container"
|
||||
>
|
||||
<span></span>
|
||||
<template #content>
|
||||
<a-radio-group
|
||||
v-if="currentNodeTags.length > 0 && tags.length > 0"
|
||||
v-model:model-value="currentNodeTags[0]"
|
||||
type="button"
|
||||
size="mini"
|
||||
@change="(val) => handleTagChange(val as string)"
|
||||
>
|
||||
<a-radio v-for="tag of currentNodeTags" :key="tag" :value="tag">{{ tag }}</a-radio>
|
||||
<a-radio v-for="tag of tags" :key="tag" :value="tag">{{ tag }}</a-radio>
|
||||
</a-radio-group>
|
||||
<a-dropdown
|
||||
v-if="props.insertSiblingMenus.length > 0"
|
||||
v-model:popup-visible="insertSiblingMenuVisible"
|
||||
class="ms-minder-dropdown"
|
||||
:popup-translate="[0, 4]"
|
||||
position="bl"
|
||||
trigger="click"
|
||||
@select="(val) => handleMinderMenuSelect('AppendSiblingNode',val as string)"
|
||||
>
|
||||
<a-tooltip :content="t('minder.hotboxMenu.insetBrother')">
|
||||
<MsButton
|
||||
type="icon"
|
||||
class="ms-minder-node-float-menu-icon-button"
|
||||
:class="[insertSiblingMenuVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
||||
>
|
||||
<MsIcon type="icon-icon_title-top-align_outlined1" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<template #content>
|
||||
<div class="mx-[6px] px-[8px] py-[3px] text-[var(--color-text-4)]">
|
||||
{{ t('minder.hotboxMenu.insetBrother') }}
|
||||
</div>
|
||||
<a-doption v-for="menu of props.insertSiblingMenus" :key="menu.value" :value="menu.value">
|
||||
{{ t(menu.label) }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-dropdown
|
||||
v-if="props.insertSonMenus.length > 0"
|
||||
v-model:popup-visible="insertSonMenuVisible"
|
||||
class="ms-minder-dropdown"
|
||||
:popup-translate="[0, 4]"
|
||||
position="bl"
|
||||
trigger="click"
|
||||
@select="(val) => handleMinderMenuSelect('AppendChildNode',val as string)"
|
||||
>
|
||||
<a-tooltip :content="t('minder.hotboxMenu.insetSon')">
|
||||
<MsButton
|
||||
type="icon"
|
||||
class="ms-minder-node-float-menu-icon-button"
|
||||
:class="[insertSonMenuVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
||||
>
|
||||
<MsIcon type="icon-icon_title-left-align_outlined1" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<template #content>
|
||||
<div class="mx-[6px] px-[8px] py-[3px] text-[var(--color-text-4)]">
|
||||
{{ t('minder.hotboxMenu.insetSon') }}
|
||||
</div>
|
||||
<a-doption v-for="menu of props.insertSonMenus" :key="menu.value" :value="menu.value">
|
||||
{{ t(menu.label) }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-dropdown
|
||||
v-if="props.priorityCount && props.canShowPriorityMenu"
|
||||
v-model:popup-visible="priorityMenuVisible"
|
||||
class="ms-minder-dropdown"
|
||||
:popup-translate="[0, 4]"
|
||||
position="bl"
|
||||
trigger="click"
|
||||
@select="(val) => handleMinderMenuSelect('priority',val as string)"
|
||||
>
|
||||
<a-tooltip :content="props.priorityTooltip" :disabled="!props.priorityTooltip">
|
||||
<MsButton
|
||||
type="icon"
|
||||
class="ms-minder-node-float-menu-icon-button"
|
||||
:class="[priorityMenuVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
||||
>
|
||||
<div
|
||||
class="h-[16px] w-[16px] rounded-full bg-[rgb(var(--primary-5))] text-center text-[12px] font-medium text-white"
|
||||
>
|
||||
P
|
||||
</div>
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<template #content>
|
||||
<div v-if="props.priorityTooltip" class="mx-[6px] px-[8px] py-[3px] text-[var(--color-text-4)]">
|
||||
{{ props.priorityTooltip }}
|
||||
</div>
|
||||
<template v-for="(item, pIndex) in priorityCount + 1" :key="item">
|
||||
<a-doption v-if="pIndex != 0" :value="pIndex">
|
||||
<div
|
||||
class="flex h-[20px] w-[20px] items-center justify-center rounded-full text-[12px] font-medium text-white"
|
||||
:style="{
|
||||
backgroundColor: priorityColorMap[pIndex],
|
||||
}"
|
||||
>
|
||||
{{ priorityPrefix }}{{ priorityStartWithZero ? pIndex - 1 : pIndex }}
|
||||
</div>
|
||||
</a-doption>
|
||||
</template>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<slot name="extractMenu"></slot>
|
||||
<a-dropdown
|
||||
v-if="props.canShowMoreMenu"
|
||||
v-model:popup-visible="moreMenuVisible"
|
||||
class="ms-minder-dropdown"
|
||||
:popup-translate="[0, 4]"
|
||||
position="bl"
|
||||
trigger="click"
|
||||
@select="(val) => handleMinderMenuSelect(val)"
|
||||
>
|
||||
<a-tooltip :content="t('common.more')">
|
||||
<MsButton
|
||||
type="icon"
|
||||
class="ms-minder-node-float-menu-icon-button"
|
||||
:class="[moreMenuVisible ? 'ms-minder-node-float-menu-icon-button--focus' : '']"
|
||||
>
|
||||
<MsIcon type="icon-icon_more_outlined" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<template #content>
|
||||
<a-doption v-if="props.canShowEnterNode" value="enterNode">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.enterNode') }}</div>
|
||||
<!-- <div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl+ Enter)</div> -->
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="copy">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.copy') }}</div>
|
||||
<!-- <div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl + C)</div> -->
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="cut">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.cut') }}</div>
|
||||
<!-- <div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl + X)</div> -->
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption v-if="props.canShowPasteMenu && minderStore.clipboard.length > 0" value="paste">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.paste') }}</div>
|
||||
<!-- <div class="ml-[4px] text-[var(--color-text-4)]">(Ctrl + V)</div> -->
|
||||
</div>
|
||||
</a-doption>
|
||||
<a-doption value="delete">
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('minder.hotboxMenu.delete') }}</div>
|
||||
<!-- <div class="ml-[4px] text-[var(--color-text-4)]">(Backspace)</div> -->
|
||||
</div>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TriggerPopupTranslate } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
import { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import { floatMenuProps, insertProps, MinderJsonNode, priorityProps, tagProps } from '../props';
|
||||
import { isDisableNode, isNodeInMinderView, setPriorityView } from '../script/tool/utils';
|
||||
|
||||
const props = defineProps({
|
||||
...floatMenuProps,
|
||||
...insertProps,
|
||||
...tagProps,
|
||||
...priorityProps,
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const minderStore = useMinderStore();
|
||||
|
||||
const currentNodeTags = ref<string[]>([]);
|
||||
const tags = ref<string[]>([]);
|
||||
|
||||
const menuVisible = ref(false);
|
||||
const menuPopupOffset = ref<TriggerPopupTranslate>([0, 0]);
|
||||
|
||||
watch(
|
||||
() => minderStore.event.timestamp,
|
||||
() => {
|
||||
let nodePosition: MinderNodePosition | undefined;
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (minderStore.event.name === MinderEventName.NODE_SELECT) {
|
||||
nodePosition = minderStore.event.nodePosition;
|
||||
currentNodeTags.value = minderStore.event.nodes?.[0].data?.resource || [];
|
||||
if (props.replaceableTags) {
|
||||
tags.value = props.replaceableTags(selectedNodes);
|
||||
} else {
|
||||
tags.value = [];
|
||||
}
|
||||
}
|
||||
if (selectedNodes.length > 1) {
|
||||
// 多选时隐藏悬浮菜单 TODO:支持批量操作
|
||||
menuVisible.value = false;
|
||||
return;
|
||||
}
|
||||
if (minderStore.event.name === MinderEventName.VIEW_CHANGE) {
|
||||
// 脑图画布移动时,重新计算节点位置
|
||||
nodePosition = window.minder.getSelectedNode()?.getRenderBox();
|
||||
}
|
||||
if (nodePosition && isNodeInMinderView(undefined, nodePosition, nodePosition.width / 2)) {
|
||||
// 判断节点在脑图可视区域内且遮挡的节点不超过节点宽度的一半,则显示菜单
|
||||
const nodeDomHeight = nodePosition.height || 0;
|
||||
menuPopupOffset.value = [nodePosition.x, nodePosition.y + nodeDomHeight + 4]; // 菜单显示在节点下方4px处
|
||||
menuVisible.value = true;
|
||||
} else {
|
||||
menuVisible.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const insertSiblingMenuVisible = ref(false);
|
||||
const insertSonMenuVisible = ref(false);
|
||||
const priorityMenuVisible = ref(false);
|
||||
const moreMenuVisible = ref(false);
|
||||
|
||||
/**
|
||||
* 执行插入
|
||||
* @param command 插入命令
|
||||
*/
|
||||
function execInsertCommand(command: string, value?: string) {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (props.insertNode) {
|
||||
props.insertNode(node, command, value);
|
||||
return;
|
||||
}
|
||||
if (window.minder.queryCommandState(command) !== -1) {
|
||||
window.minder.execCommand(command);
|
||||
nextTick(() => {
|
||||
const newNode: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (!newNode.data) {
|
||||
newNode.data = {
|
||||
id: getGenerateId(),
|
||||
text: '',
|
||||
};
|
||||
}
|
||||
newNode.data.isNew = true; // 新建的节点标记为新建
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换标签
|
||||
* @param value 切换后的标签
|
||||
*/
|
||||
function handleTagChange(value: string) {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (selectedNodes.length > 0) {
|
||||
const origin = window.minder.queryCommandValue('resource');
|
||||
if (props.singleTag) {
|
||||
origin.splice(0, origin.length, value);
|
||||
} else {
|
||||
const index = origin.indexOf(value);
|
||||
// 先删除排他的标签
|
||||
if (props.distinctTags.indexOf(value) > -1) {
|
||||
for (let i = 0; i < origin.length; i++) {
|
||||
if (props.distinctTags.indexOf(origin[i]) > -1) {
|
||||
origin.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index !== -1) {
|
||||
origin.splice(index, 1);
|
||||
} else {
|
||||
origin.push(value);
|
||||
}
|
||||
}
|
||||
window.minder.execCommand('resource', origin);
|
||||
minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, undefined, selectedNodes);
|
||||
}
|
||||
}
|
||||
|
||||
const priorityColorMap: Record<number, string> = {
|
||||
1: 'rgb(var(--danger-6))',
|
||||
2: 'rgb(var(--link-6))',
|
||||
3: 'rgb(var(--success-6))',
|
||||
4: 'rgb(var(--warning-6))',
|
||||
};
|
||||
const priorityDisabled = ref(true);
|
||||
function isDisable(): boolean {
|
||||
if (Object.keys(window.minder).length === 0) return true;
|
||||
nextTick(() => {
|
||||
setPriorityView(props.priorityStartWithZero, props.priorityPrefix);
|
||||
});
|
||||
const node = window.minder.getSelectedNode();
|
||||
if (isDisableNode(window.minder) || !node || node.parent === null) {
|
||||
return true;
|
||||
}
|
||||
if (props.priorityDisableCheck) {
|
||||
return props.priorityDisableCheck(node);
|
||||
}
|
||||
return !!window.minder.queryCommandState && window.minder.queryCommandState('priority') === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理快捷菜单选择
|
||||
* @param type 选择的菜单项
|
||||
*/
|
||||
function handleMinderMenuSelect(type: string | number | Record<string, any> | undefined, value?: string) {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (selectedNodes.length > 0) {
|
||||
switch (type) {
|
||||
case 'AppendChildNode':
|
||||
execInsertCommand('AppendChildNode', value);
|
||||
minderStore.dispatchEvent(MinderEventName.INSERT_CHILD, value, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'AppendSiblingNode':
|
||||
execInsertCommand('AppendSiblingNode', value);
|
||||
minderStore.dispatchEvent(MinderEventName.INSERT_SIBLING, value, undefined, undefined, selectedNodes);
|
||||
break;
|
||||
case 'copy':
|
||||
minderStore.dispatchEvent(MinderEventName.COPY_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('Copy');
|
||||
break;
|
||||
case 'cut':
|
||||
minderStore.dispatchEvent(MinderEventName.CUT_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('Cut');
|
||||
break;
|
||||
case 'paste':
|
||||
minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('Paste');
|
||||
break;
|
||||
case 'delete':
|
||||
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('RemoveNode');
|
||||
break;
|
||||
case 'enterNode':
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [selectedNodes[0]]);
|
||||
break;
|
||||
case 'priority':
|
||||
if (value && !priorityDisabled.value) {
|
||||
window.minder.execCommand('priority', value);
|
||||
setPriorityView(props.priorityStartWithZero, props.priorityPrefix);
|
||||
} else if (window.minder.execCommand && !priorityDisabled.value) {
|
||||
window.minder.execCommand('priority');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 保持菜单常显
|
||||
nextTick(() => {
|
||||
menuVisible.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const freshFuc = setPriorityView;
|
||||
if (window.minder) {
|
||||
window.minder.on('contentchange', () => {
|
||||
// 异步执行,否则执行完,还会被重置
|
||||
setTimeout(() => {
|
||||
freshFuc(props.priorityStartWithZero, props.priorityPrefix);
|
||||
}, 0);
|
||||
});
|
||||
window.minder.on('selectionchange', () => {
|
||||
priorityDisabled.value = isDisable();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ms-minder-node-float-menu {
|
||||
.arco-trigger-content {
|
||||
@apply flex w-auto flex-1 items-center bg-white;
|
||||
|
||||
padding: 4px 8px;
|
||||
gap: 8px;
|
||||
border-radius: var(--border-radius-small);
|
||||
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
|
||||
}
|
||||
.ms-minder-node-float-menu-icon-button {
|
||||
@apply !mr-0;
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1)) !important;
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-4)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-minder-node-float-menu-icon-button--focus {
|
||||
background-color: rgb(var(--primary-1)) !important;
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,53 +1,11 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" class="ms-minder-editor-container">
|
||||
<div class="flex-1">
|
||||
<minderHeader
|
||||
:sequence-enable="props.sequenceEnable"
|
||||
:tag-enable="props.tagEnable"
|
||||
:progress-enable="props.progressEnable"
|
||||
:priority-count="props.priorityCount"
|
||||
:priority-prefix="props.priorityPrefix"
|
||||
:priority-start-with-zero="props.priorityStartWithZero"
|
||||
:tags="props.tags"
|
||||
:move-enable="props.moveEnable"
|
||||
:move-confirm="props.moveConfirm"
|
||||
:tag-edit-check="props.tagEditCheck"
|
||||
:tag-disable-check="props.tagDisableCheck"
|
||||
:priority-disable-check="props.priorityDisableCheck"
|
||||
:distinct-tags="props.distinctTags"
|
||||
:default-mold="props.defaultMold"
|
||||
:del-confirm="props.delConfirm"
|
||||
:arrange-enable="props.arrangeEnable"
|
||||
:mold-enable="props.moldEnable"
|
||||
:font-enable="props.fontEnable"
|
||||
:style-enable="props.styleEnable"
|
||||
:replaceable-tags="props.replaceableTags"
|
||||
:single-tag="props.singleTag"
|
||||
:insert-node="props.insertNode"
|
||||
:after-tag-edit="props.afterTagEdit"
|
||||
@mold-change="handleMoldChange"
|
||||
/>
|
||||
<mainEditor
|
||||
:disabled="props.disabled"
|
||||
:sequence-enable="props.sequenceEnable"
|
||||
:tag-enable="props.tagEnable"
|
||||
:move-enable="props.moveEnable"
|
||||
:move-confirm="props.moveConfirm"
|
||||
:progress-enable="props.progressEnable"
|
||||
:import-json="props.importJson"
|
||||
:height="props.height"
|
||||
:tags="props.tags"
|
||||
:distinct-tags="props.distinctTags"
|
||||
:tag-edit-check="props.tagEditCheck"
|
||||
:tag-disable-check="props.tagDisableCheck"
|
||||
:priority-count="props.priorityCount"
|
||||
:priority-prefix="props.priorityPrefix"
|
||||
:priority-start-with-zero="props.priorityStartWithZero"
|
||||
:insert-node="props.insertNode"
|
||||
@after-mount="() => emit('afterMount')"
|
||||
@save="save"
|
||||
@enter-node="handleEnterNode"
|
||||
/>
|
||||
<mainEditor v-model:import-json="importJson" v-bind="props" @after-mount="() => emit('afterMount')" @save="save">
|
||||
<template #extractMenu>
|
||||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
</mainEditor>
|
||||
</div>
|
||||
<template v-if="props.extractContentTabList?.length">
|
||||
<div class="ms-minder-editor-extra" :class="[extraVisible ? 'ms-minder-editor-extra--visible' : '']">
|
||||
|
@ -64,19 +22,22 @@
|
|||
|
||||
<script lang="ts" name="minderEditor" setup>
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import minderHeader from './main/header.vue';
|
||||
import mainEditor from './main/mainEditor.vue';
|
||||
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import useMinderEventListener from './hooks/useMinderEventListener';
|
||||
import {
|
||||
delProps,
|
||||
editMenuProps,
|
||||
headerProps,
|
||||
floatMenuProps,
|
||||
insertProps,
|
||||
mainEditorProps,
|
||||
MinderEvent,
|
||||
MinderJson,
|
||||
MinderJsonNode,
|
||||
moleProps,
|
||||
priorityProps,
|
||||
|
@ -86,17 +47,17 @@
|
|||
|
||||
const emit = defineEmits<{
|
||||
(e: 'moldChange', data: number): void;
|
||||
(e: 'save', data: Record<string, any>): void;
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
(e: 'afterMount'): void;
|
||||
(e: 'enterNode', data: MinderJsonNode): void;
|
||||
(e: 'nodeSelect', data: MinderJsonNode): void;
|
||||
(e: 'contentChange', data: MinderJsonNode): void;
|
||||
(e: 'action', event: MinderCustomEvent): void;
|
||||
(e: 'beforeExecCommand', event: MinderEvent): void;
|
||||
(e: 'nodeUnselect'): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps({
|
||||
...headerProps,
|
||||
...floatMenuProps,
|
||||
...insertProps,
|
||||
...editMenuProps,
|
||||
...mainEditorProps,
|
||||
|
@ -116,29 +77,43 @@
|
|||
const extraVisible = defineModel<boolean>('extraVisible', {
|
||||
default: false,
|
||||
});
|
||||
const importJson = defineModel<MinderJson>('importJson', {
|
||||
default: () => ({
|
||||
root: {},
|
||||
template: 'default',
|
||||
treePath: [] as any[],
|
||||
}),
|
||||
});
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
|
||||
onMounted(async () => {
|
||||
window.minderProps = props;
|
||||
});
|
||||
|
||||
function handleMoldChange(data: number) {
|
||||
emit('moldChange', data);
|
||||
}
|
||||
|
||||
function save(data: Record<string, any>) {
|
||||
emit('save', data);
|
||||
}
|
||||
|
||||
function handleEnterNode(data: any) {
|
||||
emit('enterNode', data);
|
||||
function save(data: MinderJson, callback: () => void) {
|
||||
emit('save', data, callback);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
useMinderEventListener({
|
||||
handleSelectionChange: () => {
|
||||
const selectedNode: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (Object.keys(window.minder).length > 0 && selectedNode) {
|
||||
if (selectedNode) {
|
||||
emit('nodeSelect', selectedNode);
|
||||
const box = selectedNode.getRenderBox();
|
||||
minderStore.dispatchEvent(
|
||||
MinderEventName.NODE_SELECT,
|
||||
undefined,
|
||||
{
|
||||
...box,
|
||||
},
|
||||
selectedNode.rc.node,
|
||||
[selectedNode]
|
||||
);
|
||||
} else {
|
||||
emit('nodeUnselect');
|
||||
minderStore.dispatchEvent(MinderEventName.NODE_UNSELECT);
|
||||
}
|
||||
},
|
||||
handleContentChange: (node: MinderJsonNode) => {
|
||||
|
@ -150,6 +125,9 @@
|
|||
handleBeforeExecCommand: (event) => {
|
||||
emit('beforeExecCommand', event);
|
||||
},
|
||||
handleViewChange() {
|
||||
minderStore.dispatchEvent(MinderEventName.VIEW_CHANGE);
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -158,10 +136,9 @@
|
|||
.ms-minder-editor-container {
|
||||
@apply relative flex h-full w-full;
|
||||
.ms-minder-editor-extra {
|
||||
@apply flex flex-col overflow-hidden border-l;
|
||||
@apply flex flex-col overflow-hidden;
|
||||
|
||||
width: 0;
|
||||
border-color: var(--color-text-n8);
|
||||
transition: all 300ms ease-in-out;
|
||||
.ms-minder-editor-extra-content {
|
||||
@apply relative flex-1 overflow-y-auto;
|
||||
|
@ -171,8 +148,11 @@
|
|||
}
|
||||
}
|
||||
.ms-minder-editor-extra--visible {
|
||||
@apply border-l;
|
||||
|
||||
width: 35%;
|
||||
min-width: 360px;
|
||||
border-color: var(--color-text-n8);
|
||||
transition: all 300ms ease-in-out;
|
||||
animation: minWidth 300ms ease-in-out;
|
||||
}
|
||||
|
|
|
@ -29,13 +29,14 @@ export interface MinderJsonNode {
|
|||
data?: MinderJsonNodeData;
|
||||
parent?: MinderJsonNode;
|
||||
children?: MinderJsonNode[];
|
||||
type?: string; // 节点类型,root为根节点,其他为普通节点
|
||||
[key: string]: any; // minder 内置字段
|
||||
}
|
||||
|
||||
export interface MinderJson {
|
||||
root: MinderJsonNode;
|
||||
template: string;
|
||||
treePath: Record<string, MinderJsonNode>[];
|
||||
treePath: MinderJsonNode[];
|
||||
}
|
||||
// 脑图类
|
||||
export interface MinderClass {
|
||||
|
@ -53,16 +54,6 @@ export interface MinderEvent extends MinderClass {
|
|||
}
|
||||
|
||||
export const mainEditorProps = {
|
||||
importJson: {
|
||||
type: Object as PropType<MinderJson>,
|
||||
default() {
|
||||
return {
|
||||
root: {},
|
||||
template: 'default',
|
||||
treePath: [] as any[],
|
||||
};
|
||||
},
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
|
@ -98,6 +89,10 @@ export const priorityProps = {
|
|||
},
|
||||
priorityDisableCheck: Function as PropType<(node: MinderJsonNode) => boolean>,
|
||||
operators: [],
|
||||
priorityTooltip: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
|
||||
export interface MinderReplaceTag {
|
||||
|
@ -130,9 +125,54 @@ export const tagProps = {
|
|||
afterTagEdit: Function as PropType<(nodes: MinderJsonNode[], tag: string) => void>,
|
||||
};
|
||||
|
||||
export interface InsertMenuItem {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
export const floatMenuProps = {
|
||||
// 插入同级选项
|
||||
insertSiblingMenus: {
|
||||
type: Array as PropType<InsertMenuItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
// 插入子级选项
|
||||
insertSonMenus: {
|
||||
type: Array as PropType<InsertMenuItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
// 是否显示更多菜单
|
||||
canShowMoreMenu: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 是否显示进入节点
|
||||
canShowEnterNode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 是否显示粘贴菜单
|
||||
canShowPasteMenu: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 是否显示等级菜单
|
||||
canShowPriorityMenu: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 节点可选标签集合
|
||||
replaceableTags: {
|
||||
type: Function as PropType<(nodes: MinderJsonNode[]) => string[]>,
|
||||
},
|
||||
};
|
||||
|
||||
export const insertProps = {
|
||||
insertNode: {
|
||||
type: Function as PropType<(node: MinderJsonNode, type: string) => void>,
|
||||
type: Function as PropType<(node: MinderJsonNode, type: string, value?: string) => void>,
|
||||
default: undefined,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -16,14 +16,9 @@ function HotboxRuntime(this: any) {
|
|||
|
||||
function handleHotBoxShow() {
|
||||
const node = minder.getSelectedNode();
|
||||
let position: MinderNodePosition | undefined;
|
||||
if (node) {
|
||||
const box = node.getRenderBox();
|
||||
position = {
|
||||
x: box.cx,
|
||||
y: box.cy,
|
||||
};
|
||||
minderStore.dispatchEvent(MinderEventName.HOTBOX, position, node.rc.node);
|
||||
minderStore.dispatchEvent(MinderEventName.HOTBOX, undefined, box, node.rc.node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
|
||||
|
||||
import type { MinderJsonNode } from '../../props';
|
||||
|
||||
export function isDisableNode(minder: any) {
|
||||
|
@ -120,3 +122,18 @@ export function isDisableForNode(node: MinderJsonNode) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断脑图节点是否在脑图视图框内
|
||||
* @param node 目标节点
|
||||
*/
|
||||
export function isNodeInMinderView(node?: MinderJsonNode, nodePosition?: MinderNodePosition, offsetX?: number) {
|
||||
const containerWidth = document.querySelector('.ms-minder-container')?.clientWidth || 0;
|
||||
if (node) {
|
||||
nodePosition = (node || window.minder.getSelectedNode())?.getRenderBox();
|
||||
}
|
||||
if (nodePosition) {
|
||||
return nodePosition.x >= 0 && nodePosition.x + (offsetX || 0) <= containerWidth;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ export const reviewDefaultDetail: ReviewItem = {
|
|||
unPassCount: 0,
|
||||
reviewedCount: 0,
|
||||
underReviewedCount: 0,
|
||||
unReviewCount: 0,
|
||||
pos: 5000,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
|
|
|
@ -3,13 +3,16 @@ export enum MinderEventName {
|
|||
'HOTBOX' = 'HOTBOX', // 热键菜单
|
||||
'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', // 设置节点标签
|
||||
'NODE_SELECT' = 'NODE_SELECT', // 选中节点
|
||||
'NODE_UNSELECT' = 'NODE_UNSELECT', // 取消选中节点
|
||||
'VIEW_CHANGE' = 'VIEW_CHANGE', // 脑图视图移动
|
||||
'MINDER_CHANGED' = 'MINDER_CHANGED', // 脑图更改事件
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -11,7 +11,7 @@ export interface UseFullScreen {
|
|||
* @param domRef dom ref
|
||||
*/
|
||||
export default function useFullScreen(
|
||||
domRef: Ref<HTMLElement | null | undefined> | HTMLElement | Element | null | undefined
|
||||
domRef: Ref<HTMLElement | Element | null | undefined> | HTMLElement | Element | null | undefined
|
||||
): UseFullScreen {
|
||||
const isFullScreen = ref(false);
|
||||
const originalStyle = ref('');
|
||||
|
|
|
@ -178,6 +178,7 @@ export interface ReviewItem {
|
|||
unPassCount: number;
|
||||
reReviewedCount: number;
|
||||
underReviewedCount: number;
|
||||
unReviewCount: number;
|
||||
reviewedCount: number;
|
||||
followFlag: boolean; // 关注标识
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
|
|||
|
||||
import type { MinderJsonNode } from '@/components/pure/ms-minder-editor/props';
|
||||
|
||||
import type { MinderEventName } from '@/enums/minderEnum';
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import { MinderNodePosition, MinderState } from './types';
|
||||
|
||||
|
@ -12,14 +12,16 @@ const useMinderStore = defineStore('minder', {
|
|||
event: {
|
||||
name: '' as MinderEventName,
|
||||
timestamp: 0,
|
||||
params: '',
|
||||
nodePosition: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
} as MinderNodePosition,
|
||||
nodeDom: undefined,
|
||||
nodes: undefined,
|
||||
},
|
||||
mold: 0,
|
||||
clipboard: [],
|
||||
}),
|
||||
actions: {
|
||||
/**
|
||||
|
@ -31,21 +33,29 @@ const useMinderStore = defineStore('minder', {
|
|||
*/
|
||||
dispatchEvent(
|
||||
name: MinderEventName,
|
||||
params?: string,
|
||||
position?: MinderNodePosition,
|
||||
nodeDom?: HTMLElement,
|
||||
nodes?: MinderJsonNode[]
|
||||
) {
|
||||
this.event = {
|
||||
name,
|
||||
params,
|
||||
timestamp: Date.now(),
|
||||
nodePosition: position,
|
||||
nodeDom,
|
||||
nodes,
|
||||
};
|
||||
if ([MinderEventName.COPY_NODE, MinderEventName.CUT_NODE].includes(name)) {
|
||||
this.setClipboard(nodes);
|
||||
}
|
||||
},
|
||||
setMold(val: number) {
|
||||
this.mold = val;
|
||||
},
|
||||
setClipboard(nodes?: MinderJsonNode[]) {
|
||||
this.clipboard = nodes || [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -5,11 +5,20 @@ import type { MinderEventName } from '@/enums/minderEnum';
|
|||
export interface MinderNodePosition {
|
||||
x: number;
|
||||
y: number;
|
||||
cx: number;
|
||||
cy: number;
|
||||
height: number;
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
width: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface MinderCustomEvent {
|
||||
name: MinderEventName;
|
||||
timestamp: number;
|
||||
params?: any;
|
||||
nodePosition?: MinderNodePosition;
|
||||
nodeDom?: HTMLElement;
|
||||
nodes?: MinderJsonNode[];
|
||||
|
@ -18,4 +27,5 @@ export interface MinderCustomEvent {
|
|||
export interface MinderState {
|
||||
event: MinderCustomEvent;
|
||||
mold: number;
|
||||
clipboard: MinderJsonNode[]; // 剪切板
|
||||
}
|
||||
|
|
|
@ -315,16 +315,17 @@ export function filterTree<T>(
|
|||
export function findNodeByKey<T>(
|
||||
trees: TreeNode<T>[],
|
||||
targetKey: string | number,
|
||||
customKey = 'key'
|
||||
customKey = 'key',
|
||||
dataKey: string | undefined = undefined
|
||||
): TreeNode<T> | T | null {
|
||||
for (let i = 0; i < trees.length; i++) {
|
||||
const node = trees[i];
|
||||
if (node[customKey] === targetKey) {
|
||||
if (dataKey ? node[dataKey]?.[customKey] === targetKey : node[customKey] === targetKey) {
|
||||
return node; // 如果当前节点的 key 与目标 key 匹配,则返回当前节点
|
||||
}
|
||||
|
||||
if (Array.isArray(node.children) && node.children.length > 0) {
|
||||
const _node = findNodeByKey(node.children, targetKey, customKey); // 递归在子节点中查找
|
||||
const _node = findNodeByKey(node.children, targetKey, customKey, dataKey); // 递归在子节点中查找
|
||||
if (_node) {
|
||||
return _node; // 如果在子节点中找到了匹配的节点,则返回该节点
|
||||
}
|
||||
|
@ -389,6 +390,34 @@ export function findNodePathByKey<T>(
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据customKey替换树节点
|
||||
*/
|
||||
export function replaceNodeInTree<T>(
|
||||
tree: TreeNode<T>[],
|
||||
targetKey: string,
|
||||
newNode: TreeNode<T>,
|
||||
dataKey?: string,
|
||||
customKey = 'key'
|
||||
): boolean {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const node = tree[i];
|
||||
if (dataKey ? node[dataKey]?.[customKey] === targetKey : node[customKey] === targetKey) {
|
||||
// 找到目标节点,进行替换
|
||||
tree[i] = newNode;
|
||||
return true;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
// 如果当前节点有子节点,递归查找
|
||||
if (replaceNodeInTree(node.children, targetKey, newNode, dataKey, customKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在某个节点前/后插入单个新节点
|
||||
* @param treeArr 目标树
|
||||
|
|
|
@ -1452,15 +1452,12 @@
|
|||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent !important;
|
||||
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
.arco-select-view-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
.arco-select-view-value {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
|
|
|
@ -1465,8 +1465,10 @@
|
|||
|
||||
watch(
|
||||
() => showType.value,
|
||||
() => {
|
||||
initData();
|
||||
(val) => {
|
||||
if (val === 'list') {
|
||||
initData();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -141,7 +141,6 @@
|
|||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { filter } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
|
@ -153,7 +152,6 @@
|
|||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import TableFilter from '../../tableFilter.vue';
|
||||
import conditionStatus from '@/views/api-test/report/component/conditionStatus.vue';
|
||||
|
||||
import {
|
||||
addPrepositionRelation,
|
||||
|
|
|
@ -47,6 +47,15 @@
|
|||
{{ props.reviewDetail.underReviewedCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[var(--color-text-4)]"></div>
|
||||
<div>{{ t('caseManagement.caseReview.unReview') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.reviewDetail.unReviewCount }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
</MsColorLine>
|
||||
|
|
Loading…
Reference in New Issue