浏览代码

Merge branch 'dev' of http://192.168.0.115:3000/bill/traffic-laser into dev

xzw 2 年之前
父节点
当前提交
1b257ae44d

+ 64 - 21
src/assets/public.scss

@@ -137,13 +137,40 @@ samp {
 
 :root.light {
   body {
-    --editor-head-back   : rgba(255, 255, 255, 1);
-    --editor-menu-back   : rgba(255, 255, 255, 1);
-    --colors-primary-fill: 0, 0, 0;
-    --editor-men-color   : rgb(0, 0, 0);
+    --colors-primary-base-fill: 23, 121, 237;
+    --editor-head-back        : rgba(255, 255, 255, 1);
+    --editor-menu-back        : rgba(255, 255, 255, 1);
+    --colors-primary-fill     : 0, 0, 0;
+    --editor-men-color        : rgb(0, 0, 0);
+
+    .edit-fix-point {
+      background-color: #fff;
+
+      .ui-input {
+        background: #F0F0F0 !important;
+
+        input {
+          color: #000 !important;
+        }
+      }
+
+      .header h3 {
+        color: #666;
+      }
+
+      .select span,
+      .select p {
+        color: #000;
+
+        &.active {
+          color     : rgba(23, 121, 237, 1);
+          background: #EFF0F2;
+        }
+      }
+    }
 
     .ui-input .range .range-locus .range-slide {
-      background-color: rgba(0, 0, 0, 1)
+      background-color: rgba(255, 255, 255, 1)
     }
 
     .ui-input {
@@ -168,12 +195,26 @@ samp {
     }
 
     .menu.border:after {
-      border-bottom-color: rgba(230, 230, 230, 1);
+      border-bottom-color: #ccc !important;
     }
 
+    .button-pane.type {
+      padding: 4px 18px !important;
 
-    .menu.active {
-      background-color: rgba(47, 143, 255, 0.50);
+      .menu {
+        max-width: 56px;
+      }
+
+      .menu.active {
+        color: rgba(23, 121, 237, 1) !important;
+      }
+    }
+
+    .button-pane {
+      .menu.active {
+        color           : rgba(23, 121, 237, 1) !important;
+        background-color: #EFF0F2;
+      }
     }
 
     .scene-mode-tabs {
@@ -213,10 +254,8 @@ samp {
     }
 
     .photo-select {
-      border: 1px solid #ccc;
-
       i {
-        color: #000;
+        color: #fff;
       }
     }
 
@@ -233,8 +272,9 @@ samp {
       }
 
       .foot .menus .menu {
-        background-color: rgb(22, 24, 26);
-        color           : #fff !important
+        background-color: #E6E9F0;
+        ;
+        color: rgba(22, 24, 26, 1) !important
       }
 
     }
@@ -263,8 +303,9 @@ samp {
 
 
     .save-file.save {
-      background-color: #fff !important;
-      color           : #000;
+      background: #E6E9F0 !important;
+      color     : #000;
+      border    : none;
     }
 
     .ui-dialog {
@@ -288,7 +329,7 @@ samp {
         color: #000;
 
         &:not(.active) {
-          background-color: rgba(0, 0, 0, 0.1);
+          // background-color: rgba(0, 0, 0, 0.1);
         }
       }
 
@@ -303,7 +344,8 @@ samp {
       background: #fff;
 
       .info-bottom>div {
-        background: rgba(0, 0, 0, 0.1);
+        background: rgba(0, 0, 0, 0);
+        border    : 1px solid #CCCCCC;
         color     : #000;
       }
 
@@ -311,15 +353,16 @@ samp {
         color: rgba(0, 0, 0, 0.8);
 
         .right-btn {
-          background: rgba(0, 0, 0, 0.1);
+          background: rgba(0, 0, 0, 0);
+          border    : 1px solid #CCCCCC;
 
         }
 
         .text-item .info-textarea,
         .input-item input {
-          color     : rgba(0, 0, 0, 0.8);
-          background: rgba(0, 0, 0, 0);
-          border    : 1px solid rgba(0, 0, 0, 0.5);
+          color     : rgba(0, 0, 0, 1);
+          background: rgba(240, 240, 240, 1);
+          border    : 1px solid #CCCCCC;
         }
 
       }

+ 19 - 19
src/components/base/assets/scss/components/_men-item.scss

@@ -1,28 +1,28 @@
-
-
 .ui-menu-item {
-  height: 100%;
-  width: 100%;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
+  height         : 100%;
+  width          : 100%;
+  cursor         : pointer;
+  display        : flex;
+  align-items    : center;
   justify-content: center;
-  flex-direction: column;
-  color: var(--editor-men-color);
-  transition: all .3s ease;
+  flex-direction : column;
+  color          : var(--editor-men-color);
+  transition     : all .3s ease;
 
-  span{
+  span {
     margin-top: 6px;
     //width: 68px;
-    overflow: hidden;
-    word-wrap: break-word;
+    overflow  : hidden;
+    word-wrap : break-word;
     text-align: center;
   }
-  &:hover{
-      //color: var( --color-main-hover);
+
+  &:hover {
+    //color: var( --color-main-hover);
   }
-  &.active{
-      color: var( --color-main-normal);
-      background-color: rgba(255,255,255,0.06);
+
+  &.active {
+    color           : var(--color-main-normal);
+    background-color: #EFF0F2;
   }
-}
+}

+ 28 - 15
src/components/button-pane/index.vue

@@ -1,34 +1,43 @@
 <template>
-  <div class="button-pane" :style="style" ref="pane">
+  <div class="button-pane" :style="style" ref="pane" :class="{ type: !!type, boxShadow }">
     <slot />
   </div>
 </template>
 
 <script setup lang="ts">
-import {computed, onMounted, ref} from "vue";
+import { computed, onMounted, ref } from "vue";
 
 const props = withDefaults(
-  defineProps<{ dire?: 'row' | 'column', size?: number, auto?: boolean }>(),
-  { dire: 'row', size: 64 }
+  defineProps<{
+    dire?: "row" | "column";
+    size?: number;
+    auto?: boolean;
+    type?: 1 | 0;
+    boxShadow?: boolean;
+  }>(),
+  { dire: "row", size: 64 }
 );
 
-const pane = ref<HTMLDivElement>()
-const height = ref<number>(1000)
-onMounted(() => height.value = props.dire === 'row' ? pane.value.offsetWidth : pane.value.offsetHeight)
+const pane = ref<HTMLDivElement>();
+const height = ref<number>(1000);
+onMounted(
+  () =>
+    (height.value =
+      props.dire === "row" ? pane.value.offsetWidth : pane.value.offsetHeight)
+);
 
 const style = computed(() => {
-  const isRow = props.dire === 'row';
-  const bound = isRow ? { height: `${props.size}px` } : { width: `${props.size}px` }
-  const psize = height.value > props.size ? "10px" : `${props.size / 2}px`
+  const isRow = props.dire === "row";
+  const bound = isRow ? { height: `${props.size}px` } : { width: `${props.size}px` };
+  const psize = height.value > props.size ? "10px" : `${props.size / 2}px`;
 
-  console.log(height.value)
   return {
     padding: isRow ? `4px ${psize}` : `${psize} 4px`,
-    borderRadius: `${props.size / 2}px`,
+    borderRadius: props.type === 1 ? `${props.size / 2}px` : `4px`,
     ...bound,
-    flexDirection: props.dire
-  }
-})
+    flexDirection: props.dire,
+  };
+});
 </script>
 
 <style lang="scss" scoped>
@@ -38,5 +47,9 @@ const style = computed(() => {
   background-color: var(--editor-menu-back);
   display: flex;
   align-items: center;
+
+  &.boxShadow {
+    box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
+  }
 }
 </style>

+ 19 - 3
src/components/group-button/index.vue

@@ -1,11 +1,18 @@
 <template>
-  <ButtonPane class="menus" :size="size" :dire="dire">
+  <ButtonPane
+    class="menus"
+    :size="size"
+    :dire="dire"
+    :type="type"
+    :class="{ single: menus.length === 1, boxShadow: !noBoxShadow }"
+  >
     <div
       v-for="menu in menus"
       :key="menu.key"
       class="menu"
       :style="menuStyle"
       :class="{
+        range: menu.range,
         active: activeKey === menu.key && !menu.switch,
         dire,
         disabled: disabledMap[menu.key],
@@ -52,7 +59,9 @@ const props = withDefaults(
     menus: any[];
     activeKey?: any;
     dire?: "row" | "column";
+    type?: 1 | 0;
     size?: number;
+    noBoxShadow?: boolean;
   }>(),
   { dire: "row", size: 64 }
 );
@@ -74,17 +83,20 @@ const menuStyle = computed(() => {
   const offset = props.size / 4;
   return props.dire === "row"
     ? {
-        padding: `0 10px`,
+        padding: props.type === 1 ? `0 18px` : `0 10px`,
         marginRight: "8px",
       }
     : {
-        padding: `10px 0 `,
+        padding: props.type === 1 ? `0 18px` : `10px 0 `,
         marginBottom: "8px",
       };
 });
 </script>
 
 <style lang="scss" scoped>
+.boxShadow {
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
+}
 .menu {
   &:first-child:last-child {
     position: absolute;
@@ -105,6 +117,10 @@ const menuStyle = computed(() => {
   align-items: center;
   justify-content: center;
 
+  &.range {
+    border: 1px solid #ccc;
+  }
+
   &.border {
     position: relative;
     &:after {

+ 7 - 1
src/components/photos/header.vue

@@ -14,7 +14,9 @@
       <template v-if="$slots.center">
         <slot name="center" :count="count" />
       </template>
-      <template v-else-if="count"> 已选择 {{ count }} 张 </template>
+      <template v-else-if="count">
+        已选择 <span class="count">{{ count }}</span> 张
+      </template>
     </span>
     <div class="right">
       <slot />
@@ -64,4 +66,8 @@ defineProps<{ count?: number; title: string; onBack?: () => void; type?: string
   align-items: center;
   justify-content: center;
 }
+.count {
+  color: var(--colors-primary-base);
+  padding: 0 5px;
+}
 </style>

+ 2 - 0
src/components/photos/index.vue

@@ -102,6 +102,7 @@ const changeSelects = (item: Item, selected: boolean) => {
   right: 16px;
   bottom: 16px;
   z-index: 1;
+  border: none;
 }
 
 .img-layout {
@@ -141,6 +142,7 @@ const changeSelects = (item: Item, selected: boolean) => {
 .photo-select.ui-input .checkbox input + .replace {
   background-color: #ccc;
   border-color: #fff;
+  border: none;
   border-radius: 50%;
 }
 .photo-select.ui-input .checkbox input + .replace.checked {

二进制
src/components/photos/undata.png


+ 2 - 2
src/components/photos/undata.vue

@@ -27,11 +27,11 @@
     margin-top: 10px;
     text-align: center;
     font-size: 14px;
-    color: #fff;
+    color: #000;
   }
 }
 </style>
 
 <script setup lang="ts">
-defineProps<{undataMsg?: string}>()
+defineProps<{ undataMsg?: string }>();
 </script>

+ 3 - 3
src/main.ts

@@ -28,8 +28,8 @@ app.use(Components);
 
 app.mount("#app");
 
-if (params.theme) {
-  document.documentElement.classList.add(params.theme);
-}
+// if (params.theme) {
+document.documentElement.classList.add(params.theme);
+// }
 
 export default app;

+ 4 - 1
src/sdk/types/sdk.ts

@@ -331,6 +331,7 @@ export type FixPoint3DArgs = {
   measure: boolean;
   graph?: Pos3D[];
   pos?: Pos3D;
+  basePoint?: Pos3D;
 };
 export type FixPoint3D = {
   // 销毁当前固定点对象,测量、形状一起删除
@@ -339,12 +340,14 @@ export type FixPoint3D = {
   quitMeasure(): void;
   // 当不会形状时 外面可以修改固定点位置, 通过这个函数通知3d对象
   changePos(data: Pos3D): void;
+  // 当不会形状时 外面可以修改固定点位置, 通过这个函数通知3d对象
+  changeBase(data: Pos3D): void;
   // 当正在绘制形状时,外面可以确定完成 或者取消, 确定完成时通过这个对象告知
   graphDrawComplete(): void;
 
   bus: Emitter<{
     // 测量线被选中对象,值为是否选中
-    selectMeasure: boolean;
+    selected: boolean;
     // 形状被选中事件,值为是否选中
     selectGraph: boolean;
     // 这个不用实现

+ 7 - 3
src/store/fixPoint.ts

@@ -14,11 +14,15 @@ type FixPointBase = {
   id: string;
   text: string;
   pos: Point;
-} & { measure: boolean; lines: Point[][] };
+} & {
+  measure: boolean;
+  lines: { points: Point[]; dis: number }[];
+  baseId?: string;
+};
 
-type PFixPoint = FixPointBase & { type: FixType.POINT };
+export type PFixPoint = FixPointBase & { type: FixType.POINT };
 
-type GFixPoint = FixPointBase & { type: FixType.GRAPH; points: Point[] };
+export type GFixPoint = FixPointBase & { type: FixType.GRAPH; points: Point[] };
 
 export type FixPoint = FixPointBase | PFixPoint | GFixPoint;
 

+ 3 - 4
src/views/accidents/index.vue

@@ -63,7 +63,7 @@
     v-if="active"
   >
     <template v-slot:foot>
-      <ActionMenus class="menus" :menus="menus" dire="row" />
+      <ActionMenus class="menus" :menus="menus" dire="row" no-box-shadow />
     </template>
     <template v-slot:topRight>
       <ui-icon type="del" @click="delPhoto(active)" />
@@ -84,8 +84,6 @@ import Photos from "@/components/photos/index.vue";
 import Header from "@/components/photos/header.vue";
 import { useConfirm } from "@/hook";
 import Undata from "@/components/photos/undata.vue";
-import { api } from "@/store/sync";
-import { photos } from "@/store/photos";
 
 const sortPhotos = computed(() => {
   const photos = [...accidentPhotos.value];
@@ -204,7 +202,7 @@ onDeactivated(() => {
   right: 0;
   bottom: 0;
   overflow-y: auto;
-  background: #2e2e2e;
+  background: #fff;
   padding: 25px 0;
 }
 
@@ -244,6 +242,7 @@ onDeactivated(() => {
 .type-title {
   padding: 0 24px 2px;
   font-size: 16px;
+  color: #666666;
 }
 
 .select-menus {

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

@@ -68,7 +68,7 @@ const clickHandler = (menu) => {
   }
 
   &.active {
-    background-color: rgba(255, 255, 255, 0.06);
+    background-color: rgba(239, 240, 242, 1);
   }
 
   .icon {

+ 15 - 8
src/views/graphic/confirm.vue

@@ -1,20 +1,27 @@
 <template>
   <div class="confirm">
-    <GraphicAction class="confirm-action" @click="() => drawRef.uiControl.confirmCancel()">
-      <ui-icon type="close"  color="#f84540" />
+    <GraphicAction
+      class="confirm-action"
+      @click="() => drawRef.uiControl.confirmCancel()"
+      :type="1"
+    >
+      <ui-icon type="close" color="#f84540" />
     </GraphicAction>
-    <GraphicAction class="confirm-action" @click="() => drawRef.uiControl.confirmEntry()">
-      <ui-icon type="affirm"  color="#38c43b" />
+    <GraphicAction
+      class="confirm-action"
+      @click="() => drawRef.uiControl.confirmEntry()"
+      :type="1"
+    >
+      <ui-icon type="affirm" color="#38c43b" />
     </GraphicAction>
   </div>
 </template>
 
 <script setup lang="ts">
-import {customMap} from "@/hook";
+import { customMap } from "@/hook";
 import GraphicAction from "@/components/button-pane/index.vue";
 import UiIcon from "@/components/base/components/icon/index.vue";
-import {uiType, drawRef} from '@/hook/useGraphic'
-
+import { uiType, drawRef } from "@/hook/useGraphic";
 </script>
 
 <style scoped lang="scss">
@@ -34,6 +41,6 @@ import {uiType, drawRef} from '@/hook/useGraphic'
   font-size: 22px;
   justify-content: center;
   background-color: #fff;
-  box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.5);
+  box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.5);
 }
 </style>

+ 1 - 1
src/views/graphic/geos/road.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="layout">
-    <GraphicAction class="full-action">
+    <GraphicAction class="full-action" box-shadow>
       <ui-icon type="lock" ctrl @click="clickHandlerFactory(VectorEvents.UnLock)()" />
     </GraphicAction>
     <GeoTeleport

+ 9 - 0
src/views/graphic/geos/roadEdge.vue

@@ -4,6 +4,7 @@
   <VRange
     v-if="showChange"
     :max="1000"
+    id="hei"
     :min="0"
     :step="1"
     unit="mm"
@@ -192,4 +193,12 @@ const active = computed(() => {
 .select-floating.select-float.dire-top {
   margin-top: -10px;
 }
+
+#hei {
+  --editor-menu-back: #000;
+
+  .fun-ctrl {
+    color: #fff !important;
+  }
+}
 </style>

+ 5 - 1
src/views/graphic/geos/text.vue

@@ -19,7 +19,6 @@
       </template>
     </template>
   </GeoTeleport>
-
   <div class="text-model" v-if="updateText">
     <div class="text-input">
       <ui-input
@@ -167,7 +166,12 @@ const menus = [
 }
 
 .text-input .ui-input .text input {
+  background-color: #fff;
   font-size: 16px;
   padding: 16px 21px;
+  color: #000;
+}
+.text-input .ui-input .text.suffix .len {
+  font-size: 14px;
 }
 </style>

+ 11 - 0
src/views/graphic/header.vue

@@ -377,3 +377,14 @@ const createTable = async () => {
   color: #fff;
 }
 </style>
+
+<style lang="scss">
+.table .ui-input .text.ready input {
+  color: #000;
+  border: 1px solid #000;
+
+  .retouch {
+    border: none;
+  }
+}
+</style>

+ 14 - 4
src/views/graphic/imageLabel.vue

@@ -4,9 +4,9 @@
       <ui-icon type="return" class="icon" ctrl @click="$emit('quit')" />
       <p>{{ config.title }}</p>
     </div>
-    <ui-input type="text" width="100%" v-model="keyword">
+    <ui-input type="text" width="100%" v-model="keyword" class="keySearch">
       <template v-slot:preIcon>
-        <ui-icon type="magnify_g" color="rgba(255,255,255,0.6)" />
+        <ui-icon type="magnify_g" color="rgba(0,0,0,0.6)" />
       </template>
     </ui-input>
 
@@ -34,7 +34,7 @@
     </template>
     <div v-else class="empty-images">
       <div>
-        <img src="@/assets/images/empty-label.png" />
+        <img src="@/components/photos/undata.png" />
         <p>无结果</p>
       </div>
     </div>
@@ -221,7 +221,17 @@ defineEmits<{ (e: "quit") }>();
   }
   div p {
     margin-top: 10px;
-    color: rgba(255, 255, 255, 1);
+    color: rgba(0, 0, 0, 1);
+  }
+}
+</style>
+
+<style lang="scss">
+.keySearch {
+  .text input {
+    background: #f0f0f0 !important;
+    border: none;
+    color: #000;
   }
 }
 </style>

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

@@ -20,7 +20,7 @@
       @quit="uiType.change(null)"
     />
 
-    <GraphicAction class="full-action" v-if="!graphicState.continuedMode">
+    <GraphicAction class="full-action" v-if="!graphicState.continuedMode" box-shadow>
       <ui-icon
         :type="isFull ? 'screen_c' : 'screen_f'"
         ctrl

+ 13 - 3
src/views/graphic/setting.vue

@@ -132,12 +132,13 @@ const handler = (label) => {
     text-align: center;
     height: 32px;
     line-height: 32px;
-    color: #fff;
+    color: #666666;
     font-size: 14px;
-    background: rgba(255, 255, 255, 0.1);
+    border: 1px solid #cccccc;
     border-radius: 4px;
     &.active {
-      background: rgba(47, 143, 255, 0.5);
+      border: 1px solid #1779ed;
+      color: #1779ed;
     }
   }
 }
@@ -146,3 +147,12 @@ const handler = (label) => {
   padding: 10px;
 }
 </style>
+
+<style lang="scss">
+.setting-layout {
+  .ui-input .text input {
+    background: #f0f0f0;
+    border: none;
+  }
+}
+</style>

+ 6 - 1
src/views/photos/index.vue

@@ -54,7 +54,7 @@
     :getURL="(data) => data.urlRaw || data.url"
   >
     <template v-slot:foot>
-      <ActionMenus class="menus" :menus="menus" dire="row" />
+      <ActionMenus class="menus" :menus="menus" dire="row" :noBoxShadow="true" />
     </template>
     <template v-slot:topRight>
       <ui-icon type="share" @click="sharePhoto" />
@@ -228,6 +228,7 @@ onActivated(() => {
   left: 50%;
   transform: translateX(-50%);
   bottom: var(--boundMargin);
+  box-shadow: 0 0 10px #ccc;
 }
 
 .back {
@@ -255,4 +256,8 @@ onActivated(() => {
   transform: translateX(-50%);
   bottom: var(--boundMargin);
 }
+
+.photo-select {
+  border-radius: 50% !important;
+}
 </style>

+ 1 - 1
src/views/roads/index.vue

@@ -75,7 +75,7 @@
     v-if="active"
   >
     <template v-slot:foot>
-      <ActionMenus class="menus" :menus="menus" dire="row" />
+      <ActionMenus class="menus" :menus="menus" dire="row" :no-box-shadow="true" />
     </template>
     <template v-slot:topRight>
       <ui-icon type="copy" @click="copyPhoto" />

+ 24 - 9
src/views/scene/container.vue

@@ -1,6 +1,8 @@
 <template>
   <div class="canvas-layout">
-    <p v-if="currentMeterPerPixel && viewStatus" class="meterPerPixel">1: {{ currentMeterPerPixel }}</p>
+    <p v-if="currentMeterPerPixel && viewStatus" class="meterPerPixel">
+      1: {{ currentMeterPerPixel }}
+    </p>
     <div class="scene-canvas" ref="sceneLayoutRef" />
   </div>
 </template>
@@ -53,7 +55,9 @@ onMounted(async () => {
   emit("loaded");
 
   sdk.scene.on("posChange", (pos) => {
-    currentMeterPerPixel.value = pos.meterPerPixel ? Math.round(1 / pos.meterPerPixel) : null;
+    currentMeterPerPixel.value = pos.meterPerPixel
+      ? Math.round(1 / pos.meterPerPixel)
+      : null;
   });
 
   if (!sceneSeting.value) {
@@ -77,7 +81,9 @@ onMounted(async () => {
   // 90
   setTimeout(() => {
     watchEffect(() => {
-      const doms = Array.from(sceneLayoutRef.value.querySelectorAll("#navCube, #home")) as HTMLElement[];
+      const doms = Array.from(
+        sceneLayoutRef.value.querySelectorAll("#navCube, #home")
+      ) as HTMLElement[];
       if (!disabledMap.mode) {
         if (customMap.mode === Mode.pano) {
           doms.forEach((dom) => {
@@ -118,17 +124,27 @@ onMounted(async () => {
 
 <style lang="scss">
 .canvas-layout {
-  .meterPerPixel,
   #navCube {
     position: absolute !important;
+    right: var(--boundMargin) !important;
+    top: auto !important;
+    bottom: var(--boundMargin) !important;
+  }
+
+  .meterPerPixel {
+    position: absolute !important;
     right: calc(var(--boundMargin) + 10px) !important;
-    top: calc(var(--boundMargin) + 10px) !important;
+    bottom: calc(var(--boundMargin) + 100px) !important;
     // z-index: 1;
   }
   #home {
     position: absolute !important;
-    right: var(--boundMargin) !important;
-    top: var(--boundMargin) !important;
+    // right: var(--boundMargin) !important;
+    // top: var(--boundMargin) !important;
+    right: calc(var(--boundMargin) + 80px) !important;
+    top: auto !important;
+    bottom: calc(var(--boundMargin) + 80px) !important;
+
     display: flex;
     align-items: center;
     justify-content: center;
@@ -155,10 +171,9 @@ onMounted(async () => {
   }
 
   .meterPerPixel {
-    text-align: center;
+    text-align: right;
     width: 100px;
     color: #fff;
-    margin-top: 100px;
     z-index: 999;
     pointer-events: none;
     font-weight: 400;

+ 1 - 1
src/views/scene/covers/fixPoint.vue

@@ -39,7 +39,7 @@ const fix3d = getFix3d(props.data);
 
 if (fix3d) {
   fix3d.bus.on("selectGraph", (select) => (select ? emit("focus") : emit("blur")));
-  fix3d.bus.on("selectMeasure", (select) =>
+  fix3d.bus.on("selected", (select) =>
     select ? emit("focusMeasure") : emit("blurMeasure")
   );
 }

+ 4 - 5
src/views/scene/covers/fixPoints.vue

@@ -16,11 +16,13 @@
     <ActionMenus
       v-if="customMap.activeFixPoint"
       :menus="activeActionMenus"
+      :type="1"
       :active-key="edit ? 'edit' : null"
       dire="row"
     />
     <ActionMenus
       v-if="ingActionMenus"
+      :type="1"
       :menus="ingActionMenus"
       :active-key="null"
       dire="row"
@@ -96,12 +98,9 @@ const activeActionMenus = computed(() =>
           color: "#FF4D4F",
           iconColor: "#fff",
           onClick() {
-            const index = fixPoints.value.indexOf(customMap.activeFixPoint);
-            if (~index) {
-              fixPoints.value.splice(index, 1);
-              edit.value = null;
+            if (delFix3d(customMap.activeFixPoint)) {
               customMap.activeFixPoint = null;
-              delFix3d(customMap.activeFixPoint);
+              edit.value = null;
             }
           },
         },

+ 126 - 10
src/views/scene/fixManage.ts

@@ -1,11 +1,77 @@
-import { useSDK } from "@/hook";
-import { FixPoint3D, FixPoint3DArgs } from "@/sdk";
-import { FixPoint, FixType } from "@/store/fixPoint";
-import { ref, shallowRef, watchEffect } from "vue";
+import { CustomCom, customMap, useAsyncSDK, useConfirm, useSDK } from "@/hook";
+import { Base, FixPoint3D, FixPoint3DArgs, SDK } from "@/sdk";
+import { baseLines } from "@/store/baseLine";
+import { BasePoint, basePoints } from "@/store/basePoint";
+import { FixPoint, FixType, fixPoints } from "@/store/fixPoint";
+import { Message } from "@kankan/components/expose-common";
+import { ref, nextTick, shallowRef, watch, watchEffect } from "vue";
+import { goto } from "./menus/data";
 
-const sdk = useSDK();
+let sdk: SDK;
+useAsyncSDK().then((a) => (sdk = a));
 const fix3ds: { [key in string]: FixPoint3D } = {};
 
+let basePointId: string;
+export const baseCheck = () => {
+  if (!measureMode.value) {
+    return true;
+  }
+  if (!baseLines.value.length) {
+    useConfirm({
+      title: "提示",
+      content: "请线绘制基准线",
+      okText: "去绘制",
+      noText: "取消",
+    }).then((ok) => {
+      if (ok) {
+        goto(["baseLineOrPoint", "baseLine"]);
+      }
+    });
+    return false;
+  }
+  if (!basePoints.value.length) {
+    useConfirm({
+      title: "提示",
+      content: "请线创建基准点",
+      okText: "创建基准点",
+      noText: "取消",
+    }).then((ok) => {
+      if (ok) {
+        goto(["baseLineOrPoint", "basePoint"]);
+      }
+    });
+    return false;
+  }
+
+  let hideTip;
+  let stopWatch;
+  return {
+    stop() {
+      hideTip && hideTip();
+      hideTip = null;
+      stopWatch && stopWatch();
+      stopWatch = null;
+    },
+    promise: new Promise<BasePoint>((resolve) => {
+      stopWatch = watchEffect(() => {
+        if (!customMap.activeBasePoint) {
+          hideTip = Message.success({ msg: "请选择一个基准点" });
+        } else {
+          resolve(customMap.activeBasePoint);
+          hideTip && hideTip();
+          hideTip = null;
+          nextTick(() => {
+            stopWatch && stopWatch();
+            stopWatch = null;
+          });
+        }
+      });
+    }).then((basePoint) => {
+      basePointId = basePoint.id;
+    }),
+  };
+};
+
 export enum DrawStatus {
   un,
   ing,
@@ -14,9 +80,6 @@ export enum DrawStatus {
 }
 export const drawstatus = ref(DrawStatus.un);
 export const measureMode = ref(false);
-watchEffect(() => {
-  console.error(measureMode.value);
-});
 const drawingFix3d = shallowRef<FixPoint3D[]>([]);
 
 watchEffect(() => {
@@ -28,13 +91,47 @@ watchEffect(() => {
     drawingFix3d.value = [];
   } else if (drawstatus.value === DrawStatus.quit) {
     drawingFix3d.value.forEach((fix3d) => {
-      fix3d.destroy();
       fix3d.bus.emit("graphDrawComplete", false);
+      fix3d.destroy();
     });
     drawingFix3d.value = [];
   }
 });
 
+// 固定点关联基准线 基准点
+const fixJoinBase = (data: FixPoint, fix3d: FixPoint3D) => {
+  const stopBaseExixtsWatch = watch(
+    () => ({
+      existsBasePoint: !basePoints.value.some(
+        (base) => base.id === data.baseId
+      ),
+      existsBaseLine: baseLines.value.length === 0,
+    }),
+    (args) => {
+      if (!args.existsBasePoint || !args.existsBaseLine) {
+        quitMeasure(data);
+        stopBaseExixtsWatch();
+      }
+    }
+  );
+
+  const stopBasePosWatch = watch(
+    () => {
+      const basePoint = basePoints.value.find(
+        (base) => base.id === data.baseId
+      );
+      return basePoint ? { ...basePoint.pos } : null;
+    },
+    (basePos) => {
+      if (!basePos) {
+        stopBasePosWatch();
+      } else {
+        fix3d.changeBase(basePos);
+      }
+    }
+  );
+};
+
 export const getFix3d = (data: FixPoint) => {
   if (fix3ds[data.id]) {
     return fix3ds[data.id];
@@ -53,6 +150,17 @@ export const getFix3d = (data: FixPoint) => {
   if (!args.measure && !args.graph) {
     return;
   }
+  if (args.measure) {
+    let basePoint: BasePoint;
+    let baseId = data.baseId || basePointId;
+    if (
+      !baseId ||
+      !(basePoint = basePoints.value.find((item) => item.id === baseId))
+    ) {
+      return;
+    }
+    args.basePoint = basePoint.pos;
+  }
 
   const fix3d = sdk.scene.createFixPoint(args);
   if (drawstatus.value === DrawStatus.ing) {
@@ -71,6 +179,7 @@ export const getFix3d = (data: FixPoint) => {
     });
   }
   fix3ds[data.id] = fix3d;
+  fixJoinBase(data, fix3d);
 };
 
 export const quitMeasure = (data: FixPoint) => {
@@ -94,9 +203,16 @@ export const delFix3d = (data) => {
   const fix3d = fix3ds[data.id];
   if (fix3d) {
     fix3d.destroy();
-    const index = drawingFix3d.value.indexOf(fix3d);
+    let index = drawingFix3d.value.indexOf(fix3d);
     if (~index) {
       drawingFix3d.value.splice(index, 1);
     }
+    index = fixPoints.value.indexOf(data);
+    if (~index) {
+      fixPoints.value.splice(index, 1);
+    }
+    return true;
+  } else {
+    return false;
   }
 };

+ 56 - 18
src/views/scene/index.vue

@@ -3,7 +3,13 @@
     <template v-slot:header>
       <div class="photos-header">
         <div class="left">
-          <ui-icon class="back-icon" type="return" ctrl style="margin-right: 10px" @click="back" />
+          <ui-icon
+            class="back-icon"
+            type="return"
+            ctrl
+            style="margin-right: 10px"
+            @click="back"
+          />
           <span> {{ sceneTitle }} </span>
         </div>
       </div>
@@ -14,36 +20,69 @@
         <div class="info-top-left" :class="{ full: viewStatus }">
           <Container @loaded="loaded = true" :viewStatus="viewStatus" />
           <template v-if="loaded && !trackMode">
-            <Menus v-if="viewStatus" @active="(data) => (activeMenuKeys = data)" @enter-child="childPage = true" @leave-child="childPage = false" />
+            <Menus
+              v-if="viewStatus"
+              @active="(data) => (activeMenuKeys = data)"
+              @enter-child="childPage = true"
+              @leave-child="childPage = false"
+            />
             <!-- v-if="currentView" -->
             <BasePoints />
             <FixPoints />
             <Measures />
-            <Photo />
-            <Range v-if="activeMenuKeys[0] === 'range'" :rangeKey="activeMenuKeys.slice(1).join(':')" />
+            <Photo :size="viewStatus ? 88 : 64" />
+            <Range
+              v-if="activeMenuKeys[0] === 'range'"
+              :rangeKey="activeMenuKeys.slice(1).join(':')"
+            />
             <!-- <ButtonPane class="back fun-ctrl" size="48" @click="router.push('/scene')" v-if="!childPage"> -->
-            <ButtonPane class="back fun-ctrl" :size="viewStatus ? 64 : 48" @click="onScale" v-if="!childPage">
+            <ButtonPane
+              class="back fun-ctrl"
+              :size="viewStatus ? 64 : 48"
+              @click="onScale"
+              v-if="!childPage"
+            >
               <ui-icon :type="viewStatus ? 'screen_c' : 'screen_f'" class="icon" />
             </ButtonPane>
-            <Mode />
+            <Mode :size="viewStatus ? 64 : 48" />
           </template>
         </div>
         <div class="info-top-right" :class="{ full: viewStatus }">
           <div class="input-item">
             <p>事故时间:</p>
-            <input id="accidentTime" type="text" v-model="sceneInfo.accidentTime" @input="inputHandler" />
+            <input
+              id="accidentTime"
+              type="text"
+              v-model="sceneInfo.accidentTime"
+              @input="inputHandler"
+            />
           </div>
           <div class="input-item">
             <p>天气:</p>
-            <input id="weather" type="text" v-model="sceneInfo.weather" @input="inputHandler" />
+            <input
+              id="weather"
+              type="text"
+              v-model="sceneInfo.weather"
+              @input="inputHandler"
+            />
           </div>
           <div class="input-item">
             <p>地点:</p>
-            <input id="address" type="text" v-model="sceneInfo.address" @input="inputHandler" />
+            <input
+              id="address"
+              type="text"
+              v-model="sceneInfo.address"
+              @input="inputHandler"
+            />
           </div>
           <div class="text-item">
             <p>事故描述:</p>
-            <textarea id="accidentDesc" class="info-textarea" v-model="sceneInfo.accidentDesc" @input="inputHandler"></textarea>
+            <textarea
+              id="accidentDesc"
+              class="info-textarea"
+              v-model="sceneInfo.accidentDesc"
+              @input="inputHandler"
+            ></textarea>
           </div>
           <div class="info-btn">
             <div
@@ -57,7 +96,9 @@
             >
               现场绘图({{ sceneSortPhotos.length }})
             </div>
-            <div class="right-btn" @click="router.push('/accidents?back=1')">事故照片({{ accodentSortPhotos.length }})</div>
+            <div class="right-btn" @click="router.push('/accidents?back=1')">
+              事故照片({{ accodentSortPhotos.length }})
+            </div>
           </div>
         </div>
       </div>
@@ -94,6 +135,7 @@ import { roadPhotos } from "@/store/roadPhotos";
 import { types, accidentPhotos } from "@/store/accidentPhotos";
 import { debounce, getQueryString } from "@/utils";
 import { tables } from "@/store/tables";
+import { goto } from "./menus/data";
 const layoutRef = ref(null);
 const activeMenuKeys = ref<string[]>([]);
 const accodentSortPhotos = computed(() => {
@@ -112,7 +154,9 @@ const enum TypeEnum {
 const currentType = ref(TypeEnum.Draw);
 const sceneSortPhotos = computed(() => roadPhotos.value);
 
-const sceneTitle = ref(getQueryString("title") ? decodeURIComponent(getQueryString("title")) : "案件");
+const sceneTitle = ref(
+  getQueryString("title") ? decodeURIComponent(getQueryString("title")) : "案件"
+);
 
 const loaded = ref(false);
 const childPage = ref(false);
@@ -258,9 +302,6 @@ onActivated(async () => {
         left: 0;
         z-index: 1000;
         transform-origin: center center;
-        .scene-mode-tabs {
-          height: 64px !important;
-        }
 
         .fun-ctrl {
           .iconfont {
@@ -271,9 +312,6 @@ onActivated(async () => {
       .hide {
         display: none !important;
       }
-      .scene-mode-tabs {
-        // height: 48px !important;
-      }
       .fun-ctrl {
         position: absolute;
         left: var(--boundMargin);

+ 89 - 49
src/views/scene/menus/actions.ts

@@ -7,7 +7,7 @@ import {
 import { list, MeasureAtom, MeasureType } from "@/store/measure";
 import { baseLines } from "@/store/baseLine";
 import { basePoints } from "@/store/basePoint";
-import { nextTick, Ref, watch } from "vue";
+import { nextTick, reactive, Ref, watch } from "vue";
 import {
   activeBasePointStack,
   activeFixPointStack,
@@ -21,6 +21,7 @@ import { FixPoint, fixPoints, FixType } from "@/store/fixPoint";
 import Message from "@/components/base/components/message/message.vue";
 import { getId } from "@/utils";
 import {
+  baseCheck,
   delFix3d,
   drawstatus,
   DrawStatus,
@@ -132,64 +133,103 @@ const menuActions = {
     };
   },
   [menuEnum.FIX_POINT]: (_, onComplete) => {
-    let hide = Message.success({ msg: "请单击选择固定点位置" });
-    const onDestroy = trackPosMenuAction(
-      () => {
-        hide && hide();
+    const add = () => {
+      hide = Message.success({ msg: "请单击选择固定点位置" });
+      onDestroy = trackPosMenuAction(
+        () => {
+          hide && hide();
+          onComplete();
+        },
+        (pos) => {
+          const len = fixPoints.value.push({
+            id: getId(),
+            pos,
+            text: "固定点",
+            type: FixType.POINT,
+            measure: measureMode.value,
+            lines: undefined,
+          });
+          activeFixPointStack.current.value.value = fixPoints.value[
+            len - 1
+          ] as FixPoint;
+          if (hide) {
+            hide();
+            hide = null;
+          }
+        },
+        false
+      );
+    };
+
+    let onDestroy;
+    let hide;
+    let stop;
+    const result = baseCheck();
+    if (typeof result === "boolean") {
+      if (result) {
+        add();
+      } else {
         onComplete();
-      },
-      (pos) => {
-        const len = fixPoints.value.push({
-          id: getId(),
-          pos,
-          text: "固定点",
-          type: FixType.POINT,
-          measure: measureMode.value,
-          lines: undefined,
-        });
-        activeFixPointStack.current.value.value = fixPoints.value[
-          len - 1
-        ] as FixPoint;
-        if (hide) {
-          hide();
-          hide = null;
-        }
-      },
-      false
-    );
+      }
+    } else {
+      stop = result.stop;
+      result.promise.then(() => {
+        add();
+      });
+    }
     return () => {
-      onDestroy();
+      console.log("?????");
+      onDestroy && onDestroy();
       hide && hide();
+      stop && stop();
     };
   },
   [menuEnum.FIX_GRANH]: (_, onComplete) => {
-    let hide = Message.success({ msg: "请单击绘制固定点形状" });
-    const data: FixPoint = {
-      id: getId(),
-      pos: { x: 0, y: 0, z: 0 },
-      text: "形状",
-      type: FixType.GRAPH,
-      measure: measureMode.value,
-      lines: undefined,
+    let hide;
+    const add = () => {
+      hide = Message.success({ msg: "请单击绘制固定点形状" });
+      const data: FixPoint = reactive({
+        id: getId(),
+        pos: { x: 0, y: 0, z: 0 },
+        text: "形状",
+        type: FixType.GRAPH,
+        measure: measureMode.value,
+        lines: undefined,
+      });
+      drawstatus.value = DrawStatus.ing;
+      getFix3d(data).bus.on("graphDrawComplete", (complete) => {
+        if (complete) {
+          const len = fixPoints.value.push(data);
+          activeFixPointStack.current.value.value = fixPoints.value[
+            len - 1
+          ] as FixPoint;
+        }
+        if (hide) {
+          hide();
+          hide = null;
+        }
+        onComplete();
+      });
     };
-    drawstatus.value = DrawStatus.ing;
-    getFix3d(data).bus.on("graphDrawComplete", (complete) => {
-      if (complete) {
-        const len = fixPoints.value.push(data);
-        activeFixPointStack.current.value.value = fixPoints.value[
-          len - 1
-        ] as FixPoint;
-      }
-      if (hide) {
-        hide();
-        hide = null;
-      }
-      onComplete();
-    });
 
+    let stop;
+    const result = baseCheck();
+    if (typeof result === "boolean") {
+      if (result) {
+        add();
+      } else {
+        onComplete();
+      }
+    } else {
+      stop = result.stop;
+      result.promise.then(() => {
+        add();
+      });
+    }
     return () => {
       drawstatus.value = DrawStatus.quit;
       hide && hide();
+      stop && stop();
     };
   },
   // [menuEnum.FIX_MEASURE]: (_, onComplete) => {
@@ -263,7 +303,7 @@ const menuActions = {
 export const joinActions = (activeKey: Ref<string>) => {
   return watch(
     () => activeKey.value,
-    (key, oldKey, onCleanup) => {
+    async (key, oldKey, onCleanup) => {
       if (key && menuActions[key]) {
         const menu = findMenuByKey(key as any);
         const cleanup = menuActions[key](menu, () => {

+ 45 - 0
src/views/scene/menus/data.ts

@@ -0,0 +1,45 @@
+import { MenuRaw, generateMixMenus } from "./menus";
+
+const stores = [];
+export const createStore = (data: MenuRaw[]) => {
+  const store = generateMixMenus("children", (m) => m, data);
+  stores.push(store);
+  return {
+    store,
+    destroy() {
+      let index = stores.indexOf(store);
+      if (~index) {
+        stores.splice(index, 1);
+      }
+    },
+  };
+};
+
+export const goto = (local: string[], localStores = stores) => {
+  for (let i = 0; i < localStores.length; i++) {
+    const store = localStores[i];
+    for (const menu of store.menus) {
+      if (menu.key === local[0]) {
+        store.child.value = null;
+        store.child.itemActiveKey = null;
+        setTimeout(() => {
+          menu.onClick();
+
+          if (
+            "defaultSelect" in menu &&
+            (menu.defaultSelect || menu.defaultSelect())
+          ) {
+            setTimeout(() => {
+              menu.onClick();
+            });
+          }
+
+          if (local.length > 1) {
+            setTimeout(() => goto(local.slice(1)), 10);
+          }
+        });
+        return;
+      }
+    }
+  }
+};

+ 7 - 4
src/views/scene/menus/pane.vue

@@ -23,8 +23,8 @@ import ActionMenus from "@/components/group-button/index.vue";
 import { generateMixMenus, MenuRaw, menus, findMenuByKey } from "./menus";
 import { joinActions } from "./actions";
 import { computed, onMounted, onUnmounted, ref, watchEffect } from "vue";
-import { disabledMap, laserModeStack } from "@/hook";
-import { Mode } from "@/sdk";
+import { disabledMap } from "@/hook";
+import { createStore } from "./data";
 
 const props = withDefaults(
   defineProps<{ menus?: MenuRaw[]; level?: number; parentKey?: string }>(),
@@ -40,13 +40,16 @@ const backMenu = {
   icon: "return",
   text: "",
   key: "back",
+  range: true,
   onClick: () => emit("back"),
 };
 
 const menusMix = computed(() => (props.level === 1 ? menus : [backMenu, ...props.menus]));
-const store = generateMixMenus("children", (m) => m, menusMix.value);
+const { store, destroy } = createStore(menusMix.value);
 const childActive = ref<string[]>([]);
 
+onUnmounted(destroy);
+
 watchEffect(() => {
   const currentActive = store.activeMenuKey.value || store.itemActiveKey.value;
   if (currentActive) {
@@ -143,7 +146,7 @@ export default { name: "scene-menus" };
     left: 10px;
     right: 10px;
     bottom: -15px;
-    background-color: rgba(255, 255, 255, 0.2);
+    background-color: #ccc;
   }
 }
 </style>

+ 9 - 4
src/views/scene/mode.vue

@@ -2,6 +2,8 @@
   <GroupButton
     class="scene-mode-tabs"
     :menus="menus"
+    :size="size"
+    :style="{ width: `${size}px`, height: `${size}px` }"
     :active-key="customMap.mode"
     v-if="!disabledMap.mode"
   />
@@ -14,6 +16,8 @@ import { computed, ref, watch, watchEffect } from "vue";
 import { customMap, disabledMap } from "@/hook/custom/index";
 import { params } from "@/hook";
 
+defineProps<{ size: number }>();
+
 const tabs = [
   {
     mode: Mode.pano,
@@ -48,8 +52,8 @@ watch(
 
 <style lang="scss" scoped>
 .scene-mode-tabs {
-  left: var(--boundMargin);
-  bottom: var(--boundMargin);
+  right: var(--boundMargin);
+  top: var(--boundMargin);
 }
 </style>
 
@@ -57,9 +61,10 @@ watch(
 .scene-mode-tabs {
   padding: 0 !important;
   .menu {
-    width: 64px;
+    min-width: 100%;
+    padding: 0 !important;
     margin-right: 0 !important;
-    border-radius: 32px;
+    border-radius: 4px;
     display: none;
 
     &.active {

+ 65 - 39
src/views/scene/photo.vue

@@ -1,15 +1,19 @@
 <template>
   <img :src="tempPhoto" class="face-animation" v-if="tempPhoto" ref="coverRef" />
   <div class="photo-layout" v-if="disabledMap.photo">
-    <ButtonPane class="photo-btn fun-ctrl" :size="80" @click="photo">
-      <ui-icon type="photo" class="icon" />
+    <ButtonPane class="photo-btn fun-ctrl" :size="size" @click="photo" box-shadow>
+      <ui-icon type="photo" class="icon" :size="size / 2" />
     </ButtonPane>
 
     <img
       v-if="showCoverUrl"
       :src="showCoverUrl.value"
       class="cover"
-      :style="{ opacity: showCoverUrl ? '1' : 0 }"
+      :style="{
+        opacity: showCoverUrl ? '1' : 0,
+        width: `${size - 24}px`,
+        height: `${size - 24}px`,
+      }"
       @click="router.push(writeRouteName.photos)"
     />
   </div>
@@ -19,7 +23,7 @@
 import UiIcon from "@/components/base/components/icon/index.vue";
 import ButtonPane from "@/components/button-pane/index.vue";
 import { list } from "@/store/measure";
-import { fixPoints } from "@/store/fixPoint";
+import { FixType, GFixPoint, fixPoints } from "@/store/fixPoint";
 import { baseLines } from "@/store/baseLine";
 import { basePoints } from "@/store/basePoint";
 import { photos } from "@/store/photos";
@@ -31,10 +35,12 @@ import { base64ToBlob, formatDate, getId } from "@/utils";
 import { computed, nextTick, ref, watchEffect } from "vue";
 import { api, downloadImage, uploadImage } from "@/store/sync";
 import { router, writeRouteName } from "@/router";
-import { LaserSDK, Pos, Pos3D } from "@/sdk";
+import { LaserSDK, Pos, Pos3D, TypeEmu } from "@/sdk";
 import { useStaticUrl } from "@/hook/useStaticUrl";
 import { Loading } from "@kankan/components/index";
 import { generateMixMenus, MenuRaw, menus, findMenuByKey } from "./menus/menus";
+
+defineProps<{ size: number }>();
 const menusMix = computed(() => menus);
 const store = generateMixMenus("children", (m) => m, menusMix.value);
 const showCoverUrl = computed(() => {
@@ -114,40 +120,61 @@ const photo = async () => {
   tempPhoto.value = await api.getFile(data.rawUrl);
   console.log("获取到临时文件");
   await nextTick();
+  return new Promise((resolve, reject) => {
+    const handler = async () => {
+      coverRef.value.removeEventListener("animationend", handler);
+      tempPhoto.value = null;
 
-  const handler = async () => {
-    coverRef.value.removeEventListener("animationend", handler);
-    tempPhoto.value = null;
-    const photoData = {
-      id: getId(),
-      url: data.url,
-      urlRaw: data.rawUrl,
-      time: new Date().getTime(),
-      meterPerPixel: data.meterPerPixel,
-      measures: list.value
-        .map((data) => {
-          const pos = getCurrentScreens(data.points);
-          if (pos.length) {
-            return { pos, dis: sdk.carry.measureMap.get(data).getDistance().value };
-          } else {
-            return null;
-          }
+      const fixMeasures = fixPoints.value
+        .filter((fix) => fix.measure && fix.lines)
+        .map((fix) => {
+          return fix.lines.reduce(
+            (t, line) => t.concat({ dis: line.dis, pos: getCurrentScreens(line.points) }),
+            [] as { pos: Pos[]; dis: number }[]
+          );
         })
-        .filter((poss) => poss?.pos.length === 2),
-      baseLines: baseLines.value
-        .map((data) => getCurrentScreens(data.points))
-        .filter((poss) => poss.length === 2),
-      fixPoints: fixPoints.value
-        .map((data) => ({ text: data.text, pos: getCurrentScreen(data.pos) }))
-        .filter((data) => !!data.pos),
-      basePoints: getCurrentScreens(basePoints.value.map((data) => data.pos)),
+        .flat();
+      const fixGraph = fixPoints.value
+        .filter((fix) => "type" in fix && fix.type === FixType.GRAPH)
+        .map((fix) => getCurrentScreens((fix as GFixPoint).points))
+        .filter((points) => points.length > 1);
+
+      const photoData = {
+        id: getId(),
+        url: data.url,
+        urlRaw: data.rawUrl,
+        time: new Date().getTime(),
+        meterPerPixel: data.meterPerPixel,
+        measures: list.value
+          .map((data) => {
+            const pos = getCurrentScreens(data.points);
+            if (pos.length) {
+              return { pos, dis: sdk.carry.measureMap.get(data).getDistance().value };
+            } else {
+              return null;
+            }
+          })
+          .concat(fixMeasures)
+          .filter((poss) => poss?.pos.length === 2),
+        fixGraph,
+        baseLines: baseLines.value
+          .map((data) => getCurrentScreens(data.points))
+          .filter((poss) => poss.length === 2),
+        fixPoints: fixPoints.value
+          .map((data) => ({ text: data.text, pos: getCurrentScreen(data.pos) }))
+          .filter((data) => !!data.pos),
+        basePoints: getCurrentScreens(basePoints.value.map((data) => data.pos)),
+      };
+      console.log(photoData);
+      photos.value.push(photoData);
+      Loading.hide();
+      resolve(photoData);
     };
-    console.log(photoData);
-    photos.value.push(photoData);
-    Loading.hide();
-  };
-  coverRef.value.addEventListener("animationend", handler);
+    coverRef.value.addEventListener("animationend", handler);
+  });
 };
+
+(window as any).photo = photo;
 </script>
 
 <style scoped lang="scss">
@@ -170,18 +197,17 @@ const photo = async () => {
 .photo-btn {
   position: static;
   margin-bottom: 16px;
+  border-radius: 50% !important;
 
   .icon {
-    font-size: 28px;
+    font-size: 40px;
   }
 }
 
 .cover {
-  width: 48px;
-  height: 48px;
   border: 1px solid #fff;
   object-fit: cover;
-  border-radius: 24px;
+  border-radius: 50%;
   overflow: hidden;
 }
 .face-animation {