feat: 移除旧的上下文菜单组件,增强机器人菜单功能,新增自定义图片设置和关闭按钮,优化用户交互体验
This commit is contained in:
parent
a88876697f
commit
8f6087cea4
@ -1,45 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- 使用新的模块化组件 -->
|
|
||||||
<ContextMenu
|
|
||||||
:visible="visible"
|
|
||||||
:x="x"
|
|
||||||
:y="y"
|
|
||||||
:menu-type="menuType"
|
|
||||||
:storage-locations="storageLocations"
|
|
||||||
:robot-info="robotInfo"
|
|
||||||
:api-base-url="apiBaseUrl"
|
|
||||||
@close="$emit('close')"
|
|
||||||
@action-complete="$emit('actionComplete', $event)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ContextMenu } from './context-menu';
|
|
||||||
import type { StorageLocationInfo } from '../services/context-menu';
|
|
||||||
import type { RobotInfo } from '../services/context-menu';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
visible: boolean;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
menuType?: 'default' | 'storage' | 'storage-background' | 'robot' | 'point' | 'area';
|
|
||||||
storageLocations?: StorageLocationInfo[];
|
|
||||||
robotInfo?: RobotInfo;
|
|
||||||
apiBaseUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: 'close'): void;
|
|
||||||
(e: 'actionComplete', data: any): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
defineProps<Props>();
|
|
||||||
defineEmits<Emits>();
|
|
||||||
|
|
||||||
// 定义组件名称
|
|
||||||
defineOptions({
|
|
||||||
name: 'ContextMenu',
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 样式已移至各个子组件中 -->
|
|
||||||
@ -16,6 +16,9 @@
|
|||||||
<div class="context-menu-overlay">
|
<div class="context-menu-overlay">
|
||||||
<div class="context-menu-header">
|
<div class="context-menu-header">
|
||||||
<div class="menu-title">{{ headerTitle }}</div>
|
<div class="menu-title">{{ headerTitle }}</div>
|
||||||
|
<div class="close-button" @click="handleCloseMenu" title="关闭菜单">
|
||||||
|
<span class="close-icon">×</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 库位菜单 -->
|
<!-- 库位菜单 -->
|
||||||
@ -33,6 +36,7 @@
|
|||||||
v-else-if="menuType === 'robot' && robotInfo"
|
v-else-if="menuType === 'robot' && robotInfo"
|
||||||
:robot-info="robotInfo"
|
:robot-info="robotInfo"
|
||||||
@action-complete="handleActionComplete"
|
@action-complete="handleActionComplete"
|
||||||
|
@custom-image="handleCustomImage"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 默认菜单 -->
|
<!-- 默认菜单 -->
|
||||||
@ -48,8 +52,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||||
|
|
||||||
import type { RobotInfo } from '../../services/context-menu';
|
import type { RobotInfo, StorageLocationInfo } from '../../services/context-menu';
|
||||||
import type { StorageLocationInfo } from '../../services/context-menu';
|
|
||||||
// 使用动态导入避免 TypeScript 错误
|
// 使用动态导入避免 TypeScript 错误
|
||||||
const DefaultMenu = defineAsyncComponent(() => import('./default-menu.vue'));
|
const DefaultMenu = defineAsyncComponent(() => import('./default-menu.vue'));
|
||||||
const RobotMenu = defineAsyncComponent(() => import('./robot-menu.vue'));
|
const RobotMenu = defineAsyncComponent(() => import('./robot-menu.vue'));
|
||||||
@ -68,6 +72,7 @@ interface Props {
|
|||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
(e: 'actionComplete', data: any): void;
|
(e: 'actionComplete', data: any): void;
|
||||||
|
(e: 'customImage', data: { robotInfo: RobotInfo }): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@ -126,11 +131,20 @@ const handleOpenChange = (open: boolean) => {
|
|||||||
const handleActionComplete = (data: any) => {
|
const handleActionComplete = (data: any) => {
|
||||||
console.log('菜单操作完成:', data);
|
console.log('菜单操作完成:', data);
|
||||||
emit('actionComplete', data);
|
emit('actionComplete', data);
|
||||||
|
// 所有操作都不关闭菜单,只有关闭按钮才能关闭
|
||||||
|
};
|
||||||
|
|
||||||
// 某些操作完成后关闭菜单
|
// 处理自定义图片事件
|
||||||
if (data.action && ['occupy', 'release', 'lock', 'unlock', 'enable', 'disable'].includes(data.action)) {
|
const handleCustomImage = (data: { robotInfo: RobotInfo }) => {
|
||||||
emit('close');
|
console.log('打开自定义图片设置:', data.robotInfo);
|
||||||
}
|
emit('customImage', data);
|
||||||
|
// 不关闭菜单,只有关闭按钮才能关闭
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理关闭按钮点击
|
||||||
|
const handleCloseMenu = () => {
|
||||||
|
console.log('用户点击关闭按钮');
|
||||||
|
emit('close');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -169,6 +183,9 @@ const handleActionComplete = (data: any) => {
|
|||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 6px 6px 0 0;
|
border-radius: 6px 6px 0 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 黑色主题菜单头部 */
|
/* 黑色主题菜单头部 */
|
||||||
@ -186,4 +203,48 @@ const handleActionComplete = (data: any) => {
|
|||||||
:root[theme='dark'] .menu-title {
|
:root[theme='dark'] .menu-title {
|
||||||
color: #ffffffd9 !important;
|
color: #ffffffd9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 关闭按钮样式 */
|
||||||
|
.close-button {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover {
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
color: #666;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover .close-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 黑色主题关闭按钮 */
|
||||||
|
:root[theme='dark'] .close-button {
|
||||||
|
background-color: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[theme='dark'] .close-button:hover {
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[theme='dark'] .close-icon {
|
||||||
|
color: #ffffffd9;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -93,6 +93,10 @@
|
|||||||
<!-- 系统管理 -->
|
<!-- 系统管理 -->
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<div class="action-group-title">系统管理</div>
|
<div class="action-group-title">系统管理</div>
|
||||||
|
<div class="action-item" @click="handleRobotAction('custom_image', '自定义图片')">
|
||||||
|
<span class="action-icon">🖼️</span>
|
||||||
|
<span>自定义图片</span>
|
||||||
|
</div>
|
||||||
<div class="action-item" @click="handleRobotAction('reset', '重置')">
|
<div class="action-item" @click="handleRobotAction('reset', '重置')">
|
||||||
<span class="action-icon">🔄</span>
|
<span class="action-icon">🔄</span>
|
||||||
<span>重置</span>
|
<span>重置</span>
|
||||||
@ -109,27 +113,67 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 机器人图片设置模态框 -->
|
||||||
|
<RobotImageSettingsModal
|
||||||
|
v-model:open="imageSettingsVisible"
|
||||||
|
:robots="availableRobots"
|
||||||
|
:selected-robot-name="selectedRobotName"
|
||||||
|
@save="handleImageSettingsSave"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RobotAction,RobotInfo } from '../../services/context-menu/robot-menu.service';
|
import { message } from 'ant-design-vue';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import type { RobotAction, RobotInfo } from '../../services/context-menu/robot-menu.service';
|
||||||
import {
|
import {
|
||||||
executeRobotAction,
|
executeRobotAction,
|
||||||
getRobotStatusColor,
|
getRobotStatusColor,
|
||||||
getRobotStatusText} from '../../services/context-menu/robot-menu.service';
|
getRobotStatusText
|
||||||
|
} from '../../services/context-menu/robot-menu.service';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
robotInfo?: RobotInfo;
|
robotInfo?: RobotInfo;
|
||||||
|
availableRobots?: Array<{ name: string; type: string; id: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'actionComplete', data: { action: RobotAction; robot: RobotInfo; success: boolean }): void;
|
(e: 'actionComplete', data: { action: RobotAction; robot: RobotInfo; success: boolean }): void;
|
||||||
|
(e: 'customImage', data: { robotInfo: RobotInfo }): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
availableRobots: () => []
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
// 图片设置模态框状态
|
||||||
|
const imageSettingsVisible = ref(false);
|
||||||
|
const selectedRobotName = ref('');
|
||||||
|
|
||||||
|
// 生成可用的机器人列表
|
||||||
|
const availableRobots = computed(() => {
|
||||||
|
// 如果父组件传递了机器人列表,使用父组件的
|
||||||
|
if (props.availableRobots && props.availableRobots.length > 0) {
|
||||||
|
return props.availableRobots;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则,基于当前机器人信息生成列表
|
||||||
|
if (props.robotInfo) {
|
||||||
|
return [{
|
||||||
|
name: props.robotInfo.name,
|
||||||
|
type: 'robot', // 默认类型
|
||||||
|
id: props.robotInfo.id
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
// 定义组件名称
|
// 定义组件名称
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'RobotMenu',
|
name: 'RobotMenu',
|
||||||
@ -152,10 +196,36 @@ const getBatteryClass = (batteryLevel: number) => {
|
|||||||
return 'battery-low';
|
return 'battery-low';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理自定义图片操作
|
||||||
|
const handleCustomImage = () => {
|
||||||
|
if (!props.robotInfo?.name) {
|
||||||
|
message.error('未找到机器人信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('打开机器人图片设置:', props.robotInfo);
|
||||||
|
|
||||||
|
// 打开模态框,不关闭右键菜单
|
||||||
|
selectedRobotName.value = props.robotInfo.name;
|
||||||
|
imageSettingsVisible.value = true;
|
||||||
|
console.log('设置模态框可见性:', imageSettingsVisible.value);
|
||||||
|
|
||||||
|
// 只触发自定义图片事件,不触发操作完成事件
|
||||||
|
emit('customImage', {
|
||||||
|
robotInfo: props.robotInfo
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 处理机器人操作
|
// 处理机器人操作
|
||||||
const handleRobotAction = async (action: RobotAction, actionName: string) => {
|
const handleRobotAction = async (action: RobotAction | 'custom_image', actionName: string) => {
|
||||||
if (!props.robotInfo) return;
|
if (!props.robotInfo) return;
|
||||||
|
|
||||||
|
// 处理自定义图片操作
|
||||||
|
if (action === 'custom_image') {
|
||||||
|
handleCustomImage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`执行机器人操作: ${action} (${actionName})`, props.robotInfo);
|
console.log(`执行机器人操作: ${action} (${actionName})`, props.robotInfo);
|
||||||
|
|
||||||
@ -173,7 +243,6 @@ const handleRobotAction = async (action: RobotAction, actionName: string) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`机器人${actionName}操作失败:`, error);
|
console.error(`机器人${actionName}操作失败:`, error);
|
||||||
|
|
||||||
// 发送操作失败事件
|
|
||||||
emit('actionComplete', {
|
emit('actionComplete', {
|
||||||
action,
|
action,
|
||||||
robot: props.robotInfo,
|
robot: props.robotInfo,
|
||||||
@ -181,6 +250,15 @@ const handleRobotAction = async (action: RobotAction, actionName: string) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理图片设置保存
|
||||||
|
const handleImageSettingsSave = (data: any) => {
|
||||||
|
console.log('机器人图片设置保存:', data);
|
||||||
|
message.success('机器人图片设置保存成功');
|
||||||
|
|
||||||
|
// 可以在这里添加额外的保存后处理逻辑
|
||||||
|
// 比如通知父组件更新机器人显示等
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -218,35 +218,6 @@ const hideSubMenu = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 延迟隐藏子菜单(已移除,由 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) => {
|
const handleSubMenuOpenChange = (open: boolean) => {
|
||||||
|
|||||||
401
src/components/modal/robot-image-settings-modal.vue
Normal file
401
src/components/modal/robot-image-settings-modal.vue
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:open="props.open"
|
||||||
|
title="机器人图片设置"
|
||||||
|
width="800px"
|
||||||
|
:confirm-loading="loading"
|
||||||
|
@ok="handleSave"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@update:open="(value) => emit('update:open', value)"
|
||||||
|
>
|
||||||
|
<div class="robot-image-settings">
|
||||||
|
<a-form :model="formData" layout="vertical">
|
||||||
|
<!-- 机器人选择 -->
|
||||||
|
<a-form-item label="选择机器人">
|
||||||
|
<a-select
|
||||||
|
v-model:value="selectedRobot"
|
||||||
|
placeholder="请选择要设置图片的机器人"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="robot in availableRobots"
|
||||||
|
:key="robot.name"
|
||||||
|
:value="robot.name"
|
||||||
|
>
|
||||||
|
{{ robot.name }} ({{ robot.type }})
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 图片设置区域 -->
|
||||||
|
<div v-if="selectedRobot" class="image-settings-section">
|
||||||
|
<a-divider>图片设置</a-divider>
|
||||||
|
|
||||||
|
<!-- 普通状态图片 -->
|
||||||
|
<a-form-item label="普通状态图片">
|
||||||
|
<div class="image-upload-container">
|
||||||
|
<a-upload
|
||||||
|
:file-list="[]"
|
||||||
|
:before-upload="(file) => handleImageUpload(file, 'normal')"
|
||||||
|
accept="image/*"
|
||||||
|
:show-upload-list="false"
|
||||||
|
>
|
||||||
|
<a-button :loading="uploading.normal">
|
||||||
|
<template #icon>
|
||||||
|
<UploadOutlined />
|
||||||
|
</template>
|
||||||
|
选择普通状态图片
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
|
||||||
|
<div v-if="formData.images.normal" class="image-preview">
|
||||||
|
<img :src="formData.images.normal" alt="普通状态" />
|
||||||
|
<div class="image-actions">
|
||||||
|
<a-button size="small" @click="removeImage('normal')">删除</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 激活状态图片 -->
|
||||||
|
<a-form-item label="激活状态图片">
|
||||||
|
<div class="image-upload-container">
|
||||||
|
<a-upload
|
||||||
|
:file-list="[]"
|
||||||
|
:before-upload="(file) => handleImageUpload(file, 'active')"
|
||||||
|
accept="image/*"
|
||||||
|
:show-upload-list="false"
|
||||||
|
>
|
||||||
|
<a-button :loading="uploading.active">
|
||||||
|
<template #icon>
|
||||||
|
<UploadOutlined />
|
||||||
|
</template>
|
||||||
|
选择激活状态图片
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
|
||||||
|
<div v-if="formData.images.active" class="image-preview">
|
||||||
|
<img :src="formData.images.active" alt="激活状态" />
|
||||||
|
<div class="image-actions">
|
||||||
|
<a-button size="small" @click="removeImage('active')">删除</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 图片尺寸设置 -->
|
||||||
|
<a-form-item label="图片尺寸">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.imageWidth"
|
||||||
|
:min="20"
|
||||||
|
:max="200"
|
||||||
|
placeholder="宽度"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span class="size-label">宽度 (px)</span>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="formData.imageHeight"
|
||||||
|
:min="20"
|
||||||
|
:max="200"
|
||||||
|
placeholder="高度"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span class="size-label">高度 (px)</span>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<a-form-item>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="resetImages">重置图片</a-button>
|
||||||
|
<a-button danger @click="clearAllImages">清除所有图片</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import colorConfig from '../../services/color/color-config.service';
|
||||||
|
|
||||||
|
interface RobotInfo {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
robots: RobotInfo[];
|
||||||
|
selectedRobotName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:open', open: boolean): void;
|
||||||
|
(e: 'update:selectedRobotName', name: string): void;
|
||||||
|
(e: 'save', data: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
open: false,
|
||||||
|
robots: () => [],
|
||||||
|
selectedRobotName: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const selectedRobot = ref<string>(props.selectedRobotName);
|
||||||
|
const loading = ref(false);
|
||||||
|
const uploading = ref({
|
||||||
|
normal: false,
|
||||||
|
active: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
images: {
|
||||||
|
normal: '',
|
||||||
|
active: ''
|
||||||
|
},
|
||||||
|
imageWidth: 42,
|
||||||
|
imageHeight: 76
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const availableRobots = computed(() => props.robots);
|
||||||
|
|
||||||
|
// 组件挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.open && props.selectedRobotName) {
|
||||||
|
selectedRobot.value = props.selectedRobotName;
|
||||||
|
loadRobotImages(props.selectedRobotName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听选中机器人变化
|
||||||
|
watch(selectedRobot, (newRobot) => {
|
||||||
|
if (newRobot) {
|
||||||
|
loadRobotImages(newRobot);
|
||||||
|
emit('update:selectedRobotName', newRobot);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听props变化
|
||||||
|
watch(() => props.selectedRobotName, (newName) => {
|
||||||
|
console.log('模态框接收到selectedRobotName变化:', newName);
|
||||||
|
if (newName && newName !== selectedRobot.value) {
|
||||||
|
selectedRobot.value = newName;
|
||||||
|
console.log('设置selectedRobot为:', newName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听open状态变化
|
||||||
|
watch(() => props.open, (isOpen) => {
|
||||||
|
console.log('模态框open状态变化:', isOpen);
|
||||||
|
if (isOpen) {
|
||||||
|
// 如果有选中的机器人名称,则设置并加载数据
|
||||||
|
if (props.selectedRobotName) {
|
||||||
|
selectedRobot.value = props.selectedRobotName;
|
||||||
|
loadRobotImages(props.selectedRobotName);
|
||||||
|
console.log('模态框打开,设置selectedRobot为:', props.selectedRobotName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 关闭时重置所有状态
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载机器人图片
|
||||||
|
const loadRobotImages = (robotName: string) => {
|
||||||
|
formData.value.images.normal = colorConfig.getRobotCustomImage(robotName, 'normal') || '';
|
||||||
|
formData.value.images.active = colorConfig.getRobotCustomImage(robotName, 'active') || '';
|
||||||
|
|
||||||
|
// 加载图片尺寸配置
|
||||||
|
formData.value.imageWidth = Number(colorConfig.getColor('robot.imageWidth')) || 42;
|
||||||
|
formData.value.imageHeight = Number(colorConfig.getColor('robot.imageHeight')) || 76;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理图片上传
|
||||||
|
const handleImageUpload = async (file: File, state: 'normal' | 'active') => {
|
||||||
|
if (!selectedRobot.value) {
|
||||||
|
message.error('请先选择机器人');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件类型
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
message.error('请选择图片文件');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件大小 (限制为2MB)
|
||||||
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
|
message.error('图片文件大小不能超过2MB');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value[state] = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await colorConfig.saveRobotCustomImage(selectedRobot.value, state, file);
|
||||||
|
formData.value.images[state] = colorConfig.getRobotCustomImage(selectedRobot.value, state) || '';
|
||||||
|
message.success(`${state === 'normal' ? '普通状态' : '激活状态'}图片上传成功`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('图片上传失败:', error);
|
||||||
|
message.error('图片上传失败,请重试');
|
||||||
|
} finally {
|
||||||
|
uploading.value[state] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // 阻止默认上传行为
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除图片
|
||||||
|
const removeImage = (state: 'normal' | 'active') => {
|
||||||
|
if (!selectedRobot.value) return;
|
||||||
|
|
||||||
|
colorConfig.removeRobotCustomImage(selectedRobot.value, state);
|
||||||
|
formData.value.images[state] = '';
|
||||||
|
message.success(`${state === 'normal' ? '普通状态' : '激活状态'}图片已删除`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置图片
|
||||||
|
const resetImages = () => {
|
||||||
|
if (!selectedRobot.value) return;
|
||||||
|
|
||||||
|
colorConfig.removeRobotCustomImage(selectedRobot.value);
|
||||||
|
formData.value.images.normal = '';
|
||||||
|
formData.value.images.active = '';
|
||||||
|
message.success('图片已重置');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除所有图片
|
||||||
|
const clearAllImages = () => {
|
||||||
|
if (!selectedRobot.value) return;
|
||||||
|
|
||||||
|
colorConfig.removeRobotCustomImage(selectedRobot.value);
|
||||||
|
formData.value.images.normal = '';
|
||||||
|
formData.value.images.active = '';
|
||||||
|
message.success('所有图片已清除');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存设置
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!selectedRobot.value) {
|
||||||
|
message.error('请选择机器人');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 保存图片尺寸配置
|
||||||
|
if (formData.value.imageWidth !== Number(colorConfig.getColor('robot.imageWidth'))) {
|
||||||
|
colorConfig.setColor('robot.imageWidth', formData.value.imageWidth.toString());
|
||||||
|
}
|
||||||
|
if (formData.value.imageHeight !== Number(colorConfig.getColor('robot.imageHeight'))) {
|
||||||
|
colorConfig.setColor('robot.imageHeight', formData.value.imageHeight.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('save', {
|
||||||
|
robotName: selectedRobot.value,
|
||||||
|
images: formData.value.images,
|
||||||
|
imageWidth: formData.value.imageWidth,
|
||||||
|
imageHeight: formData.value.imageHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
message.success('设置保存成功');
|
||||||
|
handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存设置失败:', error);
|
||||||
|
message.error('保存设置失败,请重试');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:open', false);
|
||||||
|
// 重置表单
|
||||||
|
selectedRobot.value = '';
|
||||||
|
formData.value = {
|
||||||
|
images: {
|
||||||
|
normal: '',
|
||||||
|
active: ''
|
||||||
|
},
|
||||||
|
imageWidth: 42,
|
||||||
|
imageHeight: 76
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.robot-image-settings {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-settings-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-upload-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview img {
|
||||||
|
max-width: 120px;
|
||||||
|
max-height: 120px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview:hover .image-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-divider {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -407,13 +407,16 @@ const handleActionComplete = (data: any) => {
|
|||||||
* @param event 点击事件
|
* @param event 点击事件
|
||||||
*/
|
*/
|
||||||
const handleGlobalClick = (event: MouseEvent) => {
|
const handleGlobalClick = (event: MouseEvent) => {
|
||||||
// 如果右键菜单可见,检查点击是否在菜单内
|
|
||||||
if (contextMenuState.value.visible) {
|
// 检查是否点击了关闭按钮
|
||||||
// 如果点击不在菜单内,则关闭菜单
|
const closeButton = (event.target as Element)?.closest('.close-button');
|
||||||
if (!isClickInsideMenu(event, contextMenuState.value.visible)) {
|
if (closeButton) {
|
||||||
contextMenuManager.close();
|
// 如果点击了关闭按钮,让按钮自己的点击事件处理关闭
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -98,6 +98,14 @@ export interface EditorColorConfig {
|
|||||||
// 机器人图片尺寸
|
// 机器人图片尺寸
|
||||||
imageWidth: number; // 机器人图片宽度
|
imageWidth: number; // 机器人图片宽度
|
||||||
imageHeight: number; // 机器人图片高度
|
imageHeight: number; // 机器人图片高度
|
||||||
|
// 单个机器人自定义图片配置
|
||||||
|
customImages: {
|
||||||
|
[robotName: string]: {
|
||||||
|
normal: string; // Base64编码的普通状态图片
|
||||||
|
active: string; // Base64编码的激活状态图片
|
||||||
|
};
|
||||||
|
};
|
||||||
|
useCustomImages: boolean; // 是否启用自定义图片
|
||||||
};
|
};
|
||||||
|
|
||||||
// 库位颜色
|
// 库位颜色
|
||||||
@ -318,7 +326,9 @@ const DEFAULT_COLORS: EditorColorConfig = {
|
|||||||
strokeFault: '#FF4D4F99',
|
strokeFault: '#FF4D4F99',
|
||||||
fillFault: '#FF4D4F33',
|
fillFault: '#FF4D4F33',
|
||||||
imageWidth: 42, // 机器人图片宽度 - 42像素
|
imageWidth: 42, // 机器人图片宽度 - 42像素
|
||||||
imageHeight: 76 // 机器人图片高度 - 76像素
|
imageHeight: 76, // 机器人图片高度 - 76像素
|
||||||
|
customImages: {}, // 单个机器人自定义图片配置
|
||||||
|
useCustomImages: false // 默认不启用自定义图片
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
occupied: '#ff4d4f',
|
occupied: '#ff4d4f',
|
||||||
@ -465,7 +475,9 @@ const DARK_THEME_COLORS: EditorColorConfig = {
|
|||||||
strokeFault: '#FF4D4F99',
|
strokeFault: '#FF4D4F99',
|
||||||
fillFault: '#FF4D4F33',
|
fillFault: '#FF4D4F33',
|
||||||
imageWidth: 42, // 机器人图片宽度 - 42像素
|
imageWidth: 42, // 机器人图片宽度 - 42像素
|
||||||
imageHeight: 76 // 机器人图片高度 - 76像素
|
imageHeight: 76, // 机器人图片高度 - 76像素
|
||||||
|
customImages: {}, // 单个机器人自定义图片配置
|
||||||
|
useCustomImages: false // 默认不启用自定义图片
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
occupied: '#ff4d4f',
|
occupied: '#ff4d4f',
|
||||||
@ -879,7 +891,9 @@ class ColorConfigService {
|
|||||||
strokeFault: theme.robot['stroke-fault'] || DEFAULT_COLORS.robot.strokeFault,
|
strokeFault: theme.robot['stroke-fault'] || DEFAULT_COLORS.robot.strokeFault,
|
||||||
fillFault: theme.robot['fill-fault'] || DEFAULT_COLORS.robot.fillFault,
|
fillFault: theme.robot['fill-fault'] || DEFAULT_COLORS.robot.fillFault,
|
||||||
imageWidth: DEFAULT_COLORS.robot.imageWidth,
|
imageWidth: DEFAULT_COLORS.robot.imageWidth,
|
||||||
imageHeight: DEFAULT_COLORS.robot.imageHeight
|
imageHeight: DEFAULT_COLORS.robot.imageHeight,
|
||||||
|
customImages: DEFAULT_COLORS.robot.customImages,
|
||||||
|
useCustomImages: DEFAULT_COLORS.robot.useCustomImages
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1009,6 +1023,152 @@ class ColorConfigService {
|
|||||||
}, obj);
|
}, obj);
|
||||||
target[lastKey] = value;
|
target[lastKey] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件转换为Base64字符串
|
||||||
|
* @param file 图片文件
|
||||||
|
* @param maxWidth 最大宽度,用于压缩
|
||||||
|
* @param quality 压缩质量 (0-1)
|
||||||
|
*/
|
||||||
|
public async fileToBase64(file: File, maxWidth: number = 200, quality: number = 0.8): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
// 创建canvas进行图片压缩
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
|
||||||
|
// 计算压缩后的尺寸
|
||||||
|
const ratio = Math.min(maxWidth / img.width, maxWidth / img.height);
|
||||||
|
canvas.width = img.width * ratio;
|
||||||
|
canvas.height = img.height * ratio;
|
||||||
|
|
||||||
|
// 绘制压缩后的图片
|
||||||
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 转换为Base64
|
||||||
|
const base64 = canvas.toDataURL('image/png', quality);
|
||||||
|
resolve(base64);
|
||||||
|
};
|
||||||
|
img.onerror = reject;
|
||||||
|
img.src = reader.result as string;
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存单个机器人的自定义图片
|
||||||
|
* @param robotName 机器人名称
|
||||||
|
* @param state 图片状态 ('normal' | 'active')
|
||||||
|
* @param file 图片文件
|
||||||
|
*/
|
||||||
|
public async saveRobotCustomImage(robotName: string, state: 'normal' | 'active', file: File): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 将图片转换为Base64
|
||||||
|
const base64 = await this.fileToBase64(file);
|
||||||
|
|
||||||
|
// 确保customImages对象存在
|
||||||
|
if (!this.config.value.robot.customImages) {
|
||||||
|
this.setNestedValue(this.config.value, 'robot.customImages', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保机器人对象存在
|
||||||
|
if (!this.config.value.robot.customImages[robotName]) {
|
||||||
|
this.setNestedValue(this.config.value, `robot.customImages.${robotName}`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存图片数据
|
||||||
|
this.setNestedValue(this.config.value, `robot.customImages.${robotName}.${state}`, base64);
|
||||||
|
|
||||||
|
// 启用自定义图片
|
||||||
|
this.setColor('robot.useCustomImages', 'true');
|
||||||
|
|
||||||
|
// 更新单个机器人的图片
|
||||||
|
if (this.editorService && typeof this.editorService.updateRobotImage === 'function') {
|
||||||
|
this.editorService.updateRobotImage(robotName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发重新渲染
|
||||||
|
this.triggerRender();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存机器人自定义图片失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个机器人的自定义图片
|
||||||
|
* @param robotName 机器人名称
|
||||||
|
* @param state 图片状态 ('normal' | 'active')
|
||||||
|
*/
|
||||||
|
public getRobotCustomImage(robotName: string, state: 'normal' | 'active'): string | null {
|
||||||
|
const customImages = this.config.value.robot.customImages;
|
||||||
|
if (!customImages || !customImages[robotName]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return customImages[robotName][state] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单个机器人的自定义图片
|
||||||
|
* @param robotName 机器人名称
|
||||||
|
* @param state 图片状态 ('normal' | 'active'),不传则删除所有状态
|
||||||
|
*/
|
||||||
|
public removeRobotCustomImage(robotName: string, state?: 'normal' | 'active'): void {
|
||||||
|
const customImages = this.config.value.robot.customImages;
|
||||||
|
if (!customImages || !customImages[robotName]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
// 删除特定状态的图片
|
||||||
|
this.setNestedValue(this.config.value, `robot.customImages.${robotName}.${state}`, '');
|
||||||
|
} else {
|
||||||
|
// 删除整个机器人的图片配置
|
||||||
|
delete customImages[robotName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否还有任何自定义图片
|
||||||
|
const hasAnyCustomImages = Object.values(customImages).some(robot =>
|
||||||
|
robot.normal || robot.active
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasAnyCustomImages) {
|
||||||
|
this.setColor('robot.useCustomImages', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.triggerRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查机器人是否有自定义图片
|
||||||
|
* @param robotName 机器人名称
|
||||||
|
*/
|
||||||
|
public hasRobotCustomImage(robotName: string): boolean {
|
||||||
|
const customImages = this.config.value.robot.customImages;
|
||||||
|
if (!customImages || !customImages[robotName]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !!(customImages[robotName].normal || customImages[robotName].active);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有有自定义图片的机器人名称列表
|
||||||
|
*/
|
||||||
|
public getRobotsWithCustomImages(): string[] {
|
||||||
|
const customImages = this.config.value.robot.customImages;
|
||||||
|
if (!customImages) return [];
|
||||||
|
|
||||||
|
return Object.keys(customImages).filter(robotName =>
|
||||||
|
customImages[robotName].normal || customImages[robotName].active
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ColorConfigService();
|
export default new ColorConfigService();
|
||||||
|
|||||||
@ -85,10 +85,18 @@ export function parsePenData(penData: Record<string, unknown>): ParsedEventData
|
|||||||
|
|
||||||
if (tags.includes('robot')) {
|
if (tags.includes('robot')) {
|
||||||
// 机器人区域
|
// 机器人区域
|
||||||
|
// 机器人的真实名称存储在text字段中,而不是name字段
|
||||||
|
const robotName = (pen?.text as string) || name || `机器人-${id}`;
|
||||||
|
console.log('解析pen数据中的机器人信息:', {
|
||||||
|
id,
|
||||||
|
originalName: name,
|
||||||
|
penText: pen?.text,
|
||||||
|
finalName: robotName
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
type: 'robot',
|
type: 'robot',
|
||||||
id,
|
id,
|
||||||
name,
|
name: robotName,
|
||||||
tags,
|
tags,
|
||||||
target: document.elementFromPoint(position.x, position.y) as HTMLElement,
|
target: document.elementFromPoint(position.x, position.y) as HTMLElement,
|
||||||
position,
|
position,
|
||||||
@ -198,10 +206,18 @@ export function parseEventData(event: MouseEvent | PointerEvent): ParsedEventDat
|
|||||||
|
|
||||||
if (tags.includes('robot')) {
|
if (tags.includes('robot')) {
|
||||||
// 机器人区域
|
// 机器人区域
|
||||||
|
// 机器人的真实名称存储在text字段中,而不是name字段
|
||||||
|
const robotName = (pen?.text as string) || name || `机器人-${id}`;
|
||||||
|
console.log('解析机器人信息:', {
|
||||||
|
id,
|
||||||
|
originalName: name,
|
||||||
|
penText: pen?.text,
|
||||||
|
finalName: robotName
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
type: 'robot',
|
type: 'robot',
|
||||||
id,
|
id,
|
||||||
name,
|
name: robotName,
|
||||||
tags,
|
tags,
|
||||||
target,
|
target,
|
||||||
position,
|
position,
|
||||||
@ -282,7 +298,11 @@ export function parseEventData(event: MouseEvent | PointerEvent): ParsedEventDat
|
|||||||
export function isClickInsideMenu(event: MouseEvent, isMenuVisible: boolean): boolean {
|
export function isClickInsideMenu(event: MouseEvent, isMenuVisible: boolean): boolean {
|
||||||
if (!isMenuVisible) return false;
|
if (!isMenuVisible) return false;
|
||||||
|
|
||||||
const menuElement = document.querySelector('.context-menu');
|
// 查找右键菜单元素,支持多种可能的类名
|
||||||
|
const menuElement = document.querySelector('.context-menu-overlay') ||
|
||||||
|
document.querySelector('.context-menu') ||
|
||||||
|
document.querySelector('[class*="context-menu"]');
|
||||||
|
|
||||||
if (!menuElement) return false;
|
if (!menuElement) return false;
|
||||||
|
|
||||||
const rect = menuElement.getBoundingClientRect();
|
const rect = menuElement.getBoundingClientRect();
|
||||||
|
|||||||
@ -36,7 +36,8 @@ export type RobotAction =
|
|||||||
| 'stop' // 停止
|
| 'stop' // 停止
|
||||||
| 'reset' // 重置
|
| 'reset' // 重置
|
||||||
| 'diagnose' // 诊断
|
| 'diagnose' // 诊断
|
||||||
| 'update'; // 更新
|
| 'update' // 更新
|
||||||
|
| 'custom_image'; // 自定义图片
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取机器人信息
|
* 获取机器人信息
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
type Point,
|
type Point,
|
||||||
type Rect,
|
type Rect,
|
||||||
} from '@api/map';
|
} from '@api/map';
|
||||||
import type { RobotGroup, RobotInfo, RobotRealtimeInfo, RobotType } from '@api/robot';
|
import type { RobotGroup, RobotInfo, RobotType } from '@api/robot';
|
||||||
import type {
|
import type {
|
||||||
BinLocationGroup,
|
BinLocationGroup,
|
||||||
BinLocationItem,
|
BinLocationItem,
|
||||||
@ -25,7 +25,7 @@ import type {
|
|||||||
import sTheme from '@core/theme.service';
|
import sTheme from '@core/theme.service';
|
||||||
import { CanvasLayer, LockState, Meta2d, type Meta2dStore, type Pen, s8 } from '@meta2d/core';
|
import { CanvasLayer, LockState, Meta2d, type Meta2dStore, type Pen, s8 } from '@meta2d/core';
|
||||||
import { useObservable } from '@vueuse/rxjs';
|
import { useObservable } from '@vueuse/rxjs';
|
||||||
import { clone, get, isEmpty, isNil, isString, nth, omitBy, pick, remove, some } from 'lodash-es';
|
import { clone, get, isEmpty, isNil, isString, nth, pick, remove, some } from 'lodash-es';
|
||||||
import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from 'rxjs';
|
import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from 'rxjs';
|
||||||
import { reactive, watch } from 'vue';
|
import { reactive, watch } from 'vue';
|
||||||
|
|
||||||
@ -1105,7 +1105,7 @@ export class EditorService extends Meta2d {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.robots.map(async ({ id, label, type }) => {
|
this.robots.map(async ({ id, label, type }) => {
|
||||||
const pen: MapPen = {
|
const pen: MapPen = {
|
||||||
...this.#mapRobotImage(type, true),
|
...this.#mapRobotImage(type, true, label), // 传递机器人名称(label)
|
||||||
id,
|
id,
|
||||||
name: 'robot',
|
name: 'robot',
|
||||||
tags: ['robot'],
|
tags: ['robot'],
|
||||||
@ -1171,10 +1171,10 @@ export class EditorService extends Meta2d {
|
|||||||
* 当机器人图片尺寸配置发生变化时调用
|
* 当机器人图片尺寸配置发生变化时调用
|
||||||
*/
|
*/
|
||||||
public updateAllRobotImageSizes(): void {
|
public updateAllRobotImageSizes(): void {
|
||||||
this.robots.forEach(({ id, type }) => {
|
this.robots.forEach(({ id, type, label }) => {
|
||||||
const pen = this.getPenById(id);
|
const pen = this.getPenById(id);
|
||||||
if (pen && pen.robot) {
|
if (pen && pen.robot) {
|
||||||
const imageConfig = this.#mapRobotImage(type, pen.robot.active);
|
const imageConfig = this.#mapRobotImage(type, pen.robot.active, label); // 传递机器人名称
|
||||||
this.setValue(
|
this.setValue(
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
@ -1188,14 +1188,56 @@ export class EditorService extends Meta2d {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新单个机器人的图片
|
||||||
|
* 当机器人自定义图片配置发生变化时调用
|
||||||
|
* @param robotName 机器人名称
|
||||||
|
*/
|
||||||
|
public updateRobotImage(robotName: string): void {
|
||||||
|
const robot = this.robots.find(r => r.label === robotName);
|
||||||
|
if (!robot) return;
|
||||||
|
|
||||||
|
const pen = this.getPenById(robot.id);
|
||||||
|
if (pen && pen.robot) {
|
||||||
|
const imageConfig = this.#mapRobotImage(robot.type, pen.robot.active, robotName);
|
||||||
|
this.setValue(
|
||||||
|
{
|
||||||
|
id: robot.id,
|
||||||
|
image: imageConfig.image,
|
||||||
|
iconWidth: imageConfig.iconWidth,
|
||||||
|
iconHeight: imageConfig.iconHeight,
|
||||||
|
iconTop: imageConfig.iconTop,
|
||||||
|
},
|
||||||
|
{ render: true, history: false, doEvent: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#mapRobotImage(
|
#mapRobotImage(
|
||||||
type: RobotType,
|
type: RobotType,
|
||||||
active?: boolean,
|
active?: boolean,
|
||||||
|
robotName?: string,
|
||||||
): Required<Pick<MapPen, 'image' | 'iconWidth' | 'iconHeight' | 'iconTop' | 'canvasLayer'>> {
|
): Required<Pick<MapPen, 'image' | 'iconWidth' | 'iconHeight' | 'iconTop' | 'canvasLayer'>> {
|
||||||
const theme = this.data().theme;
|
const theme = this.data().theme;
|
||||||
const image =
|
|
||||||
import.meta.env.BASE_URL + (active ? `/robot/${type}-active-${theme}.png` : `/robot/${type}-${theme}.png`);
|
// 检查是否启用自定义图片且有机器人名称
|
||||||
|
const useCustomImages = colorConfig.getColor('robot.useCustomImages') === 'true';
|
||||||
|
let image: string;
|
||||||
|
|
||||||
|
if (useCustomImages && robotName) {
|
||||||
|
// 尝试获取自定义图片
|
||||||
|
const customImage = colorConfig.getRobotCustomImage(robotName, active ? 'active' : 'normal');
|
||||||
|
if (customImage) {
|
||||||
|
image = customImage;
|
||||||
|
} else {
|
||||||
|
// 如果没有自定义图片,回退到默认图片
|
||||||
|
image = import.meta.env.BASE_URL + (active ? `/robot/${type}-active-${theme}.png` : `/robot/${type}-${theme}.png`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用默认图片
|
||||||
|
image = import.meta.env.BASE_URL + (active ? `/robot/${type}-active-${theme}.png` : `/robot/${type}-${theme}.png`);
|
||||||
|
}
|
||||||
|
|
||||||
// 使用配置的机器人图片尺寸,如果没有配置则使用默认值
|
// 使用配置的机器人图片尺寸,如果没有配置则使用默认值
|
||||||
const iconWidth = colorConfig.getColor('robot.imageWidth') ? Number(colorConfig.getColor('robot.imageWidth')) : 42;
|
const iconWidth = colorConfig.getColor('robot.imageWidth') ? Number(colorConfig.getColor('robot.imageWidth')) : 42;
|
||||||
@ -1634,6 +1676,16 @@ export class EditorService extends Meta2d {
|
|||||||
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
||||||
// 监听所有画布事件
|
// 监听所有画布事件
|
||||||
this.on('*', (e, v) => this.#listen(e, v));
|
this.on('*', (e, v) => this.#listen(e, v));
|
||||||
|
|
||||||
|
// 添加额外的右键事件监听器,确保阻止默认行为
|
||||||
|
const canvasElement = this.canvas as unknown as HTMLCanvasElement;
|
||||||
|
if (canvasElement && canvasElement.addEventListener) {
|
||||||
|
canvasElement.addEventListener('contextmenu', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
// 注册自定义绘制函数和锚点
|
// 注册自定义绘制函数和锚点
|
||||||
this.#register();
|
this.#register();
|
||||||
|
|
||||||
@ -1665,7 +1717,9 @@ export class EditorService extends Meta2d {
|
|||||||
});
|
});
|
||||||
this.find('robot').forEach((pen) => {
|
this.find('robot').forEach((pen) => {
|
||||||
if (!pen.robot?.type) return;
|
if (!pen.robot?.type) return;
|
||||||
this.canvas.updateValue(pen, this.#mapRobotImage(pen.robot.type, pen.robot.active));
|
// 从pen的text属性获取机器人名称
|
||||||
|
const robotName = pen.text || '';
|
||||||
|
this.canvas.updateValue(pen, this.#mapRobotImage(pen.robot.type, pen.robot.active, robotName));
|
||||||
});
|
});
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user