Bläddra i källkod

feat: 需求更改

bill 1 år sedan
förälder
incheckning
c880035caa
8 ändrade filer med 422 tillägg och 249 borttagningar
  1. 2 0
      src/api/guide.ts
  2. 7 0
      src/api/sys.ts
  3. 46 14
      src/model/app.vue
  4. 182 102
      src/sdk/association.ts
  5. 7 0
      src/store/guide.ts
  6. 24 4
      src/views/fire/index.vue
  7. 135 123
      src/views/guide/edit-paths.vue
  8. 19 6
      src/views/guide/sign.vue

+ 2 - 0
src/api/guide.ts

@@ -11,12 +11,14 @@ interface ServiceGuide {
   fusionGuideId: number
   cover: string
   title: string
+  recoveryContent?: string
 }
 
 export interface Guide {
   id: string
   cover: string
   title: string
+  recoveryContent?: string
 }
 
 export type Guides = Guide[]

+ 7 - 0
src/api/sys.ts

@@ -61,6 +61,13 @@ export type FireProject = {
   field2: string;
   field3: string;
   field4: string;
+
+  field5: string;
+  field6: string;
+  field7: string;
+  field8: string;
+  field9: string;
+  field10: string;
 };
 
 export interface Case {

+ 46 - 14
src/model/app.vue

@@ -1,9 +1,16 @@
 <template>
   <iframe class="external" :src="url" ref="iframeRef" v-if="url"></iframe>
   <div class="laser-layer" v-show="!url">
-    <div class="scene-canvas" ref="fuseRef">
+    <div class="scene-canvas" ref="fuseRef" :class="{ full: full === 'scene' }">
+      <span class="taggle switch" v-if="full !== 'scene'" @click="full = 'scene'">
+        <ui-icon type="f-l" ctrl />
+      </span>
       <div id="direction"></div>
-      <div id="scene-map"></div>
+    </div>
+    <div id="scene-map" :class="{ full: full === 'map' }">
+      <span class="taggle switch" v-if="full === 'scene'" @click="full = 'map'">
+        <ui-icon type="f-l" ctrl />
+      </span>
     </div>
   </div>
 </template>
@@ -144,6 +151,7 @@ export const Model = defineComponent({
 
     return {
       iframeRef,
+      full: ref("scene"),
       fuseRef,
       url,
     };
@@ -161,12 +169,6 @@ export default Model;
   top: 0;
   width: 100%;
   height: 100%;
-
-  .scene-canvas {
-    width: 100%;
-    height: 100%;
-    background-color: #ccc;
-  }
 }
 
 .external {
@@ -185,14 +187,44 @@ export default Model;
   margin: 10px;
   transition: top 0.3s ease, right 0.3s ease;
 }
+.scene-canvas,
+#scene-map {
+  &:not(.full) {
+    position: absolute !important;
+    right: calc(var(--editor-menu-right) + var(--editor-toolbox-width)) !important;
+    bottom: 0;
+    width: 320px;
+    height: 200px;
+    z-index: 99;
+  }
+
+  &.full {
+    width: 100%;
+    height: 100%;
+    background-color: #ccc;
+    z-index: 90;
+  }
+}
 
 #scene-map {
-  position: absolute;
-  right: calc(var(--editor-menu-right) + var(--editor-toolbox-width)) !important;
-  bottom: 0;
-  width: 320px;
   display: none;
-  height: 200px;
-  z-index: 99;
+}
+
+.taggle {
+  position: absolute;
+  font-size: 16px;
+  color: #fff;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 9999999;
+  width: var(--taggle-btn-width);
+  height: var(--taggle-btn-width);
+  display: flex;
+  align-items: center;
+  left: 10px;
+  top: 10px;
+  overflow: hidden;
+  justify-content: center;
+  border-radius: 3px;
+  cursor: pointer;
 }
 </style>

+ 182 - 102
src/sdk/association.ts

@@ -15,7 +15,8 @@ import {
   hideLoad,
   deepIsRevise,
   round,
-  togetherCallback
+  togetherCallback,
+  asyncTimeout
 } from '@/utils'
 import { 
   dynamicAddedModelIds,
@@ -31,13 +32,14 @@ import {
   getMeasureIsShow,
   SceneStatus,
   setting,
-  caseProject
+  caseProject,
+  getGuidePaths
 } from '@/store'
 import { currentLayout, RoutesName } from '@/router'
 
 import TaggingComponent from '@/components/tagging/list.vue'
 
-import type { FuseModel, Tagging, Measure } from '@/store'
+import type { FuseModel, Tagging, Measure, FuseModels, Guide } from '@/store'
 import type { 
   SDK, 
   SceneModel, 
@@ -66,109 +68,113 @@ export const modelRange: ModelAttrRange  = {
 const sceneModelMap = reactive(new WeakMap<FuseModel, SceneModel>())
 export const getSceneModel = (model?: FuseModel | null) => model && sceneModelMap.get(toRaw(model))
 
-const associationModels = (sdk: SDK) => {
-  const getModels = () => fuseModels.value
-    .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value)
-  
-  shallowWatchArray(getModels, (models, oldModels) => {
-    const { added, deleted } = diffArrayChange(models, oldModels)
-    for (const item of added) {
-      if (getSceneModel(item)) {
-        continue;
-      }
+const setModels = (models: FuseModels, oldModels: FuseModels) => {
+  const { added, deleted } = diffArrayChange(models, oldModels)
+  for (const item of added) {
+    if (getSceneModel(item)) {
+      continue;
+    }
 
-      if (item.status !== SceneStatus.SUCCESS) {
-        item.error = true
-        item.loaded = true
-        continue;
-      }
+    if (item.status !== SceneStatus.SUCCESS) {
+      item.error = true
+      item.loaded = true
+      continue;
+    }
 
-      const itemRaw = toRaw(item)
-      let sceneModel: SceneModel
-      try {
-        sceneModel = sdk.addModel({
-          ...itemRaw,
-          ...modelRange,
-          mode: RoutesName.signModel === currentLayout.value! ? 'single' : 'many',
-          isDynamicAdded: dynamicAddedModelIds.value.some(id => itemRaw.id === id),
-          type: [SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? 'laser' : item.modelType,
-          url: [SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? item.url : item.url && getResource(item.url)
-        })
-      } catch(e) {
-        console.error('模型加载失败', e)
-        item.error = true
-        return;
-      }
+    const itemRaw = toRaw(item)
+    let sceneModel: SceneModel
+    try {
+      sceneModel = sdk.addModel({
+        ...itemRaw,
+        ...modelRange,
+        mode: RoutesName.signModel === currentLayout.value! ? 'single' : 'many',
+        isDynamicAdded: dynamicAddedModelIds.value.some(id => itemRaw.id === id),
+        type: [SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? 'laser' : item.modelType,
+        url: [SceneType.SWSS, SceneType.SWYDSS].includes(item.type) ? item.url : item.url && getResource(item.url)
+      })
+    } catch(e) {
+      console.error('模型加载失败', e)
+      item.error = true
+      return;
+    }
 
-      sceneModelMap.set(itemRaw, sceneModel)
+    sceneModelMap.set(itemRaw, sceneModel)
 
-      let changeId: NodeJS.Timeout
-      sceneModel.bus.on('transformChanged', transform => {
-        clearTimeout(changeId)
+    let changeId: NodeJS.Timeout
+    sceneModel.bus.on('transformChanged', transform => {
+      clearTimeout(changeId)
 
-        changeId = setTimeout(() => {
-          transform = { ...transform }
-          if (transform.rotation) {
-            transform.rotation = {
-              x: round(transform.rotation.x, 5),
-              y: round(transform.rotation.y, 5),
-              z: round(transform.rotation.z, 5),
-            }
-          }
-          if (transform.position) {
-            transform.position = {
-              x: round(transform.position.x, 5),
-              y: round(transform.position.y, 5),
-              z: round(transform.position.z, 5),
-            }
+      changeId = setTimeout(() => {
+        transform = { ...transform }
+        if (transform.rotation) {
+          transform.rotation = {
+            x: round(transform.rotation.x, 5),
+            y: round(transform.rotation.y, 5),
+            z: round(transform.rotation.z, 5),
           }
-          delete transform.bottom
-          // if (transform.bottom) {
-          //   transform.bottom = round(transform.bottom, 2)
-          // }
-          if (transform.scale) {
-            transform.scale = round(transform.scale, 2)
-          }
-
-          const updateKeys = Object.keys(transform)
-          const update: any = {}
-          for (const key of updateKeys) {
-            update[key] = (item as any)[key]
-          }
-          
-          if (deepIsRevise(update, transform)) {
-            unSet(() => Object.assign(item, transform))
+        }
+        if (transform.position) {
+          transform.position = {
+            x: round(transform.position.x, 5),
+            y: round(transform.position.y, 5),
+            z: round(transform.position.z, 5),
           }
-        }, 16)
-      })
+        }
+        delete transform.bottom
+        // if (transform.bottom) {
+        //   transform.bottom = round(transform.bottom, 2)
+        // }
+        if (transform.scale) {
+          transform.scale = round(transform.scale, 2)
+        }
 
-      sceneModel.bus.on('changeSelect', select => {
-        unSet(() => {
-          if (custom.currentModel === item && !select) {
-            custom.currentModel = null
-          } else if (custom.currentModel !== item && select) {
-            custom.currentModel = item
-          }
-        })
-      })
-      showLoad()
-      sceneModel.bus.on('loadDone', () => {
-        item.loaded = true
-        hideLoad()
-      })
-      sceneModel.bus.on('loadError', () => {
-        item.error = true
-        item.show = false
+        const updateKeys = Object.keys(transform)
+        const update: any = {}
+        for (const key of updateKeys) {
+          update[key] = (item as any)[key]
+        }
         
-        custom.showModelsMap.delete(item)
-        hideLoad()
+        if (deepIsRevise(update, transform)) {
+          unSet(() => Object.assign(item, transform))
+        }
+      }, 16)
+    })
+
+    sceneModel.bus.on('changeSelect', select => {
+      unSet(() => {
+        if (custom.currentModel === item && !select) {
+          custom.currentModel = null
+        } else if (custom.currentModel !== item && select) {
+          custom.currentModel = item
+        }
       })
-      sceneModel.bus.on('loadProgress', progress => item.progress = progress)
-    }
-    for (const item of deleted) {
-      console.error('remove')
-      getSceneModel(item)?.destroy()
-    }
+    })
+    showLoad()
+    sceneModel.bus.on('loadDone', () => {
+      item.loaded = true
+      hideLoad()
+    })
+    sceneModel.bus.on('loadError', () => {
+      item.error = true
+      item.show = false
+      
+      custom.showModelsMap.delete(item)
+      hideLoad()
+    })
+    sceneModel.bus.on('loadProgress', progress => item.progress = progress)
+  }
+  for (const item of deleted) {
+    getSceneModel(item)?.destroy()
+  }
+}
+
+
+const associationModels = (sdk: SDK) => {
+  const getModels = () => fuseModels.value
+    .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value)
+  
+  shallowWatchArray(getModels, (models, oldModels) => {
+    setModels(models, oldModels)
   })
   
   arrayChildEffectScope(getModels, item => {
@@ -366,18 +372,87 @@ const fullView = async (fn: () => void) => {
   }
 }
 
-export const isScenePlayIng = ref(false)
-export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallback?: (index: number) => void, forceFull = false) => {
+export const recovery = async (guide: Guide) => {
+  let rFuseModels: (FuseModel & {viewShow: boolean})[];
+  try {
+    if (!guide.recoveryContent) {
+      throw "没有recovery";
+    }
+    rFuseModels = JSON.parse(guide.recoveryContent);
+  } catch (e) {
+    return () => {};
+  }
+  const initFuseModels = JSON.parse(JSON.stringify(fuseModels.value)) as FuseModels;
+  const initViewShow = fuseModels.value.map(item => custom.showModelsMap.get(item))
+
+
+  console.error(initFuseModels, rFuseModels)
+  const setModels = async (models:  (FuseModel & {viewShow: boolean})[]) => {
+    for (let i = 0; i < models.length; i++) {
+      const ndx = fuseModels.value.findIndex(({ modelId }) => modelId === models[i].modelId);
+      if (~ndx) {
+        Object.assign(fuseModels.value[ndx], models[i]);
+        custom.showModelsMap.set(toRaw(fuseModels.value[ndx]), models[i].viewShow)
+      } else {
+        fuseModels.value.push(models[i]);
+        custom.showModelsMap.set(toRaw(models[i]), models[i].viewShow)
+      }
+    }
+    // console.log(models)
+    for (let i = 0; i < fuseModels.value.length; i++) {
+      const ndx = models.findIndex(({ modelId }) => modelId === fuseModels.value[i].modelId);
+      if (!~ndx) {
+        fuseModels.value.splice(i, 1);
+        i--
+      }
+    }
+
+    await asyncTimeout(100)
+    await new Promise<void>((resolve) => {
+      const stop = watchEffect(() => {
+        if (fuseModelsLoaded.value) {
+          setTimeout(() => stop())
+          resolve()
+        }
+      })
+    })
+  };
+  
+  
+  for (let i = 0; i < fuseModels.value.length; i++) {
+    const ndx = rFuseModels.findIndex(({ modelId }) => modelId === fuseModels.value[i].modelId);
+    if (!~ndx) {
+      rFuseModels.push({...fuseModels.value[i], viewShow: false})
+    }
+  }
+
+  await setModels(rFuseModels);
+  return () => 
+    setModels(initFuseModels.map((item, i) => ({...item, viewShow: initViewShow[i]!})));
+  
+};
+
+export enum ScenePlayIngEnum {
+  ing = 1,
+  stop = 0,
+  ready = 2
+}
+export const isScenePlayIng = ref<ScenePlayIngEnum>(ScenePlayIngEnum.stop)
+let pauseRecovery: () => void
+export const playSceneGuide = async (guide: Guide, changeIndexCallback?: (index: number) => void, forceFull = false) => {
+  const paths = getGuidePaths(guide)
   if (isScenePlayIng.value) {
     throw new Error('导览正在播放')
   }
-  isScenePlayIng.value = true
+  isScenePlayIng.value = ScenePlayIngEnum.ready
+  pauseRecovery = await recovery(guide)
+  isScenePlayIng.value = ScenePlayIngEnum.ing
 
   const sceneGuide = sdk.enterSceneGuide(paths)
 
   changeIndexCallback && sceneGuide.bus.on('changePoint', changeIndexCallback)
 
-  const quitHandler = () => (isScenePlayIng.value = false)
+  const quitHandler = pauseSceneGuide
   const clearHandler = !forceFull && isEdit.value ? null : await fullView(quitHandler)
   if (!clearHandler) {
     sysBus.on('leave', quitHandler, { last: true })
@@ -399,7 +474,8 @@ export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallbac
   ]
 
   await Promise.race(reces)
-  isScenePlayIng.value = false
+  pauseSceneGuide()
+  
   if (clearHandler) {
     clearHandler()
   } else {
@@ -410,7 +486,11 @@ export const playSceneGuide = async (paths: SceneGuidePath[], changeIndexCallbac
   sceneGuide.bus.off('changePoint')
 }
 
-export const pauseSceneGuide = () => isScenePlayIng.value = false
+export const pauseSceneGuide = () => {
+  console.error('pause?')
+  isScenePlayIng.value = ScenePlayIngEnum.stop
+  pauseRecovery && pauseRecovery()
+}
 
 
 // -----------------启动关联--------------------

+ 7 - 0
src/store/guide.ts

@@ -26,6 +26,8 @@ import {
 } from './guide-path'
 
 import type { Guide as SGuide } from '@/api'
+import { fuseModels } from './fuse-model'
+import { custom } from '@/env'
 
 export type Guide = LocalMode<SGuide, 'cover'>
 export type Guides = Guide[]
@@ -52,6 +54,11 @@ export const transformGuide = async (guide: Guide): Promise<SGuide> => {
 
 export const addGuide = addStoreItem(guides, async (guide) => {
   const paths = getGuidePaths(guide)
+  const recoveryContent = JSON.stringify(fuseModels.value.map(item => ({
+    ...item,
+    viewShow: custom.showModelsMap.get(item)
+  })))
+  guide.recoveryContent = recoveryContent
   const newGuide = await postAddGuide(guide)
   paths.forEach(path => path.guideId = newGuide.id)
   return newGuide

+ 24 - 4
src/views/fire/index.vue

@@ -3,17 +3,29 @@
     <div class="info" v-if="caseProject?.tmProject">
       <h2>案件信息</h2>
       <p>
-        <span>事件分类:</span>
+        <span>项目编号:</span>
         {{ caseProject.tmProject.projectSn }}
       </p>
       <p>
+        <span>事件分类:</span>
+        {{ caseProject.tmProject.field5 }}
+      </p>
+      <p>
         <span>详细地址:</span>
         {{ caseProject.tmProject.projectAddress }}
       </p>
       <p>
-        <span>勘验信息:</span>
+        <span>起火场所</span>
         {{ caseProject.tmProject.projectSite }}
       </p>
+      <p>
+        <span>勘验信息:</span>
+        {{ caseProject.tmProject.field7 }}
+      </p>
+      <p>
+        <span>火灾原因:</span>
+        {{ caseProject.tmProject.fireReason }}
+      </p>
 
       <p>
         <span>勘验地址:</span>
@@ -38,18 +50,26 @@
       </p>
 
       <p>
-        <span>分类登记:</span>
+        <span>起火对象:</span>
         {{ caseProject.tmProject.projectName }}
       </p>
       <p>
+        <span>分类登记:</span>
+        {{ caseProject.tmProject.field6 }}
+      </p>
+      <p>
         <span>勘验人员:</span>
         {{ caseProject.tmProject.organizerUsers }}
       </p>
       <p>
-        <span>勘验日期:</span>
+        <span>事故日期:</span>
         {{ caseProject.tmProject.accidentDate }}
       </p>
       <p>
+        <span>勘验日期:</span>
+        {{ caseProject.tmProject.field8 }}
+      </p>
+      <p>
         <span>项目状态:</span>
         {{ caseProject.tmProject.statusDesc }}
       </p>

+ 135 - 123
src/views/guide/edit-paths.vue

@@ -1,16 +1,16 @@
 <template>
   <div class="video">
     <div class="overflow">
-      <ui-icon 
-        ctrl 
-        :type="isScenePlayIng ? 'pause' : 'preview'" 
-        :disabled="!paths.length" 
+      <ui-icon
+        ctrl
+        :type="isScenePlayIng ? 'pause' : 'preview'"
+        :disabled="!paths.length"
         @click="play"
       />
-      <ui-button 
-        type="primary" 
-        @click="addPath" 
-        width="200px" 
+      <ui-button
+        type="primary"
+        @click="addPath"
+        width="200px"
         :class="{ disabled: isScenePlayIng }"
       >
         添加视角
@@ -19,11 +19,11 @@
     <div class="info" v-if="paths.length">
       <div class="meta">
         <div class="length">
-          <span>视频时长</span>{{paths.reduce((t, c) => t + c.time, 0).toFixed(1)}}s
+          <span>视频时长</span>{{ paths.reduce((t, c) => t + c.time, 0).toFixed(1) }}s
         </div>
-        <div 
-          class="fun-ctrl clear" 
-          @click="deleteAll" 
+        <div
+          class="fun-ctrl clear"
+          @click="deleteAll"
           :class="{ disabled: isScenePlayIng }"
         >
           <ui-icon type="del" />
@@ -33,48 +33,47 @@
 
       <div class="photo-list" ref="listVm">
         <template v-for="(path, i) in paths" :key="path.id">
-          <div 
-            class="photo" 
+          <div
+            class="photo"
             :class="{ active: current === path, disabled: isScenePlayIng }"
             @click="changeCurrent(path)"
           >
-            <ui-icon 
-              type="del" 
-              ctrl 
-              @click.stop="deletePath(path)" 
-              :class="{ disabled: isScenePlayIng }" 
+            <ui-icon
+              type="del"
+              ctrl
+              @click.stop="deletePath(path)"
+              :class="{ disabled: isScenePlayIng }"
             />
             <img :src="getResource(getFileUrl(path.cover))" />
           </div>
           <div class="set-phone-attr" v-if="i !== paths.length - 1">
-            <ui-input 
-              type="number" 
-              width="54px" 
+            <ui-input
+              type="number"
+              width="54px"
               height="26px"
-              :modelValue="path.speed" 
+              :modelValue="path.speed"
               @update:modelValue="(val: number) => updatePathInfo(i, { speed: val })"
-              :ctrl="false" 
-              :min="0.1" 
+              :ctrl="false"
+              :min="0.1"
               :max="10"
             >
               <template #icon><span>m/s</span></template>
             </ui-input>
-            <ui-input 
-              type="number" 
-              width="54px" 
-              height="26px" 
-              :modelValue="path.time" 
+            <ui-input
+              type="number"
+              width="54px"
+              height="26px"
+              :modelValue="path.time"
               @update:modelValue="(val: number) => updatePathInfo(i, { time: val })"
-              :ctrl="false" 
-              :min="0.1" 
-              :max="20" 
+              :ctrl="false"
+              :min="0.1"
+              :max="20"
               class="time"
             >
               <template #icon><span class="time">s</span></template>
             </ui-input>
           </div>
         </template>
-        
       </div>
     </div>
     <p class="un-video" v-else>暂无导览</p>
@@ -82,30 +81,43 @@
 </template>
 
 <script setup lang="ts">
-import { loadPack, togetherCallback, getFileUrl, asyncTimeout } from '@/utils'
-import { sdk, playSceneGuide, pauseSceneGuide, isScenePlayIng } from '@/sdk'
-import { createGuidePath, isTemploraryID, useAutoSetMode, guides, getGuidePaths, guidePaths } from '@/store'
-import { Dialog, Message } from 'bill/index'
-import { useViewStack } from '@/hook'
-import { nextTick, ref, toRaw, watchEffect } from 'vue'
-import { showRightPanoStack, showLeftCtrlPanoStack, showLeftPanoStack, showRightCtrlPanoStack, getResource } from '@/env'
+import { loadPack, togetherCallback, getFileUrl, asyncTimeout } from "@/utils";
+import { sdk, playSceneGuide, pauseSceneGuide, isScenePlayIng, recovery } from "@/sdk";
+import {
+  createGuidePath,
+  isTemploraryID,
+  useAutoSetMode,
+  guides,
+  getGuidePaths,
+  guidePaths,
+} from "@/store";
+import { Dialog, Message } from "bill/index";
+import { useViewStack } from "@/hook";
+import { nextTick, onUnmounted, ref, toRaw, watchEffect } from "vue";
+import {
+  showRightPanoStack,
+  showLeftCtrlPanoStack,
+  showLeftPanoStack,
+  showRightCtrlPanoStack,
+  getResource,
+} from "@/env";
 
-import type { Guide, GuidePaths, GuidePath } from '@/store'
-import type { CalcPathProps } from '@/sdk'
+import type { Guide, GuidePaths, GuidePath } from "@/store";
+import type { CalcPathProps } from "@/sdk";
 
-const props = defineProps< { data: Guide }>()
-const paths = ref<GuidePaths>(getGuidePaths(props.data))
-const current = ref<GuidePath>(paths.value[0])
+const props = defineProps<{ data: Guide }>();
+const paths = ref<GuidePaths>(getGuidePaths(props.data));
+const current = ref<GuidePath>(paths.value[0]);
 
+let init: () => void;
+recovery(props.data).then((fn) => (init = fn));
+onUnmounted(() => init());
 const updatePathInfo = (index: number, calcInfo: CalcPathProps[1]) => {
-  const info = sdk.calcPathInfo(
-    paths.value.slice(index, index + 2) as any,
-    calcInfo
-  )
-  Object.assign(paths.value[index], info)
-}
+  const info = sdk.calcPathInfo(paths.value.slice(index, index + 2) as any, calcInfo);
+  Object.assign(paths.value[index], info);
+};
 
-useViewStack(() => 
+useViewStack(() =>
   togetherCallback([
     showRightPanoStack.push(ref(false)),
     showLeftCtrlPanoStack.push(ref(false)),
@@ -114,97 +126,100 @@ useViewStack(() =>
   ])
 );
 
-useAutoSetMode(paths, {
-  save() {
-    if (!paths.value.length) {
-      Dialog.alert('无法保存空路径导览!')
-      throw '无法保存空路径导览!'
-    }
-    const oldPaths = getGuidePaths(props.data)
-    props.data.cover = paths.value[0].cover
-    guidePaths.value = guidePaths.value
-      .filter(path => !oldPaths.includes(path))
-      .concat(paths.value)
-    if (isTemploraryID(props.data.id)) {
-      console.error("现在才保存?")
-      guides.value.push(props.data)
-    }
+useAutoSetMode(
+  paths,
+  {
+    save() {
+      if (!paths.value.length) {
+        Dialog.alert("无法保存空路径导览!");
+        throw "无法保存空路径导览!";
+      }
+      const oldPaths = getGuidePaths(props.data);
+      props.data.cover = paths.value[0].cover;
+      guidePaths.value = guidePaths.value
+        .filter((path) => !oldPaths.includes(path))
+        .concat(paths.value);
+      if (isTemploraryID(props.data.id)) {
+        guides.value.push(props.data);
+      }
+    },
   },
-}, false)
+  false
+);
 
 const addPath = () => {
   loadPack(async () => {
-    const dataURL = await sdk.screenshot(260, 160)
-    const res = await fetch(dataURL)
-    const blob = await res.blob()
+    const dataURL = await sdk.screenshot(260, 160);
+    const res = await fetch(dataURL);
+    const blob = await res.blob();
 
-    const pose = sdk.getPose()
-    const index = paths.value.indexOf(current.value) + 1
-    const path: GuidePath = createGuidePath({ 
-      ...pose, 
+    const pose = sdk.getPose();
+    const index = paths.value.indexOf(current.value) + 1;
+    const path: GuidePath = createGuidePath({
+      ...pose,
       guideId: props.data.id,
-      cover: { url: dataURL, blob } 
-    })
-    paths.value.splice(index, 0, path)
-    current.value = path
+      cover: { url: dataURL, blob },
+    });
+    paths.value.splice(index, 0, path);
+    current.value = path;
     if (paths.value.length > 1) {
-      const index = paths.value.length - 2
-      updatePathInfo(index, { time: 3 })
+      const index = paths.value.length - 2;
+      updatePathInfo(index, { time: 3 });
     }
-  })
-}
+  });
+};
 
 const deletePath = async (path: GuidePath, fore: boolean = false) => {
-  if (fore || (await Dialog.confirm('确定要删除此画面吗?'))) {
-    const index = paths.value.indexOf(path)
+  if (fore || (await Dialog.confirm("确定要删除此画面吗?"))) {
+    const index = paths.value.indexOf(path);
     if (~index) {
-      paths.value.splice(index, 1)
+      paths.value.splice(index, 1);
     }
     if (path === current.value) {
-      current.value = paths.value[index + (index === 0 ? 0 : -1)]
+      current.value = paths.value[index + (index === 0 ? 0 : -1)];
     }
   }
-}
+};
 
 const deleteAll = async () => {
-  if (await Dialog.confirm('确定要清空画面吗?')) {
-    paths.value.length = 0
-    current.value = paths.value[0]
+  if (await Dialog.confirm("确定要清空画面吗?")) {
+    paths.value.length = 0;
+    current.value = paths.value[0];
   }
-}
+};
 
 const changeCurrent = (path: GuidePath) => {
-  sdk.comeTo({ dur: 300, ...path })
-  current.value = path
-}
+  sdk.comeTo({ dur: 300, ...path });
+  current.value = path;
+};
 
 const play = async () => {
   if (isScenePlayIng.value) {
-    pauseSceneGuide()
+    pauseSceneGuide();
   } else {
-    changeCurrent(paths.value[0])
-    await asyncTimeout(400)
-    playSceneGuide(toRaw(paths.value), (index) => {
-      current.value = paths.value[index - 1]
-    })
+    changeCurrent(paths.value[0]);
+    await asyncTimeout(400);
+    playSceneGuide(props.data, (index) => {
+      current.value = paths.value[index - 1];
+    });
   }
-}
+};
 
-const listVm = ref<HTMLDivElement>()
+const listVm = ref<HTMLDivElement>();
 watchEffect(async () => {
-  const index = paths.value.indexOf(current.value)
+  const index = paths.value.indexOf(current.value);
   if (~index && listVm.value) {
-    await nextTick()
-    const scrollWidth = listVm.value.scrollWidth / paths.value.length
-    const centerWidth = listVm.value.offsetWidth / 2
-    const offsetLeft = scrollWidth * index - centerWidth
+    await nextTick();
+    const scrollWidth = listVm.value.scrollWidth / paths.value.length;
+    const centerWidth = listVm.value.offsetWidth / 2;
+    const offsetLeft = scrollWidth * index - centerWidth;
 
     listVm.value.scroll({
       left: offsetLeft,
       top: 0,
-    })
+    });
   }
-})
+});
 </script>
 
 <style lang="scss" scoped>
@@ -229,11 +244,11 @@ watchEffect(async () => {
 
   .meta {
     font-size: 12px;
-    border-bottom: 1px solid rgba(255,255,255,.16);
+    border-bottom: 1px solid rgba(255, 255, 255, 0.16);
     padding: 10px 20px;
     display: flex;
     justify-content: space-between;
-    
+
     .length span {
       margin-right: 10px;
     }
@@ -248,7 +263,6 @@ watchEffect(async () => {
     }
   }
 
-
   .photo-list {
     padding: 10px 20px 20px;
     overflow-x: auto;
@@ -264,8 +278,8 @@ watchEffect(async () => {
 
       &::before,
       &::after {
-        content: '';
-        color: rgba(255,255,255,.6);
+        content: "";
+        color: rgba(255, 255, 255, 0.6);
         position: absolute;
         top: 50%;
         transform: translateY(-50%);
@@ -284,12 +298,11 @@ watchEffect(async () => {
         border-left: 7px solid currentColor;
       }
     }
-    
+
     .photo {
       cursor: pointer;
       flex: none;
       position: relative;
-      
 
       &.active {
         outline: 2px solid var(--colors-primary-base);
@@ -302,8 +315,8 @@ watchEffect(async () => {
         width: 24px;
         font-size: 12px;
         height: 24px;
-        background-color: rgba(0,0,0,0.6);
-        color: rgba(255,255,255,.6);
+        background-color: rgba(0, 0, 0, 0.6);
+        color: rgba(255, 255, 255, 0.6);
         display: flex;
         align-items: center;
         justify-content: center;
@@ -311,7 +324,6 @@ watchEffect(async () => {
         border-radius: 50%;
       }
 
-
       img {
         width: 230px;
         height: 160px;
@@ -324,7 +336,7 @@ watchEffect(async () => {
   height: 100px;
   line-height: 100px;
   text-align: center;
-  color: rgba(255,255,255,0.6);
+  color: rgba(255, 255, 255, 0.6);
   font-size: 1.2em;
 }
 </style>
@@ -349,4 +361,4 @@ watchEffect(async () => {
     text-align: right;
   }
 }
-</style>
+</style>

+ 19 - 6
src/views/guide/sign.vue

@@ -7,7 +7,7 @@
           type="preview"
           class="icon"
           ctrl
-          @click="playSceneGuide(paths, undefined, true)"
+          @click="playSceneGuide(guide, undefined, true)"
           v-if="paths.length"
         />
       </div>
@@ -71,17 +71,16 @@
 </template>
 
 <script setup lang="ts">
-import { Guide, getGuidePaths } from "@/store";
-import { getFileUrl, saveAs } from "@/utils";
+import { Guide, getGuidePaths, FuseModels, fuseModels } from "@/store";
+import { diffArrayChange, getFileUrl, saveAs } from "@/utils";
 import { getResource } from "@/env";
 import { computed, watchEffect, nextTick, ref } from "vue";
-import { playSceneGuide, isScenePlayIng, pauseSceneGuide } from "@/sdk";
+import { playSceneGuide, isScenePlayIng, pauseSceneGuide, ScenePlayIngEnum } from "@/sdk";
 import { VideoRecorder } from "@simaq/core";
 
 const props = withDefaults(defineProps<{ guide: Guide; edit?: boolean }>(), {
   edit: true,
 });
-
 const emit = defineEmits<{
   (e: "delete"): void;
   (e: "play"): void;
@@ -112,6 +111,7 @@ const actions = {
         }
       });
     });
+
     const config: any = {
       // uploadUrl: '',
       // resolution: '1080p' | '2k' | '4k';
@@ -133,6 +133,19 @@ const actions = {
     };
 
     const videoRecorder = new VideoRecorder(config);
+    playSceneGuide(props.guide, undefined, true);
+    await new Promise<void>((resolve) => {
+      const stop = watchEffect(
+        () => {
+          if (isScenePlayIng.value === ScenePlayIngEnum.ing) {
+            stop();
+            resolve();
+          }
+        },
+        { flush: "sync" }
+      );
+    });
+    console.log("开始录屏");
     videoRecorder.startRecord();
 
     let stopWatch: () => void;
@@ -142,6 +155,7 @@ const actions = {
     };
 
     videoRecorder.on("record", (blob) => {
+      console.log("录屏结束");
       saveAs(
         new File([blob], "录屏.mp4", { type: "video/mp4; codecs=h264" }),
         props.guide.title + ".mp4"
@@ -150,7 +164,6 @@ const actions = {
 
     videoRecorder.off("*");
     videoRecorder.on("startRecord", () => {
-      playSceneGuide(paths.value, undefined, true);
       stopWatch = watchEffect(() => {
         if (!isScenePlayIng.value) {
           videoRecorder.endRecord();