Prechádzať zdrojové kódy

fix: 制作查看也

bill 1 rok pred
rodič
commit
264f437f42

BIN
public/templaten.xls


BIN
public/本体边界坐标.xls


+ 32 - 3
src/router.ts

@@ -1,4 +1,5 @@
 import { RouteRecordRaw, createRouter, createWebHashHistory } from "vue-router";
+import { gHeaders } from "./request/state";
 
 const history = createWebHashHistory();
 const routes: RouteRecordRaw[] = [
@@ -25,13 +26,37 @@ const routes: RouteRecordRaw[] = [
           {
             path: "",
             name: "map",
-            meta: { title: "文物" },
+            meta: { title: "文物", navClass: "map" },
             component: () => import("@/view/map/map.vue"),
           },
           {
             path: "pano/:pid",
             name: "pano",
-            meta: { title: "点位" },
+            meta: { title: "点位", navClass: "pano" },
+            component: () => import("@/view/pano/pano.vue"),
+          },
+        ],
+      },
+    ],
+  },
+  {
+    path: "/query",
+    name: "query-main-layout",
+    component: () => import("@/view/layout/nav.vue"),
+    children: [
+      {
+        path: "relics/:relicsId",
+        children: [
+          {
+            path: "",
+            name: "query-map",
+            meta: { title: "文物", navClass: "map" },
+            component: () => import("@/view/map/map.vue"),
+          },
+          {
+            path: "pano/:pid",
+            name: "query-pano",
+            meta: { title: "点位", navClass: "pano" },
             component: () => import("@/view/pano/pano.vue"),
           },
         ],
@@ -47,7 +72,11 @@ export const setDocTitle = (title: string) => {
 
 router.beforeEach((to, _, next) => {
   if (!to.name || to.name === "main-layout") {
-    router.replace({ name: "relics" });
+    if (gHeaders.token) {
+      router.replace({ name: "relics" });
+    } else {
+      router.replace({ name: "login" });
+    }
     return;
   }
   if (to.meta?.title) {

+ 5 - 4
src/store/scene.ts

@@ -8,6 +8,7 @@ import {
 } from "@/request";
 import { computed, ref } from "vue";
 import { Relics, RelicsScene, RelicsScenePoint } from "@/request/type";
+import { gHeaders } from "@/request/state";
 
 export type { RelicsScene, RelicsScenePoint };
 
@@ -25,7 +26,7 @@ const fileNames = new Array(6).fill(0);
 export const getPointPano = (sceneCode: string, pid: number) =>
   fileNames.map(
     (_, i) =>
-      `https://4dkk.4dage.com/scene_view_data/${sceneCode}/images/tiles/4k/${pid}_skybox${i}.jpg?x-oss-process=image/resize,h_2048&version=2`
+      `https://4dkk.4dage.com/scene_view_data/${sceneCode}/images/tiles/4k/${pid}_skybox${i}.jpg`
   );
 
 const refreshScenes = async (relicsId: number) => {
@@ -65,11 +66,11 @@ export const updateScenePointName = async (
 export const gotoScene = (scene: RelicsScene) => {
   const params = new URLSearchParams();
   params.set("m", scene.sceneCode);
-  params.set("token", "xxx");
+  params.set("token", gHeaders.token);
   params.set("lang", "zh");
   if (scene.sceneCode.startsWith("KJ")) {
-    window.open(`https://www.4dkankan.com/spg.html?` + params.toString());
+    window.open(`https://test.4dkankan.com/spg.html?` + params.toString());
   } else {
-    window.open(`https://laser.4dkankan.com/?` + params.toString());
+    window.open(`https://uat-laser.4dkankan.com/uat/?` + params.toString());
   }
 };

+ 3 - 3
src/store/user.ts

@@ -30,8 +30,8 @@ errorHook.push((code) => {
     logout();
   }
 });
-{
-  const token = localStorage.getItem("token");
-  token && (gHeaders.token = token);
+const token = localStorage.getItem("token");
+if (token) {
+  gHeaders.token = token;
   getUserInfo();
 }

+ 34 - 16
src/util/pc4xlsl.ts

@@ -2,25 +2,10 @@ import * as XLSX from "xlsx";
 import { round, toDegrees } from "./";
 import { saveAs } from "./file-serve";
 
-export const downloadPointsXLSL = async (
-  points: number[][],
-  desc: { title: string; desc: string }[] = [],
-  name: string
-) => {
-  const data = await fetch("/template.xls").then((r) => r.arrayBuffer());
+const genXLSLByTemp = (data: ArrayBuffer, tabs: any[][], name: string) => {
   const workbook = XLSX.read(data);
   const sheetName = workbook.SheetNames[0];
   const worksheet = workbook.Sheets[sheetName];
-  const tabs = points.map((point, i) => {
-    const des = desc[i] || { title: "无", desc: "无" };
-    return [
-      toDegrees(point[0], 4),
-      toDegrees(point[1], 4),
-      round(point[2], 4),
-      des.title,
-      des.desc,
-    ];
-  });
   XLSX.utils.sheet_add_aoa(worksheet, tabs, { origin: "A2" });
 
   const wbout = XLSX.write(workbook, {
@@ -40,3 +25,36 @@ export const downloadPointsXLSL = async (
   });
   return saveAs(blob, `${name}.xls`);
 };
+
+export const downloadPointsXLSL = async (
+  points: number[][],
+  desc: { title: string; desc: string }[] = [],
+  name: string
+) => {
+  const temps = await Promise.all([
+    fetch("/templaten.xls").then((r) => r.arrayBuffer()),
+    fetch("/本体边界坐标.xls").then((r) => r.arrayBuffer()),
+  ]);
+  const tabsArray = [
+    points.map((point, i) => {
+      const des = desc[i] || { title: "无", desc: "无" };
+      return [i, des.title, toDegrees(point[1], 4), toDegrees(point[0], 4)];
+    }),
+    points.map((point, i) => {
+      const des = desc[i] || { title: "无", desc: "无" };
+      return [
+        toDegrees(point[1], 4),
+        toDegrees(point[0], 4),
+        round(point[2], 4),
+        des.title,
+        des.desc,
+      ];
+    }),
+  ];
+  const names = [name, name + "本体边界坐标"];
+
+  return Promise.all([
+    genXLSLByTemp(temps[0], tabsArray[0], names[0]),
+    genXLSLByTemp(temps[1], tabsArray[1], names[1]),
+  ]);
+};

+ 3 - 3
src/view/layout/nav.vue

@@ -30,14 +30,14 @@ import { computed } from "vue";
 import { user, logout } from "@/store/user";
 import { errorHook } from "@/request/state";
 
-const name = computed(() => router.currentRoute.value.name as string);
+const name = computed(() => router.currentRoute.value.meta?.navClass as string);
 const logoutHandler = () => {
   logout();
-  router.replace("login");
+  router.replace({ name: "login" });
 };
 errorHook.push((code) => {
   if (code === 4008) {
-    router.replace("login");
+    router.replace({ name: "login" });
   }
 });
 </script>

+ 2 - 2
src/view/login.vue

@@ -4,8 +4,8 @@
       <div class="login-layer">
         <div class="content">
           <div class="info">
-            <h1>不移动动文物管理平台</h1>
-            <p>Non mobile cultural relic management platform</p>
+            <h1>第四次全国文物普查</h1>
+            <p>数据采集管理平台</p>
           </div>
           <el-form class="panel login" :model="form" @submit.stop>
             <h2>欢迎登录</h2>

+ 42 - 12
src/view/map/map-right.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="right-layout">
     <div class="right-content">
-      <el-form :inline="false">
+      <el-form :inline="false" v-if="router.currentRoute.value.name === 'map'">
         <el-form-item v-if="relics">
           <el-input v-model="relicsName" :maxlength="50" placeholder="不可移动文物名称">
             <template #append>
@@ -36,20 +36,34 @@
               class="tree-item"
               @click="!data.disable && emit((data.type === 'scene' ? 'flyScene' : 'flyPoint') as any, data.raw)"
             >
-              <span :class="{ disable: data.disable }">
+              <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 /> </el-icon>
+                  {{ data.raw.sceneName }}
+                  <span class="tree-scene-name">{{ node.label }}</span>
+                </span>
+              </el-tooltip>
+              <span :class="{ disable: data.disable }" class="title" v-else>
                 <el-icon>
-                  <Grid v-if="data.type === 'scene'" />
-                  <LocationInformation v-else />
+                  <LocationInformation />
                 </el-icon>
                 {{ node.label }}
               </span>
-              <span>
-                <el-icon color="#409efc" v-if="data.type === 'scene'">
-                  <Delete @click.stop="delScene(data.raw)" />
-                </el-icon>
-                <el-icon v-else color="#409efc">
-                  <Edit @click.stop="inputPoint = data.raw" />
-                </el-icon>
+              <span class="oper">
+                <template v-if="router.currentRoute.value.name === 'map'">
+                  <el-icon color="#409efc" v-if="data.type === 'scene'">
+                    <Delete @click.stop="delScene(data.raw)" />
+                  </el-icon>
+                  <el-icon v-else color="#409efc">
+                    <Edit @click.stop="inputPoint = data.raw" />
+                  </el-icon>
+                </template>
                 <el-icon color="#409efc" style="margin-left: 8px">
                   <Link
                     @click.stop="
@@ -112,6 +126,7 @@ import {
 import SingleInput from "@/components/single-input.vue";
 import { downloadPointsXLSL } from "@/util/pc4xlsl";
 import { ElMessage } from "element-plus";
+import { router } from "@/router";
 
 const emit = defineEmits<{
   (e: "flyScene", data: RelicsScene): void;
@@ -187,9 +202,19 @@ const exportFile = async () => {
 <style lang="scss" scoped>
 .tree-item {
   display: flex;
-  width: 100%;
+  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;
+  }
 }
 
 .tree-layout {
@@ -207,4 +232,9 @@ const exportFile = async () => {
     overflow-y: auto;
   }
 }
+.tree-layout .tree-scene-name {
+  font-size: 10px;
+  margin: 0;
+  color: #999;
+}
 </style>

+ 18 - 10
src/view/map/map.vue

@@ -80,7 +80,7 @@ const points = computed(() =>
 
 const gotoPoint = (point: RelicsScenePoint) => {
   router.push({
-    name: "pano",
+    name: router.currentRoute.value.name === "map" ? "pano" : "query-pano",
     params: { pid: point.id },
   });
 };
@@ -148,15 +148,22 @@ const refreshTileType = () => {
 
 watch(points, refreshHots, { immediate: true });
 watch(tileType, refreshTileType, { immediate: true });
+watch(
+  () => router.currentRoute.value.name,
+  () => {
+    if (["map", "query-map"].includes(router.currentRoute.value.name as string)) {
+      initRelics(Number(router.currentRoute.value.params.relicsId)).then(() => {
+        scenes.value.length && flyScene(scenes.value[0]);
+      });
+    }
+  },
+  { immediate: true }
+);
 watchEffect(() => {
-  if (router.currentRoute.value.name === "map") {
-    initRelics(Number(router.currentRoute.value.params.relicsId)).then(() => {
-      scenes.value.length && flyScene(scenes.value[0]);
-    });
-  }
-});
-watchEffect(() => {
-  if (router.currentRoute.value.name === "map" && relics.value) {
+  if (
+    ["map", "query-map"].includes(router.currentRoute.value.name as string) &&
+    relics.value
+  ) {
     setDocTitle(relics.value.name);
   }
 });
@@ -178,7 +185,8 @@ watchEffect(() => {
 }
 
 .right-control {
-  flex: 0 0 300px;
+  flex: none;
+  width: 300px;
   padding: 15px;
 
   border-left: 1px solid var(--border-color);

+ 3 - 3
src/view/pano/env.ts

@@ -61,7 +61,7 @@ const getDrawVaring = (gl: WebGL2RenderingContext) => {
 
 export const init = (canvas: HTMLCanvasElement) => {
   const size = [canvas.width, canvas.height];
-  const gl = canvas.getContext("webgl2")!;
+  const gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true })!;
   const program = createProgram(gl, envVertSource, envFragSource);
   const projectionMat = mat4.perspective(
     mat4.create(),
@@ -78,12 +78,12 @@ export const init = (canvas: HTMLCanvasElement) => {
     mat4.invert(invProjectionViewMat, invProjectionViewMat);
   };
 
+  gl.viewport(0, 0, size[0], size[1]);
   const redraw = () => {
     gl.clear(gl.COLOR_BUFFER_BIT);
     gl.enable(gl.DEPTH_TEST);
     gl.depthFunc(gl.LEQUAL);
     gl.useProgram(program);
-    gl.viewport(0, 0, size[0], size[1]);
     gl.bindVertexArray(varing.vao);
     setUniforms(gl, program, {
       invProjectionViewMat,
@@ -101,7 +101,7 @@ export const init = (canvas: HTMLCanvasElement) => {
     },
     [0, 1, 0],
     [0, 0, 0],
-    {},
+    { yaw: glMatrix.toRadian(-180) },
     80
   );
   return {

+ 29 - 5
src/view/pano/pano.vue

@@ -4,13 +4,27 @@
     <div class="btns">
       <el-button
         size="large"
+        type="primary"
+        style="margin-right: 20px; width: 100px"
+        @click="photo"
+      >
+        屏幕拍照
+      </el-button>
+      <el-button
+        size="large"
         style="margin-right: 20px; width: 100px"
         @click="copyGis"
         v-if="point?.pos"
       >
         复制经纬度
       </el-button>
-      <el-button size="large" type="primary" style="width: 100px" @click="update = true">
+      <el-button
+        size="large"
+        type="primary"
+        style="width: 100px"
+        @click="update = true"
+        v-if="router.currentRoute.value.name === 'pano'"
+      >
         修改名称
       </el-button>
     </div>
@@ -40,6 +54,7 @@ import {
 import { copyText, toDegrees } from "@/util";
 import { ElMessage } from "element-plus";
 import { relicsScenePosInfoFetch } from "@/request";
+import saveAs from "@/util/file-serve";
 
 type Params = { pid?: string } | null;
 const params = computed(() => router.currentRoute.value.params as Params);
@@ -67,16 +82,25 @@ const loading = ref(false);
 const copyGis = async () => {
   const pos = point.value!.pos;
   await copyText(
-    `经度:${toDegrees(pos[1])}, 纬度: ${toDegrees(pos[0])}, 高: ${pos[2]}`
+    `经度:${toDegrees(pos[1])}, 纬度: ${toDegrees(pos[0])}, 高: ${pos[2]}`
   );
-  ElMessage.success("经纬度高层复制成功");
+  ElMessage.success("经纬度高程复制成功");
+};
+
+const photo = () => {
+  panoDomRef.value!.toBlob(async (blob) => {
+    if (blob) {
+      await saveAs(blob, "pano.png");
+      ElMessage.success("图片导出成功");
+    }
+  }, "image/png");
 };
 
 onMounted(() => {
   if (!panoDomRef.value) throw "没有canvas DOM";
   const canvas = panoDomRef.value;
-  canvas.width = canvas.offsetWidth;
-  canvas.height = canvas.offsetHeight;
+  canvas.width = canvas.offsetWidth * 4;
+  canvas.height = canvas.offsetHeight * 4;
   const pano = init(canvas);
 
   destroyFns.push(

+ 23 - 2
src/view/relics.vue

@@ -21,8 +21,19 @@
 
     <el-table :data="relicsArray" border>
       <el-table-column prop="name" label="文物保护单位名称" />
-      <el-table-column label="操作" width="120">
+      <el-table-column label="操作" width="200">
         <template #default="{ row }">
+          <el-button link type="primary" size="small" @click="shareHandler(row)">
+            分享
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            size="small"
+            @click="router.push(getQueryRouteLocation(row))"
+          >
+            查看
+          </el-button>
           <el-button
             link
             type="primary"
@@ -69,7 +80,8 @@ import {
 } from "@/request";
 import { Relics } from "@/request/type";
 import { router } from "@/router";
-import { ElMessageBox } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { copyText } from "@/util";
 
 const initProps: RelicsPageProps = {
   name: "",
@@ -99,6 +111,15 @@ const delHandler = async (relicsId: number) => {
     await refresh();
   }
 };
+const getQueryRouteLocation = (row: Relics) =>
+  router.resolve({ name: "query-map", params: { relicsId: row.relicsId } });
+
+const shareHandler = async (row: Relics) => {
+  const link = location.origin + location.pathname + getQueryRouteLocation(row).href;
+  await copyText(link);
+  await ElMessage.success("链接复制成功");
+};
+
 const inputMode = ref(false);
 
 watchEffect(refresh);