import sTheme from '@core/theme.service'; import { ref, watch } from 'vue'; /** * 编辑器颜色配置接口 */ export interface EditorColorConfig { // 点位颜色 point: { small: { stroke: string; strokeActive: string; fill: Record; // 按点位类型索引 }; large: { stroke: string; strokeActive: string; strokeOccupied: string; strokeUnoccupied: string; strokeEmpty: string; strokeDisabled: string; strokeEnabled: string; strokeLocked: string; strokeUnlocked: string; }; // 各类型点位专用颜色 types: { [key: number]: { stroke: string; strokeActive: string; fill: string; }; }; }; // 路线颜色 route: { strokeActive: string; strokeEmpty: string; // 空载路线颜色 strokeLoaded: string; // 载货路线颜色 strokeForbidden: string; // 禁行路线颜色 }; // 区域颜色 area: { strokeActive: string; stroke: Record; // 按区域类型索引 fill: Record; // 按区域类型索引 // 各类型区域专用颜色 types: { [key: number]: { stroke: string; strokeActive: string; fill: string; }; }; }; // 机器人颜色 robot: { stroke: string; fill: string; line: string; strokeNormal: string; fillNormal: string; strokeWarning: string; fillWarning: string; strokeFault: string; fillFault: string; }; // 库位颜色 storage: { occupied: string; available: string; default: string; locked: string; moreButton: { background: string; border: string; text: string; }; }; // 自动门颜色 autoDoor: { strokeClosed: string; fillClosed: string; strokeOpen: string; fillOpen: string; }; // 通用颜色 common: { color: string; background: string; }; } /** * 默认颜色配置 */ const DEFAULT_COLORS: EditorColorConfig = { point: { small: { stroke: '#8C8C8C', strokeActive: '#EBB214', fill: { 1: '#14D1A5', 2: '#69C6F5', 3: '#E48B1D', 4: '#E48B1D', 5: '#a72b69' } }, large: { stroke: '#595959', strokeActive: '#EBB214', strokeOccupied: '#ff4d4f', strokeUnoccupied: '#52c41a', strokeEmpty: '#1890ff', strokeDisabled: '#bfbfbf', strokeEnabled: '#52c41a', strokeLocked: '#faad14', strokeUnlocked: '#52c41a' }, types: { // 小点位类型 (1-9) 1: { stroke: '#8C8C8C', strokeActive: '#EBB214', fill: '#14D1A5' }, // 普通点 2: { stroke: '#8C8C8C', strokeActive: '#EBB214', fill: '#69C6F5' }, // 等待点 3: { stroke: '#8C8C8C', strokeActive: '#EBB214', fill: '#E48B1D' }, // 避让点 4: { stroke: '#8C8C8C', strokeActive: '#EBB214', fill: '#E48B1D' }, // 临时避让点 5: { stroke: '#8C8C8C', strokeActive: '#EBB214', fill: '#a72b69' }, // 库区点 // 大点位类型 (11+) 11: { stroke: '#595959', strokeActive: '#EBB214', fill: '#1890ff' }, // 电梯点 12: { stroke: '#595959', strokeActive: '#EBB214', fill: '#52c41a' }, // 自动门点 13: { stroke: '#595959', strokeActive: '#EBB214', fill: '#faad14' }, // 充电点 14: { stroke: '#595959', strokeActive: '#EBB214', fill: '#722ed1' }, // 停靠点 15: { stroke: '#595959', strokeActive: '#EBB214', fill: '#13c2c2' }, // 动作点 16: { stroke: '#595959', strokeActive: '#EBB214', fill: '#ff4d4f' }, // 禁行点 } }, route: { strokeActive: '#EBB214', strokeEmpty: '#52C41A', // 空载路线 - 绿色 strokeLoaded: '#1982F3', // 载货路线 - 蓝色 strokeForbidden: '#E63A3A' // 禁行路线 - 红色 }, area: { strokeActive: '#EBB214', stroke: { 1: '#9ACDFF99', 11: '#FF535399', 12: '#0DBB8A99', 13: '#e61e4aad', 14: '#FFD70099' }, fill: { 1: '#9ACDFF33', 11: '#FF9A9A33', 12: '#0DBB8A33', 13: '#e61e4a33', 14: '#FFD70033' }, types: { // 区域类型 1: { stroke: '#9ACDFF99', strokeActive: '#EBB214', fill: '#9ACDFF33' }, // 库区 11: { stroke: '#FF535399', strokeActive: '#EBB214', fill: '#FF9A9A33' }, // 互斥区 12: { stroke: '#0DBB8A99', strokeActive: '#EBB214', fill: '#0DBB8A33' }, // 非互斥区 13: { stroke: '#e61e4aad', strokeActive: '#EBB214', fill: '#e61e4a33' }, // 约束区 14: { stroke: '#FFD70099', strokeActive: '#EBB214', fill: '#FFD70033' }, // 描述区 } }, robot: { stroke: '#01FDAF99', fill: '#01FAAD33', line: '#01fdaf', strokeNormal: '#01FDAF99', fillNormal: '#01FAAD33', strokeWarning: '#FF851B99', fillWarning: '#FF851B33', strokeFault: '#FF4D4F99', fillFault: '#FF4D4F33' }, storage: { occupied: '#ff4d4f', available: '#52c41a', default: '#f5f5f5', locked: '#faad14', moreButton: { background: '#e6f4ff', border: '#1677ff', text: '#1677ff' } }, autoDoor: { strokeClosed: '#FF4D4F99', fillClosed: '#FF4D4F33', strokeOpen: '#1890FF99', fillOpen: '#1890FF33' }, common: { color: '#595959', background: '#F0F2F5' } }; /** * 颜色配置管理服务 */ class ColorConfigService { private config = ref({ ...DEFAULT_COLORS }); private editorService: any = null; private readonly STORAGE_KEY = 'editor-color-config'; constructor() { // 从本地存储加载配置 this.loadFromLocalStorage(); // 监听主题变化,重新加载配置 watch( () => sTheme.theme, () => { this.loadConfig(); } ); // 监听配置变化,自动保存到本地存储 watch( () => this.config.value, (newConfig) => { this.saveToLocalStorage(newConfig); }, { deep: true } ); } /** * 获取当前颜色配置 */ public get currentConfig(): EditorColorConfig { return this.config.value; } /** * 设置编辑器服务实例 */ public setEditorService(editor: any): void { this.editorService = editor; } /** * 触发编辑器重新渲染 */ private triggerRender(): void { if (this.editorService && typeof this.editorService.render === 'function') { this.editorService.render(); } } /** * 从本地存储加载配置 */ private loadFromLocalStorage(): void { try { const stored = localStorage.getItem(this.STORAGE_KEY); if (stored) { const parsedConfig = JSON.parse(stored); this.config.value = this.mergeConfig(DEFAULT_COLORS, parsedConfig); } else { this.config.value = { ...DEFAULT_COLORS }; } } catch (error) { console.warn('Failed to load color config from localStorage:', error); this.config.value = { ...DEFAULT_COLORS }; } } /** * 保存配置到本地存储 */ private saveToLocalStorage(config: EditorColorConfig): void { try { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config)); } catch (error) { console.warn('Failed to save color config to localStorage:', error); } } /** * 从场景数据加载配置(已废弃,现在使用本地存储) * @deprecated 使用本地存储替代场景文件存储 */ public loadFromScene(): void { // 不再从场景数据加载,保持向后兼容 console.warn('loadFromScene is deprecated, color config now uses localStorage'); } /** * 获取当前配置(已废弃,现在使用本地存储) * @deprecated 使用本地存储替代场景文件存储 */ public getConfigForSave(): EditorColorConfig { console.warn('getConfigForSave is deprecated, color config now uses localStorage'); return { ...this.config.value }; } /** * 更新颜色配置 * @param updates 部分配置更新 */ public updateConfig(updates: Partial): void { this.config.value = { ...this.config.value, ...updates }; // 触发编辑器重新渲染 this.triggerRender(); } /** * 重置为默认配置 */ public resetToDefault(): void { this.config.value = { ...DEFAULT_COLORS }; // 触发编辑器重新渲染 this.triggerRender(); } /** * 获取特定类型的颜色 * @param path 颜色路径,如 'point.small.stroke' */ public getColor(path: string): string { return this.getNestedValue(this.config.value, path) || ''; } /** * 设置特定类型的颜色 * @param path 颜色路径 * @param value 颜色值 */ public setColor(path: string, value: string): void { this.setNestedValue(this.config.value, path, value); // 触发编辑器重新渲染 this.triggerRender(); } /** * 从主题配置加载默认颜色 */ private loadConfig(): void { const theme = sTheme.editor as any; if (theme) { // 从主题配置中加载颜色,如果没有则使用默认值 this.config.value = this.mergeConfig(DEFAULT_COLORS, { point: { small: { stroke: theme['point-s']?.stroke || DEFAULT_COLORS.point.small.stroke, strokeActive: theme['point-s']?.strokeActive || DEFAULT_COLORS.point.small.strokeActive, fill: { 1: theme['point-s']?.['fill-1'] || DEFAULT_COLORS.point.small.fill[1], 2: theme['point-s']?.['fill-2'] || DEFAULT_COLORS.point.small.fill[2], 3: theme['point-s']?.['fill-3'] || DEFAULT_COLORS.point.small.fill[3], 4: theme['point-s']?.['fill-4'] || DEFAULT_COLORS.point.small.fill[4], 5: theme['point-s']?.['fill-5'] || DEFAULT_COLORS.point.small.fill[5], } }, large: { stroke: theme['point-l']?.stroke || DEFAULT_COLORS.point.large.stroke, strokeActive: theme['point-l']?.strokeActive || DEFAULT_COLORS.point.large.strokeActive, strokeOccupied: theme['point-l']?.['stroke-occupied'] || DEFAULT_COLORS.point.large.strokeOccupied, strokeUnoccupied: theme['point-l']?.['stroke-unoccupied'] || DEFAULT_COLORS.point.large.strokeUnoccupied, strokeEmpty: theme['point-l']?.['stroke-empty'] || DEFAULT_COLORS.point.large.strokeEmpty, strokeDisabled: theme['point-l']?.['stroke-disabled'] || DEFAULT_COLORS.point.large.strokeDisabled, strokeEnabled: theme['point-l']?.['stroke-enabled'] || DEFAULT_COLORS.point.large.strokeEnabled, strokeLocked: theme['point-l']?.['stroke-locked'] || DEFAULT_COLORS.point.large.strokeLocked, strokeUnlocked: theme['point-l']?.['stroke-unlocked'] || DEFAULT_COLORS.point.large.strokeUnlocked, } }, route: { strokeActive: theme.route?.strokeActive || DEFAULT_COLORS.route.strokeActive, strokeEmpty: theme.route?.['stroke-empty'] || DEFAULT_COLORS.route.strokeEmpty, strokeLoaded: theme.route?.['stroke-loaded'] || DEFAULT_COLORS.route.strokeLoaded, strokeForbidden: theme.route?.['stroke-forbidden'] || DEFAULT_COLORS.route.strokeForbidden, }, area: { strokeActive: theme.area?.strokeActive || DEFAULT_COLORS.area.strokeActive, stroke: { 1: theme.area?.['stroke-1'] || DEFAULT_COLORS.area.stroke[1], 11: theme.area?.['stroke-11'] || DEFAULT_COLORS.area.stroke[11], 12: theme.area?.['stroke-12'] || DEFAULT_COLORS.area.stroke[12], 13: theme.area?.['stroke-13'] || DEFAULT_COLORS.area.stroke[13], 14: theme.area?.['stroke-14'] || DEFAULT_COLORS.area.stroke[14], }, fill: { 1: theme.area?.['fill-1'] || DEFAULT_COLORS.area.fill[1], 11: theme.area?.['fill-11'] || DEFAULT_COLORS.area.fill[11], 12: theme.area?.['fill-12'] || DEFAULT_COLORS.area.fill[12], 13: theme.area?.['fill-13'] || DEFAULT_COLORS.area.fill[13], 14: theme.area?.['fill-14'] || DEFAULT_COLORS.area.fill[14], } }, robot: { stroke: theme.robot?.stroke || DEFAULT_COLORS.robot.stroke, fill: theme.robot?.fill || DEFAULT_COLORS.robot.fill, line: theme.robot?.line || DEFAULT_COLORS.robot.line, strokeNormal: theme.robot?.['stroke-normal'] || DEFAULT_COLORS.robot.strokeNormal, fillNormal: theme.robot?.['fill-normal'] || DEFAULT_COLORS.robot.fillNormal, strokeWarning: theme.robot?.['stroke-warning'] || DEFAULT_COLORS.robot.strokeWarning, fillWarning: theme.robot?.['fill-warning'] || DEFAULT_COLORS.robot.fillWarning, strokeFault: theme.robot?.['stroke-fault'] || DEFAULT_COLORS.robot.strokeFault, fillFault: theme.robot?.['fill-fault'] || DEFAULT_COLORS.robot.fillFault, }, autoDoor: { strokeClosed: theme.autoDoor?.['stroke-closed'] || DEFAULT_COLORS.autoDoor.strokeClosed, fillClosed: theme.autoDoor?.['fill-closed'] || DEFAULT_COLORS.autoDoor.fillClosed, strokeOpen: theme.autoDoor?.['stroke-open'] || DEFAULT_COLORS.autoDoor.strokeOpen, fillOpen: theme.autoDoor?.['fill-open'] || DEFAULT_COLORS.autoDoor.fillOpen, }, common: { color: theme.color || DEFAULT_COLORS.common.color, background: theme.background || DEFAULT_COLORS.common.background, } }); } } /** * 深度合并配置对象 */ private mergeConfig(target: any, source: any): any { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.mergeConfig(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } /** * 获取嵌套对象的值 */ private getNestedValue(obj: any, path: string): any { return path.split('.').reduce((current, key) => current?.[key], obj); } /** * 设置嵌套对象的值 */ private setNestedValue(obj: any, path: string, value: any): void { const keys = path.split('.'); const lastKey = keys.pop()!; const target = keys.reduce((current, key) => { if (!current[key]) { // 如果是数字键,创建数组或对象 if (!isNaN(Number(key))) { current[key] = {}; } else { current[key] = {}; } } return current[key]; }, obj); target[lastKey] = value; } } export default new ColorConfigService();