feat: 添加区域操作服务,支持区域大小变化的防抖处理与更新功能
This commit is contained in:
parent
d48a96f36b
commit
3ce3e674ae
186
src/services/area-operation.service.ts
Normal file
186
src/services/area-operation.service.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import type { MapAreaInfo, MapPen } from '@api/map';
|
||||||
|
import { MapAreaType, MapPointType } from '@api/map';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区域操作服务类
|
||||||
|
* 负责处理区域大小变化时的点位绑定检查和管理
|
||||||
|
*/
|
||||||
|
export class AreaOperationService {
|
||||||
|
/**
|
||||||
|
* 处理区域大小变化事件
|
||||||
|
* 当互斥区或非互斥区的大小发生变化时,自动检查并绑定新包含的点位
|
||||||
|
* @param pen 发生变化的图形对象
|
||||||
|
* @param findPoints 查找点位的函数
|
||||||
|
* @param updateArea 更新区域的函数
|
||||||
|
*/
|
||||||
|
public handleAreaSizeChange(
|
||||||
|
pen: MapPen,
|
||||||
|
findPoints: (type: string) => MapPen[],
|
||||||
|
updateArea: (id: string, info: Partial<MapAreaInfo>) => void,
|
||||||
|
): void {
|
||||||
|
// 检查是否为区域类型
|
||||||
|
if (!pen.area?.type || !['area'].includes(pen.tags?.[0] || '')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const areaType = pen.area.type;
|
||||||
|
|
||||||
|
// 只处理互斥区和非互斥区
|
||||||
|
if (![MapAreaType.互斥区, MapAreaType.非互斥区].includes(areaType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取区域边界
|
||||||
|
const areaRect = this.getPenRect(pen);
|
||||||
|
if (!areaRect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找所有点位
|
||||||
|
const allPoints = findPoints('point') as MapPen[];
|
||||||
|
|
||||||
|
// 根据区域类型过滤点位,借鉴 addArea 的逻辑
|
||||||
|
const containedPoints: string[] = [];
|
||||||
|
const currentBoundPoints = pen.area.points || [];
|
||||||
|
|
||||||
|
allPoints.forEach((point) => {
|
||||||
|
if (!point.point?.type) return;
|
||||||
|
|
||||||
|
// 根据区域类型过滤点位类型
|
||||||
|
let shouldInclude = false;
|
||||||
|
switch (areaType) {
|
||||||
|
case MapAreaType.互斥区:
|
||||||
|
case MapAreaType.非互斥区:
|
||||||
|
// 互斥区和非互斥区绑定所有类型的点位
|
||||||
|
shouldInclude = true;
|
||||||
|
break;
|
||||||
|
case MapAreaType.库区:
|
||||||
|
// 库区只绑定动作点
|
||||||
|
shouldInclude = point.point.type === MapPointType.动作点;
|
||||||
|
break;
|
||||||
|
case MapAreaType.约束区:
|
||||||
|
// 约束区绑定所有类型的点位
|
||||||
|
shouldInclude = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shouldInclude = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldInclude) return;
|
||||||
|
|
||||||
|
// 检查点位是否在区域内(使用点位中心坐标)
|
||||||
|
const pointCenter = this.getPointRect(point);
|
||||||
|
if (!pointCenter) return;
|
||||||
|
|
||||||
|
const isContained = this.isPointInArea(pointCenter, areaRect);
|
||||||
|
|
||||||
|
if (isContained) {
|
||||||
|
containedPoints.push(point.id!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 找出新包含的点位(之前未绑定的)
|
||||||
|
const newPoints = containedPoints.filter((id) => !currentBoundPoints.includes(id));
|
||||||
|
|
||||||
|
// 找出需要解绑的点位(之前绑定但现在不在区域内的)
|
||||||
|
const removedPoints = currentBoundPoints.filter((id) => !containedPoints.includes(id));
|
||||||
|
|
||||||
|
if (newPoints.length > 0 || removedPoints.length > 0) {
|
||||||
|
// 更新区域绑定的点位:保留仍在区域内的点位,添加新包含的点位
|
||||||
|
const updatedPoints = containedPoints;
|
||||||
|
|
||||||
|
updateArea(pen.id!, { points: updatedPoints });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查点位是否在指定区域内
|
||||||
|
* @param point 点位坐标
|
||||||
|
* @param area 区域边界
|
||||||
|
* @returns 是否在区域内
|
||||||
|
*/
|
||||||
|
private isPointInArea(
|
||||||
|
point: { x: number; y: number },
|
||||||
|
area: { x: number; y: number; width: number; height: number },
|
||||||
|
): boolean {
|
||||||
|
return point.x >= area.x && point.x <= area.x + area.width && point.y >= area.y && point.y <= area.y + area.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取区域边界矩形
|
||||||
|
* @param pen 区域图形对象
|
||||||
|
* @returns 区域边界矩形
|
||||||
|
*/
|
||||||
|
private getPenRect(pen: MapPen): { x: number; y: number; width: number; height: number } | null {
|
||||||
|
if (!pen.x || !pen.y || !pen.width || !pen.height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: pen.x,
|
||||||
|
y: pen.y,
|
||||||
|
width: pen.width,
|
||||||
|
height: pen.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点位中心坐标矩形
|
||||||
|
* @param pen 点位图形对象
|
||||||
|
* @returns 点位中心坐标矩形
|
||||||
|
*/
|
||||||
|
private getPointRect(pen: MapPen): { x: number; y: number; width: number; height: number } | null {
|
||||||
|
if (!pen.x || !pen.y || !pen.width || !pen.height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: pen.x + pen.width / 2,
|
||||||
|
y: pen.y + pen.height / 2,
|
||||||
|
width: pen.width,
|
||||||
|
height: pen.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动检查并更新指定区域的点位绑定
|
||||||
|
* 当用户调整区域大小后,可以调用此方法手动触发检查
|
||||||
|
* @param areaId 区域ID
|
||||||
|
* @param getPenById 根据ID获取图形对象的函数
|
||||||
|
* @param findPoints 查找点位的函数
|
||||||
|
* @param updateArea 更新区域的函数
|
||||||
|
*/
|
||||||
|
public checkAndUpdateAreaPoints(
|
||||||
|
areaId: string,
|
||||||
|
getPenById: (id: string) => MapPen | undefined,
|
||||||
|
findPoints: (type: string) => MapPen[],
|
||||||
|
updateArea: (id: string, info: Partial<MapAreaInfo>) => void,
|
||||||
|
): void {
|
||||||
|
const pen = getPenById(areaId);
|
||||||
|
if (!pen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleAreaSizeChange(pen, findPoints, updateArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并更新所有互斥区和非互斥区的点位绑定
|
||||||
|
* 用于批量检查和更新
|
||||||
|
* @param findAreas 查找区域的函数
|
||||||
|
* @param findPoints 查找点位的函数
|
||||||
|
* @param updateArea 更新区域的函数
|
||||||
|
*/
|
||||||
|
public checkAndUpdateAllAreas(
|
||||||
|
findAreas: (type: string) => MapPen[],
|
||||||
|
findPoints: (type: string) => MapPen[],
|
||||||
|
updateArea: (id: string, info: Partial<MapAreaInfo>) => void,
|
||||||
|
): void {
|
||||||
|
const allAreas = findAreas('area') as MapPen[];
|
||||||
|
|
||||||
|
allAreas.forEach((area) => {
|
||||||
|
if (area.area?.type && [MapAreaType.互斥区, MapAreaType.非互斥区].includes(area.area.type)) {
|
||||||
|
this.handleAreaSizeChange(area, findPoints, updateArea);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ import { clone, get, isEmpty, isNil, isString, nth, omitBy, pick, remove, some }
|
|||||||
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';
|
||||||
|
|
||||||
|
import { AreaOperationService } from './area-operation.service';
|
||||||
import { LayerManagerService } from './layer-manager.service';
|
import { LayerManagerService } from './layer-manager.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,6 +47,9 @@ export class EditorService extends Meta2d {
|
|||||||
/** 图层管理服务实例 */
|
/** 图层管理服务实例 */
|
||||||
private readonly layerManager: LayerManagerService;
|
private readonly layerManager: LayerManagerService;
|
||||||
|
|
||||||
|
/** 区域操作服务实例 */
|
||||||
|
private readonly areaOperationService!: AreaOperationService;
|
||||||
|
|
||||||
//#region 场景文件管理
|
//#region 场景文件管理
|
||||||
/**
|
/**
|
||||||
* 加载场景文件到编辑器
|
* 加载场景文件到编辑器
|
||||||
@ -782,6 +786,15 @@ export class EditorService extends Meta2d {
|
|||||||
/** 画布变化事件流,用于触发响应式数据更新 */
|
/** 画布变化事件流,用于触发响应式数据更新 */
|
||||||
readonly #change$$ = new Subject<boolean>();
|
readonly #change$$ = new Subject<boolean>();
|
||||||
|
|
||||||
|
/** 区域大小变化防抖处理的事件流 */
|
||||||
|
readonly #areaSizeChange$$ = new Subject<MapPen>();
|
||||||
|
|
||||||
|
/** 防抖处理后的区域大小变化事件,延迟500ms执行 */
|
||||||
|
readonly #debouncedAreaSizeChange = this.#areaSizeChange$$.pipe(
|
||||||
|
debounceTime(500), // 500ms防抖延迟
|
||||||
|
map((pen) => pen),
|
||||||
|
);
|
||||||
|
|
||||||
/** 当前选中的图形对象,响应式更新 */
|
/** 当前选中的图形对象,响应式更新 */
|
||||||
public readonly current = useObservable<MapPen>(
|
public readonly current = useObservable<MapPen>(
|
||||||
this.#change$$.pipe(
|
this.#change$$.pipe(
|
||||||
@ -1267,6 +1280,9 @@ export class EditorService extends Meta2d {
|
|||||||
// 初始化图层管理服务
|
// 初始化图层管理服务
|
||||||
this.layerManager = new LayerManagerService(this);
|
this.layerManager = new LayerManagerService(this);
|
||||||
|
|
||||||
|
// 初始化区域操作服务
|
||||||
|
this.areaOperationService = new AreaOperationService();
|
||||||
|
|
||||||
// 禁用第6个子元素的拖放功能
|
// 禁用第6个子元素的拖放功能
|
||||||
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
||||||
// 监听所有画布事件
|
// 监听所有画布事件
|
||||||
@ -1280,6 +1296,15 @@ export class EditorService extends Meta2d {
|
|||||||
(v) => this.#load(v),
|
(v) => this.#load(v),
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 订阅防抖处理后的区域大小变化事件
|
||||||
|
this.#debouncedAreaSizeChange.subscribe((pen: MapPen) => {
|
||||||
|
this.areaOperationService.handleAreaSizeChange(
|
||||||
|
pen,
|
||||||
|
(type) => this.find(type),
|
||||||
|
(id, info) => this.updateArea(id, info),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#load(theme: string): void {
|
#load(theme: string): void {
|
||||||
@ -1330,6 +1355,11 @@ export class EditorService extends Meta2d {
|
|||||||
break;
|
break;
|
||||||
case 'valueUpdate':
|
case 'valueUpdate':
|
||||||
this.#change$$.next(true);
|
this.#change$$.next(true);
|
||||||
|
// 检查是否是区域属性更新
|
||||||
|
if (v && v.area?.type && ['area'].includes(v.tags?.[0] || '')) {
|
||||||
|
// 发送到防抖流,避免频繁触发
|
||||||
|
this.#areaSizeChange$$.next(v);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'active':
|
case 'active':
|
||||||
@ -1342,13 +1372,60 @@ export class EditorService extends Meta2d {
|
|||||||
case 'mouseup':
|
case 'mouseup':
|
||||||
this.#mouse$$.next({ type: e, value: pick(this.getPenRect(v), 'x', 'y') });
|
this.#mouse$$.next({ type: e, value: pick(this.getPenRect(v), 'x', 'y') });
|
||||||
break;
|
break;
|
||||||
|
// 监听区域调整大小事件
|
||||||
|
case 'resizePens': {
|
||||||
|
// resizePens 事件的目标可能是 undefined,需要从 store.active 获取
|
||||||
|
const activePens = this.store.active;
|
||||||
|
if (activePens && activePens.length > 0) {
|
||||||
|
activePens.forEach((pen: MapPen) => {
|
||||||
|
if (pen.area?.type && ['area'].includes(pen.tags?.[0] || '')) {
|
||||||
|
// 发送到防抖流,避免频繁触发
|
||||||
|
this.#areaSizeChange$$.next(pen);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听区域线条更新事件,这通常是区域位置变化时触发的
|
||||||
|
case 'updateLines':
|
||||||
|
if (v && v.area?.type && ['area'].includes(v.tags?.[0] || '')) {
|
||||||
|
// 发送到防抖流,避免频繁触发
|
||||||
|
this.#areaSizeChange$$.next(v);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// console.log(e, v);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动检查并更新指定区域的点位绑定
|
||||||
|
* 当用户调整区域大小后,可以调用此方法手动触发检查
|
||||||
|
* @param areaId 区域ID
|
||||||
|
*/
|
||||||
|
public checkAndUpdateAreaPoints(areaId: string): void {
|
||||||
|
this.areaOperationService.checkAndUpdateAreaPoints(
|
||||||
|
areaId,
|
||||||
|
(id) => this.getPenById(id),
|
||||||
|
(type) => this.find(type),
|
||||||
|
(id, info) => this.updateArea(id, info),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并更新所有互斥区和非互斥区的点位绑定
|
||||||
|
* 用于批量检查和更新
|
||||||
|
*/
|
||||||
|
public checkAndUpdateAllAreas(): void {
|
||||||
|
this.areaOperationService.checkAndUpdateAllAreas(
|
||||||
|
(type) => this.find(type),
|
||||||
|
(type) => this.find(type),
|
||||||
|
(id, info) => this.updateArea(id, info),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#register() {
|
#register() {
|
||||||
this.register({ line: () => new Path2D() });
|
this.register({ line: () => new Path2D() });
|
||||||
this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea, robot: drawRobot });
|
this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea, robot: drawRobot });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user