feat(context-menu): 优化站点右键菜单样式并改用Ant Design Menu组件,同时为机器人选择弹窗添加搜索功能和视觉优化
This commit is contained in:
parent
284fce1873
commit
322a4fa8a7
@ -4,5 +4,5 @@ ENV_WEBSOCKET_BASE=/ws
|
||||
ENV_STORAGE_WEBSOCKET_BASE=/vwedWs
|
||||
|
||||
# 开发环境token配置 - 可以手动设置或从另一个项目获取后填入
|
||||
ENV_DEV_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjQ3MDExNDcsInVzZXJuYW1lIjoiYWRtaW4ifQ.NHLOjXm9JblCXXaDmc8PRvqB-uIpjwltrFh2EH4usIc
|
||||
ENV_DEV_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NjUzMDIxMjcsInVzZXJuYW1lIjoiYWRtaW4ifQ.Bu5YpD-lg4YG2_H6kkbauZK7WpjqOxoBtwD4AiZXAWI
|
||||
ENV_DEV_TENANT_ID=1000
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
<!-- 菜单头部:显示站点信息 -->
|
||||
<div class="menu-header">
|
||||
<div class="point-info">
|
||||
<div class="point-icon">📍</div>
|
||||
<div class="point-details">
|
||||
<div class="point-name">{{ pointInfo?.name || '未知站点' }}</div>
|
||||
<div class="point-id">ID: {{ pointInfo?.id }}</div>
|
||||
@ -12,25 +11,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 菜单分割线 -->
|
||||
<a-divider style="margin: 12px 0;" />
|
||||
<a-divider style="margin: 6px 0;" />
|
||||
|
||||
<!-- 菜单选项 -->
|
||||
<div class="menu-options">
|
||||
<!-- 导航至此站点 -->
|
||||
<div
|
||||
class="menu-item"
|
||||
:class="{ disabled: !canNavigate }"
|
||||
<a-menu
|
||||
:selectable="false"
|
||||
class="menu-options"
|
||||
>
|
||||
<a-menu-item
|
||||
key="navigate"
|
||||
:disabled="!canNavigate"
|
||||
@click="handleNavigateToPoint"
|
||||
>
|
||||
<div class="menu-item-content">
|
||||
<span class="menu-icon">🧭</span>
|
||||
<span class="menu-text">导航至此站点</span>
|
||||
</div>
|
||||
<div class="menu-description">
|
||||
选择机器人并下发移动任务到当前站点
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
导航至此站点
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
|
||||
<!-- 机器人选择弹窗 -->
|
||||
<RobotSelectorModal
|
||||
@ -157,25 +152,24 @@ const handleRobotSelectorCancel = () => {
|
||||
|
||||
<style scoped>
|
||||
.point-menu {
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
padding: 8px;
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* 菜单头部 */
|
||||
.menu-header {
|
||||
padding: 8px 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.point-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.point-icon {
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -183,35 +177,42 @@ const handleRobotSelectorCancel = () => {
|
||||
|
||||
.point-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.point-name {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.point-id {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
font-size: 11px;
|
||||
color: var(--text-color-secondary, #8c8c8c);
|
||||
}
|
||||
|
||||
/* 菜单选项 */
|
||||
.menu-options {
|
||||
padding: 0 4px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 12px;
|
||||
margin: 4px 0;
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
margin: 2px 0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.menu-item:hover:not(.disabled) {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--hover-bg, #f5f5f5);
|
||||
}
|
||||
|
||||
/* 确保菜单项不超出容器宽度 */
|
||||
.menu-item-content {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu-item.disabled {
|
||||
@ -222,30 +223,71 @@ const handleRobotSelectorCancel = () => {
|
||||
.menu-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 4px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 16px;
|
||||
width: 20px;
|
||||
font-size: 14px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
color: var(--text-color, #262626);
|
||||
}
|
||||
|
||||
.menu-description {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
margin-left: 32px;
|
||||
/* 深色主题样式 */
|
||||
:root([data-theme='dark']) .point-menu {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
|
||||
/* 分割线样式 */
|
||||
:deep(.ant-divider) {
|
||||
border-color: #f0f0f0;
|
||||
:root([data-theme='dark']) .point-name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
:root([data-theme='dark']) .point-id {
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
:root([data-theme='dark']) .menu-item:hover:not(.disabled) {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
:root([data-theme='dark']) .menu-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 兼容其他可能的深色主题类名 */
|
||||
.dark .point-menu,
|
||||
[data-theme="dark"] .point-menu,
|
||||
.theme-dark .point-menu {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
|
||||
.dark .point-name,
|
||||
[data-theme="dark"] .point-name,
|
||||
.theme-dark .point-name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.dark .point-id,
|
||||
[data-theme="dark"] .point-id,
|
||||
.theme-dark .point-id {
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
.dark .menu-item:hover:not(.disabled),
|
||||
[data-theme="dark"] .menu-item:hover:not(.disabled),
|
||||
.theme-dark .menu-item:hover:not(.disabled) {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.dark .menu-text,
|
||||
[data-theme="dark"] .menu-text,
|
||||
.theme-dark .menu-text {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -7,30 +7,37 @@
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="robot-selector">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-section">
|
||||
<a-input
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索机器人名称或ID"
|
||||
:allow-clear="true"
|
||||
class="search-input"
|
||||
>
|
||||
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<!-- 机器人列表 -->
|
||||
<div class="robot-list">
|
||||
<a-radio-group v-model:value="selectedRobotId" class="radio-group">
|
||||
<div
|
||||
v-for="robot in robotList"
|
||||
:key="robot.id"
|
||||
class="robot-item"
|
||||
>
|
||||
<a-radio :value="robot.id">
|
||||
<div class="robot-info">
|
||||
<div class="robot-name">{{ robot.label }}</div>
|
||||
<div class="robot-details">
|
||||
<a-tag :color="getRobotStatusColor(robot.state)">
|
||||
{{ getRobotStatusText(robot.state) }}
|
||||
</a-tag>
|
||||
<span class="robot-id">ID: {{ robot.id }}</span>
|
||||
<span v-if="robot.targetPoint" class="current-station">
|
||||
目标位置: {{ robot.targetPoint }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio>
|
||||
<div
|
||||
v-for="robot in filteredRobotList"
|
||||
:key="robot.id"
|
||||
class="robot-item"
|
||||
:class="{ selected: selectedRobotId === robot.id }"
|
||||
@click="handleSelectRobot(robot.id)"
|
||||
>
|
||||
<div class="robot-info">
|
||||
<span class="robot-name">{{ robot.label }}</span>
|
||||
<span class="robot-status">- {{ getRobotStatusText(robot.state) }}</span>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
<div class="check-indicator" v-if="selectedRobotId === robot.id">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
@ -55,7 +62,7 @@ import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import type { RobotInfo } from '../../apis/robot';
|
||||
import { RobotState } from '../../apis/robot';
|
||||
import { getAllRobots } from '../../apis/robot';
|
||||
import { editorStore } from '../../stores/editor.store';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
@ -80,18 +87,32 @@ const visible = ref(false);
|
||||
const robotList = ref<RobotInfo[]>([]);
|
||||
const selectedRobotId = ref<string>('');
|
||||
const loading = ref(false);
|
||||
const searchKeyword = ref<string>('');
|
||||
|
||||
// 计算属性
|
||||
const selectedRobot = computed(() =>
|
||||
robotList.value.find(robot => robot.id === selectedRobotId.value)
|
||||
);
|
||||
|
||||
const filteredRobotList = computed(() => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
return robotList.value;
|
||||
}
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase().trim();
|
||||
return robotList.value.filter(robot =>
|
||||
robot.label.toLowerCase().includes(keyword) ||
|
||||
robot.id.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
// 监听 props.open 变化
|
||||
watch(() => props.open, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal) {
|
||||
loadRobotList();
|
||||
selectedRobotId.value = '';
|
||||
searchKeyword.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
@ -101,14 +122,17 @@ watch(visible, (newVal) => {
|
||||
});
|
||||
|
||||
// 加载机器人列表
|
||||
const loadRobotList = async () => {
|
||||
const loadRobotList = () => {
|
||||
try {
|
||||
const robots = await getAllRobots();
|
||||
robotList.value = robots || [];
|
||||
const editor = editorStore.getEditorValue();
|
||||
if (editor) {
|
||||
robotList.value = editor.robots || [];
|
||||
} else {
|
||||
robotList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载机器人列表失败:', error);
|
||||
robotList.value = [];
|
||||
message.error('加载机器人列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
@ -129,17 +153,7 @@ const getRobotStatusText = (state?: RobotState): string => {
|
||||
return stateMap[state || RobotState.空闲中] || '未知';
|
||||
};
|
||||
|
||||
// 获取机器人状态颜色
|
||||
const getRobotStatusColor = (state?: RobotState): string => {
|
||||
const colorMap: Record<RobotState, string> = {
|
||||
[RobotState.未知]: '#d9d9d9', // 灰色 - 未知
|
||||
[RobotState.任务执行中]: '#1890ff', // 蓝色 - 忙碌
|
||||
[RobotState.充电中]: '#faad14', // 橙色 - 充电
|
||||
[RobotState.停靠中]: '#52c41a', // 绿色 - 正常
|
||||
[RobotState.空闲中]: '#52c41a', // 绿色 - 正常
|
||||
};
|
||||
return colorMap[state || RobotState.空闲中] || '#d9d9d9';
|
||||
};
|
||||
|
||||
|
||||
// 处理确定按钮点击
|
||||
const handleConfirm = async () => {
|
||||
@ -172,6 +186,11 @@ const handleConfirm = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理选择机器人
|
||||
const handleSelectRobot = (robotId: string) => {
|
||||
selectedRobotId.value = robotId;
|
||||
};
|
||||
|
||||
// 处理取消按钮点击
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
@ -188,76 +207,95 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.robot-selector {
|
||||
padding: 16px 0;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 12px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
color: var(--text-color-secondary, #8c8c8c);
|
||||
}
|
||||
|
||||
.robot-list {
|
||||
max-height: 400px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.robot-item {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.robot-item:hover {
|
||||
border-color: #1890ff;
|
||||
background-color: #f5f5f5;
|
||||
.robot-item.selected {
|
||||
background-color: #0dbb8a;
|
||||
}
|
||||
|
||||
.robot-item.selected .robot-name {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.robot-item.selected .robot-status {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.check-indicator {
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.robot-info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.robot-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.robot-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.robot-id {
|
||||
color: #595959;
|
||||
.robot-name {
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.current-station {
|
||||
color: #595959;
|
||||
.robot-status {
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
/* 自定义单选框样式 */
|
||||
:deep(.ant-radio-wrapper) {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:deep(.ant-radio-wrapper span:last-child) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user