feat: 添加右键菜单功能,优化机器人列表交互,增强用户体验

This commit is contained in:
xudan 2025-09-11 15:07:44 +08:00
parent fe1ef44e9d
commit c32dd105ef
2 changed files with 61 additions and 31 deletions

View File

@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { type RobotGroup, type RobotInfo, seizeRobotByIds } from '@api/robot'; import { type RobotGroup, type RobotInfo } from '@api/robot';
import type { RobotAddModalRef } from '@common/modal/robot-add-modal.vue'; import type { RobotAddModalRef } from '@common/modal/robot-add-modal.vue';
import type { RobotGroupRenameModalRef } from '@common/modal/robot-group-rename-modal.vue'; import type { RobotGroupRenameModalRef } from '@common/modal/robot-group-rename-modal.vue';
import type { RobotRegisterModalRef } from '@common/modal/robot-register-modal.vue'; import type { RobotRegisterModalRef } from '@common/modal/robot-register-modal.vue';
import type { EditorService } from '@core/editor.service'; import type { EditorService } from '@core/editor.service';
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
import { watch } from 'vue'; import { computed, inject, type InjectionKey, reactive, ref, type ShallowRef, shallowRef, watch } from 'vue';
import { reactive } from 'vue';
import { computed, inject, type InjectionKey, ref, type ShallowRef, shallowRef } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { createContextMenuManager, handleContextMenu } from '../services/context-menu';
import ContextMenu from './context-menu/context-menu.vue';
type Props = { type Props = {
token: InjectionKey<ShallowRef<EditorService>>; token: InjectionKey<ShallowRef<EditorService>>;
@ -27,15 +27,25 @@ type Events = {
const emit = defineEmits<Events>(); const emit = defineEmits<Events>();
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); // const router = useRouter(); // 使
//#region //#region
const seizeRobots = async () => { const contextMenuManager = createContextMenuManager();
const res = await seizeRobotByIds([...selected.keys()]); const contextMenuState = ref(contextMenuManager.getState());
editor.value.updateRobots(res, { canControl: true });
//
contextMenuManager.subscribe((state) => {
contextMenuState.value = state;
});
//
const handleContextMenuClose = () => {
contextMenuManager.close();
}; };
//#endregion //#endregion
const keyword = ref<string>(''); const keyword = ref<string>('');
//#region //#region
@ -75,6 +85,22 @@ const selectRobot = (id: RobotInfo['id'], checked: boolean) => {
selected.delete(id); selected.delete(id);
} }
}; };
//
const handleRobotRightClick = (event: MouseEvent, robot: RobotInfo) => {
//
if (event.target && event.target instanceof HTMLElement) {
event.target.setAttribute('data-robot-id', robot.id);
event.target.setAttribute('data-robot-type', 'robot');
}
// - handleContextMenu preventDefault stopPropagation
handleContextMenu(event, contextMenuManager, {
robotService: {
getRobotById: (id: string) => editor.value.getRobotById(id)
}
});
};
//#endregion //#endregion
//#region //#region
@ -92,16 +118,6 @@ const toDeleteGroup = (id: RobotGroup['id']) =>
onOk: () => editor.value.deleteRobotGroup(id), onOk: () => editor.value.deleteRobotGroup(id),
}); });
const toEditGroup = (id: RobotGroup['id']) =>
Modal.confirm({
class: 'confirm',
title: t('您确定要编辑该机器人组吗?'),
content: t('请确保当前场景已经保存,否则将导致当前场景数据丢失。'),
centered: true,
cancelText: t('取消'),
okText: t('编辑'),
onOk: () => router.push({ name: '组编辑', params: { sid: props.sid, id } }),
});
//#endregion //#endregion
//#region //#region
@ -137,6 +153,16 @@ const toRemoveRobots = () =>
<RobotAddModal ref="refAddRobot" :token="token" /> <RobotAddModal ref="refAddRobot" :token="token" />
<RobotRegisterModal ref="refRegisterRobot" :token="token" /> <RobotRegisterModal ref="refRegisterRobot" :token="token" />
<RobotGroupRenameModal ref="refRenameGroup" :token="token" /> <RobotGroupRenameModal ref="refRenameGroup" :token="token" />
<!-- 右键菜单 -->
<ContextMenu
:visible="contextMenuState.visible"
:x="contextMenuState.x"
:y="contextMenuState.y"
:menu-type="contextMenuState.menuType"
:robot-info="contextMenuState.robotInfo"
@close="handleContextMenuClose"
/>
<a-flex class="full" vertical> <a-flex class="full" vertical>
<a-input class="search mb-16" :placeholder="$t('请输入搜索关键字')" v-model:value="keyword"> <a-input class="search mb-16" :placeholder="$t('请输入搜索关键字')" v-model:value="keyword">
@ -148,16 +174,6 @@ const toRemoveRobots = () =>
<a-flex v-if="editable" class="mb-8" style="height: 32px" justify="space-between" align="center"> <a-flex v-if="editable" class="mb-8" style="height: 32px" justify="space-between" align="center">
<a-checkbox :checked="isAllSelected" @change="selectAll($event.target.checked)">{{ $t('全选') }}</a-checkbox> <a-checkbox :checked="isAllSelected" @change="selectAll($event.target.checked)">{{ $t('全选') }}</a-checkbox>
<a-space align="center"> <a-space align="center">
<a-button
v-if="false"
class="icon-btn panel-btn"
size="small"
:title="$t('抢占控制权')"
@click="seizeRobots"
:disabled="!selected.size"
>
<i class="mask control" />
</a-button>
<a-button <a-button
class="icon-btn panel-btn" class="icon-btn panel-btn"
size="small" size="small"
@ -241,6 +257,7 @@ const toRemoveRobots = () =>
:class="{ 'ph-16': !editable, 'pl-12': editable, 'pr-8': editable, selected: item.id === current }" :class="{ 'ph-16': !editable, 'pl-12': editable, 'pr-8': editable, selected: item.id === current }"
style="height: 36px" style="height: 36px"
@click="emit('change', item.id)" @click="emit('change', item.id)"
@contextmenu="handleRobotRightClick($event, item)"
> >
<template v-if="editable" #actions> <template v-if="editable" #actions>
<a-button <a-button
@ -283,4 +300,14 @@ const toRemoveRobots = () =>
visibility: visible; visibility: visible;
} }
} }
//
.ant-list-item {
cursor: pointer;
transition: background-color 0.2s ease;
&:hover {
background-color: #f5f5f5;
}
}
</style> </style>

View File

@ -165,7 +165,10 @@ export function parseEventData(event: MouseEvent | PointerEvent): ParsedEventDat
} }
// 回退到DOM元素检查 // 回退到DOM元素检查
if (target?.closest('.robot-item')) { // 检查机器人类型 - 支持多种选择器
if (target?.closest('.robot-item') ||
target?.closest('.ant-list-item') && target.dataset.robotType === 'robot' ||
target?.classList.contains('ant-list-item') && target.dataset.robotType === 'robot') {
return { return {
type: 'robot', type: 'robot',
id: target.dataset.robotId || target.id, id: target.dataset.robotId || target.id,