ソースを参照

Merge branch 'dev' into prod

bill 9 ヶ月 前
コミット
bcf42bf025

+ 1 - 0
.env

@@ -1,4 +1,5 @@
 VITE_QJ_URL=https://test.4dkankan.com/panorama
 VITE_LASER_URL=https://uat-laser.4dkankan.com/4pc
 VITE_API=https://uat-sp.4dkankan.com/
+VITE_4DKK_URL=https://test.4dkankan.com
 

+ 2 - 1
.env.development

@@ -1,3 +1,4 @@
 VITE_QJ_URL=https://test.4dkankan.com/panorama
 VITE_LASER_URL=https://uat-laser.4dkankan.com/4pc
-VITE_API=https://uat-sp.4dkankan.com/
+VITE_API=https://uat-sp.4dkankan.com/api
+VITE_4DKK_URL=https://test.4dkankan.com

+ 2 - 1
.env.production

@@ -1,3 +1,4 @@
 VITE_QJ_URL=https://4dkankan.com/panorama
 VITE_LASER_URL=https://laser.4dkankan.com/4pc
-VITE_API=https://sp.4dkankan.com/
+VITE_API=https://sp.4dkankan.com/
+VITE_4DKK_URL=https://www.4dkankan.com/

+ 2 - 1
.env.uat

@@ -1,3 +1,4 @@
 VITE_QJ_URL=https://test.4dkankan.com/panorama
 VITE_LASER_URL=https://uat-laser.4dkankan.com/4pc
-VITE_API=https://uat-sp.4dkankan.com/
+VITE_API=https://uat-sp.4dkankan.com/
+VITE_4DKK_URL=https://test.4dkankan.com

+ 2 - 2
package.json

@@ -5,8 +5,8 @@
   "type": "module",
   "scripts": {
     "dev": "vite",
-    "build": "vue-tsc && vite build --mode production",
-    "build-uat": "vue-tsc && vite build --mode uat",
+    "build": "vite build --mode production",
+    "build-uat": "vite build --mode uat",
     "preview": "vite preview"
   },
   "dependencies": {

ファイルの差分が大きいため隠しています
+ 7 - 0
src/assets/icon/cjgl.svg


ファイルの差分が大きいため隠しています
+ 7 - 0
src/assets/icon/cjgl_active.svg


ファイルの差分が大きいため隠しています
+ 9 - 0
src/assets/icon/dwgl.svg


ファイルの差分が大きいため隠しています
+ 9 - 0
src/assets/icon/dwgl_active.svg


+ 8 - 0
src/assets/icon/sbgl.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="sbgl">
+<g id="Union">
+<path d="M8 11C9.10457 11 10 10.1046 10 9C10 7.89543 9.10457 7 8 7C6.89543 7 6 7.89543 6 9C6 10.1046 6.89543 11 8 11Z" fill="#303133"/>
+<path d="M5.61803 2C5.23926 2 4.893 2.214 4.72361 2.55279L4 4H2C1.44772 4 1 4.44771 1 5V14C1 14.5523 1.44772 15 2 15H14C14.5523 15 15 14.5523 15 14V5C15 4.44772 14.5523 4 14 4H12L11.2764 2.55279C11.107 2.214 10.7607 2 10.382 2H5.61803ZM8 12C6.34315 12 5 10.6569 5 9C5 7.34315 6.34315 6 8 6C9.65685 6 11 7.34315 11 9C11 10.6569 9.65685 12 8 12Z" fill="#303133"/>
+</g>
+</g>
+</svg>

+ 8 - 0
src/assets/icon/sbgl_active.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="sbgl">
+<g id="Union">
+<path d="M8 11C9.10457 11 10 10.1046 10 9C10 7.89543 9.10457 7 8 7C6.89543 7 6 7.89543 6 9C6 10.1046 6.89543 11 8 11Z" fill="#409eff"/>
+<path d="M5.61803 2C5.23926 2 4.893 2.214 4.72361 2.55279L4 4H2C1.44772 4 1 4.44771 1 5V14C1 14.5523 1.44772 15 2 15H14C14.5523 15 15 14.5523 15 14V5C15 4.44772 14.5523 4 14 4H12L11.2764 2.55279C11.107 2.214 10.7607 2 10.382 2H5.61803ZM8 12C6.34315 12 5 10.6569 5 9C5 7.34315 6.34315 6 8 6C9.65685 6 11 7.34315 11 9C11 10.6569 9.65685 12 8 12Z" fill="#409eff"/>
+</g>
+</g>
+</svg>

+ 8 - 0
src/assets/icon/wwpc.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="wwpc">
+<g id="Union">
+<path d="M2 2C1.44772 2 1 2.44772 1 3V5H15V3C15 2.44772 14.5523 2 14 2H2Z" fill="#303133"/>
+<path d="M2 6H14V14C14 14.5523 13.5523 15 13 15H3C2.44772 15 2 14.5523 2 14V6ZM5 8.5C5 8.77614 5.22386 9 5.5 9H10.5C10.7761 9 11 8.77614 11 8.5C11 8.22386 10.7761 8 10.5 8H5.5C5.22386 8 5 8.22386 5 8.5Z" fill="#303133"/>
+</g>
+</g>
+</svg>

+ 8 - 0
src/assets/icon/wwpc_active.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="wwpc">
+<g id="Union">
+<path d="M2 2C1.44772 2 1 2.44772 1 3V5H15V3C15 2.44772 14.5523 2 14 2H2Z" fill="#409eff"/>
+<path d="M2 6H14V14C14 14.5523 13.5523 15 13 15H3C2.44772 15 2 14.5523 2 14V6ZM5 8.5C5 8.77614 5.22386 9 5.5 9H10.5C10.7761 9 11 8.77614 11 8.5C11 8.22386 10.7761 8 10.5 8H5.5C5.22386 8 5 8.22386 5 8.5Z" fill="#409eff"/>
+</g>
+</g>
+</svg>

+ 8 - 0
src/assets/icon/yhgl.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="yhgl">
+<g id="Union">
+<path d="M8 9C10.2091 9 12 7.20914 12 5C12 2.79086 10.2091 1 8 1C5.79086 1 4 2.79086 4 5C4 7.20914 5.79086 9 8 9Z" fill="#303133"/>
+<path d="M5 10C3.34315 10 2 11.3431 2 13V14C2 14.5523 2.44772 15 3 15H13C13.5523 15 14 14.5523 14 14V13C14 11.3431 12.6569 10 11 10H5Z" fill="#303133"/>
+</g>
+</g>
+</svg>

+ 8 - 0
src/assets/icon/yhgl_active.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="yhgl">
+<g id="Union">
+<path d="M8 9C10.2091 9 12 7.20914 12 5C12 2.79086 10.2091 1 8 1C5.79086 1 4 2.79086 4 5C4 7.20914 5.79086 9 8 9Z" fill="#409eff"/>
+<path d="M5 10C3.34315 10 2 11.3431 2 13V14C2 14.5523 2.44772 15 3 15H13C13.5523 15 14 14.5523 14 14V13C14 11.3431 12.6569 10 11 10H5Z" fill="#409eff"/>
+</g>
+</g>
+</svg>

+ 152 - 0
src/components/pointEdit.vue

@@ -0,0 +1,152 @@
+<template>
+  <el-dialog
+    :model-value="visible"
+    @update:model-value="(val) => emit('update:visible', val)"
+    :title="title"
+    width="500"
+  >
+    <el-form
+      :model="form"
+      :rules="formRules"
+      label-width="100px"
+      class="container"
+      ref="formRef"
+      label-position="right"
+      status-icon
+    >
+      <el-form-item label="坐标" prop="coord">
+        <el-input
+          v-model="form.coord"
+          show-word-limit
+          :autosize="{ minRows: 3, maxRows: 3 }"
+          type="textarea"
+          disabled
+          :placeholder="placeholder"
+        />
+      </el-form-item>
+
+      <el-form-item label="测试类型" prop="type">
+        <el-select style="width: 100%" v-model="form.type" clearable>
+          <el-option
+            :value="Number(key)"
+            :label="type"
+            v-for="(type, key) in measurePointDesc"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="测试说明" prop="name" required>
+        <el-input
+          v-model.trim="form.name"
+          :maxlength="100"
+          show-word-limit
+          :autosize="{ minRows: 3, maxRows: 8 }"
+          type="textarea"
+          placeholder="请输入"
+        />
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          v-model.trim="form.remark"
+          :maxlength="100"
+          type="text"
+          placeholder="请输入"
+        />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="emit('update:visible', false)">取消</el-button>
+        <el-button type="primary" @click="submit"> 确定 </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, unref, watchEffect, reactive } from "vue";
+import type { FormRules } from "element-plus";
+import { PointTypeEnum } from "drawing-board";
+const measurePointDesc = PointTypeEnum;
+// import { measurePointDesc } from "@/store/relics";
+
+console.log(measurePointDesc);
+const form = ref({
+  coord: "",
+  name: "",
+  type: "",
+  remark: "",
+});
+
+const formRules = reactive<FormRules>({
+  type: [{ required: true, message: "请输入", trigger: "change" }],
+  name: [{ required: true, message: "请输入", trigger: "blur" }],
+});
+
+const props = withDefaults(
+  defineProps<{
+    visible: boolean;
+    value: any;
+    title: string;
+    name?: string;
+    placeholder: string;
+    isAllowEmpty?: boolean;
+    updateValue: (value: any) => void;
+  }>(),
+  {
+    placeholder: "请输入",
+  }
+);
+const emit = defineEmits<{
+  (e: "update:visible", visible: boolean): void;
+}>();
+const formRef = ref();
+// const ivalue = ref(props.value);
+
+watchEffect(() => {
+  // ivalue.value = props.value;
+  console.log("props.value", props.value);
+  if (props.value) {
+    // debugger
+    form.value.coord = `纬度:${props.value.pos[0]}\n经度:${props.value.pos[1]}\n高程:${props.value.pos[2]}`;
+    form.value.type = props.value.type;
+    form.value.name = props.value.name;
+    form.value.remark = props.value.remark;
+  } else {
+    form.value.coord = "";
+    form.value.type = "";
+    form.value.name = "";
+    form.value.remark = "";
+  }
+});
+
+const submit = async () => {
+  const res = await unref(formRef)?.validate();
+  console.log("res", res);
+  if (res) {
+    const lastObj = {
+      ...props.value,
+      ...form.value,
+    };
+    delete lastObj.coord;
+
+    lastObj.type = measurePointDesc[lastObj.type];
+    // console.log("lastObj", lastObj);
+    await props.updateValue(lastObj);
+    emit("update:visible", false);
+  }
+  // if (ivalue.value.length === 0 && !props.isAllowEmpty) {
+  //   return ElMessage.error(`${props.name || "点位"}名称不能为空!`);
+  // }
+};
+</script>
+
+<style lang="scss" scoped>
+.container {
+  padding: 0px 60px 0 20px;
+}
+:global(.container .el-form-item) {
+  margin-bottom: 20px;
+}
+</style>

ファイルの差分が大きいため隠しています
+ 9 - 2
src/lib/board/4dmap.d.ts


ファイルの差分が大きいため隠しています
+ 1024 - 984
src/lib/board/4dmap.js


ファイルの差分が大きいため隠しています
+ 6 - 6
src/lib/board/4dmap.umd.cjs


+ 3 - 2
src/request/type.ts

@@ -4,7 +4,7 @@ import {
   relicsTypeDesc,
   creationMethodDesc,
 } from "@/store/relics";
-import { SceneStatus } from "@/store/scene";
+import { SceneStatus, SceneType } from "@/store/scene";
 import { PointTypeEnum } from "drawing-board";
 
 type UserInfoRoles = {
@@ -53,6 +53,7 @@ export type ScenePoint = {
   tbStatus: number;
   createTime: string;
   updateTime: string;
+  sceneType: SceneType
   cameraType: DeviceType;
   index: number;
   id: number;
@@ -131,7 +132,7 @@ export type Scene = {
   scenePos: ScenePoint[];
   algorithmTime: string;
   endTime: string;
-  sceneSource: 0;
+  sceneSource: SceneType;
   datasets: Dataset[];
   snCode: string;
   startTime: string;

+ 6 - 6
src/router.ts

@@ -48,7 +48,7 @@ const routes: RouteRecordRaw[] = [
       {
         path: "relics",
         name: "relics",
-        meta: { title: "文物普查" },
+        meta: { title: "文物普查", svgIcon: "wwpc" },
         component: () => import("@/view/relics.vue"),
       },
       {
@@ -91,25 +91,25 @@ const routes: RouteRecordRaw[] = [
       {
         path: "scene",
         name: "scene",
-        meta: { title: "场景管理" },
+        meta: { title: "场景管理", svgIcon: 'cjgl' },
         component: () => import("@/view/scene.vue"),
       },
       {
         path: "device",
         name: "device",
-        meta: { title: "设备管理" },
+        meta: { title: "设备管理", svgIcon: 'sbgl' },
         component: () => import("@/view/device.vue"),
       },
       {
         path: "organization",
         name: "organization",
-        meta: { title: "单位管理" },
+        meta: { title: "单位管理", svgIcon: 'dwgl' },
         component: () => import("@/view/organization.vue"),
       },
       {
         path: "users",
         name: "users",
-        meta: { title: "用户管理" },
+        meta: { title: "用户管理", svgIcon: 'yhgl' },
         component: () => import("@/view/users.vue"),
       },
     ],
@@ -155,7 +155,7 @@ const routes: RouteRecordRaw[] = [
   { path: '/:pathMatch(.*)*', component: import("@/view/layout/nav.vue") },
 
 ];
-
+// console.log('routes', routes)
 export const findRoute = (
   routeName: string,
   fullPath = false,

+ 43 - 12
src/store/scene.ts

@@ -2,7 +2,7 @@ import { relicsScenesFetch, updateRelicsScenePosNameFetch } from "@/request";
 import { computed, ref, watch } from "vue";
 import { Scene, ScenePoint } from "@/request/type";
 import { relics } from "./relics";
-import { DeviceType, DeviceType as SceneType } from "./device";
+import { DeviceType } from "./device";
 import { conversionFactory } from "@/helper/coord-transform";
 import { getTokenFetch } from "@/request";
 import {
@@ -39,7 +39,11 @@ export const getPointPano = (point: ScenePoint, tile = false) => {
         `https://4dkk.4dage.com/scene_view_data/${point.sceneCode}/images/tiles/4k/${point.uuid}_skybox${i}.jpg`
     );
   } else if (point.cameraType === DeviceType.VR) {
-    return `https://4dkankan.oss-cn-shenzhen.aliyuncs.com/scene_view_data/${point.sceneCode}/images/panoramas/${point.uuid}.jpg`;
+    if (point.sceneType === SceneType.VR) {
+      return `https://4dkankan.oss-cn-shenzhen.aliyuncs.com/scene_view_data/${point.sceneCode}/images/panoramas/${point.uuid}.jpg`;
+    } else {
+      return `https://4dkk.4dage.com/scene_result_data/${point.sceneCode}/caches/images/${point.uuid}.jpg`
+    }
   } else if (point.cameraType === DeviceType.CLUNT) {
     return `https://4dkk.4dage.com/scene_view_data/${point.sceneCode}/images/pan/high/${point.uuid}.jpg`;
   }
@@ -86,7 +90,6 @@ export const refreshScenes = async () => {
             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];
@@ -96,6 +99,7 @@ export const refreshScenes = async () => {
           }
           return {
             ...pos,
+            sceneType: scene.sceneSource,
             pos: coord,
           };
         }),
@@ -121,15 +125,37 @@ export const gotoScene = async (scene: Scene, edit = false) => {
     }
   }
   params.set("lang", "zh");
-  if (scene.sceneCode.startsWith("KJ")) {
-    const qjURL = import.meta.env.VITE_QJ_URL;
-    params.set("id", scene.sceneCode);
-    // console.log('')
-    window.open(`${qjURL}/${edit ? "edit" : "show"}.html?` + params.toString());
-  } else {
-    params.set("m", scene.sceneCode);
-    window.open(`${import.meta.env.VITE_LASER_URL}/?` + params.toString());
+
+  switch (Number(scene.sceneSource)) {
+    case SceneType.VR:
+      const qjURL = import.meta.env.VITE_QJ_URL;
+      params.set("id", scene.sceneCode);
+      window.open(`${qjURL}/${edit ? "edit" : "show"}.html?` + params.toString());
+      break;
+    case SceneType.CLUNT:
+      params.set("m", scene.sceneCode);
+      window.open(`${import.meta.env.VITE_LASER_URL}/?` + params.toString());
+      break;
+    case SceneType.MESH:
+      params.set("m", scene.sceneCode);
+      const kk_url = import.meta.env.VITE_4DKK_URL;
+      const dest = edit ? `${kk_url}/epg.html?${params.toString()}` : `${kk_url}/spg.html?${params.toString()}`;
+      console.log('mesh-url', dest)
+      window.open(dest);
+      break;
+    default:
+      break;
   }
+
+  // if (scene.sceneCode.startsWith("KJ")) {
+  //   const qjURL = import.meta.env.VITE_QJ_URL;
+  //   params.set("id", scene.sceneCode);
+  //   // console.log('')
+  //   window.open(`${qjURL}/${edit ? "edit" : "show"}.html?` + params.toString());
+  // } else {
+  //   params.set("m", scene.sceneCode);
+  //   window.open(`${import.meta.env.VITE_LASER_URL}/?` + params.toString());
+  // }
 };
 
 // 普通场景状态
@@ -142,10 +168,15 @@ export enum SceneStatus {
   // RERUN = 4,
 }
 
-export { SceneType };
+export enum SceneType {
+  VR = 6,
+  CLUNT = 5,
+  MESH = 3
+}
 export const SceneTypeDesc: { [key in SceneType]: string } = {
   [SceneType.VR]: "全景VR",
   [SceneType.CLUNT]: "点云场景",
+  [SceneType.MESH]: "mesh场景",
 };
 
 export const SceneStatusDesc: { [key in SceneStatus]: string } = {

+ 18 - 13
src/util/gl.ts

@@ -217,6 +217,7 @@ export const createFPSCamera = (
   let pitch = initView.pitch || 0;
   // 偏航角
   let yaw = initView.yaw || -Math.PI / 2;
+  yaw = Math.PI
 
   const cameraMat = mat4.identity(mat4.create());
   const updateCameraMat = () => {
@@ -228,29 +229,28 @@ export const createFPSCamera = (
     front[0] = Math.cos(pitch) * Math.cos(yaw);
     front[1] = Math.sin(pitch);
     front[2] = Math.cos(pitch) * Math.sin(yaw);
-    // console.log(pitch, yaw);
     vec3.normalize(front, front);
     vec3.cross(up, vec3.cross(vec3.create(), front, worldUp), front);
   };
 
   const start = vec2.create();
   const mousedownHandler = (ev: MouseEvent) => {
-    start[0] = ev.offsetX;
-    start[1] = ev.offsetY;
+    start[0] = ev.clientX;
+    start[1] = ev.clientY;
 
-    mount.addEventListener("mousemove", mouseMoveHandler);
-    mount.addEventListener("mouseup", mouseUpHandler);
+    document.documentElement.addEventListener("mousemove", mouseMoveHandler);
+    document.documentElement.addEventListener("mouseup", mouseUpHandler);
   };
 
   const rotatePixelAmount = 1500;
   const mouseMoveHandler = (ev: MouseEvent) => {
-    const end = vec2.fromValues(ev.offsetX, ev.offsetY);
+    const end = vec2.fromValues(ev.clientX, ev.clientY);
     const move = vec2.sub(vec2.create(), end, start);
     pitch += (move[1] / rotatePixelAmount) * Math.PI;
-    if (pitch > 89) {
-      pitch = 89;
-    } else if (pitch < -89) {
-      pitch = -89;
+    if (pitch > Math.PI / 2) {
+      pitch = Math.PI / 2;
+    } else if (pitch < -Math.PI / 2) {
+      pitch = -Math.PI / 2;
     }
     yaw -= (move[0] / rotatePixelAmount) * Math.PI;
     start[0] = end[0];
@@ -260,8 +260,8 @@ export const createFPSCamera = (
   };
 
   const mouseUpHandler = () => {
-    mount.removeEventListener("mousemove", mouseMoveHandler);
-    mount.removeEventListener("moseup", mouseUpHandler);
+    document.documentElement.removeEventListener("mousemove", mouseMoveHandler);
+    document.documentElement.removeEventListener("moseup", mouseUpHandler);
   };
   const wheelHandler = (ev: WheelEvent) => {
     const amount = ev.deltaY * -0.01;
@@ -286,7 +286,12 @@ export const createFPSCamera = (
     recovery() {
       vec3.copy(eys, vec3.fromValues(initEys[0], initEys[1], initEys[2]));
       pitch = initView.pitch || 0;
-      yaw = initView.yaw || -Math.PI / 2;
+      yaw = initView.yaw || 0;
+      updateFront();
+      updateCameraMat();
+    },
+    setYaw(_yaw: number) {
+      yaw = _yaw
       updateFront();
       updateCameraMat();
     },

+ 62 - 0
src/util/image.ts

@@ -0,0 +1,62 @@
+import { getTextBound, toDegrees } from ".";
+
+const canvas = document.createElement("canvas");
+// 水印添加函数
+export const addWatermark = (imgURL: string, pos: number[], ration: number) => {
+  const ctx = canvas.getContext("2d");
+  const image = new Image();
+  image.src = imgURL;
+  image.crossOrigin = 'anonymous';
+
+  return new Promise<Blob>((resolve, reject) => {
+    image.onload = () => {
+      canvas.width = image.width;
+      canvas.height = image.height;
+      ctx.drawImage(image, 0, 0, image.width, image.height);
+
+      const font = `${ration * 20}px Arial`;
+      const lines = `经度: ${toDegrees(pos[0])}\n纬度: ${toDegrees(pos[1])}`.split("\n");
+      const lineTopPadding = 5 * ration;
+      const lineBounds = lines.map((line) =>
+        getTextBound(line, font, [lineTopPadding, 0])
+      );
+      const bound = lineBounds.reduce(
+        (t, { width, height }) => {
+          t.width = Math.max(t.width, width);
+          t.height += height;
+          return t;
+        },
+        { width: 0, height: 0 }
+      );
+      const padding = 20 * ration;
+      const margin = 80 * ration;
+
+      const position = [
+        image.width - margin - bound.width,
+        image.height - margin - bound.height,
+      ];
+
+      ctx.rect(
+        position[0] - padding,
+        position[1] - padding,
+        bound.width + 2 * padding,
+        bound.height + 2 * padding
+      );
+      ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
+      ctx.fill();
+
+      ctx.font = font;
+      ctx.textBaseline = "top";
+      ctx.fillStyle = "#fff";
+      let itemTop = 0;
+      lines.forEach((line, ndx) => {
+        ctx.fillText(line, position[0], position[1] + itemTop + lineTopPadding);
+        itemTop += lineBounds[ndx].height;
+      });
+      canvas.toBlob(blob => {
+        resolve(blob)
+      }, "image/jpeg", 1)
+    };
+    image.onerror = reject;
+  });
+};

+ 15 - 1
src/view/layout/nav.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="main-layout">
+  <div class="main-layout" :class="{ [routeName]: true }">
     <div class="header" :class="{ [name]: true }">
       <span class="title-span">
         <el-button
@@ -131,6 +131,18 @@ const showBack = computed(() => {
   display: flex;
   flex-direction: column;
   height: 100%;
+  &.scene,
+  &.relics,
+  &.device,
+  &.organization,
+  &.users {
+    .content {
+      .view {
+        margin-left: 8px;
+        margin-top: 8px;
+      }
+    }
+  }
 }
 
 .header {
@@ -194,6 +206,8 @@ const showBack = computed(() => {
   flex: 1;
   overflow: hidden;
   display: flex;
+  background: #f2f3f5ff;
+  --bgColor: #fff;
 
   .slide {
     width: 200px;

+ 1 - 0
src/view/layout/slide/index.vue

@@ -23,6 +23,7 @@ import { user } from "@/store/user";
 
 import { router, findRoute } from "@/router";
 //@TODO
+
 const isSuper = computed(
   () =>
     user.value.roles.filter((item) => item.roleKey === "super_admin").length > 0

+ 30 - 1
src/view/layout/slide/submenu.vue

@@ -1,10 +1,39 @@
 <template>
   <el-menu-item :index="name" :key="name">
-    <i :class="'iconfont ' + meta.icon" v-if="meta.icon"></i>
+    <img
+      class="svg-icon"
+      :src="getIconUrl(meta.svgIcon, isActiveRoute(name))"
+      v-if="meta.svgIcon"
+    />
+    <i :class="'iconfont ' + meta.icon" v-else-if="meta.icon"></i>
     <span>{{ meta.title }}</span>
   </el-menu-item>
 </template>
 
 <script lang="ts" setup>
+import { computed } from "vue";
+import { router } from "@/router";
 defineProps<{ meta: any; name: string }>();
+
+const isActiveRoute = computed(() => (name: string) => {
+  return router.currentRoute.value.name === name;
+});
+const getIconUrl = computed(() => (name: string, isActive: boolean) => {
+  const url = isActive
+    ? new URL(`../../../assets/icon/${name}_active.svg`, import.meta.url)
+    : new URL(`../../../assets/icon/${name}.svg`, import.meta.url);
+  // console.log("当前URL", url);
+  return String(url);
+});
 </script>
+<style scoped>
+.svg-icon {
+  width: 16px;
+  height: 16px;
+  margin-right: 8px;
+}
+:global(.is-active .svg-icon) {
+  fill: var(--el-menu-active-color);
+  /* color: var(--el-menu-active-color); */
+}
+</style>

+ 1 - 1
src/view/map/install.ts

@@ -16,7 +16,7 @@ mapManage.setCenter(defaultCenter);
 watchEffect(() => mapManage.setTileType(tileType.value));
 
 export const noValidPoint = (pos: ScenePoint) =>
-  !pos.pos || pos.pos.length === 0 || pos.pos.some((i) => !i);
+  !pos?.pos || pos.pos.length === 0 || pos.pos.some((i) => !i);
 export const validScene = (scene: Scene) => !scene.scenePos.every(noValidPoint);
 
 export const flyScene = (scene: Scene) => {

+ 20 - 3
src/view/map/pc4Helper.ts

@@ -11,6 +11,7 @@ import {
 } from "@/util/pc4xlsl";
 import { noValidPoint } from "./install";
 import { PointTypeEnum } from "@/lib/board/4dmap";
+import { addWatermark } from "@/util/image";
 
 export const exportFile = async (
   points: ScenePoint[],
@@ -73,9 +74,25 @@ export const exportImage = async (points: ScenePoint[], name?: string) => {
 
   const downloadImages = Promise.all(
     points.map((point) => {
-      const url = getPointPano(point);
-      return fetch(url as string)
-        .then((res) => res.blob())
+      const url = getPointPano(point) as string;
+
+
+      let loadBlob: Promise<Blob>
+      console.log(point.pos, noValidPoint(point.pos as any))
+      if (!noValidPoint(point)) {
+        let ration = 6;
+        loadBlob = new Promise<void>(resolve => {
+          const img = new Image()
+          img.src = url
+          img.onload = () => {
+            ration = img.width / 2730
+            resolve()
+          }
+        }).then(() => addWatermark(url, point!.pos, ration))
+      } else {
+        loadBlob = fetch(url).then(res => res.blob())
+      }
+      return loadBlob
         .then((blob) => {
           imgFolder.file(`${point.sceneCode}-${point.uuid}.jpg`, blob);
         })

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

@@ -79,7 +79,7 @@ const getDrawVaring = (gl: WebGL2RenderingContext) => {
   };
 };
 
-export const init = (canvas: HTMLCanvasElement) => {
+export const init = (canvas: HTMLCanvasElement, initYaw: number) => {
   let activeTex: "skyCubeTex1" | "skyCubeTex" = "skyCubeTex1";
   const gl = canvas.getContext("webgl2", { preserveDrawingBuffer: true })!;
   const program = createProgram(gl, envVertSource, envFragSource);
@@ -126,7 +126,7 @@ export const init = (canvas: HTMLCanvasElement) => {
 
   setSize([canvas.width, canvas.height]);
   const fps = createFPSCamera(
-    canvas.parentElement!,
+    canvas!,
     (nViewMat) => {
       mat4.copy(viewMat, nViewMat);
       updateInv();
@@ -134,12 +134,16 @@ export const init = (canvas: HTMLCanvasElement) => {
     },
     [0, 1, 0],
     [0, 0, 0],
-    { yaw: glMatrix.toRadian(-180) },
+    { yaw: glMatrix.toRadian(initYaw) },
     80
   );
   return {
     setSize,
     redraw,
+    setYaw(yaw: number) {
+      fps.setYaw(yaw)
+      redraw()
+    },
     changeUrls(urls: string | string[]) {
       fps.recovery();
       return varing.preset(urls).then(() => {

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

@@ -1,5 +1,5 @@
 <template>
-  <div class="pano-layout" v-loading="loading">
+  <div class="pano-layout" v-loading="loading" :element-loading-text="loadingStr">
     <canvas ref="panoDomRef"></canvas>
     <div class="btns">
       <el-button
@@ -10,6 +10,14 @@
       >
         屏幕拍照
       </el-button>
+      <el-input-number
+        style="margin-right: 20px"
+        v-model="tempRadion"
+        :precision="2"
+        :step="0.01"
+        :min="1"
+        :max="3"
+      />
       <el-button
         size="large"
         style="margin-right: 20px; width: 100px"
@@ -42,7 +50,8 @@
 import SingleInput from "@/components/point-input.vue";
 import { router, setDocTitle } from "@/router";
 import { mergeFuns, round } from "@/util";
-import { computed, onMounted, onUnmounted, ref, watchEffect } from "vue";
+import { glMatrix } from "gl-matrix";
+import { computed, nextTick, onMounted, onUnmounted, ref, watchEffect } from "vue";
 import { init } from "./env";
 import {
   updateScenePointName,
@@ -56,12 +65,15 @@ import saveAs from "@/util/file-serve";
 import { DeviceType } from "@/store/device";
 import { initRelics, relics } from "@/store/relics";
 import { noValidPoint } from "../map/install";
+import { addWatermark } from "@/util/image";
 
 type Params = { pid?: string; relicsId?: string } | null;
 const params = computed(() => router.currentRoute.value.params as Params);
 const panoDomRef = ref<HTMLCanvasElement>();
 const destroyFns: (() => void)[] = [];
 const point = ref<ScenePoint>();
+const tempRadion = ref(3.0);
+
 watchEffect(() => {
   if (params.value?.pid) {
     const pid = Number(params.value!.pid);
@@ -85,6 +97,7 @@ const panoUrls = computed(() => {
 });
 const update = ref(false);
 const loading = ref(false);
+const loadingStr = ref("");
 
 const getGis = () => {
   const pos = point.value!.pos as number[];
@@ -160,12 +173,14 @@ const addWatermark = (imgURL: string, ration: number) => {
 
 const photo = async () => {
   loading.value = true;
+  loadingStr.value = "原图提取中";
   await new Promise((resolve) => setTimeout(resolve, 300));
-  const ration = 3;
+  const ration = tempRadion.value;
+  console.log("ration", ration);
   setSize(ration, 1920, 1080);
   let dataURL = panoDomRef.value.toDataURL("image/jpeg", 1);
   if (!noValidPoint(point.value)) {
-    dataURL = await addWatermark(dataURL, ration);
+    dataURL = await addWatermark(dataURL, point.value!.pos, ration);
   }
 
   await saveAs(dataURL, `${relics.value?.name}.jpg`);
@@ -184,7 +199,7 @@ const setSize = (ration: number, w?: number, h?: number) => {
 
 onMounted(() => {
   if (!panoDomRef.value) throw "没有canvas DOM";
-  pano = init(panoDomRef.value);
+  pano = init(panoDomRef.value, 0);
   const resizeHandler = () => {
     setSize(devicePixelRatio);
   };
@@ -196,6 +211,9 @@ onMounted(() => {
       if (panoUrls.value) {
         loading.value = true;
         pano.changeUrls(panoUrls.value).then(() => (loading.value = false));
+        pano.setYaw(
+          point.value.cameraType === DeviceType.CLUNT ? glMatrix.toRadian(180) : 0
+        );
       }
     }),
     pano.destory,

+ 57 - 14
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,8 +69,13 @@
     </div>
 
     <div class="relics-content">
-      <el-table :data="sceneArray" border row-key="'sceneCode'" @selection-change="handleTableSelect"
-        :ref="(d) => { tableProps && ((tableProps as any).tableRef.value = d) }">
+      <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)">
@@ -52,7 +84,7 @@
         </el-table-column>
 
         <el-table-column label="场景类型" v-slot:default="{ row }">
-          <TexToolTip :text="SceneTypeDesc[row.cameraType as SceneType]" />
+          <TexToolTip :text="SceneTypeDesc[row.sceneSource as SceneType]" />
         </el-table-column>
         <el-table-column label="场景码" v-slot:default="{ row }">
           <TexToolTip :text="row.sceneCode" />
@@ -92,8 +124,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>
@@ -101,9 +138,15 @@
       </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>

+ 1 - 1
vite.config.ts

@@ -43,7 +43,7 @@ export default ({ mode }: any) =>
           target: loadEnv(mode, process.cwd()).VITE_API,
           // target: `http://192.168.0.11:8324/relics/`,
           changeOrigin: true,
-          rewrite: (path) => path.replace(/^\/api/, "/api"),
+          rewrite: (path) => path.replace(/^\/api/, ""),
         },
         // "/api": {
         //   target: `https://uat-sp.4dkankan.com/`,