feat(area-detail-card): 新增门区域绑定路段显示,优化区域信息展示逻辑

This commit is contained in:
xudan 2025-10-20 19:02:48 +08:00
parent 2978c58b42
commit a0e33b2e86
3 changed files with 113 additions and 63 deletions

View File

@ -47,6 +47,15 @@ const bindPoint = computed<string>(
.join('、') ?? '', .join('、') ?? '',
); );
//
const bindRoutes = computed<string>(
() =>
area.value?.routes
?.map((rid) => editor.value.getRouteLabel(rid))
.filter((v) => !!v)
.join('、') ?? '',
);
const ruleText = computed(() => { const ruleText = computed(() => {
if (area.value?.inoutflag === 1) return '先进先出'; if (area.value?.inoutflag === 1) return '先进先出';
if (area.value?.inoutflag === 2) return '后进先出'; if (area.value?.inoutflag === 2) return '后进先出';
@ -74,26 +83,39 @@ const ruleText = computed(() => {
<a-list class="block mt-16"> <a-list class="block mt-16">
<a-list-item v-if="area.type === MapAreaType.约束区"> <a-list-item v-if="area.type === MapAreaType.约束区">
<a-flex :gap="8" vertical> <a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('最大可容纳AMR数') }}</a-typography-text> <a-typography-text type="secondary">{{ $t('最大AMR数') }}</a-typography-text>
<a-typography-text>{{ pen.area?.maxAmr ?? $t('无') }}</a-typography-text> <a-typography-text>{{ pen.area?.maxAmr ?? $t('无') }}</a-typography-text>
</a-flex> </a-flex>
</a-list-item> </a-list-item>
<a-list-item v-if="MapAreaType.库区 === area.type"> <a-list-item v-if="MapAreaType.库区 === area.type">
<a-flex :gap="8" vertical> <a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('库区规则') }}</a-typography-text> <a-typography-text type="secondary">{{ $t('进出规则') }}</a-typography-text>
<a-typography-text>{{ ruleText || $t('无') }}</a-typography-text> <a-typography-text>{{ ruleText || $t('无') }}</a-typography-text>
</a-flex> </a-flex>
</a-list-item> </a-list-item>
<a-list-item v-if="MapAreaType.库区 === area.type"> <a-list-item v-if="MapAreaType.库区 === area.type">
<a-flex :gap="8" vertical> <a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('绑定库位') }}</a-typography-text> <a-typography-text type="secondary">{{ $t('绑定库位') }}</a-typography-text>
<a-typography-text>{{ bindStorageLocations || $t('暂无') }}</a-typography-text> <a-typography-text>{{ bindStorageLocations || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<!-- 门区域显示设备与绑定路段 -->
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('设备ID') }}</a-typography-text>
<a-typography-text>{{ area.doorDeviceId || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('已绑定路段') }}</a-typography-text>
<a-typography-text>{{ bindRoutes || $t('无') }}</a-typography-text>
</a-flex> </a-flex>
</a-list-item> </a-list-item>
<a-list-item v-if="[MapAreaType.互斥区, MapAreaType.非互斥区, MapAreaType.约束区].includes(area.type)"> <a-list-item v-if="[MapAreaType.互斥区, MapAreaType.非互斥区, MapAreaType.约束区].includes(area.type)">
<a-flex :gap="8" vertical> <a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('绑定站点') }}</a-typography-text> <a-typography-text type="secondary">{{ $t('') }}</a-typography-text>
<a-typography-text>{{ bindPoint || $t('暂无') }}</a-typography-text> <a-typography-text>{{ bindPoint || $t('无') }}</a-typography-text>
</a-flex> </a-flex>
</a-list-item> </a-list-item>
</a-list> </a-list>

View File

@ -125,6 +125,8 @@ const syncDoorDeviceToRoutes = () => {
}); });
}; };
const saveScene = async (payload?: SaveScenePayload) => { const saveScene = async (payload?: SaveScenePayload) => {
// deviceId
try { syncDoorDeviceToRoutes(); } catch {}
const currentJson = payload?.json ?? editor.value?.save(); const currentJson = payload?.json ?? editor.value?.save();
if (currentJson) { if (currentJson) {
try { try {

View File

@ -23,7 +23,7 @@ import type {
import sTheme from '@core/theme.service'; import sTheme from '@core/theme.service';
import { LockState, Meta2d, type Pen } from '@meta2d/core'; import { LockState, Meta2d, type Pen } from '@meta2d/core';
import { useObservable } from '@vueuse/rxjs'; import { useObservable } from '@vueuse/rxjs';
import { clone, get, isEmpty, isNil, isString, pick, } from 'lodash-es'; import { clone, get, isEmpty, isNil, isString, pick } from 'lodash-es';
import { debounceTime, filter, map, Subject, switchMap } from 'rxjs'; import { debounceTime, filter, map, Subject, switchMap } from 'rxjs';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { watch } from 'vue'; import { watch } from 'vue';
@ -31,11 +31,7 @@ import { watch } from 'vue';
import { AreaOperationService } from './area-operation.service'; import { AreaOperationService } from './area-operation.service';
import { BinTaskManagerService } from './bintask-manager.service'; import { BinTaskManagerService } from './bintask-manager.service';
import colorConfig from './color/color-config.service'; import colorConfig from './color/color-config.service';
import { import { drawStorageBackground, drawStorageLocation, drawStorageMore } from './draw/storage-location-drawer';
drawStorageBackground,
drawStorageLocation,
drawStorageMore,
} from './draw/storage-location-drawer';
import { AreaManager } from './editor/area-manager.service'; import { AreaManager } from './editor/area-manager.service';
import { anchorPoint, drawArea, drawLine, drawPoint, lineBezier2, lineBezier3 } from './editor/editor-drawers'; import { anchorPoint, drawArea, drawLine, drawPoint, lineBezier2, lineBezier3 } from './editor/editor-drawers';
import { drawRobot, EditorRobotService } from './editor/editor-robot.service'; import { drawRobot, EditorRobotService } from './editor/editor-robot.service';
@ -90,7 +86,7 @@ export class EditorService extends Meta2d {
detail?: Partial<GroupSceneDetail>, detail?: Partial<GroupSceneDetail>,
isImport = false, isImport = false,
): Promise<void> { ): Promise<void> {
const sceneData = (isString(map) ? (map ? JSON.parse(map) : {}) : map); const sceneData = isString(map) ? (map ? JSON.parse(map) : {}) : map;
const scene: StandardScene = sceneData || {}; const scene: StandardScene = sceneData || {};
if (!isEmpty(detail?.group)) { if (!isEmpty(detail?.group)) {
scene.robotGroups = [detail.group]; scene.robotGroups = [detail.group];
@ -211,7 +207,6 @@ export class EditorService extends Meta2d {
* @param robots * @param robots
*/ */
/** /**
* *
* @param points * @param points
@ -280,7 +275,7 @@ export class EditorService extends Meta2d {
#loadSceneRoutes(routes?: StandardSceneRoute[], isImport = false): void { #loadSceneRoutes(routes?: StandardSceneRoute[], isImport = false): void {
if (!routes?.length) return; if (!routes?.length) return;
routes.map((v) => { routes.map((v) => {
const { id, desc, from, to, type, pass, c1, c2, properties, maxSpeed } = v; const { id, desc, from, to, type, pass, c1, c2, properties, maxSpeed } = v as any;
const p1 = this.getPenById(from); const p1 = this.getPenById(from);
const p2 = this.getPenById(to); const p2 = this.getPenById(to);
if (isNil(p1) || isNil(p2)) return; if (isNil(p1) || isNil(p2)) return;
@ -315,6 +310,10 @@ export class EditorService extends Meta2d {
c1: transformedC1, c1: transformedC1,
c2: transformedC2, c2: transformedC2,
maxSpeed, maxSpeed,
// 门区域扩展字段
deviceId: (v as any).deviceId,
deviceStatus: (v as any).deviceStatus,
isConnected: (v as any).isConnected,
}, },
}, },
{ render: false, history: false, doEvent: false }, { render: false, history: false, doEvent: false },
@ -330,7 +329,8 @@ export class EditorService extends Meta2d {
if (!areas?.length) return; if (!areas?.length) return;
await Promise.all( await Promise.all(
areas.map(async (v) => { areas.map(async (v) => {
const { id, name, desc, x, y, w, h, type, points, routes, maxAmr, inoutflag, storageLocations, properties } = v; const { id, name, desc, x, y, w, h, type, points, routes, maxAmr, inoutflag, storageLocations, properties } =
v as any;
// 只有在导入场景文件时才进行反向坐标转换 // 只有在导入场景文件时才进行反向坐标转换
let finalX = x; let finalX = x;
let finalY = y; let finalY = y;
@ -348,7 +348,7 @@ export class EditorService extends Meta2d {
await this.addArea({ x: finalX, y: finalY }, { x: finalX + finalW, y: finalY + finalH }, type, id); await this.addArea({ x: finalX, y: finalY }, { x: finalX + finalW, y: finalY + finalH }, type, id);
// 对于库区类型需要将点位名称数组转换为点位ID数组并更新动作点的库位信息 // 对于库区类型需要将点位名称数组转换为点位ID数组并更新动作点的库位信息
let processedPoints = points; let processedPoints: string[] | undefined = points as any;
if (type === MapAreaType. && points?.length) { if (type === MapAreaType. && points?.length) {
// 将点位名称数组转换为点位ID数组 // 将点位名称数组转换为点位ID数组
const actionPoints = this.find('point').filter( const actionPoints = this.find('point').filter(
@ -363,7 +363,7 @@ export class EditorService extends Meta2d {
const storageLocationsMap: Record<string, string[]> = {}; const storageLocationsMap: Record<string, string[]> = {};
storageLocations.forEach((item) => { storageLocations.forEach((item) => {
Object.entries(item).forEach(([pointName, locations]) => { Object.entries(item).forEach(([pointName, locations]) => {
storageLocationsMap[pointName] = locations; storageLocationsMap[pointName] = locations as unknown as string[];
}); });
}); });
@ -380,7 +380,23 @@ export class EditorService extends Meta2d {
} }
this.setValue( this.setValue(
{ id, label: name, desc, properties, area: { type, points: processedPoints, routes, maxAmr, inoutflag } }, {
id,
label: name,
desc,
properties,
area: {
type,
points: processedPoints,
routes,
maxAmr,
inoutflag,
// 门区域扩展字段
doorDeviceId: (v as any).doorDeviceId,
deviceStatus: (v as any).deviceStatus,
isConnected: (v as any).isConnected,
},
},
{ render: false, history: false, doEvent: false }, { render: false, history: false, doEvent: false },
); );
}), }),
@ -432,7 +448,7 @@ export class EditorService extends Meta2d {
#mapSceneRoute(pen?: MapPen): StandardSceneRoute | null { #mapSceneRoute(pen?: MapPen): StandardSceneRoute | null {
if (!pen?.id || pen.anchors?.length !== 2 || isEmpty(pen?.route)) return null; if (!pen?.id || pen.anchors?.length !== 2 || isEmpty(pen?.route)) return null;
const { id, anchors, desc, properties } = pen; const { id, anchors, desc, properties } = pen;
const { type, direction = 1, pass, c1, c2, maxSpeed } = pen.route; const { type, direction = 1, pass, c1, c2, maxSpeed, deviceId, deviceStatus, isConnected } = pen.route as any;
const [p1, p2] = anchors.map((v) => this.getPenById(v.connectTo!)); const [p1, p2] = anchors.map((v) => this.getPenById(v.connectTo!));
if (isNil(p1) || isNil(p2)) return null; if (isNil(p1) || isNil(p2)) return null;
const route: StandardSceneRoute = { const route: StandardSceneRoute = {
@ -446,6 +462,10 @@ export class EditorService extends Meta2d {
config: {}, config: {},
properties, properties,
}; };
// 门区域扩展字段:保持到顶层,便于后端识别
(route as any).deviceId = deviceId;
(route as any).deviceStatus = deviceStatus;
(route as any).isConnected = isConnected;
const { x: x1, y: y1 } = this.getPointRect(p1)!; const { x: x1, y: y1 } = this.getPointRect(p1)!;
const { x: x2, y: y2 } = this.getPointRect(p2)!; const { x: x2, y: y2 } = this.getPointRect(p2)!;
const cp1 = { x: x1 + (c1?.x ?? 0), y: y1 + (c1?.y ?? 0) }; const cp1 = { x: x1 + (c1?.x ?? 0), y: y1 + (c1?.y ?? 0) };
@ -472,7 +492,7 @@ export class EditorService extends Meta2d {
#mapSceneArea(pen: MapPen): StandardSceneArea | null { #mapSceneArea(pen: MapPen): StandardSceneArea | null {
if (!pen.id || isEmpty(pen.area)) return null; if (!pen.id || isEmpty(pen.area)) return null;
const { id, label, desc, properties } = pen; const { id, label, desc, properties } = pen;
const { type, points, maxAmr, inoutflag } = pen.area; const { type, points, routes, maxAmr, inoutflag, doorDeviceId, deviceStatus, isConnected } = pen.area as any;
const { x, y, width, height } = this.getPenRect(pen); const { x, y, width, height } = this.getPenRect(pen);
// 进行坐标转换:左上角原点 -> 中心点原点同时应用ratio缩放 // 进行坐标转换:左上角原点 -> 中心点原点同时应用ratio缩放
const transformedCoords = this.#transformCoordinate(x, y); const transformedCoords = this.#transformCoordinate(x, y);
@ -488,6 +508,11 @@ export class EditorService extends Meta2d {
config: {}, config: {},
properties, properties,
}; };
// 门区域扩展字段
(area as any).routes = routes;
(area as any).doorDeviceId = doorDeviceId;
(area as any).deviceStatus = deviceStatus;
(area as any).isConnected = isConnected;
if (type === MapAreaType.) { if (type === MapAreaType.) {
area.maxAmr = maxAmr; area.maxAmr = maxAmr;
} }
@ -655,7 +680,6 @@ export class EditorService extends Meta2d {
this.robotService.removeRobotsFromAllLabels(robotIds); this.robotService.removeRobotsFromAllLabels(robotIds);
} }
public async initRobots(): Promise<void> { public async initRobots(): Promise<void> {
await this.robotService.initRobots(); await this.robotService.initRobots();
} }
@ -668,7 +692,11 @@ export class EditorService extends Meta2d {
this.robotService.updateRobotImage(robotName); this.robotService.updateRobotImage(robotName);
} }
public updateRobotStatusOverlay(id: string, render = false, newPosition?: { x: number; y: number; rotate: number }): void { public updateRobotStatusOverlay(
id: string,
render = false,
newPosition?: { x: number; y: number; rotate: number },
): void {
this.robotService.updateRobotStatusOverlay(id, render, newPosition); this.robotService.updateRobotStatusOverlay(id, render, newPosition);
} }
//#endregion //#endregion
@ -751,7 +779,6 @@ export class EditorService extends Meta2d {
* *
*/ */
/** 画布变化事件流,用于触发响应式数据更新 */ /** 画布变化事件流,用于触发响应式数据更新 */
readonly #change$$ = new Subject<boolean>(); readonly #change$$ = new Subject<boolean>();
@ -874,7 +901,7 @@ export class EditorService extends Meta2d {
public updateStorageLocation( public updateStorageLocation(
pointId: string, pointId: string,
updates: string | Record<string, { occupied?: boolean; locked?: boolean }>, updates: string | Record<string, { occupied?: boolean; locked?: boolean }>,
state?: { occupied?: boolean; locked?: boolean } state?: { occupied?: boolean; locked?: boolean },
): void { ): void {
this.storageLocationService?.update(pointId, updates, state); this.storageLocationService?.update(pointId, updates, state);
} }
@ -908,9 +935,12 @@ export class EditorService extends Meta2d {
// 使用 requestIdleCallback 在浏览器空闲时执行避免阻塞UI // 使用 requestIdleCallback 在浏览器空闲时执行避免阻塞UI
if (typeof requestIdleCallback !== 'undefined') { if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(() => { requestIdleCallback(
() => {
this.storageLocationService?.createAll(); this.storageLocationService?.createAll();
}, { timeout: 200 }); },
{ timeout: 200 },
);
} else { } else {
// 降级到 requestAnimationFrame // 降级到 requestAnimationFrame
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -944,7 +974,10 @@ export class EditorService extends Meta2d {
* @param areaName * @param areaName
* @returns * @returns
*/ */
public batchGenerateStorageForPoints(pointIds: string[], areaName: string): { public batchGenerateStorageForPoints(
pointIds: string[],
areaName: string,
): {
success: number; success: number;
skipped: number; skipped: number;
failed: number; failed: number;
@ -953,8 +986,6 @@ export class EditorService extends Meta2d {
return this.autoStorageGenerator.batchGenerateStorageForPoints(pointIds, areaName); return this.autoStorageGenerator.batchGenerateStorageForPoints(pointIds, areaName);
} }
/** /**
* ID更新自动门点状态 * ID更新自动门点状态
* @param deviceId ID * @param deviceId ID
@ -1080,8 +1111,6 @@ export class EditorService extends Meta2d {
this.pointManager.changePointType(id, type); this.pointManager.changePointType(id, type);
} }
//#endregion //#endregion
//#region 线路 //#region 线路
@ -1268,7 +1297,6 @@ export class EditorService extends Meta2d {
}); });
} }
#listen(e: unknown, v: any) { #listen(e: unknown, v: any) {
switch (e) { switch (e) {
case 'opened': case 'opened':
@ -1372,14 +1400,14 @@ export class EditorService extends Meta2d {
* @param pointType * @param pointType
*/ */
public batchUpdatePointType(pointIds: string[], pointType: MapPointType): void { public batchUpdatePointType(pointIds: string[], pointType: MapPointType): void {
pointIds.forEach(id => { pointIds.forEach((id) => {
const pen = this.getPenById(id); const pen = this.getPenById(id);
if (pen?.name === 'point') { if (pen?.name === 'point') {
this.updatePen(id, { this.updatePen(id, {
point: { point: {
...pen.point, ...pen.point,
type: pointType type: pointType,
} },
}); });
} }
}); });
@ -1391,14 +1419,14 @@ export class EditorService extends Meta2d {
* @param routeType 线 * @param routeType 线
*/ */
public batchUpdateRouteType(routeIds: string[], routeType: MapRouteType): void { public batchUpdateRouteType(routeIds: string[], routeType: MapRouteType): void {
routeIds.forEach(id => { routeIds.forEach((id) => {
const pen = this.getPenById(id); const pen = this.getPenById(id);
if (pen?.name === 'line' && pen.route) { if (pen?.name === 'line' && pen.route) {
this.updatePen(id, { this.updatePen(id, {
route: { route: {
...pen.route, ...pen.route,
type: routeType type: routeType,
} },
}); });
} }
}); });
@ -1410,14 +1438,14 @@ export class EditorService extends Meta2d {
* @param passType * @param passType
*/ */
public batchUpdateRoutePassType(routeIds: string[], passType: MapRoutePassType): void { public batchUpdateRoutePassType(routeIds: string[], passType: MapRoutePassType): void {
routeIds.forEach(id => { routeIds.forEach((id) => {
const pen = this.getPenById(id); const pen = this.getPenById(id);
if (pen?.name === 'line' && pen.route) { if (pen?.name === 'line' && pen.route) {
this.updatePen(id, { this.updatePen(id, {
route: { route: {
...pen.route, ...pen.route,
pass: passType pass: passType,
} },
}); });
} }
}); });
@ -1429,14 +1457,14 @@ export class EditorService extends Meta2d {
* @param direction * @param direction
*/ */
public batchUpdateRouteDirection(routeIds: string[], direction: 1 | -1): void { public batchUpdateRouteDirection(routeIds: string[], direction: 1 | -1): void {
routeIds.forEach(id => { routeIds.forEach((id) => {
const pen = this.getPenById(id); const pen = this.getPenById(id);
if (pen?.name === 'line' && pen.route) { if (pen?.name === 'line' && pen.route) {
this.updatePen(id, { this.updatePen(id, {
route: { route: {
...pen.route, ...pen.route,
direction direction,
} },
}); });
} }
}); });
@ -1464,14 +1492,12 @@ export class EditorService extends Meta2d {
robot: drawRobot, robot: drawRobot,
'storage-location': drawStorageLocation, 'storage-location': drawStorageLocation,
'storage-more': drawStorageMore, 'storage-more': drawStorageMore,
'storage-background': drawStorageBackground 'storage-background': drawStorageBackground,
}); });
this.registerAnchors({ point: anchorPoint }); this.registerAnchors({ point: anchorPoint });
this.addDrawLineFn('bezier2', lineBezier2); this.addDrawLineFn('bezier2', lineBezier2);
this.addDrawLineFn('bezier3', lineBezier3); this.addDrawLineFn('bezier3', lineBezier3);
} }
} }
//#region 自定义绘制函数 //#region 自定义绘制函数