feat(脑图): 脑图快捷键屏蔽&脑图拖拽拦截&部分样式调整 (#31248)

Co-authored-by: baiqi <qi.bai@fit2cloud.com>
This commit is contained in:
MeterSphere Bot 2024-06-03 21:58:34 +08:00 committed by GitHub
parent fdafb1236a
commit 800fcdb2ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 100 additions and 53 deletions

View File

@ -469,7 +469,6 @@
/** radio **/ /** radio **/
.arco-radio-group-button { .arco-radio-group-button {
padding: 1px;
background-color: var(--color-text-n8); background-color: var(--color-text-n8);
.arco-radio-button { .arco-radio-button {
@apply bg-transparent; @apply bg-transparent;

View File

@ -16,6 +16,7 @@
@content-change="handleContentChange" @content-change="handleContentChange"
@node-select="handleNodeSelect" @node-select="handleNodeSelect"
@action="handleAction" @action="handleAction"
@before-exec-command="handleBeforeExecCommand"
@save="handleMinderSave" @save="handleMinderSave"
> >
<template #extractTabContent> <template #extractTabContent>
@ -44,7 +45,12 @@
import { FormItem } from '@/components/pure/ms-form-create/types'; import { FormItem } from '@/components/pure/ms-form-create/types';
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue'; import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props'; import type {
MinderEvent,
MinderJson,
MinderJsonNode,
MinderJsonNodeData,
} from '@/components/pure/ms-minder-editor/props';
import { MsFileItem } from '@/components/pure/ms-upload/types'; import { MsFileItem } from '@/components/pure/ms-upload/types';
import attachment from './attachment.vue'; import attachment from './attachment.vue';
import baseInfo from './basInfo.vue'; import baseInfo from './basInfo.vue';
@ -60,7 +66,7 @@
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { MinderEvent } from '@/store/modules/components/minder-editor/types'; import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
import { filterTree, getGenerateId, mapTree } from '@/utils'; import { filterTree, getGenerateId, mapTree } from '@/utils';
import { import {
@ -805,7 +811,7 @@
* 处理脑图节点操作 * 处理脑图节点操作
* @param event 脑图事件对象 * @param event 脑图事件对象
*/ */
function handleAction(event: MinderEvent) { function handleAction(event: MinderCustomEvent) {
const { node, name } = event; const { node, name } = event;
if (node) { if (node) {
switch (name) { switch (name) {
@ -831,6 +837,13 @@
} }
} }
} }
function handleBeforeExecCommand(event: MinderEvent) {
if (event.commandName === 'movetoparent') {
// TODO:
event.stopPropagation();
}
}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -83,9 +83,8 @@
class="list-item-action flex flex-row items-center gap-[8px] opacity-0" class="list-item-action flex flex-row items-center gap-[8px] opacity-0"
:class="{ '!opacity-100': element.id === currentId }" :class="{ '!opacity-100': element.id === currentId }"
> >
<div class="icon-button"> <div v-if="element.type === systemType" class="icon-button">
<MsIcon <MsIcon
v-if="element.type === systemType"
v-permission="props.updatePermission" v-permission="props.updatePermission"
type="icon-icon_add_outlined" type="icon-icon_add_outlined"
size="16" size="16"
@ -213,9 +212,8 @@
class="list-item-action flex flex-row items-center gap-[8px] opacity-0" class="list-item-action flex flex-row items-center gap-[8px] opacity-0"
:class="{ '!opacity-100': element.id === currentId }" :class="{ '!opacity-100': element.id === currentId }"
> >
<div class="icon-button"> <div v-if="element.type === systemType" class="icon-button">
<MsIcon <MsIcon
v-if="element.type === systemType"
v-permission="props.updatePermission" v-permission="props.updatePermission"
type="icon-icon_add_outlined" type="icon-icon_add_outlined"
size="16" size="16"
@ -317,9 +315,8 @@
class="list-item-action flex flex-row items-center gap-[8px] opacity-0" class="list-item-action flex flex-row items-center gap-[8px] opacity-0"
:class="{ '!opacity-100': element.id === currentId }" :class="{ '!opacity-100': element.id === currentId }"
> >
<div class="icon-button"> <div v-if="element.type === systemType" class="icon-button">
<MsIcon <MsIcon
v-if="element.type === systemType"
v-permission="props.updatePermission" v-permission="props.updatePermission"
type="icon-icon_add_outlined" type="icon-icon_add_outlined"
size="16" size="16"

View File

@ -1,14 +1,15 @@
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
import useMinderStore from '@/store/modules/components/minder-editor/index'; import useMinderStore from '@/store/modules/components/minder-editor/index';
import type { MinderEvent } from '@/store/modules/components/minder-editor/types'; import type { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
import type { MinderJsonNode } from '../props'; import type { MinderEvent, MinderJsonNode } from '../props';
export interface UseEventListenerProps { export interface UseEventListenerProps {
handleContentChange?: (node: MinderJsonNode) => void; handleContentChange?: (node: MinderJsonNode) => void;
handleSelectionChange?: (node: MinderJsonNode) => void; handleSelectionChange?: (node: MinderJsonNode) => void;
handleMinderEvent?: (event: MinderEvent) => void; handleMinderEvent?: (event: MinderCustomEvent) => void;
handleBeforeExecCommand?: (event: MinderEvent) => void;
} }
export default function useEventListener(listener: UseEventListenerProps) { export default function useEventListener(listener: UseEventListenerProps) {
@ -34,6 +35,21 @@ export default function useEventListener(listener: UseEventListenerProps) {
}, 300) }, 300)
); );
// minder.on('dragStart', () => {
// const node: MinderJsonNode = minder.getSelectedNode();
// console.log('dragStart', node);
// });
// minder.on('dragFinish', () => {
// console.log('dragFinish', minder.history);
// });
minder.on('beforeExecCommand', (e: any) => {
if (listener.handleBeforeExecCommand) {
listener.handleBeforeExecCommand(e);
}
});
// 监听脑图自定义事件 // 监听脑图自定义事件
watch( watch(
() => minderStore.event.timestamp, () => minderStore.event.timestamp,

View File

@ -67,15 +67,16 @@
import minderHeader from './main/header.vue'; import minderHeader from './main/header.vue';
import mainEditor from './main/mainEditor.vue'; import mainEditor from './main/mainEditor.vue';
import { MinderEvent } from '@/store/modules/components/minder-editor/types'; import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
import useEventListener from './hooks/useEventListener'; import useMinderEventListener from './hooks/useMinderEventListener';
import { import {
delProps, delProps,
editMenuProps, editMenuProps,
headerProps, headerProps,
insertProps, insertProps,
mainEditorProps, mainEditorProps,
MinderEvent,
MinderJsonNode, MinderJsonNode,
moleProps, moleProps,
priorityProps, priorityProps,
@ -90,7 +91,8 @@
(e: 'enterNode', data: MinderJsonNode): void; (e: 'enterNode', data: MinderJsonNode): void;
(e: 'nodeSelect', data: MinderJsonNode): void; (e: 'nodeSelect', data: MinderJsonNode): void;
(e: 'contentChange', data: MinderJsonNode): void; (e: 'contentChange', data: MinderJsonNode): void;
(e: 'action', event: MinderEvent): void; (e: 'action', event: MinderCustomEvent): void;
(e: 'beforeExecCommand', event: MinderEvent): void;
}>(); }>();
const props = defineProps({ const props = defineProps({
@ -132,7 +134,7 @@
} }
onMounted(() => { onMounted(() => {
useEventListener({ useMinderEventListener({
handleSelectionChange: () => { handleSelectionChange: () => {
const selectedNode: MinderJsonNode = window.minder.getSelectedNode(); const selectedNode: MinderJsonNode = window.minder.getSelectedNode();
if (Object.keys(window.minder).length > 0 && selectedNode) { if (Object.keys(window.minder).length > 0 && selectedNode) {
@ -145,6 +147,9 @@
handleMinderEvent: (event) => { handleMinderEvent: (event) => {
emit('action', event); emit('action', event);
}, },
handleBeforeExecCommand: (event) => {
emit('beforeExecCommand', event);
},
}); });
}); });
</script> </script>

View File

@ -6,6 +6,19 @@ import type { MoveMode } from '@/models/common';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
export interface MinderClass {
stopPropagation: () => void; // 阻止事件冒泡
stopPropagationImmediately: () => void; // 立即阻止事件冒泡
[key: string]: any; // TODO: 其他事件属性
}
// TODO:脑图事件类型补充
export interface MinderEvent extends MinderClass {
command: any;
commandArgs: Record<string, any>[];
commandName: string;
minder: any;
type: string;
}
export interface MinderIconButtonItem { export interface MinderIconButtonItem {
icon: string; icon: string;
tooltip: string; tooltip: string;

View File

@ -22,11 +22,13 @@ function createDragRuntime(this: DragRuntimeOptions) {
// when jumped to drag mode, enter // when jumped to drag mode, enter
fsm.when('* -> drag', () => { fsm.when('* -> drag', () => {
// now is drag mode // now is drag mode
minder.fire('dragStart');
}); });
fsm.when('drag -> *', (exit: any, enter: any, reason: string) => { fsm.when('drag -> *', (exit: any, enter: any, reason: string) => {
if (reason === 'drag-finish') { if (reason === 'drag-finish') {
// now exit drag mode // now exit drag mode
minder.fire('dragFinish');
} }
}); });
} }

View File

@ -13,14 +13,6 @@ function handlerConditionMatch(condition: any, when: string, exit: string, enter
return true; return true;
} }
type Handler = () => void & {
condition: {
when: string;
exit: string;
enter: string;
};
};
class FSM { class FSM {
private currentState: string; private currentState: string;

View File

@ -180,6 +180,7 @@ export default function HistoryRuntime(this: { minder: any; hotbox: any; editTex
hasUndo, hasUndo,
hasRedo, hasRedo,
}; };
window.minderHistory = this.history;
reset(); reset();
minder.on('contentchange', changed); minder.on('contentchange', changed);
minder.on('import', reset); minder.on('import', reset);

View File

@ -7,7 +7,7 @@ export interface MinderNodePosition {
y: number; y: number;
} }
export interface MinderEvent { export interface MinderCustomEvent {
name: MinderEventName; name: MinderEventName;
timestamp: number; timestamp: number;
nodePosition?: MinderNodePosition; nodePosition?: MinderNodePosition;
@ -16,6 +16,6 @@ export interface MinderEvent {
} }
export interface MinderState { export interface MinderState {
event: MinderEvent; event: MinderCustomEvent;
mold: number; mold: number;
} }

View File

@ -4,9 +4,9 @@
class="mb-[16px] flex items-center" class="mb-[16px] flex items-center"
:class="{ 'justify-between': hasAddPermission, 'justify-end': !hasAddPermission }" :class="{ 'justify-between': hasAddPermission, 'justify-end': !hasAddPermission }"
> >
<a-button v-permission="['ORGANIZATION_PROJECT:READ+ADD']" type="primary" @click="showAddProject">{{ <a-button v-permission="['ORGANIZATION_PROJECT:READ+ADD']" type="primary" @click="showAddProject">
t('system.organization.createProject') {{ t('system.organization.createProject') }}
}}</a-button> </a-button>
<a-input-search <a-input-search
v-model="keyword" v-model="keyword"
:placeholder="t('system.organization.searchIndexPlaceholder')" :placeholder="t('system.organization.searchIndexPlaceholder')"
@ -22,15 +22,16 @@
<a-tooltip class="tooltip-white"> <a-tooltip class="tooltip-white">
<template #content> <template #content>
<div class="flex flex-row"> <div class="flex flex-row">
<span class="text-[var(--color-text-1)]">{{ <span class="text-[var(--color-text-1)]">
t('system.project.revokeDeleteToolTip', { count: record.remainDayCount }) {{ t('system.project.revokeDeleteToolTip', { count: record.remainDayCount }) }}
}}</span> </span>
<MsButton <MsButton
v-if="hasAnyPermission(['ORGANIZATION_PROJECT:READ+RECOVER'])" v-if="hasAnyPermission(['ORGANIZATION_PROJECT:READ+RECOVER'])"
class="ml-[8px]" class="ml-[8px]"
@click="handleRevokeDelete(record)" @click="handleRevokeDelete(record)"
>{{ t('common.revokeDelete') }}</MsButton
> >
{{ t('common.revokeDelete') }}
</MsButton>
</div> </div>
</template> </template>
<MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" /> <MsIcon v-if="record.deleted" type="icon-icon_alarm_clock" class="ml-[4px] text-[rgb(var(--danger-6))]" />
@ -44,34 +45,35 @@
v-if="hasAnyPermission(['ORGANIZATION_PROJECT:READ+ADD_MEMBER', 'ORGANIZATION_PROJECT:READ'])" v-if="hasAnyPermission(['ORGANIZATION_PROJECT:READ+ADD_MEMBER', 'ORGANIZATION_PROJECT:READ'])"
class="cursor-pointer text-[rgb(var(--primary-5))]" class="cursor-pointer text-[rgb(var(--primary-5))]"
@click="showUserDrawer(record)" @click="showUserDrawer(record)"
>{{ record.memberCount }}</span
> >
{{ record.memberCount }}
</span>
<span v-else>{{ record.memberCount }}</span> <span v-else>{{ record.memberCount }}</span>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<template v-if="record.deleted"> <template v-if="record.deleted">
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+RECOVER']" @click="handleRevokeDelete(record)">{{ <MsButton v-permission="['ORGANIZATION_PROJECT:READ+RECOVER']" @click="handleRevokeDelete(record)">
t('common.revokeDelete') {{ t('common.revokeDelete') }}
}}</MsButton> </MsButton>
</template> </template>
<template v-else-if="!record.enable"> <template v-else-if="!record.enable">
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="handleEnableOrDisableProject(record)">{{ <MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="handleEnableOrDisableProject(record)">
t('common.enable') {{ t('common.enable') }}
}}</MsButton> </MsButton>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+DELETE']" @click="handleDelete(record)">{{ <MsButton v-permission="['ORGANIZATION_PROJECT:READ+DELETE']" @click="handleDelete(record)">
t('common.delete') {{ t('common.delete') }}
}}</MsButton> </MsButton>
</template> </template>
<template v-else> <template v-else>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="showAddProjectModal(record)">{{ <MsButton v-permission="['ORGANIZATION_PROJECT:READ+UPDATE']" @click="showAddProjectModal(record)">
t('common.edit') {{ t('common.edit') }}
}}</MsButton> </MsButton>
<MsButton v-permission="['ORGANIZATION_PROJECT:READ+ADD_MEMBER']" @click="showAddUserModal(record)">{{ <MsButton v-permission="['ORGANIZATION_PROJECT:READ+ADD_MEMBER']" @click="showAddUserModal(record)">
t('system.organization.addMember') {{ t('system.organization.addMember') }}
}}</MsButton> </MsButton>
<MsButton v-permission="['PROJECT_BASE_INFO:READ']" @click="enterProject(record.id)">{{ <MsButton v-permission="['PROJECT_BASE_INFO:READ']" @click="enterProject(record.id)">
t('system.project.enterProject') {{ t('system.project.enterProject') }}
}}</MsButton> </MsButton>
<MsTableMoreAction <MsTableMoreAction
v-permission="['ORGANIZATION_PROJECT:READ+DELETE']" v-permission="['ORGANIZATION_PROJECT:READ+DELETE']"
:list="tableActions" :list="tableActions"

View File

@ -46,6 +46,13 @@ declare interface Window {
minderEditor: Record<string, any>; minderEditor: Record<string, any>;
km: Record<string, any>; km: Record<string, any>;
canvg: (canvas: HTMLCanvasElement, xml: string, option: Record<string, any>) => void; canvg: (canvas: HTMLCanvasElement, xml: string, option: Record<string, any>) => void;
minderHistory: {
reset: () => void;
undo: () => void;
redo: () => void;
hasUndo: () => boolean;
hasRedo: () => boolean;
};
DTFrameLogin: ( DTFrameLogin: (
frameParams: IDTLoginFrameParams, // DOM包裹容器相关参数 frameParams: IDTLoginFrameParams, // DOM包裹容器相关参数
loginParams: IDTLoginLoginParams, // 统一登录参数 loginParams: IDTLoginLoginParams, // 统一登录参数