|
@@ -1,7 +1,7 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="page">
|
|
<div class="page">
|
|
|
- <div class="flex pt-10">
|
|
|
|
|
- <div class="flex items-center ml-20">
|
|
|
|
|
|
|
+ <div class="flex">
|
|
|
|
|
+ <div class="flex items-center">
|
|
|
<span>场景:</span>
|
|
<span>场景:</span>
|
|
|
<el-tree-select
|
|
<el-tree-select
|
|
|
v-model="selectedShop"
|
|
v-model="selectedShop"
|
|
@@ -11,72 +11,122 @@
|
|
|
@change="changeShop"
|
|
@change="changeShop"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="flex ml-20 items-center">
|
|
|
|
|
- <span>上传图片:</span>
|
|
|
|
|
- <el-upload
|
|
|
|
|
- class="avatar-uploader flex justify-center items-center"
|
|
|
|
|
- action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
|
|
|
|
- :show-file-list="false"
|
|
|
|
|
- :on-success="handleAvatarSuccess"
|
|
|
|
|
- :before-upload="beforeAvatarUpload"
|
|
|
|
|
- >
|
|
|
|
|
- <img v-if="bgImgUrl" :src="bgImgUrl" alt="" />
|
|
|
|
|
- <div v-else class="flex flex-col justify-center items-center">
|
|
|
|
|
- <el-icon size="30" color="#c0c4cc"><Plus /></el-icon>
|
|
|
|
|
- <span>上传背景图片</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-upload>
|
|
|
|
|
- </div>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="paint-tool mt-10 flex">
|
|
|
|
|
|
|
+ <div class="paint-tool flex">
|
|
|
<div class="camera-list">
|
|
<div class="camera-list">
|
|
|
- <span class="label-text flex">相机列表:</span>
|
|
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <span class="label-text flex">相机列表:</span>
|
|
|
|
|
+ <ElInput
|
|
|
|
|
+ style="margin: 10px; width: 230px"
|
|
|
|
|
+ placeholder="输入相机id或工位"
|
|
|
|
|
+ v-model="searchKey"
|
|
|
|
|
+ :suffix-icon="Search"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
<el-scrollbar style="height: calc(100% - 50px)">
|
|
<el-scrollbar style="height: calc(100% - 50px)">
|
|
|
<div
|
|
<div
|
|
|
- v-for="item in shopCameraList"
|
|
|
|
|
|
|
+ v-for="item in filterShopCameraList"
|
|
|
:key="item.code"
|
|
:key="item.code"
|
|
|
class="camera-item flex justify-start"
|
|
class="camera-item flex justify-start"
|
|
|
|
|
+ :class="{ isAdded: isAddedToMap(item.code) }"
|
|
|
|
|
+ @click="handleAddCamera(item.code)"
|
|
|
>
|
|
>
|
|
|
<span class="camera-id">{{ item.name }}</span>
|
|
<span class="camera-id">{{ item.name }}</span>
|
|
|
<span>{{ item.workSpaceName }}</span>
|
|
<span>{{ item.workSpaceName }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</el-scrollbar>
|
|
</el-scrollbar>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="draw-container"></div>
|
|
|
|
|
|
|
+ <div class="draw-container">
|
|
|
|
|
+ <div style="display: flex; margin-bottom: 20px">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ 默认摄像头:
|
|
|
|
|
+ <ElSelect v-model="defaultCameraId">
|
|
|
|
|
+ <ElOption
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.value"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ v-for="item in cameraOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+ </ElSelect>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ class="avatar-uploader flex justify-center items-center"
|
|
|
|
|
+ action="/temp/api/layout/uploadPicture"
|
|
|
|
|
+ :show-file-list="false"
|
|
|
|
|
+ :on-success="handleAvatarSuccess"
|
|
|
|
|
+ :with-credentials="true"
|
|
|
|
|
+ name="file"
|
|
|
|
|
+ :data="{ workshopId: selectedShopId }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button style="font-size: 12px">+ 更换/上传背景图片</el-button>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ @click="handleSave"
|
|
|
|
|
+ style="margin-left: 40px"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :disabled="!selectedShopId"
|
|
|
|
|
+ >保存布局</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div style="height: 20px; margin-bottom: 10px">
|
|
|
|
|
+ <div v-if="selectedCamera">
|
|
|
|
|
+ 已选中相机<span>: {{ selectedCamera?.cameraId }}</span>
|
|
|
|
|
+ <span style="margin-left: 20px">
|
|
|
|
|
+ 工位:{{ selectedCameraDetail?.workSpaceName }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="overflow: auto">
|
|
|
|
|
+ <canvas
|
|
|
|
|
+ width="400"
|
|
|
|
|
+ height="400"
|
|
|
|
|
+ id="mapEditCanvas"
|
|
|
|
|
+ style="border: 1px solid #ccc"
|
|
|
|
|
+ ></canvas>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
- import { onMounted } from 'vue';
|
|
|
|
|
- import { Plus } from '@element-plus/icons-vue';
|
|
|
|
|
import useMiniMap from './use-mini-map';
|
|
import useMiniMap from './use-mini-map';
|
|
|
import { storeToRefs } from 'pinia';
|
|
import { storeToRefs } from 'pinia';
|
|
|
- import { ElMessage } from 'element-plus';
|
|
|
|
|
|
|
+ import { ElMessage, ElInput } from 'element-plus';
|
|
|
|
|
+ import urlJoin from 'url-join';
|
|
|
|
|
+ import { onMounted, ref, toRaw } from 'vue';
|
|
|
|
|
+ import CameraMap, { CameraImage } from './MapBase/CameraMap';
|
|
|
|
|
+ import { ElSelect, ElOption } from 'element-plus';
|
|
|
|
|
+ import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
|
|
|
|
|
+ import { onUnmounted } from 'vue';
|
|
|
|
|
+ import { useGlobSetting } from '@/hooks/setting';
|
|
|
|
|
+ import { computed } from 'vue';
|
|
|
|
|
+ import { Search } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
|
const miniMap = useMiniMap();
|
|
const miniMap = useMiniMap();
|
|
|
- const { selectedShop, bgImgUrl, scenesTree, shopCameraList } = storeToRefs(miniMap);
|
|
|
|
|
- const { getScenesTree, getShowCameras } = miniMap;
|
|
|
|
|
-
|
|
|
|
|
- const beforeAvatarUpload = (rawFile) => {
|
|
|
|
|
- console.log('图片信息', rawFile);
|
|
|
|
|
- const render = new FileReader();
|
|
|
|
|
- render.onload = (e) => {
|
|
|
|
|
- const img = document.createElement('img');
|
|
|
|
|
- bgImgUrl.value = img.src = e.target?.result as string;
|
|
|
|
|
- img.onload = () => {
|
|
|
|
|
- console.log('width====', img.width);
|
|
|
|
|
- console.log('height====', img.height);
|
|
|
|
|
- };
|
|
|
|
|
- };
|
|
|
|
|
- render.readAsDataURL(rawFile);
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const globSetting = useGlobSetting();
|
|
|
|
|
+ const { selectedShop, scenesTree, shopCameraList, selectedShopId } = storeToRefs(miniMap);
|
|
|
|
|
+ const { getScenesTree, getShowCameras, getMapLayout } = miniMap;
|
|
|
|
|
+
|
|
|
|
|
+ let map: CameraMap;
|
|
|
|
|
+ const selectedCamera = ref<CameraImage | null>(null);
|
|
|
|
|
+ const cameraOptions = ref<{ label: string; value: string }[]>([]);
|
|
|
|
|
+ const defaultCameraId = ref('');
|
|
|
|
|
+ const searchKey = ref('');
|
|
|
|
|
|
|
|
- const handleAvatarSuccess = () => {};
|
|
|
|
|
|
|
+ const handleAvatarSuccess = (e) => {
|
|
|
|
|
+ const imgPath = e.data;
|
|
|
|
|
+ const imgUrl = urlJoin(globSetting.imgUrl!, imgPath);
|
|
|
|
|
+ map.uploadBg(imgUrl);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
const changeShop = (newVal) => {
|
|
const changeShop = (newVal) => {
|
|
|
const info = JSON.parse(newVal);
|
|
const info = JSON.parse(newVal);
|
|
|
-
|
|
|
|
|
|
|
+ console.log('info', info);
|
|
|
if (info.parentId || info.parentId == 0) {
|
|
if (info.parentId || info.parentId == 0) {
|
|
|
ElMessage({
|
|
ElMessage({
|
|
|
message: '该场景暂无相机',
|
|
message: '该场景暂无相机',
|
|
@@ -85,29 +135,131 @@
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
getShowCameras();
|
|
getShowCameras();
|
|
|
|
|
+ getMapLayout().then((res) => {
|
|
|
|
|
+ if (!res) {
|
|
|
|
|
+ map.clear();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ defaultCameraId.value = res.defaultCameraId;
|
|
|
|
|
+ map.loadFromJSON(res).then(() => {
|
|
|
|
|
+ mapJSONToOptions();
|
|
|
|
|
+ if (!defaultCameraId.value) {
|
|
|
|
|
+ defaultCameraId.value = cameraOptions.value[0]?.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
getScenesTree(2);
|
|
getScenesTree(2);
|
|
|
|
|
+ map = new CameraMap({ canvasId: 'mapEditCanvas', onSelect: onSelectCamera });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const keyupListener = (e) => {
|
|
|
|
|
+ const keyCode = e.code;
|
|
|
|
|
+ if (keyCode === 'Delete' || keyCode === 'Backspace') {
|
|
|
|
|
+ if (selectedCamera.value) {
|
|
|
|
|
+ handleDeleteCamera();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
|
+ document.addEventListener('keyup', keyupListener);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
|
+ document.removeEventListener('keyup', keyupListener);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const selectedCameraDetail = computed(() => {
|
|
|
|
|
+ return shopCameraList.value.find((item) => item.code === selectedCamera.value?.cameraId);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const filterShopCameraList = computed(() => {
|
|
|
|
|
+ const k = searchKey.value.trim();
|
|
|
|
|
+ if (!k) return shopCameraList.value;
|
|
|
|
|
+ return shopCameraList.value.filter((x) => x.code?.includes(k) || x.workSpaceName?.includes(k));
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ /** 摄像机是否已添加到底图 */
|
|
|
|
|
+ const isAddedToMap = (cameraId: string): boolean => {
|
|
|
|
|
+ return cameraOptions.value.some((x) => x.value === cameraId);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleAddCamera = (cameraId: string) => {
|
|
|
|
|
+ if (map.hasCamera(cameraId)) {
|
|
|
|
|
+ ElMessage.warning({ message: '相机已添加' });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ map.addCamera(cameraId).then((cameraImg) => {
|
|
|
|
|
+ onSelectCamera(cameraImg);
|
|
|
|
|
+ mapJSONToOptions();
|
|
|
|
|
+ if (!defaultCameraId.value) {
|
|
|
|
|
+ defaultCameraId.value = cameraOptions.value[0]?.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const onSelectCamera = (cameraImg: CameraImage) => {
|
|
|
|
|
+ console.log('onSelectCamera', cameraImg);
|
|
|
|
|
+ selectedCamera.value = cameraImg;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const mapJSONToOptions = () => {
|
|
|
|
|
+ const objects = map.toJSON()?.objects || [];
|
|
|
|
|
+ console.log('objects', objects);
|
|
|
|
|
+ cameraOptions.value = objects?.map((x) => ({
|
|
|
|
|
+ label: x.cameraId,
|
|
|
|
|
+ value: x.cameraId,
|
|
|
|
|
+ }));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSave = () => {
|
|
|
|
|
+ const json = map.toJSON();
|
|
|
|
|
+ console.log('save json', json);
|
|
|
|
|
+ if (!json?.backgroundImage) {
|
|
|
|
|
+ ElMessage.error('背景图片未添加');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const layout = JSON.stringify({ ...json, defaultCameraId: defaultCameraId.value });
|
|
|
|
|
+ updateMinMapViewLayoutApi({ layout, targetId: selectedShopId.value }).then((res) => {
|
|
|
|
|
+ console.log('updateMinMapViewLayoutApi', res);
|
|
|
|
|
+ ElMessage.success('保存成功');
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleDeleteCamera = () => {
|
|
|
|
|
+ if (!selectedCamera.value) return;
|
|
|
|
|
+ map.removeCamera(toRaw(selectedCamera.value!));
|
|
|
|
|
+ /** 如果删除的是默认选中的摄像头,那么先清空默认的摄像头再 */
|
|
|
|
|
+ mapJSONToOptions();
|
|
|
|
|
+ if (selectedCamera.value.cameraId === defaultCameraId.value) {
|
|
|
|
|
+ defaultCameraId.value = cameraOptions.value[0]?.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ selectedCamera.value = null;
|
|
|
|
|
+ };
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
.page {
|
|
.page {
|
|
|
margin: 10px 0;
|
|
margin: 10px 0;
|
|
|
- padding-bottom: 10px;
|
|
|
|
|
|
|
+ padding: 10px;
|
|
|
background-color: #ffffff;
|
|
background-color: #ffffff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.avatar-uploader {
|
|
.avatar-uploader {
|
|
|
- width: 100px;
|
|
|
|
|
- height: 100px;
|
|
|
|
|
- border: 1px solid #c0c4cc;
|
|
|
|
|
|
|
+ /* width: 120px; */
|
|
|
|
|
+ /* height: 30px; */
|
|
|
|
|
+ /* border: 1px solid #eee; */
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ margin-left: 30px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.paint-tool {
|
|
.paint-tool {
|
|
|
- height: calc(100vh - 248px - 5rem);
|
|
|
|
|
|
|
+ height: calc(100vh - 200px);
|
|
|
margin: 0 10px;
|
|
margin: 0 10px;
|
|
|
|
|
+ margin-top: 20px;
|
|
|
border: 1px solid #c0c4cc;
|
|
border: 1px solid #c0c4cc;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -118,12 +270,13 @@
|
|
|
.label-text {
|
|
.label-text {
|
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- margin: 10px 0 20px 10px;
|
|
|
|
|
|
|
+ margin: 10px 0 5px 10px;
|
|
|
}
|
|
}
|
|
|
.camera-item {
|
|
.camera-item {
|
|
|
padding: 10px 0 10px 10px;
|
|
padding: 10px 0 10px 10px;
|
|
|
margin: 0 8px 1px 8px;
|
|
margin: 0 8px 1px 8px;
|
|
|
border: 1px solid #c0c4cc;
|
|
border: 1px solid #c0c4cc;
|
|
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
}
|
|
|
.camera-id {
|
|
.camera-id {
|
|
|
width: 130px;
|
|
width: 130px;
|
|
@@ -131,8 +284,13 @@
|
|
|
|
|
|
|
|
.draw-container {
|
|
.draw-container {
|
|
|
width: calc(100% - 300px);
|
|
width: calc(100% - 300px);
|
|
|
|
|
+ margin: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
:deep(.avatar-uploader .el-upload) {
|
|
:deep(.avatar-uploader .el-upload) {
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .isAdded {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
</style>
|
|
</style>
|