diff --git a/src/components/modal/auto-create-storage-modal.vue b/src/components/modal/auto-create-storage-modal.vue new file mode 100644 index 0000000..ec39cb8 --- /dev/null +++ b/src/components/modal/auto-create-storage-modal.vue @@ -0,0 +1,159 @@ + + + + + + + 预览生成的库位: + + + + {{ point.label || point.id }} + 动作点 + + + + {{ locationName }} + + + + + + + + + + + + + + diff --git a/src/pages/scene-editor.vue b/src/pages/scene-editor.vue index c5b37b7..bff54b6 100644 --- a/src/pages/scene-editor.vue +++ b/src/pages/scene-editor.vue @@ -1,14 +1,17 @@ @@ -222,6 +255,15 @@ const backToCards = () => { + + + diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts index 9b0ea5d..4104bbe 100644 --- a/src/services/editor.service.ts +++ b/src/services/editor.service.ts @@ -38,6 +38,7 @@ import { } from './draw/storage-location-drawer'; import { LayerManagerService } from './layer-manager.service'; import { createStorageLocationUpdater,StorageLocationService } from './storage-location.service'; +import { AutoStorageGenerator } from './utils/auto-storage-generator'; /** * 场景编辑器服务类 @@ -59,6 +60,9 @@ export class EditorService extends Meta2d { /** 区域操作服务实例 */ private readonly areaOperationService!: AreaOperationService; + /** 自动生成库位工具实例 */ + private readonly autoStorageGenerator!: AutoStorageGenerator; + //#region 场景文件管理 /** * 加载场景文件到编辑器 @@ -947,9 +951,56 @@ export class EditorService extends Meta2d { /** * 为所有动作点创建库位pen对象 * 用于初始化或刷新所有动作点的库位显示 + * 优化性能:使用批量操作减少DOM操作次数 */ public createAllStorageLocationPens(): void { - this.storageLocationService?.createAll(); + if (!this.storageLocationService) return; + + // 使用 requestIdleCallback 在浏览器空闲时执行,避免阻塞UI + if (typeof requestIdleCallback !== 'undefined') { + requestIdleCallback(() => { + this.storageLocationService?.createAll(); + }, { timeout: 200 }); + } else { + // 降级到 requestAnimationFrame + requestAnimationFrame(() => { + this.storageLocationService?.createAll(); + }); + } + } + + /** + * 自动为动作点生成库位 + * @param areaName 库区名称 + * @param actionPoints 动作点列表 + */ + public autoCreateStorageLocations(areaName: string, actionPoints: MapPen[]): void { + this.autoStorageGenerator.autoCreateStorageLocations(areaName, actionPoints); + } + + /** + * 为指定动作点生成库位 + * @param pointId 动作点ID + * @param areaName 库区名称 + * @returns 生成的库位名称,如果失败返回null + */ + public generateStorageForPoint(pointId: string, areaName: string): string | null { + return this.autoStorageGenerator.generateStorageForPoint(pointId, areaName); + } + + /** + * 批量生成库位 + * @param pointIds 动作点ID列表 + * @param areaName 库区名称 + * @returns 生成结果统计 + */ + public batchGenerateStorageForPoints(pointIds: string[], areaName: string): { + success: number; + skipped: number; + failed: number; + generatedNames: string[]; + } { + return this.autoStorageGenerator.batchGenerateStorageForPoints(pointIds, areaName); } @@ -1197,14 +1248,14 @@ export class EditorService extends Meta2d { } } - public updatePoint(id: string, info: Partial): void { + public updatePoint(id: string, info: Partial, autoCreateStorage = true): void { const { point } = this.getPenById(id) ?? {}; if (!point?.type) return; const o = { ...point, ...info }; this.setValue({ id, point: o }, { render: true, history: true, doEvent: true }); // 如果是动作点且库位信息发生变化,重新创建库位pen对象 - if (point.type === MapPointType.动作点 && info.associatedStorageLocations) { + if (point.type === MapPointType.动作点 && info.associatedStorageLocations && autoCreateStorage) { this.createStorageLocationPens(id, info.associatedStorageLocations); } } @@ -1399,6 +1450,11 @@ export class EditorService extends Meta2d { }; const area = await this.addPen(pen, true, true, true); this.bottom(area); + + // 如果是库区且包含动作点,触发自动生成库位的确认对话框 + if (type === MapAreaType.库区 && points.length > 0) { + this.autoStorageGenerator.triggerAutoCreateStorageDialog(pen, points); + } } public updateArea(id: string, info: Partial): void { @@ -1425,6 +1481,9 @@ export class EditorService extends Meta2d { // 初始化库位服务 this.storageLocationService = new StorageLocationService(this, ''); + // 初始化自动生成库位工具 + this.autoStorageGenerator = new AutoStorageGenerator(this); + // 设置颜色配置服务的编辑器实例 colorConfig.setEditorService(this); diff --git a/src/services/storage-location.service.ts b/src/services/storage-location.service.ts index 4d946e2..08d4854 100644 --- a/src/services/storage-location.service.ts +++ b/src/services/storage-location.service.ts @@ -518,16 +518,10 @@ export class StorageLocationService { return; } - // 检查是否已存在库位pen对象 - const existingPens = this.getStorageLocationPens(pointId); + // 先删除已存在的库位pen对象,避免重复 + this.delete(pointId); - // 如果已存在pen对象,只更新状态,不重新创建 - if (existingPens.length > 0) { - this.update(pointId, storageLocations); - return; - } - - // 只有在不存在pen对象时才创建新的 + // 创建新的库位pen对象 const pointRect = this.editor.getPointRect(pointPen) ?? { x: 0, y: 0, width: 0, height: 0 }; const pens = createStorageLocationPens(pointId, storageLocations, pointRect, storageStateMap); @@ -605,14 +599,24 @@ export class StorageLocationService { if (!this.editor) return; const { penTypes, tags } = this.config; - const storagePens = this.editor.find( - `${penTypes.storageLocation},${penTypes.storageMore},${penTypes.storageBackground}` - ).filter( + + // 分别查找每种类型的库位pen对象 + const storageLocationPens = this.editor.find(penTypes.storageLocation).filter( (pen) => pen.tags?.includes(`${tags.pointPrefix}${pointId}`) ); - if (storagePens.length > 0) { - this.editor.delete(storagePens, true, true); + const storageMorePens = this.editor.find(penTypes.storageMore).filter( + (pen) => pen.tags?.includes(`${tags.pointPrefix}${pointId}`) + ); + + const storageBackgroundPens = this.editor.find(penTypes.storageBackground).filter( + (pen) => pen.tags?.includes(`${tags.pointPrefix}${pointId}`) + ); + + const allStoragePens = [...storageLocationPens, ...storageMorePens, ...storageBackgroundPens]; + + if (allStoragePens.length > 0) { + this.editor.delete(allStoragePens, true, true); } } diff --git a/src/services/utils/auto-storage-generator.ts b/src/services/utils/auto-storage-generator.ts new file mode 100644 index 0000000..985c53f --- /dev/null +++ b/src/services/utils/auto-storage-generator.ts @@ -0,0 +1,184 @@ +import type { MapPen } from '../../apis/map'; +import { MapPointType } from '../../apis/map'; +import type { EditorService } from '../editor.service'; + +/** + * 自动生成库位工具类 + * 负责处理库区创建时的库位自动生成逻辑 + */ +export class AutoStorageGenerator { + private editor: EditorService; + + constructor(editor: EditorService) { + this.editor = editor; + } + + /** + * 触发自动生成库位的确认对话框 + * @param area 库区对象 + * @param pointIds 动作点ID列表 + */ + public triggerAutoCreateStorageDialog(area: MapPen, pointIds: string[]): void { + const actionPoints = pointIds + .map(id => this.editor.getPenById(id)) + .filter((pen): pen is MapPen => !!pen && pen.point?.type === MapPointType.动作点); + + if (actionPoints.length === 0) return; + + // 触发自定义事件,让外部组件处理对话框显示 + this.editor.emit('autoCreateStorageDialog', { + areaName: area.label || area.id || '', + actionPoints + } as any); + } + + /** + * 自动为动作点生成库位 + * @param areaName 库区名称 + * @param actionPoints 动作点列表 + */ + public autoCreateStorageLocations(areaName: string, actionPoints: MapPen[]): void { + actionPoints.forEach(point => { + if (point.point?.type !== MapPointType.动作点) return; + + const pointName = point.label || point.id || ''; + const locationName = this.generateStorageLocationName(areaName, pointName); + + // 获取现有的库位列表,避免覆盖 + const existingLocations = point.point.associatedStorageLocations || []; + + // 检查是否已存在相同的库位名称 + if (existingLocations.includes(locationName)) { + return; // 如果已存在,跳过 + } + + // 添加新库位到现有列表 + const newLocations = [...existingLocations, locationName]; + + // 更新动作点的库位信息(禁用自动创建库位pen对象,避免重复) + this.editor.updatePoint(point.id!, { + associatedStorageLocations: newLocations + }, false); + + // 重新创建库位pen对象(create方法内部会先删除再创建) + this.editor.createStorageLocationPens(point.id!, newLocations); + }); + } + + /** + * 生成库位名称 + * @param areaName 库区名称 + * @param pointName 动作点名称 + * @returns 生成的库位名称 + */ + private generateStorageLocationName(areaName: string, pointName: string): string { + const baseName = `${areaName}_${pointName}_1`; + + // 检查是否已存在同名库位 + if (!this.checkStorageLocationExists(baseName)) { + return baseName; + } + + // 如果存在,则递增序号 + let counter = 2; + let newName = `${areaName}_${pointName}_${counter}`; + + while (this.checkStorageLocationExists(newName)) { + counter++; + newName = `${areaName}_${pointName}_${counter}`; + } + + return newName; + } + + /** + * 检查库位名称是否已存在 + * @param locationName 库位名称 + * @returns 如果存在则返回true + */ + private checkStorageLocationExists(locationName: string): boolean { + // 检查所有动作点的库位信息 + const allPoints = this.editor.find('point').filter( + pen => pen.point?.type === MapPointType.动作点 + ); + + return allPoints.some(point => + point.point?.associatedStorageLocations?.includes(locationName) + ); + } + + /** + * 为指定动作点生成库位(单个) + * @param pointId 动作点ID + * @param areaName 库区名称 + * @returns 生成的库位名称,如果失败返回null + */ + public generateStorageForPoint(pointId: string, areaName: string): string | null { + const point = this.editor.getPenById(pointId); + if (!point || point.point?.type !== MapPointType.动作点) { + return null; + } + + const pointName = point.label || point.id || ''; + const locationName = this.generateStorageLocationName(areaName, pointName); + + // 获取现有的库位列表 + const existingLocations = point.point.associatedStorageLocations || []; + + // 检查是否已存在相同的库位名称 + if (existingLocations.includes(locationName)) { + return null; // 如果已存在,返回null + } + + // 添加新库位到现有列表 + const newLocations = [...existingLocations, locationName]; + + // 先删除原有的库位pen对象,避免重复 + this.editor.deleteStorageLocation(pointId); + + // 更新动作点的库位信息 + this.editor.updatePoint(pointId, { + associatedStorageLocations: newLocations + }); + + // 重新创建库位pen对象 + this.editor.createStorageLocationPens(pointId, newLocations); + + return locationName; + } + + /** + * 批量生成库位(为多个动作点) + * @param pointIds 动作点ID列表 + * @param areaName 库区名称 + * @returns 生成结果统计 + */ + public batchGenerateStorageForPoints(pointIds: string[], areaName: string): { + success: number; + skipped: number; + failed: number; + generatedNames: string[]; + } { + let success = 0; + let skipped = 0; + let failed = 0; + const generatedNames: string[] = []; + + pointIds.forEach(pointId => { + const result = this.generateStorageForPoint(pointId, areaName); + if (result) { + success++; + generatedNames.push(result); + } else { + const point = this.editor.getPenById(pointId); + if (point && point.point?.type === MapPointType.动作点) { + skipped++; // 已存在相同名称 + } else { + failed++; // 不是动作点或不存在 + } + } + }); + + return { success, skipped, failed, generatedNames }; + } +} diff --git a/src/services/utils/index.ts b/src/services/utils/index.ts new file mode 100644 index 0000000..2cadeb4 --- /dev/null +++ b/src/services/utils/index.ts @@ -0,0 +1 @@ +export { AutoStorageGenerator } from './auto-storage-generator';