temp
This commit is contained in:
parent
081764b4b1
commit
43f16303d8
File diff suppressed because one or more lines are too long
@ -417,6 +417,7 @@
|
||||
}
|
||||
|
||||
.ant-list.block .ant-list-item {
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background-color: get-color(fill4);
|
||||
border: none;
|
||||
|
@ -36,6 +36,9 @@ export enum MapRoutePassType {
|
||||
仅载货可通行,
|
||||
禁行 = 10,
|
||||
}
|
||||
export const MAP_ROUTE_PASS_TYPES = Object.freeze(
|
||||
<[string, MapRoutePassType][]>Object.entries(MapRoutePassType).filter(([, v]) => typeof v === 'number'),
|
||||
);
|
||||
//#endregion
|
||||
|
||||
//#region 区域
|
||||
|
@ -13,9 +13,9 @@ 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 v = pen.value?.area;
|
||||
if (!v?.type) return null;
|
||||
return v;
|
||||
});
|
||||
|
||||
const icon = computed<string>(() => `area${area.value?.type}-detail`);
|
||||
|
@ -13,9 +13,9 @@ 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 v = pen.value?.point;
|
||||
if (!v?.type) return null;
|
||||
return v;
|
||||
});
|
||||
|
||||
const bindRobot = computed<string>(
|
||||
|
59
src/components/card/route-detail-card.vue
Normal file
59
src/components/card/route-detail-card.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { MAP_ROUTE_TYPE, type MapPen, type MapRouteInfo, MapRoutePassType } 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>>;
|
||||
editable?: boolean;
|
||||
current?: string;
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
|
||||
const pen = computed<MapPen | undefined>(() => editor.value.getPenById(props.current));
|
||||
const route = computed<MapRouteInfo | null>(() => {
|
||||
const v = pen.value?.route;
|
||||
if (!v?.type) return null;
|
||||
return v;
|
||||
});
|
||||
|
||||
const label = computed<string>(() => editor.value.getRouteLabel(pen.value?.id));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<template v-if="pen && route">
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col :span="24">
|
||||
<a-flex align="center" :gap="8">
|
||||
<i class="icon route" />
|
||||
<a-typography-text class="card-title" style="flex: auto" :content="label" ellipsis />
|
||||
<a-tag :bordered="false">{{ $t(MapRoutePassType[route.pass ?? MapRoutePassType.无]) }}</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>{{ $t(MAP_ROUTE_TYPE[route.type]) }}</a-typography-text>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-typography-text type="secondary">{{ $t('路段长度') }}</a-typography-text>
|
||||
<a-typography-text>{{ pen.length?.toFixed() }}</a-typography-text>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-typography-text style="flex: none" type="secondary">{{ $t('路段方向') }}</a-typography-text>
|
||||
<a-typography-text :content="label" ellipsis />
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
</a-card>
|
||||
</template>
|
163
src/components/card/route-edit-card.vue
Normal file
163
src/components/card/route-edit-card.vue
Normal file
@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
MAP_ROUTE_PASS_TYPES,
|
||||
MAP_ROUTE_TYPES,
|
||||
type MapPen,
|
||||
type MapRouteInfo,
|
||||
MapRoutePassType,
|
||||
MapRouteType,
|
||||
} 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>>;
|
||||
id?: string;
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
|
||||
const pen = computed<MapPen | null>(() => {
|
||||
const v = editor.value.current.value;
|
||||
if (v?.id !== props.id) return null;
|
||||
return v!;
|
||||
});
|
||||
const route = computed<MapRouteInfo | null>(() => {
|
||||
const v = pen.value?.route;
|
||||
if (!v?.type) return null;
|
||||
return v;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card class="full" :title="$t('属性')" :bordered="false">
|
||||
<a-flex v-if="id && pen && route" :gap="24" vertical>
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col :span="24">
|
||||
<a-select
|
||||
:value="route.pass ?? MapRoutePassType.无"
|
||||
@change="editor.updateRoute(id, { pass: <number>$event })"
|
||||
>
|
||||
<a-select-option v-for="[l, v] in MAP_ROUTE_PASS_TYPES" :key="v">{{ $t(l) }}</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="[8, 8]">
|
||||
<a-col :span="24">
|
||||
<a-typography-text>{{ $t('描述') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-textarea
|
||||
class="prop"
|
||||
:placeholder="$t('请输入描述内容')"
|
||||
:maxlength="100"
|
||||
:autoSize="{ minRows: 3, maxRows: 3 }"
|
||||
:value="pen?.desc"
|
||||
@change="editor.updatePen(id, { desc: $event.target.value }, false)"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row align="middle" :gutter="10" :wrap="false">
|
||||
<a-col flex="none">
|
||||
<a-typography-text>{{ $t('路段长度') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-input :value="pen.length?.toFixed()" disabled />
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row align="middle" :gutter="10" :wrap="false">
|
||||
<a-col flex="none">
|
||||
<a-typography-text>{{ $t('路段方向') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-select :value="route.direction || 1" @change="editor.updateRoute(id, { direction: <-1 | 1>$event })">
|
||||
<a-select-option :value="1">{{ editor.getRouteLabel(id, 1) }}</a-select-option>
|
||||
<a-select-option :value="-1">{{ editor.getRouteLabel(id, -1) }}</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row align="middle" :gutter="10" :wrap="false">
|
||||
<a-col flex="none">
|
||||
<a-typography-text>{{ $t('路段类型') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-select :value="route.type" @change="editor.changeRouteType(id, <MapRouteType>$event)">
|
||||
<a-select-option v-for="[l, v] in MAP_ROUTE_TYPES" :key="v">{{ $t(l) }}</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<template v-if="MapRouteType.三阶贝塞尔曲线 === route.type">
|
||||
<a-row align="middle" :gutter="8">
|
||||
<a-col flex="auto">
|
||||
<a-typography-text>{{ $t('控制点1') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>X:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c1?.x.toFixed()"
|
||||
@change="editor.updateRoute(id, { c1: { x: <number>$event, y: route?.c1?.y ?? 0 } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>Y:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c1?.y.toFixed()"
|
||||
@change="editor.updateRoute(id, { c1: { x: route?.c1?.x ?? 0, y: <number>$event } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row align="middle" :gutter="8">
|
||||
<a-col flex="auto">
|
||||
<a-typography-text>{{ $t('控制点2') }}:</a-typography-text>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>X:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c2?.x.toFixed()"
|
||||
@change="editor.updateRoute(id, { c2: { x: <number>$event, y: route?.c2?.y ?? 0 } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col flex="none">
|
||||
<a-space :size="8">
|
||||
<a-typography-text code>Y:</a-typography-text>
|
||||
<a-input-number
|
||||
style="width: 80px"
|
||||
:placeholder="$t('请输入')"
|
||||
:precision="0"
|
||||
:controls="false"
|
||||
:value="route?.c2?.y.toFixed()"
|
||||
@change="editor.updateRoute(id, { c2: { x: route?.c2?.x ?? 0, y: <number>$event } })"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-flex>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
</a-card>
|
||||
</template>
|
@ -47,6 +47,7 @@ const importScene = async () => {
|
||||
const exportScene = () => {
|
||||
const json = editor.value?.save();
|
||||
if (!json) return;
|
||||
getSceneById(json);
|
||||
const blob = textToBlob(json);
|
||||
if (!blob?.size) return;
|
||||
const url = URL.createObjectURL(blob);
|
||||
@ -142,7 +143,8 @@ const selectRobot = (id: string) => {
|
||||
<PointDetailCard v-else :token="EDITOR_KEY" :current="current.id" />
|
||||
</template>
|
||||
<template v-if="isRoute">
|
||||
<div v-if="editable"></div>
|
||||
<RouteEditCard v-if="editable" :token="EDITOR_KEY" :id="current.id" />
|
||||
<RouteDetailCard v-else :token="EDITOR_KEY" :current="current.id" />
|
||||
</template>
|
||||
<template v-if="isArea">
|
||||
<AreaEditCard v-if="editable" :token="EDITOR_KEY" :id="current.id" />
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
type MapPen,
|
||||
type MapPointInfo,
|
||||
MapPointType,
|
||||
type MapRouteInfo,
|
||||
MapRoutePassType,
|
||||
MapRouteType,
|
||||
type Point,
|
||||
@ -35,7 +36,7 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
|
||||
public setState(editable?: boolean): void {
|
||||
this.lock(editable ? LockState.None : LockState.DisableMoveScale);
|
||||
this.lock(editable ? LockState.None : LockState.DisableEdit);
|
||||
}
|
||||
|
||||
public override data(): SceneData {
|
||||
@ -245,7 +246,7 @@ export class EditorService extends Meta2d {
|
||||
{ initialValue: new Array<MapPen>() },
|
||||
);
|
||||
|
||||
public getRouteLabel(id?: string): string {
|
||||
public getRouteLabel(id?: string, d?: number): string {
|
||||
if (!id) return '';
|
||||
const pen = this.getPenById(id);
|
||||
if (isNil(pen)) return '';
|
||||
@ -255,15 +256,15 @@ export class EditorService extends Meta2d {
|
||||
const p2 = this.getPenById(a2.connectTo);
|
||||
if (isNil(p1) || isNil(p2)) return '';
|
||||
const { direction = 1 } = pen.route ?? {};
|
||||
return `${p1.text} ${direction > 0 ? '→' : '←'} ${p2.text}`;
|
||||
return `${p1.label}${(d ?? direction) > 0 ? '→' : '←'}${p2.label}`;
|
||||
}
|
||||
|
||||
public addRoute(p: [string, string], type = MapRouteType.直线): void {
|
||||
const [p1, p2] = p.map((v) => this.getPenById(v));
|
||||
if (!p1?.anchors?.length || !p2?.anchors?.length) return;
|
||||
const line = this.connectLine(p1, p2, undefined, undefined, false);
|
||||
const pen: MapPen = { tags: ['route'], route: { type }, lineWidth: 2 };
|
||||
this.bottom(line);
|
||||
const line = this.connectLine(p1, p2, p1.anchors[0], p2.anchors[0], false);
|
||||
const pen: MapPen = { tags: ['route'], route: { type }, lineWidth: 2, iconSize: 10 };
|
||||
// this.bottom(line);
|
||||
this.setValue({ id: line.id, ...pen }, { render: false, history: false, doEvent: false });
|
||||
this.updateLineType(line, type);
|
||||
// this.pushHistory({ type: EditType.Add, pens: [cloneDeep(line)] });
|
||||
@ -271,6 +272,12 @@ export class EditorService extends Meta2d {
|
||||
this.render();
|
||||
}
|
||||
|
||||
public updateRoute(id: string, info: Partial<MapRouteInfo>): void {
|
||||
const { route } = this.getPenById(id) ?? {};
|
||||
if (!route?.type) return;
|
||||
const o = { ...route, ...info };
|
||||
this.setValue({ id, route: o }, { render: true, history: true, doEvent: true });
|
||||
}
|
||||
public changeRouteType(id: string, type: MapRouteType): void {
|
||||
const pen = this.getPenById(id);
|
||||
if (isNil(pen)) return;
|
||||
@ -377,6 +384,8 @@ export class EditorService extends Meta2d {
|
||||
this.render();
|
||||
}
|
||||
|
||||
#onDelete(): void {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
#listen(e: unknown, v: any) {
|
||||
switch (e) {
|
||||
@ -386,8 +395,15 @@ export class EditorService extends Meta2d {
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
this.#change$$.next(true);
|
||||
break;
|
||||
case 'delete':
|
||||
this.#onDelete();
|
||||
this.#change$$.next(true);
|
||||
break;
|
||||
case 'update':
|
||||
this.#change$$.next(true);
|
||||
break;
|
||||
case 'valueUpdate':
|
||||
this.#change$$.next(true);
|
||||
break;
|
||||
@ -438,6 +454,7 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
|
||||
#register() {
|
||||
this.register({ line: () => new Path2D() });
|
||||
this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea });
|
||||
this.registerAnchors({ point: anchorPoint });
|
||||
this.addDrawLineFn('bezier3', lineBezier3);
|
||||
@ -511,11 +528,17 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.restore();
|
||||
}
|
||||
function anchorPoint(pen: MapPen): void {
|
||||
pen.anchors = [{ penId: pen.id, id: 'c', x: 0.5, y: 0.5 }];
|
||||
pen.anchors = [
|
||||
{ penId: pen.id, id: 't', x: 0.5, y: 0 },
|
||||
{ penId: pen.id, id: 'b', x: 0.5, y: 1 },
|
||||
{ penId: pen.id, id: 'l', x: 0, y: 0.5 },
|
||||
{ penId: pen.id, id: 'r', x: 1, y: 0.5 },
|
||||
];
|
||||
}
|
||||
|
||||
function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { iconSize: s = 10 } = pen.calculative ?? {};
|
||||
const [p1, p2] = pen.calculative?.worldAnchors ?? [];
|
||||
const { x: x1 = 0, y: y1 = 0 } = p1 ?? {};
|
||||
const { x: x2 = 0, y: y2 = 0 } = p2 ?? {};
|
||||
@ -524,8 +547,9 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const { x: dx2 = 0, y: dy2 = y2 - y1 } = c2 ?? {};
|
||||
|
||||
ctx.save();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = get(theme, `route.stroke-${pass}`) ?? '';
|
||||
ctx.moveTo(x1, y1);
|
||||
switch (type) {
|
||||
case MapRouteType.直线:
|
||||
ctx.lineTo(x2, y2);
|
||||
@ -539,8 +563,32 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
break;
|
||||
}
|
||||
if (pass === MapRoutePassType.禁行) {
|
||||
ctx.setLineDash([8, 4]);
|
||||
ctx.setLineDash([s / 2]);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash([0]);
|
||||
let r = (() => {
|
||||
switch (type) {
|
||||
case MapRouteType.直线:
|
||||
return Math.atan2(y2 - y1, x2 - x1);
|
||||
case MapRouteType.三阶贝塞尔曲线:
|
||||
return direction < 0 ? Math.atan2(dy1, dx1) : Math.atan2(dy2, dx2);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
if (direction < 0) {
|
||||
ctx.translate(x1, y1);
|
||||
} else {
|
||||
ctx.translate(x2, y2);
|
||||
r += Math.PI;
|
||||
}
|
||||
ctx.moveTo(Math.cos(r + Math.PI / 5) * s, Math.sin(r + Math.PI / 5) * s);
|
||||
ctx.lineTo(0, 0);
|
||||
ctx.lineTo(Math.cos(r - Math.PI / 5) * s, Math.sin(r - Math.PI / 5) * s);
|
||||
ctx.stroke();
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
function lineBezier3(_: Meta2dStore, pen: MapPen): void {
|
||||
|
Loading…
x
Reference in New Issue
Block a user