feat(脑图): 脑图批量操作&公共文案
This commit is contained in:
parent
658e78a098
commit
578fe9a157
|
@ -19,8 +19,14 @@
|
|||
:can-show-paste-menu="!stopPaste()"
|
||||
:can-show-more-menu="canShowMoreMenu()"
|
||||
:can-show-priority-menu="canShowPriorityMenu()"
|
||||
:custom-batch-expand="customBatchExpand"
|
||||
:can-show-batch-expand="canShowBatchExpand()"
|
||||
:can-show-batch-cut="true"
|
||||
:can-show-batch-copy="true"
|
||||
:can-show-batch-delete="true"
|
||||
:priority-tooltip="t('caseManagement.caseReview.caseLevel')"
|
||||
:disabled="!hasEditPermission"
|
||||
can-show-more-batch-menu
|
||||
single-tag
|
||||
tag-enable
|
||||
sequence-enable
|
||||
|
@ -697,6 +703,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量展开节点
|
||||
*/
|
||||
function customBatchExpand(node: MinderJsonNode) {
|
||||
if (node.data?.resource?.includes(caseTag)) {
|
||||
expendNodeAndChildren(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否显示批量展开按钮
|
||||
*/
|
||||
function canShowBatchExpand() {
|
||||
if (window.minder) {
|
||||
const nodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
return nodes.some((node) => !!node.data?.resource?.includes(caseTag));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析用例节点信息
|
||||
* @param node 用例节点
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type {
|
||||
InsertMenuItem,
|
||||
MinderEvent,
|
||||
MinderJsonNode,
|
||||
MinderJsonNodeData,
|
||||
MinderMenuItem,
|
||||
} from '@/components/pure/ms-minder-editor/props';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -50,8 +50,8 @@ export default function useMinderBaseApi({ hasEditPermission }: { hasEditPermiss
|
|||
return true;
|
||||
}
|
||||
|
||||
const insertSiblingMenus = ref<InsertMenuItem[]>([]);
|
||||
const insertSonMenus = ref<InsertMenuItem[]>([]);
|
||||
const insertSiblingMenus = ref<MinderMenuItem[]>([]);
|
||||
const insertSonMenus = ref<MinderMenuItem[]>([]);
|
||||
|
||||
/**
|
||||
* 检测节点可展示的菜单项
|
||||
|
@ -236,9 +236,9 @@ export default function useMinderBaseApi({ hasEditPermission }: { hasEditPermiss
|
|||
return false;
|
||||
}
|
||||
if (window.minder) {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
const nodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
// 选中节点是用例节点时,可展示优先级菜单
|
||||
return !!node?.data?.resource?.includes(caseTag);
|
||||
return nodes.every((node) => !!node.data?.resource?.includes(caseTag));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
:can-show-float-menu="canShowFloatMenu"
|
||||
:can-show-delete-menu="canShowDeleteMenu"
|
||||
:disabled="!hasEditPermission"
|
||||
:can-show-batch-delete="true"
|
||||
can-show-more-batch-menu
|
||||
custom-priority
|
||||
single-tag
|
||||
tag-enable
|
||||
|
@ -523,8 +525,13 @@
|
|||
Message.warning(t('ms.minders.unsavedTip'));
|
||||
return;
|
||||
}
|
||||
const nodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (nodes.length > 1) {
|
||||
extraVisible.value = false;
|
||||
return;
|
||||
}
|
||||
extraVisible.value = !extraVisible.value;
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
const node = nodes[0];
|
||||
switchingConfigFormData.value = true;
|
||||
if (extraVisible.value) {
|
||||
activePlanSet.value = node as PlanMinderNode;
|
||||
|
|
|
@ -7,7 +7,7 @@ import type { MinderEvent, MinderJsonNode } from '../props';
|
|||
|
||||
export interface UseEventListenerProps {
|
||||
handleContentChange?: (node?: MinderJsonNode) => void;
|
||||
handleSelectionChange?: (node?: MinderJsonNode) => void;
|
||||
handleSelectionChange?: (nodes: MinderJsonNode[]) => void;
|
||||
handleMinderEvent?: (event: MinderCustomEvent) => void;
|
||||
handleBeforeExecCommand?: (event: MinderEvent) => void;
|
||||
handleViewChange?: (event: MinderEvent) => void;
|
||||
|
@ -36,16 +36,16 @@ export default function useEventListener(listener: UseEventListenerProps) {
|
|||
minder.on(
|
||||
'selectionchange',
|
||||
debounce(() => {
|
||||
const node: MinderJsonNode = minder.getSelectedNode();
|
||||
const nodes: MinderJsonNode[] = minder.getSelectedNodes();
|
||||
// 如果节点选中后即刻进行拖拽,则等待拖拽结束后再触发选中事件
|
||||
if (isDragging.value) {
|
||||
selectionchangeEvent = () => {
|
||||
if (listener.handleSelectionChange) {
|
||||
listener.handleSelectionChange(node);
|
||||
listener.handleSelectionChange(nodes);
|
||||
}
|
||||
};
|
||||
} else if (listener.handleSelectionChange) {
|
||||
listener.handleSelectionChange(node);
|
||||
listener.handleSelectionChange(nodes);
|
||||
}
|
||||
}, 300)
|
||||
);
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import type { MinderJsonNode } from '../props';
|
||||
import { markDeleteNode, resetNodes } from '../script/tool/utils';
|
||||
import { markDeleteNode, resetNodes, setPriorityView } from '../script/tool/utils';
|
||||
|
||||
interface IData {
|
||||
getRegisterProtocol(protocol: string): {
|
||||
|
@ -15,16 +19,23 @@ interface IData {
|
|||
|
||||
export interface MinderOperationProps {
|
||||
insertNode?: (node: MinderJsonNode, command: string, value?: string) => void;
|
||||
customBatchExpand?: (node: MinderJsonNode) => void;
|
||||
disabled?: boolean;
|
||||
canShowMoreMenu?: boolean;
|
||||
canShowMoreMenuNodeOperation?: boolean;
|
||||
canShowPasteMenu?: boolean;
|
||||
canShowDeleteMenu?: boolean;
|
||||
customPriority?: boolean;
|
||||
priorityStartWithZero?: boolean;
|
||||
priorityPrefix?: string;
|
||||
canShowBatchCut?: boolean;
|
||||
canShowBatchCopy?: boolean;
|
||||
canShowBatchDelete?: boolean;
|
||||
}
|
||||
|
||||
export default function useMinderOperation({
|
||||
insertNode,
|
||||
canShowMoreMenuNodeOperation,
|
||||
canShowPasteMenu,
|
||||
}: MinderOperationProps) {
|
||||
export default function useMinderOperation(options: MinderOperationProps) {
|
||||
const minderStore = useMinderStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
function encode(nodes: Array<MinderJsonNode>): string {
|
||||
const { editor } = window;
|
||||
|
@ -42,8 +53,8 @@ export default function useMinderOperation({
|
|||
/**
|
||||
* 执行复制
|
||||
*/
|
||||
const minderCopy = (e?: ClipboardEvent) => {
|
||||
if (!canShowMoreMenuNodeOperation) {
|
||||
const minderCopy = async (e?: ClipboardEvent) => {
|
||||
if ((!options.canShowMoreMenu || !options.canShowMoreMenuNodeOperation) && options.canShowBatchCopy === false) {
|
||||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -57,8 +68,15 @@ export default function useMinderOperation({
|
|||
case 'normal': {
|
||||
const selectedNodes = minder.getSelectedNodes();
|
||||
minderStore.dispatchEvent(MinderEventName.COPY_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
if (e?.clipboardData) {
|
||||
e.clipboardData.setData('text/plain', encode(selectedNodes));
|
||||
} else {
|
||||
const { copy } = useClipboard();
|
||||
await copy(encode(selectedNodes));
|
||||
}
|
||||
minder.execCommand('Copy');
|
||||
e?.preventDefault();
|
||||
Message.success(t('common.copySuccess'));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -68,8 +86,11 @@ export default function useMinderOperation({
|
|||
/**
|
||||
* 执行剪切
|
||||
*/
|
||||
const minderCut = (e?: ClipboardEvent) => {
|
||||
if (!canShowMoreMenuNodeOperation) {
|
||||
const minderCut = async (e?: ClipboardEvent) => {
|
||||
if (
|
||||
(options.disabled || !options.canShowMoreMenu || !options.canShowMoreMenuNodeOperation) &&
|
||||
options.canShowBatchCut === false
|
||||
) {
|
||||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -79,9 +100,7 @@ export default function useMinderOperation({
|
|||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const state = fsm.state();
|
||||
|
||||
switch (state) {
|
||||
case 'input': {
|
||||
break;
|
||||
|
@ -90,11 +109,17 @@ export default function useMinderOperation({
|
|||
markDeleteNode(minder);
|
||||
const selectedNodes = minder.getSelectedNodes();
|
||||
if (selectedNodes.length) {
|
||||
e?.clipboardData?.setData('text/plain', encode(selectedNodes));
|
||||
if (e?.clipboardData) {
|
||||
e.clipboardData.setData('text/plain', encode(selectedNodes));
|
||||
} else {
|
||||
const { copy } = useClipboard();
|
||||
await copy(encode(selectedNodes));
|
||||
}
|
||||
minder.execCommand('Cut');
|
||||
}
|
||||
e?.preventDefault();
|
||||
minderStore.dispatchEvent(MinderEventName.CUT_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
e?.preventDefault();
|
||||
Message.success(t('common.cutSuccess'));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -104,8 +129,8 @@ export default function useMinderOperation({
|
|||
/**
|
||||
* 执行粘贴
|
||||
*/
|
||||
const minderPaste = (e?: ClipboardEvent) => {
|
||||
if (!canShowMoreMenuNodeOperation && !canShowPasteMenu) {
|
||||
const minderPaste = async (e?: ClipboardEvent) => {
|
||||
if (options.disabled || !options.canShowPasteMenu) {
|
||||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
@ -117,10 +142,8 @@ export default function useMinderOperation({
|
|||
e?.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const state = fsm.state();
|
||||
const textData = e?.clipboardData?.getData('text/plain');
|
||||
|
||||
const textData = e?.clipboardData ? e.clipboardData.getData('text/plain') : await navigator.clipboard.readText();
|
||||
switch (state) {
|
||||
case 'input': {
|
||||
// input状态下如果格式为application/km则不进行paste操作
|
||||
|
@ -181,8 +204,8 @@ export default function useMinderOperation({
|
|||
*/
|
||||
const execInsertCommand = (command: string, value?: string) => {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (insertNode) {
|
||||
insertNode(node, command, value);
|
||||
if (options.insertNode) {
|
||||
options.insertNode(node, command, value);
|
||||
return;
|
||||
}
|
||||
if (window.minder.queryCommandState(command) !== -1) {
|
||||
|
@ -220,13 +243,57 @@ export default function useMinderOperation({
|
|||
minderStore.dispatchEvent(MinderEventName.INSERT_SIBLING, value, undefined, undefined, selectedNodes);
|
||||
};
|
||||
|
||||
/**
|
||||
* 脑图展开
|
||||
* @param selectedNodes 当前选中的节点集合
|
||||
*/
|
||||
const minderExpand = (selectedNodes: MinderJsonNode[]) => {
|
||||
if (selectedNodes.every((node) => node.isExpanded())) {
|
||||
// 选中的节点集合全部展开,则全部收起
|
||||
selectedNodes.forEach((node) => {
|
||||
node.collapse();
|
||||
node.renderTree();
|
||||
});
|
||||
if (!options.customPriority) {
|
||||
// 展开后,需要设置一次优先级展示,避免展开后优先级显示成脑图内置文案;如果设置了自定义优先级,则不在此设置,由外部自行处理
|
||||
setPriorityView(!!options.priorityStartWithZero, options.priorityPrefix || '');
|
||||
window.minder.refresh();
|
||||
}
|
||||
minderStore.dispatchEvent(MinderEventName.COLLAPSE, undefined, undefined, undefined, selectedNodes);
|
||||
} else {
|
||||
// 选中的节点集合中有一个节点未展开,则全部展开
|
||||
selectedNodes.forEach((node) => {
|
||||
if (selectedNodes.length > 1 && options.customBatchExpand) {
|
||||
// 批量操作节点才执行customBatchExpand
|
||||
options.customBatchExpand(node);
|
||||
} else {
|
||||
node.expand();
|
||||
node.renderTree();
|
||||
}
|
||||
});
|
||||
if (!options.customPriority) {
|
||||
// 展开后,需要设置一次优先级展示,避免展开后优先级显示成脑图内置文案;如果设置了自定义优先级,则不在此设置,由外部自行处理
|
||||
setPriorityView(!!options.priorityStartWithZero, options.priorityPrefix || '');
|
||||
window.minder.refresh();
|
||||
}
|
||||
minderStore.dispatchEvent(MinderEventName.EXPAND, undefined, undefined, undefined, selectedNodes);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除节点
|
||||
* @param selectedNodes 当前选中的节点集合
|
||||
*/
|
||||
const minderDelete = (selectedNodes: MinderJsonNode[]) => {
|
||||
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('RemoveNode');
|
||||
if (
|
||||
(options.canShowDeleteMenu ||
|
||||
(options.canShowMoreMenu && options.canShowMoreMenuNodeOperation) ||
|
||||
options.canShowBatchDelete) &&
|
||||
!options.disabled
|
||||
) {
|
||||
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('RemoveNode');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -236,5 +303,6 @@ export default function useMinderOperation({
|
|||
appendChildNode,
|
||||
appendSiblingNode,
|
||||
minderDelete,
|
||||
minderExpand,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { isDisableNode, setPriorityView } from '../script/tool/utils';
|
||||
|
||||
export interface PriorityProps {
|
||||
priorityStartWithZero: boolean;
|
||||
priorityPrefix: string;
|
||||
customPriority?: boolean;
|
||||
priorityDisableCheck?: (node: any) => boolean;
|
||||
}
|
||||
|
||||
export default function usePriority(options: PriorityProps) {
|
||||
const priorityDisabled = ref(true);
|
||||
function isDisable(): boolean {
|
||||
if (Object.keys(window.minder).length === 0) return true;
|
||||
nextTick(() => {
|
||||
setPriorityView(options.priorityStartWithZero, options.priorityPrefix);
|
||||
});
|
||||
const node = window.minder.getSelectedNode();
|
||||
if (isDisableNode(window.minder) || !node || node.parent === null) {
|
||||
return true;
|
||||
}
|
||||
if (options.priorityDisableCheck) {
|
||||
return options.priorityDisableCheck(node);
|
||||
}
|
||||
return !!window.minder.queryCommandState && window.minder.queryCommandState('priority') === -1;
|
||||
}
|
||||
|
||||
function setPriority(value?: string) {
|
||||
if (value && !priorityDisabled.value) {
|
||||
window.minder.execCommand('priority', value);
|
||||
setPriorityView(options.priorityStartWithZero, options.priorityPrefix);
|
||||
} else if (window.minder.execCommand && !priorityDisabled.value) {
|
||||
window.minder.execCommand('priority');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const freshFuc = setPriorityView;
|
||||
if (window.minder && !options.customPriority) {
|
||||
window.minder.on('contentchange', () => {
|
||||
// 异步执行,否则执行完,还会被重置
|
||||
setTimeout(() => {
|
||||
freshFuc(options.priorityStartWithZero, options.priorityPrefix);
|
||||
}, 0);
|
||||
});
|
||||
window.minder.on('selectionchange', () => {
|
||||
priorityDisabled.value = isDisable();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
priorityDisabled,
|
||||
setPriority,
|
||||
};
|
||||
}
|
|
@ -29,12 +29,14 @@
|
|||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
</nodeFloatMenu>
|
||||
<batchMenu v-bind="props" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="minderContainer" setup>
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import batchMenu from '../menu/batchMenu.vue';
|
||||
import nodeFloatMenu from '../menu/nodeFloatMenu.vue';
|
||||
import minderHeader from './header.vue';
|
||||
import Navigator from './navigator.vue';
|
||||
|
@ -47,6 +49,7 @@
|
|||
|
||||
import useEventListener from '../hooks/useMinderEventListener';
|
||||
import {
|
||||
batchMenuProps,
|
||||
editMenuProps,
|
||||
floatMenuProps,
|
||||
headerProps,
|
||||
|
@ -68,6 +71,7 @@
|
|||
...mainEditorProps,
|
||||
...tagProps,
|
||||
...priorityProps,
|
||||
...batchMenuProps,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
<template>
|
||||
<div v-if="batchMenuVisible" class="ms-minder-node-float-menu ms-minder-batch-menu">
|
||||
<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="batchMenu"></slot>
|
||||
<a-dropdown
|
||||
v-if="props.canShowMoreBatchMenu"
|
||||
v-model:popup-visible="moreMenuVisible"
|
||||
class="ms-minder-dropdown"
|
||||
:popup-translate="[0, -4]"
|
||||
position="tl"
|
||||
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.canShowBatchCopy" 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 v-if="props.canShowBatchCut" 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.canShowBatchDelete" 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 v-if="props.canShowBatchExpand" 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>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import useMinderOperation from '../hooks/useMinderOperation';
|
||||
import usePriority from '../hooks/useMinderPriority';
|
||||
import {
|
||||
batchMenuProps,
|
||||
floatMenuProps,
|
||||
mainEditorProps,
|
||||
MinderJsonNode,
|
||||
priorityColorMap,
|
||||
priorityProps,
|
||||
} from '../props';
|
||||
|
||||
const props = defineProps({
|
||||
...mainEditorProps,
|
||||
...floatMenuProps,
|
||||
...priorityProps,
|
||||
...batchMenuProps,
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const minderStore = useMinderStore();
|
||||
const { setPriority } = usePriority(props);
|
||||
|
||||
const batchMenuVisible = ref(false);
|
||||
const priorityMenuVisible = ref(false);
|
||||
const moreMenuVisible = ref(false);
|
||||
|
||||
const { minderCopy, minderCut, minderDelete, minderExpand } = useMinderOperation(props);
|
||||
|
||||
watch(
|
||||
() => minderStore.event.eventId,
|
||||
async () => {
|
||||
if (window.minder) {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (
|
||||
minderStore.event.name === MinderEventName.DRAG_FINISH ||
|
||||
minderStore.event.name === MinderEventName.NODE_UNSELECT
|
||||
) {
|
||||
batchMenuVisible.value = selectedNodes.length > 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 处理快捷菜单选择
|
||||
* @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 'copy':
|
||||
minderCopy();
|
||||
break;
|
||||
case 'cut':
|
||||
minderCut();
|
||||
break;
|
||||
case 'expand':
|
||||
minderExpand(selectedNodes);
|
||||
break;
|
||||
case 'delete':
|
||||
minderDelete(selectedNodes);
|
||||
break;
|
||||
case 'priority':
|
||||
setPriority(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ms-minder-batch-menu {
|
||||
@apply absolute flex w-auto flex-1 items-center bg-white;
|
||||
|
||||
bottom: 6px;
|
||||
left: 50%;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--border-radius-small);
|
||||
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
|
@ -137,32 +137,32 @@
|
|||
<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 class="ml-[4px] text-[var(--color-text-4)]">(Ctrl+ Enter)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
<template v-if="props.canShowMoreMenuNodeOperation">
|
||||
<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 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 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 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 class="ml-[4px] text-[var(--color-text-4)]">(Backspace)</div>
|
||||
</div>
|
||||
</a-doption>
|
||||
</template>
|
||||
|
@ -200,8 +200,9 @@
|
|||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import useMinderOperation from '../hooks/useMinderOperation';
|
||||
import { floatMenuProps, mainEditorProps, MinderJsonNode, priorityProps, tagProps } from '../props';
|
||||
import { isDisableNode, isNodeInMinderView, setPriorityView } from '../script/tool/utils';
|
||||
import usePriority from '../hooks/useMinderPriority';
|
||||
import { floatMenuProps, mainEditorProps, MinderJsonNode, priorityColorMap, priorityProps, tagProps } from '../props';
|
||||
import { isNodeInMinderView } from '../script/tool/utils';
|
||||
|
||||
const props = defineProps({
|
||||
...mainEditorProps,
|
||||
|
@ -215,6 +216,7 @@
|
|||
|
||||
const { t } = useI18n();
|
||||
const minderStore = useMinderStore();
|
||||
const { setPriority } = usePriority(props);
|
||||
|
||||
const currentNodeTags = ref<string[]>([]);
|
||||
const tags = ref<string[]>([]);
|
||||
|
@ -240,7 +242,7 @@
|
|||
}
|
||||
}
|
||||
if (selectedNodes.length > 1) {
|
||||
// 多选时隐藏悬浮菜单 TODO:支持批量操作
|
||||
// 多选时隐藏悬浮菜单
|
||||
menuVisible.value = false;
|
||||
return;
|
||||
}
|
||||
|
@ -304,31 +306,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const { minderCopy, minderCut, minderPaste, appendChildNode, appendSiblingNode, minderDelete } = useMinderOperation({
|
||||
insertNode: props.insertNode,
|
||||
});
|
||||
const { minderCopy, minderCut, minderPaste, appendChildNode, appendSiblingNode, minderDelete } =
|
||||
useMinderOperation(props);
|
||||
|
||||
/**
|
||||
* 处理快捷菜单选择
|
||||
|
@ -360,12 +339,7 @@
|
|||
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');
|
||||
}
|
||||
setPriority(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -385,23 +359,6 @@
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
const freshFuc = setPriorityView;
|
||||
if (window.minder && !props.customPriority) {
|
||||
window.minder.on('contentchange', () => {
|
||||
// 异步执行,否则执行完,还会被重置
|
||||
setTimeout(() => {
|
||||
freshFuc(props.priorityStartWithZero, props.priorityPrefix);
|
||||
}, 0);
|
||||
});
|
||||
window.minder.on('selectionchange', () => {
|
||||
priorityDisabled.value = isDisable();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
<template>
|
||||
<div class="arrange-group">
|
||||
<div class="arrange menu-btn" :disabled="disabled" @click="resetlayout">
|
||||
<span class="tab-icons" />
|
||||
<span class="label">
|
||||
{{ t('minder.menu.arrange.arrange_layout') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="Arrange" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const disabled = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
return window.minder.queryCommandState && window.minder.queryCommandState('resetlayout') === -1;
|
||||
});
|
||||
|
||||
function resetlayout() {
|
||||
if (window.minder.queryCommandState('resetlayout') !== -1) {
|
||||
window.minder.execCommand('resetlayout');
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,261 +0,0 @@
|
|||
<template>
|
||||
<div class="font-group ml-[10px]">
|
||||
<a-select
|
||||
v-model="fontFamilyDefaultValue"
|
||||
:placeholder="t('minder.menu.font.font')"
|
||||
class="font-family-select"
|
||||
:disabled="disabledFont"
|
||||
size="mini"
|
||||
@change="execCommandFontFamily"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in fontFamilys"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
:style="{ 'font-family': item.value }"
|
||||
/>
|
||||
</a-select>
|
||||
<a-select
|
||||
v-model="fontSizeDefaultValue"
|
||||
:placeholder="t('minder.menu.font.size')"
|
||||
class="font-size-select"
|
||||
:disabled="disabledFontSize"
|
||||
size="mini"
|
||||
@change="execCommandFontSize"
|
||||
>
|
||||
<a-option
|
||||
v-for="item in fontSizes"
|
||||
:key="item.id"
|
||||
:label="item.label.toString()"
|
||||
:value="item.value"
|
||||
:style="{
|
||||
'font-size': item.value + 'px',
|
||||
'height': 2 * item.value + 'px',
|
||||
'line-height': 2 * item.value + 'px',
|
||||
'padding': 0,
|
||||
}"
|
||||
/>
|
||||
</a-select>
|
||||
<span class="font-btn mt-[2px]">
|
||||
<span
|
||||
class="menu-btn tab-icons font-bold"
|
||||
:class="{ selected: boldSelected }"
|
||||
:disabled="disabledBold"
|
||||
@click="execCommandFontStyle('bold')"
|
||||
/>
|
||||
<span
|
||||
class="font-italic menu-btn tab-icons"
|
||||
:class="{ selected: italicSelected }"
|
||||
:disabled="disabledItalic"
|
||||
@click="execCommandFontStyle('italic')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="StyleOperation" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const fontFamilys = [
|
||||
{
|
||||
id: 1,
|
||||
value: '宋体,SimSun',
|
||||
name: '宋体',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: '微软雅黑,Microsoft YaHei',
|
||||
name: '微软雅黑',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
value: '楷体,楷体_GB2312,SimKai',
|
||||
name: '楷体',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
value: '黑体, SimHei',
|
||||
name: '黑体',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
value: '隶书, SimLi',
|
||||
name: '隶书',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
value: 'andale mono',
|
||||
name: 'Andale Mono',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
value: 'arial,helvetica,sans-serif',
|
||||
name: 'Arial',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
value: 'arial black,avant garde',
|
||||
name: 'arialBlack',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
value: 'comic sans ms',
|
||||
name: 'comic Sans Ms',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
value: 'impact,chicago',
|
||||
name: 'Impact',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
value: 'times new roman',
|
||||
name: 'times New Roman',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
value: 'sans-serif',
|
||||
name: 'Sans-Serif',
|
||||
},
|
||||
];
|
||||
const fontSizes = [
|
||||
{
|
||||
id: 1,
|
||||
value: 10,
|
||||
label: 10,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 12,
|
||||
label: 12,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
value: 16,
|
||||
label: 16,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
value: 18,
|
||||
label: 18,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
value: 24,
|
||||
label: 24,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
value: 32,
|
||||
label: 32,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
value: 48,
|
||||
label: 48,
|
||||
},
|
||||
];
|
||||
|
||||
const fontFamilyDefaultValue = ref('');
|
||||
const fontSizeDefaultValue = ref('');
|
||||
|
||||
const disabledFont = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentFontFamily = window.minder.queryCommandValue('fontfamily');
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
fontFamilyDefaultValue.value = currentFontFamily || t('minder.menu.font.font');
|
||||
return window.minder.queryCommandState('fontfamily') === -1;
|
||||
});
|
||||
const disabledFontSize = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
fontSizeDefaultValue.value = window.minder.queryCommandValue('fontsize') || t('minder.menu.font.size');
|
||||
return window.minder.queryCommandState('fontsize') === -1;
|
||||
});
|
||||
const disabledBold = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
return window.minder.queryCommandState('bold') === -1;
|
||||
});
|
||||
const disabledItalic = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
return window.minder.queryCommandState('italic') === -1;
|
||||
});
|
||||
const boldSelected = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
return window.minder.queryCommandState('bold') === -1;
|
||||
});
|
||||
const italicSelected = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
return window.minder.queryCommandState('italic') === -1;
|
||||
});
|
||||
|
||||
function execCommandFontFamily(
|
||||
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
if (value === t('minder.menu.font.font')) {
|
||||
return;
|
||||
}
|
||||
window.minder.execCommand('fontfamily', value);
|
||||
}
|
||||
|
||||
function execCommandFontSize(
|
||||
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
if (typeof value !== 'number') {
|
||||
return;
|
||||
}
|
||||
window.minder.execCommand('fontsize', value);
|
||||
}
|
||||
|
||||
function execCommandFontStyle(style: string) {
|
||||
switch (style) {
|
||||
case 'bold':
|
||||
if (window.minder.queryCommandState('bold') !== -1) {
|
||||
window.minder.execCommand('bold');
|
||||
}
|
||||
break;
|
||||
case 'italic':
|
||||
if (window.minder.queryCommandState('italic') !== -1) {
|
||||
window.minder.execCommand('italic');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,93 +0,0 @@
|
|||
<template>
|
||||
<a-dropdown class="toggle" :disabled="disabled" @select="handleCommand">
|
||||
<span class="dropdown-toggle mold-icons menu-btn cursor-pointer" :class="'mold-' + (moldIndex + 1)" />
|
||||
<template #content>
|
||||
<a-doption class="dropdown-item" :value="1">
|
||||
<div class="mold-icons mold-1"></div>
|
||||
</a-doption>
|
||||
<a-doption class="dropdown-item" :value="2">
|
||||
<div class="mold-icons mold-2"></div>
|
||||
</a-doption>
|
||||
<a-doption class="dropdown-item" :value="3">
|
||||
<div class="mold-icons mold-3"></div>
|
||||
</a-doption>
|
||||
<a-doption class="dropdown-item" :value="4">
|
||||
<div class="mold-icons mold-4"></div>
|
||||
</a-doption>
|
||||
<a-doption class="dropdown-item" :value="5">
|
||||
<div class="mold-icons mold-5"></div>
|
||||
</a-doption>
|
||||
<a-doption class="dropdown-item" :value="6">
|
||||
<div class="mold-icons mold-6"></div>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="Mold" setup>
|
||||
import { computed, nextTick, onMounted, ref } from 'vue';
|
||||
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
|
||||
import { moleProps } from '../../props';
|
||||
|
||||
const props = defineProps(moleProps);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'moldChange', data: number): void;
|
||||
}>();
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
const moldIndex = ref(0);
|
||||
|
||||
const disabled = computed(() => {
|
||||
try {
|
||||
if (!window.minder) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
return window.minder.queryCommandState('template') === -1;
|
||||
});
|
||||
|
||||
const templateList = computed(() => window.kityminder.Minder.getTemplateList());
|
||||
|
||||
function handleCommand(value: string | number | Record<string, any> | undefined) {
|
||||
moldIndex.value = (value as number) - 1;
|
||||
window.minder.execCommand('template', Object.keys(templateList.value)[(value as number) - 1]);
|
||||
emit('moldChange', (value as number) - 1);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => handleCommand(props.defaultMold));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-dropdown-list) {
|
||||
@apply grid grid-cols-2;
|
||||
}
|
||||
.dropdown-toggle .mold-icons,
|
||||
.mold-icons {
|
||||
background-image: url('@/assets/images/minder/mold.png');
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.dropdown-item {
|
||||
@apply flex items-center justify-center;
|
||||
|
||||
height: 50px !important;
|
||||
}
|
||||
.mold-loop(@i) when (@i > 0) {
|
||||
.mold-@{i} {
|
||||
@apply flex;
|
||||
|
||||
margin-top: 5px;
|
||||
width: 50px;
|
||||
height: 45px;
|
||||
background-position: (1 - @i) * 50px 0;
|
||||
}
|
||||
.mold-loop(@i - 1);
|
||||
}
|
||||
|
||||
.mold-loop(6);
|
||||
</style>
|
|
@ -1,79 +0,0 @@
|
|||
<template>
|
||||
<div class="style-group">
|
||||
<div class="clear-style-btn menu-btn" :disabled="disabled" @click="clearstyle">
|
||||
<span class="tab-icons" />
|
||||
<span class="label">
|
||||
{{ t('minder.menu.style.clear') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="copy-paste-panel" @click="copystyle">
|
||||
<div class="copy-style menu-btn" :disabled="disabled">
|
||||
<span class="tab-icons" />
|
||||
<span class="label">
|
||||
{{ t('minder.menu.style.copy') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="paste-style menu-btn" :disabled="disabled" @click="pastestyle">
|
||||
<span class="tab-icons" />
|
||||
<span class="label">
|
||||
{{ t('minder.menu.style.paste') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="StyleOperation" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
let minder = reactive<any>({});
|
||||
const disabled = ref(true);
|
||||
|
||||
function checkDisabled() {
|
||||
try {
|
||||
if (Object.keys(minder).length === 0) return false;
|
||||
} catch (e) {
|
||||
// 如果window的还没挂载minder,先捕捉undefined异常
|
||||
return false;
|
||||
}
|
||||
const nodes = minder.getSelectedNodes && minder.getSelectedNodes();
|
||||
disabled.value = nodes === null || nodes.length === 0;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
minder = window.minder;
|
||||
minder.on('selectionchange', () => {
|
||||
checkDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function clearstyle() {
|
||||
if (minder.queryCommandState && minder.execCommand && minder.queryCommandState('clearstyle') !== -1) {
|
||||
minder.execCommand('clearstyle');
|
||||
}
|
||||
}
|
||||
function copystyle() {
|
||||
if (minder.queryCommandState && minder.execCommand && minder.queryCommandState('copystyle') !== -1) {
|
||||
minder.execCommand('copystyle');
|
||||
}
|
||||
}
|
||||
function pastestyle() {
|
||||
if (minder.queryCommandState && minder.execCommand && minder.queryCommandState('pastestyle') !== -1) {
|
||||
minder.execCommand('pastestyle');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.mold-dropdown-list .mold-icons,
|
||||
.mold-icons {
|
||||
background-image: url('@/assets/images/minder/mold.png');
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
|
@ -1,39 +0,0 @@
|
|||
<template>
|
||||
<div class="menu-container">
|
||||
<mold v-if="moldEnable" :default-mold="props.defaultMold" @mold-change="handleMoldChange" />
|
||||
<arrange v-if="arrangeEnable" />
|
||||
<style-operation v-if="styleEnable" />
|
||||
<font-operation v-if="fontEnable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="viewMenu" setup>
|
||||
import arrange from './arrange.vue';
|
||||
import fontOperation from './fontOperation.vue';
|
||||
import mold from './mold.vue';
|
||||
import styleOperation from './styleOperation.vue';
|
||||
|
||||
import { moleProps, viewMenuProps } from '../../props';
|
||||
|
||||
const props = defineProps({ ...moleProps, ...viewMenuProps });
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'moldChange', data: number): void;
|
||||
}>();
|
||||
|
||||
function handleMoldChange(data: number) {
|
||||
emit('moldChange', data);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.menu-container {
|
||||
height: 60px;
|
||||
i {
|
||||
@apply inline-block;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -37,6 +37,7 @@
|
|||
import useMinderOperation from './hooks/useMinderOperation';
|
||||
import useShortCut from './hooks/useShortCut';
|
||||
import {
|
||||
batchMenuProps,
|
||||
delProps,
|
||||
editMenuProps,
|
||||
floatMenuProps,
|
||||
|
@ -50,13 +51,14 @@
|
|||
tagProps,
|
||||
viewMenuProps,
|
||||
} from './props';
|
||||
import { isNodeInMinderView, setPriorityView } from './script/tool/utils';
|
||||
import { isNodeInMinderView } from './script/tool/utils';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'moldChange', data: number): void;
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
(e: 'afterMount'): void;
|
||||
(e: 'nodeSelect', data: MinderJsonNode): void;
|
||||
(e: 'nodeBatchSelect', data: MinderJsonNode[]): void;
|
||||
(e: 'contentChange', data?: MinderJsonNode): void;
|
||||
(e: 'action', event: MinderCustomEvent): void;
|
||||
(e: 'beforeExecCommand', event: MinderEvent): void;
|
||||
|
@ -74,6 +76,7 @@
|
|||
...tagProps,
|
||||
...delProps,
|
||||
...viewMenuProps,
|
||||
...batchMenuProps,
|
||||
});
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
|
@ -115,11 +118,7 @@
|
|||
emit('save', data, callback);
|
||||
}
|
||||
|
||||
const { appendChildNode, appendSiblingNode, minderDelete } = useMinderOperation({
|
||||
insertNode: props.insertNode,
|
||||
canShowMoreMenuNodeOperation: props.canShowMoreMenuNodeOperation,
|
||||
canShowPasteMenu: props.canShowPasteMenu,
|
||||
});
|
||||
const { appendChildNode, appendSiblingNode, minderDelete, minderExpand } = useMinderOperation(props);
|
||||
const { unbindShortcuts } = useShortCut(
|
||||
{
|
||||
undo: () => {
|
||||
|
@ -135,26 +134,17 @@
|
|||
}
|
||||
},
|
||||
delete: () => {
|
||||
if (props.canShowMoreMenuNodeOperation && !props.disabled) {
|
||||
if (
|
||||
(props.canShowDeleteMenu || (props.canShowMoreMenu && props.canShowMoreMenuNodeOperation)) &&
|
||||
!props.disabled
|
||||
) {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
minderDelete(selectedNodes);
|
||||
}
|
||||
},
|
||||
expand: () => {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (selectedNodes.every((node) => node.isExpanded())) {
|
||||
// 选中的节点集合全部展开,则全部收起
|
||||
window.minder.execCommand('Collapse');
|
||||
minderStore.dispatchEvent(MinderEventName.COLLAPSE, undefined, undefined, undefined, selectedNodes);
|
||||
} else {
|
||||
// 选中的节点集合中有一个节点未展开,则全部展开
|
||||
window.minder.execCommand('Expand');
|
||||
if (!props.customPriority) {
|
||||
// 展开后,需要设置一次优先级展示,避免展开后优先级显示成脑图内置文案;如果设置了自定义优先级,则不在此设置,由外部自行处理
|
||||
setPriorityView(props.priorityStartWithZero, props.priorityPrefix);
|
||||
}
|
||||
minderStore.dispatchEvent(MinderEventName.EXPAND, undefined, undefined, undefined, selectedNodes);
|
||||
}
|
||||
minderExpand(selectedNodes);
|
||||
},
|
||||
appendChildNode: () => {
|
||||
if (props.insertSonMenus.length > 0 || props.insertNode) {
|
||||
|
@ -169,17 +159,18 @@
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
insertNode: props.insertNode,
|
||||
canShowMoreMenuNodeOperation: props.canShowMoreMenuNodeOperation,
|
||||
canShowPasteMenu: props.canShowPasteMenu,
|
||||
}
|
||||
props
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
window.minderProps = props;
|
||||
useMinderEventListener({
|
||||
handleSelectionChange: (node?: MinderJsonNode) => {
|
||||
handleSelectionChange: (nodes: MinderJsonNode[]) => {
|
||||
if (nodes && nodes.length > 1) {
|
||||
emit('nodeBatchSelect', nodes);
|
||||
return;
|
||||
}
|
||||
const node = nodes[0];
|
||||
if (node) {
|
||||
emit('nodeSelect', node);
|
||||
const box = node.getRenderBox();
|
||||
|
|
|
@ -102,6 +102,12 @@ export const priorityProps = {
|
|||
default: '',
|
||||
},
|
||||
};
|
||||
export 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))',
|
||||
};
|
||||
|
||||
export interface MinderReplaceTag {
|
||||
tags: string[];
|
||||
|
@ -133,7 +139,7 @@ export const tagProps = {
|
|||
afterTagEdit: Function as PropType<(nodes: MinderJsonNode[], tag: string) => void>,
|
||||
};
|
||||
|
||||
export interface InsertMenuItem {
|
||||
export interface MinderMenuItem {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
@ -146,14 +152,14 @@ export interface MoreMenuOtherOperationItem {
|
|||
export const floatMenuProps = {
|
||||
// 插入同级选项
|
||||
insertSiblingMenus: {
|
||||
type: Array as PropType<InsertMenuItem[]>,
|
||||
type: Array as PropType<MinderMenuItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
// 插入子级选项
|
||||
insertSonMenus: {
|
||||
type: Array as PropType<InsertMenuItem[]>,
|
||||
type: Array as PropType<MinderMenuItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
|
@ -210,6 +216,31 @@ export const floatMenuProps = {
|
|||
default: false,
|
||||
},
|
||||
};
|
||||
export const batchMenuProps = {
|
||||
canShowMoreBatchMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canShowBatchCopy: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canShowBatchCut: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canShowBatchDelete: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canShowBatchExpand: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customBatchExpand: {
|
||||
type: Function as PropType<(node: MinderJsonNode) => void>,
|
||||
},
|
||||
};
|
||||
|
||||
export const editMenuProps = {
|
||||
sequenceEnable: {
|
||||
|
|
|
@ -201,4 +201,5 @@ export default {
|
|||
'common.jump': 'Jump',
|
||||
'common.gotIt': 'Got it',
|
||||
'common.inputPleaseEnterTags': 'Please enter the update tag enter add, not more than 64 characters',
|
||||
'common.cutSuccess': 'Cut successfully',
|
||||
};
|
||||
|
|
|
@ -200,4 +200,5 @@ export default {
|
|||
'common.jump': '跳转',
|
||||
'common.gotIt': '知道了',
|
||||
'common.inputPleaseEnterTags': '请输入更新标签回车添加,不得超过64字符',
|
||||
'common.cutSuccess': '剪切成功',
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
'login.form.title': 'Modern, open-source test management and interface testing tools',
|
||||
'login.form.title': 'Open Source Continuous Testing Tools',
|
||||
'login.form.userName.errMsg': 'Username cannot be empty',
|
||||
'login.form.password.errMsg': 'Password cannot be empty',
|
||||
'login.form.login.errMsg': 'Login error, refresh and try again',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
'login.form.title': '现代化、开源的测试管理和接口测试工具',
|
||||
'login.form.title': '开源持续测试工具',
|
||||
'login.form.userName.errMsg': '用户名不能为空',
|
||||
'login.form.password.errMsg': '密码不能为空',
|
||||
'login.form.login.errMsg': '登录出错,请刷新重试',
|
||||
|
|
Loading…
Reference in New Issue