bill 1 anno fa
parent
commit
db2173b3e3

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

File diff suppressed because it is too large
+ 541 - 481
public/test-case/model-list.json


File diff suppressed because it is too large
+ 2822 - 890
src/components/bill-ui/components/icon/iconfont/demo_index.html


+ 457 - 121
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=1692865200116') format('woff2'),
-       url('iconfont.woff?t=1692865200116') format('woff'),
-       url('iconfont.ttf?t=1692865200116') format('truetype');
+  font-family: "iconfont"; /* Project id 2930899 */
+  src: url('iconfont.woff2?t=1722475563134') format('woff2'),
+       url('iconfont.woff?t=1722475563134') format('woff'),
+       url('iconfont.ttf?t=1722475563134') format('truetype');
 }
 
 .iconfont {
@@ -13,160 +13,320 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
-.icon-list-detail:before {
-  content: "\e740";
+.icon-help_n:before {
+  content: "\e76d";
 }
 
-.icon-without:before {
-  content: "\e70e";
+.icon-surface_g:before {
+  content: "\e758";
 }
 
-.icon-keyboard:before {
-  content: "\e70d";
+.icon-draw_a:before {
+  content: "\e759";
 }
 
-.icon-recover:before {
-  content: "\e693";
+.icon-draw_d:before {
+  content: "\e757";
 }
 
-.icon-repeal:before {
-  content: "\e694";
+.icon-coord:before {
+  content: "\e754";
 }
 
-.icon-broken_l:before {
-  content: "\e6fd";
+.icon-tailor:before {
+  content: "\e755";
 }
 
-.icon-arrows:before {
-  content: "\e6fe";
+.icon-zip_d:before {
+  content: "\e74e";
 }
 
-.icon-blood:before {
-  content: "\e6ff";
+.icon-zip:before {
+  content: "\e74f";
 }
 
-.icon-circle:before {
-  content: "\e700";
+.icon-detail:before {
+  content: "\e74a";
 }
 
-.icon-cigarette_e:before {
-  content: "\e701";
+.icon-a-a-calculate:before {
+  content: "\e74c";
 }
 
-.icon-corpse:before {
-  content: "\e702";
+.icon-draw_e:before {
+  content: "\e749";
 }
 
-.icon-icon_n:before {
+.icon-aicon-earthwork-n:before {
+  content: "\e748";
+}
+
+.icon-jump_kk:before {
   content: "\e703";
 }
 
-.icon-form:before {
+.icon-close_r:before {
   content: "\e704";
 }
 
-.icon-footprint_l:before {
-  content: "\e705";
+.icon-cloud_c:before {
+  content: "\e6fc";
 }
 
-.icon-footprint_r:before {
-  content: "\e706";
+.icon-clear1:before {
+  content: "\e640";
 }
 
-.icon-fire_p:before {
-  content: "\e707";
+.icon-a-exclude:before {
+  content: "\e6f7";
 }
 
-.icon-rectangle:before {
-  content: "\e708";
+.icon-a-intersect:before {
+  content: "\e6f8";
 }
 
-.icon-shoeprints_l:before {
-  content: "\e709";
+.icon-a-scale-d:before {
+  content: "\e6f9";
 }
 
-.icon-text:before {
-  content: "\e70a";
+.icon-update:before {
+  content: "\e6f6";
 }
 
-.icon-fingerprint:before {
-  content: "\e70b";
+.icon-icon_f_z_n:before {
+  content: "\e6f3";
 }
 
-.icon-shoeprints_r:before {
-  content: "\e70c";
+.icon-icon_h_z_n:before {
+  content: "\e6f4";
 }
 
-.icon-nav-setup:before {
-  content: "\e64b";
+.icon-icon_v_z_n:before {
+  content: "\e6f5";
 }
 
-.icon-a-film:before {
-  content: "\e6e8";
+.icon-state_gps_d:before {
+  content: "\e6f2";
 }
 
-.icon-nav-edit:before {
-  content: "\e642";
+.icon-panorama-f:before {
+  content: "\e6f1";
 }
 
-.icon-pic:before {
-  content: "\e648";
+.icon-international:before {
+  content: "\e6b3";
 }
 
-.icon-list-scene:before {
-  content: "\e6e4";
+.icon-left1:before {
+  content: "\e6ae";
 }
 
-.icon-list-file:before {
-  content: "\e6e5";
+.icon-right:before {
+  content: "\e6af";
 }
 
-.icon-list-record:before {
-  content: "\e6e6";
+.icon-video1:before {
+  content: "\e63b";
 }
 
-.icon-list-view:before {
-  content: "\e6e7";
+.icon-more:before {
+  content: "\e600";
 }
 
-.icon-video1:before {
-  content: "\e63b";
+.icon-preview:before {
+  content: "\e63a";
+}
+
+.icon-nav-record:before {
+  content: "\e6dc";
 }
 
 .icon-order:before {
   content: "\e6dd";
 }
 
+.icon-point-s:before {
+  content: "\e6d9";
+}
+
+.icon-a-connect-dis:before {
+  content: "\e6d1";
+}
+
+.icon-data_revise_n:before {
+  content: "\e6d2";
+}
+
+.icon-zoom_n:before {
+  content: "\e6d4";
+}
+
+.icon-a-connect:before {
+  content: "\e6d0";
+}
+
+.icon-dianwei:before {
+  content: "\e6cc";
+}
+
+.icon-weitiao:before {
+  content: "\e6cd";
+}
+
+.icon-state_gps:before {
+  content: "\e6ce";
+}
+
+.icon-m-r:before {
+  content: "\e6cf";
+}
+
+.icon-lessen:before {
+  content: "\e6aa";
+}
+
+.icon-change1:before {
+  content: "\e6a8";
+}
+
+.icon-nav:before {
+  content: "\e6a6";
+}
+
+.icon-city:before {
+  content: "\e6ea";
+}
+
+.icon-pipeline:before {
+  content: "\e6eb";
+}
+
+.icon-users:before {
+  content: "\e6ec";
+}
+
+.icon-quanbu:before {
+  content: "\e6e8";
+}
+
+.icon-factory:before {
+  content: "\e6e9";
+}
+
+.icon-list:before {
+  content: "\e69c";
+}
+
+.icon-map-m:before {
+  content: "\e692";
+}
+
+.icon-extend:before {
+  content: "\e690";
+}
+
+.icon-shrink:before {
+  content: "\e691";
+}
+
+.icon-download:before {
+  content: "\e61c";
+}
+
+.icon-logo:before {
+  content: "\e68f";
+}
+
+.icon-d-r:before {
+  content: "\e68d";
+}
+
+.icon-up-a:before {
+  content: "\e68e";
+}
+
+.icon-menu:before {
+  content: "\e689";
+}
+
+.icon-user:before {
+  content: "\e68c";
+}
+
+.icon-password:before {
+  content: "\e68b";
+}
+
+.icon-hole:before {
+  content: "\e68a";
+}
+
+.icon-affirm:before {
+  content: "\e688";
+}
+
+.icon-cancel:before {
+  content: "\e687";
+}
+
+.icon-location:before {
+  content: "\e686";
+}
+
 .icon-pin1:before {
-  content: "\e6e3";
+  content: "\e685";
 }
 
-.icon-nav-measure:before {
-  content: "\e64a";
+.icon-lock_s:before {
+  content: "\e683";
 }
 
-.icon-v-l:before {
-  content: "\e66f";
+.icon-lock_n:before {
+  content: "\e684";
 }
 
-.icon-h-r:before {
-  content: "\e670";
+.icon-add_d:before {
+  content: "\e682";
 }
 
-.icon-f-l:before {
-  content: "\e673";
+.icon-uploading1:before {
+  content: "\e681";
 }
 
-.icon-search:before {
-  content: "\e64c";
+.icon-move:before {
+  content: "\e680";
 }
 
-.icon-left1:before {
-  content: "\e6ae";
+.icon-flip:before {
+  content: "\e67e";
 }
 
-.icon-right:before {
-  content: "\e6af";
+.icon-rotate_r:before {
+  content: "\e67f";
+}
+
+.icon-rotate_l:before {
+  content: "\e67d";
+}
+
+.icon-pin:before {
+  content: "\e67c";
+}
+
+.icon-layout:before {
+  content: "\e67b";
+}
+
+.icon-change:before {
+  content: "\e67a";
+}
+
+.icon-iorigin:before {
+  content: "\e678";
+}
+
+.icon-destination:before {
+  content: "\e679";
 }
 
 .icon-state_e:before {
@@ -181,48 +341,168 @@
   content: "\e626";
 }
 
-.icon-eye-n:before {
-  content: "\e621";
+.icon-clear:before {
+  content: "\e676";
 }
 
-.icon-eye-s:before {
-  content: "\e622";
+.icon-cut:before {
+  content: "\e677";
 }
 
-.icon-more:before {
-  content: "\e600";
+.icon-copy:before {
+  content: "\e675";
+}
+
+.icon-v-m:before {
+  content: "\e674";
+}
+
+.icon-f-m:before {
+  content: "\e671";
+}
+
+.icon-v-r:before {
+  content: "\e672";
+}
+
+.icon-f-l:before {
+  content: "\e673";
+}
+
+.icon-h-l:before {
+  content: "\e66d";
+}
+
+.icon-h-m:before {
+  content: "\e66e";
+}
+
+.icon-v-l:before {
+  content: "\e66f";
+}
+
+.icon-h-r:before {
+  content: "\e670";
+}
+
+.icon-share:before {
+  content: "\e66c";
+}
+
+.icon-magnify:before {
+  content: "\e66b";
+}
+
+.icon-a-2d:before {
+  content: "\e669";
+}
+
+.icon-a-3d:before {
+  content: "\e66a";
+}
+
+.icon-left:before {
+  content: "\e668";
+}
+
+.icon-video:before {
+  content: "\e667";
+}
+
+.icon-pic:before {
+  content: "\e64e";
+}
+
+.icon-full:before {
+  content: "\e638";
+}
+
+.icon-del:before {
+  content: "\e632";
+}
+
+.icon-link:before {
+  content: "\e618";
+}
+
+.icon-uploading:before {
+  content: "\e619";
+}
+
+.icon-complete:before {
+  content: "\e61b";
+}
+
+.icon-web:before {
+  content: "\e635";
+}
+
+.icon-music:before {
+  content: "\e637";
 }
 
 .icon-element:before {
   content: "\e666";
 }
 
-.icon-extend:before {
-  content: "\e690";
+.icon-add:before {
+  content: "\e631";
 }
 
-.icon-shrink:before {
-  content: "\e691";
+.icon-edit:before {
+  content: "\e61f";
 }
 
-.icon-pause:before {
-  content: "\e636";
+.icon-info:before {
+  content: "\e65e";
 }
 
-.icon-preview:before {
-  content: "\e63a";
+.icon-transmit:before {
+  content: "\e65f";
 }
 
-.icon-clear:before {
-  content: "\e63f";
+.icon-room:before {
+  content: "\e660";
+}
+
+.icon-point:before {
+  content: "\e661";
+}
+
+.icon-bulid:before {
+  content: "\e662";
 }
 
-.icon-play_stop:before {
-  content: "\e6b4";
+.icon-floor:before {
+  content: "\e663";
 }
 
-.icon-transparency:before {
-  content: "\e6d7";
+.icon-pull-up1:before {
+  content: "\e664";
+}
+
+.icon-pull-down1:before {
+  content: "\e665";
+}
+
+.icon-checkbox1:before {
+  content: "\e65d";
+}
+
+.icon-minimize:before {
+  content: "\e65c";
+}
+
+.icon-reset:before {
+  content: "\e65a";
+}
+
+.icon-refresh:before {
+  content: "\e65b";
+}
+
+.icon-checkbox:before {
+  content: "\e659";
 }
 
 .icon-pull-down:before {
@@ -233,55 +513,111 @@
   content: "\e61e";
 }
 
-.icon-add:before {
-  content: "\e631";
+.icon-show_pic_s:before {
+  content: "\e658";
+}
+
+.icon-show_pic_n:before {
+  content: "\e650";
+}
+
+.icon-show_dot_s:before {
+  content: "\e64f";
+}
+
+.icon-show_dot_n:before {
+  content: "\e657";
+}
+
+.icon-share1:before {
+  content: "\e656";
+}
+
+.icon-portrait:before {
+  content: "\e655";
+}
+
+.icon-course:before {
+  content: "\e652";
+}
+
+.icon-self-more:before {
+  content: "\e64b";
+}
+
+.icon-search:before {
+  content: "\e64c";
+}
+
+.icon-pull-more:before {
+  content: "\e64d";
+}
+
+.icon-switch:before {
+  content: "\e651";
+}
+
+.icon-nav-hotspot:before {
+  content: "\e64a";
+}
+
+.icon-nav-measure:before {
+  content: "\e649";
+}
+
+.icon-nav-edit:before {
+  content: "\e642";
+}
+
+.icon-nav-setup:before {
+  content: "\e648";
 }
 
 .icon-close:before {
   content: "\e633";
 }
 
-.icon-pin:before {
-  content: "\e67c";
+.icon-nav-browse:before {
+  content: "\e63d";
 }
 
-.icon-flip:before {
-  content: "\e67e";
+.icon-nav-correct:before {
+  content: "\e63e";
 }
 
-.icon-move:before {
-  content: "\e680";
+.icon-nav_data-setup:before {
+  content: "\e63f";
 }
 
-.icon-del:before {
-  content: "\e632";
+.icon-nav-coord:before {
+  content: "\e641";
 }
 
-.icon-checkbox:before {
-  content: "\e649";
+.icon-nav-geography:before {
+  content: "\e643";
 }
 
-.icon-nor:before {
-  content: "\e696";
+.icon-nav-space:before {
+  content: "\e644";
 }
 
-.icon-joint:before {
-  content: "\e6e0";
+.icon-nav-download:before {
+  content: "\e645";
 }
 
-.icon-path:before {
-  content: "\e6e1";
+.icon-nav-data:before {
+  content: "\e646";
 }
 
-.icon-label:before {
-  content: "\e6e2";
+.icon-nav-house:before {
+  content: "\e647";
 }
 
-.icon-case:before {
-  content: "\e6da";
+.icon-eye-s:before {
+  content: "\e653";
 }
 
-.icon-scene:before {
-  content: "\e6db";
+.icon-eye-n:before {
+  content: "\e654";
 }
 

File diff suppressed because it is too large
+ 1 - 1
src/components/bill-ui/components/icon/iconfont/iconfont.js


File diff suppressed because it is too large
+ 883 - 295
src/components/bill-ui/components/icon/iconfont/iconfont.json


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


+ 54 - 41
src/env/index.ts

@@ -1,24 +1,30 @@
-import { stackFactory, flatStacksValue, strToParams } from '@/utils'
-import { reactive, ref } from 'vue'
+import { stackFactory, flatStacksValue, strToParams } from "@/utils";
+import { reactive, ref } from "vue";
 
-import type { FuseModel, TaggingPosition, View } from '@/store'
+import type { FuseModel, TaggingPosition, View } from "@/store";
 
-export const viewModeStack = stackFactory(ref<'full' | 'auto'>('auto'))
-export const showToolbarStack = stackFactory(ref<boolean>(false))
-export const showHeadBarStack = stackFactory(ref<boolean>(true))
-export const showRightPanoStack = stackFactory(ref<boolean>(true))
-export const showLeftPanoStack = stackFactory(ref<boolean>(false))
-export const showLeftCtrlPanoStack = stackFactory(ref<boolean>(true))
-export const showRightCtrlPanoStack = stackFactory(ref<boolean>(true))
-export const showBottomBarStack = stackFactory(ref<boolean>(false), true)
-export const bottomBarHeightStack = stackFactory(ref<string>('60px'))
-export const showTaggingsStack = stackFactory(ref<boolean>(true))
-export const showMeasuresStack = stackFactory(ref<boolean>(true))
-export const currentModelStack = stackFactory(ref<FuseModel | null>(null))
-export const showModelsMapStack = stackFactory(ref<WeakMap<FuseModel, boolean>>(new WeakMap()), true)
-export const modelsChangeStoreStack = stackFactory(ref<boolean>(false))
-export const showTaggingPositionsStack = stackFactory(ref<WeakSet<TaggingPosition>>(new WeakSet()))
-export const currentViewStack = stackFactory(ref<View>())
+export const viewModeStack = stackFactory(ref<"full" | "auto">("auto"));
+export const showToolbarStack = stackFactory(ref<boolean>(false));
+export const showHeadBarStack = stackFactory(ref<boolean>(true));
+export const showRightPanoStack = stackFactory(ref<boolean>(true));
+export const showLeftPanoStack = stackFactory(ref<boolean>(false));
+export const showLeftCtrlPanoStack = stackFactory(ref<boolean>(true));
+export const showRightCtrlPanoStack = stackFactory(ref<boolean>(true));
+export const showBottomBarStack = stackFactory(ref<boolean>(false), true);
+export const bottomBarHeightStack = stackFactory(ref<string>("60px"));
+export const showTaggingsStack = stackFactory(ref<boolean>(true));
+export const showMeasuresStack = stackFactory(ref<boolean>(true));
+export const currentModelStack = stackFactory(ref<FuseModel | null>(null));
+export const showModelsMapStack = stackFactory(
+  ref<WeakMap<FuseModel, boolean>>(new WeakMap()),
+  true
+);
+export const modelsChangeStoreStack = stackFactory(ref<boolean>(false));
+export const showTaggingPositionsStack = stackFactory(
+  ref<WeakSet<TaggingPosition>>(new WeakSet())
+);
+export const currentViewStack = stackFactory(ref<View>());
+export const showModeStack = stackFactory(ref<"pano" | "fuse">("fuse"));
 
 export const custom = flatStacksValue({
   viewMode: viewModeStack,
@@ -36,30 +42,37 @@ export const custom = flatStacksValue({
   showBottomBar: showBottomBarStack,
   bottomBarHeight: bottomBarHeightStack,
   showHeadBar: showHeadBarStack,
-  currentView: currentViewStack
-})
+  currentView: currentViewStack,
+  showMode: showModeStack,
+});
 
+export const params = reactive(
+  strToParams(location.search)
+) as unknown as Params;
+params.caseId = Number(params.caseId);
+params.share = Boolean(Number(params.share));
+params.single = Boolean(Number(params.single));
 
-export const params = reactive(strToParams(location.search)) as unknown as Params
-params.caseId = Number(params.caseId)
-params.share = Boolean(Number(params.share))
-params.single = Boolean(Number(params.single))
-
-export type Params = { 
-  caseId: number,
-  baseURL?: string,
-  modelId?: string,
-  m?: string
-  share?: boolean,
-  single?: boolean
-  token?: string
-}
-
-export const baseURL = params.baseURL ? params.baseURL : '/'
-export const appBackRoot =  'https://www.4dkankan.com/'
+export type Params = {
+  caseId: number;
+  baseURL?: string;
+  modelId?: string;
+  m?: string;
+  share?: boolean;
+  single?: boolean;
+  token?: string;
+};
 
+export const baseURL = params.baseURL ? params.baseURL : "/";
+export const appBackRoot = "https://www.4dkankan.com/";
 
 export const getResource = (uri: string) => {
-  if (~uri.indexOf('base64') || ~uri.indexOf('bolb') || ~uri.indexOf('//') || uri.startsWith('/')) return uri
-  return `${baseURL}/${uri}`
-}
+  if (
+    ~uri.indexOf("base64") ||
+    ~uri.indexOf("bolb") ||
+    ~uri.indexOf("//") ||
+    uri.startsWith("/")
+  )
+    return uri;
+  return `${baseURL}/${uri}`;
+};

+ 6 - 0
src/layout/model-list/index.vue

@@ -64,4 +64,10 @@ const modelDelete = (model: FuseModel) => {
     fuseModels.value.splice(index, 1)
   }
 }
+
+watchEffect(() => {
+  
+
+  console.log(custom.currentModel?.raw)
+})
 </script>

+ 58 - 28
src/layout/model-list/sign.vue

@@ -1,53 +1,83 @@
 <template>
-  <div @click="!model.error && $emit('click')" class="sign-layout">
+  <div
+    @click="!model.error && $emit('click')"
+    class="sign-layout"
+    :class="{ disabled: custom.showMode === 'pano' && !supperPano }"
+  >
     <div class="model-header">
       <p>{{ model.title }}</p>
       <div class="model-action">
-        <ui-input 
-          type="checkbox" 
-          v-model="show" 
-          @click.stop 
-          :class="{disabled: model.error}"
+        <ui-input
+          type="checkbox"
+          v-model="show"
+          @click.stop
+          :class="{ disabled: model.error }"
         />
-        <ui-icon 
-          v-if="custom.modelsChangeStore" 
-          type="del" 
-          ctrl 
-          @click="$emit('delete')" 
+        <ui-icon
+          v-if="custom.modelsChangeStore"
+          type="del"
+          ctrl
+          @click="$emit('delete')"
         />
       </div>
     </div>
-    <div class="model-desc"  v-if="active">
+    <div class="model-desc" v-if="active">
       <p><span>数据来源:</span>{{ SceneTypeDesc[model.type] }}</p>
       <p v-if="model.type !== SceneType.SWSS"><span>数据大小:</span>{{ model.size }}</p>
       <p><span>拍摄时间:</span>{{ model.time }}</p>
     </div>
   </div>
+
+  <Teleport to="#layout-app" v-if="active && supperPano">
+    <div class="mode-tab strengthen">
+      <div
+        class="mode-icon-layout"
+        @click="custom.showMode = 'fuse'"
+        :class="{ active: custom.showMode === 'fuse' }"
+      >
+        <ui-icon type="surface_g" class="icon" />
+      </div>
+      <div
+        class="mode-icon-layout"
+        @click="custom.showMode = 'pano'"
+        :class="{ active: custom.showMode === 'pano' }"
+      >
+        <ui-icon type="panorama-f" class="icon" />
+      </div>
+    </div>
+  </Teleport>
 </template>
 
 <script lang="ts" setup>
-import { getFuseModelShowVariable, SceneTypeDesc, SceneType } from '@/store'
-import { custom } from '@/env'
+import { getFuseModelShowVariable, SceneTypeDesc, SceneType } from "@/store";
+import { custom, showModeStack } from "@/env";
+import { getSceneModel } from "@/sdk";
+
+import type { FuseModel } from "@/store";
+import { computed } from "vue";
+import { currentModel, fuseModel } from "@/model";
 
-import type { FuseModel } from '@/store'
-import { computed } from 'vue';
-import { currentModel, fuseModel } from '@/model';
+type ModelProps = { model: FuseModel; canChange?: boolean };
+const props = defineProps<ModelProps>();
 
-type ModelProps = { model: FuseModel, canChange?: boolean }
-const props = defineProps<ModelProps>()
-const active = computed(() => 
-  custom.currentModel === props.model && currentModel.value === fuseModel
-)
+const active = computed(
+  () => custom.currentModel === props.model && currentModel.value === fuseModel
+);
+const supperPano = computed(() => getSceneModel(props.model)?.supportPano());
 
 type ModelEmits = {
-  (e: 'changeSelect', selected: boolean): void
-  (e: 'delete'): void
-  (e: 'click'): void
-}
+  (e: "changeSelect", selected: boolean): void;
+  (e: "delete"): void;
+  (e: "click"): void;
+};
 defineEmits<ModelEmits>();
 
-const show = getFuseModelShowVariable(props.model)
+const show = getFuseModelShowVariable(props.model);
 
+const switchMode = () => {
+  console.log("click");
+  custom.showMode = "pano";
+};
 </script>
 
-<style lang="scss" scoped src="./style.scss"></style>
+<style lang="scss" scoped src="./style.scss"></style>

+ 49 - 15
src/layout/model-list/style.scss

@@ -1,35 +1,69 @@
 .model-header {
-  display: flex;
+  display        : flex;
   justify-content: space-between;
-  align-items: center;
-  margin-bottom: 10px;
-  
+  align-items    : center;
+  margin-bottom  : 10px;
+
   p {
-    font-size: 14px;
-    color: #fff;
-    height: 1.5em;
-    overflow: hidden;
+    font-size    : 14px;
+    color        : #fff;
+    height       : 1.5em;
+    overflow     : hidden;
     text-overflow: ellipsis;
-    white-space: nowrap;
+    white-space  : nowrap;
   }
 }
 
 .model-desc {
-  color: rgba(255,255,255,0.6);
+  color      : rgba(255, 255, 255, 0.6);
   line-height: 18px;
-  font-size: 12px;
+  font-size  : 12px;
 }
 
 .model-action {
-  display: flex;
+  display    : flex;
   align-items: center;
-  flex: none;
-  > * {
+  flex       : none;
+
+  >* {
     margin-left: 20px;
   }
 }
 
 .sign-layout {
-  margin: -20px 0;
+  margin : -20px 0;
   padding: 20px 0;
 }
+
+
+.mode-tab {
+  position        : absolute;
+  z-index         : 2;
+  background-color: var(--editor-menu-back);
+  transition      : all .3s ease;
+  display         : flex;
+  align-items     : center;
+  justify-content : space-evenly;
+  height          : 34px;
+  border-radius   : 17px;
+  margin-left     : 10px;
+  bottom          : calc(10px + var(--editor-menu-bottom));
+  left            : calc(var(--left-pano-left) + var(--left-pano-width));
+  padding         : 0 5px;
+
+  .mode-icon-layout {
+    padding: 0 8px;
+
+    &.active .icon {
+      color: var(--color-main-normal);
+    }
+  }
+
+  .icon {
+    font-size : 18px;
+    cursor    : pointer;
+    transition: color .3s ease;
+
+  }
+
+}

+ 139 - 121
src/sdk/association/fuseMode.ts

@@ -1,220 +1,238 @@
-import { 
-  SDK, 
-  SceneModel, 
-  ModelAttrRange,  } from '../sdk'
-import { toRaw, watch, reactive } from 'vue'
-import { 
-  custom, 
-  getResource 
-} from '@/env'
-import { 
-  diffArrayChange, 
-  shallowWatchArray, 
+import { SDK, SceneModel, ModelAttrRange } from "../sdk";
+import { toRaw, watch, reactive } from "vue";
+import { custom, getResource } from "@/env";
+import {
+  diffArrayChange,
+  shallowWatchArray,
   arrayChildEffectScope,
   showLoad,
   hideLoad,
   deepIsRevise,
   round,
-} from '@/utils'
-import { 
+} from "@/utils";
+import {
   dynamicAddedModelIds,
-  fuseModels, 
-  getFuseModelShowVariable, 
+  fuseModels,
+  getFuseModelShowVariable,
   SceneType,
   SceneStatus,
-  FuseModel, FuseModels
-} from '@/store'
-import { currentLayout, RoutesName } from '@/router'
-import { isUnSet, unSet } from '@/utils/unset'
-
-
+  FuseModel,
+  FuseModels,
+} from "@/store";
+import { currentLayout, RoutesName } from "@/router";
+import { isUnSet, unSet } from "@/utils/unset";
 
 // -----------------模型关联--------------------
 
-export const modelRange: ModelAttrRange  = {
+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 }
-}
+  scaleRange: { min: 0, max: 200, step: 0.1 },
+};
 
-const sceneModelMap = reactive(new WeakMap<FuseModel, SceneModel>())
-export const getSceneModel = (model?: FuseModel | null) => model && sceneModelMap.get(toRaw(model))
+const sceneModelMap = reactive(new WeakMap<FuseModel, SceneModel>());
+export const getSceneModel = (model?: FuseModel | null) =>
+  model && sceneModelMap.get(toRaw(model));
 
 const setModels = (sdk: SDK, models: FuseModels, oldModels: FuseModels) => {
-  const { added, deleted } = diffArrayChange(models, oldModels)
+  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
+      item.error = true;
+      item.loaded = true;
       continue;
     }
 
-    const itemRaw = toRaw(item)
-    let sceneModel: SceneModel
+    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),
-        fromType: item.type
-      })
-    } catch(e) {
-      console.error('模型加载失败', e)
-      item.error = true
+        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),
+        fromType: item.type,
+      });
+    } 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 }
+        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),
-          }
+          };
         }
-        delete transform.bottom
+        delete transform.bottom;
         // if (transform.bottom) {
         //   transform.bottom = round(transform.bottom, 2)
         // }
         if (transform.scale) {
-          transform.scale = round(transform.scale, 2)
+          transform.scale = round(transform.scale, 2);
         }
 
-        const updateKeys = Object.keys(transform)
-        const update: any = {}
+        const updateKeys = Object.keys(transform);
+        const update: any = {};
         for (const key of updateKeys) {
-          update[key] = (item as any)[key]
+          update[key] = (item as any)[key];
         }
-        
+
         if (deepIsRevise(update, transform)) {
-          unSet(() => Object.assign(item, transform))
+          unSet(() => Object.assign(item, transform));
         }
-      }, 16)
-    })
+      }, 16);
+    });
 
-    sceneModel.bus.on('changeSelect', select => {
+    sceneModel.bus.on("changeSelect", (select) => {
       unSet(() => {
-        if (custom.currentModel === item && !select) {
-          custom.currentModel = null
-        } else if (custom.currentModel !== item && select) {
-          custom.currentModel = item
+        if (custom.showMode === "fuse") {
+          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
-      
-      custom.showModelsMap.delete(item)
-      hideLoad()
-    })
-    sceneModel.bus.on('loadProgress', progress => item.progress = progress)
+      });
+    });
+    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) {
-    console.error('销毁', item)
-    getSceneModel(item)?.destroy()
+    console.error("销毁", item);
+    getSceneModel(item)?.destroy();
   }
-}
-
+};
 
 export const associationModels = (sdk: SDK) => {
-  const getModels = () => fuseModels.value
-    .filter(model => getSceneModel(model) || getFuseModelShowVariable(model).value)
-  
+  watch(
+    () => ({
+      showMode: custom.showMode,
+      active: custom.currentModel,
+    }),
+    (status, _, onCleanup) => {
+      if (status.showMode !== "pano" || !status.active) return;
+      const sceneModel = getSceneModel(status.active);
+      sceneModel?.flyInPano();
+
+      onCleanup(() => {
+        sceneModel?.flyOutPano();
+      });
+    }
+  );
+
+  const getModels = () =>
+    fuseModels.value.filter(
+      (model) => getSceneModel(model) || getFuseModelShowVariable(model).value
+    );
+
   shallowWatchArray(getModels, (models, oldModels) => {
-    setModels(sdk, models, oldModels)
-  })
-  
-  arrayChildEffectScope(getModels, item => {
+    setModels(sdk, models, oldModels);
+  });
+
+  arrayChildEffectScope(getModels, (item) => {
     const stopLoadedWatch = watch(
       () => item.loaded,
       (loaded) => {
         if (loaded) {
-          const modelShow = getFuseModelShowVariable(item)
+          const modelShow = getFuseModelShowVariable(item);
           watch(
-            () => item.bottom, 
-            () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom), 
+            () => item.bottom,
+            () => isUnSet || getSceneModel(item)?.changeBottom(item.bottom)
             // { immediate: true }
-          )
+          );
           watch(
-            () => item.opacity, 
-            () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity), 
+            () => item.opacity,
+            () => isUnSet || getSceneModel(item)?.changeOpacity(item.opacity)
             // { immediate: true }
-          )
+          );
           watch(
-            () => item.scale, 
-            () => isUnSet || getSceneModel(item)?.changeScale(item.scale), 
+            () => item.scale,
+            () => isUnSet || getSceneModel(item)?.changeScale(item.scale)
             // { immediate: true }
-          )
+          );
           watch(
-            () => item.position, 
+            () => item.position,
             () => {
               if (!isUnSet) {
-                getSceneModel(item)?.changePosition(item.position)
+                getSceneModel(item)?.changePosition(item.position);
               }
-            }, 
+            }
             // { immediate: true }
-          )
+          );
           watch(
-            () => item.rotation, 
+            () => item.rotation,
             () => {
               if (!isUnSet) {
-                getSceneModel(item)?.changeRotation(item.rotation)
+                getSceneModel(item)?.changeRotation(item.rotation);
               }
-            }, 
+            }
             // { immediate: true }
-          )
+          );
           watch(
-            () => modelShow.value, 
+            () => modelShow.value,
             () => {
-              const sceneModel = getSceneModel(item)
+              const sceneModel = getSceneModel(item);
               if (!isUnSet && sceneModel) {
-                sceneModel.changeSelect(false)
-                sceneModel.changeShow(modelShow.value)
+                sceneModel.changeSelect(false);
+                sceneModel.changeShow(modelShow.value);
               }
-            }, 
+            },
             { immediate: true }
-          )
+          );
 
           watch(
             () => custom.currentModel === item,
             (selected) => {
-              isUnSet || console.log(item.title, selected, getSceneModel(item))
-              isUnSet || getSceneModel(item)?.changeSelect(selected)
+              isUnSet || console.log(item.title, selected, getSceneModel(item));
+              isUnSet || getSceneModel(item)?.changeSelect(selected);
             }
-          )
+          );
 
-          stopLoadedWatch()
+          stopLoadedWatch();
         }
-      },
+      }
       // { immediate: true }
-    )
-  })
-}
+    );
+  });
+};

+ 4 - 0
src/sdk/sdk.ts

@@ -32,6 +32,10 @@ export type SceneModel = ToChangeAPI<SceneModelAttrs>
     leaveAlignment: () => void
     enterScaleSet:() => ScaleSet
     leaveScaleSet: () => void
+
+    supportPano: () => boolean;
+    flyInPano: () => void;
+    flyOutPano: () => void;
   }
 
 export interface ScaleSet {

+ 5 - 4
src/utils/stack.ts

@@ -20,21 +20,21 @@ export const stackFactory = <T>(initVal?: T, debug?: boolean): Stack<T> => {
     push(raw: T) {
       stack.push(raw);
       if (debug) {
-        console.warn('push', raw)
+        console.warn("push", raw);
       }
       return () => {
         const index = stack.indexOf(raw);
         ~index && stack.splice(index, 1);
         if (debug) {
-          console.warn('pop', raw)
-          console.warn('current', stack)
+          console.warn("pop", raw);
+          console.warn("current", stack);
         }
       };
     },
     pop() {
       let ret = stack[stack.length-- - 1];
       if (debug) {
-        console.warn('pop current', stack)
+        console.warn("pop current", stack);
       }
       return ret;
     },
@@ -69,6 +69,7 @@ export const flatStacksValue = <T extends { [key in any]: Stack<any> }>(
       } else {
         (stacks[key].current.value as any) = val;
       }
+      console.error("set", stacks[key].current.value.value, val);
       return true;
     },
   });