Pārlūkot izejas kodu

添加绘制关联

bill 2 gadi atpakaļ
vecāks
revīzija
1ec1d71da3

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "body-parser": "^1.20.2",
     "coordtransform": "^2.1.2",
     "driver.js": "^0.9.8",
+    "express-fileupload": "^1.4.0",
     "html2canvas": "^1.4.1",
     "js-base64": "^3.7.5",
     "jspdf": "^2.5.1",

+ 20 - 3
server/mock.ts

@@ -2,6 +2,7 @@ import express from "express";
 import path from "path";
 import bodyParser from 'body-parser'
 import * as fs from "fs";
+import fileUpload from 'express-fileupload'
 
 const staticDir = path.resolve(__dirname, "test");
 
@@ -14,7 +15,8 @@ export async function createServer(port: number) {
 
   const app = express();
   app.use(express.static(staticDir));
-  app.use(bodyParser())
+  app.use(bodyParser({ limit: '200mb' }))
+  app.use(fileUpload({createParentPath: true}))
   app.listen(port);
   startup = true;
 
@@ -25,11 +27,26 @@ export async function createServer(port: number) {
     const p = path.resolve(staticDir, scene, "./attach", filename)
 
 
-    console.log(p, req.body)
-    fs.writeFileSync(p, JSON.stringify(req.body))
+    if (req.url.includes("/upload")) {
+      return next()
+    } else {
+      fs.writeFileSync(p, JSON.stringify(req.body))
+    }
     res.json({code: 0, msg: 'ok'})
   })
 
+  app.post("/:sceneCode/upload", (req, res) => {
+    const file = (req as any).files.file
+    const relUrl = `/attach/upload/${file.name}`
+    const absUrl = path.resolve(staticDir, `./${req.params.sceneCode}/${relUrl}`)
+    file.mv(absUrl, err => {
+      if (err) {
+        res.json({code: 1, msg: 'ok'})
+      } else {
+        res.json({code: 0, msg: 'ok', data: relUrl})
+      }
+    })
+  })
 
   console.log("模拟环境已开启");
 }

+ 0 - 1
server/test/SS-t-P1d6CwREny2/attach/SS-t-P1d6CwREny2

@@ -1 +0,0 @@
-{}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 1
server/test/SS-t-P1d6CwREny2/attach/sceneStore


+ 1 - 1
src/components/base/assets/scss/editor/_head.scss

@@ -8,5 +8,5 @@
     left: 0;
     top: 0;
     width: 100%;
-    z-index: 1000;
+    z-index: 2;
 }

+ 4 - 1
src/components/base/components/gate/layer.vue

@@ -1,5 +1,6 @@
 <template>
   <div
+    ref="layerRef"
     class="ui-gate-layer"
     :style="{
       height: normalizeUnitToStyle(height),
@@ -14,7 +15,7 @@
 </template>
 
 <script setup>
-import { ref, computed, provide, watch } from "vue";
+import {ref, computed, provide, watch, reactive} from "vue";
 import { normalizeUnitToStyle } from "../../utils";
 import { Relation } from "./constant";
 
@@ -49,6 +50,8 @@ watch([contentInstances, slideIndex], () => {
 });
 
 provide(Relation, contentInstances);
+const layerRef = ref()
+defineExpose(reactive({dom: layerRef}))
 </script>
 
 <script>

+ 46 - 3
src/components/base/components/slide/index.vue

@@ -1,7 +1,7 @@
 <template>
     <div class="ui-slide" :class="{'stop-animation': stopAmimation}" v-if="items.length">
-        <Gate :index="extendIndex">
-            <GateContent v-for="(item, i) in extendItems">
+        <Gate :index="extendIndex" ref="gate">
+            <GateContent v-for="(item, i) in extendItems" @mousedown.stop.prevent="mousedownHandler">
                 <slot :raw="item" :active="items[index]" :index="getIndex(i)" />
             </GateContent>
         </Gate>
@@ -68,11 +68,54 @@ const extendItems = computed(() => {
 const index = computed(() => getIndex(extendIndex.value))
 
 watchEffect(() => {
-    console.log(props.currentIndex, extendLength.value)
     extendIndex.value = props.currentIndex + extendLength.value
 })
 
 const stopAmimation = ref(false)
+
+const gate = ref()
+const mousedownHandler = ev => {
+    stopAmimation.value = true
+    const width = gate.value.dom.offsetWidth
+    const dom = gate.value.dom.querySelector(".ui-gate-slides")
+    const startTime = new Date().getTime();
+    const style = window.getComputedStyle(dom, null);
+    const matrixStr = style.transform
+    const matrix = matrixStr.substring(matrixStr.indexOf('(') + 1, matrixStr.indexOf(')'))
+        .split(",")
+    const startX = Number(matrix[4]);
+    const start = {
+        x: ev.pageX,
+        y: ev.pageY
+    }
+    let move
+    const moveHandler = (ev) => {
+        move = {
+            x: ev.pageX - start.x,
+            y: ev.pageY - start.y
+        }
+        matrix[4] = startX + move.x
+        dom.style.transform = `matrix(${matrix.join(",")})`
+    }
+    const upHandler = ev => {
+        document.documentElement.removeEventListener("mousemove", moveHandler)
+        document.documentElement.removeEventListener("mouseup", upHandler)
+        stopAmimation.value = false
+        const endTime = new Date().getTime()
+        const isFast = endTime - startTime < 200
+        const limen = width / (isFast ? 8 : 3)
+
+        if (move.x < -limen) {
+            nextHandler()
+        } else if (move.x > limen) {
+            prevHandler()
+        }
+        dom.style.removeProperty("transform")
+    }
+    document.documentElement.addEventListener("mousemove", moveHandler)
+    document.documentElement.addEventListener("mouseup", upHandler)
+}
+
 let prevent = false
 const openPrevent = (fn) => {
     prevent = true

+ 81 - 0
src/components/fill-slide/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="fill-slide">
+    <div class="header">
+      <slot name="header" />
+      <ui-icon class="close" type="close" @click="$emit('quit')" ctrl />
+    </div>
+    <div class="slide-layout">
+      <ui-slide
+          :items="data"
+          :current-index="data.indexOf(active)"
+          @change="index => $emit('update:active', data[index])"
+          show-ctrl
+      >
+        <template v-slot="{raw}">
+          <img :src="getStaticFile(raw.url)" class="image" />
+        </template>
+      </ui-slide>
+    </div>
+    <div class="foot">
+      <slot name="foot" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import UiSlide from "@/components/base/components/slide/index.vue";
+import {getStaticFile} from "@/dbo/main";
+import {photos} from "@/store/photos";
+import UiIcon from "@/components/base/components/icon/index.vue";
+
+type Item = {url: string}
+
+defineProps<{ data: Item[], active: Item }>()
+defineEmits<{
+  (e: 'update:active', d: Item): void,
+  (e: 'quit'): void
+}>()
+</script>
+
+<style scoped lang="scss">
+.fill-slide {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: #000;
+  z-index: 3;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+
+.slide-layout {
+  width: 90%;
+  height: 80%;
+}
+
+.image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.header {
+  height: 10%;
+  width: 100%;
+  position: relative;
+}
+
+.close {
+  position: absolute;
+  right: 10px;
+  top: 10px;
+}
+.foot {
+  height: 10%;
+  width: 100%;
+}
+</style>

+ 4 - 13
src/components/main-panel/index.vue

@@ -1,18 +1,12 @@
 <template>
   <UiEditorLayout class="layout" :class="layoutClass">
     <UiEditorHead class="header">
-<!--      <div-->
-<!--        class="menu"-->
-<!--        :class="{ abs: isFull }"-->
-<!--        @click="customMap.sysView = isFull ? 'auto' : 'full'"-->
-<!--      >-->
-<!--        <ui-icon :type="isFull ? 'menu' : 'close'" ctrl />-->
-<!--      </div>-->
       <slot name="header" />
     </UiEditorHead>
     <slot/>
 
     <Menu
+      v-if="menus"
       :menu="menus"
       :active-key="activeMenuKey"
       @update:active-key="val => emit('update:activeMenuKey', val)"
@@ -23,17 +17,14 @@
 <script setup lang="ts">
 import UiEditorLayout from "@/components/base/editor/layout/index.vue";
 import UiEditorHead from "@/components/base/editor/layout/Head.vue";
-import UiIcon from "@/components/base/components/icon/index.vue";
 import Menu from '@/views/sys/menu'
 import {MenuAtom, MenuRaw} from "@/views/sys/menu/menu.js";
 import { customMap } from "@/hook/custom";
-import { computed, ref } from "vue";
-// import "@/preset/pc.scss"
-// import "@/preset/style.scss"
+import { computed } from "vue";
 
 const props = defineProps<{
-  menus: MenuRaw,
-  activeMenuKey: string,
+  menus?: MenuRaw,
+  activeMenuKey?: string,
 }>();
 const emit = defineEmits<{
   (e: 'update:activeMenuKey', t: MenuAtom['name']): void

+ 1 - 1
src/dbo/main.ts

@@ -9,6 +9,6 @@ const baseURL =
 
 instance.defaults.baseURL = baseURL
 
-console.log(baseURL)
+export const getStaticFile = (url: string) => baseURL + url
 
 export default instance

+ 1 - 0
src/graphic/CanvasStyle/default.js

@@ -86,6 +86,7 @@ const Circle = {
   strokeStyle: "red",
   fillStyle: "rgba(0,0,0,0)",
   lineWidth: 2,
+  radius: 30
 };
 
 const Measure = {

+ 3 - 3
src/graphic/Renderer/Draw.js

@@ -428,13 +428,12 @@ export default class Draw {
   drawCircle(element) {
     this.drawPoint({
       ...element,
-      ...element.center,
+      geoType: 'Circle'
     });
   }
 
   drawPoint(vector) {
-    // const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y });
-    const pt = vector
+    const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y });
     const ctx = this.context;
     const style = help.setVectorStyle(ctx, vector, vector.geoType || "Point");
     if (vector.color) {
@@ -483,6 +482,7 @@ export default class Draw {
   }
 
   drawLine(vector) {
+    console.log(vector)
     if (vector.category === "Arrow") {
       return this.drawArrow(vector);
     }

+ 6 - 2
src/hook/useGraphic.ts

@@ -1,4 +1,4 @@
-import {computed, reactive, ref, watch, watchEffect} from "vue";
+import {computed, reactive, Ref, ref, watch, watchEffect} from "vue";
 import { structureDraw } from "@/graphic";
 import VectorType from '@/graphic/enum/VectorType'
 import UIType from '@/graphic/enum/UIEvents'
@@ -11,9 +11,13 @@ const newsletter = ref<{
   focusVector?: { type: VectorTypeT[keyof VectorTypeT], vectorId: string }
 }>({ selectUI: null, focusVector: null });
 
-export const setCanvas = async (canvas: HTMLCanvasElement) => {
+export const setCanvas = async (canvas: HTMLCanvasElement, data: Ref<any>) => {
   await import("../graphic/index.js").then((model) => {
     drawRef.value = model.structureDraw(canvas, newsletter);
+
+    watchEffect(() => {
+      console.log(data.value)
+    })
   });
 };
 

+ 5 - 1
src/main.vue

@@ -1,3 +1,7 @@
 <template>
-  <router-view></router-view>
+  <router-view v-slot="{ Component }">
+    <keep-alive>
+      <component :is="Component" />
+    </keep-alive>
+  </router-view>
 </template>

+ 3 - 1
src/router/constant.ts

@@ -8,7 +8,8 @@ export const readyRouteName = {
   hotspot: "hotspot",
   measure: "measure",
   graphic: "graphic",
-  scene: "scene"
+  scene: "scene",
+  photos: "photos"
 } as const;
 
 export const writeRouteName = {
@@ -40,6 +41,7 @@ export const readyRouteMeta: RouteMetaRaw = {
   [readyRouteName.measure]: { title: ui18n.t("measure.name") },
   [readyRouteName.graphic]: { title: "绘图" },
   [readyRouteName.scene]: { title: "绘图" },
+  [readyRouteName.photos]: {title: "相册"}
 };
 
 export const writeRouteMeta: RouteMetaRaw<typeof modeFlags.LOGIN> = {

+ 7 - 15
src/router/info.ts

@@ -23,24 +23,10 @@ export type RouteAtom<
 
 export type RoutesRaw<T extends ModeFlag = any> = RouteAtom<T>[];
 
-export const baseAppRoute: RouteAtom = {
-  path: "/",
-  name: readyRouteName.layout,
-  meta: readyRouteMeta.layout,
-  component: () => import("@/preset/app.vue"),
-};
-
-export const readlyChildren: RoutesRaw = [];
-
-
-export const writeChildren: RoutesRaw<typeof modeFlags.LOGIN> = [
-  ...readlyChildren,
-];
 
 export const writeRoutesRaw: RoutesRaw<typeof modeFlags.LOGIN> = [
-  { ...baseAppRoute, children: writeChildren },
   {
-    path: "/graphic/:mode",
+    path: "/graphic/:mode/:action/:id",
     name: readyRouteName.graphic,
     meta: readyRouteMeta.graphic,
     component: () => import("@/views/graphic/index.vue"),
@@ -51,6 +37,12 @@ export const writeRoutesRaw: RoutesRaw<typeof modeFlags.LOGIN> = [
     meta: readyRouteMeta.scene,
     component: () => import("@/views/scene/index.vue"),
   },
+  {
+    path: "/photos",
+    name: readyRouteName.photos,
+    meta: readyRouteMeta.photos,
+    component: () => import("@/views/photos/index.vue"),
+  },
 ];
 
 export type RoutesRef<T extends ModeFlag = any> = ComputedRef<{

+ 11 - 0
src/store/accidentPhotos.ts

@@ -0,0 +1,11 @@
+import {ref} from "vue";
+
+export type AccidentPhoto = {
+  id: string
+  photoUrl: string
+  url: string
+  time: number,
+  data: any
+}
+
+export const accidentPhotos = ref<AccidentPhoto[]>([])

+ 9 - 0
src/store/photos.ts

@@ -0,0 +1,9 @@
+import {ref} from "vue";
+
+export type PhotoRaw = {
+  id: string
+  url: string
+  time: number,
+}
+
+export const photos = ref<PhotoRaw[]>([])

+ 11 - 0
src/store/roadPhotos.ts

@@ -0,0 +1,11 @@
+import {ref} from "vue";
+
+export type RoadPhoto = {
+  id: string
+  photoUrl: string
+  url: string
+  time: number,
+  data: any
+}
+
+export const roadPhotos = ref<RoadPhoto[]>([])

+ 22 - 2
src/store/sync.ts

@@ -3,7 +3,10 @@ import { list } from "@/store/measure";
 import {baseLines} from "@/store/baseLine";
 import {basePoints} from "@/store/basePoint";
 import {fixPoints} from "@/store/fixPoint";
-import {debounce} from '@/utils'
+import {photos} from "@/store/photos";
+import {accidentPhotos} from "@/store/accidentPhotos";
+import {roadPhotos} from "@/store/roadPhotos";
+import {debounce, getId} from '@/utils'
 import {watch} from "vue";
 
 axios.get("/attach/sceneStore")
@@ -13,6 +16,9 @@ axios.get("/attach/sceneStore")
       baseLines.value = data.data.baseLines || []
       basePoints.value = data.data.basePoints || []
       fixPoints.value = data.data.fixPoints || []
+      photos.value = data.data.photos || []
+      accidentPhotos.value = data.data.accidentPhotos || []
+      roadPhotos.value = data.data.roadPhotos || []
     }
 
     syncSceneStore()
@@ -22,13 +28,27 @@ export const updateSceneStore = debounce((data) => {
   axios.post("sceneStore", data)
 }, 1000)
 
+export const uploadImage = async (blob: Blob) => {
+  const file = new File([blob], `${getId()}.jpg`)
+  const res = await axios({
+    url: "/upload",
+    headers: { "Content-Type": "multipart/form-data" },
+    method: 'post',
+    data: { file }
+  });
+  return res.data.data
+}
+
 const syncSceneStore = () => {
   return watch(
     () => ({
       measures: list.value,
       baseLines: baseLines.value,
       basePoints: basePoints.value,
-      fixPoints: fixPoints.value
+      fixPoints: fixPoints.value,
+      photos: photos.value,
+      accidentPhotos: accidentPhotos.value,
+      roadPhotos: roadPhotos.value
     }),
     (data) => {
       updateSceneStore(data)

+ 5 - 0
src/utils/index.ts

@@ -525,3 +525,8 @@ export const base64ToBlob = (base64Data: string) => {
     type: fileType,
   });
 };
+
+
+export const getId = () => {
+  return (new Date()).getTime().toString() + Math.ceil(Math.random() * 1000).toString()
+}

+ 17 - 37
src/views/graphic/container.vue

@@ -7,53 +7,33 @@
 </template>
 
 <script setup lang="ts">
-import {onMounted, ref} from "vue";
+import {computed, onMounted, ref} from "vue";
 import {setCanvas} from "@/hook/useGraphic";
-import { draw } from '@/graphic/Renderer/Draw'
+import {router} from '@/router'
+import {Mode} from "@/views/graphic/menus";
+import {accidentPhotos} from "@/store/accidentPhotos";
+import {roadPhotos} from "@/store/roadPhotos";
+import { useData } from './data'
 
+const data = computed(() => {
+  const params = router.currentRoute.value.params
+  const mode = Number(params.mode) as Mode
+  if (mode === Mode.Photo) {
+    return accidentPhotos.value.find(data => data.id === params.id)
+  } else {
+    return roadPhotos.value.find(data => data.id === params.id)
+  }
+})
 const drawCanvasRef = ref<HTMLCanvasElement>();
 const setCanvasSize = () => {
   drawCanvasRef.value.width = drawCanvasRef.value.offsetWidth;
   drawCanvasRef.value.height = drawCanvasRef.value.offsetHeight;
 };
 
+
 onMounted(() => {
   setCanvasSize();
-  setCanvas(drawCanvasRef.value);
-  // setTimeout(() =>{
-  //   draw.drawArrow({
-  //     start: {x: 300, y: 100},
-  //     end: {x: 150, y: 200},
-  //     geoType: "Arrow",
-  //     color: 'blue'
-  //   })
-  //
-  //
-  //
-  //   draw.drawCircle({
-  //     geoType: 'Circle',
-  //     radius: 10,
-  //     color: 'blue',
-  //     center: {
-  //     x: 200,
-  //     y: 200
-  //     }
-  //   })
-  //
-  //   draw.drawMagnifier({
-  //     position: { x: 150, y: 200 },
-  //     popPosition: { x: 250, y: 300 },
-  //     geoType: "Magnifier"
-  //   })
-  //
-  //   draw.drawText({
-  //     geoType: 'Text',
-  //     color: 'red',
-  //     fontSize: 14,
-  //     value: "啊啊啊在",
-  //     center: { x: 200, y: 200}
-  //   })
-  // }, 1000)
+  setCanvas(drawCanvasRef.value, useData());
 });
 </script>
 

+ 35 - 0
src/views/graphic/data.ts

@@ -0,0 +1,35 @@
+import {computed} from "vue";
+import {Mode} from "@/views/graphic/menus";
+import {accidentPhotos} from "@/store/accidentPhotos";
+import {roadPhotos} from "@/store/roadPhotos";
+import {router} from '@/router'
+import {getId} from "@/utils";
+import {photos} from "@/store/photos";
+import {baseLines} from '@/store/baseLine'
+import {fixPoints} from '@/store/fixPoint'
+import {basePoints} from '@/store/basePoint'
+
+export const useData = () => computed(() => {
+  const params = router.currentRoute.value.params
+  if (params.action === 'add') {
+    return {
+      data: null,
+      sceneData: {
+        fixPoints: fixPoints.value,
+        baseLines: baseLines.value,
+        basePoints: basePoints.value
+      },
+      photoUrl: photos.value.find(data => data.id === params.id)?.url,
+      id: getId(),
+      url: null,
+      time: new Date().getTime()
+    }
+  } else {
+    const mode = Number(params.mode) as Mode
+    if (mode === Mode.Photo) {
+      return accidentPhotos.value.find(data => data.id === params.id)
+    } else {
+      return roadPhotos.value.find(data => data.id === params.id)
+    }
+  }
+})

+ 1 - 1
src/views/graphic/header.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="graphic-header">
     <div class="title">
-      <ui-icon type="close" />
+      <ui-icon type="close" @click="router.back" />
       <p>{{ mode === Mode.Road ? '现场绘图' : '事故照片' }}</p>
     </div>
 

+ 123 - 0
src/views/photos/index.vue

@@ -0,0 +1,123 @@
+<template>
+  <MainPanel>
+    <template v-slot:header>
+      照片管理
+    </template>
+
+    <div class="photos-layout">
+      <div class="photos">
+        <div v-for="photo in sortPhotos" :key="photo.id" class="photo" @click="active = photo">
+          <img :src="getStaticFile(photo.url)"  />
+        </div>
+      </div>
+
+      <ButtonPane class="back fun-ctrl" @click="router.push(writeRouteName.scene)">
+        <ui-icon type="close" class="icon" />
+      </ButtonPane>
+    </div>
+  </MainPanel>
+  <FillSlide :data="sortPhotos" v-model:active="active" @quit="active = null" v-if="active">
+    <template v-slot:header>
+      <div class="btns">
+        <ui-button width="100px" @click="gotoDraw(Mode.Road)" class="first-btn">
+          现场绘图
+        </ui-button>
+        <ui-button width="100px" @click="gotoDraw(Mode.Photo)">
+          事故照片
+        </ui-button>
+      </div>
+    </template>
+    <template v-slot:foot>
+      <div class="foot">
+        <ui-icon
+          type="close"
+          @click="delPhoto"
+          ctrl
+        />
+      </div>
+    </template>
+  </FillSlide>
+</template>
+
+<script setup lang="ts">
+import MainPanel from '@/components/main-panel/index.vue'
+import FillSlide from '@/components/fill-slide/index.vue'
+import {PhotoRaw, photos} from '@/store/photos'
+import {getStaticFile} from "@/dbo/main";
+import UiIcon from "@/components/base/components/icon/index.vue";
+import {router, writeRouteName} from '@/router'
+import ButtonPane from "@/components/button-pane/index.vue";
+import {computed, ref} from "vue";
+import {Mode} from '@/views/graphic/menus'
+import UiButton from "@/components/base/components/button/index.vue";
+import { accidentPhotos } from "@/store/accidentPhotos"
+import { roadPhotos } from "@/store/roadPhotos"
+import {getId} from "@/utils";
+
+const sortPhotos = computed(() => photos.value.reverse())
+const active = ref<PhotoRaw>()
+const delPhoto = () => {
+  const index = photos.value.indexOf(active.value)
+  if (~index) {
+    photos.value.splice(index, 1)
+  }
+}
+
+const gotoDraw = (mode: Mode) => {
+  router.push({ name: writeRouteName.graphic, params: {mode, id: active.value.id, action: 'add'} })
+}
+
+</script>
+
+<style scoped lang="scss">
+.photos-layout {
+  position: absolute;
+  top: calc(var(--header-top) + var(--editor-head-height));
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+.photos {
+  display: grid;
+  grid-gap: 10px;
+  padding: 10px;
+  //grid-template-rows: auto;
+  grid-template-columns: repeat(5, 1fr);
+}
+.photo img {
+  width: 100%;
+}
+
+
+.back {
+  right: var(--boundMargin);
+  bottom: var(--boundMargin);
+  color: #fff;
+  font-size: 20px;
+  transition: color .3s ease;
+
+  .icon {
+    position: absolute;
+    transform: translateX(-50%);
+  }
+}
+
+.btns {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  justify-content: center;
+
+  .first-btn {
+    margin-right: 10px;
+  }
+}
+
+.foot {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  height: 100%;
+}
+</style>

+ 1 - 1
src/views/scene/linkage/cover.ts

@@ -8,6 +8,6 @@ export const getCoverPos = (onComplete: (pos: Pos3D) => void) => {
   result.promise.then(onComplete)
   return () => {
     result.quit()
-    onComplete(null)
+    // onComplete(null)
   }
 }

+ 4 - 2
src/views/scene/menus/actions.ts

@@ -9,6 +9,7 @@ import {getCoverPos} from '../linkage/cover'
 import {Pos3D} from "@/sdk";
 import {fixPoints} from "@/store/fixPoint";
 import Message from "@/components/base/components/message/message.vue";
+import { getId } from '@/utils'
 
 const trackPosMenuAction = (onComplete: () => void, onAddStore: (pos: Pos3D) => void) => {
   customMap.magnifier = true
@@ -37,6 +38,7 @@ const trackMeasureMenuAction = (
   const hide = Message.success({ msg: "请选择一个位置单击,确定基准线的起点" })
   customMap.magnifier = true
   const onAddMeasure = (data: MeasureAtom) => {
+    data.id = getId();
     if (data) {
       onAddStore(data)
       onComplete()
@@ -63,7 +65,7 @@ const menuActions = {
         hide()
         onComplete()
       },
-      pos => basePoints.value.push({ id: "aa", pos })
+      pos => basePoints.value.push({ id: getId(), pos })
     )
   },
   [menuEnum.FIX_POINT]:  (_, onComplete) => {
@@ -73,7 +75,7 @@ const menuActions = {
         hide()
         onComplete()
       },
-      pos => fixPoints.value.push({ id: "aa", pos, text: "其他散落物" })
+      pos => fixPoints.value.push({ id: getId(), pos, text: "其他散落物" })
     )
   },
   [menuEnum.MEASURE_ROW]: (menu, onComplete) => {

+ 25 - 3
src/views/scene/photo.vue

@@ -6,18 +6,30 @@
       <ui-icon type="close" class="icon" />
     </ButtonPane>
 
-    <img src="/static/t.png" class="cover">
+    <img
+        :src="showCoverUrl"
+        class="cover"
+        :style="{opacity: showCoverUrl ? '1' : 0}"
+        @click="router.replace(writeRouteName.photos)"
+    >
   </div>
 </template>
 
 <script setup lang="ts">
 import UiIcon from "@/components/base/components/icon/index.vue";
 import ButtonPane from "@/components/button-pane/index.vue";
+import {photos} from '@/store/photos'
 import { useSDK } from '@/hook/useLaser'
 import {genUseLoading} from "@/hook";
-import {base64ToBlob} from "@/utils";
+import {base64ToBlob, getId} from "@/utils";
 import {nextTick, ref} from "vue";
+import {getStaticFile} from '@/dbo/main'
+import {uploadImage} from "@/store/sync";
+import {router, writeRouteName} from "@/router";
 
+const showCoverUrl = ref<string>(
+  photos.value[photos.value.length - 1]?.url && getStaticFile(photos.value[photos.value.length - 1]?.url)
+)
 const tempPhoto = ref<string>();
 const coverRef = ref<HTMLImageElement>()
 const photo = genUseLoading(async () => {
@@ -26,11 +38,21 @@ const photo = genUseLoading(async () => {
   const {dataUrl: base64} = await sdk.scene.screenshot(dom.offsetWidth, dom.offsetHeight)
   const blob = base64ToBlob(base64)
   tempPhoto.value = URL.createObjectURL(blob)
+
+  const upload = uploadImage(blob)
   await nextTick();
   const handler = () => {
     coverRef.value.removeEventListener("animationend", handler)
-
+    showCoverUrl.value = tempPhoto.value
     tempPhoto.value = null
+    upload.then(url => {
+      photos.value.push({
+        id: getId(),
+        url: url,
+        time: new Date().getTime()
+      })
+      showCoverUrl.value = getStaticFile(url)
+    })
   }
   coverRef.value.addEventListener("animationend", handler)
 })

+ 19 - 0
yarn.lock

@@ -483,6 +483,13 @@ btoa@^1.2.1:
   resolved "http://192.168.0.47:4873/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
   integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
 
+busboy@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+  integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+  dependencies:
+    streamsearch "^1.1.0"
+
 bytes@3.1.2:
   version "3.1.2"
   resolved "http://192.168.0.47:4873/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
@@ -666,6 +673,13 @@ etag@~1.8.1:
   resolved "http://192.168.0.47:4873/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
   integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
 
+express-fileupload@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.4.0.tgz#be9d70a881d6c2b1ce668df86e4f89ddbf238ec7"
+  integrity sha512-RjzLCHxkv3umDeZKeFeMg8w7qe0V09w3B7oGZprr/oO2H/ISCgNzuqzn7gV3HRWb37GjRk429CCpSLS2KNTqMQ==
+  dependencies:
+    busboy "^1.6.0"
+
 express@^4.18.2:
   version "4.18.2"
   resolved "http://192.168.0.47:4873/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
@@ -1209,6 +1223,11 @@ statuses@2.0.1:
   resolved "http://192.168.0.47:4873/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
   integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
 
+streamsearch@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+  integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
 supports-preserve-symlinks-flag@^1.0.0:
   version "1.0.0"
   resolved "http://192.168.0.47:4873/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"