feat: 新增约束区类型及最大可容纳AMR数配置,优化场景编辑功能

This commit is contained in:
xudan 2025-07-01 14:38:21 +08:00
parent 0479943204
commit 33f665d5fd
13 changed files with 81 additions and 11 deletions

View File

@ -102,6 +102,8 @@ export enum MapAreaType {
= 11,
/** 非互斥区 - 可以同时有多个机器人进入的区域 */
,
/** 约束区 - 机器人运动受到特定约束的区域 */
,
}
/**

View File

@ -43,6 +43,7 @@ export interface MapAreaInfo {
type: MapAreaType; // 区域类型
points?: Array<string>; // 绑定点位id集合
routes?: Array<string>; // 绑定线路id集合
maxAmr?: number; // 最大可容纳AMR数
}
//#endregion

View File

@ -64,6 +64,7 @@ export interface StandardSceneArea {
type: number; // 区域类型
points?: Array<string>; // 绑定点位id集合
routes?: Array<string>; // 绑定线路id集合
maxAmr?: number; // 最大可容纳AMR数
config?: object; // 其它属性配置(可按需增加)
properties?: unknown; // 附加数据(前端不做任何处理)
}

View File

@ -8,6 +8,9 @@ $icons: (
area12-active,
area12-detail,
area12,
area13-active,
area13-detail,
area13,
battery_charge,
battery,
connect_off,

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

View File

@ -28,6 +28,8 @@
"fill-11": "#FF9A9A33",
"stroke-12": "#0DBB8A99",
"fill-12": "#0DBB8A33",
"stroke-13": "#e61e4aad",
"fill-13": "#e61e4a33",
"strokeActive": "#FCC947"
},
"robot": {

View File

@ -65,6 +65,21 @@ const routes = computed<string[]>(
</a-input>
</a-col>
</a-row>
<a-row v-if="area.type === MapAreaType.约束区" :gutter="[8, 8]">
<a-col :span="24">
<a-typography-text>{{ $t('最大可容纳AMR数') }}:</a-typography-text>
</a-col>
<a-col :span="24">
<a-input-number
class="full"
:placeholder="$t('请输入数量')"
:min="0"
:max="99"
:value="pen.area?.maxAmr"
@change="editor.updateArea(id, { maxAmr: $event as number })"
/>
</a-col>
</a-row>
<a-row :gutter="[8, 8]">
<a-col :span="24">
@ -108,7 +123,7 @@ const routes = computed<string[]>(
</a-collapse-panel>
<a-collapse-panel
v-if="[MapAreaType.互斥区, MapAreaType.非互斥区].includes(area.type)"
v-if="[MapAreaType.互斥区, MapAreaType.非互斥区, MapAreaType.约束区].includes(area.type)"
:header="$t('绑定站点')"
>
<template #extra>

View File

@ -42,7 +42,7 @@ const canDelete = computed<boolean>(() => editor.value.current.value?.name === '
<template>
<a-space class="toolbar" :size="0">
<a-button
<!-- <a-button
class="icon-btn tool-btn"
:class="{ active: mode === MapAreaType.库区 }"
size="large"
@ -50,6 +50,15 @@ const canDelete = computed<boolean>(() => editor.value.current.value?.name === '
@click="mode = MapAreaType.库区"
>
<i class="icon" :class="mode === MapAreaType.库区 ? 'area1-active' : 'area1'" />
</a-button> -->
<a-button
class="icon-btn tool-btn"
:class="{ active: mode === MapAreaType.约束区 }"
size="large"
:title="$t('添加约束区')"
@click="mode = MapAreaType.约束区"
>
<i class="icon" :class="mode === MapAreaType.约束区 ? 'area1-active' : 'area1'" />
</a-button>
<a-button
class="icon-btn tool-btn ml-12"

View File

@ -2,6 +2,8 @@
import type { RobotRealtimeInfo } from '@api/robot';
import { getSceneByGroupId, getSceneById, monitorSceneById } from '@api/scene';
import { EditorService } from '@core/editor.service';
import { useViewState } from '@core/useViewState';
import { message } from 'ant-design-vue';
import { isNil } from 'lodash-es';
import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
@ -52,6 +54,8 @@ onMounted(async () => {
await readScene();
await editor.value?.initRobots();
await monitorScene();
//
await handleRestoreViewState();
});
onUnmounted(() => {
client.value?.close();
@ -80,6 +84,32 @@ const selectRobot = (id: string) => {
current.value = { type: 'robot', id };
editor.value?.inactive();
};
//
const { saveViewState, restoreViewState, isSaving } = useViewState();
//
const handleSaveViewState = async () => {
if (!editor.value) return;
try {
await saveViewState(editor.value, props.sid, props.id);
message.success('视图状态保存成功');
} catch {
message.error('视图状态保存失败');
}
};
//
const handleRestoreViewState = async () => {
if (!editor.value) return;
const restored = await restoreViewState(editor.value, props.sid, props.id);
if (!restored) {
// 使centerView
editor.value.centerView();
}
};
</script>
<template>
@ -87,6 +117,7 @@ const selectRobot = (id: string) => {
<a-layout-header class="p-16" style="height: 64px">
<a-flex justify="space-between" align="center">
<a-typography-text class="title">{{ title }}--场景仿真</a-typography-text>
<!-- <a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button> -->
</a-flex>
</a-layout-header>
@ -96,9 +127,9 @@ const selectRobot = (id: string) => {
<a-tab-pane key="1" :tab="$t('机器人')">
<RobotGroups v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
</a-tab-pane>
<a-tab-pane key="2" :tab="$t('库区')">
<!-- <a-tab-pane key="2" :tab="$t('库区')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
</a-tab-pane>
</a-tab-pane> -->
<a-tab-pane key="3" :tab="$t('高级组')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
</a-tab-pane>

View File

@ -29,13 +29,13 @@ const saveScene = async () => {
if (!json) return Promise.reject('无法获取场景数据');
const res = await saveSceneById(props.id, json);
if (!res) return Promise.reject('保存失败');
//
if (editor.value?.store) {
editor.value.store.historyIndex = undefined;
editor.value.store.histories = [];
}
message.success(t('场景保存成功'));
return Promise.resolve();
};
@ -47,7 +47,6 @@ const pushScene = async () => {
return Promise.resolve();
};
//#endregion
const title = ref<string>('');
@ -162,9 +161,9 @@ const selectRobot = (id: string) => {
show-group-edit
/>
</a-tab-pane>
<a-tab-pane key="2" :tab="$t('库区')">
<!-- <a-tab-pane key="2" :tab="$t('库区')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
</a-tab-pane>
</a-tab-pane> -->
<a-tab-pane key="3" :tab="$t('高级组')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
</a-tab-pane>

View File

@ -225,7 +225,7 @@ export class EditorService extends Meta2d {
#mapSceneArea(pen: MapPen): StandardSceneArea | null {
if (!pen.id || isEmpty(pen.area)) return null;
const { id, label, desc, properties } = pen;
const { type, points, routes } = pen.area;
const { type, points, routes, maxAmr } = pen.area;
const { x, y, width, height } = this.getPenRect(pen);
// 进行坐标转换:左上角原点 -> 中心点原点同时应用ratio缩放
const transformedCoords = this.#transformCoordinate(x, y);
@ -244,7 +244,7 @@ export class EditorService extends Meta2d {
if (MapAreaType. === type) {
area.points = points?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.);
}
if ([MapAreaType., MapAreaType.].includes(type)) {
if ([MapAreaType., MapAreaType., MapAreaType.].includes(type)) {
area.points = points?.filter((v) => {
const { point } = this.getPenById(v) ?? {};
if (isNil(point)) return false;
@ -255,6 +255,9 @@ export class EditorService extends Meta2d {
if (MapAreaType. === type) {
area.routes = routes?.filter((v) => !isEmpty(this.getPenById(v)?.area));
}
if (MapAreaType. === type) {
area.maxAmr = maxAmr;
}
return area;
}
//#endregion
@ -777,6 +780,10 @@ export class EditorService extends Meta2d {
case MapAreaType.:
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
break;
case MapAreaType.:
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
break;
default:
break;
}