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 { reactive, watch } from 'vue';
|
||||
|
||||
import { AreaOperationService } from './area-operation.service';
|
||||
import { LayerManagerService } from './layer-manager.service';
|
||||
|
||||
/**
|
||||
@ -46,6 +47,9 @@ export class EditorService extends Meta2d {
|
||||
/** 图层管理服务实例 */
|
||||
private readonly layerManager: LayerManagerService;
|
||||
|
||||
/** 区域操作服务实例 */
|
||||
private readonly areaOperationService!: AreaOperationService;
|
||||
|
||||
//#region 场景文件管理
|
||||
/**
|
||||
* 加载场景文件到编辑器
|
||||
@ -782,6 +786,15 @@ export class EditorService extends Meta2d {
|
||||
/** 画布变化事件流,用于触发响应式数据更新 */
|
||||
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>(
|
||||
this.#change$$.pipe(
|
||||
@ -1267,6 +1280,9 @@ export class EditorService extends Meta2d {
|
||||
// 初始化图层管理服务
|
||||
this.layerManager = new LayerManagerService(this);
|
||||
|
||||
// 初始化区域操作服务
|
||||
this.areaOperationService = new AreaOperationService();
|
||||
|
||||
// 禁用第6个子元素的拖放功能
|
||||
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
||||
// 监听所有画布事件
|
||||
@ -1280,6 +1296,15 @@ export class EditorService extends Meta2d {
|
||||
(v) => this.#load(v),
|
||||
{ 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 {
|
||||
@ -1330,6 +1355,11 @@ export class EditorService extends Meta2d {
|
||||
break;
|
||||
case 'valueUpdate':
|
||||
this.#change$$.next(true);
|
||||
// 检查是否是区域属性更新
|
||||
if (v && v.area?.type && ['area'].includes(v.tags?.[0] || '')) {
|
||||
// 发送到防抖流,避免频繁触发
|
||||
this.#areaSizeChange$$.next(v);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'active':
|
||||
@ -1342,13 +1372,60 @@ export class EditorService extends Meta2d {
|
||||
case 'mouseup':
|
||||
this.#mouse$$.next({ type: e, value: pick(this.getPenRect(v), 'x', 'y') });
|
||||
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:
|
||||
// console.log(e, v);
|
||||
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() {
|
||||
this.register({ line: () => new Path2D() });
|
||||
this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea, robot: drawRobot });
|
||||
|
Loading…
x
Reference in New Issue
Block a user