feat: 重构上下文菜单组件,使用 Ant Design 的下拉菜单替代原有实现,优化子菜单逻辑和样式,提升用户体验
This commit is contained in:
parent
d270e82c8b
commit
a4b1975492
@ -1,42 +1,59 @@
|
||||
<template>
|
||||
<div v-if="visible" ref="mainMenuRef" class="context-menu" :style="menuStyle" @click.stop @contextmenu.prevent>
|
||||
<div class="context-menu-header">
|
||||
<div class="menu-title">{{ headerTitle }}</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"
|
||||
<a-dropdown
|
||||
v-if="visible"
|
||||
:open="visible"
|
||||
:trigger="[]"
|
||||
:placement="dropdownPlacement"
|
||||
:get-popup-container="getPopupContainer"
|
||||
@open-change="handleOpenChange"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- 机器人菜单 -->
|
||||
<RobotMenu
|
||||
v-else-if="menuType === 'robot' && robotInfo"
|
||||
:robot-info="robotInfo"
|
||||
@action-complete="handleActionComplete"
|
||||
/>
|
||||
<!-- 库位菜单 -->
|
||||
<StorageMenu
|
||||
v-if="menuType === 'storage-background' && storageLocations?.length"
|
||||
:storage-locations="storageLocations"
|
||||
:menu-x="x"
|
||||
:menu-y="y"
|
||||
:main-menu-width="mainMenuWidth"
|
||||
@action-complete="handleActionComplete"
|
||||
/>
|
||||
|
||||
<!-- 默认菜单 -->
|
||||
<DefaultMenu
|
||||
v-else
|
||||
@action-complete="handleActionComplete"
|
||||
/>
|
||||
</div>
|
||||
<!-- 机器人菜单 -->
|
||||
<RobotMenu
|
||||
v-else-if="menuType === 'robot' && robotInfo"
|
||||
:robot-info="robotInfo"
|
||||
@action-complete="handleActionComplete"
|
||||
/>
|
||||
|
||||
<!-- 默认菜单 -->
|
||||
<DefaultMenu
|
||||
v-else
|
||||
@action-complete="handleActionComplete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick,ref, watch } from 'vue';
|
||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||
|
||||
import type { StorageLocationInfo } from '../../services/context-menu';
|
||||
import type { RobotInfo } from '../../services/context-menu';
|
||||
import DefaultMenu from './default-menu.vue';
|
||||
import RobotMenu from './robot-menu.vue';
|
||||
import StorageMenu from './storage-menu.vue';
|
||||
import type { StorageLocationInfo } from '../../services/context-menu';
|
||||
// 使用动态导入避免 TypeScript 错误
|
||||
const DefaultMenu = defineAsyncComponent(() => import('./default-menu.vue'));
|
||||
const RobotMenu = defineAsyncComponent(() => import('./robot-menu.vue'));
|
||||
const StorageMenu = defineAsyncComponent(() => import('./storage-menu.vue'));
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
@ -67,57 +84,26 @@ defineOptions({
|
||||
name: 'ContextMenu',
|
||||
});
|
||||
|
||||
// 主菜单引用
|
||||
const mainMenuRef = ref<HTMLElement | null>(null);
|
||||
// 触发器引用
|
||||
const triggerRef = ref<HTMLElement | null>(null);
|
||||
const mainMenuWidth = ref(200); // 默认宽度
|
||||
|
||||
// 监听主菜单元素变化,更新宽度
|
||||
watch(mainMenuRef, async (newRef) => {
|
||||
if (newRef) {
|
||||
await nextTick();
|
||||
mainMenuWidth.value = newRef.offsetWidth;
|
||||
}
|
||||
});
|
||||
// 触发器样式 - 创建一个不可见的定位点
|
||||
const triggerStyle = computed(() => ({
|
||||
position: 'fixed' as const,
|
||||
left: `${props.x}px`,
|
||||
top: `${props.y}px`,
|
||||
width: '1px',
|
||||
height: '1px',
|
||||
pointerEvents: 'none' as const,
|
||||
zIndex: 9999,
|
||||
}));
|
||||
|
||||
// 菜单样式计算
|
||||
const menuStyle = computed(() => {
|
||||
// 菜单尺寸估算
|
||||
const menuWidth = mainMenuWidth.value || 200;
|
||||
const menuHeight = 300; // 估算高度
|
||||
|
||||
// 视口边界检测和调整
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
let left = props.x;
|
||||
let top = props.y;
|
||||
|
||||
// 右边界检测:如果菜单会超出右边界,则向左调整
|
||||
if (left + menuWidth > viewportWidth) {
|
||||
left = viewportWidth - menuWidth - 10; // 留10px边距
|
||||
}
|
||||
|
||||
// 下边界检测:如果菜单会超出下边界,则向上调整
|
||||
if (top + menuHeight > viewportHeight) {
|
||||
top = viewportHeight - menuHeight - 10; // 留10px边距
|
||||
}
|
||||
|
||||
// 上边界检测:确保不会超出上边界
|
||||
if (top < 0) {
|
||||
top = 10; // 留10px边距
|
||||
}
|
||||
|
||||
// 左边界检测:确保不会超出左边界
|
||||
if (left < 0) {
|
||||
left = 10; // 留10px边距
|
||||
}
|
||||
|
||||
const style = {
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
};
|
||||
return style;
|
||||
});
|
||||
// 使用 Ant Design 的自动边界检测,无需手动计算
|
||||
const dropdownPlacement = 'bottomLeft' as const;
|
||||
|
||||
// 获取弹出层容器
|
||||
const getPopupContainer = () => document.body;
|
||||
|
||||
// 动态标题
|
||||
const headerTitle = computed(() => {
|
||||
@ -129,6 +115,13 @@ const headerTitle = computed(() => {
|
||||
return '右键菜单';
|
||||
});
|
||||
|
||||
// 处理下拉菜单开关变化
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
emit('close');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理操作完成事件
|
||||
const handleActionComplete = (data: any) => {
|
||||
console.log('菜单操作完成:', data);
|
||||
@ -142,9 +135,17 @@ const handleActionComplete = (data: any) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.context-menu {
|
||||
/* 触发器样式 - 不可见定位点 */
|
||||
.context-menu-trigger {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* 下拉菜单覆盖层样式 */
|
||||
.context-menu-overlay {
|
||||
background: white;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
@ -156,16 +157,33 @@ const handleActionComplete = (data: any) => {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* 黑色主题 */
|
||||
:root[theme='dark'] .context-menu-overlay {
|
||||
background: #141414;
|
||||
border-color: #424242;
|
||||
color: #ffffffd9;
|
||||
}
|
||||
|
||||
/* 菜单头部 */
|
||||
.context-menu-header {
|
||||
padding: 8px 12px;
|
||||
background-color: #fafafa;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
/* 黑色主题菜单头部 */
|
||||
:root[theme='dark'] .context-menu-header {
|
||||
background-color: #212121;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
color: #000 !important;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* 黑色主题标题 */
|
||||
:root[theme='dark'] .menu-title {
|
||||
color: #ffffffd9 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -35,55 +35,66 @@
|
||||
<div class="arrow-icon">▶</div>
|
||||
</div>
|
||||
|
||||
<!-- 库位操作子菜单 -->
|
||||
<div
|
||||
v-if="showSubMenu && selectedLocation"
|
||||
class="sub-menu-container"
|
||||
:style="subMenuStyle"
|
||||
@click.stop
|
||||
@mouseenter="handleSubMenuMouseEnter"
|
||||
@mouseleave="handleSubMenuMouseLeave"
|
||||
<!-- 库位操作子菜单 - 使用 Ant Design Dropdown -->
|
||||
<a-dropdown
|
||||
v-if="showSubMenu && selectedLocation"
|
||||
:open="!!showSubMenu"
|
||||
:trigger="[]"
|
||||
:placement="subMenuPlacement"
|
||||
:get-popup-container="getSubMenuContainer"
|
||||
@open-change="handleSubMenuOpenChange"
|
||||
>
|
||||
<!-- 连接区域,确保鼠标移动时不会断开 -->
|
||||
<div class="sub-menu-connector"></div>
|
||||
<div class="sub-menu">
|
||||
<div class="sub-menu-header">
|
||||
<div class="sub-menu-title">{{ selectedLocation.name }} - 操作</div>
|
||||
<div
|
||||
ref="subMenuTriggerRef"
|
||||
class="sub-menu-trigger"
|
||||
:style="subMenuTriggerStyle"
|
||||
/>
|
||||
<template #overlay>
|
||||
<div
|
||||
class="sub-menu-overlay"
|
||||
@mouseenter="handleSubMenuMouseEnter"
|
||||
@mouseleave="handleSubMenuMouseLeave"
|
||||
>
|
||||
<div class="sub-menu-header">
|
||||
<div class="sub-menu-title">{{ selectedLocation.name }} - 操作</div>
|
||||
</div>
|
||||
<div class="sub-menu-actions">
|
||||
<div class="menu-item" @click="handleStorageAction('occupy', '占用')">
|
||||
<span class="action-icon">📦</span>
|
||||
<span>占用</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('release', '释放')">
|
||||
<span class="action-icon">📤</span>
|
||||
<span>释放</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('lock', '锁定')">
|
||||
<span class="action-icon">🔒</span>
|
||||
<span>锁定</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('unlock', '解锁')">
|
||||
<span class="action-icon">🔓</span>
|
||||
<span>解锁</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('enable', '启用')">
|
||||
<span class="action-icon">✅</span>
|
||||
<span>启用</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('disable', '禁用')">
|
||||
<span class="action-icon">❌</span>
|
||||
<span>禁用</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('set_empty_tray', '设为空托盘')">
|
||||
<span class="action-icon">📋</span>
|
||||
<span>设为空托盘</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="handleStorageAction('clear_empty_tray', '清除空托盘')">
|
||||
<span class="action-icon">🗑️</span>
|
||||
<span>清除空托盘</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('occupy', '占用')">
|
||||
<span class="action-icon">📦</span>
|
||||
<span>占用</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('release', '释放')">
|
||||
<span class="action-icon">📤</span>
|
||||
<span>释放</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('lock', '锁定')">
|
||||
<span class="action-icon">🔒</span>
|
||||
<span>锁定</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('unlock', '解锁')">
|
||||
<span class="action-icon">🔓</span>
|
||||
<span>解锁</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('enable', '启用')">
|
||||
<span class="action-icon">✅</span>
|
||||
<span>启用</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('disable', '禁用')">
|
||||
<span class="action-icon">❌</span>
|
||||
<span>禁用</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('set_empty_tray', '设为空托盘')">
|
||||
<span class="action-icon">📋</span>
|
||||
<span>设为空托盘</span>
|
||||
</div>
|
||||
<div class="sub-menu-item" @click="handleStorageAction('clear_empty_tray', '清除空托盘')">
|
||||
<span class="action-icon">🗑️</span>
|
||||
<span>清除空托盘</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -115,6 +126,7 @@ defineOptions({
|
||||
const showSubMenu = ref<string | null>(null);
|
||||
const selectedLocation = ref<StorageLocationInfo | null>(null);
|
||||
const hideTimer = ref<number | null>(null);
|
||||
const subMenuTriggerRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// 监听子菜单显示状态
|
||||
watch(showSubMenu, (newValue) => {
|
||||
@ -130,55 +142,67 @@ watch(showSubMenu, (newValue) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 子菜单样式计算
|
||||
const subMenuStyle = computed(() => {
|
||||
// 添加鼠标离开处理,但使用延迟来避免闪烁
|
||||
const handleStorageMouseLeave = () => {
|
||||
// 使用较长的延迟,让用户有时间移动到子菜单
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
}
|
||||
hideTimer.value = window.setTimeout(() => {
|
||||
showSubMenu.value = null;
|
||||
hideTimer.value = null;
|
||||
}, 500); // 增加到500ms延迟,给用户更多时间
|
||||
};
|
||||
|
||||
// 处理鼠标进入子菜单区域
|
||||
const handleSubMenuMouseEnter = () => {
|
||||
// 清除隐藏定时器,防止子菜单消失
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
hideTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理鼠标离开子菜单区域
|
||||
const handleSubMenuMouseLeave = () => {
|
||||
// 当鼠标离开子菜单时,立即隐藏
|
||||
showSubMenu.value = null;
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
hideTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 子菜单触发器样式 - 创建不可见的定位点
|
||||
const subMenuTriggerStyle = computed(() => {
|
||||
if (!showSubMenu.value) return {};
|
||||
|
||||
// 计算子菜单位置,让它与对应的库位项对齐
|
||||
// 计算子菜单触发器的位置,让子菜单的左上角与父菜单的右上角平齐
|
||||
const menuItemHeight = 60; // 库位项的高度
|
||||
const headerHeight = 40; // 菜单头部高度
|
||||
const itemIndex = props.storageLocations.findIndex(loc => loc.id === showSubMenu.value);
|
||||
const offsetY = headerHeight + (itemIndex * menuItemHeight) - 10; // 往上调整10px
|
||||
|
||||
// 子菜单尺寸(估算)
|
||||
const subMenuWidth = 180; // 子菜单宽度
|
||||
const subMenuHeight = 300; // 子菜单高度(估算)
|
||||
|
||||
// 计算初始位置
|
||||
let left = props.menuX + props.mainMenuWidth;
|
||||
let top = props.menuY + offsetY;
|
||||
|
||||
// 视口边界检测和调整
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
// 右边界检测:如果子菜单会超出右边界,则向左显示
|
||||
if (left + subMenuWidth > viewportWidth) {
|
||||
left = props.menuX - subMenuWidth; // 显示在主菜单左侧
|
||||
}
|
||||
|
||||
// 下边界检测:如果子菜单会超出下边界,则向上调整
|
||||
if (top + subMenuHeight > viewportHeight) {
|
||||
top = viewportHeight - subMenuHeight - 50; // 留10px边距
|
||||
}
|
||||
|
||||
// 上边界检测:确保不会超出上边界
|
||||
if (top < 0) {
|
||||
top = 10; // 留10px边距
|
||||
}
|
||||
|
||||
// 左边界检测:确保不会超出左边界
|
||||
if (left < 0) {
|
||||
left = 10; // 留10px边距
|
||||
}
|
||||
const offsetY = headerHeight + (itemIndex * menuItemHeight);
|
||||
|
||||
const style = {
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
position: 'absolute' as const,
|
||||
left: '100%', // 紧贴主菜单右侧
|
||||
top: `${offsetY}px`, // 与对应的库位项对齐
|
||||
width: '1px',
|
||||
height: '1px',
|
||||
pointerEvents: 'none' as const,
|
||||
zIndex: 10,
|
||||
// 调整位置,使子菜单左上角与父菜单右上角平齐
|
||||
transform: 'translateY(-1px)',
|
||||
};
|
||||
return style;
|
||||
});
|
||||
|
||||
// 子菜单位置 - 使用 rightTop 确保左上角与父菜单右上角平齐
|
||||
const subMenuPlacement = 'rightTop' as const;
|
||||
|
||||
// 获取子菜单弹出层容器
|
||||
const getSubMenuContainer = () => document.body;
|
||||
|
||||
// 选择库位
|
||||
const handleSelectStorage = (location: StorageLocationInfo) => {
|
||||
console.log('选择库位:', location);
|
||||
@ -194,34 +218,52 @@ const hideSubMenu = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 延迟隐藏子菜单
|
||||
const hideSubMenuDelayed = () => {
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
// 延迟隐藏子菜单(已移除,由 Ant Design 处理)
|
||||
// const hideSubMenuDelayed = () => {
|
||||
// if (hideTimer.value) {
|
||||
// clearTimeout(hideTimer.value);
|
||||
// }
|
||||
// hideTimer.value = window.setTimeout(() => {
|
||||
// showSubMenu.value = null;
|
||||
// hideTimer.value = null;
|
||||
// }, 150); // 150ms延迟,给用户足够时间移动到子菜单
|
||||
// };
|
||||
|
||||
// 处理库位项鼠标离开(已移除,由 Ant Design 处理)
|
||||
// const handleStorageMouseLeave = () => {
|
||||
// hideSubMenuDelayed();
|
||||
// };
|
||||
|
||||
// 处理子菜单鼠标进入(已移除,由 Ant Design 处理)
|
||||
// const handleSubMenuMouseEnter = () => {
|
||||
// // 清除隐藏定时器
|
||||
// if (hideTimer.value) {
|
||||
// clearTimeout(hideTimer.value);
|
||||
// hideTimer.value = null;
|
||||
// }
|
||||
// };
|
||||
|
||||
// 处理子菜单鼠标离开(已移除,由 Ant Design 处理)
|
||||
// const handleSubMenuMouseLeave = () => {
|
||||
// hideSubMenu();
|
||||
// };
|
||||
|
||||
// 处理子菜单开关变化
|
||||
const handleSubMenuOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
// 清除隐藏定时器
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
hideTimer.value = null;
|
||||
}
|
||||
hideSubMenu();
|
||||
} else {
|
||||
// 当子菜单打开时,清除任何待隐藏的定时器
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
hideTimer.value = null;
|
||||
}
|
||||
}
|
||||
hideTimer.value = window.setTimeout(() => {
|
||||
showSubMenu.value = null;
|
||||
hideTimer.value = null;
|
||||
}, 150); // 150ms延迟,给用户足够时间移动到子菜单
|
||||
};
|
||||
|
||||
// 处理库位项鼠标离开
|
||||
const handleStorageMouseLeave = () => {
|
||||
hideSubMenuDelayed();
|
||||
};
|
||||
|
||||
// 处理子菜单鼠标进入
|
||||
const handleSubMenuMouseEnter = () => {
|
||||
// 清除隐藏定时器
|
||||
if (hideTimer.value) {
|
||||
clearTimeout(hideTimer.value);
|
||||
hideTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理子菜单鼠标离开
|
||||
const handleSubMenuMouseLeave = () => {
|
||||
hideSubMenu();
|
||||
};
|
||||
|
||||
// 库位操作处理
|
||||
@ -287,10 +329,9 @@ const getStorageTooltip = (location: StorageLocationInfo) => {
|
||||
|
||||
<style scoped>
|
||||
.storage-menu {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 库位项样式 */
|
||||
.storage-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -305,6 +346,10 @@ const getStorageTooltip = (location: StorageLocationInfo) => {
|
||||
border-left-color: #1890ff;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .storage-item:hover {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.storage-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -313,7 +358,6 @@ const getStorageTooltip = (location: StorageLocationInfo) => {
|
||||
|
||||
.storage-name {
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@ -334,79 +378,28 @@ const getStorageTooltip = (location: StorageLocationInfo) => {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
background-color: #d9d9d9; /* 默认灰色 */
|
||||
transition: background-color 0.2s;
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.status-indicator.occupied {
|
||||
background-color: #d9d9d9; /* 默认灰色 */
|
||||
}
|
||||
|
||||
.status-indicator.occupied.active {
|
||||
background-color: #ff4d4f; /* 占用时红色 */
|
||||
}
|
||||
|
||||
.status-indicator.locked {
|
||||
background-color: #d9d9d9; /* 默认灰色 */
|
||||
}
|
||||
|
||||
.status-indicator.locked.active {
|
||||
background-color: #faad14; /* 锁定时橙色 */
|
||||
}
|
||||
|
||||
.status-indicator.disabled {
|
||||
background-color: #d9d9d9; /* 默认灰色 */
|
||||
}
|
||||
|
||||
.status-indicator.disabled.active {
|
||||
background-color: #8c8c8c; /* 禁用时深灰色 */
|
||||
}
|
||||
|
||||
.status-indicator.empty-tray {
|
||||
background-color: #d9d9d9; /* 默认灰色 */
|
||||
}
|
||||
|
||||
.status-indicator.empty-tray.active {
|
||||
background-color: #13c2c2; /* 空托盘时青色 */
|
||||
}
|
||||
.status-indicator.occupied.active { background-color: #ff4d4f; }
|
||||
.status-indicator.locked.active { background-color: #faad14; }
|
||||
.status-indicator.disabled.active { background-color: #8c8c8c; }
|
||||
.status-indicator.empty-tray.active { background-color: #13c2c2; }
|
||||
|
||||
.status-text {
|
||||
color: #666;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 库位状态样式 */
|
||||
.storage-occupied {
|
||||
background-color: #fff2f0;
|
||||
border-left-color: #ff4d4f;
|
||||
}
|
||||
.storage-occupied { background-color: #fff2f0; border-left-color: #ff4d4f; }
|
||||
.storage-locked { background-color: #fffbe6; border-left-color: #faad14; }
|
||||
.storage-disabled { background-color: #f5f5f5; border-left-color: #8c8c8c; }
|
||||
.storage-empty-tray { background-color: #e6fffb; border-left-color: #13c2c2; }
|
||||
.storage-available { background-color: #f6ffed; border-left-color: #52c41a; }
|
||||
|
||||
.storage-locked {
|
||||
background-color: #fffbe6;
|
||||
border-left-color: #faad14;
|
||||
}
|
||||
|
||||
.storage-disabled {
|
||||
background-color: #f5f5f5;
|
||||
border-left-color: #8c8c8c;
|
||||
}
|
||||
|
||||
.storage-empty-tray {
|
||||
background-color: #e6fffb;
|
||||
border-left-color: #13c2c2;
|
||||
}
|
||||
|
||||
.storage-available {
|
||||
background-color: #f6ffed;
|
||||
border-left-color: #52c41a;
|
||||
}
|
||||
|
||||
/* 箭头图标 */
|
||||
.arrow-icon {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
transition: color 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@ -414,76 +407,83 @@ const getStorageTooltip = (location: StorageLocationInfo) => {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 子菜单容器样式 */
|
||||
.sub-menu-container {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
.sub-menu-trigger {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 连接区域 - 确保鼠标移动流畅 */
|
||||
.sub-menu-connector {
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
pointer-events: auto;
|
||||
/* 确保连接区域覆盖可能的间隙 */
|
||||
margin-left: -6px;
|
||||
/* 添加一个微妙的背景,帮助用户理解连接关系 */
|
||||
background: linear-gradient(to right, transparent 0%, rgba(24, 144, 255, 0.05) 50%, transparent 100%);
|
||||
}
|
||||
|
||||
/* 子菜单样式 */
|
||||
.sub-menu {
|
||||
.sub-menu-overlay {
|
||||
background: white;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-left: none; /* 移除左边框,与主菜单无缝连接 */
|
||||
border-radius: 0 6px 6px 0; /* 只圆角右侧,左侧与主菜单连接 */
|
||||
box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.15); /* 调整阴影,避免左侧阴影 */
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 4px 0;
|
||||
min-width: 160px;
|
||||
user-select: none;
|
||||
color: #000;
|
||||
pointer-events: auto;
|
||||
/* 确保与主菜单无缝连接 */
|
||||
margin-left: 0;
|
||||
/* 添加一个微妙的左边框,模拟与主菜单的连接 */
|
||||
border-left: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .sub-menu-overlay {
|
||||
background: #141414;
|
||||
border-color: #424242;
|
||||
}
|
||||
|
||||
.sub-menu-header {
|
||||
padding: 8px 12px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 0 6px 0 0; /* 只圆角右上角,与子菜单整体设计一致 */
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .sub-menu-header {
|
||||
background-color: #212121;
|
||||
border-bottom-color: #303030;
|
||||
}
|
||||
|
||||
.sub-menu-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.sub-menu-item {
|
||||
.sub-menu-actions {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.sub-menu-actions .menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
gap: 8px;
|
||||
height: auto;
|
||||
line-height: 1.4;
|
||||
background: transparent;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
font-size: 13px;
|
||||
color: #000;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sub-menu-item:hover {
|
||||
.sub-menu-actions .menu-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .sub-menu-actions .menu-item {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:root[theme='dark'] .sub-menu-actions .menu-item:hover {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 14px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user