temp
16
README.md
@ -102,10 +102,14 @@ interface MapPen {
|
||||
interface MapPointInfo {
|
||||
type: MapPointType; // 点位类型
|
||||
robots?: Array<RobotInfo['id']>; // 绑定机器人id集合
|
||||
actions?: Array<string>; // 绑定动作点id集合
|
||||
isBlock?: boolean; // 是否禁行
|
||||
isForbidAvoid?: boolean; // 是否禁止避让
|
||||
}
|
||||
interface MapRouteInfo {
|
||||
type: MapRouteType; // 线路类型
|
||||
direction?: -1 | 1; // 线路方向
|
||||
direction?: -1 | 1; // 方向
|
||||
pass?: MapRoutePassType; // 可通行类型
|
||||
}
|
||||
interface MapAreaInfo {
|
||||
type: MapAreaType; // 区域类型
|
||||
@ -128,10 +132,20 @@ enum MapPointType {
|
||||
|
||||
障碍点 = 99, // 待优化,后续将单独抽离
|
||||
}
|
||||
|
||||
enum MapRouteType {
|
||||
直线 = 'line',
|
||||
三阶贝塞尔曲线 = 'bezier3',
|
||||
}
|
||||
enum MapRoutePassType {
|
||||
无,
|
||||
|
||||
仅空载可通行,
|
||||
仅载货可通行,
|
||||
|
||||
禁行 = 10,
|
||||
}
|
||||
|
||||
enum MapAreaType {
|
||||
库区 = 1,
|
||||
|
||||
|
@ -650,6 +650,11 @@
|
||||
color: get-color(text4);
|
||||
}
|
||||
|
||||
&.card-title {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
& > strong {
|
||||
font: 500 16px/22px Roboto;
|
||||
}
|
||||
|
@ -27,6 +27,13 @@ export enum MapRouteType {
|
||||
三阶贝塞尔曲线 = 'bezier3',
|
||||
}
|
||||
export const MAP_ROUTE_TYPES = Object.freeze(<[string, MapRouteType][]>Object.entries(MapRouteType));
|
||||
|
||||
export enum MapRoutePassType {
|
||||
无,
|
||||
仅空载可通行,
|
||||
仅载货可通行,
|
||||
禁行 = 10,
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 区域
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { RobotInfo } from '@api/robot';
|
||||
import type { Pen } from '@meta2d/core';
|
||||
|
||||
import type { MapAreaType, MapPointType, MapRouteType } from './constant';
|
||||
import type { MapAreaType, MapPointType, MapRoutePassType, MapRouteType } from './constant';
|
||||
|
||||
export interface MapPen extends Pen {
|
||||
label?: string; // 展示名称
|
||||
@ -19,13 +19,17 @@ export interface MapPen extends Pen {
|
||||
export interface MapPointInfo {
|
||||
type: MapPointType; // 点位类型
|
||||
robots?: Array<RobotInfo['id']>; // 绑定机器人id集合
|
||||
actions?: Array<string>; // 绑定动作点id集合
|
||||
isBlock?: boolean; // 是否禁行
|
||||
isForbidAvoid?: boolean; // 是否禁止避让
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 线路
|
||||
export interface MapRouteInfo {
|
||||
type: MapRouteType; // 线路类型
|
||||
direction?: -1 | 1; // 线路方向
|
||||
direction?: -1 | 1; // 方向
|
||||
pass?: MapRoutePassType; // 可通行类型
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
$icons: (
|
||||
area1-active,
|
||||
area1-detail,
|
||||
area1,
|
||||
area2-active,
|
||||
area2,
|
||||
area3-active,
|
||||
area3,
|
||||
area11-active,
|
||||
area11-detail,
|
||||
area11,
|
||||
area12-active,
|
||||
area12-detail,
|
||||
area12,
|
||||
battery_charge,
|
||||
battery,
|
||||
connect_off,
|
||||
@ -17,6 +20,7 @@ $icons: (
|
||||
exit,
|
||||
pen,
|
||||
plus,
|
||||
point,
|
||||
redo,
|
||||
register,
|
||||
robot,
|
||||
|
BIN
src/assets/icons/dark/area1-detail.png
Normal file
After Width: | Height: | Size: 590 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/icons/dark/area11-detail.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/icons/dark/area12-detail.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/icons/dark/point.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -13,5 +13,14 @@
|
||||
"stroke": "#595959",
|
||||
"strokeActive": "#FCC947"
|
||||
},
|
||||
"area": {
|
||||
"stroke-1": "#9ACDFF99",
|
||||
"fill-1": "#9ACDFF33",
|
||||
"stroke-11": "#FF535399",
|
||||
"fill-11": "#FF9A9A33",
|
||||
"stroke-12": "#0DBB8A99",
|
||||
"fill-12": "#0DBB8A33",
|
||||
"strokeActive": "#FCC947"
|
||||
},
|
||||
"line": "#8C8C8C"
|
||||
}
|
||||
|
79
src/components/card/area-detail-card.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { type MapAreaInfo, MapAreaType, type MapPen } from '@api/map';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import sTheme from '@core/theme.service';
|
||||
import { computed, inject, type InjectionKey, type ShallowRef } from 'vue';
|
||||
|
||||
type Props = {
|
||||
token: InjectionKey<ShallowRef<EditorService>>;
|
||||
current?: string;
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
|
||||
const pen = computed<MapPen | undefined>(() => editor.value.getPenById(props.current));
|
||||
const area = computed<MapAreaInfo | null>(() => {
|
||||
const area = pen.value?.area;
|
||||
if (!area?.type) return null;
|
||||
return area;
|
||||
});
|
||||
|
||||
const icon = computed<string>(() => `area${area.value?.type}-detail`);
|
||||
|
||||
const bindPoint = computed<string>(
|
||||
() =>
|
||||
area.value?.points
|
||||
?.map((v) => editor.value.getPenById(v)?.label)
|
||||
.filter((v) => !!v)
|
||||
.join('、') ?? '',
|
||||
);
|
||||
const bindRoute = computed<string>(
|
||||
() =>
|
||||
area.value?.routes
|
||||
?.map((v) => editor.value.getRouteLabel(v))
|
||||
.filter((v) => !!v)
|
||||
.join('、') ?? '',
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<template v-if="pen && area">
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col :span="24">
|
||||
<a-flex align="center" :gap="8">
|
||||
<i class="icon" :class="icon" />
|
||||
<a-typography-text class="card-title" style="flex: auto" :content="pen.label" ellipsis />
|
||||
<a-tag :bordered="false">{{ $t(MapAreaType[area.type]) }}</a-tag>
|
||||
</a-flex>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-typography-text code>{{ pen.desc || $t('暂无描述') }}</a-typography-text>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-list class="block mt-16">
|
||||
<a-list-item v-if="MapAreaType.库区 === area.type">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('绑定动作点') }}</a-typography-text>
|
||||
<a-typography-text>{{ bindPoint || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="[MapAreaType.互斥区, MapAreaType.非互斥区].includes(area.type)">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('绑定站点') }}</a-typography-text>
|
||||
<a-typography-text>{{ bindPoint || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="MapAreaType.互斥区 === area.type">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('绑定路段') }}</a-typography-text>
|
||||
<a-typography-text>{{ bindRoute || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
</a-card>
|
||||
</template>
|
115
src/components/card/point-detail-card.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { MapAreaType, type MapPen, type MapPointInfo, MapPointType } from '@api/map';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import sTheme from '@core/theme.service';
|
||||
import { computed, inject, type InjectionKey, type ShallowRef } from 'vue';
|
||||
|
||||
type Props = {
|
||||
token: InjectionKey<ShallowRef<EditorService>>;
|
||||
current?: string;
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
|
||||
const pen = computed<MapPen | undefined>(() => editor.value.getPenById(props.current));
|
||||
const point = computed<MapPointInfo | null>(() => {
|
||||
const point = pen.value?.point;
|
||||
if (!point?.type) return null;
|
||||
return point;
|
||||
});
|
||||
|
||||
const bindRobot = computed<string>(
|
||||
() =>
|
||||
point.value?.robots
|
||||
?.map((v) => editor.value.getRobotById(v)?.label)
|
||||
.filter((v) => !!v)
|
||||
.join('、') ?? '',
|
||||
);
|
||||
const bindAction = computed<string>(
|
||||
() =>
|
||||
point.value?.actions
|
||||
?.map((v) => editor.value.getPenById(v)?.label)
|
||||
.filter((v) => !!v)
|
||||
.join('、') ?? '',
|
||||
);
|
||||
|
||||
const mapAreas = (type: MapAreaType): string => {
|
||||
const id = pen.value?.id;
|
||||
if (!id) return '';
|
||||
return (
|
||||
editor.value
|
||||
.find(`area-${type}`)
|
||||
.filter(({ area }) => area?.points?.includes(id))
|
||||
?.map(({ label }) => label)
|
||||
.filter((v) => !!v)
|
||||
.join('、') ?? ''
|
||||
);
|
||||
};
|
||||
const coArea1 = computed<string>(() => mapAreas(MapAreaType.库区));
|
||||
const coArea2 = computed<string>(() => mapAreas(MapAreaType.互斥区));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<template v-if="pen && point">
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col :span="24">
|
||||
<a-flex align="center" :gap="8">
|
||||
<i class="icon point" />
|
||||
<a-typography-text class="card-title" style="flex: auto" :content="pen.label" ellipsis />
|
||||
<a-tag :bordered="false">{{ $t(MapPointType[point.type]) }}</a-tag>
|
||||
</a-flex>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-typography-text code>{{ pen.desc || $t('暂无描述') }}</a-typography-text>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-list class="block mt-16">
|
||||
<a-list-item>
|
||||
<a-typography-text type="secondary">{{ $t('站点坐标') }}</a-typography-text>
|
||||
<a-typography-text>({{ pen.x?.toFixed() }},{{ pen.y?.toFixed() }})</a-typography-text>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="[MapPointType.充电点, MapPointType.停靠点].includes(point.type)">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('绑定机器人') }}</a-typography-text>
|
||||
<a-typography-text>{{ bindRobot || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="MapPointType.等待点 === point.type">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('绑定动作点') }}</a-typography-text>
|
||||
<a-typography-text>{{ bindAction || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="MapPointType.动作点 === point.type">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('关联库区') }}</a-typography-text>
|
||||
<a-typography-text>{{ coArea1 || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item
|
||||
v-if="
|
||||
[
|
||||
MapPointType.普通点,
|
||||
MapPointType.电梯点,
|
||||
MapPointType.自动门点,
|
||||
MapPointType.等待点,
|
||||
MapPointType.充电点,
|
||||
MapPointType.停靠点,
|
||||
MapPointType.动作点,
|
||||
MapPointType.临时避让点,
|
||||
].includes(point.type)
|
||||
"
|
||||
>
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('关联互斥区') }}</a-typography-text>
|
||||
<a-typography-text>{{ coArea2 || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
</a-card>
|
||||
</template>
|
@ -22,13 +22,13 @@ const stateDot = computed<string>(() => `state-${robot.value?.state}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card class="card-container" :bordered="false">
|
||||
<a-card :bordered="false">
|
||||
<template v-if="robot">
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col :span="24">
|
||||
<a-flex align="center" :gap="8">
|
||||
<i class="icon robot" />
|
||||
<a-typography-text class="title" style="flex: auto" :content="robot.label" ellipsis />
|
||||
<a-typography-text class="card-title" style="flex: auto" :content="robot.label" ellipsis />
|
||||
<a-tag :bordered="false">{{ $t(RobotType[robot.type]) }}</a-tag>
|
||||
</a-flex>
|
||||
</a-col>
|
||||
@ -81,11 +81,6 @@ const stateDot = computed<string>(() => `state-${robot.value?.state}`);
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ant-typography.title {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
@ -38,7 +38,7 @@ watch(editor.value.mouseBrush, (v) => {
|
||||
size="large"
|
||||
@click="mode = MapAreaType.互斥区"
|
||||
>
|
||||
<i class="icon" :class="mode === MapAreaType.互斥区 ? 'area2-active' : 'area2'" />
|
||||
<i class="icon" :class="mode === MapAreaType.互斥区 ? 'area11-active' : 'area11'" />
|
||||
</a-button>
|
||||
<a-button
|
||||
class="icon-btn tool-btn ml-12"
|
||||
@ -46,7 +46,7 @@ watch(editor.value.mouseBrush, (v) => {
|
||||
size="large"
|
||||
@click="mode = MapAreaType.非互斥区"
|
||||
>
|
||||
<i class="icon" :class="mode === MapAreaType.非互斥区 ? 'area3-active' : 'area3'" />
|
||||
<i class="icon" :class="mode === MapAreaType.非互斥区 ? 'area12-active' : 'area12'" />
|
||||
</a-button>
|
||||
|
||||
<a-divider class="size-24 mh-8" type="vertical" />
|
||||
|
@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { type MapPen, MapPointType } from '@api/map';
|
||||
import { MapAreaType, type MapPen, MapPointType } from '@api/map';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import { computed, inject, type InjectionKey, ref, type ShallowRef } from 'vue';
|
||||
|
||||
type Props = {
|
||||
token: InjectionKey<ShallowRef<EditorService>>;
|
||||
current?: string;
|
||||
onlyArea1?: boolean;
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
@ -22,89 +23,134 @@ const points = computed<MapPen[]>(() =>
|
||||
//#endregion
|
||||
|
||||
//#region 区域列表
|
||||
const areas = computed<MapPen[]>(() => editor.value.areas.value.filter(({ label }) => label?.includes(keyword.value)));
|
||||
//#endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-input class="search mb-16" :placeholder="$t('请输入搜索关键字')" v-model:value="keyword">
|
||||
<template #suffix>
|
||||
<i class="icon search size-16" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-flex class="full" vertical>
|
||||
<a-input class="search mb-16" :placeholder="$t('请输入搜索关键字')" v-model:value="keyword">
|
||||
<template #suffix>
|
||||
<i class="icon search size-16" />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<a-collapse style="flex: auto; overflow-y: auto" expand-icon-position="end" ghost>
|
||||
<template #expandIcon="v">
|
||||
<i class="icon dropdown" :class="{ active: v?.isActive }" />
|
||||
</template>
|
||||
<a-collapse style="flex: auto; overflow-y: auto" expand-icon-position="end" ghost>
|
||||
<template #expandIcon="v">
|
||||
<i class="icon dropdown" :class="{ active: v?.isActive }" />
|
||||
</template>
|
||||
|
||||
<a-collapse-panel :header="$t('互斥区')"></a-collapse-panel>
|
||||
<a-collapse-panel v-if="onlyArea1" :header="$t('库区')">
|
||||
<a-list rowKey="id" :data-source="areas.filter(({ area }) => area?.type === MapAreaType.库区)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
<template v-else>
|
||||
<a-collapse-panel :header="$t('互斥区')">
|
||||
<a-list rowKey="id" :data-source="areas.filter(({ area }) => area?.type === MapAreaType.互斥区)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('非互斥区')"></a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('非互斥区')">
|
||||
<a-list rowKey="id" :data-source="areas.filter(({ area }) => area?.type === MapAreaType.非互斥区)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('仅载货可通行路线')"></a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('仅载货可通行路线')"></a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('仅空载可通行路线')"></a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('仅空载可通行路线')"></a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('禁行路线')"></a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('禁行路线')"></a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('等待点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.等待点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('等待点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.等待点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('充电点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.充电点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('充电点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.充电点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('停靠点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.停靠点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel :header="$t('停靠点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.停靠点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
|
||||
<a-collapse-panel :header="$t('禁行点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.禁行点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-collapse-panel :header="$t('禁行点')">
|
||||
<a-list rowKey="id" :data-source="points.filter(({ point }) => point?.type === MapPointType.禁行点)">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item
|
||||
class="ph-16"
|
||||
:class="{ selected: item.id === current }"
|
||||
style="height: 36px"
|
||||
@click="editor.active(item.id)"
|
||||
>
|
||||
<a-typography-text type="secondary">{{ item.label }}</a-typography-text>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-collapse-panel>
|
||||
</template>
|
||||
</a-collapse>
|
||||
</a-flex>
|
||||
</template>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { getSceneById } from '@api/scene';
|
||||
import { EditorService } from '@core/editor.service';
|
||||
import { computed, nextTick, watch } from 'vue';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { computed, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, provide, shallowRef } from 'vue';
|
||||
|
||||
@ -16,7 +17,7 @@ const props = defineProps<Props>();
|
||||
const readScene = async () => {
|
||||
const res = await getSceneById(props.id);
|
||||
title.value = res?.label ?? '';
|
||||
editor.value?.load(res?.json, !editable.value);
|
||||
editor.value?.load(res?.json, editable.value);
|
||||
};
|
||||
//#endregion
|
||||
|
||||
@ -42,12 +43,10 @@ const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>()
|
||||
watch(
|
||||
() => editor.value?.selected.value[0],
|
||||
(v) => {
|
||||
if (v) {
|
||||
const [pen] = editor.value?.find(v) ?? [];
|
||||
if (pen?.id) {
|
||||
current.value = { type: <'point' | 'line' | 'area'>pen.name, id: pen.id };
|
||||
return;
|
||||
}
|
||||
const pen = editor.value?.getPenById(v);
|
||||
if (pen?.id) {
|
||||
current.value = { type: <'point' | 'line' | 'area'>pen.name, id: pen.id };
|
||||
return;
|
||||
}
|
||||
if (current.value?.type === 'robot') return;
|
||||
current.value = undefined;
|
||||
@ -97,7 +96,9 @@ const selectRobot = (id: string) => {
|
||||
@change="selectRobot"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" :tab="$t('库区')">Content of Tab Pane 2</a-tab-pane>
|
||||
<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 key="3" :tab="$t('高级组')">
|
||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
|
||||
</a-tab-pane>
|
||||
@ -120,7 +121,17 @@ const selectRobot = (id: string) => {
|
||||
<div v-if="show" class="card-container">
|
||||
<RobotDetailCard v-if="isRobot" :token="EDITOR_KEY" :current="current.id" />
|
||||
|
||||
<template v-if="isPoint"> </template>
|
||||
<template v-if="isPoint">
|
||||
<div v-if="editable"></div>
|
||||
<PointDetailCard v-else :token="EDITOR_KEY" :current="current.id" />
|
||||
</template>
|
||||
<template v-if="isRoute">
|
||||
<div v-if="editable"></div>
|
||||
</template>
|
||||
<template v-if="isArea">
|
||||
<div v-if="editable"></div>
|
||||
<AreaDetailCard v-else :token="EDITOR_KEY" :current="current.id" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
@ -15,6 +15,17 @@ export class EditorService extends Meta2d {
|
||||
const data = map ? JSON.parse(map) : undefined;
|
||||
this.open(data);
|
||||
this.setState(editable);
|
||||
this.addPoint({ x: 100, y: 100 }, 1);
|
||||
this.addPoint({ x: 200, y: 100 }, 2);
|
||||
this.addPoint({ x: 300, y: 100 }, 3);
|
||||
this.addPoint({ x: 400, y: 100 }, 4);
|
||||
|
||||
this.addPoint({ x: 100, y: 200 }, 11);
|
||||
this.addPoint({ x: 200, y: 200 }, 12);
|
||||
this.addPoint({ x: 300, y: 200 }, 13);
|
||||
this.addPoint({ x: 400, y: 200 }, 14);
|
||||
this.addPoint({ x: 500, y: 200 }, 15);
|
||||
this.addPoint({ x: 600, y: 200 }, 16);
|
||||
}
|
||||
public save(): string {
|
||||
const data = this.data();
|
||||
@ -26,7 +37,7 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
|
||||
public setState(editable?: boolean): void {
|
||||
this.lock(editable ? LockState.None : LockState.Disable);
|
||||
this.lock(editable ? LockState.None : LockState.DisableMoveScale);
|
||||
}
|
||||
|
||||
public override data(): SceneData {
|
||||
@ -99,7 +110,7 @@ export class EditorService extends Meta2d {
|
||||
|
||||
public createRobotGroup(): void {
|
||||
const id = s8();
|
||||
const label = `RG-${id}`;
|
||||
const label = `RG${id}`;
|
||||
const groups = clone(this.#robotGroups$$.value);
|
||||
groups.push({ id, label });
|
||||
this.#robotGroups$$.next(groups);
|
||||
@ -145,6 +156,10 @@ export class EditorService extends Meta2d {
|
||||
public override find(target: string): MapPen[] {
|
||||
return super.find(target);
|
||||
}
|
||||
public getPenById(id?: string): MapPen | undefined {
|
||||
if (!id) return;
|
||||
return this.find(id)[0];
|
||||
}
|
||||
|
||||
public override active(target: string | Pen[], emit?: boolean): void {
|
||||
const pens = isString(target) ? this.find(target) : target;
|
||||
@ -175,7 +190,7 @@ export class EditorService extends Meta2d {
|
||||
id,
|
||||
name: 'point',
|
||||
tags: ['point', `point-${type}`],
|
||||
label: `P-${id}`,
|
||||
label: `P${id}`,
|
||||
point: { type },
|
||||
};
|
||||
const { x, y, width, height } = this.getPenRect(pen);
|
||||
@ -200,27 +215,69 @@ export class EditorService extends Meta2d {
|
||||
//#endregion
|
||||
|
||||
//#region 线路
|
||||
public getRouteLabel(id?: string): string {
|
||||
if (!id) return '';
|
||||
const pen = this.getPenById(id);
|
||||
if (isNil(pen)) return '';
|
||||
const [a1, a2] = pen.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return '';
|
||||
const p1 = this.getPenById(a1.connectTo);
|
||||
const p2 = this.getPenById(a2.connectTo);
|
||||
if (isNil(p1) || isNil(p2)) return '';
|
||||
const { direction = 1 } = pen.route ?? {};
|
||||
return `${p1.text} ${direction > 0 ? '→' : '←'} ${p2.text}`;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 区域
|
||||
public readonly areas = useObservable<MapPen[], MapPen[]>(
|
||||
this.#change$$.pipe(
|
||||
filter((v) => v),
|
||||
debounceTime(100),
|
||||
map(() => this.find('area')),
|
||||
),
|
||||
{ initialValue: new Array<MapPen>() },
|
||||
);
|
||||
|
||||
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区) {
|
||||
const scale = this.data().scale ?? 1;
|
||||
const w = Math.abs(p1.x - p2.x);
|
||||
const h = Math.abs(p1.y - p2.y);
|
||||
if (w * scale < 50 || h * scale < 60) return;
|
||||
const selected = <MapPen[]>this.store.active;
|
||||
const id = s8();
|
||||
const points = new Array<string>();
|
||||
const routes = new Array<string>();
|
||||
switch (type) {
|
||||
case MapAreaType.库区:
|
||||
selected?.filter(({ point }) => point?.type === MapPointType.动作点).forEach(({ id }) => points.push(id!));
|
||||
break;
|
||||
case MapAreaType.互斥区:
|
||||
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||
selected?.filter(({ route }) => route?.type).forEach(({ id }) => routes.push(id!));
|
||||
break;
|
||||
case MapAreaType.非互斥区:
|
||||
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const pen: MapPen = {
|
||||
id,
|
||||
name: 'area',
|
||||
tags: ['area', `area-${type}`],
|
||||
label: `A${id}`,
|
||||
x: Math.min(p1.x, p2.x),
|
||||
y: Math.min(p1.y, p2.y),
|
||||
width: w,
|
||||
height: h,
|
||||
area: { type },
|
||||
lineWidth: 1,
|
||||
area: { type, points, routes },
|
||||
locked: LockState.DisableMoveScale,
|
||||
};
|
||||
const area = await this.addPen(pen, false, true, true);
|
||||
this.bottom(area);
|
||||
this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] });
|
||||
// this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] });
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@ -399,13 +456,23 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
}
|
||||
|
||||
function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const { x = 0, y = 0, width = 0, height = 0 } = pen.calculative?.worldRect ?? {};
|
||||
const theme = sTheme.editor;
|
||||
const { active, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
const { type } = pen.area ?? {};
|
||||
const { label = '' } = pen ?? {};
|
||||
|
||||
ctx.save();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillText(String(type), x + width / 2, y);
|
||||
ctx.rect(x, y, w, h);
|
||||
ctx.fillStyle = get(theme, `area.fill-${type}`) ?? '';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = get(theme, active ? 'area.strokeActive' : `area.stroke-${type}`) ?? '';
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = get(theme, 'color') ?? '';
|
||||
ctx.font = `${fontSize}px/${lineHeight} ${fontFamily}`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(label, x + w / 2, y - fontSize * lineHeight);
|
||||
ctx.restore();
|
||||
}
|
||||
//#endregion
|
||||
|