Browse Source

Merge branch 'dev' of http://192.168.0.115:3000/bill/4pc into dev

gemercheung 1 năm trước cách đây
mục cha
commit
ba72c46f1c

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 7 - 0
src/assets/location_n.svg


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 7 - 0
src/assets/location_o.svg


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 56 - 10
src/lib/board/4dmap.d.ts


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1592 - 1581
src/lib/board/4dmap.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 4 - 4
src/lib/board/4dmap.umd.cjs


+ 13 - 15
src/request/type.ts

@@ -11,20 +11,18 @@ import {
   WholeLinePolygonAttrib,
 } from "drawing-board";
 
-
-
 type UserInfoRoles = {
-  roleId: number
-  roleKey: string
-  roleName: string
-}
+  roleId: number;
+  roleKey: string;
+  roleName: string;
+};
 export type UserInfo = {
   head: string;
   nickName: string;
   userName: string;
-  roles: UserInfoRoles[]
-  orgId: string
-  orgName?: string
+  roles: UserInfoRoles[];
+  orgId: string;
+  orgName?: string;
 };
 
 export type Relics = {
@@ -47,18 +45,18 @@ export type ResPage<T> = {
 };
 
 export type ResResult = {
-  code: number
-  data: any
-  message: any
-  success: boolean
-  timestamp: string
-
+  code: number;
+  data: any;
+  message: any;
+  success: boolean;
+  timestamp: string;
 };
 export type ScenePoint = {
   tbStatus: number;
   createTime: string;
   updateTime: string;
   cameraType: DeviceType;
+  index: number;
   id: number;
   uuid: number;
   name: string;

+ 47 - 10
src/router.ts

@@ -2,6 +2,11 @@ import { RouteRecordRaw, createRouter, createWebHashHistory } from "vue-router";
 import { UserStatus, logintAuth, userStatus } from "./store/user";
 import { watch, watchEffect } from "vue";
 
+export const COORD_NAME = "map-coord";
+export const POYS_NAME = "map-poy";
+export const QUERY_COORD_NAME = "query-map-coord";
+export const QUERY_POYS_NAME = "query-map-poy";
+
 const history = createWebHashHistory();
 const routes: RouteRecordRaw[] = [
   {
@@ -35,18 +40,32 @@ const routes: RouteRecordRaw[] = [
       {
         path: "relics/:relicsId",
         children: [
+          // {
+          //   path: "",
+          //   name: "map",
+          //   meta: { title: "文物", navClass: "map" },
+          //   component: () => import("@/view/map/map-board.vue"),
+          // },
           {
-            path: "",
+            path: "map",
             name: "map",
             meta: { title: "文物", navClass: "map" },
-            component: () => import("@/view/map/map-board.vue"),
+            component: () => import("@/view/map/layout.vue"),
+            children: [
+              {
+                path: "coord",
+                name: COORD_NAME,
+                meta: { title: "文物", navClass: "map" },
+                component: () => import("@/view/map/coord.vue"),
+              },
+              {
+                path: "polygons",
+                name: POYS_NAME,
+                meta: { title: "文物", navClass: "map" },
+                component: () => import("@/view/map/polygons.vue"),
+              },
+            ],
           },
-          // {
-          //   path: "test",
-          //   name: "map-test",
-          //   meta: { title: "文物", navClass: "map" },
-          //   component: () => import("@/view/map/test-board.vue"),
-          // },
           {
             path: "pano/:pid",
             name: "pano",
@@ -79,7 +98,6 @@ const routes: RouteRecordRaw[] = [
         meta: { title: "用户管理" },
         component: () => import("@/view/users.vue"),
       },
-  
     ],
   },
   {
@@ -94,7 +112,21 @@ const routes: RouteRecordRaw[] = [
             path: "",
             name: "query-map",
             meta: { title: "文物", navClass: "map" },
-            component: () => import("@/view/map/map.vue"),
+            component: () => import("@/view/map/layout.vue"),
+            children: [
+              {
+                path: "query-coord",
+                name: QUERY_COORD_NAME,
+                meta: { title: "文物", navClass: "map" },
+                component: () => import("@/view/map/coord.vue"),
+              },
+              {
+                path: "query-polygons",
+                name: QUERY_POYS_NAME,
+                meta: { title: "文物", navClass: "map" },
+                component: () => import("@/view/map/polygons.vue"),
+              },
+            ],
           },
           {
             path: "pano/:pid",
@@ -169,6 +201,11 @@ router.beforeEach((to, _, next) => {
     }
     return;
   }
+  if (to.name === "map") {
+    router.replace({ name: COORD_NAME, params: to.params });
+  } else if (to.name === "query-map") {
+    router.replace({ name: QUERY_COORD_NAME, params: to.params });
+  }
 
   if (to.meta?.title) {
     setDocTitle(to.meta.title as string);

+ 96 - 21
src/store/scene.ts

@@ -1,11 +1,17 @@
 import { relicsScenesFetch, updateRelicsScenePosNameFetch } from "@/request";
-import { computed, ref } from "vue";
+import { computed, ref, watch } from "vue";
 import { Scene, ScenePoint } from "@/request/type";
 import { gHeaders } from "@/request/state";
 import { relics } from "./relics";
 import { DeviceType, DeviceType as SceneType } from "./device";
 import { conversionFactory } from "@/helper/coord-transform";
 import { getTokenFetch } from "@/request";
+import {
+  PolygonsPointAttrib,
+  getWholeLineLinesByPointId,
+  PolygonsAttrib,
+} from "drawing-board";
+import { getDrawingDetailFetch } from "@/request/drawing";
 
 export type { Scene, ScenePoint };
 
@@ -21,6 +27,7 @@ export const scenePoints = computed(() =>
     return t;
   }, [] as ScenePoint[])
 );
+
 export const relicsId = computed(() => relics.value!.relicsId);
 
 // https://4dkankan.oss-cn-shenzhen.aliyuncs.com/scene_view_data/KJ-t-OgSx9XIrvNQ/images/panoramas/22.jpg?x-oss-process=image/resize,m_fixed,w_6144&171342528615
@@ -38,6 +45,7 @@ export const getPointPano = (point: ScenePoint, tile = false) => {
     return `https://4dkk.4dage.com/scene_view_data/${point.sceneCode}/images/pan/high/${point.uuid}.jpg`;
   }
 };
+
 export const refreshScenes = async () => {
   const sscenes = await relicsScenesFetch(relicsId.value);
   scenes.value = sscenes.map((scene) => {
@@ -70,28 +78,32 @@ export const refreshScenes = async () => {
 
     return {
       ...scene,
-      scenePos: scene.scenePos.map((pos) => {
-        let coord =
-          scene.calcStatus !== SceneStatus.SUCCESS ? ([] as any) : pos.pos;
-        if (conversion && scene.calcStatus === SceneStatus.SUCCESS) {
-          let center = scenesTransform[pos.sceneCode]?.translate || [0, 0, 0];
-          let rotate = scenesTransform[pos.sceneCode]?.rotate || 0;
-          let [x, y, z] = pos.location;
-          console.log(pos.location);
-          const cos = Math.cos(rotate);
-          const sin = Math.sin(rotate);
-          x = x * cos - y * sin + center[0];
-          y = x * sin + y * cos + center[1];
-
-          coord = conversion.toWGS84([x, y, z]);
-        }
-        return {
-          ...pos,
-          pos: coord,
-        };
-      }),
+      scenePos: scene.scenePos
+        .sort((a, b) => a.index - b.index)
+        .map((pos) => {
+          let coord =
+            scene.calcStatus !== SceneStatus.SUCCESS ? ([] as any) : pos.pos;
+          if (conversion && scene.calcStatus === SceneStatus.SUCCESS) {
+            let center = scenesTransform[pos.sceneCode]?.translate || [0, 0, 0];
+            let rotate = scenesTransform[pos.sceneCode]?.rotate || 0;
+            let [x, y, z] = pos.location;
+            console.log(pos.location);
+            const cos = Math.cos(rotate);
+            const sin = Math.sin(rotate);
+            x = x * cos - y * sin + center[0];
+            y = x * sin + y * cos + center[1];
+
+            coord = conversion.toWGS84([x, y, z]);
+          }
+          return {
+            ...pos,
+            pos: coord,
+          };
+        }),
     };
   });
+
+  await refreshBoardData();
 };
 
 export const updateScenePointName = async (
@@ -145,3 +157,66 @@ export const SceneStatusDesc: { [key in SceneStatus]: string } = {
   [SceneStatus.ERR]: "计算失败",
   [SceneStatus.SUCCESS]: "计算成功",
 };
+
+export const boardData = ref<PolygonsAttrib & { id: string }>();
+export const refreshBoardData = async () => {
+  const data = (await getDrawingDetailFetch(String(relicsId.value)))
+    .data as PolygonsAttrib;
+
+  boardData.value = {
+    ...data,
+    id: relicsId.value.toString(),
+  };
+};
+
+const scenePosTransform = (scenes: Scene[]) => {
+  const points: PolygonsPointAttrib[] = [];
+  scenes.forEach((scene) => {
+    if (scene.calcStatus !== SceneStatus.SUCCESS) {
+      return;
+    }
+    scene.scenePos.forEach((pos) => {
+      if (!pos.pos || pos.pos.length === 0) {
+        return;
+      }
+      points.push({
+        x: pos.pos[0],
+        y: pos.pos[1],
+        title: pos.name,
+        id: pos.id.toString(),
+        rtk: true,
+      });
+    });
+  });
+  return points;
+};
+
+watch(
+  () => ({ scenes: scenes.value, poyData: boardData.value }),
+  ({ scenes, poyData }) => {
+    if (!poyData) return;
+
+    const points = scenePosTransform(scenes);
+    const canDelPoint = (id: string) =>
+      getWholeLineLinesByPointId(poyData, id).length === 0 &&
+      !points.some(({ id: rtkId }) => id === rtkId);
+
+    // 查看是否有多余的点,有则删除,出现原因是删除了场景
+    for (let i = 0; i < poyData.points.length; i++) {
+      if (canDelPoint(poyData.points[i].id)) {
+        poyData.points.splice(i--, 1);
+      }
+    }
+
+    // 将rtk点加入
+    for (let i = 0; i < points.length; i++) {
+      const ndx = poyData.points.findIndex(({ id }) => id === points[i].id);
+      if (!~ndx) {
+        poyData.points.push(points[i]);
+      } else {
+        poyData.points[ndx] = { ...points[i] };
+      }
+    }
+  },
+  { immediate: true, flush: "sync" }
+);

+ 158 - 91
src/view/map/map-right.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="right-layout">
+  <div class="right-layout" @click="board.polygon.status.lightPointId = null">
     <div class="right-content">
-      <el-form :inline="false" v-if="router.currentRoute.value.name === 'map'">
+      <el-form :inline="false" v-if="!queryMode">
         <el-form-item>
           <el-button type="primary" :icon="Plus" style="width: 100%" @click="addHandler">
             添加场景
@@ -10,13 +10,33 @@
       </el-form>
       <div class="tree-layout">
         <p class="sub-title">全部数据</p>
-        <el-tree style="max-width: 600px" :data="treeNode" :props="{ disabled: 'run' }" node-key="id" ref="treeRef"
-          :show-checkbox="router.currentRoute.value.name === 'map'" default-expand-all :expand-on-click-node="false">
+        <el-tree
+          style="max-width: 600px"
+          :data="treeNode"
+          node-key="id"
+          ref="treeRef"
+          :show-checkbox="!queryMode"
+          default-expand-all
+          :expand-on-click-node="false"
+        >
           <template #default="{ node, data }">
-            <div class="tree-item"
-              @click="!data.disable && emit((data.type === 'scene' ? 'flyScene' : 'flyPoint') as any, data.raw)">
-              <el-tooltip v-if="data.type === 'scene'" class="box-item" effect="dark"
-                :content="data.raw.sceneName + ' ' + node.label" placement="top">
+            <div
+              class="tree-item"
+              :class="{
+                active: board.polygon.status.lightPointId === data.raw.id.toString(),
+              }"
+              @click.stop="
+                !data.disable &&
+                  (data.type === 'scene' ? flyScene(data) : flyPos(data.raw))
+              "
+            >
+              <el-tooltip
+                v-if="data.type === 'scene'"
+                class="box-item"
+                effect="dark"
+                :content="data.raw.sceneName + ' ' + node.label"
+                placement="top"
+              >
                 <span :class="{ disable: data.disable }" class="title">
                   <el-icon>
                     <Grid />
@@ -26,7 +46,13 @@
                   <span class="tree-scene-name">{{ node.label }}</span>
                 </span>
               </el-tooltip>
-              <el-tooltip v-else class="box-item" effect="dark" :content="node.label" placement="top">
+              <el-tooltip
+                v-else
+                class="box-item"
+                effect="dark"
+                :content="data.raw.name || node.label"
+                placement="top"
+              >
                 <div class="title-box">
                   <span :class="{ disable: data.disable }" class="title">
                     <el-icon>
@@ -34,15 +60,14 @@
                       <DeleteLocation v-else />
                     </el-icon>
                     {{ node.label }}
-                    <!-- uu -->
                   </span>
                   <span :class="{ disable: data.disable }" class="name">
                     {{ data.raw.name }}
                   </span>
                 </div>
               </el-tooltip>
-              <span class="oper">
-                <template v-if="router.currentRoute.value.name === 'map'">
+              <span class="oper" @click.stop>
+                <template v-if="!queryMode">
                   <template v-if="data.type === 'scene'">
                     <el-icon color="#409efc" v-if="data.raw.creationMethod !== 2">
                       <Delete @click.stop="delSceneHandler([data.raw])" />
@@ -55,18 +80,24 @@
                 <el-icon color="#409efc" style="margin-left: 8px">
                   <!-- root -->
                   <template v-if="data.raw.scenePos">
-                    <FrameIcon v-if="!data.run" @click.stop="
-                      data.type === 'scene'
-                        ? gotoScene(data.raw)
-                        : emit('gotoPoint', data.raw)
-                      " />
+                    <FrameIcon
+                      v-if="!data.run"
+                      @click.stop="
+                        data.type === 'scene'
+                          ? gotoScene(data.raw)
+                          : gotoPointPage(data.raw)
+                      "
+                    />
                   </template>
                   <template v-else>
-                    <PanoramaIcon v-if="!data.run" @click.stop="
-                      data.type === 'scene'
-                        ? gotoScene(data.raw)
-                        : emit('gotoPoint', data.raw)
-                      " />
+                    <PanoramaIcon
+                      v-if="!data.run"
+                      @click.stop="
+                        data.type === 'scene'
+                          ? gotoScene(data.raw)
+                          : gotoPointPage(data.raw)
+                      "
+                    />
                   </template>
                 </el-icon>
               </span>
@@ -76,27 +107,39 @@
       </div>
     </div>
 
-    <template v-if="router.currentRoute.value.name === 'map'">
-      <el-button type="primary" :icon="Download" style="width: 100%" @click="handlerExport(2, relics?.name)">
+    <template v-if="!queryMode">
+      <el-button
+        type="primary"
+        :icon="Download"
+        style="width: 100%"
+        @click="exportFile(getSelectPoints(), 2, relics?.name)"
+      >
         导出本体边界坐标
       </el-button>
-      <!-- <el-button type="primary" :icon="Download" style="width: 100%; margin-top: 20px; margin-left: 0"
-        @click="exportFile(getSelectPoints(), 1, relics?.name)">
-        导出绘制矢量数据
-      </el-button> -->
 
-      <el-button type="primary" :icon="Download" style="width: 100%; margin-top: 20px; margin-left: 0"
-        @click="exportImage(getSelectPoints(), relics?.name)">
+      <el-button
+        type="primary"
+        :icon="Download"
+        style="width: 100%; margin-top: 20px; margin-left: 0"
+        @click="exportImage(getSelectPoints(), relics?.name)"
+      >
         下载全景图
       </el-button>
     </template>
   </div>
 
-  <SingleInput :visible="!!inputPoint" @update:visible="inputPoint = null" :value="inputPoint?.name || ''"
-    :update-value="updatePointName" title="测点说明" placeholder="请填写测点说明" />
+  <SingleInput
+    :visible="!!inputPoint"
+    @update:visible="inputPoint = null"
+    :value="inputPoint?.name || ''"
+    :update-value="updatePointName"
+    title="测点说明"
+    placeholder="请填写测点说明"
+  />
 </template>
 
 <script setup lang="ts">
+import { boardDataChange, queryMode } from "./install";
 import {
   Plus,
   Delete,
@@ -105,7 +148,7 @@ import {
   DeleteLocation,
   Edit,
 } from "@element-plus/icons-vue";
-import { computed, ref, watchEffect } from "vue";
+import { computed, onBeforeUnmount, ref, watchEffect } from "vue";
 import {
   Scene,
   scenes,
@@ -114,10 +157,11 @@ import {
   gotoScene,
   relicsId,
   refreshScenes,
+  boardData,
+  scenePoints,
 } from "@/store/scene";
 import { relics } from "@/store/relics";
 import SingleInput from "@/components/single-input.vue";
-import { router } from "@/router";
 import { selectScenes } from "../quisk";
 import { addRelicsScenesFetch, delRelicsScenesFetch } from "@/request";
 import { exportFile, exportImage } from "./pc4Helper";
@@ -125,24 +169,30 @@ import { SceneStatus } from "@/store/scene";
 import StateGpsIcon from "@/assets/state_gps.svg";
 import PanoramaIcon from "@/assets/panorama.svg";
 import FrameIcon from "@/assets/frame.svg";
-import { DrawingDataType, getDrawingDetailFetch } from "@/request/drawing";
 import { alert } from "@/helper/message";
-import { Polygons, getWholeLineLinesByPointId } from "drawing-board";
-import { ElMessageBox } from "element-plus";
-const props = defineProps<{
-  data: DrawingDataType | null;
-  boardPolygons: Polygons;
-}>();
-
-const emit = defineEmits<{
-  (e: "flyScene", data: Scene): void;
-  (e: "flyPoint", data: ScenePoint): void;
-  (e: "gotoPoint", data: ScenePoint): void;
-}>();
+import {
+  PolygonsPointAttrib,
+  getWholeLineLinesByPointId,
+  getWholeLinePoint,
+} from "drawing-board";
+import { flyScene, gotoPointPage, mapManage } from "./install";
+import { board } from "./install";
 
 const inputPoint = ref<ScenePoint | null>(null);
 const updatePointName = async (title: string) => {
-  await updateScenePointName(inputPoint.value!, title);
+  const point = getWholeLinePoint(
+    boardData.value,
+    inputPoint.value.id.toString()
+  ) as PolygonsPointAttrib;
+  await Promise.all([
+    boardDataChange(() => (point.title = title)),
+    updateScenePointName(inputPoint.value!, title),
+  ]);
+};
+
+const flyPos = (point: ScenePoint) => {
+  mapManage.map.getView().setCenter(point.pos);
+  board.polygon.status.lightPointId = point.id.toString();
 };
 
 const relicsName = ref("");
@@ -176,11 +226,27 @@ const getSelectPoints = () =>
 
 watchEffect(() => {
   if (treeRef.value) {
-    props.boardPolygons.status.selectPoiIds = getSelectPoints().map((point) =>
+    board.polygon.status.selectPoiIds = getSelectPoints().map((point) =>
       point.id.toString()
     );
   }
 });
+
+const delScenesBeforeCheck = async (scenes: Scene[]) => {
+  if (scenes.length === 0) return true;
+  for (const scene of scenes) {
+    const que = scene.scenePos.some((pos) => {
+      const id = pos.id.toString();
+      return getWholeLineLinesByPointId(boardData.value, id).length !== 0;
+    });
+    if (que) {
+      await alert("已存在矢量图数据,不可删除。");
+      return false;
+    }
+    return true;
+  }
+};
+
 const addHandler = async () => {
   const sceneCodes = scenes.value.map((scene) => scene.sceneCode);
   await selectScenes({
@@ -192,9 +258,25 @@ const addHandler = async () => {
         .filter((sceneCode) => !nScene.some((scene) => scene.sceneCode === sceneCode))
         .map((sceneCode) => scenes.value.find((scene) => scene.sceneCode === sceneCode)!);
 
-      delScenes.length && requests.push(delRelicsScenes(delScenes));
+      if (!(await delScenesBeforeCheck(delScenes))) {
+        throw "不可删除";
+      }
+
+      delScenes.length &&
+        requests.push(
+          delRelicsScenesFetch(
+            relicsId.value,
+            delScenes.map((item) => ({ sceneCode: item.sceneCode, id: item.sceneId }))
+          )
+        );
       const addScenes = nScene.filter(({ sceneCode }) => !sceneCodes.includes(sceneCode));
-      addScenes.length && requests.push(addSceneHandler(addScenes));
+      addScenes.length &&
+        requests.push(
+          addRelicsScenesFetch(
+            relicsId.value!,
+            addScenes.map((item) => ({ sceneCode: item.sceneCode, id: item.sceneId }))
+          )
+        );
 
       await Promise.all(requests);
       requests.length && (await refreshScenes());
@@ -202,50 +284,33 @@ const addHandler = async () => {
   });
 };
 
-const delRelicsScenes = (scenes: Pick<Scene, "sceneId" | "sceneCode">[]) =>
-  delRelicsScenesFetch(
-    relicsId.value,
-    scenes.map((item) => ({ sceneCode: item.sceneCode, id: item.sceneId }))
-  );
-
 const delSceneHandler = async (scenes: Scene[]) => {
-
-  const ok = await ElMessageBox.confirm("确定要删除场景吗?", {
-    type: "warning",
-  });
-  if (ok) {
-    const res = await getDrawingDetailFetch(String(relicsId.value));
-    for (const scene of scenes) {
-      const que = scene.scenePos.some((pos) => {
-        const id = pos.id.toString();
-        console.log(getWholeLineLinesByPointId(res.data as any, id), id);
-        return getWholeLineLinesByPointId(res.data as any, id).length !== 0;
-      });
-      if (que) {
-        await alert("已存在矢量图数据,不可删除。");
-        return;
-      }
-    }
-
-    console.log(res.data);
+  if (!(await delScenesBeforeCheck(scenes))) {
     return;
-    await delRelicsScenes(scenes);
-    await refreshScenes();
   }
-
-};
-
-const addSceneHandler = async (scenes: Pick<Scene, "sceneId" | "sceneCode">[]) =>
-  await addRelicsScenesFetch(
-    relicsId.value!,
+  await delRelicsScenesFetch(
+    relicsId.value,
     scenes.map((item) => ({ sceneCode: item.sceneCode, id: item.sceneId }))
   );
+  await refreshScenes();
+};
 
-const handlerExport = (type: number, name: string) => {
-  console.log("type", type, name);
-  console.log("getSelectPoints", getSelectPoints());
-  exportFile(getSelectPoints(), type, name);
+const pointClickHandler = ({ id }: { id: any }) => {
+  const point = scenePoints.value.find((point) => point.id.toString() === id);
+  point && gotoPointPage(point);
 };
+
+board.polygon.container.stage.on("click.checkPointSelect", (ev) => {
+  if (ev.target === board.polygon.container.stage) {
+    board.polygon.status.lightPointId = null;
+  }
+});
+board.polygon.bus.on("clickPoint", pointClickHandler);
+onBeforeUnmount(() => {
+  board.polygon.bus.off("clickPoint", pointClickHandler);
+  board.polygon.container.stage.off("click.checkPointSelect");
+  board.polygon.status.lightPointId = null;
+});
 </script>
 
 <style lang="scss" scoped>
@@ -259,14 +324,12 @@ const handlerExport = (type: number, name: string) => {
 :deep(.el-tree-node__children .el-tree-node__content) {
   --el-tree-node-content-height: 52px;
 
-  &>label.el-checkbox {
+  & > label.el-checkbox {
     padding-top: 6px;
     align-items: flex-start;
   }
 }
 
-:deep(.el-tree .tree-item) {}
-
 .tree-item {
   display: flex;
   width: calc(100% - 50px);
@@ -274,6 +337,10 @@ const handlerExport = (type: number, name: string) => {
   justify-content: space-between;
   font-size: var(--font14);
 
+  &.active {
+    color: rgba(64, 158, 255, 1);
+  }
+
   .title {
     flex: 1;
     overflow: hidden;

+ 106 - 0
src/view/map/install.ts

@@ -0,0 +1,106 @@
+import { TileType, createMap, Manage } from "./openlayer";
+import { computed, ref, watch, watchEffect } from "vue";
+import ScaleLine from "ol/control/ScaleLine";
+import { createBoard } from "drawing-board";
+import { Scene, ScenePoint, boardData, relicsId, scenes } from "@/store/scene";
+import { router } from "@/router";
+import { addOrUpdateDrawingFetch } from "@/request/drawing";
+
+// ---------map---------
+
+export const tileOptions: TileType[] = ["影像底图", "矢量底图"];
+export const tileType = ref<TileType>(tileOptions[0]);
+export const defaultCenter = [116.412611, 39.908866];
+
+const addScale = (mapManage: Manage) => {
+  const scaleLine = new ScaleLine({
+    className: "scale-view",
+    maxWidth: 150,
+    minWidth: 100,
+    units: "metric",
+  });
+  // 加载比例尺
+  mapManage.map.addControl(scaleLine);
+
+  watch(
+    tileType,
+    (type) => {
+      const el = (scaleLine as any).element as HTMLDivElement;
+      el.classList.add(type === "影像底图" ? "light" : "dark");
+      el.classList.remove(type === "影像底图" ? "dark" : "light");
+    },
+    { flush: "post", immediate: true }
+  );
+};
+
+export const mapManage = createMap();
+mapManage.setCenter(defaultCenter);
+watchEffect(() => mapManage.setTileType(tileType.value));
+addScale(mapManage);
+
+const noValidPoint = (pos: ScenePoint) => !pos.pos || pos.pos.length === 0;
+const validScene = (scene: Scene) => !scene.scenePos.every(noValidPoint);
+
+export const flyScene = (scene: Scene) => {
+  console.log("flyScene", scene);
+  const totalPos = [0, 0];
+  let numCalc = 0;
+  for (let i = 0; i < scene.scenePos.length; i++) {
+    const coord = scene.scenePos[i].pos as number[];
+    if (coord && coord.length > 0) {
+      totalPos[0] += coord[0];
+      totalPos[1] += coord[1];
+      numCalc++;
+    }
+  }
+
+  totalPos[0] /= numCalc;
+  totalPos[1] /= numCalc;
+  mapManage.map.getView().setCenter(totalPos);
+};
+
+export const gotoPointPage = (point: ScenePoint) => {
+  router.push({
+    name: queryMode.value ? "query-pano" : "pano",
+    params: { pid: point.id },
+  });
+};
+
+export const autoInitPos = () => {
+  const scene = scenes.value.find(validScene);
+  if (scene) {
+    flyScene(scene);
+    return true;
+  } else {
+    return false;
+  }
+};
+
+watch(
+  () => scenes.value.find(validScene)?.sceneCode,
+  (code) => {
+    code && autoInitPos();
+  },
+  { immediate: true }
+);
+
+// -------board------
+export const board = createBoard({ map: mapManage.map });
+watch(boardData, (data) => data && board.setData(data), {
+  immediate: true,
+  flush: "pre",
+});
+
+export const boardDataChange = (dataChange?: () => void) => {
+  dataChange && dataChange();
+  return addOrUpdateDrawingFetch({
+    relicsId: relicsId.value.toString(),
+    data: boardData.value,
+  });
+};
+
+// -----------status----------
+
+export const queryMode = computed(() =>
+  router.currentRoute.value.name.toString().includes("query")
+);

+ 339 - 0
src/view/map/layout.vue

@@ -0,0 +1,339 @@
+<template>
+  <div class="map-layout" v-loading="!loaded || captureing">
+    <div class="custom_bar">
+      <div class="back_container" v-if="!queryMode">
+        <el-button :icon="Back" circle type="primary" @click="router.back()" />
+      </div>
+      <div class="nav_container">
+        <div
+          v-for="menu in menus"
+          :key="menu.name"
+          class="nav_item"
+          :class="{
+            active: menu.router.includes(router.currentRoute.value.name.toString()),
+          }"
+          @click="router.replace({ name: menu.router[Number(queryMode)] })"
+        >
+          <el-icon size="20">
+            <component :is="menu.icon" />
+          </el-icon>
+          <span>{{ menu.name }}</span>
+        </div>
+      </div>
+    </div>
+
+    <div class="map-oper-layout">
+      <div class="map-container" :ref="setMapContainer">
+        <div class="board" :ref="setBoardContainer"></div>
+        <div class="map-top-out-pano">
+          <el-button @click="capture" v-if="loaded"> 屏幕截图 </el-button>
+
+          <el-button @click="showPoints = !showPoints">
+            <el-checkbox :modelValue="showPoints" label="点位" size="large" />
+          </el-button>
+          <div class="tile-select">
+            <el-select
+              v-model="tileType"
+              placeholder="选择底图"
+              style="width: 120px"
+              class="tile-type-select"
+            >
+              <el-option
+                v-for="item in tileOptions"
+                :key="item"
+                :label="item"
+                :value="item"
+              />
+            </el-select>
+          </div>
+        </div>
+        <div class="map-bottom-out-pano">
+          <div class="point-info">
+            <div>
+              <el-icon size="20" color="rgb(230, 162, 60)">
+                <locationIcon />
+              </el-icon>
+              <p>RTK点位</p>
+            </div>
+            <div>
+              <el-icon size="20" color="rgba(64, 158, 255)">
+                <locationIcon />
+              </el-icon>
+              <p>地图选点</p>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="data-panel">
+        <RouterView v-slot="{ Component }" v-if="loaded">
+          <component :is="Component" />
+        </RouterView>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { router } from "@/router";
+import { Back } from "@element-plus/icons-vue";
+import vectorIcon from "@/assets/vector.svg";
+import locationIcon from "@/assets/location.svg";
+import { COORD_NAME, POYS_NAME, QUERY_COORD_NAME, QUERY_POYS_NAME } from "@/router";
+import {
+  mapManage,
+  board,
+  autoInitPos,
+  tileOptions,
+  defaultCenter,
+  tileType,
+} from "./install";
+import { nextTick, ref, watch } from "vue";
+import { initRelics, relics } from "@/store/relics";
+import { queryMode } from "./install";
+import { PoPoint } from "drawing-board";
+import { boardData } from "@/store/scene";
+import saveAs from "@/util/file-serve";
+
+const menus = [
+  {
+    icon: locationIcon,
+    name: "坐标",
+    router: [COORD_NAME, QUERY_COORD_NAME],
+  },
+  {
+    icon: vectorIcon,
+    name: "矢量图",
+    router: [POYS_NAME, QUERY_POYS_NAME],
+  },
+];
+const setMapContainer = (dom: HTMLDivElement) => setTimeout(() => mapManage.mount(dom));
+const setBoardContainer = (dom: HTMLDivElement) =>
+  setTimeout(() => board.setProps({ dom }));
+
+const loaded = ref(false);
+watch(
+  () => router.currentRoute.value.params?.relicsId,
+  (rid) => {
+    if (!rid) return;
+    loaded.value = false;
+    initRelics(Number(rid)).finally(() => {
+      if (!relics.value) {
+        return router.replace({ name: "relics" });
+      }
+      if (mapManage && !autoInitPos()) {
+        mapManage.flyUserCenter(defaultCenter);
+      }
+      loaded.value = true;
+    });
+  },
+  { immediate: true }
+);
+
+const showPoints = ref(true);
+watch(
+  () => [router.currentRoute.value.name, boardData.value, showPoints.value] as const,
+  ([name, _, showPoints]) => {
+    if (!board.polygon) return;
+    const rtksOnly = name && ![COORD_NAME, QUERY_COORD_NAME].includes(name.toString());
+    board.polygon.children.forEach((entity) => {
+      if (entity instanceof PoPoint) {
+        if (entity.attrib.rtk) {
+          entity.visible(showPoints);
+        } else {
+          entity.visible(showPoints && rtksOnly);
+        }
+      } else {
+        entity.visible(rtksOnly);
+      }
+    });
+  },
+  { immediate: true, flush: "post" }
+);
+
+const captureing = ref(false);
+const capture = async () => {
+  captureing.value = true;
+  await nextTick();
+  try {
+    const dataURL = await board.toDataURL(2);
+    await saveAs(dataURL, "map.png");
+  } finally {
+    captureing.value = false;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.map-layout {
+  display: flex;
+  flex-direction: row;
+  height: 100%;
+}
+
+.custom_bar {
+  width: 60px;
+  height: 100%;
+  background-color: white;
+
+  // padding-top: 76px;
+  .back_container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #606266;
+    height: 76px;
+  }
+
+  .nav_container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    color: #606266;
+
+    .nav_item {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      padding: 10px 0;
+      cursor: pointer;
+      user-select: none;
+      width: 100%;
+      span {
+        line-height: 26px;
+        font-size: var(--font14);
+      }
+
+      &.active {
+        .icon {
+          color: #409eff;
+        }
+
+        color: #409eff;
+        background-color: #ecf5ff;
+        position: relative;
+
+        &::before {
+          content: "";
+          height: 100%;
+          width: 4px;
+          position: absolute;
+          top: 0;
+          left: 0;
+          background-color: #409eff;
+        }
+      }
+    }
+  }
+}
+
+.map-oper-layout {
+  display: flex;
+  flex-direction: row;
+  height: 100%;
+  flex: 1;
+}
+
+.map-container {
+  flex: 1;
+  position: relative;
+
+  .map-component {
+    pointer-events: none;
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    z-index: 9;
+  }
+
+  .board {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    z-index: 1;
+  }
+}
+
+.data-panel {
+  width: 320px;
+  padding: 15px;
+  border-left: 1px solid var(--border-color);
+  position: relative;
+  z-index: 3;
+}
+.map-top-out-pano {
+  display: flex;
+  position: absolute;
+  right: 10px;
+  top: 10px;
+  z-index: 3;
+  > * {
+    margin-left: 10px;
+  }
+}
+.map-bottom-out-pano {
+  position: absolute;
+  right: 10px;
+  bottom: 10px;
+  z-index: 3;
+}
+.point-info {
+  background: #ffffff;
+  border-radius: 4px 4px 4px 4px;
+  padding: 10px;
+
+  > div {
+    display: flex;
+    align-items: center;
+    &:not(:last-child) {
+      margin-bottom: 10px;
+    }
+
+    p {
+      font-size: 16px;
+      color: #606266;
+      margin: 0;
+      margin-left: 6px;
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.scale-view {
+  --color: #fff;
+  position: absolute;
+  left: 20px;
+  bottom: 20px;
+  height: 8px;
+  color: var(--color);
+  text-align: center;
+  border: 1px solid var(--color);
+  border-top: none;
+  z-index: 1;
+  font-size: 14px;
+  display: flex;
+  align-items: end;
+
+  &.light {
+    --color: #fff;
+
+    > div {
+      text-shadow: 0 0 2px #000;
+    }
+  }
+
+  &.dark {
+    --color: #000;
+
+    > div {
+      text-shadow: 0 0 2px #fff;
+    }
+  }
+}
+</style>

+ 0 - 596
src/view/map/map-board.vue

@@ -1,596 +0,0 @@
-<template>
-  <div class="map-layout">
-    <Teleport to=".header" v-if="isMounted">
-      <div class="custom_bar">
-        <div class="back_container">
-          <el-button :icon="Back" circle type="primary" @click="router.back()" />
-        </div>
-        <div class="nav_container">
-          <div
-            class="nav_item"
-            :class="{ active: isCurrentTab(0) }"
-            @click="handleTabs(0)"
-          >
-            <el-icon size="20">
-              <locationIcon />
-            </el-icon>
-            <span>坐标</span>
-          </div>
-          <div
-            class="nav_item"
-            :class="{ active: isCurrentTab(1) }"
-            @click="handleTabs(1)"
-          >
-            <el-icon size="20">
-              <vectorIcon />
-            </el-icon>
-            <span>矢量图</span>
-          </div>
-        </div>
-      </div>
-    </Teleport>
-    <div id="map" class="map-container" ref="mapContainer" @click.stop="unActiveId">
-      <div class="map-component">
-        <el-select
-          v-model="tileType"
-          placeholder="选择底图"
-          style="width: 120px"
-          class="tile-type-select"
-        >
-          <el-option
-            v-for="item in tileOptions"
-            :key="item"
-            :label="item"
-            :value="item"
-          />
-        </el-select>
-      </div>
-
-      <div class="board" ref="boardContainer"></div>
-    </div>
-    <div class="right-control">
-      <MapRight
-        :data="boardData"
-        v-if="isCurrentTab(0)"
-        @fly-point="flyScenePoint"
-        @fly-scene="flyScene"
-        :boardPolygons="board.polygon"
-        @goto-point="gotoPointPage"
-      />
-      <MapRightPoly
-        @del="handlePolysDel"
-        @edit="handlePolysEdit"
-        :data="boardData"
-        @sync="handleSyncDataToServer"
-        ref="rightPoly"
-        :boardPolygons="board.polygon"
-        v-if="boardData && isCurrentTab(1)"
-      >
-      </MapRightPoly>
-    </div>
-
-    <Teleport to="body" v-if="isMounted">
-      <!-- <el-button class="temp_btn" @click="clearPolys">后端全清</el-button> -->
-      <div
-        class="draw-global-icon"
-        v-if="isCurrentTab(1) && !board.polygon.status.editPolygonId"
-        @click="rightPoly.enterEdit()"
-      >
-        <el-icon size="36">
-          <picpenIcon />
-        </el-icon>
-      </div>
-    </Teleport>
-  </div>
-</template>
-
-<script setup lang="ts">
-import MapRight from "./map-right.vue";
-import { router, setDocTitle } from "@/router";
-import { Manage } from "./openlayer";
-import { ScenePoint, Scene, scenePoints, scenes, SceneStatus } from "@/store/scene";
-import { initRelics, initSelfRelics, relics } from "@/store/relics";
-import { onMounted, ref, watchEffect, watch, onUnmounted, computed } from "vue";
-import {
-  createBoard,
-  getWholeLineLinesByPointId,
-  PolygonsPointAttrib,
-} from "drawing-board";
-import MapRightPoly from "./map-right-poly.vue";
-import { Back } from "@element-plus/icons-vue";
-import vectorIcon from "@/assets/vector.svg";
-import picpenIcon from "@/assets/pic_pen.svg";
-import locationIcon from "@/assets/location.svg";
-import {
-  addOrUpdateDrawingFetch,
-  getDrawingDetailFetch,
-  DrawingParamsType,
-  DrawingDataType,
-  PolyDataType,
-} from "@/request/drawing.ts";
-// import { relicsPolyginsFetch } from "@/request";
-// import { Grid, LocationInformation } from "@element-plus/icons-vue";
-
-import { mapManageInit, flyUserCenter, tileOptions, tileType } from "./map-flow";
-
-const scenePosTransform = (scenes: Scene[]) => {
-  const points: PolygonsPointAttrib[] = [];
-
-  scenes.forEach((scene) => {
-    if (scene.calcStatus !== SceneStatus.SUCCESS) {
-      return;
-    }
-    scene.scenePos.forEach((pos) => {
-      if (!pos.pos || pos.pos.length === 0) {
-        return;
-      }
-      points.push({
-        x: pos.pos[0],
-        y: pos.pos[1],
-        title: pos.name,
-        id: pos.id.toString(),
-        rtk: true,
-      });
-    });
-  });
-  return points;
-};
-
-const relicsId = computed(() => router.currentRoute.value.params.relicsId || "");
-
-const gotoPointPage = (point: ScenePoint) => {
-  router.push({
-    name: router.currentRoute.value.name === "map" ? "pano" : "query-pano",
-    params: { pid: point.id },
-  });
-};
-
-const isMounted = ref(false);
-const currentTab = ref(0);
-const isCurrentTab = ref((index: number) => currentTab.value === index);
-const rightPoly = ref<any>();
-
-const autoInitPos = () => {
-  const scene = scenes.value.find(
-    (scene) => !scene.scenePos.every((pos) => !pos.pos || pos.pos.length === 0)
-  );
-  if (scene) {
-    flyScene(scene);
-    return true;
-  } else {
-    return false;
-  }
-};
-const flyPos = (pos: number[]) => mapManage.map.getView().setCenter(pos);
-
-const flyScene = (scene: Scene) => {
-  const totalPos = [0, 0];
-  let numCalc = 0;
-  for (let i = 0; i < scene.scenePos.length; i++) {
-    const coord = scene.scenePos[i].pos as number[];
-    if (coord && coord.length > 0) {
-      totalPos[0] += coord[0];
-      totalPos[1] += coord[1];
-      numCalc++;
-    }
-  }
-
-  totalPos[0] /= numCalc;
-  totalPos[1] /= numCalc;
-  flyPos(totalPos);
-};
-const unActiveId = () => {
-  board.polygon.status.activePointId = null;
-};
-const flyScenePoint = (point: ScenePoint) => {
-  flyPos(point.pos);
-  board.polygon.status.activePointId = point.id.toString();
-};
-
-watch(
-  () => [router.currentRoute.value.name, router.currentRoute.value.params?.relicsId],
-  ([name, rid], old) => {
-    if (["map", "query-map"].includes(name as string) && (!old || old[1] !== rid)) {
-      relics.value = undefined;
-      const fn = name === "map" ? initSelfRelics : initRelics;
-      fn(Number(rid)).finally(() => {
-        if (!relics.value) {
-          return router.replace({ name: "relics" });
-        }
-        if (mapManage && !autoInitPos()) {
-          flyUserCenter(mapManage);
-        }
-
-        board.polygon.bus.on("clickPoint", (bpoint) => {
-          if (isCurrentTab.value(0)) {
-            const point =
-              bpoint.id &&
-              scenePoints.value.find((point) => point.id.toString() === bpoint.id);
-            point && gotoPointPage(point);
-          }
-        });
-      });
-    }
-  },
-  { immediate: true }
-);
-
-watch(
-  () => {
-    const scene = scenes.value.find(
-      (scene) => !scene.scenePos.every((pos) => !pos.pos || pos.pos.length === 0)
-    );
-    return scene?.sceneCode;
-  },
-  (firstCode) => {
-    if (firstCode && mapManage) {
-      autoInitPos();
-    }
-  }
-);
-
-watchEffect(() => {
-  if (
-    ["map", "query-map"].includes(router.currentRoute.value.name as string) &&
-    relics.value
-  ) {
-    setDocTitle(relics.value.name);
-  }
-});
-
-// ----------流程--------
-const mapContainer = ref<HTMLDivElement>();
-const boardContainer = ref<HTMLDivElement>();
-let mapManage: Manage;
-
-const board = createBoard();
-const boardData = ref<DrawingDataType | null>(null);
-let endEdithandler;
-
-onMounted(async () => {
-  mapManage = mapManageInit(mapContainer.value!);
-  board.setProps({
-    dom: boardContainer.value!,
-    map: mapManage.map,
-  });
-  isMounted.value = true;
-});
-
-const borardPolyData = computed(() => board.getData());
-watch(
-  borardPolyData,
-  (updater) => {
-    if (updater) {
-      boardData.value = (updater as any) as DrawingDataType;
-    }
-  },
-  { immediate: true, deep: true }
-);
-
-onUnmounted(() => {
-  mapManage.map.dispose();
-  board.destory();
-});
-
-const handleTabs = (index: number) => {
-  currentTab.value = index;
-};
-
-const handleSyncDataToServer = () => {
-  setTimeout(async () => {
-    console.log("handleSyncDataToServer");
-    const data = (board.getData() as any) as DrawingDataType;
-    console.log(data);
-    boardData.value = data;
-    const param: DrawingParamsType = {
-      data: data,
-      relicsId: String(relicsId.value),
-    };
-    patchPolyName(data);
-
-    await addOrUpdateDrawingFetch(param);
-
-    if (endEdithandler) {
-      console.log("end edit");
-    }
-  }, 1000);
-};
-
-const patchPolyName = (data: DrawingDataType) => {
-  const poly = data.polygons;
-  poly?.forEach((item) => {
-    if (!item.name) {
-      item.name = "本体边界" + item.id;
-    }
-  });
-};
-
-const initCroodTabdata = async () => {
-  const points = scenePosTransform(scenes.value);
-  board.setData({
-    id: String(relicsId.value),
-    points: points,
-    lines: [],
-    polygons: [],
-  });
-};
-
-watch(
-  currentTab,
-  (tab, _, onCleanup) => {
-    if (tab === 1) {
-      initPolyTabData();
-      onCleanup(() => {});
-    } else {
-      // 防止被vue组件收集,自己控制
-      let stop: () => void;
-      let timeout = setTimeout(() => {
-        stop = watch(() => scenes.value, initCroodTabdata, {
-          immediate: true,
-          deep: true,
-        });
-      });
-      onCleanup(() => {
-        stop && stop();
-        clearTimeout(timeout);
-      });
-    }
-  },
-  { immediate: true }
-);
-
-const initPolyTabData = async () => {
-  try {
-    setTimeout(async () => {
-      const points = scenePosTransform(scenes.value);
-      const res = await getDrawingDetailFetch(String(relicsId.value));
-      // console.log("res", points, res.data);
-      if (res.data) {
-        const canDelPoint = (id: string) =>
-          getWholeLineLinesByPointId(res.data as any, id).length === 0 &&
-          !points.some(({ id: rtkId }) => id === rtkId);
-
-        // 查看是否有多余的点,有则删除,出现原因是删除了场景
-        for (let i = 0; i < res.data.points.length; i++) {
-          if (canDelPoint(res.data.points[i].id)) {
-            res.data.points.splice(i--, 1);
-          }
-        }
-
-        // 将rtk点加入
-        for (let i = 0; i < points.length; i++) {
-          const ndx = res.data.points.findIndex(({ id }) => id === points[i].id);
-          if (!~ndx) {
-            res.data.points.push(points[i]);
-          } else {
-            res.data.points[ndx] = { ...points[i] };
-          }
-        }
-        console.log(res.data.points);
-
-        boardData.value = res.data;
-        board.setData({ ...boardData.value, id: String(relicsId.value) });
-      }
-    }, 500);
-  } catch (error) {}
-};
-
-const handlePolysDel = (id: string) => {
-  try {
-    board.polygon.removePolygon(id);
-    handleSyncDataToServer();
-  } catch (error) {
-    console.error("handlePolysDel", error);
-  }
-};
-
-const handlePolysEdit = (item: PolyDataType) => {
-  console.log("handlePolysEdit", item);
-  const data = (board.getData() as any) as DrawingDataType;
-  const index = data.polygons.findIndex((i) => item.id === i.id);
-  data.polygons[index] = item;
-  handleSyncDataToServer();
-};
-
-// const clearPolys = async () => {
-//   await addOrUpdateDrawingFetch({
-//     relicsId: String(relicsId.value),
-//     data: {
-//       points: [],
-//       polygons: [],
-//       lines: [],
-//     },
-//   });
-// };
-</script>
-
-<style lang="scss">
-.tooltip {
-  pointer-events: none;
-}
-
-.map-layout {
-  display: flex;
-  flex-direction: row;
-  height: 100%;
-}
-
-.map-container {
-  margin-left: 60px;
-  flex: 1;
-  position: relative;
-}
-
-.right-control {
-  flex: none;
-  width: 320px;
-  padding: 15px;
-
-  border-left: 1px solid var(--border-color);
-}
-
-.map-component {
-  width: 100%;
-  height: 100%;
-  position: relative;
-}
-
-.active {
-  cursor: pointer;
-}
-
-.active-point {
-  position: absolute;
-  pointer-events: none;
-}
-
-.map-component {
-  pointer-events: none;
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  left: 0;
-  top: 0;
-  z-index: 9;
-}
-
-.env {
-  width: 100%;
-  height: 100%;
-}
-
-.tile-type-select {
-  pointer-events: all;
-  position: absolute;
-  right: 10px;
-  top: 10px;
-}
-
-.scale-view {
-  --color: #fff;
-  position: absolute;
-  left: 20px;
-  bottom: 20px;
-  height: 8px;
-  color: var(--color);
-  text-align: center;
-  border: 1px solid var(--color);
-  border-top: none;
-  z-index: 1;
-  font-size: 14px;
-  display: flex;
-  align-items: end;
-
-  &.light {
-    --color: #fff;
-
-    > div {
-      text-shadow: 0 0 2px #000;
-    }
-  }
-
-  &.dark {
-    --color: #000;
-
-    > div {
-      text-shadow: 0 0 2px #fff;
-    }
-  }
-}
-
-.board {
-  position: absolute;
-  left: 0;
-  top: 0;
-  bottom: 0;
-  right: 0;
-  z-index: 1;
-}
-
-.custom_bar {
-  width: 60px;
-  position: fixed;
-  top: 0;
-  left: 0;
-  height: 100vh;
-  background-color: white;
-
-  // padding-top: 76px;
-  .back_container {
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    color: #606266;
-    height: 76px;
-  }
-
-  .nav_container {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    color: #606266;
-
-    .nav_item {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      padding: 10px 0;
-      cursor: pointer;
-      user-select: none;
-      width: 100%;
-      span {
-        line-height: 26px;
-        font-size: var(--font14);
-      }
-
-      &.active {
-        .icon {
-          color: #409eff;
-        }
-
-        color: #409eff;
-        background-color: #ecf5ff;
-        position: relative;
-
-        &::before {
-          content: "";
-          height: 100%;
-          width: 4px;
-          position: absolute;
-          top: 0;
-          left: 0;
-          background-color: #409eff;
-        }
-      }
-    }
-  }
-}
-
-.draw-global-icon {
-  width: 64px;
-  height: 64px;
-  background: #ffffff;
-  border-radius: 50%;
-  position: fixed;
-  z-index: 1000;
-  transform: translateX(calc(-1 * calc(50% - 300px)));
-  left: calc(50% - 300px);
-  top: 90%;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  color: #409eff;
-}
-
-.temp_btn {
-  top: 40px;
-  position: fixed;
-  z-index: 1000;
-  top: 10px;
-  transform: translateX(calc(-1 * calc(50% - 300px)));
-  left: calc(50% - 300px);
-}
-</style>

+ 0 - 62
src/view/map/map-flow.ts

@@ -1,62 +0,0 @@
-import { TileType, createMap, Manage } from "./openlayer";
-import { ref, watch, watchEffect } from "vue";
-import ScaleLine from "ol/control/ScaleLine";
-
-export const tileOptions: TileType[] = ["影像底图", "矢量底图"];
-export const tileType = ref<TileType>(tileOptions[0]);
-const defaultCenter = [116.412611, 39.908866];
-
-const refreshTileType = (mapManage: Manage) => {
-  mapManage.setTileType(tileType.value);
-};
-
-export const flyUserCenter = (mapManage: Manage) => {
-  mapManage.setCenter(defaultCenter);
-  navigator.geolocation.getCurrentPosition(
-    (pos) => {
-      console.log("获取中心位置成功", pos);
-      mapManage.setCenter([pos.coords.longitude, pos.coords.latitude]);
-    },
-    (e) => {
-      console.error(e);
-      console.error("获取中心位置失败");
-    },
-    {
-      enableHighAccuracy: false,
-      timeout: 50000,
-      maximumAge: 0,
-    }
-  );
-};
-
-const addScale = (mapManage: Manage) => {
-  const scaleLine = new ScaleLine({
-    className: "scale-view",
-    maxWidth: 150,
-    minWidth: 100,
-    units: "metric",
-  });
-  // 加载比例尺
-  mapManage.map.addControl(scaleLine);
-
-  watch(
-    tileType,
-    (type) => {
-      const el = (scaleLine as any).element as HTMLDivElement;
-      el.classList.add(type === "影像底图" ? "light" : "dark");
-      el.classList.remove(type === "影像底图" ? "dark" : "light");
-      console.log(el, type);
-    },
-    { flush: "post", immediate: true }
-  );
-};
-
-export const mapManageInit = (container: HTMLDivElement) => {
-  const mapManage = createMap(container!);
-  mapManage.setCenter(defaultCenter);
-
-  watchEffect(() => refreshTileType(mapManage));
-  addScale(mapManage);
-
-  return mapManage;
-};

+ 0 - 257
src/view/map/map-right-poly.vue

@@ -1,257 +0,0 @@
-<template>
-  <div class="right-layout">
-    <div class="right-content">
-      <div class="tree-layout">
-        <p class="sub-title">全部数据</p>
-        <div class="poly-list">
-          <template v-if="data.polygons.length > 0" v-for="item in data.polygons">
-            <div
-              class="poly-list-item"
-              :class="{
-                active: [boardStatus.lightPolygonId, boardStatus.editPolygonId].includes(
-                  item.id
-                ),
-              }"
-              @mouseenter="boardStatus.lightPolygonId = item.id"
-              @mouseleave="boardStatus.lightPolygonId = null"
-              @click="enterEdit(item)"
-            >
-              <div class="left">
-                <span>{{ item.name ? item.name : "本体边界" + item.id }}</span>
-              </div>
-              <div class="right" @click.stop v-if="!boardStatus.editPolygonId">
-                <el-icon class="icon">
-                  <Delete @click="del(item.id)" />
-                </el-icon>
-                <el-icon class="icon">
-                  <Edit @click="handleShowEditModel(item)" />
-                </el-icon>
-                <el-icon class="icon">
-                  <Download @click="handleDownload(item)" />
-                </el-icon>
-              </div>
-            </div>
-          </template>
-          <template v-else>
-            <div class="empty">暂没数据</div>
-          </template>
-        </div>
-      </div>
-    </div>
-    <SingleInput
-      v-if="currentItem"
-      :visible="isShowPolyEditName"
-      @update:visible="isShowPolyEditName = false"
-      :value="currentItem.name || ''"
-      :update-value="
-        (name) =>
-          emit('edit', {
-            ...currentItem,
-            name,
-          })
-      "
-      placeholder="请输入"
-      title="修改边界名称"
-    />
-  </div>
-</template>
-
-<script setup lang="ts">
-import { computed, onUnmounted, ref, watch, watchEffect } from "vue";
-import type { PolyDataType, DrawingDataType } from "@/request/drawing.ts";
-import { Delete, Download, Edit } from "@element-plus/icons-vue";
-import SingleInput from "@/components/single-input.vue";
-import { downloadPointsXLSL2 } from "@/util/pc4xlsl";
-import { scenePoints } from "@/store/scene";
-import { ElMessageBox } from "element-plus";
-import { Polygons, getWholeLinePolygonPoints } from "drawing-board";
-
-const props = defineProps<{
-  data: DrawingDataType | null;
-  boardPolygons: Polygons;
-}>();
-
-const emit = defineEmits<{
-  (e: "del", id: string): void;
-  (e: "edit", data: PolyDataType): void;
-  (e: "editPolygon", data: PolyDataType): void;
-  (e: "sync"): void;
-  // (e: "edit", id: string): void;
-}>();
-
-const boardStatus = computed(() => props.boardPolygons.status);
-const currentItem = computed(() => {
-  if (!boardStatus.value.editPolygonId || !props.data) {
-    return null;
-  } else {
-    return props.data.polygons.find(({ id }) => id === boardStatus.value.editPolygonId);
-  }
-});
-
-let cleanupEdit: () => void;
-const enterEdit = (item?: PolyDataType) => {
-  console.error("enterEdit");
-  cleanupEdit && cleanupEdit();
-  const quitEdit = props.boardPolygons.editPolygon(item?.id);
-  let needUpdate = false;
-  const stopWatch = watch(
-    () => props.boardPolygons.attrib,
-    () => (needUpdate = true),
-    { deep: true }
-  );
-  cleanupEdit = () => {
-    props.boardPolygons.bus.off("penEndHandler", cleanupEdit);
-    quitEdit();
-    stopWatch();
-    needUpdate && emit("sync");
-    cleanupEdit = null;
-  };
-  props.boardPolygons.bus.on("penEndHandler", cleanupEdit);
-};
-
-props.boardPolygons.bus.on("clickPolygon", enterEdit);
-onUnmounted(() => {
-  cleanupEdit && cleanupEdit();
-  props.boardPolygons.bus.off("clickPolygon", enterEdit);
-});
-
-const isShowPolyEditName = ref(false);
-const handleShowEditModel = (item: PolyDataType) => {
-  enterEdit(item);
-  isShowPolyEditName.value = true;
-  const stopWatch = watchEffect(() => {
-    if (!isShowPolyEditName.value) {
-      cleanupEdit();
-      stopWatch();
-    }
-  });
-};
-
-const del = async (id: string) => {
-  const ok = await ElMessageBox.confirm("确定要删除吗", {
-    type: "warning",
-  });
-  if (ok) {
-    emit("del", id);
-  }
-};
-const handleDownload = async (item: any) => {
-  const polygonPoints: any[] = getWholeLinePolygonPoints(props.data as any, item.id);
-
-  const points = polygonPoints.map((p) => {
-    const pos = [p.x, p.y, 0];
-    if (p.rtk) {
-      const sPoint = scenePoints.value.find(({ id }) => id.toString() === p.title);
-      if (sPoint) {
-        pos[2] = sPoint.pos[2];
-      }
-    }
-    return pos;
-  });
-  const dists = polygonPoints.map((p) => ({
-    title: p.id,
-    desc: p.title || p.id,
-  }));
-  await downloadPointsXLSL2(
-    points,
-    dists,
-    `${item.name ? item.name : "本体边界" + item.id}`
-  );
-};
-
-defineExpose({
-  enterEdit,
-});
-</script>
-
-<style lang="scss" scoped>
-.tree-item {
-  display: flex;
-  width: calc(100% - 50px);
-  align-items: center;
-  justify-content: space-between;
-
-  .title {
-    flex: 1;
-    overflow: hidden;
-    text-overflow: ellipsis; //文本溢出显示省略号
-    white-space: nowrap; //文本不会换行
-  }
-
-  .oper {
-    flex: none;
-  }
-}
-
-.disable {
-  pointer-events: all;
-}
-
-.tree-layout {
-  p {
-    color: #303133;
-    font-size: 14px;
-  }
-
-  .sub-title {
-    font-size: 14px;
-    font-weight: bolder;
-    margin-bottom: 30px;
-  }
-}
-
-.right-layout {
-  display: flex;
-  height: 100%;
-  flex-direction: column;
-  font-size: 16px;
-
-  .right-content {
-    flex: 1;
-    overflow-y: auto;
-  }
-}
-
-.tree-layout .tree-scene-name {
-  font-size: 10px;
-  margin: 0;
-  color: #999;
-}
-
-.poly-list {
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-  font-size: 14px;
-  user-select: none;
-
-  .poly-list-item {
-    width: 100%;
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 10px;
-
-    &.active {
-      color: #409eff;
-    }
-
-    .icon {
-      margin-left: 8px;
-      font-size: 16px;
-      color: #409eff;
-      cursor: pointer;
-    }
-  }
-
-  .empty {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    font-size: 13px;
-    color: gray;
-    padding-top: 40px;
-  }
-}
-</style>

+ 0 - 342
src/view/map/map.vue

@@ -1,342 +0,0 @@
-<template>
-  <div class="map-layout" @click="activeId = null">
-    <div
-      id="map"
-      class="map-container"
-      ref="container"
-      :class="{ active: !!activeId }"
-      @click.stop
-      @mousedown="activeId = null"
-      @wheel="activeId = null"
-    >
-      <div class="map-component">
-        <el-tooltip
-          class="tooltip"
-          :visible="!!activeId"
-          :content="active?.name"
-          effect="light"
-          placement="top"
-          virtual-triggering
-          :virtual-ref="triggerRef"
-        />
-        <el-select
-          v-model="tileType"
-          placeholder="选择底图"
-          style="width: 120px"
-          class="tile-type-select"
-        >
-          <el-option
-            v-for="item in tileOptions"
-            :key="item"
-            :label="item"
-            :value="item"
-          />
-        </el-select>
-      </div>
-    </div>
-    <div class="right-control">
-      <MapRight
-        :data="2 as any"
-        :board-polygons="1 as any"
-        @fly-point="flyScenePoint"
-        @fly-scene="flyScene"
-        @goto-point="gotoPoint"
-      />
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import MapRight from "./map-right.vue";
-import { router, setDocTitle } from "@/router";
-import { TileType, createMap, Manage } from "./openlayer";
-import { ScenePoint, Scene, scenePoints, scenes } from "@/store/scene";
-import { initRelics, initSelfRelics, relics } from "@/store/relics";
-import { computed, onMounted, ref, watchEffect, watch, onUnmounted } from "vue";
-import ScaleLine from "ol/control/ScaleLine";
-
-const activeId = ref<ScenePoint["id"] | null>();
-const active = computed(() =>
-  scenePoints.value.find((point) => point.id === activeId.value)
-);
-
-const activePixel = ref<number[] | null>();
-const triggerRef = ref({
-  getBoundingClientRect() {
-    return DOMRect.fromRect({
-      x: activePixel.value![0],
-      y: activePixel.value![1],
-      width: 0,
-      height: 0,
-    });
-  },
-});
-
-const tileOptions: TileType[] = ["影像底图", "矢量底图"];
-const tileType = ref<TileType>(tileOptions[0]);
-
-const points = computed(() =>
-  scenePoints.value
-    .filter((point) => point.pos)
-    .map((point) => ({
-      data: point.pos,
-      id: point.id,
-      label: point.name,
-    }))
-);
-
-const gotoPoint = (point: ScenePoint) => {
-  router.push({
-    name: router.currentRoute.value.name === "map" ? "pano" : "query-pano",
-    params: { pid: point.id },
-  });
-};
-
-const flyUserCenter = () => {
-  mapManage.setCenter([116.412611, 39.908866]);
-  navigator.geolocation.getCurrentPosition(
-    (pos) => {
-      console.log("获取中心位置成功", pos);
-      mapManage.setCenter([pos.coords.longitude, pos.coords.latitude]);
-    },
-    (e) => {
-      console.error(e);
-      console.error("获取中心位置失败");
-    },
-    {
-      enableHighAccuracy: false,
-      timeout: 50000,
-      maximumAge: 0,
-    }
-  );
-};
-const center = [109.47293862712675, 30.26530938156551];
-const container = ref<HTMLDivElement>();
-let mapManage: Manage;
-onMounted(async () => {
-  mapManage = createMap(container.value!);
-  mapManage.setCenter(center);
-  mapManage.hotsBus.on("active", (id) => {
-    if (id) {
-      activeId.value = id;
-      active.value && activeScenePoint(active.value!);
-    } else {
-      activeId.value = null;
-      activePixel.value = null;
-    }
-  });
-  mapManage.hotsBus.on("click", (id) => {
-    const point = id && scenePoints.value.find((point) => point.id === id);
-    point && gotoPoint(point);
-  });
-  refreshHots();
-  refreshTileType();
-  const scaleLine = new ScaleLine({
-    className: "scale-view",
-    maxWidth: 150,
-    minWidth: 100,
-    units: "metric",
-  });
-  // 加载比例尺
-  mapManage.map.addControl(scaleLine);
-  watch(
-    tileType,
-    (type) => {
-      const el = (scaleLine as any).element as HTMLDivElement;
-      el.classList.add(type === "影像底图" ? "light" : "dark");
-      el.classList.remove(type === "影像底图" ? "dark" : "light");
-      console.log(el, type);
-    },
-    { flush: "post", immediate: true }
-  );
-});
-
-const activeScenePoint = (point: ScenePoint) => {
-  activePixel.value = mapManage.map.getPixelFromCoordinate(point.pos);
-  activeId.value = point.id;
-};
-
-const flyPos = (pos: number[]) => mapManage.map.getView().setCenter(pos);
-
-const flyScenePoint = (point: ScenePoint) => {
-  flyPos(point.pos);
-  setTimeout(() => {
-    activeScenePoint(point);
-  }, 16);
-};
-
-const flyScene = (scene: Scene) => {
-  const totalPos = [0, 0];
-  let numCalc = 0;
-  for (let i = 0; i < scene.scenePos.length; i++) {
-    const coord = scene.scenePos[i].pos as number[];
-    if (coord && coord.length > 0) {
-      totalPos[0] += coord[0];
-      totalPos[1] += coord[1];
-      numCalc++;
-    }
-  }
-
-  totalPos[0] /= numCalc;
-  totalPos[1] /= numCalc;
-  flyPos(totalPos);
-};
-
-const refreshHots = () => {
-  if (!mapManage) return;
-  mapManage.clearHots();
-  mapManage.addHots(points.value);
-};
-
-const refreshTileType = () => {
-  if (!mapManage) return;
-  mapManage.setTileType(tileType.value);
-};
-
-watch(points, refreshHots, { immediate: true });
-watch(tileType, refreshTileType, { immediate: true });
-watch(
-  () => [router.currentRoute.value.name, router.currentRoute.value.params?.relicsId],
-  ([name, rid], old) => {
-    if (["map", "query-map"].includes(name as string) && (!old || old[1] !== rid)) {
-      relics.value = undefined;
-      const fn = name === "map" ? initSelfRelics : initRelics;
-      fn(Number(rid)).finally(() => {
-        if (!relics.value) {
-          router.replace({ name: "relics" });
-        }
-        if (!autoInitPos()) {
-          flyUserCenter();
-        }
-      });
-    }
-  },
-  { immediate: true }
-);
-
-const autoInitPos = () => {
-  const scene = scenes.value.find(
-    (scene) => !scene.scenePos.every((pos) => !pos.pos || pos.pos.length === 0)
-  );
-  if (scene) {
-    flyScene(scene);
-    return true;
-  } else {
-    return false;
-  }
-};
-
-watch(
-  () => {
-    const scene = scenes.value.find(
-      (scene) => !scene.scenePos.every((pos) => !pos.pos || pos.pos.length === 0)
-    );
-    return scene?.sceneCode;
-  },
-  (firstCode) => {
-    if (firstCode) {
-      autoInitPos();
-    }
-  }
-);
-
-watchEffect(() => {
-  if (
-    ["map", "query-map"].includes(router.currentRoute.value.name as string) &&
-    relics.value
-  ) {
-    setDocTitle(relics.value.name);
-  }
-});
-
-onUnmounted(() => mapManage.map.dispose());
-</script>
-
-<style lang="scss">
-.tooltip {
-  pointer-events: none;
-}
-.map-layout {
-  display: flex;
-  flex-direction: row;
-  height: 100%;
-}
-
-.map-container {
-  flex: 1;
-  position: relative;
-}
-
-.right-control {
-  flex: none;
-  width: 320px;
-  padding: 15px;
-
-  border-left: 1px solid var(--border-color);
-}
-
-.map-component {
-  width: 100%;
-  height: 100%;
-  position: relative;
-}
-
-.active {
-  cursor: pointer;
-}
-
-.active-point {
-  position: absolute;
-  pointer-events: none;
-}
-
-.map-component {
-  pointer-events: none;
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  left: 0;
-  top: 0;
-  z-index: 9;
-}
-.env {
-  width: 100%;
-  height: 100%;
-}
-
-.tile-type-select {
-  pointer-events: all;
-  position: absolute;
-  right: 10px;
-  top: 10px;
-}
-
-.scale-view {
-  --color: #fff;
-  position: absolute;
-  left: 20px;
-  bottom: 20px;
-  height: 8px;
-  color: var(--color);
-  text-align: center;
-  border: 1px solid var(--color);
-  border-top: none;
-  z-index: 1;
-  font-size: 14px;
-  display: flex;
-  align-items: end;
-  &.light {
-    --color: #fff;
-    > div {
-      text-shadow: 0 0 2px #000;
-    }
-  }
-  &.dark {
-    --color: #000;
-    > div {
-      text-shadow: 0 0 2px #fff;
-    }
-  }
-}
-</style>
-./openlayer./openlayer/manage

+ 1 - 1
src/view/map/openlayer/index.ts

@@ -1,7 +1,7 @@
 import { Manage } from "./manage";
 export type { TileType } from "./tile";
 
-export const createMap = (dom: HTMLDivElement) => {
+export const createMap = (dom?: HTMLDivElement) => {
   return new Manage(dom);
 };
 

+ 36 - 4
src/view/map/openlayer/manage.ts

@@ -11,7 +11,7 @@ import {
 import { Emitter } from "mitt";
 import { boundingExtent } from "ol/extent";
 
-const createMap = (container: HTMLDivElement) => {
+const createMap = (container?: HTMLDivElement) => {
   const view = new View({
     center: [113.59562585879772, 22.367660742553472],
     projection: "EPSG:4326",
@@ -33,9 +33,17 @@ export class Manage {
     click: any;
   }>;
 
-  constructor(container: HTMLDivElement) {
-    this.map = createMap(container);
+  moundDOM: HTMLDivElement;
+  constructor(container?: HTMLDivElement) {
+    this.moundDOM = document.createElement("div");
+    this.moundDOM.style.width = "300px";
+    this.moundDOM.style.height = "300px";
+
+    this.map = createMap(this.moundDOM);
     this.hotsBus = dynamicHots(this.map);
+    if (container) {
+      this.mount(container);
+    }
   }
 
   setTileType(type: TileType) {
@@ -55,6 +63,7 @@ export class Manage {
 
   setCenter(center: number[]) {
     this.map.getView().setCenter(center);
+    console.log(center);
   }
 
   delHots(ids: HotData["id"][]) {
@@ -73,11 +82,34 @@ export class Manage {
     this.map.getView().fit(extent, {
       size: this.map.getSize(),
       padding: [0, 0, 0, 0], // 根据需要调整边距
-      maxZoom: 19.5, // 防止过度放大
     });
   }
+  flyUserCenter(defaultCenter: number[]) {
+    this.setCenter(defaultCenter);
+    navigator.geolocation.getCurrentPosition(
+      (pos) => {
+        console.log("获取中心位置成功", pos);
+        this.setCenter([pos.coords.longitude, pos.coords.latitude]);
+      },
+      (e) => {
+        console.error(e);
+        console.error("获取中心位置失败");
+      },
+      {
+        enableHighAccuracy: false,
+        timeout: 50000,
+        maximumAge: 0,
+      }
+    );
+  }
 
   render() {
     this.map.render();
   }
+
+  mount(dom: HTMLDivElement) {
+    dom.appendChild(this.moundDOM);
+    this.moundDOM.style.width = "100%";
+    this.moundDOM.style.height = "100%";
+  }
 }

+ 1 - 0
src/view/map/openlayer/tile.ts

@@ -63,6 +63,7 @@ const getWMTS = (type: TileType, mapEpsg: string) => {
   const url = `https://t0.tianditu.gov.cn/${layer}_c/wmts?tk=${key}`;
   return new WMTS({
     url,
+    crossOrigin: "anonymous", // 设置跨域
     layer,
     version: "1.0.0",
     matrixSet: "c",

+ 287 - 0
src/view/map/polygons.vue

@@ -0,0 +1,287 @@
+<template>
+  <div class="right-layout" @click.stop="selectChange(null)">
+    <div class="right-content">
+      <div class="tree-layout">
+        <p class="sub-title">全部数据</p>
+        <div class="poly-list">
+          <template v-if="boardData.polygons.length > 0">
+            <div
+              v-for="item in boardData.polygons"
+              class="poly-list-item"
+              :class="{
+                active: [
+                  boardStatus.lightPolygonId,
+                  boardStatus.editPolygonId,
+                  selectId,
+                ].includes(item.id),
+              }"
+              @mouseenter="!selectId && (boardStatus.lightPolygonId = item.id)"
+              @mouseleave="!selectId && (boardStatus.lightPolygonId = null)"
+              @click.stop="!currentItem && selectChange(item.id)"
+            >
+              <div class="left">
+                <span>{{ item.name ? item.name : "本体边界" + item.id }}</span>
+              </div>
+              <div
+                class="right"
+                @click.stop
+                v-if="!boardStatus.editPolygonId && !queryMode"
+              >
+                <el-icon class="icon">
+                  <Delete @click="del(item.id)" />
+                </el-icon>
+                <el-icon class="icon">
+                  <Edit @click="handleShowEditModel(item)" />
+                </el-icon>
+                <el-icon class="icon">
+                  <Download @click="handleDownload(item)" />
+                </el-icon>
+              </div>
+            </div>
+          </template>
+          <template v-else>
+            <div class="empty">暂没数据</div>
+          </template>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <Teleport to="body" v-if="!queryMode">
+    <div class="draw-global-icon" @click="cleanupEdit ? cleanupEdit() : enterEdit()">
+      <el-icon size="36">
+        <Check v-if="cleanupEdit" />
+        <picpenIcon v-else />
+      </el-icon>
+    </div>
+    <SingleInput
+      v-if="selectItem"
+      :visible="isShowPolyEditName"
+      @update:visible="isShowPolyEditName = false"
+      :value="selectItem.name || ''"
+      :update-value="(name) => boardDataChange(() => (selectItem.name = name))"
+      placeholder="请输入"
+      title="修改边界名称"
+    />
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import { computed, onBeforeUnmount, ref, shallowRef, watch } from "vue";
+import type { PolyDataType } from "@/request/drawing.ts";
+import { Delete, Download, Edit, Check } from "@element-plus/icons-vue";
+import SingleInput from "@/components/single-input.vue";
+import { downloadPointsXLSL1 } from "@/util/pc4xlsl";
+import { boardData, scenePoints } from "@/store/scene";
+import { getWholeLinePolygonPoints } from "drawing-board";
+import { board, boardDataChange, mapManage, queryMode } from "./install";
+import { confirm } from "@/helper/message";
+import picpenIcon from "@/assets/pic_pen.svg";
+
+const boardStatus = board.polygon.status;
+const selectId = ref<string>();
+
+const selectChange = (id: string) => {
+  if (currentItem.value) return;
+  if (selectId.value === id) {
+    boardStatus.lightPolygonId = null;
+    selectId.value = null;
+  } else {
+    selectId.value = id;
+    if (!selectItem.value) {
+      selectChange(null);
+    } else {
+      boardStatus.lightPolygonId = selectItem.value.id;
+      const points = getWholeLinePolygonPoints(boardData.value, selectItem.value.id);
+      if (points.length) {
+        const total = points.reduce((t, p) => [t[0] + p.x, t[1] + p.y], [0, 0]);
+        mapManage.map
+          .getView()
+          .setCenter([total[0] / points.length, total[1] / points.length]);
+      }
+    }
+  }
+};
+
+board.polygon.bus.on("activePolygonId", selectChange);
+
+const selectItem = computed(() =>
+  boardData.value.polygons.find(({ id }) => id === selectId.value)
+);
+const currentItem = computed(() =>
+  boardData.value.polygons.find(({ id }) => id === boardStatus.editPolygonId)
+);
+
+const cleanupEdit = shallowRef<() => void>();
+const enterEdit = () => {
+  cleanupEdit.value && cleanupEdit.value();
+  const quitEdit = board.polygon.editPolygon(selectId.value);
+  let needUpdate = false;
+  const stopWatch = watch(
+    () => currentItem.value,
+    () => (needUpdate = true),
+    { deep: true }
+  );
+  cleanupEdit.value = () => {
+    board.polygon.bus.off("penEndHandler", cleanupEdit.value);
+    quitEdit();
+    selectChange(null);
+    stopWatch();
+    needUpdate && boardDataChange();
+    cleanupEdit.value = null;
+  };
+  board.polygon.bus.on("penEndHandler", cleanupEdit.value);
+};
+
+onBeforeUnmount(() => {
+  cleanupEdit.value && cleanupEdit.value();
+  board.polygon.status.lightPointId = null;
+});
+
+const isShowPolyEditName = ref(false);
+const handleShowEditModel = (item: PolyDataType) => {
+  selectChange(item.id);
+  isShowPolyEditName.value = true;
+};
+
+const del = async (id: string) => {
+  if ((await confirm("确定要删除吗")) && !currentItem.value) {
+    boardDataChange(() => board.polygon.removePolygon(id));
+  }
+};
+
+const handleDownload = async (item: any) => {
+  const polygonPoints: any[] = getWholeLinePolygonPoints(boardData.value, item.id);
+
+  const points = polygonPoints.map((p) => {
+    const pos = [p.x, p.y, 0];
+    if (p.rtk) {
+      const sPoint = scenePoints.value.find(({ id }) => id.toString() === p.title);
+      if (sPoint) {
+        pos[2] = sPoint.pos[2];
+      }
+    }
+    return pos;
+  });
+  const dists = polygonPoints.map((p) => ({
+    title: p.id,
+    desc: p.title || p.id,
+  }));
+  await downloadPointsXLSL1(
+    points,
+    dists,
+    `${item.name ? item.name : "本体边界" + item.id}`
+  );
+};
+</script>
+
+<style lang="scss" scoped>
+.tree-item {
+  display: flex;
+  width: calc(100% - 50px);
+  align-items: center;
+  justify-content: space-between;
+
+  .title {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis; //文本溢出显示省略号
+    white-space: nowrap; //文本不会换行
+  }
+
+  .oper {
+    flex: none;
+  }
+}
+
+.disable {
+  pointer-events: all;
+}
+
+.tree-layout {
+  p {
+    color: #303133;
+    font-size: 14px;
+  }
+
+  .sub-title {
+    font-size: 14px;
+    font-weight: bolder;
+    margin-bottom: 30px;
+  }
+}
+
+.right-layout {
+  display: flex;
+  height: 100%;
+  flex-direction: column;
+  font-size: 16px;
+
+  .right-content {
+    flex: 1;
+    overflow-y: auto;
+  }
+}
+
+.tree-layout .tree-scene-name {
+  font-size: 10px;
+  margin: 0;
+  color: #999;
+}
+
+.poly-list {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  font-size: 14px;
+  user-select: none;
+
+  .poly-list-item {
+    cursor: pointer;
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+
+    &.active {
+      color: #409eff;
+    }
+
+    .icon {
+      margin-left: 8px;
+      font-size: 16px;
+      color: #409eff;
+      cursor: pointer;
+    }
+  }
+
+  .empty {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 13px;
+    color: gray;
+    padding-top: 40px;
+  }
+}
+
+.draw-global-icon {
+  width: 64px;
+  height: 64px;
+  background: #ffffff;
+  border-radius: 50%;
+  position: fixed;
+  z-index: 1000;
+  transform: translateX(calc(-1 * calc(50% - 300px)));
+  left: calc(50% - 300px);
+  top: 90%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #409eff;
+}
+</style>

+ 0 - 3
src/view/quisk.ts

@@ -7,7 +7,6 @@ import OrganizationEdit from "./organization-edit.vue";
 import UsersAdd from "./users-add.vue";
 import UsersEdit from "./users-edit.vue";
 import UsersPasswordEdit from "./users-password-edit.vue";
-console.log('RelicsEdit', RelicsEdit)
 
 export const relicsEdit = quiskMountFactory(RelicsEdit, {
   title: "创建文物",
@@ -45,5 +44,3 @@ export const usersPasswordEdit = quiskMountFactory(UsersPasswordEdit, {
   title: "修改密码",
   width: 520,
 });
-
-

+ 1 - 2
src/view/scene-select.vue

@@ -90,7 +90,6 @@ const tableProps = {
 
 let time: NodeJS.Timeout;
 const checkedTable = () => {
-  console.log('checkedTable', tableProps.tableRef.value)
   if (tableProps.tableRef.value) {
     tableProps.tableRef.value!.clearSelection();
     console.log("1");
@@ -104,7 +103,7 @@ const checkedTable = () => {
   }, 100);
 };
 
-watch(tableProps.tableRef, checkedTable);
+watch(() => tableProps.tableRef.value, checkedTable);
 
 defineExpose<QuiskExpose>({
   async submit() {

+ 60 - 24
src/view/scene.vue

@@ -4,23 +4,50 @@
       <div class="search">
         <el-form label-width="100px" inline>
           <el-form-item label="场景标题:">
-            <el-input clearable v-model="pageProps.sceneName" style="width: 250px" placeholder="请输入" />
+            <el-input
+              clearable
+              v-model="pageProps.sceneName"
+              style="width: 250px"
+              placeholder="请输入"
+            />
           </el-form-item>
           <el-form-item label="场景码:">
-            <el-input clearable v-model="pageProps.sceneCode" style="width: 250px" placeholder="请输入" />
+            <el-input
+              clearable
+              v-model="pageProps.sceneCode"
+              style="width: 250px"
+              placeholder="请输入"
+            />
           </el-form-item>
           <template v-if="!simple">
             <el-form-item label="SN码:">
-              <el-input clearable v-model="pageProps.snCode" style="width: 250px" placeholder="请输入" />
+              <el-input
+                clearable
+                v-model="pageProps.snCode"
+                style="width: 250px"
+                placeholder="请输入"
+              />
             </el-form-item>
             <el-form-item label="设备类型:">
               <el-select style="width: 250px" v-model="pageProps.cameraType" clearable>
-                <el-option :value="Number(key)" :label="type" v-for="(type, key) in DeviceTypeDesc" />
+                <el-option
+                  :value="Number(key)"
+                  :label="type"
+                  v-for="(type, key) in DeviceTypeDesc"
+                />
               </el-select>
             </el-form-item>
             <el-form-item label="拍摄时间:">
-              <el-date-picker clearable type="daterange" v-model="pageProps.shootTime" start-placeholder="请选择"
-                end-placeholder="请选择" range-separator="-" placeholder="请选择" style="width: 250px" />
+              <el-date-picker
+                clearable
+                type="daterange"
+                v-model="pageProps.shootTime"
+                start-placeholder="请选择"
+                end-placeholder="请选择"
+                range-separator="-"
+                placeholder="请选择"
+                style="width: 250px"
+              />
             </el-form-item>
             <!-- <el-form-item label="绑定账号:">
               <el-input
@@ -42,7 +69,13 @@
     </div>
 
     <div class="relics-content">
-      <el-table :data="sceneArray" border row-key="'sceneCode'" @selection-change="handleTableSelect" ref="tableRef">
+      <el-table
+        :data="sceneArray"
+        border
+        row-key="'sceneCode'"
+        @selection-change="handleTableSelect"
+        :ref="(d) => {tableProps && ((tableProps as any).tableRef.value = d)}"
+      >
         <slot name="table"></slot>
         <el-table-column label="场景标题" v-slot:default="{ row }">
           <a class="link" @click="gotoScene(row, false)">
@@ -90,8 +123,13 @@
             <el-button link type="primary" size="small" @click="gotoScene(row, true)">
               编辑
             </el-button>
-            <el-button link type="danger" @click="delHandler(row.sceneId)" size="small"
-              v-if="row.calcStatus !== SceneStatus.RUN">
+            <el-button
+              link
+              type="danger"
+              @click="delHandler(row.sceneId)"
+              size="small"
+              v-if="row.calcStatus !== SceneStatus.RUN"
+            >
               删除
             </el-button>
           </template>
@@ -99,15 +137,21 @@
       </el-table>
     </div>
     <div class="pag-layout">
-      <el-pagination background layout="total, prev, pager, next, sizes, jumper" v-model:page-size="pageProps.pageSize"
-        :page-sizes="[10, 20, 50, 100]" :total="total" @current-change="(data: number) => pageProps.pageNum = data"
-        :current-page="pageProps.pageNum" />
+      <el-pagination
+        background
+        layout="total, prev, pager, next, sizes, jumper"
+        v-model:page-size="pageProps.pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        @current-change="(data: number) => pageProps.pageNum = data"
+        :current-page="pageProps.pageNum"
+      />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { onActivated, ref, unref, watch, watchEffect } from "vue";
+import { onActivated, ref, watch } from "vue";
 import { scenePageFetch, ScenePageProps, delSceneFetch } from "@/request";
 import {
   SceneStatusDesc,
@@ -131,7 +175,6 @@ const initProps: ScenePageProps = {
 const pageProps = ref({ ...initProps });
 const total = ref<number>(0);
 const sceneArray = ref<Scene[]>([]);
-const tableRef = ref('');
 
 const refresh = debounce(async () => {
   const data = await scenePageFetch(pageProps.value);
@@ -155,19 +198,12 @@ const delHandler = async (relicsId: number) => {
 watch(pageProps, refresh, { deep: true, immediate: true });
 onActivated(refresh);
 
-watchEffect(() => {
-  if (unref(tableRef)) {
-    // props.tableProps.tableRef.value = unref(tableRef)
-  }
-})
-
-
 const handleTableSelect = (val: any) => {
   if (props.tableProps && "selectionChange" in props.tableProps) {
-    console.log('selectionChange')
-    props.tableProps.selectionChange(val)
+    console.log("selectionChange");
+    props.tableProps.selectionChange(val);
   }
-}
+};
 </script>
 
 <style scoped lang="scss">

+ 2 - 2
tsconfig.json

@@ -27,8 +27,8 @@
         "src/*"
       ],
       "drawing-board": [
-        "./src/lib/board/4dmap.d.ts"
-        // "../drawing-board/src/app/4dmap/index.ts"
+        // "./src/lib/board/4dmap.d.ts"
+        "../drawing-board/src/app/4dmap/index.ts"
       ]
     }
   },

+ 5 - 5
vite.config.ts

@@ -16,11 +16,11 @@ export default ({ mode }: any) =>
         },
         {
           find: "drawing-board",
-          replacement: resolve(__dirname, "./src/lib/board/4dmap.js"),
-          // replacement: resolve(
-          //   __dirname,
-          //   "../drawing-board/src/app/4dmap/index.ts"
-          // ),
+          // replacement: resolve(__dirname, "./src/lib/board/4dmap.js"),
+          replacement: resolve(
+            __dirname,
+            "../drawing-board/src/app/4dmap/index.ts"
+          ),
         },
       ],
     },