Forráskód Böngészése

Merge branch 'master' of http://192.168.0.115:3000/shaogen1995/HQ_yueYue into master

shaogen1995 2 napja
szülő
commit
6629497c42

+ 2 - 3
小程序入口_嵌套展示端/app.json

@@ -1,9 +1,8 @@
 {
   "pages": [
     "pages/index/index",
-    "pages/download/index"
-    
-    
+    "pages/download/index",
+    "pages/ar/index"
   ],
   "window": {
     "navigationBarTextStyle": "black",

+ 48 - 0
小程序入口_嵌套展示端/components/common/share-behavior.js

@@ -0,0 +1,48 @@
+export default Behavior({
+  created: function () {
+    this.checkInitShare();
+  },
+  methods: {
+    checkInitShare() {
+      wx.xrScene = undefined;
+
+      if (!this.scene) {
+        setTimeout(() => {
+          this.checkInitShare()
+        }, 100);
+        return;
+      }
+
+      if (this.scene.ar) {
+        if (this.scene.ar.ready) {
+          this.initARTrackerState(this.scene);    
+        } else { 
+          this.scene.event.add('ar-ready', () => this.initARTrackerState(this.scene));
+        }
+      }
+
+      if (!this.scene.share.supported) {
+        console.warn('Not support xr-frame share system now!');
+        return;
+      }
+
+      this.sharing = false;
+      wx.xrScene = this.scene;
+    },
+    initARTrackerState(scene) {
+      const xrFrameSystem = wx.getXrFrameSystem();
+      scene.dfs(() => {}, undefined, true, el => {
+        const comp = el.getComponent(xrFrameSystem.ARTracker);
+        if (comp) {
+          if (typeof comp.state === 'number') {
+            this.triggerEvent('arTrackerState', {state: comp.state, error: comp.errorMessage});
+            el.event.add('ar-tracker-state', tracker => {
+              this.triggerEvent('arTrackerState', {state: tracker.state, error: tracker.errorMessage});
+            });
+          }
+          return true;
+        }
+      });
+    }
+  }
+})

+ 791 - 0
小程序入口_嵌套展示端/components/xr-ar-2dmarker/index.js

@@ -0,0 +1,791 @@
+Component({
+  behaviors: [require('../common/share-behavior').default],
+  properties: {
+    markerImg: {
+      type: String
+    },
+  },
+  data: {
+    loaded: false,
+    arReady: false,
+    isStartPlay1: false,
+    adlScale: '0 0 0',
+    adlPos: '-0.1 -0.5 -0.05',
+    sunReady: false,
+    tigerReady: false
+  },
+  lifetimes: {
+    attached() {
+    },
+    detached() {
+      this.stopSunRotation();
+      this.stopTiger();
+    }
+  },
+  methods: {
+    handleReady({
+      detail
+    }) {
+      const xrScene = this.scene = detail.value;
+      // console.log('xr-scene', xrScene);
+    },
+    handleAssetsProgress: function ({
+      detail
+    }) {
+      // console.log('assets progress', detail.value);
+    },
+    handleAssetsLoaded: function ({
+      detail
+    }) {
+      console.log('assets loaded', detail.value);
+      this.setData({
+        loaded: true
+      });
+    },
+    handleARReady: function ({
+      detail
+    }) {
+      console.log('arReady');
+      this.setData({
+        arReady: true
+      })
+    },
+    handleItem1Loaded({ detail }) {
+      const el = detail.value.target;
+      const animator = el.getComponent("animator");
+      this.animator1 = animator
+      console.log('animator1', animator)
+
+      const gltf = el.getComponent("gltf");
+      this.bgGltf = gltf;
+      this.sunMeshes = this.resolveSunMeshesFromBG(gltf);
+      this.setData({ sunReady: !!(this.sunMeshes && this.sunMeshes.length) });
+      if (this.sunShouldRun) this.startSunRotation();
+
+      this.tigerMeshes = this.resolveTigerMeshesFromBG(gltf);
+      this.setData({ tigerReady: !!(this.tigerMeshes && this.tigerMeshes.length) });
+      if (this.tigerShouldRun && this.tigerStartRequested) this.scheduleTigerStart();
+    },
+    handleItem2Loaded({ detail }) {
+      const el = detail.value.target;
+      const animator = el.getComponent("animator");
+      this.animator2 = animator;
+    },
+    handleARTrackerState1({
+      detail
+    }) {
+      // 事件的值即为`ARTracker`实例
+      const tracker = detail.value;
+      // 获取当前状态和错误信息
+      console.log('tracker', tracker)
+
+      const {
+        state,
+      } = tracker;
+      if (state == 2) {
+        this.play()
+      } else {
+        this.pause()
+      }
+    },
+    play() {
+      if (!this.data.loaded) return
+      this.sunShouldRun = true;
+      this.startSunRotation();
+      this.tigerShouldRun = true;
+      this.tigerStartRequested = false;
+      this.prepareTigerAudio();
+      this.prepareTigerFrames();
+
+      if (!this.data.isStartPlay1) {
+        this.setData({
+          isStartPlay1: true
+        }, () => {
+          this.playitem1Action()
+        })
+      }
+    },
+    pause() {
+      this.sunShouldRun = false;
+      this.stopSunRotation();
+      this.tigerShouldRun = false;
+      this.tigerStartRequested = false;
+      this.stopTiger();
+    },
+    resolveSunMeshesFromBG(gltf) {
+      if (!gltf || !this.scene) return [];
+
+      const candidates = this.getSunNameCandidates();
+      const uniqueCandidates = [];
+      for (const name of candidates) {
+        if (name && !uniqueCandidates.includes(name)) uniqueCandidates.push(name);
+      }
+
+      for (const name of uniqueCandidates) {
+        if (typeof gltf.getPrimitivesByNodeName === 'function') {
+          const meshes = gltf.getPrimitivesByNodeName(name);
+          if (meshes && meshes.length) return meshes;
+        }
+        if (typeof gltf.getPrimitivesByMeshName === 'function') {
+          const meshes = gltf.getPrimitivesByMeshName(name);
+          if (meshes && meshes.length) return meshes;
+        }
+      }
+
+      if (typeof gltf.getMeshes === 'function') {
+        const meshes = gltf.getMeshes() || [];
+        const meshNames = meshes.map(m => m && (m.name || m.el && m.el.name)).filter(Boolean);
+        console.warn('sun mesh not found, available mesh names:', meshNames);
+      } else {
+        console.warn('sun mesh not found');
+      }
+      return [];
+    },
+    getSunNameCandidates() {
+      const defaultCandidates = ['Sun', 'sun', 'SUN', '太阳', 'Taiyang', 'taiyang'];
+      if (!this.scene) return defaultCandidates;
+
+      const gltfAsset = this.scene.assets && this.scene.assets.getAsset && this.scene.assets.getAsset('gltf', 'bg');
+      const jsonRaw = gltfAsset && gltfAsset.jsonRaw;
+      if (!jsonRaw) return defaultCandidates;
+
+      const names = [];
+      const addIfMatch = (n) => {
+        if (!n || typeof n !== 'string') return;
+        if (/Sun/i.test(n) || n.includes('太阳')) names.push(n);
+      };
+
+      if (Array.isArray(jsonRaw.nodes)) {
+        for (const node of jsonRaw.nodes) addIfMatch(node && node.name);
+      }
+      if (Array.isArray(jsonRaw.meshes)) {
+        for (const mesh of jsonRaw.meshes) addIfMatch(mesh && mesh.name);
+      }
+
+      return names.length ? names.concat(defaultCandidates) : defaultCandidates;
+    },
+    startSunRotation() {
+      if (!this.scene) return;
+      if (!this.sunShouldRun) return;
+      if (!this.sunMeshes || !this.sunMeshes.length) return;
+      if (this.sunRotationTimer) return;
+
+      this.sunFrameCount = 159;
+      this.sunFrameIndex = this.sunFrameIndex || 1;
+      this.sunTextureCacheLimit = 12;
+      if (!this.sunTextureCache) this.sunTextureCache = new Map();
+      if (!this.sunTextureQueue) this.sunTextureQueue = [];
+
+      this.updateSunFrame();
+      this.sunRotationTimer = setInterval(() => this.updateSunFrame(), 80);
+    },
+    stopSunRotation() {
+      if (this.sunRotationTimer) {
+        clearInterval(this.sunRotationTimer);
+        this.sunRotationTimer = null;
+      }
+      this.sunFrameLoading = false;
+      if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.sunTextureQueue) {
+        for (const assetId of this.sunTextureQueue) {
+          this.scene.assets.releaseAsset('texture', assetId);
+        }
+      }
+      this.sunTextureCache = null;
+      this.sunTextureQueue = null;
+    },
+    async updateSunFrame() {
+      if (this.sunFrameLoading) return;
+      if (!this.scene || !this.sunMeshes || !this.sunMeshes.length) return;
+      if (!this.sunShouldRun) return;
+
+      this.sunFrameLoading = true;
+      try {
+        const frame = this.sunFrameIndex;
+        this.sunFrameIndex = frame >= this.sunFrameCount ? 1 : frame + 1;
+
+        const frameStr = String(frame).padStart(3, '0');
+        const assetId = `sun-${frameStr}`;
+        const src = `https://ossxiaoan.4dage.com/hq-eduction-vr/sun/Sun_${frameStr}.png`;
+
+        let texture = this.sunTextureCache && this.sunTextureCache.get(assetId);
+        if (!texture) {
+          const result = await this.scene.assets.loadAsset({ type: 'texture', assetId, src });
+          texture = result && result.value;
+          if (texture) {
+            if (this.sunTextureCache) this.sunTextureCache.set(assetId, texture);
+            if (this.sunTextureQueue) this.sunTextureQueue.push(assetId);
+            while (this.sunTextureQueue && this.sunTextureQueue.length > this.sunTextureCacheLimit) {
+              const oldAssetId = this.sunTextureQueue.shift();
+              if (oldAssetId) {
+                this.scene.assets.releaseAsset('texture', oldAssetId);
+                if (this.sunTextureCache) this.sunTextureCache.delete(oldAssetId);
+              }
+            }
+          }
+        }
+
+        if (texture) {
+          for (const mesh of this.sunMeshes) {
+            if (mesh && mesh.material && typeof mesh.material.setTexture === 'function') {
+              mesh.material.setTexture('u_baseColorMap', texture);
+            }
+          }
+        }
+      } catch (e) {
+        console.warn('updateSunFrame failed', e);
+      } finally {
+        this.sunFrameLoading = false;
+      }
+    },
+    resolveTigerMeshesFromBG(gltf) {
+      if (!gltf || !this.scene) return [];
+
+      const candidates = this.getTigerNameCandidates();
+      const uniqueCandidates = [];
+      for (const name of candidates) {
+        if (name && !uniqueCandidates.includes(name)) uniqueCandidates.push(name);
+      }
+
+      for (const name of uniqueCandidates) {
+        if (typeof gltf.getPrimitivesByNodeName === 'function') {
+          const meshes = gltf.getPrimitivesByNodeName(name);
+          if (meshes && meshes.length) return meshes;
+        }
+        if (typeof gltf.getPrimitivesByMeshName === 'function') {
+          const meshes = gltf.getPrimitivesByMeshName(name);
+          if (meshes && meshes.length) return meshes;
+        }
+      }
+
+      if (typeof gltf.getMeshes === 'function') {
+        const meshes = gltf.getMeshes() || [];
+        const meshNames = meshes.map(m => m && (m.name || m.el && m.el.name)).filter(Boolean);
+        console.warn('tiger mesh not found, available mesh names:', meshNames);
+      } else {
+        console.warn('tiger mesh not found');
+      }
+      return [];
+    },
+    getTigerNameCandidates() {
+      const defaultCandidates = ['Tiger', 'tiger', 'TIGER', '老虎', '虎', 'Laohu', 'laohu', 'hu'];
+      if (!this.scene) return defaultCandidates;
+
+      const gltfAsset = this.scene.assets && this.scene.assets.getAsset && this.scene.assets.getAsset('gltf', 'bg');
+      const jsonRaw = gltfAsset && gltfAsset.jsonRaw;
+      if (!jsonRaw) return defaultCandidates;
+
+      const names = [];
+      const addIfMatch = (n) => {
+        if (!n || typeof n !== 'string') return;
+        if (/tiger/i.test(n) || /laohu/i.test(n) || n.includes('虎')) names.push(n);
+      };
+
+      if (Array.isArray(jsonRaw.nodes)) {
+        for (const node of jsonRaw.nodes) addIfMatch(node && node.name);
+      }
+      if (Array.isArray(jsonRaw.meshes)) {
+        for (const mesh of jsonRaw.meshes) addIfMatch(mesh && mesh.name);
+      }
+
+      return names.length ? names.concat(defaultCandidates) : defaultCandidates;
+    },
+    scheduleTigerStart() {
+      if (!this.scene) return;
+      if (!this.tigerShouldRun) return;
+      if (!this.tigerStartRequested) return;
+      if (this.tigerCompleted) return;
+      if (this.tigerRotationTimer) return;
+      if (!this.tigerMeshes || !this.tigerMeshes.length) return;
+      if (this.tigerAppearTimer) return;
+
+      this.tigerAppearTimer = setInterval(() => {
+        if (!this.tigerShouldRun || this.tigerCompleted) {
+          this.stopTigerAppearWatch();
+          return;
+        }
+
+        const visibleMeshes = this.getVisibleTigerMeshes();
+        if (!visibleMeshes.length) return;
+
+        this.stopTigerAppearWatch();
+        this.tigerTargetMeshes = visibleMeshes;
+        this.startTigerAudio();
+        this.startTigerAnimation();
+      }, 100);
+    },
+    stopTigerAppearWatch() {
+      if (!this.tigerAppearTimer) return;
+      clearInterval(this.tigerAppearTimer);
+      this.tigerAppearTimer = null;
+    },
+    isTigerVisible() {
+      return this.getVisibleTigerMeshes().length > 0;
+    },
+    getVisibleTigerMeshes() {
+      if (!this.tigerMeshes || !this.tigerMeshes.length) return [];
+      const visible = [];
+
+      for (const mesh of this.tigerMeshes) {
+        const el = mesh && mesh.el;
+        if (!el || typeof el.getComponent !== 'function') continue;
+        try {
+          const transform = el.getComponent('transform');
+          const scale = transform && transform.scale;
+          if (!scale) continue;
+
+          if (typeof scale === 'string') {
+            const parts = scale.split(/\s+/).map(Number);
+            const maxV = Math.max(...parts.filter(n => Number.isFinite(n)));
+            if (Number.isFinite(maxV) && maxV > 0.001) visible.push(mesh);
+            continue;
+          }
+
+          if (typeof scale === 'object') {
+            const sx = typeof scale.x === 'number' ? scale.x : undefined;
+            const sy = typeof scale.y === 'number' ? scale.y : undefined;
+            const sz = typeof scale.z === 'number' ? scale.z : undefined;
+            const maxV = Math.max(sx || 0, sy || 0, sz || 0);
+            if (maxV > 0.001) visible.push(mesh);
+          }
+        } catch (e) {
+        }
+      }
+
+      return visible;
+    },
+    startTigerAnimation() {
+      if (!this.scene) return;
+      if (!this.tigerShouldRun) return;
+      if (!this.tigerMeshes || !this.tigerMeshes.length) return;
+      if (this.tigerCompleted) return;
+      if (this.tigerRotationTimer) return;
+
+      this.tigerFrameStart = 1;
+      this.tigerFrameEnd = 460;
+      this.tigerFrameCount = 460;
+      const skipFrames = 29;
+      this.tigerPlayedFrames = Math.min(skipFrames, this.tigerFrameCount);
+      this.tigerTextureCacheLimit = 48;
+      if (!this.tigerTextureCache) this.tigerTextureCache = new Map();
+      if (!this.tigerTextureQueue) this.tigerTextureQueue = [];
+      this.prepareTigerFrames();
+
+      this.updateTigerFrame();
+      const intervalMs = this.getTigerFrameIntervalMs();
+      this.tigerRotationTimer = setInterval(() => this.updateTigerFrame(), intervalMs);
+    },
+    getTigerFrameIntervalMs() {
+      const durationMs = Number.isFinite(this.tigerAudioDurationMs) && this.tigerAudioDurationMs > 0
+        ? this.tigerAudioDurationMs
+        : 46000;
+      const frameCount = typeof this.tigerFrameCount === 'number' && this.tigerFrameCount > 0 ? this.tigerFrameCount : 460;
+      const raw = Math.round(durationMs / frameCount);
+      return Math.min(500, Math.max(16, raw));
+    },
+    stopTiger() {
+      this.stopTigerAppearWatch();
+      if (this.tigerRotationTimer) {
+        clearInterval(this.tigerRotationTimer);
+        this.tigerRotationTimer = null;
+      }
+      this.tigerFrameLoading = false;
+      this.tigerTargetMeshes = null;
+      if (this.scene && this.scene.assets && this.scene.assets.releaseAsset && this.tigerTextureQueue) {
+        for (const assetId of this.tigerTextureQueue) {
+          if (assetId === this.tigerLastAssetId) continue;
+          this.scene.assets.releaseAsset('texture', assetId);
+        }
+      }
+      this.tigerTextureCache = null;
+      this.tigerTextureQueue = null;
+      this.stopTigerAudio();
+      this.stopTigerFramesPreload();
+    },
+    prepareTigerFrames() {
+      if (this.tigerFramesPrepared || this.tigerFramesPreparing) return;
+      this.tigerFramesPreparing = true;
+      this.tigerFramesAbort = false;
+
+      if (!this.tigerFrameTempPaths) this.tigerFrameTempPaths = Object.create(null);
+      if (!this.tigerFrameDownloadTasks) this.tigerFrameDownloadTasks = Object.create(null);
+
+      const startNo = 39;
+      const endNo = 460;
+      const maxConcurrent = 6;
+      let nextNo = startNo;
+      let inFlight = 0;
+
+      const pump = () => {
+        if (this.tigerFramesAbort) return;
+
+        while (inFlight < maxConcurrent && nextNo <= endNo && !this.tigerFramesAbort) {
+          const frameNo = nextNo++;
+          const frameStr = String(frameNo).padStart(3, '0');
+          const assetId = `tigerx-${frameStr}`;
+          if (this.tigerFrameTempPaths[assetId]) continue;
+
+          inFlight += 1;
+          const task = wx.downloadFile({
+            url: `https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/Tigerx_${frameStr}.png`,
+            success: (res) => {
+              if (this.tigerFramesAbort) return;
+              const tempPath = res && res.statusCode === 200 ? res.tempFilePath : '';
+              if (tempPath) this.tigerFrameTempPaths[assetId] = tempPath;
+            },
+            complete: () => {
+              inFlight -= 1;
+              if (this.tigerFrameDownloadTasks) delete this.tigerFrameDownloadTasks[assetId];
+              if (nextNo > endNo && inFlight <= 0) {
+                this.tigerFramesPrepared = true;
+                this.tigerFramesPreparing = false;
+                return;
+              }
+              pump();
+            }
+          });
+          if (task && typeof task.abort === 'function') this.tigerFrameDownloadTasks[assetId] = task;
+        }
+      };
+
+      pump();
+    },
+    stopTigerFramesPreload() {
+      this.tigerFramesAbort = true;
+      this.tigerFramesPrepared = false;
+      this.tigerFramesPreparing = false;
+      if (this.tigerFrameDownloadTasks) {
+        for (const k of Object.keys(this.tigerFrameDownloadTasks)) {
+          const task = this.tigerFrameDownloadTasks[k];
+          if (task && typeof task.abort === 'function') {
+            try {
+              task.abort();
+            } catch (e) {
+            }
+          }
+        }
+      }
+      this.tigerFrameDownloadTasks = null;
+      this.tigerFrameTempPaths = null;
+    },
+    async updateTigerFrame() {
+      if (this.tigerFrameLoading) return;
+      if (!this.scene || !this.tigerMeshes || !this.tigerMeshes.length) return;
+      if (!this.tigerShouldRun) return;
+      if (this.tigerCompleted) return;
+
+      this.tigerFrameLoading = true;
+      try {
+        const frameCount = typeof this.tigerFrameCount === 'number' && this.tigerFrameCount > 0 ? this.tigerFrameCount : 460;
+        const durationMs = Number.isFinite(this.tigerAudioDurationMs) && this.tigerAudioDurationMs > 0
+          ? this.tigerAudioDurationMs
+          : 46000;
+
+        const played = this.tigerPlayedFrames || 0;
+        let desiredPlayed = played + 1;
+
+        const audioCtx = this.tigerAudioCtx;
+        if (audioCtx && this.tigerAudioStarted) {
+          const currentTime = Number(audioCtx.currentTime);
+          if (Number.isFinite(currentTime) && currentTime >= 0) {
+            const p = Math.floor((currentTime * 1000 / durationMs) * frameCount) + 1;
+            desiredPlayed = Math.max(desiredPlayed, p);
+          }
+        }
+
+        if (desiredPlayed <= played) return;
+        if (desiredPlayed >= frameCount) desiredPlayed = frameCount;
+
+        if (played >= frameCount) {
+          this.completeTigerAnimation();
+          return;
+        }
+
+        const start = typeof this.tigerFrameStart === 'number' ? this.tigerFrameStart : 1;
+        const end = typeof this.tigerFrameEnd === 'number' ? this.tigerFrameEnd : frameCount;
+        const rangeLen = Math.max(1, end - start + 1);
+        const frameNo = start + ((desiredPlayed - 1) % rangeLen);
+        this.tigerPlayedFrames = desiredPlayed;
+
+        const frameStr = String(frameNo).padStart(3, '0');
+        const assetId = `tigerx-${frameStr}`;
+        const localSrc = this.tigerFrameTempPaths && this.tigerFrameTempPaths[assetId];
+        const src = localSrc || `https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/Tigerx_${frameStr}.png`;
+
+        let texture = this.tigerTextureCache && this.tigerTextureCache.get(assetId);
+        if (!texture) {
+          const result = await this.scene.assets.loadAsset({ type: 'texture', assetId, src });
+          texture = result && result.value;
+          if (texture) {
+            if (this.tigerTextureCache) this.tigerTextureCache.set(assetId, texture);
+            if (this.tigerTextureQueue) this.tigerTextureQueue.push(assetId);
+            while (this.tigerTextureQueue && this.tigerTextureQueue.length > this.tigerTextureCacheLimit) {
+              const oldAssetId = this.tigerTextureQueue.shift();
+              if (oldAssetId) {
+                this.scene.assets.releaseAsset('texture', oldAssetId);
+                if (this.tigerTextureCache) this.tigerTextureCache.delete(oldAssetId);
+              }
+            }
+          }
+        }
+
+        if (texture) {
+          this.tigerLastAssetId = assetId;
+          const targets = (this.tigerTargetMeshes && this.tigerTargetMeshes.length) ? this.tigerTargetMeshes : this.tigerMeshes;
+          for (const mesh of targets) {
+            if (mesh && mesh.material && typeof mesh.material.setTexture === 'function') {
+              mesh.material.setTexture('u_baseColorMap', texture);
+            }
+          }
+        }
+
+        if (this.tigerPlayedFrames >= this.tigerFrameCount) {
+          this.completeTigerAnimation();
+        }
+      } catch (e) {
+        console.warn('updateTigerFrame failed', e);
+      } finally {
+        this.tigerFrameLoading = false;
+      }
+    },
+    completeTigerAnimation() {
+      if (this.tigerRotationTimer) {
+        clearInterval(this.tigerRotationTimer);
+        this.tigerRotationTimer = null;
+      }
+      this.tigerCompleted = true;
+    },
+    prepareTigerAudio() {
+      if (this.tigerAudioCtx || this.tigerAudioPreparing) return;
+      const url = 'https://ossxiaoan.4dage.com/hq-eduction-vr/tiger/tiger.mp3';
+
+      this.tigerAudioPrepared = false;
+      this.tigerAudioStartPending = !!this.tigerAudioStartPending;
+      this.tigerAudioStarted = false;
+      this.tigerAudioPreparing = true;
+
+      const createAudioCtx = (src) => {
+        const audioCtx = wx.createInnerAudioContext();
+        audioCtx.src = src;
+        audioCtx.loop = false;
+        audioCtx.volume = 0;
+
+        audioCtx.onCanplay(() => {
+          if (this.tigerAudioPrepared) return;
+          this.tigerAudioPrepared = true;
+          if (this.tigerAudioStarted) return;
+
+          const updateDuration = () => {
+            if (!this.tigerAudioCtx) return;
+            const d = Number(audioCtx.duration);
+            if (Number.isFinite(d) && d > 0) this.tigerAudioDurationMs = Math.round(d * 1000);
+          };
+          updateDuration();
+          setTimeout(updateDuration, 300);
+
+          try {
+            audioCtx.volume = 0;
+          } catch (e) {
+          }
+          try {
+            audioCtx.play();
+          } catch (e) {
+          }
+          setTimeout(() => {
+            if (!this.tigerAudioCtx) return;
+            if (this.tigerAudioStarted) return;
+            try {
+              audioCtx.pause();
+            } catch (e) {
+            }
+            try {
+              if (Number(audioCtx.currentTime) > 0.1) audioCtx.seek(0);
+            } catch (e) {
+            }
+            if (this.tigerAudioStartPending) this.startTigerAudio();
+          }, 200);
+        });
+
+        audioCtx.onEnded(() => {
+          this.stopTigerAudio();
+          this.completeTigerAnimation();
+        });
+
+        this.tigerAudioCtx = audioCtx;
+        this.tigerAudioPreparing = false;
+
+        try {
+          audioCtx.play();
+        } catch (e) {
+        }
+      };
+
+      if (this.tigerAudioTempPath) {
+        createAudioCtx(this.tigerAudioTempPath);
+        return;
+      }
+
+      wx.downloadFile({
+        url,
+        success: (res) => {
+          const tempPath = res && res.statusCode === 200 ? res.tempFilePath : '';
+          if (tempPath) this.tigerAudioTempPath = tempPath;
+          createAudioCtx(tempPath || url);
+        },
+        fail: () => {
+          createAudioCtx(url);
+        }
+      });
+    },
+    startTigerAudio() {
+      if (this.tigerAudioStarted) return;
+
+      if (!this.tigerAudioCtx) {
+        this.tigerAudioStartPending = true;
+        this.prepareTigerAudio();
+      }
+      if (!this.tigerAudioCtx) return;
+      if (this.tigerAudioStarted) return;
+
+      const audioCtx = this.tigerAudioCtx;
+      const requestedOffsetSec = typeof this.tigerAudioStartOffsetSec === 'number' ? this.tigerAudioStartOffsetSec : 2;
+      const startOffsetSec = Math.max(0, Math.min(3, requestedOffsetSec));
+      const startNow = () => {
+        if (!this.tigerAudioCtx) return;
+        if (this.tigerAudioStarted) return;
+        try {
+          audioCtx.volume = 1;
+        } catch (e) {
+        }
+        try {
+          const t = Number(audioCtx.currentTime);
+          if (startOffsetSec > 0) {
+            if (!Number.isFinite(t) || Math.abs(t - startOffsetSec) > 0.2) audioCtx.seek(startOffsetSec);
+          } else {
+            if (Number.isFinite(t) && t > 0.1) audioCtx.seek(0);
+          }
+        } catch (e) {
+        }
+        try {
+          audioCtx.play();
+          this.tigerAudioStarted = true;
+        } catch (e) {
+          this.tigerAudioStarted = false;
+        }
+      };
+
+      if (this.tigerAudioPrepared) {
+        this.tigerAudioStartPending = false;
+        startNow();
+        return;
+      }
+
+      this.tigerAudioStartPending = true;
+      startNow();
+    },
+    stopTigerAudio() {
+      if (!this.tigerAudioCtx) return;
+      this.tigerAudioCtx.stop();
+      this.tigerAudioCtx.destroy();
+      this.tigerAudioCtx = null;
+      this.tigerAudioPrepared = false;
+      this.tigerAudioStartPending = false;
+      this.tigerAudioStarted = false;
+      this.tigerAudioPreparing = false;
+      this.tigerAudioDurationMs = null;
+    },
+    playitem1Action() {
+      if (!this.animator1 || !this.animator2) return;
+
+      console.log('start animator1');
+      this.animator1.play('All Animations', {
+        loop: 0
+      });
+
+      setTimeout(() => {
+        console.log('start animator2');
+        this.animator2.pause();
+
+          const duration = 1500;
+          const startTime = Date.now();
+          const startScale = 0;
+          const endScale = 0.38;
+          const startPX = -0.1
+          const endPX = 0
+          const startPY = -0.5
+          const endPY = 0
+          const startPZ = -0.05
+          const endPZ = 0
+
+          const step = () => {
+            const now = Date.now();
+            let t = (now - startTime) / duration;
+            if (t > 1) t = 1;
+
+            const s = startScale + (endScale - startScale) * t;
+            const scaleStr = `${s} ${s} ${s}`;
+            const p = `${startPX + (endPX - startPX) * t} ${startPY + (endPY - startPY) * t} ${startPZ + (endPZ - startPZ) * t}`;
+
+            if (t >= 0.5 && !this.isStartPlay2) {
+              this.isStartPlay2 = true;
+              this.animator2.play('All Animations', {
+                loop: 0
+              });
+            }
+
+            this.setData({
+              adlScale: scaleStr,
+              adlPos: p
+            });
+
+            if (t < 1) {
+              setTimeout(step, 16); // 60fps
+            } else {
+              const audioUrl = 'https://houseoss.4dkankan.com/project/hq-eduction-vr/public/%E5%AE%89%E5%BE%B7%E7%83%88.mp3';
+              if (!this.audioCtx) {
+                const audioCtx = wx.createInnerAudioContext();
+                audioCtx.src = audioUrl;
+                audioCtx.play();
+                audioCtx.onEnded(() => {
+                  const hideDuration = 1000;
+                  const hideStartTime = Date.now();
+                  const startScaleHide = 0.38;
+                  const endScaleHide = 0;
+
+                  const hideStep = () => {
+                    const now = Date.now();
+                    let t = (now - hideStartTime) / hideDuration;
+                    if (t > 1) t = 1;
+
+                    const s = startScaleHide + (endScaleHide - startScaleHide) * t;
+                    const scaleStr = `${s} ${s} ${s}`;
+                    this.setData({
+                      adlScale: scaleStr
+                    });
+
+                    if (t < 1) {
+                      setTimeout(hideStep, 16);
+                    } else {
+                      this.animator2 = null;
+                      if (this.audioCtx) {
+                        this.audioCtx.destroy();
+                        this.audioCtx = null;
+                      }
+                      this.tigerStartRequested = true;
+                      this.scheduleTigerStart();
+                    }
+                  };
+
+                  hideStep();
+                });
+              }
+            }
+          };
+
+          this.setData({
+            adlScale: '0 0 0'
+          });
+          step();
+      }, 1500);
+    }
+  }
+})

+ 5 - 0
小程序入口_嵌套展示端/components/xr-ar-2dmarker/index.json

@@ -0,0 +1,5 @@
+{
+  "component": true,
+  "usingComponents": {},
+  "renderer": "xr-frame"
+}

+ 22 - 0
小程序入口_嵌套展示端/components/xr-ar-2dmarker/index.wxml

@@ -0,0 +1,22 @@
+<xr-scene ar-system="modes:Marker" id="xr-scene" bind:ready="handleReady" bind:ar-ready="handleARReady">
+  <xr-assets bind:progress="handleAssetsProgress" bind:loaded="handleAssetsLoaded">
+    <xr-asset-load type="gltf" asset-id="bg" src="https://houseoss.4dkankan.com/project/hq-eduction-vr/public/BG/HQ.gltf" options="preserveRaw: true" />
+    <xr-asset-load type="gltf" asset-id="adl" src="https://houseoss.4dkankan.com/project/hq-eduction-vr/public/adl-gltf/adl.gltf" options="preserveRaw: true" />
+  </xr-assets>
+
+  <xr-env env-data="xr-frame-team-workspace-day" />
+  
+  <xr-node wx:if="{{arReady}}">
+    <xr-ar-tracker mode="Marker" src="{{markerImg}}" bind:ar-tracker-state="handleARTrackerState1">
+      <xr-gltf vis model="bg" position="0 0 0" scale="0.5 0.5 0.5" rotation="-90 0 0" bind:gltf-loaded="handleItem1Loaded" />
+
+      <xr-gltf vis model="adl" position="{{adlPos}}" scale="{{adlScale}}" rotation="-90 0 0" bind:gltf-loaded="handleItem2Loaded" />
+    </xr-ar-tracker>
+
+    <xr-camera id="camera" position="1 1 1" node-id="camera" clear-color="0.925 0.925 0.925 1" background="ar" is-ar-camera></xr-camera>
+  </xr-node>
+  <xr-node node-id="lights">
+    <xr-light type="ambient" color="1 1 1" intensity="1" />
+    <xr-light type="directional" rotation="180 0 0" color="1 1 1" intensity="2" />
+  </xr-node>
+</xr-scene>

+ 1 - 0
小程序入口_嵌套展示端/components/xr-ar-2dmarker/index.wxss

@@ -0,0 +1 @@
+/* components/xr-ar-2dmarker/index.wxss */

+ 45 - 0
小程序入口_嵌套展示端/pages/ar/index.js

@@ -0,0 +1,45 @@
+var sceneReadyBehavior = require('../behavior-scene/scene-ready');
+var handleDecodedXML = require('../behavior-scene/util').handleDecodedXML;
+var xmlCode = `&lt;xr-scene ar-system=&quot;modes:Marker&quot; id=&quot;xr-scene&quot; bind:ready=&quot;handleReady&quot; bind:arReady=&quot;handleARReady&quot; bind:log=&quot;handleLog&quot;&gt;
+&lt;xr-assets bind:progress=&quot;handleAssetsProgress&quot; bind:loaded=&quot;handleAssetsLoaded&quot;&gt;
+  &lt;xr-asset-material asset-id=&quot;ar-anchor&quot; effect=&quot;standrand&quot; uniforms=&quot;u_baseColorFactor:0 1 0 1&quot;&gt;&lt;/xr-asset-material&gt;
+&lt;/xr-assets&gt;
+&lt;xr-node&gt;
+  &lt;xr-node node-id=&quot;id2&quot; scale=&quot;2 2 2&quot; position=&quot;0 0 0&quot;&gt;&lt;/xr-node&gt;
+  &lt;xr-ar-tracker mode=&quot;Marker&quot; src=&quot;{{markerImg}}&quot;&gt;
+    &lt;xr-mesh geometry=&quot;cylinder&quot; material=&quot;ar-anchor&quot; /&gt;
+  &lt;/xr-ar-tracker&gt;
+  &lt;xr-camera
+    id=&quot;camera&quot; node-id=&quot;camera&quot; position=&quot;0.8 2.2 -5&quot; clear-color=&quot;0.925 0.925 0.925 1&quot;
+    target=&quot;id2&quot; background=&quot;ar&quot; is-ar-camera
+    camera-orbit-control
+  &gt;&lt;/xr-camera&gt;
+&lt;/xr-node&gt;
+&lt;xr-node node-id=&quot;lights&quot;&gt;
+  &lt;xr-light type=&quot;ambient&quot; color=&quot;1 1 1&quot; intensity=&quot;1&quot; /&gt;
+  &lt;xr-light type=&quot;directional&quot; rotation=&quot;180 0 0&quot; color=&quot;1 1 1&quot; intensity=&quot;3&quot; /&gt;
+&lt;/xr-node&gt;
+&lt;/xr-scene&gt;
+`;
+Page({
+  behaviors: [sceneReadyBehavior],
+  data: {
+    xmlCode: '<div class="codeWrap">' + handleDecodedXML(xmlCode) + '</div>',
+    markerImg: 'https://ossxiaoan.4dage.com/hq-eduction-vr/hq-bag.jpg'
+  },
+  handleChangeMarkerImg: function() {
+    wx.chooseMedia({
+      count: 1,
+      sizeType: ['compressed'],
+      mediaType: ['image'],
+      sourceType: ['album'],
+      success: res => {
+        const fp = res.tempFiles[0].tempFilePath;
+        this.setData({markerImg: fp});
+      },
+      fail: err => {
+        console.error('[xr-demo]chooseImage failed', err);
+      }
+    });
+  }
+});

+ 8 - 0
小程序入口_嵌套展示端/pages/ar/index.json

@@ -0,0 +1,8 @@
+{
+  "usingComponents": {
+    "xr-ar-2dmarker": "../../components/xr-ar-2dmarker/index"
+  },
+  "disableScroll": true,
+  "renderer": "webview",
+  "navigationStyle": "custom"
+}

+ 11 - 0
小程序入口_嵌套展示端/pages/ar/index.wxml

@@ -0,0 +1,11 @@
+<view class="ar-page">
+  <xr-ar-2dmarker
+    disable-scroll
+    id="main-frame"
+    width="{{renderWidth}}"
+    height="{{renderHeight}}"
+    style="width:{{width}}px;height:{{height}}px;top:{{top}}px;left:{{left}}px;display:block;"
+    markerImg="{{markerImg}}"
+    bind:arTrackerState="handleARTrackerState"
+  />
+</view>

+ 5 - 0
小程序入口_嵌套展示端/pages/ar/index.wxss

@@ -0,0 +1,5 @@
+.ar-page {
+  position: absolute;
+  inset: 0;
+  overflow: hidden;
+}

+ 88 - 0
小程序入口_嵌套展示端/pages/behavior-scene/scene-ready.js

@@ -0,0 +1,88 @@
+module.exports = Behavior({
+  behaviors: [],
+  properties: {
+  },
+  data: {
+    left: 0,
+    top: 0,
+    width: 0,
+    height: 0,
+    renderWidth: 0,
+    renderHeight: 0,
+    windowHeight: 1000,
+    heightScale: 1,
+    dpiScale: 1,
+    showBackBtn: false,
+    activeValues: [1],
+    arTrackerShow: false,
+    arTrackerState: 'Init',
+    arTrackerError: ''
+  },
+  attached: function(){},
+  ready() {
+    const info = wx.getSystemInfoSync();
+    const width = info.windowWidth;
+    const windowHeight = info.windowHeight;
+    const height = windowHeight * this.data.heightScale;
+    const dpi = info.pixelRatio;
+    this.setData({
+      width,
+      height,
+      renderWidth: width * dpi * this.data.dpiScale,
+      renderHeight: height * dpi * this.data.dpiScale,
+      windowHeight
+    });
+  },
+  methods: {
+    onLoad(options) {
+      wx.reportEvent("xr_frame", {
+        "xr_page_path": options.path
+      });
+    },
+    onShareAppMessage() {
+      try {
+        if (wx.xrScene) {
+          const buffer = wx.xrScene.share.captureToArrayBuffer({quality: 0.5});
+          const fp = `${wx.env.USER_DATA_PATH}/xr-frame-share.jpg`;
+          wx.getFileSystemManager().writeFileSync(fp, buffer, 'binary');
+          return {
+            title: this.getTitle(),
+            imageUrl: fp
+          };
+        }
+      } catch (e) {
+        return {
+          title: this.getTitle()
+        };
+      }
+    },
+    onShareTimeline() {
+      try {
+        if (wx.xrScene) {
+          const buffer = wx.xrScene.share.captureToArrayBuffer({quality: 0.5});
+          const fp = `${wx.env.USER_DATA_PATH}/xr-frame-share.jpg`;
+          wx.getFileSystemManager().writeFileSync(fp, buffer, 'binary');
+          return {
+            title: this.getTitle(),
+            imageUrl: fp
+          };
+        }
+      } catch (e) {
+        return {
+          title: this.getTitle()
+        }
+      }
+    },
+    getTitle() {
+      return wx.xrTitle ? `XR - ${wx.xrTitle}` : 'XR-FRAME官方示例';
+    },
+    handleARTrackerState({detail}) {
+      const {state, error} = detail;
+      this.setData({
+        arTrackerShow: true,
+        arTrackerState: wx.getXrFrameSystem().EARTrackerState[state],
+        arTrackerError: error
+      });
+    }
+  }
+})

+ 75 - 0
小程序入口_嵌套展示端/pages/behavior-scene/util.js

@@ -0,0 +1,75 @@
+var handleDecodedXML = function(decodedXml) {
+  let rerurnXml = '';
+
+  const blockArr = decodedXml.split('&lt;');
+
+  for (let i = 0; i < blockArr.length; i++) {
+    let blockStr = blockArr[i];
+    let handleBlockStr = '';
+    let returnBlockStr = '';
+
+    const sliceBlockStr = blockStr.split(' ');
+
+    for(let j = 0; j < sliceBlockStr.length; j++) {
+      const subBlockStr = sliceBlockStr[j];
+      
+      const eIndex = subBlockStr.indexOf('=');
+      if (eIndex !== -1) {
+        handleBlockStr += ' <span class="attr-name">' + subBlockStr.slice(0, eIndex) +'</span>' + subBlockStr.slice(eIndex);
+      } else {
+        handleBlockStr += subBlockStr;
+      }
+    }
+    // console.log(sliceBlockStr);
+
+    const blockEndIndexB = handleBlockStr.indexOf(' ');
+    const blockEndIndexR = handleBlockStr.indexOf('&gt;');
+    // Handle XMLTag
+    if (blockEndIndexB === -1 && blockEndIndexR === -1) {
+      continue;
+    }
+    const endBlockFlag = handleBlockStr[0] === '/';
+
+    if (blockEndIndexR !== -1) {
+      handleBlockStr += '<br>'
+    }
+    if (blockEndIndexR < blockEndIndexB) {
+      returnBlockStr = '&lt;' + (endBlockFlag ? '/' : '') + '<span class="block-name">' + handleBlockStr.slice(endBlockFlag ? 1 : 0, blockEndIndexR) + '</span>' + handleBlockStr.slice(blockEndIndexR);
+    } else if (blockEndIndexB !== -1) {
+      returnBlockStr = '&lt;' + (endBlockFlag ? '/' : '') +'<span class="block-name">' + handleBlockStr.slice(endBlockFlag ? 1 : 0, blockEndIndexB) + '</span>' + handleBlockStr.slice(blockEndIndexB);
+    } else if (blockEndIndexR !== -1) {
+      returnBlockStr = '&lt;' + (endBlockFlag ? '/' : '') + '<span class="block-name">' + handleBlockStr.slice(endBlockFlag ? 1 : 0, blockEndIndexR) + '</span>' + handleBlockStr.slice(blockEndIndexR);
+    }
+    rerurnXml += returnBlockStr;
+  }
+  return rerurnXml;
+}
+
+var escapeMarkup = function(dangerousInput) {
+  const dangerousString = String(dangerousInput);
+  const matchHtmlRegExp = /["'&<>]/;
+  const match = matchHtmlRegExp.exec(dangerousString);
+  if (!match) {
+    return dangerousInput;
+  }
+
+  const encodedSymbolMap = {
+    '"': '&quot;',
+    '\'': '&#39;',
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;'
+  };
+  const dangerousCharacters = dangerousString.split('');
+  const safeCharacters = dangerousCharacters.map(function (character) {
+    return encodedSymbolMap[character] || character;
+  });
+  const safeString = safeCharacters.join('');
+  return safeString;
+}
+
+
+module.exports = {
+  handleDecodedXML,
+  escapeMarkup
+}

+ 15 - 2
小程序入口_嵌套展示端/project.config.json

@@ -17,12 +17,25 @@
       "ignore": [],
       "disablePlugins": [],
       "outputPath": ""
-    }
+    },
+    "compileWorklet": false,
+    "uglifyFileName": false,
+    "uploadWithSourceMap": true,
+    "packNpmManually": false,
+    "minifyWXSS": true,
+    "minifyWXML": true,
+    "localPlugins": false,
+    "disableUseStrict": false,
+    "useCompilerPlugins": false,
+    "condition": false,
+    "swc": false,
+    "disableSWC": true
   },
   "condition": {},
   "editorSetting": {
     "tabIndent": "auto",
     "tabSize": 2
   },
-  "appid": "wxa8f5e5ba0792de6a"
+  "appid": "wxa8f5e5ba0792de6a",
+  "simulatorPluginLibVersion": {}
 }

+ 23 - 8
小程序入口_嵌套展示端/project.private.config.json

@@ -1,20 +1,35 @@
 {
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "projectname": "%E6%A8%AA%E7%90%B4%E9%A2%84%E7%BA%A6",
+  "projectname": "%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%85%A5%E5%8F%A3_%E5%B5%8C%E5%A5%97%E5%B1%95%E7%A4%BA%E7%AB%AF",
   "setting": {
-    "compileHotReLoad": true
+    "compileHotReLoad": true,
+    "urlCheck": true,
+    "coverView": true,
+    "lazyloadPlaceholderEnable": false,
+    "skylineRenderEnable": false,
+    "preloadBackgroundData": false,
+    "autoAudits": false,
+    "useApiHook": true,
+    "showShadowRootInWxmlPanel": true,
+    "useStaticServer": false,
+    "useLanDebug": false,
+    "showES6CompileOption": false,
+    "checkInvalidKey": true,
+    "ignoreDevUnusedFiles": true,
+    "bigPackageSizeSupport": false
   },
   "condition": {
     "miniprogram": {
       "list": [
         {
-          "name": "",
-          "pathName": "pages/download/index",
-          "query": "url=https://sit-hqbooking.4dage.com/config/A2templatePDF/doc/20250228_12004476418.pdf",
-          "launchMode": "default",
-          "scene": null
+          "name": "pages/ar/index",
+          "pathName": "pages/ar/index",
+          "query": "",
+          "scene": null,
+          "launchMode": "default"
         }
       ]
     }
-  }
+  },
+  "libVersion": "3.12.1"
 }