web-map/src/pages/scene-editor.vue

181 lines
5.3 KiB
Vue
Raw Normal View History

2025-04-27 00:05:18 +08:00
<script setup lang="ts">
2025-04-30 00:17:09 +08:00
import { getSceneById } from '@api/scene';
2025-04-27 00:05:18 +08:00
import { EditorService } from '@core/editor.service';
2025-05-07 20:12:15 +08:00
import { decodeTextFile, downloadFile, selectFile, textToBlob } from '@core/utils';
2025-05-06 23:48:21 +08:00
import { computed, watch } from 'vue';
2025-04-30 00:17:09 +08:00
import { ref } from 'vue';
2025-04-27 00:05:18 +08:00
import { onMounted, provide, shallowRef } from 'vue';
const EDITOR_KEY = Symbol('editor-key');
type Props = {
id: string;
};
const props = defineProps<Props>();
2025-05-05 01:06:09 +08:00
//#region 接口
const readScene = async () => {
const res = await getSceneById(props.id);
title.value = res?.label ?? '';
2025-05-06 23:48:21 +08:00
editor.value?.load(res?.json, editable.value);
2025-05-05 01:06:09 +08:00
};
//#endregion
2025-04-30 00:17:09 +08:00
const title = ref<string>('');
2025-05-05 01:06:09 +08:00
watch(
() => props.id,
() => readScene(),
{ immediate: true, flush: 'post' },
);
2025-04-30 00:17:09 +08:00
2025-04-27 00:05:18 +08:00
const container = shallowRef<HTMLDivElement>();
const editor = shallowRef<EditorService>();
provide(EDITOR_KEY, editor);
onMounted(() => {
editor.value = new EditorService(container.value!);
});
2025-04-30 00:17:09 +08:00
const editable = ref<boolean>(false);
2025-05-05 01:06:09 +08:00
watch(editable, (v) => editor.value?.setState(v));
2025-05-07 20:12:15 +08:00
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);
};
2025-05-05 23:21:31 +08:00
const show = ref<boolean>(true);
const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>();
watch(
() => editor.value?.selected.value[0],
(v) => {
2025-05-06 23:48:21 +08:00
const pen = editor.value?.getPenById(v);
if (pen?.id) {
current.value = { type: <'point' | 'line' | 'area'>pen.name, id: pen.id };
return;
2025-05-05 23:21:31 +08:00
}
if (current.value?.type === 'robot') return;
current.value = undefined;
},
);
const isRobot = computed(() => current.value?.type === 'robot');
const isPoint = computed(() => current.value?.type === 'point');
const isRoute = computed(() => current.value?.type === 'line');
const isArea = computed(() => current.value?.type === 'area');
const selectRobot = (id: string) => {
current.value = { type: 'robot', id };
editor.value?.inactive();
};
2025-04-27 00:05:18 +08:00
</script>
<template>
<a-layout class="full">
2025-05-01 01:07:16 +08:00
<a-layout-header class="p-16" style="height: 64px">
<a-flex justify="space-between" align="center">
2025-05-05 23:21:31 +08:00
<a-typography-text class="title">{{ title }}</a-typography-text>
2025-05-01 01:07:16 +08:00
<a-space align="center">
2025-05-02 00:35:53 +08:00
<a-button v-if="editable" class="warning" @click="editable = false">
2025-05-03 00:28:22 +08:00
<i class="icon exit size-18 mr-8" />
2025-05-02 00:35:53 +08:00
<span>{{ $t('退出编辑器') }}</span>
</a-button>
<a-button v-else type="primary" @click="editable = true">
2025-05-03 00:28:22 +08:00
<i class="icon edit size-18 mr-8" />
2025-05-01 01:07:16 +08:00
<span>{{ $t('启用编辑器') }}</span>
</a-button>
<a-button>{{ $t('推送') }}</a-button>
2025-05-07 20:12:15 +08:00
<a-button @click="importScene">{{ $t('导入') }}</a-button>
<a-button @click="exportScene">{{ $t('导出') }}</a-button>
2025-05-01 01:07:16 +08:00
</a-space>
2025-04-30 00:17:09 +08:00
</a-flex>
</a-layout-header>
2025-04-27 00:05:18 +08:00
2025-05-01 01:07:16 +08:00
<a-layout class="p-16">
<a-layout-sider :width="320">
2025-05-05 23:21:31 +08:00
<a-tabs type="card">
2025-05-01 01:07:16 +08:00
<a-tab-pane key="1" :tab="$t('机器人')">
2025-05-05 01:06:09 +08:00
<RobotGroups
v-if="editor"
:token="EDITOR_KEY"
:editable="editable"
2025-05-05 23:21:31 +08:00
:current="current?.id"
@change="selectRobot"
2025-05-05 01:06:09 +08:00
/>
2025-05-01 01:07:16 +08:00
</a-tab-pane>
2025-05-06 23:48:21 +08:00
<a-tab-pane key="2" :tab="$t('库区')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
</a-tab-pane>
2025-05-05 23:21:31 +08:00
<a-tab-pane key="3" :tab="$t('高级组')">
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
</a-tab-pane>
2025-05-01 01:07:16 +08:00
</a-tabs>
2025-04-27 00:05:18 +08:00
</a-layout-sider>
<a-layout-content>
2025-05-01 01:07:16 +08:00
<div ref="container" class="editor-container full"></div>
2025-04-27 00:05:18 +08:00
</a-layout-content>
</a-layout>
</a-layout>
2025-05-05 23:21:31 +08:00
<div v-if="editable" class="toolbar-container">
2025-05-07 20:12:15 +08:00
<EditorToolbar v-if="editor" :token="EDITOR_KEY" :id="id" />
2025-05-05 23:21:31 +08:00
</div>
<template v-if="current?.id">
<a-float-button style="top: 80px; right: 16px" shape="square" @click="show = !show">
<template #icon><i class="icon detail" /></template>
</a-float-button>
<div v-if="show" class="card-container">
<RobotDetailCard v-if="isRobot" :token="EDITOR_KEY" :current="current.id" />
2025-05-06 23:48:21 +08:00
<template v-if="isPoint">
2025-05-08 00:42:08 +08:00
<PointEditCard v-if="editable" :token="EDITOR_KEY" :id="current.id" />
2025-05-06 23:48:21 +08:00
<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>
2025-05-05 23:21:31 +08:00
</div>
</template>
2025-04-27 00:05:18 +08:00
</template>
2025-05-01 01:07:16 +08:00
<style scoped lang="scss">
.editor-container {
background-color: transparent !important;
}
2025-05-05 23:21:31 +08:00
.toolbar-container {
position: fixed;
right: 50%;
bottom: 40px;
left: 50%;
2025-05-07 20:12:15 +08:00
z-index: 100;
2025-05-05 23:21:31 +08:00
}
.card-container {
position: fixed;
top: 80px;
right: 64px;
2025-05-07 20:12:15 +08:00
z-index: 100;
2025-05-05 23:21:31 +08:00
width: 320px;
2025-05-08 00:42:08 +08:00
max-height: calc(100% - 96px);
2025-05-05 23:21:31 +08:00
}
.ant-typography.title {
font: 500 16px/22px SourceHanSansSC;
}
2025-05-01 01:07:16 +08:00
</style>