ソースを参照

配准页面编写

bill 3 年 前
コミット
80e83864dc

+ 95 - 3
src/components/bill-ui/components/icon/iconfont/demo_index.html

@@ -55,6 +55,30 @@
           <ul class="icon_lists dib-box">
           
             <li class="dib">
+              <span class="icon iconfont">&#xe6e4;</span>
+                <div class="name">list-scene</div>
+                <div class="code-name">&amp;#xe6e4;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e5;</span>
+                <div class="name">list-file</div>
+                <div class="code-name">&amp;#xe6e5;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e6;</span>
+                <div class="name">list-record</div>
+                <div class="code-name">&amp;#xe6e6;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e7;</span>
+                <div class="name">list-view</div>
+                <div class="code-name">&amp;#xe6e7;</div>
+              </li>
+          
+            <li class="dib">
               <span class="icon iconfont">&#xe63b;</span>
                 <div class="name">video</div>
                 <div class="code-name">&amp;#xe63b;</div>
@@ -306,9 +330,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1661163199866') format('woff2'),
-       url('iconfont.woff?t=1661163199866') format('woff'),
-       url('iconfont.ttf?t=1661163199866') format('truetype');
+  src: url('iconfont.woff2?t=1661479592359') format('woff2'),
+       url('iconfont.woff?t=1661479592359') format('woff'),
+       url('iconfont.ttf?t=1661479592359') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -335,6 +359,42 @@
         <ul class="icon_lists dib-box">
           
           <li class="dib">
+            <span class="icon iconfont icon-list-scene"></span>
+            <div class="name">
+              list-scene
+            </div>
+            <div class="code-name">.icon-list-scene
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-list-file"></span>
+            <div class="name">
+              list-file
+            </div>
+            <div class="code-name">.icon-list-file
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-list-record"></span>
+            <div class="name">
+              list-record
+            </div>
+            <div class="code-name">.icon-list-record
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-list-view"></span>
+            <div class="name">
+              list-view
+            </div>
+            <div class="code-name">.icon-list-view
+            </div>
+          </li>
+          
+          <li class="dib">
             <span class="icon iconfont icon-video1"></span>
             <div class="name">
               video
@@ -714,6 +774,38 @@
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-list-scene"></use>
+                </svg>
+                <div class="name">list-scene</div>
+                <div class="code-name">#icon-list-scene</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-list-file"></use>
+                </svg>
+                <div class="name">list-file</div>
+                <div class="code-name">#icon-list-file</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-list-record"></use>
+                </svg>
+                <div class="name">list-record</div>
+                <div class="code-name">#icon-list-record</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-list-view"></use>
+                </svg>
+                <div class="name">list-view</div>
+                <div class="code-name">#icon-list-view</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-video1"></use>
                 </svg>
                 <div class="name">video</div>

+ 19 - 3
src/components/bill-ui/components/icon/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 3549513 */
-  src: url('iconfont.woff2?t=1661163199866') format('woff2'),
-       url('iconfont.woff?t=1661163199866') format('woff'),
-       url('iconfont.ttf?t=1661163199866') format('truetype');
+  src: url('iconfont.woff2?t=1661479592359') format('woff2'),
+       url('iconfont.woff?t=1661479592359') format('woff'),
+       url('iconfont.ttf?t=1661479592359') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,22 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-list-scene:before {
+  content: "\e6e4";
+}
+
+.icon-list-file:before {
+  content: "\e6e5";
+}
+
+.icon-list-record:before {
+  content: "\e6e6";
+}
+
+.icon-list-view:before {
+  content: "\e6e7";
+}
+
 .icon-video1:before {
   content: "\e63b";
 }

ファイルの差分が大きいため隠しています
+ 1 - 1
src/components/bill-ui/components/icon/iconfont/iconfont.js


+ 28 - 0
src/components/bill-ui/components/icon/iconfont/iconfont.json

@@ -6,6 +6,34 @@
   "description": "",
   "glyphs": [
     {
+      "icon_id": "31485449",
+      "name": "list-scene",
+      "font_class": "list-scene",
+      "unicode": "e6e4",
+      "unicode_decimal": 59108
+    },
+    {
+      "icon_id": "31485450",
+      "name": "list-file",
+      "font_class": "list-file",
+      "unicode": "e6e5",
+      "unicode_decimal": 59109
+    },
+    {
+      "icon_id": "31485451",
+      "name": "list-record",
+      "font_class": "list-record",
+      "unicode": "e6e6",
+      "unicode_decimal": 59110
+    },
+    {
+      "icon_id": "31485452",
+      "name": "list-view",
+      "font_class": "list-view",
+      "unicode": "e6e7",
+      "unicode_decimal": 59111
+    },
+    {
       "icon_id": "23781429",
       "name": "video",
       "font_class": "video1",

BIN
src/components/bill-ui/components/icon/iconfont/iconfont.ttf


BIN
src/components/bill-ui/components/icon/iconfont/iconfont.woff


BIN
src/components/bill-ui/components/icon/iconfont/iconfont.woff2


+ 3 - 5
src/layout/edit/fuse-edit.vue

@@ -34,10 +34,8 @@ Promise.all([
   initialGuides(),
   initialMeasures()
 ])
-.then(() => {
-  loaded.value = true
-  loadModel(fuseModel)
-})
+.then(() => loadModel(fuseModel))
+.then(() => loaded.value = true)
 
 router.beforeEach(async (to, from, next) => {
   if (to.params.save && isOld.value) {
@@ -53,5 +51,5 @@ watch(currentMeta, (meta, _, onClean) => {
       showRightPanoStack.push(ref(false)),
     ]))
   }
-}, { flush: 'post' })
+}, { flush: 'post', immediate: true })
 </script>

+ 26 - 0
src/layout/show/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <SlideMenu />
+  
+  <router-view v-slot="{ Component }">
+    <keep-alive>
+      <component :is="Component" />
+    </keep-alive>
+  </router-view>
+</template>
+
+<script lang="ts" setup>
+import { custom } from '@/env'
+import { onMounted } from 'vue'
+import { loadModel, fuseModel } from '@/model'
+import SlideMenu from './slide-menu.vue'
+
+onMounted(() => loadModel(fuseModel))
+custom.showLeftPano = true
+</script>
+
+<style>
+:root {
+  --editor-menu-width: 80px;
+  --editor-head-height: 0px;
+}
+</style>

+ 88 - 0
src/layout/show/slide-menu.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="slide-menu">
+    <div 
+      v-for="item in items" 
+      :class="{active: item.name === router.currentRoute.value.name}" 
+      :key="item.name"
+      @click="router.push({ name: item.name })"
+    >
+      <ui-icon :type="item.icon" :tip="item.title" class="icon" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { metas, RoutesName, router } from '@/router'
+
+const items = [
+  {
+    name: RoutesName.summaryShow,
+    ...metas[RoutesName.summaryShow]
+  },
+  {
+    name: RoutesName.viewShow,
+    ...metas[RoutesName.viewShow]
+  },
+  {
+    name: RoutesName.recordShow,
+    ...metas[RoutesName.recordShow]
+  },
+  {
+    name: RoutesName.folderShow,
+    ...metas[RoutesName.folderShow]
+  }
+]
+</script>
+
+<style lang="scss" scoped>
+
+.slide-menu {
+  width: var(--editor-menu-width);
+  filter: var(--editor-menu-filter); 
+  background-color: var(--editor-menu-back);
+  position: fixed;
+  left: var(--editor-menu-left);
+  top: var(--editor-head-height);
+  bottom: 0;
+  z-index: 2000;
+  overflow: hidden;
+  backdrop-filter: blur(4px);
+
+  > div {
+    height: 70px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+    color: rgba(255, 255, 255, 0.6);
+    transition: color .3s ease;
+    cursor: pointer;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      width: 0;
+      background: currentColor;
+      transition: width .3s ease;
+    }
+
+    &.active,
+    &:hover {
+      color: #00C8AF;
+    }
+
+    &.active::before {
+      width: 4px;
+    }
+
+    .icon {
+      font-size: 24px;
+      color: currentColor;
+    }
+    
+  }
+}
+</style>

+ 41 - 13
src/router/config.ts

@@ -4,40 +4,40 @@ import type { RouteRecordRaw } from 'vue-router'
 
 export const routes: RouteRecordRaw[] = [
   {
-    path: paths.fuseEdit,
+    path: paths[RoutesName.fuseEdit],
     name: RoutesName.fuseEdit,
     component: () => import('@/layout/edit/fuse-edit.vue'),
     children: [
       {
-        path: paths.switch,
-        name: RoutesName.switch,
+        path: paths[RoutesName.fuseEditSwitch],
+        name: RoutesName.fuseEditSwitch,
         component: () => import('@/layout/edit/fuse-switch.vue'),
         children: [
           {
-            path: paths.merge,
+            path: paths[RoutesName.merge],
             name: RoutesName.merge,
             meta: metas.merge,
             component: () => import('@/views/merge/index.vue')
           },
           {
-            path: paths.tagging,
+            path: paths[RoutesName.tagging],
             name: RoutesName.tagging,
             meta: metas.tagging,
             component: () => import('@/views/tagging/index.vue')
           },
           {
-            path: paths.taggingPosition,
+            path: paths[RoutesName.taggingPosition],
             name: RoutesName.taggingPosition,
             component: () => import('@/views/tagging-position/index.vue')
           },
           {
-            path: paths.measure,
+            path: paths[RoutesName.measure],
             name: RoutesName.measure,
             meta: metas.measure,
             component: () => import('@/views/measure/index.vue')
           },
           {
-            path: paths.guide,
+            path: paths[RoutesName.guide],
             name: RoutesName.guide,
             meta: metas.guide,
             component: () => import('@/views/guide/index.vue')
@@ -45,33 +45,61 @@ export const routes: RouteRecordRaw[] = [
         ]
       },
       {
-        path: paths.registration,
+        path: paths[RoutesName.registration],
         name: RoutesName.registration,
         component: () => import('@/views/registration/index.vue')
       },
       {
-        path: paths.proportion,
+        path: paths[RoutesName.proportion],
         name: RoutesName.proportion,
         component: () => import('@/views/proportion/index.vue')
       }
     ]
   },
   {
-    path: paths.sceneEdit,
+    path: paths[RoutesName.sceneEdit],
     name: RoutesName.sceneEdit,
     component: () => import('@/layout/edit/scene-edit.vue'),
     children: [
       {
-        path: paths.record,
+        path: paths[RoutesName.record],
         name: RoutesName.record,
         component: () => import('@/views/record/index.vue')
       },
       {
-        path: paths.view,
+        path: paths[RoutesName.view],
         name: RoutesName.view,
         component: () => import('@/views/view/index.vue')
       }
     ]
+  },
+  {
+    path: paths[RoutesName.show],
+    name: RoutesName.show,
+    component: () => import('@/layout/show/index.vue'),
+    children: [
+      {
+        path: paths[RoutesName.summaryShow],
+        name: RoutesName.summaryShow,
+        component: () => import('@/views/summary/index.vue')
+      },
+      {
+        path: paths[RoutesName.viewShow],
+        name: RoutesName.viewShow,
+        component: () => import('@/views/view/show.vue')
+      },
+      {
+        path: paths[RoutesName.recordShow],
+        name: RoutesName.recordShow,
+        component: () => import('@/views/record/show.vue')
+      },
+      {
+        path: paths[RoutesName.folderShow],
+        name: RoutesName.folderShow,
+        component: () => import('@/views/folder/index.vue')
+      },
+      
+    ]
   }
 ]
 

+ 47 - 9
src/router/constant.ts

@@ -1,26 +1,40 @@
 export enum RoutesName {
-  merge = 'merge',
+  // 编辑融合页面
+  fuseEdit = 'fuseEdit',
+  
+  // 独立,配准、设置比例
   registration = 'registration',
   proportion = 'proportion',
 
+  // 菜单,独立编辑融合页面
+  fuseEditSwitch = 'fuseEditSwitch',
+  merge = 'merge',
   tagging = 'tagging',
   taggingPosition = 'taggingPosition',
   guide = 'guide',
   measure = 'measure',
-
-  fuseEdit = 'fuseEdit',
-  switch = 'switch',
-
+  
+  // 编辑场景,提取视图,录制视频
   sceneEdit = 'sceneEdit',
   record = 'record',
-  view = 'view'
+  view = 'view',
+
+
+
+  // 展示界面,包括融合和独立场景
+  show = 'show',
+  // 汇总
+  summaryShow = 'summaryShow',
+  recordShow = 'recordShow',
+  viewShow = 'viewShow',
+  folderShow = 'folderShow',
 }
 
 
 export const paths = {
   [RoutesName.fuseEdit]: '/fuseEdit',
 
-  [RoutesName.switch]: '',
+  [RoutesName.fuseEditSwitch]: '',
   [RoutesName.merge]: 'merge',
   [RoutesName.registration]: 'registration/:id',
   [RoutesName.proportion]: 'proportion/:id',
@@ -32,7 +46,14 @@ export const paths = {
   
   [RoutesName.sceneEdit]: '/sceneEdit',
   [RoutesName.record]: 'record',
-  [RoutesName.view]: 'view'
+  [RoutesName.view]: 'view',
+
+  [RoutesName.show]: '/show',
+  [RoutesName.summaryShow]: 'summary',
+  [RoutesName.recordShow]: 'record',
+  [RoutesName.viewShow]: 'view',
+  [RoutesName.folderShow]: 'folder',
+  
 }
 
 export const metas = {
@@ -54,6 +75,23 @@ export const metas = {
     icon: 'nav-measure',
     title: '测量'
   },
-}
 
+
+  [RoutesName.summaryShow]: {
+    icon: 'list-scene',
+    title: '汇总'
+  },
+  [RoutesName.recordShow]: {
+    icon: 'list-view',
+    title: '视图'
+  },
+  [RoutesName.viewShow]: {
+    icon: 'list-record',
+    title: '录屏'
+  },
+  [RoutesName.folderShow]: {
+    icon: 'list-file',
+    title: '卷宗'
+  },
+}
 export const ViewHome = RoutesName.merge

+ 1 - 1
src/router/index.ts

@@ -31,7 +31,7 @@ export const currentRouteNames = computed(() => {
 
 export const currentLayout = computed(() => {
   const names = currentRouteNames.value
-  const layoutNames = [RoutesName.switch] as const
+  const layoutNames = [RoutesName.fuseEditSwitch] as const
   return layoutNames.find(name => names.includes(name))
 })
 

+ 21 - 10
src/sdk/association.ts

@@ -1,6 +1,6 @@
 import { sdk } from './sdk'
 import { fuseModels, taggings, isEdit, sysBus, getFuseModelShowVariable, SceneType } from '@/store'
-import { toRaw, watchEffect, ref, watch } from 'vue'
+import { toRaw, watchEffect, ref, watch, nextTick } from 'vue'
 import { viewModeStack, custom, getResource } from '@/env'
 import { 
   mount, 
@@ -13,19 +13,28 @@ import {
 
 import TaggingComponent from '@/components/tagging/list.vue'
 
+import type { SDK, SceneModel, SceneGuidePath, ModelAttrRange } from '.'
+import { FuseModel, Tagging } from '@/store'
+
 export const modelRange: ModelAttrRange  = {
   opacityRange: { min: 0, max: 100, step: 0.1 },
   bottomRange: { min: -30, max: 70, step: 0.1 },
   scaleRange: { min: 0, max: 200, step: 0.1 }
 }
 
-import type { SDK, SceneModel, SceneGuidePath, ModelAttrRange } from '.'
-import { FuseModel, Tagging } from '@/store'
-
 const sceneModelMap = new WeakMap<FuseModel, SceneModel>()
-export const getSceneModel = (model: FuseModel | null) => model && sceneModelMap.get(toRaw(model))
+export const getSceneModel = (model?: FuseModel | null) => model && sceneModelMap.get(toRaw(model))
 
 const associationModels = (sdk: SDK) => {
+  let isUnSet = false
+  const unSet = ((fn: () => void) => {
+    nextTick(() => {
+      isUnSet = true
+      fn()
+      nextTick(() => isUnSet = false)
+    })
+  })
+
   const getModels = () => fuseModels.value
   shallowWatchArray(getModels, (models, oldModels) => {
     const { added, deleted } = diffArrayChange(models, oldModels)
@@ -44,7 +53,7 @@ const associationModels = (sdk: SDK) => {
       sceneModelMap.set(itemRaw, sceneModel)
 
       sceneModel.bus.on('transformChanged', transform => {
-        Object.assign(item, transform)
+        unSet(() => Object.assign(item, transform))
       })
       sceneModel.bus.on('changeSelect', select => {
         if (custom.currentModel === item && !select) {
@@ -77,10 +86,12 @@ const associationModels = (sdk: SDK) => {
       (loaded) => {
         if (loaded) {
           const modelShow = getFuseModelShowVariable(item)
-          watchEffect(() => getSceneModel(item)?.changeBottom(item.bottom))
-          watchEffect(() => getSceneModel(item)?.changeOpacity(item.opacity))
-          watchEffect(() => getSceneModel(item)?.changeScale(item.scale))
-          watchEffect(() => getSceneModel(item)?.changeShow(modelShow.value))
+          watch(() => item.bottom, () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom), {immediate: true})
+          watch(() => item.opacity, () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity), {immediate: true})
+          watch(() => item.scale, () => isUnSet || getSceneModel(item)?.changeScale(item.scale), {immediate: true})
+          watch(() => item.position, () => isUnSet || getSceneModel(item)?.changePosition(item.position), {immediate: true})
+          watch(() => item.rotation, () => isUnSet || getSceneModel(item)?.changeRotation(item.rotation), {immediate: true})
+          watch(() => modelShow.value, () => isUnSet || getSceneModel(item)?.changeShow(modelShow.value), {immediate: true})
           stopLoadedWatch()
         }
       }

+ 4 - 1
src/sdk/sdk.ts

@@ -6,7 +6,7 @@ import type { Emitter } from 'mitt'
 
 
 type SceneModelAttrs = FuseModelAttrs & { select: boolean }
-export type SceneModel = ToChangeAPI<Omit<SceneModelAttrs, 'position' | 'rotation'>>
+export type SceneModel = ToChangeAPI<SceneModelAttrs>
   & { 
     bus: Emitter<
       Pick<SceneModelAttrs, 'select'> & 
@@ -27,7 +27,10 @@ export type SceneModel = ToChangeAPI<Omit<SceneModelAttrs, 'position' | 'rotatio
     enterRotateMode: () => void
     enterMoveMode: () => void
     leaveTransform: () => void
+    enterAlignment: () => void
+    leaveAlignment: () => void
   }
+  
 
 export type ModelAttrRange = {
   [key in 'opacity' | 'bottom' | 'scale' as `${key}Range`]: {

+ 0 - 0
src/views/folder/index.vue


+ 0 - 3
src/views/guide/index.vue

@@ -13,7 +13,6 @@
         v-for="guide in guides" 
         :key="guide.id" 
         :guide="guide" 
-        @play="playSceneGuide(getGuidePaths(guide))"
         @edit="edit(guide)"
         @delete="deleteGuide(guide)"
       />
@@ -31,13 +30,11 @@ import { ref } from 'vue';
 import GuideSign from './sign.vue'
 import EditPaths from './edit-paths.vue'
 import { useViewStack } from '@/hook'
-import { playSceneGuide } from '@/sdk'
 import { 
   guides, 
   createGuide, 
   enterEdit, 
   sysBus, 
-  getGuidePaths,
   autoSaveGuides
 } from '@/store'
 

+ 0 - 0
src/views/guide/show.vue


+ 3 - 1
src/views/guide/sign.vue

@@ -7,7 +7,8 @@
           type="preview" 
           class="icon" 
           ctrl 
-          @click="emit('play')" v-if="paths.length" 
+          @click="playSceneGuide(paths)" 
+          v-if="paths.length" 
         />
       </div>
       <div>
@@ -29,6 +30,7 @@ import { Guide, getGuidePaths } from '@/store'
 import { getFileUrl } from '@/utils'
 import { getResource } from '@/env'
 import { computed } from 'vue';
+import { playSceneGuide } from '@/sdk'
 
 const props = defineProps<{ guide: Guide }>()
 const emit = defineEmits<{ 

+ 0 - 0
src/views/measure/show.vue


+ 8 - 3
src/views/merge/index.vue

@@ -61,14 +61,14 @@
 </template>
 
 <script lang="ts" setup>
-import { RoutesName, router } from '@/router'
+import { RoutesName, router, currentMeta } from '@/router'
 import { RightPano } from '@/layout'
 import { autoSaveFuseModels, defaultFuseModelAttrs } from '@/store'
 import { togetherCallback } from '@/utils'
 import { getSceneModel, modelRange } from '@/sdk'
 import { useViewStack, useActive } from '@/hook'
 import { showLeftCtrlPanoStack, showLeftPanoStack, custom, modelsChangeStoreStack } from '@/env'
-import { ref, nextTick } from 'vue'
+import { ref, nextTick, watchEffect } from 'vue'
 import { Dialog } from 'bill/expose-common'
 
 import Actions from '@/components/actions/index.vue'
@@ -76,7 +76,6 @@ import Actions from '@/components/actions/index.vue'
 import type { ActionsProps, ActionsItem } from '@/components/actions/index.vue'
 
 const active = useActive()
-const currentItem = ref<ActionsItem | null>(null)
 const actionItems: ActionsProps['items'] = [
   {
     icon: 'move',
@@ -95,6 +94,12 @@ const actionItems: ActionsProps['items'] = [
     }
   },
 ]
+const currentItem = ref<ActionsItem | null>(null)
+watchEffect(() => {
+  if (!custom.currentModel) {
+    currentItem.value = null
+  }
+})
 
 const reset = async () => {
   if (custom.currentModel && await Dialog.confirm('确定恢复默认?此操作无法撤销')) {

+ 1 - 0
src/views/record/show.vue

@@ -0,0 +1 @@
+

+ 85 - 42
src/views/registration/index.vue

@@ -1,78 +1,121 @@
 <template>
-  <ControlPanl 
-    :group="[{ items: options }]" 
-    v-model="selectOptions" 
-    ref="selectExpose"
-  />
-  <ui-floating
-    v-if="selectOptions.some(({key}) => key === 'opacity')"
-    :refer="opacityOptionEl"
-    isTransform
-    dire="right-center"
-  >
-    <div class="floating-range strengthen">
+  <template v-if="model && sceneModel">
+    <ControlPanl 
+      :group="[{ items: options }]" 
+      v-model="selectOptions" 
+      ref="selectExpose"
+    />
+    <ui-floating
+      v-if="selectOptions.some(({key}) => key === 'opacity')"
+      :refer="opacityOptionEl"
+      isTransform
+      dire="right-center"
+    >
+      <div class="floating-range strengthen">
+        <div class="range-content">
+          <ui-input 
+            type="range" 
+            v-model="model.opacity"
+            v-bind="modelRange.opacityRange" 
+            :ctrl="false" 
+            :input="false"
+            width="100%"
+          />
+          <span class="num" :style="{left: `${model.opacity}%`}">{{model.opacity}}%</span>
+        </div>
+      </div>
+    </ui-floating>
+
+    <div class="right-range floating-range strengthen">
       <div class="range-content">
+        <span class="fun-ctrl" @click="model!.bottom += modelRange.bottomRange.step">+</span>
         <ui-input 
           type="range" 
-          v-model="a"
-          v-bind="modelRange.opacityRange" 
+          v-model="model.bottom"
+          v-bind="modelRange.bottomRange" 
+          :moveCallback="changeRange"
           :ctrl="false" 
           :input="false"
           width="100%"
         />
-        <span class="num" :style="{left: `${a}%`}">{{a}}%</span>
+        <span class="fun-ctrl" @click="model!.bottom -= modelRange.bottomRange.step">-</span>
       </div>
     </div>
-  </ui-floating>
-
-  <div class="right-range floating-range strengthen">
-    <div class="range-content">
-      <span class="fun-ctrl" @click="a += modelRange.opacityRange.step">+</span>
-      <ui-input 
-        type="range" 
-        v-model="a"
-        v-bind="modelRange.opacityRange" 
-        :moveCallback="changeRange"
-        :ctrl="false" 
-        :input="false"
-        width="100%"
-      />
-      <span class="fun-ctrl" @click="a -= modelRange.opacityRange.step">-</span>
-    </div>
-  </div>
 
-  <div class="ui-message tip-left">请在当前窗口调整水平方向位置</div>
-  <div class="ui-message tip-right">请在当前窗口调整垂直方向位置</div>
+    <div class="ui-message tip-left">请在当前窗口调整水平方向位置</div>
+    <div class="ui-message tip-right">请在当前窗口调整垂直方向位置</div>
+  </template>
 </template>
 
 <script setup lang="ts">
 import { ref, computed, watch } from 'vue'
 import { ControlPanl } from '@/components/control-panl/'
-import { modelRange } from '@/sdk'
+import { modelRange, getSceneModel } from '@/sdk'
 import { diffArrayChange } from '@/utils'
+import { useViewStack } from '@/hook'
+import { autoSaveFuseModels, getFuseModel, leave } from '@/store'
+import { router } from '@/router'
 
-import type { Items, ControlExpose } from '@/components/control-panl'
+import type { ControlExpose } from '@/components/control-panl'
+
+const model = computed(() => {
+  const modelId = router.currentRoute.value.params.id as string
+  if (modelId) {
+    return getFuseModel(modelId)
+  }
+})
 
-const options: Items = [
+const sceneModel = computed(() => model.value && getSceneModel(model.value))
+const options = [
   { desc: '移动', icon: 'move', key: 'move' },
   { desc: '旋转', icon: 'flip', key: 'rotate' },
   { desc: '透明度', icon: 'transparency', key: 'opacity' },
 ]
-const selectOptions = ref<Items>([])
+const selectOptions = ref<typeof options>([])
 const selectExpose = ref<ControlExpose>()
 const opacityOptionEl = computed(
   () => selectExpose.value?.dom?.querySelector('div[data-key="opacity"]')
 )
-const a = ref(1)
 
 const changeRange = (sp: ScreenLocalPos, cp: ScreenLocalPos, info: { start: number, locusWidth: number }) => 
   info.start + ((sp.y - cp.y) / info.locusWidth)
 
 watch(selectOptions, (nOptions, oOptions = []) => {
   const { added, deleted } = diffArrayChange(nOptions, oOptions)
-  console.log(added, deleted)
+  const setKeys = ['move', 'rotate']
+  const addOptions = added.filter(option => setKeys.includes(option.key))
+  const delOptions = deleted.filter(option => setKeys.includes(option.key))
+
+  if (sceneModel.value) {
+    if (!addOptions.length && delOptions.length) {
+        sceneModel.value.leaveTransform()
+    } else if (addOptions.length) {
+      if (addOptions[0].key === 'move') {
+        sceneModel.value.enterMoveMode()
+      } else {
+        sceneModel.value.enterRotateMode()
+      }
+    }
+  }
 }, { immediate: true })
 
+useViewStack(() => {
+  if (sceneModel.value) {
+    const model = sceneModel.value
+    model.enterAlignment()
+    return () => {
+      if (selectOptions.value.length) {
+        model.leaveTransform()
+        selectOptions.value = []
+      }
+      model.leaveAlignment()
+    }
+  } else {
+    leave()
+  }
+})
+useViewStack(autoSaveFuseModels)
+
 </script>
 
 <style lang="scss" scoped>
@@ -114,7 +157,7 @@ watch(selectOptions, (nOptions, oOptions = []) => {
   height: 40px;
   right: 10px;
   top: 50%;
-  z-index: 1;
+  z-index: 2;
   transform: translateX(40%) rotate(-90deg);
 
   .range-content {
@@ -132,7 +175,7 @@ watch(selectOptions, (nOptions, oOptions = []) => {
 
 .tip-left,.tip-right {
   top: calc(var(--editor-head-height) + var(--header-top) + 11px);
-  z-index: 1;
+  z-index: 2;
 }
 
 .tip-left {

+ 65 - 0
src/views/summary/index.vue

@@ -0,0 +1,65 @@
+<template>
+  <LeftPano>
+    <SceneList :current="currentModel" @update:current="loadModel" />
+  </LeftPano>
+
+  <RightFillPano>
+    <ui-group>
+      <TaggingSign 
+        v-for="tagging in taggings" 
+        :key="tagging.id" 
+        :tagging="tagging" 
+      />
+    </ui-group>
+    <ui-group>
+      <MeasureSign 
+        v-for="measure in measures" 
+        :key="measure.id" 
+        :measure="measure" 
+      />
+    </ui-group>
+    <ui-group>
+      <GuideSign 
+        v-for="guide in guides" 
+        :key="guide.id" 
+        :guide="guide" 
+      />
+    </ui-group>
+  </RightFillPano>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useViewStack } from '@/hook'
+import { togetherCallback } from '@/utils'
+import { showRightCtrlPanoStack, showRightPanoStack } from '@/env'
+import { currentModel, fuseModel, loadModel } from '@/model'
+import { LeftPano, RightFillPano } from '@/layout'
+import SceneList from '@/layout/scene-list/index.vue'
+import TaggingSign from '@/views/tagging/sign.vue'
+import MeasureSign from '@/views/measure/sign.vue'
+import GuideSign from '@/views/guide/sign.vue'
+import { 
+  taggings, 
+  guides, 
+  measures, 
+  initialTaggings, 
+  initialGuides, 
+  initialMeasures,
+  initialTaggingStyles,
+} from '@/store'
+
+initialTaggingStyles()
+initialTaggings() 
+initialGuides()
+initialMeasures() 
+
+const showRightPano = computed(() => currentModel.value === fuseModel)
+
+useViewStack(
+  () => togetherCallback([
+    showRightCtrlPanoStack.push(showRightPano), 
+    showRightPanoStack.push(showRightPano)
+  ])
+)
+</script>

+ 3 - 53
src/views/tagging/index.vue

@@ -30,7 +30,7 @@
         :selected="selectTagging === tagging"
         @edit="editTagging = tagging"
         @delete="deleteTagging(tagging)"
-        @select="selectTagging = tagging"
+        @select="selected => selectTagging = selected ? tagging : null"
         @fixed="fixedTagging(tagging)"
       />
     </ui-group>
@@ -49,23 +49,20 @@ import Edit from './edit.vue'
 import TagingSign from './sign.vue'
 import { RightFillPano } from '@/layout'
 import { useViewStack } from '@/hook'
-import { computed, nextTick, ref, watchEffect } from 'vue';
-import { sdk } from '@/sdk'
+import { computed, ref } from 'vue';
 import { router, RoutesName } from '@/router'
+import { custom } from '@/env'
 import { 
   taggings, 
   isTemploraryID, 
   Tagging, 
   autoSaveTaggings, 
   createTagging,
-  getFuseModel,
-  getFuseModelShowVariable,
   getTaggingPositions,
   taggingPositions,
   isOld,
   save
 } from '@/store'
-import { custom, showTaggingPositionsStack } from '@/env'
 
 const keyword = ref('')
 const filterTaggings = computed(() => taggings.value.filter(tagging => tagging.title.includes(keyword.value)))
@@ -95,54 +92,7 @@ const fixedTagging = async (tagging: Tagging) => {
   router.push({ name: RoutesName.taggingPosition, params: { id: tagging.id } })
 }
 
-const flyTaggingPositions = (tagging: Tagging, callback?: () => void) => {
-  const positions = getTaggingPositions(tagging)
-
-  let isStop = false
-  const flyIndex = (i: number) => {
-    if (isStop || i >= positions.length) {
-      callback && nextTick(callback)
-      return;
-    }
-    const position = positions[i]
-    const model = getFuseModel(position.modelId)
-    if (!model || !getFuseModelShowVariable(model).value) {
-      flyIndex(i + 1)
-      return;
-    }
-
-    const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])))
-    sdk.comeTo({ 
-      position: position.localPos, 
-      modelId: position.modelId,
-      dur: 300,
-      distance: 3
-    })
-    
-    setTimeout(() => {
-      pop()
-      flyIndex(i + 1)
-    }, 2000)
-  }
-  flyIndex(0)
-  return () => isStop = true
-}
 
 const selectTagging = ref<Tagging | null>(null)
-watchEffect((onCleanup) => {
-  if (selectTagging.value) {
-    const success = () => selectTagging.value = null
-    const stop = flyTaggingPositions(selectTagging.value, success)
-    const keyupHandler = (ev: KeyboardEvent) => ev.code === 'Escape' && success()
-
-    document.documentElement.addEventListener('keyup', keyupHandler, false)
-    onCleanup(() => {
-      stop()
-      console.log('removeHandler')
-      document.documentElement.removeEventListener('keyup', keyupHandler, false)
-    })
-  }
-})
-
 useViewStack(autoSaveTaggings)
 </script>

+ 0 - 0
src/views/tagging/show.vue


+ 54 - 4
src/views/tagging/sign.vue

@@ -1,5 +1,9 @@
 <template>
-  <ui-group-option class="sign-tagging" :class="{active: selected}" @click="!disabledFly && emit('select')">
+  <ui-group-option 
+    class="sign-tagging" 
+    :class="{active: selected}" 
+    @click="!disabledFly && emit('select', true)"
+  >
     <div class="info">
       <img :src="getResource(getFileUrl(tagging.images.length ? tagging.images[0] : style.icon))" v-if="style">
       <div>
@@ -20,8 +24,9 @@
 
 <script setup lang="ts">
 import { getFileUrl } from '@/utils'
-import { computed } from 'vue';
-import { getResource } from '@/env'
+import { computed, ref, watchEffect, nextTick } from 'vue';
+import { getResource, showTaggingPositionsStack } from '@/env'
+import { sdk } from '@/sdk'
 import { 
   getTaggingStyle, 
   getTaggingPositions, 
@@ -43,7 +48,7 @@ const disabledFly = computed(() =>
 const emit = defineEmits<{ 
   (e: 'delete'): void 
   (e: 'edit'): void
-  (e: 'select'): void
+  (e: 'select', selected: boolean): void
   (e: 'fixed'): void
 }>()
 
@@ -56,7 +61,52 @@ const actions = {
   delete: () => emit('delete')
 }
 
+const flyTaggingPositions = (tagging: Tagging, callback?: () => void) => {
+  const positions = getTaggingPositions(tagging)
 
+  let isStop = false
+  const flyIndex = (i: number) => {
+    if (isStop || i >= positions.length) {
+      callback && nextTick(callback)
+      return;
+    }
+    const position = positions[i]
+    const model = getFuseModel(position.modelId)
+    if (!model || !getFuseModelShowVariable(model).value) {
+      flyIndex(i + 1)
+      return;
+    }
+
+    const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])))
+    sdk.comeTo({ 
+      position: position.localPos, 
+      modelId: position.modelId,
+      dur: 300,
+      distance: 3
+    })
+    
+    setTimeout(() => {
+      pop()
+      flyIndex(i + 1)
+    }, 2000)
+  }
+  flyIndex(0)
+  return () => isStop = true
+}
+watchEffect((onCleanup) => {
+  if (props.selected) {
+    const success = () => emit('select', false)
+    const stop = flyTaggingPositions(props.tagging, success)
+    const keyupHandler = (ev: KeyboardEvent) => ev.code === 'Escape' && success()
+
+    document.documentElement.addEventListener('keyup', keyupHandler, false)
+    onCleanup(() => {
+      stop()
+      console.log('removeHandler')
+      document.documentElement.removeEventListener('keyup', keyupHandler, false)
+    })
+  }
+})
 </script>
 
 <style lang="scss" scoped src="./style.scss"></style>

+ 0 - 0
src/views/view/show.vue