This commit is contained in:
chndfang 2025-05-07 20:12:15 +08:00
parent fe1ba733ef
commit f8b88edcad
6 changed files with 125 additions and 39 deletions

File diff suppressed because one or more lines are too long

View File

@ -68,6 +68,16 @@
border-radius: 8px;
box-shadow: 0 2px 8px 0 get-color(shadow1);
& > .ant-card-head {
min-height: 48px;
padding: 14px 16px;
margin: 0;
font: 500 16px/20px Roboto;
color: get-color(text1);
background-color: get-color(fill1);
border: none;
}
& > .ant-card-body {
padding: 16px;
}
@ -103,8 +113,8 @@
.ant-checkbox-wrapper {
align-items: center;
font: 400 14px/22px Roboto;
color: get-color(text2);
vertical-align: top;
color: get-color(text2);
}
.ant-collapse.ant-collapse-ghost {
@ -261,13 +271,13 @@
box-shadow: none !important;
&.ant-input-status-error {
border-color: get-color(error) !important;
outline-color: rgba($color: get-color(error), $alpha: 20%) !important;
border-color: get-color(error) !important;
}
&:not(:disabled):focus {
border-color: get-color(primary);
outline: 2px solid rgba($color: get-color(primary), $alpha: 20%);
border-color: get-color(primary);
transition: none;
}
@ -281,13 +291,13 @@
box-shadow: none !important;
&.ant-input-affix-wrapper-status-error {
border-color: get-color(error) !important;
outline-color: rgba($color: get-color(error), $alpha: 20%) !important;
border-color: get-color(error) !important;
}
&:not(.ant-input-affix-wrapper-disabled).ant-input-affix-wrapper-focused {
border-color: get-color(primary);
outline: 2px solid rgba($color: get-color(primary), $alpha: 20%);
border-color: get-color(primary);
transition: none;
}
@ -296,8 +306,8 @@
}
& > .ant-input {
background-color: transparent;
outline: none;
background-color: transparent;
}
& > .ant-input-suffix {
@ -322,13 +332,13 @@
box-shadow: none !important;
&.ant-input-number-status-error {
border-color: get-color(error) !important;
outline-color: rgba($color: get-color(error), $alpha: 20%) !important;
border-color: get-color(error) !important;
}
&:not(:disabled).ant-input-number-focused {
border-color: get-color(primary);
outline: 2px solid rgba($color: get-color(primary), $alpha: 20%);
border-color: get-color(primary);
transition: none;
}
}
@ -455,6 +465,8 @@
}
.ant-select:not(.ant-select-borderless) {
width: 100%;
& > .ant-select-selector {
color: get-color(text1);
background-color: get-color(neutral1);
@ -477,8 +489,8 @@
}
&.ant-select-status-error > .ant-select-selector {
border-color: get-color(error) !important;
outline-color: rgba($color: get-color(error), $alpha: 20%) !important;
border-color: get-color(error) !important;
}
&:not(.ant-select-disabled):hover > .ant-select-selector {
@ -486,8 +498,8 @@
}
&:not(.ant-select-disabled).ant-select-focused > .ant-select-selector {
border-color: get-color(primary);
outline: 2px solid rgba($color: get-color(primary), $alpha: 20%);
border-color: get-color(primary);
transition: none;
}
@ -578,6 +590,10 @@
margin: 0;
background-color: get-color(fill1);
&::before {
display: none;
}
& > .ant-tabs-nav-wrap > .ant-tabs-nav-list {
& > .ant-tabs-tab {
padding: 14px 32px;
@ -604,6 +620,10 @@
display: none;
}
}
& > .ant-tabs-nav-operations {
display: none;
}
}
& > .ant-tabs-content-holder > .ant-tabs-content {
@ -620,8 +640,8 @@
padding: 3px 5px;
margin: 0;
font: 500 14px/22px Roboto;
color: get-color(text1);
vertical-align: top;
color: get-color(text1);
background-color: transparent;
border-color: get-color(border1);
border-radius: 4px;

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { MAP_POINT_TYPES, type MapPen, type MapPointInfo } 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>>;
};
const props = defineProps<Props>();
const editor = inject(props.token)!;
const pen = computed<MapPen | undefined>(() => editor.value.current.value);
const point = computed<MapPointInfo | null>(() => {
const point = pen.value?.point;
if (!point?.type) return null;
return point;
});
</script>
<template>
<a-card :title="$t('属性')" :bordered="false">
<template v-if="pen && point">
<a-row :gutter="8">
<a-col :span="12">
<a-select :value="point.type" @change="editor.changePointType(pen.id!, <number>$event)">
<a-select-option v-for="[l, v] in MAP_POINT_TYPES" :key="v">{{ $t(l) }}</a-select-option>
</a-select>
</a-col>
<a-col :span="12">
<a-input
:maxlength="10"
:value="pen.label"
@change="editor.updatePen(pen.id!, { label: $event.target.value }, false)"
>
<template #suffix><EditOutlined /></template>
</a-input>
</a-col>
</a-row>
</template>
<a-empty v-else :image="sTheme.empty" />
</a-card>
</template>

View File

@ -7,7 +7,7 @@ import { inject, type InjectionKey, ref, type ShallowRef, watch } from 'vue';
type Props = {
token: InjectionKey<ShallowRef<EditorService>>;
editable?: boolean;
current?: string;
id: string;
};
const props = defineProps<Props>();
const editor = inject(props.token)!;
@ -54,13 +54,18 @@ watch(editor.value.mouseBrush, (v) => {
<a-button class="icon-btn tool-btn" size="large">
<i class="mask save" />
</a-button>
<a-button class="icon-btn tool-btn ml-12" size="large">
<a-button class="icon-btn tool-btn ml-12" size="large" @click="editor.undo()">
<i class="mask undo" />
</a-button>
<a-button class="icon-btn tool-btn ml-12" size="large">
<a-button class="icon-btn tool-btn ml-12" size="large" @click="editor.redo()">
<i class="mask redo" />
</a-button>
<a-button class="icon-btn tool-btn ml-12" size="large" :disabled="!editor.selected.value.length">
<a-button
class="icon-btn tool-btn ml-12"
size="large"
@click="editor.delete(undefined, true)"
:disabled="!editor.selected.value.length"
>
<i class="mask trash" />
</a-button>
</a-space>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { getSceneById } from '@api/scene';
import { EditorService } from '@core/editor.service';
import { isNil } from 'lodash-es';
import { decodeTextFile, downloadFile, selectFile, textToBlob } from '@core/utils';
import { computed, watch } from 'vue';
import { ref } from 'vue';
import { onMounted, provide, shallowRef } from 'vue';
@ -38,6 +38,22 @@ onMounted(() => {
const editable = ref<boolean>(false);
watch(editable, (v) => editor.value?.setState(v));
const importScene = async () => {
const file = await selectFile('.scene');
if (!file?.size) return;
const json = await decodeTextFile(file);
editor.value?.load(json, editable.value);
};
const exportScene = () => {
const json = editor.value?.save();
if (!json) return;
const blob = textToBlob(json);
if (!blob?.size) return;
const url = URL.createObjectURL(blob);
downloadFile(url, `${title.value || 'unknown'}.scene`);
URL.revokeObjectURL(url);
};
const show = ref<boolean>(true);
const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>();
watch(
@ -78,8 +94,8 @@ const selectRobot = (id: string) => {
<span>{{ $t('启用编辑器') }}</span>
</a-button>
<a-button>{{ $t('推送') }}</a-button>
<a-button>{{ $t('导入') }}</a-button>
<a-button @click="editor?.export()">{{ $t('导出') }}</a-button>
<a-button @click="importScene">{{ $t('导入') }}</a-button>
<a-button @click="exportScene">{{ $t('导出') }}</a-button>
</a-space>
</a-flex>
</a-layout-header>
@ -111,7 +127,7 @@ const selectRobot = (id: string) => {
</a-layout>
<div v-if="editable" class="toolbar-container">
<EditorToolbar v-if="editor" :token="EDITOR_KEY" />
<EditorToolbar v-if="editor" :token="EDITOR_KEY" :id="id" />
</div>
<template v-if="current?.id">
@ -122,7 +138,7 @@ const selectRobot = (id: string) => {
<RobotDetailCard v-if="isRobot" :token="EDITOR_KEY" :current="current.id" />
<template v-if="isPoint">
<div v-if="editable"></div>
<PointEditCard v-if="editable" :token="EDITOR_KEY" :current="current.id" />
<PointDetailCard v-else :token="EDITOR_KEY" :current="current.id" />
</template>
<template v-if="isRoute">
@ -146,14 +162,14 @@ const selectRobot = (id: string) => {
right: 50%;
bottom: 40px;
left: 50%;
z-index: 8888;
z-index: 100;
}
.card-container {
position: fixed;
top: 80px;
right: 64px;
z-index: 8888;
z-index: 100;
width: 320px;
}

View File

@ -15,26 +15,11 @@ 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();
return JSON.stringify(data);
}
public export(): void {
const json = this.save();
console.log(json);
}
public setState(editable?: boolean): void {
this.lock(editable ? LockState.None : LockState.DisableMoveScale);
@ -137,6 +122,12 @@ export class EditorService extends Meta2d {
//#endregion
readonly #change$$ = new Subject<boolean>();
public readonly current = useObservable<MapPen>(
this.#change$$.pipe(
debounceTime(100),
map(() => <MapPen>clone(this.store.active?.[0])),
),
);
public readonly selected = useObservable<string[], string[]>(
this.#change$$.pipe(
filter((v) => !v),
@ -171,6 +162,10 @@ export class EditorService extends Meta2d {
this.render();
}
public updatePen(id: string, pen: Partial<MapPen>, record = true): void {
this.setValue({ ...pen, id }, { render: record, history: record, doEvent: true });
}
//#region 点位
public readonly points = useObservable<MapPen[], MapPen[]>(
this.#change$$.pipe(
@ -189,7 +184,7 @@ export class EditorService extends Meta2d {
...this.#mapPointImage(type),
id,
name: 'point',
tags: ['point', `point-${type}`],
tags: ['point'],
label: `P${id}`,
point: { type },
};
@ -200,6 +195,13 @@ export class EditorService extends Meta2d {
// this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] });
}
public changePointType(id: string, type: MapPointType): void {
this.setValue(
{ id, ...this.#mapPoint(type), ...this.#mapPointImage(type), point: { type } },
{ render: true, history: true, doEvent: true },
);
}
#mapPoint(type: MapPointType): Required<Pick<MapPen, 'width' | 'height' | 'lineWidth' | 'iconSize'>> {
const width = type < 10 ? 24 : 48;
const height = type < 10 ? 24 : 60;