Explorar el Código

完成scence state移植

gemercheung hace 2 años
padre
commit
186848c3c1

+ 2 - 1
package.json

@@ -11,7 +11,8 @@
   "dependencies": {
     "consola": "^2.15.3",
     "pinia": "^2.0.23",
-    "vue": "^3.2.41"
+    "vue": "^3.2.41",
+    "vue-types": "^4.2.1"
   },
   "devDependencies": {
     "@types/node": "^18.11.7",

+ 17 - 0
pnpm-lock.yaml

@@ -11,11 +11,13 @@ specifiers:
   vite-plugin-html-env: ^1.2.7
   vue: ^3.2.41
   vue-tsc: ^1.0.9
+  vue-types: ^4.2.1
 
 dependencies:
   consola: 2.15.3
   pinia: 2.0.23_l7r24p6nevbtlimqmqcwa3ouhu
   vue: 3.2.41
+  vue-types: 4.2.1_vue@3.2.41
 
 devDependencies:
   '@types/node': 18.11.7
@@ -542,6 +544,11 @@ packages:
     engines: {node: '>=0.12.0'}
     dev: true
 
+  /is-plain-object/5.0.0:
+    resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
   /magic-string/0.25.9:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
@@ -745,6 +752,16 @@ packages:
       typescript: 4.8.4
     dev: true
 
+  /vue-types/4.2.1_vue@3.2.41:
+    resolution: {integrity: sha512-DNQZmJuOvovLUIp0BENRkdnZHbI0V4e2mNvjAZOAXKD56YGvRchtUYOXA/XqTxdv7Ng5SJLZqRKRpAhm5NLaPQ==}
+    engines: {node: '>=12.16.0'}
+    peerDependencies:
+      vue: ^2.0.0 || ^3.0.0
+    dependencies:
+      is-plain-object: 5.0.0
+      vue: 3.2.41
+    dev: false
+
   /vue/3.2.41:
     resolution: {integrity: sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==}
     dependencies:

+ 23 - 6
src/App.vue

@@ -3,7 +3,13 @@ import { onMounted, ref, h } from "vue";
 import { createApp } from "/@/hooks/userApp";
 import tagView from "/@/components/custom/tagView.vue";
 import LoadingLogo from "/@/components/basic/loading.vue";
+import MiniMap from "/@/components/basic/miniMap.vue";
+import { useSceneStore } from "./store/modules/scene";
+import type { FloorsType } from "./store/modules/scene";
+const sceneStore = useSceneStore();
+
 const scene$ = ref<HTMLElement | null>(null);
+const refMiniMap = ref<HTMLElement | null>(null);
 const show = ref(false);
 onMounted(async () => {
   const app = await createApp({
@@ -11,7 +17,7 @@ onMounted(async () => {
     num: "KK-t-KwmO6julDh",
     mobile: true,
   });
-  console.log("app", app);
+
   app.use("MinMap", { theme: { camera_fillStyle: "#ED5D18" } });
   app.use("Tag");
   app.use("TourPlayer");
@@ -33,14 +39,22 @@ onMounted(async () => {
   app.Scene.on("ready", () => {
     show.value = true;
   });
-  app.store.on("metadata", (metadata) => {
-    console.log("metadata", metadata);
+  app.store.on("metadata", (metadata: KankanMetaDataType) => {
+    // console.log("metadata", JSON.stringify(metadata));
+    sceneStore.load(metadata);
     // store.commit("scene/load", metadata);
-    // if (!metadata.controls.showMap) {
-    //   app.MinMap.hide(true);
-    // }
+    if (!metadata.controls.showMap) {
+      app.MinMap.hide(true);
+    }
     // dataLoaded.value = true;
   });
+  app.store.on("floorcad", (floor) => {
+    if (floor?.floors as FloorsType[]) {
+      sceneStore.loadFloorData(floor.floors);
+    }
+  });
+
+  // store.commit("scene/loadFloorData", floor)
 });
 </script>
 
@@ -48,6 +62,9 @@ onMounted(async () => {
   <LoadingLogo :thumb="true" />
   <div class="ui-view-layout" :class="{ show: show }">
     <div class="scene" ref="scene$"></div>
+    <!-- 小地图 start -->
+    <MiniMap :to="refMiniMap" @change-mode="" />
+    <!-- 小地图 end -->
   </div>
 </template>
 

+ 260 - 0
src/components/basic/guide.vue

@@ -0,0 +1,260 @@
+<template>
+  <div v-if="show && isMobile" class="user-guide-overlay">
+    <div class="user-guide-mobile">
+      <div class="zh">
+        <div class="btn" @click="onSet"></div>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { onMounted, watch, computed, ref, nextTick } from "vue";
+import { useApp } from "@/app";
+import browser from "@/utils/browser";
+import { useStore } from "vuex";
+
+const isMobile = browser.isMobile();
+const show = computed(() => store.getters["player"].showUserGuide);
+const store = useStore();
+
+const onSet = () => {
+  store.commit("showUserGuide", false);
+  localStorage.setItem("user_guide", Date.now());
+};
+const getTips = (tips) => {
+  let text = tips.split("<br />");
+  return `<span>${text[0]}</span><div>${text[1]}</div>`;
+};
+useApp().then((app) => {
+  app.Scene.on("loaded", () => {
+    if (!localStorage.getItem("user_guide")) {
+      store.commit("showUserGuide", true);
+    }
+  });
+});
+</script>
+<style lang="scss" scoped>
+.user-guide-overlay {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1099999;
+  background-color: rgba(0, 0, 0, 0.7);
+}
+.user-guide {
+  white-space: normal;
+  position: absolute;
+  top: 50%;
+  left: 1.2rem;
+  right: 1.2rem;
+  border-radius: 0.15rem;
+  color: #fff;
+  transform: translateY(-50%);
+  background-color: rgba(0, 0, 0, 0.7);
+  z-index: 999999;
+  .main {
+    padding: 0.8rem;
+    padding-bottom: 0;
+    h4 {
+      margin: 0;
+      font-size: 0.5rem;
+      text-align: center;
+    }
+    ul,
+    li {
+      list-style: none;
+      padding: 0;
+      margin: 0;
+      width: 100%;
+    }
+    ul {
+      margin: 0.4rem 0;
+    }
+    li {
+      display: flex;
+      align-items: center;
+      padding: 0.6rem 0;
+      i {
+        font-size: 1.2rem;
+      }
+      > div {
+        line-height: 1.6;
+        font-size: 0.4rem;
+        margin-left: 0.35rem;
+      }
+    }
+
+    button {
+      width: 100%;
+      margin-bottom: 0.5rem;
+      background-color: #fff;
+      font-size: 0.4rem;
+      color: #444;
+      height: 1.1rem;
+      border: none;
+      border-radius: 0.15rem;
+      &[type="submit"] {
+        color: #fff;
+        background-color: transparent;
+      }
+    }
+  }
+
+  @media (orientation: landscape) {
+    width: 400px;
+    left: 50% !important;
+    right: auto !important;
+    margin-left: -200px;
+    .main {
+      padding: 0.5rem;
+      h4 {
+        font-size: 0.35rem;
+      }
+      ul {
+        margin: 0.3rem 0;
+      }
+      li {
+        padding: 0.1rem 0;
+        i {
+          font-size: 0.6rem;
+        }
+        > div {
+          font-size: 0.25rem;
+        }
+      }
+
+      button {
+        margin-bottom: 0rem;
+        font-size: 0.3rem;
+        height: 0.8rem;
+      }
+    }
+  }
+}
+.user-guide-mobile {
+  position: absolute;
+  top: 3.15789rem;
+  left: 50%;
+  width: 7.89474rem;
+  transform: translateX(-50%);
+  .zh {
+    width: 100%;
+    height: 7rem;
+    background-image: url(~@/assets/images/guide/novice_guide_text@2x.png);
+    background-size: contain;
+    background-position: center top;
+    background-repeat: no-repeat;
+  }
+  .en {
+    width: 100%;
+    color: #fff;
+    ul,
+    li {
+      list-style: none;
+      padding: 0;
+      margin: 0;
+      width: 100%;
+    }
+    ul {
+      margin: 0;
+    }
+    li {
+      display: flex;
+      align-items: flex-start;
+      padding: 0.5rem 0;
+      &:first-child {
+        padding-top: 0;
+      }
+      i {
+        font-size: 1.32rem;
+      }
+      > div {
+        font-size: 0.4rem;
+        margin-left: 0.3rem;
+        :deep(span) {
+          font-size: 0.6rem;
+          color: #00c2c4;
+        }
+        :deep(div) {
+          font-size: 0.5rem;
+          margin-top: 0.1rem;
+          white-space: pre-line;
+          line-height: 1.3;
+        }
+      }
+    }
+
+    .btn {
+      background-image: none;
+      color: #00c2c4;
+      line-height: 1.2rem;
+      text-align: center;
+      font-size: 0.52632rem;
+      background-image: url(~@/assets/images/guide/novice_guide_button_empty@2x.png);
+    }
+  }
+  .btn {
+    position: absolute;
+    bottom: -3.15789rem;
+    left: 50%;
+    width: 3.5rem;
+    height: 1.31579rem;
+    background-image: url(~@/assets/images/guide/novice_guide_button@2x.png);
+    background-size: contain;
+    background-position: center top;
+    background-repeat: no-repeat;
+    transform: translateX(-50%);
+  }
+
+  .guide-tips {
+    font-size: 16px;
+    margin-top: 1rem;
+    line-height: 1.5;
+  }
+
+  @media (orientation: landscape) {
+    top: 0.5rem;
+    .zh {
+      height: 4rem;
+      .btn {
+        width: 2.5rem;
+        height: 0.8rem;
+        bottom: -1.4rem;
+      }
+    }
+
+    .en {
+      li {
+        padding: 0.15rem 0;
+        i {
+          font-size: 1rem;
+        }
+        > div {
+          margin-left: 0.3rem;
+          :deep(span) {
+            font-size: 0.3rem;
+          }
+          :deep(div) {
+            font-size: 0.25rem;
+            margin-top: 0.1rem;
+          }
+        }
+      }
+      .btn {
+        height: 0.7rem;
+        line-height: 0.63rem;
+        font-size: 0.25rem;
+        bottom: -0.7rem;
+      }
+    }
+  }
+}
+
+@media (orientation: landscape) {
+  .user-guide-mobile {
+    overflow-y: auto;
+  }
+}
+</style>

+ 1 - 1
src/components/basic/loading.vue

@@ -70,7 +70,7 @@ const props = defineProps({
   },
 });
 // const store = useStore();
-const progres = ref(0);
+// const progres = ref(0);
 const enter = ref(false);
 const thumb = ref<string>("");
 const thumbStyle = ref<string>("");

+ 42 - 0
src/components/basic/miniMap.vue

@@ -0,0 +1,42 @@
+<template>
+  <teleport v-if="show" :to="to">
+    <span class="button-switch" @click.stop="toggleMap">
+      <ui-icon type="show_map_collect"></ui-icon>
+    </span>
+    <div v-if="showDollhouse" class="change" @click="changeMode">
+      <ui-icon type="show_3d_normal"></ui-icon>
+      <span> 3D模型</span>
+    </div>
+  </teleport>
+</template>
+<script lang="ts">
+import { defineComponent } from "vue";
+import type { Ref } from "vue";
+import { propTypes } from "/@/utils/propTypes";
+// Ref<>
+export default defineComponent({
+  name: "minMap",
+  props: {
+    show: propTypes.bool.def(false),
+    showDollhouse: propTypes.bool.def(false),
+    to: {
+      type: Object as PropType<Ref<HTMLElement> | null>,
+      default: "",
+    },
+  },
+  emits: ["changeMode", "toggleMap"],
+  setup(_, { emit }) {
+    const toggleMap = () => {
+      emit("toggleMap");
+    };
+    const changeMode = () => {
+      emit("changeMode", "dollhouse");
+    };
+
+    return {
+      toggleMap,
+      changeMode,
+    };
+  },
+});
+</script>

+ 3 - 1
src/hooks/userApp.ts

@@ -46,4 +46,6 @@ export function useApp(): Promise<KanKanInstance> {
     }
     return deferred
 }
-
+export function getApp(): KanKanInstance {
+    return app
+}

+ 1 - 1
src/store/modules/rtc.ts

@@ -10,7 +10,7 @@ interface RtcState {
 }
 
 export const useRtcStore = defineStore({
-    id: 'scene',
+    id: 'rtc',
     state: (): RtcState => ({
         socket: null,
         showdaogou: false,

+ 40 - 31
src/store/modules/scene.ts

@@ -1,17 +1,18 @@
 import { defineStore } from 'pinia';
+import { getApp } from '/@/hooks/userApp';
 
-interface floorsType {
+export interface FloorsType {
     id: string,
-    subgroup: string,
+    subgroup?: string,
     name: string
+    segment?: any[]
+    tagging?: any[]
+    ['vertex-xy']?: any[]
 }
 interface SceneState {
     tags: any[],
-    floors: floorsType[],
-    metadata: {
-        music?: string;
-        num?: string;
-    }
+    floors: FloorsType[],
+    metadata: KankanMetaDataType
 }
 
 export const useSceneStore = defineStore({
@@ -19,34 +20,42 @@ export const useSceneStore = defineStore({
     state: (): SceneState => ({
         tags: [],
         floors: [],
-        metadata: {
-        }
+        metadata: {} as KankanMetaDataType
     }),
     getters: {
-        musicURL() {
-            return ''
+        musicURL(): Nullable<string> {
+            const musicURl = this.metadata.music
+            if (musicURl) {
+                if (/^0\d$/.test(musicURl)) {
+                    return (window as any).resource.getStaticURL(`static/music/${musicURl}.mp3`)
+                } else {
+                    const app = getApp();
+                    return app.resource.getUserResourceURL(this.metadata.musicFile.replace(/(.+)\.(.+)/, 'music-user.$2'))
+                }
+            }
+            return null
         },
-        loadingLogoFile() {
-            console.log(arguments)
-            return ''
+        loadingLogoFile(): Nullable<string> {
+            if (this.metadata.loadingLogo == 'user') {
+                const app = getApp();
+                return app.resource.getUserResourceURL(this.metadata.loadingLogoFile)
+            }
+            return null
         }
-        // musicURL: (state, getters, rootState, rootGetters) => {
-        //     let metadata = getters.metadata
-        //     if (metadata.music) {
-        //         if (/^0\d$/.test(metadata.music)) {
-        //             return resource.getStaticURL(`static/music/${metadata.music}.mp3`)
-        //         } else {
-        //             return getApp().resource.getUserResourceURL(metadata.musicFile.replace(/(.+)\.(.+)/, 'music-user.$2'))
-        //         }
-        //     }
-        //     return null
-        // },
-        // loadingLogoFile: (state, getters, rootState, rootGetters) => {
-        //     if (state.metadata.loadingLogo == 'user') {
-        //         return getApp().resource.getUserResourceURL(state.metadata.loadingLogoFile)
-        //     }
-        //     return null
-        // },
+
     },
+    actions: {
+        load(metadata: KankanMetaDataType): void {
+            this.metadata = metadata
+        },
+        loadFloorData(floors: FloorsType[]) {
+            if (floors?.length) {
+                this.floors = floors.map(item => {
+                    return { id: item.subgroup, name: item.name }
+                }) as FloorsType[]
+            }
+
+        },
+    }
 
 })

+ 34 - 0
src/utils/propTypes.ts

@@ -0,0 +1,34 @@
+import { CSSProperties, VNodeChild } from 'vue';
+import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types';
+
+export type VueNode = VNodeChild | JSX.Element;
+
+type PropTypes = VueTypesInterface & {
+  readonly style: VueTypeValidableDef<CSSProperties>;
+  readonly VNodeChild: VueTypeValidableDef<VueNode>;
+  // readonly trueBool: VueTypeValidableDef<boolean>;
+};
+
+const propTypes = createTypes({
+  func: undefined,
+  bool: undefined,
+  string: undefined,
+  number: undefined,
+  object: undefined,
+  integer: undefined,
+}) as PropTypes;
+
+propTypes.extend([
+  {
+    name: 'style',
+    getter: true,
+    type: [String, Object],
+    default: undefined,
+  },
+  {
+    name: 'VNodeChild',
+    getter: true,
+    type: undefined,
+  },
+]);
+export { propTypes };

+ 53 - 0
types/sdk.d.ts

@@ -12,3 +12,56 @@ declare interface KanKanInstance {
   CadCadManager: CadCadManagerType
 }
 
+declare interface KankanMetaDataType {
+  num: string
+  floorLogo: string
+  floorLogoSize: number,
+  floorLogoFile: string
+  music: string
+  musicFile: string
+  scenePassword: string,
+  title: string
+  description: string
+  controls: {
+    showMap: number
+    showLock: number
+    showTitle: number
+    showPanorama: number
+    showDollhouse: number
+    showFloorplan: number
+    showVR: number
+    showTour: number
+    showRule:number
+  },
+  createTime:string
+  version: number,
+  imgVersion: number,
+  linkVersion: number
+  floorPlanUser:number
+  entry: any,
+  sceneResolution: string,
+  sceneFrom: string
+  sceneKind: string
+  boxPhotos: string,
+  boxModels: string,
+  videos: {
+    data: [{ blend_fov: string, id: string, value:string }],
+    upPath: string,
+    version: number,
+  },
+  tags: number,
+  loadingLogo: string,
+  loadingLogoFile: string,
+  dataSync: any,
+  floorPlanAngle: number,
+  floorPlanCompass: number,
+  floorPlanUpload: any,
+  tours: number,
+  mosaic: number,
+  mosaicList: [],
+  waterMark: any,
+  links: number,
+  filters: number,
+  roiFilter: any,
+  surveillances: number,
+}