feat: 在上下文菜单中添加拖拽功能,优化菜单样式并更新依赖项以提升用户体验

This commit is contained in:
xudan 2025-09-23 18:14:49 +08:00
parent e347a6f633
commit aa568b2da4
4 changed files with 107 additions and 63 deletions

View File

@ -11,6 +11,7 @@
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@meta2d/core": "^1.0.78",
"@vueuse/core": "^13.9.0",
"@vueuse/rxjs": "^13.1.0",
"ant-design-vue": "^4.2.6",
"axios": "^1.8.4",

34
pnpm-lock.yaml generated
View File

@ -14,6 +14,9 @@ importers:
'@meta2d/core':
specifier: ^1.0.78
version: 1.0.95
'@vueuse/core':
specifier: ^13.9.0
version: 13.9.0(vue@3.5.20(typescript@5.7.3))
'@vueuse/rxjs':
specifier: ^13.1.0
version: 13.7.0(rxjs@7.8.2)(vue@3.5.20(typescript@5.7.3))
@ -669,6 +672,9 @@ packages:
'@types/node@22.18.0':
resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@typescript-eslint/eslint-plugin@8.41.0':
resolution: {integrity: sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -807,6 +813,14 @@ packages:
vue:
optional: true
'@vueuse/core@13.9.0':
resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==}
peerDependencies:
vue: ^3.5.0
'@vueuse/metadata@13.9.0':
resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==}
'@vueuse/rxjs@13.7.0':
resolution: {integrity: sha512-PawsGxhmv7yvp2dhqtu1plsaMdMRQ/2TiYCJHIMTG+RFP/761q0+uVKNOhmuaExhj5bdAk23FhQ9Cl9Sn67RUQ==}
peerDependencies:
@ -818,6 +832,11 @@ packages:
peerDependencies:
vue: ^3.5.0
'@vueuse/shared@13.9.0':
resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==}
peerDependencies:
vue: ^3.5.0
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -2684,6 +2703,8 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/web-bluetooth@0.0.21': {}
'@typescript-eslint/eslint-plugin@8.41.0(@typescript-eslint/parser@8.41.0(eslint@9.34.0)(typescript@5.7.3))(eslint@9.34.0)(typescript@5.7.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@ -2891,6 +2912,15 @@ snapshots:
typescript: 5.7.3
vue: 3.5.20(typescript@5.7.3)
'@vueuse/core@13.9.0(vue@3.5.20(typescript@5.7.3))':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 13.9.0
'@vueuse/shared': 13.9.0(vue@3.5.20(typescript@5.7.3))
vue: 3.5.20(typescript@5.7.3)
'@vueuse/metadata@13.9.0': {}
'@vueuse/rxjs@13.7.0(rxjs@7.8.2)(vue@3.5.20(typescript@5.7.3))':
dependencies:
'@vueuse/shared': 13.7.0(vue@3.5.20(typescript@5.7.3))
@ -2901,6 +2931,10 @@ snapshots:
dependencies:
vue: 3.5.20(typescript@5.7.3)
'@vueuse/shared@13.9.0(vue@3.5.20(typescript@5.7.3))':
dependencies:
vue: 3.5.20(typescript@5.7.3)
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0

View File

@ -1,51 +1,39 @@
<template>
<a-dropdown
v-if="visible"
:open="visible"
:trigger="[]"
:placement="dropdownPlacement"
:get-popup-container="getPopupContainer"
@open-change="handleOpenChange"
:z-index="998"
>
<div ref="triggerRef" class="context-menu-trigger" :style="triggerStyle" />
<template #overlay>
<div class="context-menu-overlay">
<div class="context-menu-header">
<div class="menu-title">{{ headerTitle }}</div>
<div class="close-button" @click="handleCloseMenu" title="关闭菜单">
<span class="close-icon">×</span>
</div>
</div>
<!-- 库位菜单 -->
<StorageMenu
v-if="menuType === 'storage-background' && storageLocations?.length"
:storage-locations="storageLocations"
:menu-x="x"
:menu-y="y"
:main-menu-width="mainMenuWidth"
@action-complete="handleActionComplete"
/>
<!-- 机器人菜单 -->
<RobotMenu
v-else-if="menuType === 'robot' && robotId"
:robot-id="robotId"
@action-complete="handleActionComplete"
@custom-image="handleCustomImage"
/>
<!-- 默认菜单 -->
<DefaultMenu v-else @action-complete="handleActionComplete" />
<div v-if="visible" ref="menuRef" class="context-menu-overlay" :style="style">
<div ref="handleRef" class="context-menu-header">
<div class="menu-title">{{ headerTitle }}</div>
<div class="close-button" @click="handleCloseMenu" title="关闭菜单">
<span class="close-icon">×</span>
</div>
</template>
</a-dropdown>
</div>
<!-- 库位菜单 -->
<StorageMenu
v-if="menuType === 'storage-background' && storageLocations?.length"
:storage-locations="storageLocations"
:menu-x="x"
:menu-y="y"
:main-menu-width="mainMenuWidth"
@action-complete="handleActionComplete"
/>
<!-- 机器人菜单 -->
<RobotMenu
v-else-if="menuType === 'robot' && robotId"
:robot-id="robotId"
@action-complete="handleActionComplete"
@custom-image="handleCustomImage"
/>
<!-- 默认菜单 -->
<DefaultMenu v-else @action-complete="handleActionComplete" />
</div>
</template>
<script setup lang="ts">
import { useDraggable } from '@vueuse/core';
import { message } from 'ant-design-vue';
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import type { StorageLocationInfo } from '../../services/context-menu';
import DefaultMenu from './default-menu.vue';
@ -77,6 +65,24 @@ const props = withDefaults(defineProps<Props>(), {
});
const emit = defineEmits<Emits>();
//
const menuRef = ref<HTMLElement | null>(null);
const handleRef = ref<HTMLElement | null>(null);
const { x, y, style } = useDraggable(menuRef, {
handle: handleRef,
});
watch(
() => props.visible,
(isVisible) => {
if (isVisible) {
x.value = props.x;
y.value = props.y;
}
},
);
//
defineOptions({
name: 'ContextMenu',
@ -202,6 +208,7 @@ const handleCloseMenu = () => {
/* 下拉菜单覆盖层样式 */
.context-menu-overlay {
position: fixed;
background: white;
border: 1px solid #d9d9d9;
border-radius: 6px;
@ -211,6 +218,7 @@ const handleCloseMenu = () => {
user-select: none;
color: #000;
pointer-events: auto;
z-index: 1000;
}
/* 黑色主题 */
@ -228,6 +236,7 @@ const handleCloseMenu = () => {
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
}
/* 黑色主题菜单头部 */

View File

@ -301,28 +301,28 @@ export function getRobotActionText(action: RobotAction): string {
* @param action
* @returns
*/
export function getRobotActionIcon(action: RobotAction): string {
const iconMap: Record<RobotAction, string> = {
'seize_control': '🎮',
'enable_orders': '✅',
'disable_orders': '❌',
'pause': '⏸️',
'resume': '▶️',
'go_charge': '🔋',
'go_dock': '🏠',
'navigate': '🧭',
'start': '🚀',
'stop': '⏹️',
'reset': '🔄',
'diagnose': '🔧',
'update': '📱',
'custom_image': '🖼️',
'follow_view': '👁️',
'stop_follow_view': '👁️‍🗨️',
};
// export function getRobotActionIcon(action: RobotAction): string {
// const iconMap: Record<RobotAction, string> = {
// 'seize_control': '🎮',
// 'enable_orders': '✅',
// 'disable_orders': '❌',
// 'pause': '⏸️',
// 'resume': '▶️',
// 'go_charge': '🔋',
// 'go_dock': '🏠',
// 'navigate': '🧭',
// 'start': '🚀',
// 'stop': '⏹️',
// 'reset': '🔄',
// 'diagnose': '🔧',
// 'update': '📱',
// 'custom_image': '🖼️',
// 'follow_view': '👁️',
// 'stop_follow_view': '👁️‍🗨️',
// };
return iconMap[action] || '⚙️';
}
// return iconMap[action] || '⚙️';
// }
/**
*