瀏覽代碼

Merge branch 'master' into v1.0.0

tremble 3 年之前
父節點
當前提交
f6d66148b5
共有 85 個文件被更改,包括 6346 次插入299 次删除
  1. 4 0
      .env
  2. 4 0
      .env.development
  3. 3 3
      .env.prod
  4. 247 26
      package-lock.json
  5. 4 2
      package.json
  6. 4 2
      public/index.html
  7. 2 0
      public/viewer/static/lib/lib-generate-test-usersig.min.js
  8. 2060 0
      public/viewer/static/lib/socket.io.min.js
  9. 18 9
      src/apis/index.js
  10. 104 18
      src/app.vue
  11. 3 0
      src/assets/images/icon/camera-mute.svg
  12. 3 0
      src/assets/images/icon/camera.svg
  13. 3 0
      src/assets/images/icon/clippy.svg
  14. 4 0
      src/assets/images/icon/mic-mute.svg
  15. 4 0
      src/assets/images/icon/mic.svg
  16. 1 0
      src/assets/images/icon/search.svg
  17. 二進制
      src/assets/images/rtcLive/Input_disabled@2x.png
  18. 二進制
      src/assets/images/rtcLive/Input_norma@2x.png
  19. 二進制
      src/assets/images/rtcLive/arrows@2x.png
  20. 二進制
      src/assets/images/rtcLive/avatar_small@2x.png
  21. 二進制
      src/assets/images/rtcLive/brushes@2x.png
  22. 二進制
      src/assets/images/rtcLive/brushes_selected@2_1.png
  23. 二進制
      src/assets/images/rtcLive/brushes_selected@2x.png
  24. 二進制
      src/assets/images/rtcLive/chat_off@2x.png
  25. 二進制
      src/assets/images/rtcLive/chat_on@2x.png
  26. 二進制
      src/assets/images/rtcLive/chrome.png
  27. 二進制
      src/assets/images/rtcLive/cross@2x.png
  28. 二進制
      src/assets/images/rtcLive/edge.png
  29. 二進制
      src/assets/images/rtcLive/exit@2x.png
  30. 二進制
      src/assets/images/rtcLive/firefox.png
  31. 二進制
      src/assets/images/rtcLive/guided@2x.png
  32. 二進制
      src/assets/images/rtcLive/hot_spot@2x.png
  33. 二進制
      src/assets/images/rtcLive/hot_spot_selected@2x.png
  34. 二進制
      src/assets/images/rtcLive/invitation@2x.png
  35. 二進制
      src/assets/images/rtcLive/loading@2x.png
  36. 二進制
      src/assets/images/rtcLive/members@2x.png
  37. 二進制
      src/assets/images/rtcLive/mic_all_off@2x.png
  38. 二進制
      src/assets/images/rtcLive/mic_all_on@2x.png
  39. 二進制
      src/assets/images/rtcLive/mic_off@2x.png
  40. 二進制
      src/assets/images/rtcLive/mic_off_50@2x.png
  41. 二進制
      src/assets/images/rtcLive/mic_off_black@2x.png
  42. 二進制
      src/assets/images/rtcLive/mic_on@2x.png
  43. 二進制
      src/assets/images/rtcLive/pop-up_screen_off@2x.png
  44. 二進制
      src/assets/images/rtcLive/pop-up_screen_on@2x.png
  45. 二進制
      src/assets/images/rtcLive/remove@2x.png
  46. 二進制
      src/assets/images/rtcLive/revocation@2x.png
  47. 二進制
      src/assets/images/rtcLive/safari.png
  48. 二進制
      src/assets/images/rtcLive/scene_mic_off@2x.png
  49. 二進制
      src/assets/images/rtcLive/scene_mic_on@2x.png
  50. 二進制
      src/assets/images/rtcLive/send@2x.png
  51. 二進制
      src/assets/images/rtcLive/send_norma@2x.png
  52. 二進制
      src/assets/images/rtcLive/send_selected@2x.png
  53. 二進制
      src/assets/images/rtcLive/showing@2x.png
  54. 二進制
      src/assets/images/rtcLive/speed.png
  55. 二進制
      src/assets/images/rtcLive/video_off@2x.png
  56. 二進制
      src/assets/images/rtcLive/video_off_50@2x.png
  57. 二進制
      src/assets/images/rtcLive/video_on@2x.png
  58. 二進制
      src/assets/images/rtcLive/vr@2x.png
  59. 10 2
      src/components/Controls/Control.Mobile.vue
  60. 28 89
      src/components/Controls/Panel/Guide.vue
  61. 279 127
      src/components/Controls/Panel/Main.vue
  62. 1092 0
      src/components/RTC/PageRtcLive.vue
  63. 485 0
      src/components/RTC/Trtccom.vue
  64. 67 0
      src/components/RTC/chat/chat.vue
  65. 94 0
      src/components/RTC/dialog/checkBrowser.vue
  66. 279 0
      src/components/RTC/dialog/createdRoom.vue
  67. 143 0
      src/components/RTC/dialog/index.vue
  68. 169 0
      src/components/RTC/dialog/share.vue
  69. 91 0
      src/components/RTC/index.vue
  70. 481 0
      src/components/RTC/paint/Draw.vue
  71. 308 0
      src/components/RTC/paint/index.vue
  72. 9 0
      src/components/RTC/socket/index.js
  73. 81 0
      src/components/RTC/trtc/Device.vue
  74. 1 1
      src/components/Tags/goods-list.vue
  75. 1 2
      src/components/shared/Guide.vue
  76. 1 1
      src/global_components/components/dialog/Alert.vue
  77. 5 0
      src/store/index.js
  78. 2 0
      src/store/modules/index.js
  79. 112 0
      src/store/modules/rtc.js
  80. 3 1
      src/store/modules/scene.js
  81. 19 15
      src/utils/browser.js
  82. 52 0
      src/utils/common.js
  83. 64 0
      src/utils/generateTestUserSig.js
  84. 1 0
      src/utils/rtc_socket.js
  85. 1 1
      vue.config.js

+ 4 - 0
.env

@@ -6,6 +6,10 @@ VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk文件地址
 VUE_APP_SDK_DIR=https://eurs3.4dkankan.com/v4/cdfg/sdk
 
+# socket地址
+VUE_APP_SOCKET_URL=https://vr-admin.cdfmembers.com/
+
+
 
 # 静态资源目录
 VUE_APP_STATIC_DIR=viewer

+ 4 - 0
.env.development

@@ -7,6 +7,10 @@ VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 VUE_APP_SDK_DIR=https://eurs3.4dkankan.com/v4/cdfg/sdk
 
 
+# socket地址
+VUE_APP_SOCKET_URL=https://vr-admin.cdfmembers.com/
+
+
 # 静态资源目录
 VUE_APP_STATIC_DIR=viewer
 

+ 3 - 3
.env.prod

@@ -1,12 +1,10 @@
-
 # 场景资源地址
-VUE_APP_RESOURCE_URL=https://eurs3.4dkankan.com/
+VUE_APP_RESOURCE_URL=https://glp-vr.cdfmembers.com/
 # 静态资源地址
 VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk文件地址
 VUE_APP_SDK_DIR=https://eurs3.4dkankan.com/v4/cdfg/sdk
 
-
 # 静态资源目录
 VUE_APP_STATIC_DIR=viewer
 
@@ -16,3 +14,5 @@ VUE_APP_REGION_URL=aws
 # 接口请求地址
 VUE_APP_APIS_URL=https://vr.cdfmembers.com/
 
+# socket地址
+VUE_APP_SOCKET_URL=https://vr-admin.cdfmembers.com/

+ 247 - 26
package-lock.json

@@ -11,6 +11,8 @@
         "axios": "^0.21.1",
         "clipboard": "^2.0.8",
         "core-js": "^3.8.3",
+        "socket.io": "^4.5.1",
+        "trtc-js-sdk": "^4.13.0",
         "vue": "^3.2.36",
         "vuex": "^4.0.2"
       },
@@ -1947,6 +1949,11 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/component-emitter": {
+      "version": "1.2.11",
+      "resolved": "https://registry.npmmirror.com/@types/component-emitter/-/component-emitter-1.2.11.tgz",
+      "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ=="
+    },
     "node_modules/@types/connect": {
       "version": "3.4.35",
       "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.35.tgz",
@@ -1966,6 +1973,16 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+    },
+    "node_modules/@types/cors": {
+      "version": "2.8.12",
+      "resolved": "https://registry.npmmirror.com/@types/cors/-/cors-2.8.12.tgz",
+      "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
+    },
     "node_modules/@types/eslint": {
       "version": "7.29.0",
       "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-7.29.0.tgz",
@@ -2051,8 +2068,7 @@
     "node_modules/@types/node": {
       "version": "17.0.35",
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-17.0.35.tgz",
-      "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==",
-      "dev": true
+      "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg=="
     },
     "node_modules/@types/normalize-package-data": {
       "version": "2.4.1",
@@ -2962,7 +2978,6 @@
       "version": "1.3.8",
       "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
       "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-      "dev": true,
       "dependencies": {
         "mime-types": "~2.1.34",
         "negotiator": "0.6.3"
@@ -3290,6 +3305,14 @@
       "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
       "dev": true
     },
+    "node_modules/base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+      "engines": {
+        "node": "^4.5.0 || >= 5.9"
+      }
+    },
     "node_modules/batch": {
       "version": "0.6.1",
       "resolved": "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz",
@@ -3811,6 +3834,11 @@
       "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
       "dev": true
     },
+    "node_modules/component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
+    },
     "node_modules/compressible": {
       "version": "2.0.18",
       "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz",
@@ -3999,6 +4027,18 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "dev": true
     },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/cosmiconfig": {
       "version": "7.0.1",
       "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
@@ -4308,7 +4348,6 @@
       "version": "4.3.4",
       "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
       "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-      "dev": true,
       "dependencies": {
         "ms": "2.1.2"
       },
@@ -4676,6 +4715,62 @@
         "once": "^1.4.0"
       }
     },
+    "node_modules/engine.io": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmmirror.com/engine.io/-/engine.io-6.2.0.tgz",
+      "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
+      "dependencies": {
+        "@types/cookie": "^0.4.1",
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.4.1",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.0.3",
+        "ws": "~8.2.3"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
+      "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io/node_modules/cookie": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.4.2.tgz",
+      "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/ws": {
+      "version": "8.2.3",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.2.3.tgz",
+      "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": "^5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/enhanced-resolve": {
       "version": "5.9.3",
       "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
@@ -6383,7 +6478,6 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -6392,7 +6486,6 @@
       "version": "2.1.35",
       "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "dependencies": {
         "mime-db": "1.52.0"
       },
@@ -6535,8 +6628,7 @@
     "node_modules/ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
     "node_modules/multicast-dns": {
       "version": "7.2.5",
@@ -6577,7 +6669,6 @@
       "version": "0.6.3",
       "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz",
       "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-      "dev": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -6712,7 +6803,6 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
       "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -8416,6 +8506,40 @@
         "node": ">=8"
       }
     },
+    "node_modules/socket.io": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmmirror.com/socket.io/-/socket.io-4.5.1.tgz",
+      "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==",
+      "dependencies": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "debug": "~4.3.2",
+        "engine.io": "~6.2.0",
+        "socket.io-adapter": "~2.4.0",
+        "socket.io-parser": "~4.0.4"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-adapter": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz",
+      "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg=="
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.0.5.tgz",
+      "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==",
+      "dependencies": {
+        "@types/component-emitter": "^1.2.10",
+        "component-emitter": "~1.3.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/sockjs": {
       "version": "0.3.24",
       "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz",
@@ -8924,6 +9048,11 @@
       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
       "dev": true
     },
+    "node_modules/trtc-js-sdk": {
+      "version": "4.13.0",
+      "resolved": "https://registry.npmmirror.com/trtc-js-sdk/-/trtc-js-sdk-4.13.0.tgz",
+      "integrity": "sha512-Ks1bmm4O/7w3oGI1g6XNXDoT9Uyl5ibvxRuu1aXTgY/NDR/Ji8wKxNMYXMk0oKAPszPINOSv6ztAYy+qt89dQA=="
+    },
     "node_modules/tslib": {
       "version": "2.4.0",
       "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",
@@ -9063,7 +9192,6 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
       "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
-      "dev": true,
       "engines": {
         "node": ">= 0.8"
       }
@@ -11180,6 +11308,11 @@
         "@types/node": "*"
       }
     },
+    "@types/component-emitter": {
+      "version": "1.2.11",
+      "resolved": "https://registry.npmmirror.com/@types/component-emitter/-/component-emitter-1.2.11.tgz",
+      "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ=="
+    },
     "@types/connect": {
       "version": "3.4.35",
       "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.35.tgz",
@@ -11199,6 +11332,16 @@
         "@types/node": "*"
       }
     },
+    "@types/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+    },
+    "@types/cors": {
+      "version": "2.8.12",
+      "resolved": "https://registry.npmmirror.com/@types/cors/-/cors-2.8.12.tgz",
+      "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
+    },
     "@types/eslint": {
       "version": "7.29.0",
       "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-7.29.0.tgz",
@@ -11284,8 +11427,7 @@
     "@types/node": {
       "version": "17.0.35",
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-17.0.35.tgz",
-      "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==",
-      "dev": true
+      "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg=="
     },
     "@types/normalize-package-data": {
       "version": "2.4.1",
@@ -12068,7 +12210,6 @@
       "version": "1.3.8",
       "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
       "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-      "dev": true,
       "requires": {
         "mime-types": "~2.1.34",
         "negotiator": "0.6.3"
@@ -12321,6 +12462,11 @@
       "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
       "dev": true
     },
+    "base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
+    },
     "batch": {
       "version": "0.6.1",
       "resolved": "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz",
@@ -12754,6 +12900,11 @@
       "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
       "dev": true
     },
+    "component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
+    },
     "compressible": {
       "version": "2.0.18",
       "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz",
@@ -12916,6 +13067,15 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "dev": true
     },
+    "cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "requires": {
+        "object-assign": "^4",
+        "vary": "^1"
+      }
+    },
     "cosmiconfig": {
       "version": "7.0.1",
       "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
@@ -13147,7 +13307,6 @@
       "version": "4.3.4",
       "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
       "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-      "dev": true,
       "requires": {
         "ms": "2.1.2"
       }
@@ -13439,6 +13598,41 @@
         "once": "^1.4.0"
       }
     },
+    "engine.io": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmmirror.com/engine.io/-/engine.io-6.2.0.tgz",
+      "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
+      "requires": {
+        "@types/cookie": "^0.4.1",
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.4.1",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.0.3",
+        "ws": "~8.2.3"
+      },
+      "dependencies": {
+        "cookie": {
+          "version": "0.4.2",
+          "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.4.2.tgz",
+          "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
+        },
+        "ws": {
+          "version": "8.2.3",
+          "resolved": "https://registry.npmmirror.com/ws/-/ws-8.2.3.tgz",
+          "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
+          "requires": {}
+        }
+      }
+    },
+    "engine.io-parser": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
+      "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg=="
+    },
     "enhanced-resolve": {
       "version": "5.9.3",
       "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
@@ -14818,14 +15012,12 @@
     "mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
     },
     "mime-types": {
       "version": "2.1.35",
       "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "requires": {
         "mime-db": "1.52.0"
       }
@@ -14940,8 +15132,7 @@
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-      "dev": true
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
     "multicast-dns": {
       "version": "7.2.5",
@@ -14972,8 +15163,7 @@
     "negotiator": {
       "version": "0.6.3",
       "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz",
-      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-      "dev": true
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
     },
     "neo-async": {
       "version": "2.6.2",
@@ -15077,8 +15267,7 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "dev": true
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
     },
     "object-inspect": {
       "version": "1.12.1",
@@ -16365,6 +16554,34 @@
       "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
       "dev": true
     },
+    "socket.io": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmmirror.com/socket.io/-/socket.io-4.5.1.tgz",
+      "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==",
+      "requires": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "debug": "~4.3.2",
+        "engine.io": "~6.2.0",
+        "socket.io-adapter": "~2.4.0",
+        "socket.io-parser": "~4.0.4"
+      }
+    },
+    "socket.io-adapter": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz",
+      "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg=="
+    },
+    "socket.io-parser": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.0.5.tgz",
+      "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==",
+      "requires": {
+        "@types/component-emitter": "^1.2.10",
+        "component-emitter": "~1.3.0",
+        "debug": "~4.3.1"
+      }
+    },
     "sockjs": {
       "version": "0.3.24",
       "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz",
@@ -16776,6 +16993,11 @@
       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
       "dev": true
     },
+    "trtc-js-sdk": {
+      "version": "4.13.0",
+      "resolved": "https://registry.npmmirror.com/trtc-js-sdk/-/trtc-js-sdk-4.13.0.tgz",
+      "integrity": "sha512-Ks1bmm4O/7w3oGI1g6XNXDoT9Uyl5ibvxRuu1aXTgY/NDR/Ji8wKxNMYXMk0oKAPszPINOSv6ztAYy+qt89dQA=="
+    },
     "tslib": {
       "version": "2.4.0",
       "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",
@@ -16884,8 +17106,7 @@
     "vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
-      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
-      "dev": true
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
     },
     "vue": {
       "version": "3.2.36",

+ 4 - 2
package.json

@@ -9,10 +9,12 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
-    "core-js": "^3.8.3",
-    "vue": "^3.2.36",
     "axios": "^0.21.1",
     "clipboard": "^2.0.8",
+    "core-js": "^3.8.3",
+    "socket.io": "^4.5.1",
+    "trtc-js-sdk": "^4.13.0",
+    "vue": "^3.2.36",
     "vuex": "^4.0.2"
   },
   "devDependencies": {

+ 4 - 2
public/index.html

@@ -23,12 +23,14 @@
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/flv.min.js"></script>
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/vconsole.js"></script>
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/swiper/swiper-bundle.min.js"></script>
+        <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/socket.io.min.js"></script>
+        <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/lib-generate-test-usersig.min.js"></script>
 
 
         <script src="<%= BASE_URL %><%= VUE_APP_STATIC_DIR %>/static/lib/jweixin-1.6.0.js"></script>
         
-        <script src="<%= VUE_APP_SDK_DIR %>/kankan-sdk-deps.js?v=4.0.0-alpha.54"></script>
-        <script src="<%= VUE_APP_SDK_DIR %>/kankan-sdk.js?v=4.0.0-alpha.54"></script>
+        <script src="<%= VUE_APP_SDK_DIR %>/kankan-sdk-deps.js?v=4.31.0-alpha.51"></script>
+        <script src="<%= VUE_APP_SDK_DIR %>/kankan-sdk.js?v=4.31.0-alpha.51"></script>
 
         <!-- <script src="https://4dkk.4dage.com/v4/www/sdk/kankan-sdk-deps.js?v=4.0.0-alpha.44"></script>
         <script src="https://4dkk.4dage.com/v4/www/sdk/kankan-sdk.js?v=4.0.0-alpha.44"></script> -->

文件差異過大導致無法顯示
+ 2 - 0
public/viewer/static/lib/lib-generate-test-usersig.min.js


文件差異過大導致無法顯示
+ 2060 - 0
public/viewer/static/lib/socket.io.min.js


+ 18 - 9
src/apis/index.js

@@ -29,15 +29,24 @@ export const inCat = (data) => {
   return http.get("/api/inCat", data);
 };
 
+export const getSign = (data) => {
+  return http.get("/api/tencentYun/getSign", data);
+};
+
+
+
 export const burying_point = (data) => {
   let url = encodeURI(window.location.href)
-  return http.get("/api/point", {
-    userId: browser.getURLParam("userId") || 1,
-    AccessToken: browser.getURLParam("AccessToken")||'none',
-    cookieId: browser.getURLParam("cookieId")||'none',
-    isMiniApp: browser.getURLParam("isMiniApp")||'h5',
-    whereUrl: url,
-    type: data.type,
-    productId: data.productId,
-  });
+  if (browser.getURLParam("AccessToken")) {
+    return http.get("/api/point", {
+      userId: browser.getURLParam("userId") || 1,
+      AccessToken: browser.getURLParam("AccessToken")||'none',
+      cookieId: browser.getURLParam("cookieId")||'none',
+      isMiniApp: browser.getURLParam("isMiniApp")||'h5',
+      whereUrl: url,
+      type: data.type,
+      productId: data.productId,
+    });
+  }
+
 };

+ 104 - 18
src/app.vue

@@ -6,20 +6,25 @@
   <div class="ui-view-layout" :class="{ show: show }" is-mobile="true">
     <div class="scene" ref="scene$"></div>
     <template v-if="dataLoaded">
-      <Information />
+      <Information v-if="!isshoppingguide" />
       <Control />
       <teleport v-if="refMiniMap && player.showWidgets" :to="refMiniMap">
-        <span class="button-switch" @click.stop="toggleMap">
+        <span :class="{ gudieDisabled: isshoppingguide && role.value != 'leader' }" class="button-switch" @click.stop="toggleMap">
           <ui-icon type="show_map_collect"></ui-icon>
         </span>
 
-        <p class="change" @click="changeMode('dollhouse', $event, 'focus3d')">
+        <p class="change" :class="{ gudieDisabled: isshoppingguide && role.value != 'leader' }" @click="changeMode('dollhouse', $event, 'focus3d')">
           <ui-icon type="show_3d_normal"></ui-icon>
           3D模型
         </p>
       </teleport>
       <template v-if="refMiniMap && player.showWidgets">
-        <div :class="{ disabled: flying }" v-show="mode != 'panorama'" v-if="controls.showFloorplan && controls.showDollhouse" class="tab-layer">
+        <div
+          :class="{ disabled: flying, gudieDisabled: isshoppingguide && role.value != 'leader' }"
+          v-show="mode != 'panorama'"
+          v-if="controls.showFloorplan && controls.showDollhouse"
+          class="tab-layer"
+        >
           <div class="tabs" v-if="controls.showMap">
             <span :class="{ active: mode === 'floorplan' }" @click="changeMode('floorplan', $event)">
               <ui-icon :type="mode == 'floorplan' ? 'show_plane_selected' : 'show_plane_normal'"></ui-icon>
@@ -55,6 +60,7 @@ import Control from "@/components/Controls/Control.Mobile.vue";
 import LoadingLogo from "@/components/shared/Loading.vue";
 import OpenVideo from "@/components/openVideo/";
 import Guide from "@/components/shared/Guide.vue";
+import { Dialog } from "@/global_components/";
 
 import { createApp } from "@/app";
 import { ref, onMounted, computed, nextTick, watch } from "vue";
@@ -65,6 +71,7 @@ import common from "@/utils/common";
 import { Cache } from "@/utils/index";
 
 import * as apis from "@/apis/index.js";
+const store = useStore();
 
 let jumpNewScene = (sceneFirstView) => {
   let url = window.location.href;
@@ -96,18 +103,31 @@ const musicPlayer = useMusicPlayer();
 let app = null;
 
 let tagid = browser.getURLParam("tagid");
+const role = ref(browser.getURLParam("role"));
 
 const closetagtype = () => {
   store.commit("tag/setTagClickType", {
     type: "",
     data: {},
   });
+
+  if (isshoppingguide.value) {
+    if (role.value == "leader") {
+      socket.value &&
+        socket.value.emit("action", {
+          type: "tagclose",
+        });
+    }
+  }
 };
+const socket = computed(() => store.getters["rtc/socket"]);
 
-const store = useStore();
 const tags = computed(() => {
   return store.getters["tag/tags"] || [];
 });
+
+const isshoppingguide = computed(() => store.getters["shoppingguide"]);
+
 const player = computed(() => store.getters["player"]);
 const flying = computed(() => store.getters["flying"]);
 const metadata = computed(() => store.getters["scene/metadata"]);
@@ -128,6 +148,10 @@ if (!Cache.get("HIDENVIDEOEXPIRES")) {
   }
 }
 
+if (role.value) {
+  hadVideo.value = true;
+}
+
 const show = ref(false);
 const dataLoaded = ref(false);
 const refMiniMap = ref(null);
@@ -143,14 +167,28 @@ const resize = () => {
   }
 };
 
-// watch(
-//   () => hadVideo.value,
-//   (val, old) => {
-//     if (val) {
-//       app.Scene.unlock();
-//     }
-//   }
-// );
+watch(
+  () => isshoppingguide.value,
+  (val, old) => {
+    let $minmap = document.querySelector("[xui_min_map]");
+    if ($minmap) {
+      setTimeout(async () => {
+        if (role.value == "leader") {
+          return;
+        }
+        await nextTick();
+        if (isshoppingguide.value) {
+          $minmap.classList.add("gudieDisabled");
+        } else {
+          $minmap.classList.remove("gudieDisabled");
+        }
+      });
+    }
+  },
+  {
+    deep: true,
+  }
+);
 
 watch(
   () => player.value.showMap,
@@ -223,12 +261,27 @@ const onClickTagInfo = (el) => {
       type: "goodlist",
       data: item,
     });
+    guideclicktag(item);
   } else if (item.type == "link_scene") {
     let sceneFirstView = item.hotContent.sceneFirstView;
     window.location.href = jumpNewScene(sceneFirstView);
   }
 };
 
+const guideclicktag = (tag) => {
+  if (isshoppingguide.value) {
+    if (role.value == "leader") {
+      socket.value &&
+        socket.value.emit("action", {
+          type: "tagclick",
+          data: {
+            sid: tag.sid,
+          },
+        });
+    }
+  }
+};
+
 onMounted(async () => {
   apis.burying_point({ type: 0 });
 
@@ -296,8 +349,8 @@ onMounted(async () => {
         view.focus(tag.sid).then(() => {
           if (tag.type == "coupon") {
             try {
-              document.querySelector(`[data-tag-id="${tag.sid}"] .tag-icon`).style.display = 'none'
-              let hotcontent = (typeof tag.hotContent =='string') ? JSON.parse(tag.hotContent) : tag.hotContent;
+              document.querySelector(`[data-tag-id="${tag.sid}"] .tag-icon`).style.display = "none";
+              let hotcontent = typeof tag.hotContent == "string" ? JSON.parse(tag.hotContent) : tag.hotContent;
               browser.openLink(
                 "/subPackage/pages/activity/activity?pageId=" + hotcontent.couponLink,
                 `https://m.cdfmembers.com/shop/600667208/showactivity?pageId=${hotcontent.couponLink}`,
@@ -310,11 +363,10 @@ onMounted(async () => {
               type: "waterfall",
               data: tag,
             });
+            guideclicktag(tag);
           } else if (tag.type == "applet_link") {
-              console.log(tag);
-
             try {
-              let hotcontent = (typeof tag.hotContent =='string') ? JSON.parse(tag.hotContent) : tag.hotContent;
+              let hotcontent = typeof tag.hotContent == "string" ? JSON.parse(tag.hotContent) : tag.hotContent;
               browser.openLink(
                 "/subPackage/pages/home/home?pageType=2&pageId=" + hotcontent.liveLink,
                 `https://m.cdfmembers.com/shop/600667208/showactivity?pageId=${hotcontent.liveLink}`,
@@ -359,10 +411,37 @@ onMounted(async () => {
   // }
   app.Scene.on("ready", () => {
     show.value = true;
+    Dialog.alert({
+      showCloseIcon:false,
+      content: "<span style='font-size: 16px; line-height: 1.5;'>開發者已遵守收集、使用最終用戶個人信息有關的所有可適用法律、政策和法規,保護用戶個人信息安全。<span/>",
+      title:'隱私條款:'
+    });
+  });
+  app.Scene.on("error", (data) => {
+    switch (data.code) {
+      case 5033:
+        Dialog.alert("该场景正在计算中,请稍后再试");
+        break;
+      case 5034:
+        Dialog.alert("服务端开小差,请稍后再试");
+        break;
+      case 5009:
+        Dialog.alert("服务端开小差,请稍后再试");
+        break;
+      case 5005:
+        Dialog.alert("服务端开小差,请稍后再试");
+        break;
+    }
   });
   app.Scene.on("loaded", (pano) => {
     refMiniMap.value = "[xui_min_map]";
     store.commit("setFloorId", pano.floorIndex);
+    store.commit("rtc/setShowdaogou", true);
+
+    if (browser.getURLParam("roomId")) {
+      store.commit("showShoppingguide", true);
+    }
+
     app.resource.tags(`${process.env.VUE_APP_RESOURCE_URL}cdf/hot/${browser.getURLParam("m")}/hot.json?rnd=${Math.random()}`);
     useMusicPlayer();
   });
@@ -634,6 +713,13 @@ onMounted(async () => {
   }
 }
 
+.gudieDisabled {
+  pointer-events: none !important;
+  * {
+    pointer-events: none !important;
+  }
+}
+
 @media (orientation: landscape) {
   .tab-layer {
     top: 1.2rem;

+ 3 - 0
src/assets/images/icon/camera-mute.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-camera-video-off-fill" viewBox="0 0 16 16">
+  <path fill-rule="evenodd" d="M10.961 12.365a1.99 1.99 0 0 0 .522-1.103l3.11 1.382A1 1 0 0 0 16 11.731V4.269a1 1 0 0 0-1.406-.913l-3.111 1.382A2 2 0 0 0 9.5 3H4.272l6.69 9.365zm-10.114-9A2.001 2.001 0 0 0 0 5v6a2 2 0 0 0 2 2h5.728L.847 3.366zm9.746 11.925-10-14 .814-.58 10 14-.814.58z"/>
+</svg>

+ 3 - 0
src/assets/images/icon/camera.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-camera-video-fill" viewBox="0 0 16 16">
+  <path fill-rule="evenodd" d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.462a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5z"/>
+</svg>

+ 3 - 0
src/assets/images/icon/clippy.svg

@@ -0,0 +1,3 @@
+<svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg">
+  <path d="M128 768h256v64H128v-64z m320-384H128v64h320v-64z m128 192V448L384 640l192 192V704h320V576H576z m-288-64H128v64h160v-64zM128 704h160v-64H128v64z m576 64h64v128c-1 18-7 33-19 45s-27 18-45 19H64c-35 0-64-29-64-64V192c0-35 29-64 64-64h192C256 57 313 0 384 0s128 57 128 128h192c35 0 64 29 64 64v320h-64V320H64v576h640V768zM128 256h512c0-35-29-64-64-64h-64c-35 0-64-29-64-64s-29-64-64-64-64 29-64 64-29 64-64 64h-64c-35 0-64 29-64 64z" />
+</svg>

+ 4 - 0
src/assets/images/icon/mic-mute.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-mic-mute-fill" viewBox="0 0 16 16">
+  <path d="M13 8c0 .564-.094 1.107-.266 1.613l-.814-.814A4.02 4.02 0 0 0 12 8V7a.5.5 0 0 1 1 0v1zm-5 4c.818 0 1.578-.245 2.212-.667l.718.719a4.973 4.973 0 0 1-2.43.923V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 1 0v1a4 4 0 0 0 4 4zm3-9v4.879L5.158 2.037A3.001 3.001 0 0 1 11 3z"/>
+  <path d="M9.486 10.607 5 6.12V8a3 3 0 0 0 4.486 2.607zm-7.84-9.253 12 12 .708-.708-12-12-.708.708z"/>
+</svg>

+ 4 - 0
src/assets/images/icon/mic.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-mic-fill" viewBox="0 0 16 16">
+  <path d="M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0V3z"/>
+  <path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
+</svg>

文件差異過大導致無法顯示
+ 1 - 0
src/assets/images/icon/search.svg


二進制
src/assets/images/rtcLive/Input_disabled@2x.png


二進制
src/assets/images/rtcLive/Input_norma@2x.png


二進制
src/assets/images/rtcLive/arrows@2x.png


二進制
src/assets/images/rtcLive/avatar_small@2x.png


二進制
src/assets/images/rtcLive/brushes@2x.png


二進制
src/assets/images/rtcLive/brushes_selected@2_1.png


二進制
src/assets/images/rtcLive/brushes_selected@2x.png


二進制
src/assets/images/rtcLive/chat_off@2x.png


二進制
src/assets/images/rtcLive/chat_on@2x.png


二進制
src/assets/images/rtcLive/chrome.png


二進制
src/assets/images/rtcLive/cross@2x.png


二進制
src/assets/images/rtcLive/edge.png


二進制
src/assets/images/rtcLive/exit@2x.png


二進制
src/assets/images/rtcLive/firefox.png


二進制
src/assets/images/rtcLive/guided@2x.png


二進制
src/assets/images/rtcLive/hot_spot@2x.png


二進制
src/assets/images/rtcLive/hot_spot_selected@2x.png


二進制
src/assets/images/rtcLive/invitation@2x.png


二進制
src/assets/images/rtcLive/loading@2x.png


二進制
src/assets/images/rtcLive/members@2x.png


二進制
src/assets/images/rtcLive/mic_all_off@2x.png


二進制
src/assets/images/rtcLive/mic_all_on@2x.png


二進制
src/assets/images/rtcLive/mic_off@2x.png


二進制
src/assets/images/rtcLive/mic_off_50@2x.png


二進制
src/assets/images/rtcLive/mic_off_black@2x.png


二進制
src/assets/images/rtcLive/mic_on@2x.png


二進制
src/assets/images/rtcLive/pop-up_screen_off@2x.png


二進制
src/assets/images/rtcLive/pop-up_screen_on@2x.png


二進制
src/assets/images/rtcLive/remove@2x.png


二進制
src/assets/images/rtcLive/revocation@2x.png


二進制
src/assets/images/rtcLive/safari.png


二進制
src/assets/images/rtcLive/scene_mic_off@2x.png


二進制
src/assets/images/rtcLive/scene_mic_on@2x.png


二進制
src/assets/images/rtcLive/send@2x.png


二進制
src/assets/images/rtcLive/send_norma@2x.png


二進制
src/assets/images/rtcLive/send_selected@2x.png


二進制
src/assets/images/rtcLive/showing@2x.png


二進制
src/assets/images/rtcLive/speed.png


二進制
src/assets/images/rtcLive/video_off@2x.png


二進制
src/assets/images/rtcLive/video_off_50@2x.png


二進制
src/assets/images/rtcLive/video_on@2x.png


二進制
src/assets/images/rtcLive/vr@2x.png


+ 10 - 2
src/components/Controls/Control.Mobile.vue

@@ -3,6 +3,8 @@
   <transition mode="out-in">
       <component class="limitwidth" :is="panelPage"></component>
   </transition>
+
+
 </template>
 
 <script setup>
@@ -14,10 +16,15 @@ import FloorSwitch from "./FloorSwitch";
 import vMain from "./Panel/Main";
 import Guide from "./Panel/Guide";
 
+import guideShop from "../RTC/index";
+
+
 import { Scrollbar, Dialog } from "@/global_components/";
-const store = useStore();
 import common from "@/utils/common";
 import { useMusicPlayer } from "@/utils/sound";
+
+const store = useStore();
+
 const musicPlayer = useMusicPlayer();
 const metadata = computed(() => store.getters["scene/metadata"]);
 const flying = computed(() => store.getters["flying"]);
@@ -26,11 +33,12 @@ const controls = computed(() => {
 });
 const player = computed(() => store.getters["player"]);
 const mode = computed(() => store.getters["mode"]);
+
 let timer = null;
 
 const panelPage = computed(() => {
   let status = store.getters["tour/isPlay"] ? Guide :  vMain;
-  return status;
+  return store.getters["shoppingguide"] ? guideShop : status;
 });
 
 

+ 28 - 89
src/components/Controls/Panel/Guide.vue

@@ -1,11 +1,8 @@
 <template>
   <div v-show="player.showWidgets" class="root-panel">
-    <div class="guide-panel" :style="{ '--urlbg': `url(${require('@/assets/images/icon/bg.png')})` }">
+    <div class="guide-panel" >
       <div class="g-con">
-        <div class="back" @click.stop="playTour">
-          <ui-icon type="back"></ui-icon>
-          <div>返回</div>
-        </div>
+        
         <div class="swiper-container" id="sw-guide">
           <ul class="swiper-wrapper"  v-if="tours.length > 1">
             <li
@@ -34,6 +31,10 @@
             </li>
           </ul>
         </div>
+        <div class="back" @click.stop="playTour">
+          <ui-icon type="back"></ui-icon>
+          <div>返回</div>
+        </div>
       </div>
     </div>
   </div>
@@ -135,56 +136,7 @@ const categorylist = ref([
   },
 ]);
 
-const brandlist = ref([
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理MOOST·理MOOST·理MOOST·理MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-]);
+const brandlist = ref([]);
 
 const brandScroll = () => {
   nextTick(() => {
@@ -193,7 +145,7 @@ const brandScroll = () => {
       new Swiper("#sw-guide", {
         freeMode: true,
         slidesPerView: "auto",
-        spaceBetween: 4,
+        spaceBetween: 6,
         on: {
           touchMove(swiper, e) {
             e.stopPropagation();
@@ -325,43 +277,42 @@ onMounted(() => {
 <style lang="scss" scoped>
 .root-panel {
   position: absolute;
-  bottom: 60px;
-  left: 0.2rem;
-  right: 0.2rem;
+  bottom: 46px;
+  left: 0;
+  right: 0;
   z-index: 99;
+  width: 100%;
+  background: rgba(0,0,0,0.3000);
+  border: 1px solid rgba(255,255,255,0.2000);
   .guide-panel {
     width: 100%;
-    height: 50px;
     position: relative;
-    &::before {
-      position: absolute;
-      bottom: -34px;
-      content: "";
-      background-image: var(--urlbg);
-      background-size: 100% auto;
-      display: inline-block;
-      width: 100%;
-      height: 30px;
-    }
     .g-con {
       background: rgba(0, 0, 0, 0.3);
       border-radius: 4px;
       border: 1px solid rgba(255, 255, 255, 0.2);
-      width: 90%;
+      width: 100%;
       height: 100%;
       margin: 0 auto;
       padding: 6px 10px;
       box-sizing: border-box;
       display: flex;
       align-items: center;
+      flex-direction: column;
       .back {
         text-align: center;
-        width: 40px;
+        width: 100%;
         font-size: 0;
         margin-right: 10px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        border-top: solid 1px rgba(255,255,255,0.2000);
+        height: 48px;
+
         > div {
-          font-size: 12px;
-          margin-top: 2px;
+          font-size: 14px;
+          margin-left: 10px;
         }
       }
       #sw-guide {
@@ -370,26 +321,14 @@ onMounted(() => {
         overflow: hidden;
         padding-right: 2px;
         position: relative;
-        &::after {
-          position: absolute;
-          right: -1px;
-          bottom: 0;
-          content: "";
-          display: inline-block;
-          height: 100%;
-          z-index: 99;
-          width: 17px;
-          background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, #000000 100%);
-          opacity: 0.4;
-          pointer-events: none;
-        }
+        padding-bottom: 6px;
         > ul {
           > li {
-            width: 70px;
-            height: 36px;
             border-radius: 2px;
             position: relative;
             font-size: 0;
+            width: 154px;
+            height: 82px;
             overflow: hidden;
             background-size: cover;
             > img {

+ 279 - 127
src/components/Controls/Panel/Main.vue

@@ -1,75 +1,139 @@
 <template>
-  <Panel v-show="player.showWidgets" :isOpen="isOpen">
-    <div @click="toggleOpen" class="menu color">
-      <div class="logo">
-        <img :src="require('@/assets/images/icon/logo.png')" alt="" />
-        <p>cdf澳門上葡京店</p>
-      </div>
-      <div class="vline"></div>
-      <ul>
-        <li v-if="tours.length > 0">
-          <ui-icon type="preview" @click.stop="playTour"></ui-icon>
-          <div>導覽</div>
-        </li>
-        <li @click.stop="onClickMenu(item)" v-for="(item, i) in menulist" :key="i">
-          <ui-icon :type="item.icon"></ui-icon>
-          <div>{{ item.name }}</div>
-        </li>
-      </ul>
-    </div>
-
-    <div class="toolbar color">
-      <div class="navigation">
-        <div class="h3">專櫃導航</div>
-        <div class="swiper-container" id="sw-navigation">
-          <ul class="swiper-wrapper">
-            <li
-              class="swiper-slide"
-              :class="{ liactive: item.sceneUrl === currentM && item.inPosition.indexOf(currentPose) > -1 }"
-              @click.stop="onClickShop(item)"
-              v-for="(item, i) in brandlist"
-              :key="i"
-            >
-              <div v-if="item.shopLogo" class="img" :style="{ 'background-image': `url(${item.shopLogo})` }"></div>
-              <div class="name" v-if="item.shopName">
-                <span :class="{ active: item.shopName.length > 6 }">
-                  {{ item.shopName }}
-                </span>
-              </div>
-            </li>
-          </ul>
+  <div>
+    <Panel v-show="player.showWidgets" :isOpen="isOpen">
+      <div @click="toggleOpen" class="menu color">
+        <div class="logo">
+          <img :src="require('@/assets/images/icon/logo.png')" alt="" />
+          <p>cdf澳門上葡京店</p>
         </div>
-      </div>
+        <div class="vline"></div>
+        <ul>
+          <li v-if="tours.length > 0">
+            <ui-icon type="preview" @click.stop="playTour"></ui-icon>
+            <div>導覽</div>
+          </li>
+          <li @click.stop="onClickMenu(item)" v-for="(item, i) in menulist" :key="i">
+            <ui-icon :type="item.icon"></ui-icon>
+            <div>{{ item.name }}</div>
+          </li>
+
+          <li v-if="showdaogou">
+            <ui-icon
+              type="guided_shopping"
+              @click.stop="
+                onClickMenu({
+                  icon: 'guided_shopping',
+                  id: 'guided_shopping',
+                  name: '導購',
+                })
+              "
+            ></ui-icon>
+            <div>導購</div>
+          </li>
 
-      <div class="category">
-        <div class="swiper-container" id="sw-category">
-          <ul class="swiper-wrapper">
-            <li
-              class="swiper-slide"
-              :class="{ categoryactive: '' == currentCategory.id }"
+          <li>
+            <ui-icon
+              type="shopping"
               @click.stop="
-                onClickCategory({
-                  id: '',
-                  categoryName: '全部',
+                onClickMenu({
+                  icon: 'shopping',
+                  id: 'shopping',
+                  name: '購物',
                 })
               "
-            >
-              <div>全部</div>
-            </li>
-            <li
-              @click.stop="onClickCategory(item)"
-              :class="{ categoryactive: item.id == currentCategory.id }"
-              class="swiper-slide"
-              v-for="(item, i) in categorylist"
-              :key="i"
-            >
-              <div>{{ item.categoryName }}</div>
-            </li>
-          </ul>
+            ></ui-icon>
+            <div>購物</div>
+          </li>
+        </ul>
+      </div>
+
+      <div class="toolbar color">
+        <div class="navigation">
+          <div class="h3">
+            <span>專櫃導航</span>
+            <div @click="showShopList = true">
+              <img :src="require(`@/assets/images/icon/search.svg`)" alt="" />
+              搜索專櫃
+            </div>
+          </div>
+          <div class="swiper-container" id="sw-navigation">
+            <ul class="swiper-wrapper">
+              <li
+                class="swiper-slide"
+                :class="{ liactive: item.sceneUrl === currentM && item.inPosition.indexOf(currentPose) > -1 }"
+                @click.stop="onClickShop(item)"
+                v-for="(item, i) in brandlist"
+                :key="i"
+              >
+                <div v-if="item.shopLogo" class="img" :style="{ 'background-image': `url(${item.shopLogo})` }"></div>
+                <div class="name" v-if="item.shopName">
+                  <span :class="{ active: item.shopName.length > 6 }">
+                    {{ item.shopName }}
+                  </span>
+                </div>
+              </li>
+            </ul>
+          </div>
+        </div>
+
+        <div class="category">
+          <div class="swiper-container" id="sw-category">
+            <ul class="swiper-wrapper">
+              <li
+                class="swiper-slide"
+                :class="{ categoryactive: '' == currentCategory.id }"
+                @click.stop="
+                  onClickCategory({
+                    id: '',
+                    categoryName: '全部',
+                  })
+                "
+              >
+                <div>全部</div>
+              </li>
+              <li
+                @click.stop="onClickCategory(item)"
+                :class="{ categoryactive: item.id == currentCategory.id }"
+                class="swiper-slide"
+                v-for="(item, i) in categorylist"
+                :key="i"
+              >
+                <div>{{ item.categoryName }}</div>
+              </li>
+            </ul>
+          </div>
         </div>
       </div>
-    </div>
-  </Panel>
+    </Panel>
+    <teleport :to="`#app`">
+      <div v-show="showShopList" class="shoplist">
+        <div class="l-title">
+          專櫃列表 ({{ searchList.length }})
+          <ui-icon type="close" @click="showShopList = false" />
+        </div>
+
+        <div class="search">
+          <img :src="require(`@/assets/images/icon/search.svg`)" alt="" />
+          <input v-model="searchKey" type="text" placeholder="專櫃搜索" />
+          <ui-icon class="close" v-if="searchKey.length > 0" @click.stop="searchKey = ''" type="state_f"></ui-icon>
+        </div>
+
+        <ul v-if="searchList.length > 0">
+          <li @click.stop="onClickShop(item)" v-for="(item, idx) in searchList" :key="idx">
+            <div class="img" :style="{ 'background-image': `url(${item.shopLogo})` }"></div>
+            <div class="name" v-if="item.shopName">
+              <span>
+                {{ item.shopName }}
+              </span>
+            </div>
+            <ui-icon class="right" type="right"></ui-icon>
+          </li>
+        </ul>
+
+        <div class="noresult" v-else>暫無結果</div>
+      </div>
+    </teleport>
+  </div>
 </template>
 
 <script setup>
@@ -83,6 +147,9 @@ import browser from "@/utils/browser";
 const store = useStore();
 
 const isOpen = ref(false);
+const searchKey = ref("");
+
+const showShopList = ref(false);
 
 const toggleOpen = () => {
   isOpen.value = !isOpen.value;
@@ -95,6 +162,7 @@ const currentCategory = ref({
 
 const currentM = computed(() => browser.getURLParam("m"));
 const currentPose = computed(() => browser.getURLParam("pose"));
+const showdaogou = computed(() => store.getters["rtc/showdaogou"]);
 
 const isPlay = computed(() => {
   let status = store.getters["tour/isPlay"];
@@ -140,15 +208,6 @@ const menulist = computed(() => {
       id: "kefu",
       name: "客服",
     },
-    // {
-    //   icon: "guided_shopping",
-    //   name: "導購",
-    // },
-    {
-      icon: "shopping",
-      id: "shopping",
-      name: "購物",
-    },
   ];
 
   if (!browser.isMobile()) {
@@ -159,56 +218,9 @@ const menulist = computed(() => {
 
 const categorylist = ref([]);
 
-const brandlist = ref([
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理MOOST·理MOOST·理MOOST·理MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-  {
-    img: "show_3d_normal",
-    name: "GAP 蓋璞",
-  },
-  {
-    img: "show_3d_normal",
-    name: "MOOST·理",
-  },
-  {
-    img: "show_3d_normal",
-    name: "H&M",
-  },
-]);
+const brandlist = ref([]);
+
+const searchList = ref([]);
 
 const brandScroll = () => {
   nextTick(() => {
@@ -251,11 +263,15 @@ const onClickMenu = (item) => {
       "https://webpage.qidian.qq.com/2/chat/h5/index.html?linkType=1&env=ol&kfuin=3009110132&fid=3655&key=9b4334768c39150ead3f23e11e5dc2e4&cate=7&source=0&isLBS=0&isCustomEntry=0&type=10&ftype=1&_type=wpa&qidian=true&_pid=kvrmvu.74cg11.l43qvbcu&translateSwitch=0&isSsc=0&roleValue=4&roleData=922223821";
     window.open(mglink, "_blank");
   } else if (item.id == "shopping") {
-    browser.openLink("/subPackage/pages/shoppingcart/shoppingcart", 
-    "https://m.cdfmembers.com/shop/600667208/shoppingcart", 
-    "/subPackage/pages/shoppingcart/shoppingcart");
+    browser.openLink(
+      "/subPackage/pages/shoppingcart/shoppingcart",
+      "https://m.cdfmembers.com/shop/600667208/shoppingcart",
+      "/subPackage/pages/shoppingcart/shoppingcart"
+    );
   } else if (item.id == "help") {
     store.commit("showUserGuide", true);
+  } else if (item.id == "guided_shopping") {
+    store.commit("showShoppingguide", true);
   }
 };
 
@@ -293,6 +309,14 @@ watch(
   }
 );
 
+watch(
+  () => searchKey.value,
+  (val, old) => {
+    console.log(searchKey.value);
+    getSearchList();
+  }
+);
+
 const getShoplist = async () => {
   let res = await apis.get_shop_list({
     categoryId: currentCategory.value.id,
@@ -301,8 +325,15 @@ const getShoplist = async () => {
   brandScroll();
 };
 
+const getSearchList = async () => {
+  let res = await apis.get_shop_list({
+    shopName: searchKey.value,
+  });
+  searchList.value = res.data;
+};
+
 onMounted(() => {
-  useApp().then(async (sdk) => {
+  useApp().then((app) => {
     getCategorylist();
     getShoplist();
   });
@@ -371,6 +402,23 @@ onMounted(() => {
     .h3 {
       font-size: 14px;
       padding: 0 14px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      > div {
+        width: 96px;
+        height: 24px;
+        background: rgba(0, 0, 0, 0.5);
+        border-radius: 24px;
+        display: flex;
+        font-size: 12px;
+        justify-content: center;
+        align-items: center;
+      }
+      img {
+        width: 12px;
+        margin-right: 6px;
+      }
     }
     .swiper-container {
       width: 100%;
@@ -465,6 +513,110 @@ onMounted(() => {
   }
 }
 
+.shoplist {
+  position: fixed;
+  z-index: 999;
+  width: 100%;
+  height: 30px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  height: 80vh;
+  background: rgba(0, 0, 0, 0.8);
+  border-radius: 10px 10px 0px 0px;
+  padding: 12px 15px;
+  .l-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #ffffff;
+    text-align: center;
+    position: relative;
+    width: 100%;
+
+    i {
+      position: absolute;
+      right: 0;
+      top: 2px;
+    }
+  }
+
+  .search {
+    width: 100%;
+    height: 34px;
+    background: rgba(46, 46, 46, 0.9);
+    border-radius: 20px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 8px;
+    margin-top: 20px;
+    > img {
+      width: 16px;
+      opacity: 0.8;
+    }
+    > input {
+      text-align: left;
+      font-size: 16px;
+      width: 100%;
+      color: #fff;
+      margin-left: 10px;
+    }
+    .close {
+      color: #7d7e80;
+    }
+  }
+
+  > ul {
+    width: 100%;
+    height: calc(100% - 80px);
+    overflow-y: auto;
+    > li {
+      width: 100%;
+      height: 60px;
+      position: relative;
+      font-size: 0;
+      overflow: hidden;
+      box-sizing: border-box;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      margin: 18px 0;
+      .img {
+        width: 60px;
+        height: 60px;
+        background-size: contain;
+        flex-shrink: 0;
+        border-radius: 4px;
+        margin-right: 10px;
+      }
+      .name {
+        width: 100%;
+        font-size: 12px;
+        left: 0;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        overflow: hidden;
+        text-align: left;
+        padding: 2px 4px;
+        box-sizing: border-box;
+        word-break: break-all;
+        > span {
+          display: inline-block;
+          white-space: nowrap;
+        }
+      }
+      .right {
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+.noresult {
+  text-align: center;
+  margin-top: 20vh;
+}
+
 @keyframes wordsLoop {
   0% {
     transform: translateX(100%);

文件差異過大導致無法顯示
+ 1092 - 0
src/components/RTC/PageRtcLive.vue


+ 485 - 0
src/components/RTC/Trtccom.vue

@@ -0,0 +1,485 @@
+<template>
+  <div class="trtccom" v-if="show">
+    <Device @switchDevice="switchDevice" @canUseDevice="canUseDevice" />
+    <div class="local" :class="{ disabledlocal: role == 'customer' || !videoDeviceId }" id="local" v-if="isJoined">
+      <div class="micBox">
+        <img v-if="muteAudioLeader" :src="require('@/assets/images/rtcLive/mic_off@2x.png')" alt="" />
+        <i v-else class="speak_mic"></i>
+      </div>
+    </div>
+
+    <template v-if="isJoined && invitedRemoteStreams.length > 0">
+      <div
+        class="local"
+        :data-role="item.userId_"
+        :class="{ disabledlocal: item.userId_.indexOf('customer') > -1  || !videoDeviceId }"
+        v-for="item in invitedRemoteStreams"
+        :id="item.userId_"
+        :key="item.userId_"
+      >
+        <div class="micBox">
+          <img v-if="muteAudioLeader" :src="require('@/assets/images/rtcLive/mic_off@2x.png')" alt="" />
+          <i v-else class="speak_mic"></i>
+        </div>
+      </div>
+    </template>
+
+    <div class="videoBox userVideo" v-show="props.videoMuted || muteVideoLeader">
+      <img :src="require('@/assets/images/rtcLive/avatar_small@2x.png')" alt="" />
+      <div class="micBox">
+        <img v-if="muteAudioLeader" :src="require('@/assets/images/rtcLive/mic_off@2x.png')" alt="" />
+        <i v-else class="speak_mic"></i>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import TRTC, { Client, LocalStream } from "trtc-js-sdk";
+import { Dialog } from "@/global_components/";
+import { ref, computed, watch, defineEmits, defineProps, nextTick } from "vue";
+import Device from "./trtc/Device";
+import { useStore } from "vuex";
+import browser from "@/utils/browser";
+
+const emit = defineEmits(["audioMuted", "videoMuted"]);
+
+const store = useStore();
+
+const show = ref(false);
+const invitedRemoteStreams = ref([]);
+
+const role = ref(browser.getURLParam("role"));
+
+const muteAudioLeader = ref(false);
+const muteVideoLeader = ref(false);
+
+const isJoined = computed(() => store.getters["rtc/isJoined"]);
+const isPublished = computed(() => store.getters["rtc/isPublished"]);
+const userSig = computed(() => store.getters["rtc/userSig"]);
+const videoDeviceId = computed(() => store.getters["rtc/videoDeviceId"]);
+
+const initParamsStates = computed(
+  () => !!(store.getters["rtc/sdkAppId"] && store.getters["rtc/secretKey"] && store.getters["rtc/roomId"] && store.getters["rtc/userId"])
+);
+
+let localClient = "";
+let localStream = "";
+let shareClient = "";
+
+const props = defineProps({
+  audioMuted: {
+    default: false,
+  },
+  videoMuted: {
+    default: false,
+  },
+});
+
+watch(
+  () => props.audioMuted,
+  () => {
+    if (props.audioMuted) {
+      localStream.muteAudio();
+    } else {
+      localStream.unmuteAudio();
+    }
+    if (role.value == "leader") {
+      muteAudioLeader.value = props.audioMuted;
+    }
+  }
+);
+
+watch(
+  () => props.videoMuted,
+  () => {
+    if (props.videoMuted) {
+      localStream.muteVideo();
+    } else {
+      localStream.unmuteVideo();
+    }
+  }
+);
+
+watch(
+  () => isJoined.value,
+  () => {
+    if (!isJoined.value) {
+      handleLeave();
+    }
+  }
+);
+
+TRTC.checkSystemRequirements().then((checkResult) => {
+  if (!checkResult.result) {
+    Dialog.toast({ content: `您的設備不支持音視頻通訊`, type: "error" });
+  } else {
+    show.value = true;
+  }
+});
+
+async function createLocalStream() {
+  let isLeader = role.value == "leader";
+  try {
+    localStream = TRTC.createStream({
+      userId: store.getters["rtc/userId"],
+      audio: true,
+      video: isLeader,
+      cameraId: isLeader ? store.getters["rtc/videoDeviceId"] : "",
+      microphoneId: store.getters["rtc/audioDeviceId"],
+    });
+    isLeader && localStream.setVideoProfile("480p");
+
+    await localStream.initialize();
+  } catch (error) {
+    console.log(error, "createStream");
+  }
+}
+
+async function handleJoin() {
+  if (!initParamsStates.value) {
+    return;
+  }
+
+  try {
+    localClient = TRTC.createClient({
+      mode: "rtc",
+      sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
+      userId: store.getters["rtc/userId"],
+      userSig: userSig.value,
+    });
+    installEventHandlers();
+
+    await localClient.join({ roomId: parseInt(store.getters["rtc/roomId"], 10) });
+    store.commit("rtc/setIsJoined", true);
+    // inviteLink.value = store.commit("rtc/createShareLink");
+  } catch (error) {
+    console.error(error, "error-----------");
+  }
+
+  await createLocalStream();
+  await handlePublish();
+  localStream
+    .play("local")
+    .then(() => {
+      // addLocalControlView();
+    })
+    .catch((e) => {});
+}
+
+async function handlePublish() {
+  if (!isJoined.value) {
+    return;
+  }
+  if (isPublished.value) {
+    return;
+  }
+
+  try {
+    await localClient.publish(localStream);
+    store.commit("rtc/setIsPublished", true);
+  } catch (error) {}
+}
+
+async function handleStartShare() {
+  shareClient = new ShareClient({
+    sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
+    userId: `share${store.getters["rtc/userId"]}`,
+    roomId: parseInt(store.getters["rtc/roomId"], 10),
+    secretKey: store.getters["rtc/secretKey"],
+  });
+  try {
+    await shareClient.join();
+    await shareClient.publish();
+    console.log("Start share screen success");
+    store.isShared = true;
+  } catch (error) {
+    console.error(`Start share error: ${error.message_}`);
+  }
+}
+
+async function handleUnpublish() {
+  if (!isJoined.value) {
+    return;
+  }
+  if (!isPublished.value) {
+    return;
+  }
+  try {
+    await localClient.unpublish(localStream);
+    store.commit("rtc/setIsPublished", false);
+  } catch (error) {}
+}
+
+async function handleLeave() {
+  if (isPublished.value) {
+    await handleUnpublish();
+  }
+  try {
+    uninstallEventHandlers();
+    await localClient.leave();
+    if (localStream) {
+      localStream.stop();
+      localStream.close();
+      localStream = null;
+    }
+  } catch (error) {}
+}
+
+function installEventHandlers() {
+  if (!localClient) {
+    return;
+  }
+  localClient.on("error", handleError);
+  localClient.on("client-banned", handleBanned);
+  localClient.on("peer-join", handlePeerJoin);
+  localClient.on("peer-leave", handlePeerLeave);
+  localClient.on("stream-added", handleStreamAdded);
+  localClient.on("stream-subscribed", handleStreamSubscribed);
+  localClient.on("stream-removed", handleStreamRemoved);
+  localClient.on("stream-updated", handleStreamUpdated);
+  localClient.on("mute-video", handleMuteVideo);
+  localClient.on("mute-audio", handleMuteAudio);
+  localClient.on("unmute-video", handleUnmuteVideo);
+  localClient.on("unmute-audio", handleUnmuteAudio);
+}
+
+function uninstallEventHandlers() {
+  if (!localClient) {
+    return;
+  }
+  localClient.off("error", handleError);
+  localClient.off("error", handleError);
+  localClient.off("client-banned", handleBanned);
+  localClient.off("peer-join", handlePeerJoin);
+  localClient.off("peer-leave", handlePeerLeave);
+  localClient.off("stream-added", handleStreamAdded);
+  localClient.off("stream-subscribed", handleStreamSubscribed);
+  localClient.off("stream-removed", handleStreamRemoved);
+  localClient.off("stream-updated", handleStreamUpdated);
+  localClient.off("mute-video", handleMuteVideo);
+  localClient.off("mute-audio", handleMuteAudio);
+  localClient.off("unmute-video", handleUnmuteVideo);
+  localClient.off("unmute-audio", handleUnmuteAudio);
+}
+
+function handleMuteVideo(event) {
+  console.log(`[${event.userId}] mute video`);
+  if (event.userId.indexOf("leader") > -1) {
+    muteVideoLeader.value = true;
+  }
+}
+
+function handleMuteAudio(event) {
+  if (event.userId.indexOf("leader") > -1) {
+    muteAudioLeader.value = true;
+  }
+  console.log(event, `[] mute audio`);
+}
+
+function handleUnmuteVideo(event) {
+  console.log(`[${event.userId}] unmute video`);
+  if (event.userId.indexOf("leader") > -1) {
+    muteVideoLeader.value = false;
+  }
+}
+
+function handleUnmuteAudio(event) {
+  console.log(`[${event.userId}] unmute audio`);
+  if (event.userId.indexOf("leader") > -1) {
+    muteAudioLeader.value = false;
+  }
+}
+
+function handleError(error) {
+  console.log(`LocalClient error: ${error.message_}`);
+}
+
+function handleBanned(error) {
+  console.log(`Client has been banned for ${error.message_}`);
+}
+
+function handlePeerJoin(event) {
+  const { userId } = event;
+  if (userId !== "local-screen") {
+    console.log(`Peer Client [${userId}] joined`);
+  }
+}
+
+function handlePeerLeave(event) {
+  const { userId } = event;
+  if (userId !== "local-screen") {
+    console.log(`[${userId}] leave`);
+  }
+}
+
+function handleStreamAdded(event) {
+  const remoteStream = event.stream;
+  const id = remoteStream.getId();
+  const userId = remoteStream.getUserId();
+
+  console.log(remoteStream, "-------------remoteStream");
+
+  if (remoteStream.getUserId() === store.getters["rtc/userId"]) {
+    // don't need to screen shared by us
+    localClient.unsubscribe(remoteStream).catch((error) => {
+      console.error(`unsubscribe failed: ${error.message_}`);
+    });
+  } else {
+    console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
+    localClient.subscribe(remoteStream).catch((error) => {
+      console.error(`subscribe failed: ${error.message_}`);
+    });
+  }
+}
+
+async function handleStreamSubscribed(event) {
+  const remoteStream = event.stream;
+
+  if (remoteStream.userId_ == store.getters["rtc/userId"]) {
+    return;
+  }
+
+  if (!invitedRemoteStreams.value.some((item) => item.userId_ == remoteStream.userId_)) {
+    invitedRemoteStreams.value.push(remoteStream);
+  }
+
+  console.log(invitedRemoteStreams.value, "invitedRemoteStreams.value");
+
+  await nextTick();
+  setTimeout(() => {
+  console.log(remoteStream.userId_,'remoteStream.getId()');
+    remoteStream
+      .play(remoteStream.userId_)
+      .then(() => {
+        console.log(`RemoteStream play success`, 88888888888888888888);
+      })
+      .catch((error) => {
+        console.log(`RemoteStream play failed:  error: ${error.message_}`);
+      });
+  }, 100);
+
+  // const remoteStream = event.stream;
+  // const userId = remoteStream.getUserId();
+  // console.log(`RemoteStream subscribed: [${userId}]`);
+}
+
+function handleStreamRemoved(event) {
+  const remoteStream = event.stream;
+  const userId = remoteStream.getUserId();
+  console.log(`RemoteStream removed: [${userId}]`);
+}
+
+function handleStreamUpdated(event) {
+  const remoteStream = event.stream;
+  const userId = remoteStream.getUserId();
+  console.log(`RemoteStream updated: [${userId}] audio:${remoteStream.hasAudio()} video:${remoteStream.hasVideo()}`);
+}
+
+let switchDevice = async ({ videoId, audioId }) => {
+  if (!isJoined.value) {
+    return;
+  }
+  if (videoId) {
+    try {
+      await localStream.switchDevice("video", videoId);
+    } catch (error) {}
+  }
+  if (audioId) {
+    try {
+      await localStream.switchDevice("audio", audioId);
+    } catch (error) {}
+  }
+};
+
+let canUseDevice = () => {
+  console.log("可用");
+  handleJoin();
+};
+</script>
+
+<style lang="scss" scoped>
+.trtccom {
+  .local {
+    width: 70px;
+    height: 70px;
+    position: fixed;
+    z-index: 9999;
+    top: 20px;
+    left: 20px;
+    border-radius: 50%;
+    overflow: hidden;
+    background: url(~@/assets/images/rtcLive/avatar_small@2x.png) center center no-repeat;
+  }
+  .videoBox {
+    width: 72px;
+    height: 72px;
+    top: 19px;
+    left: 19px;
+    position: fixed;
+    z-index: 99999;
+    border-radius: 50%;
+    overflow: hidden;
+    .loadingTip {
+      position: absolute;
+      z-index: 101;
+      left: 0;
+      top: 0;
+      bottom: 0;
+      right: 0;
+      animation: Rotate 1.5s infinite;
+      @keyframes Rotate {
+        0% {
+          transform: rotate(0deg);
+        }
+        100% {
+          transform: rotate(360deg);
+        }
+      }
+    }
+    > img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .micBox {
+    width: 100%;
+    height: 16px;
+    background: rgba(0, 0, 0, 0.3);
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    z-index: 100;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .speak_mic {
+      display: block;
+      background-size: 720px auto;
+      width: 12px;
+      height: 12px;
+      background-image: url(~@/assets/images/rtcLive/speed.png);
+      // width: 0.69rem;
+      // height: 0.69rem;
+      animation: myAnimation 3s steps(59) infinite;
+    }
+    > img {
+      width: 12px;
+    }
+  }
+  .disabledlocal {
+    opacity: 0 !important;
+    visibility: hidden !important;
+  }
+}
+
+@keyframes myAnimation {
+  0% {
+    background-position: 0px 0px;
+  }
+
+  100% {
+    background-position: -708px 0px;
+  }
+}
+</style>

+ 67 - 0
src/components/RTC/chat/chat.vue

@@ -0,0 +1,67 @@
+<template>
+  <div id="chat">
+    <div id="contents">
+      <div class="chat-item" v-for="(i, index) in chatList" :key="index">
+        <div class="chat-msg">
+          <span :class="{ my: i.UserId == user_info.UserId }" class="chat-name"> {{ i.Nickname }} &nbsp;</span>
+          <span class="chat-content"> {{ i.text }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, watch, defineProps, defineEmits, ref, nextTick } from "vue";
+
+const props = defineProps({
+  user_info: {
+    type: Object,
+    default: {},
+  },
+  chatList: {
+    type: Array,
+    default: [],
+  },
+});
+</script>
+
+<style scoped lang="scss">
+#chat {
+  // width: 7.03rem;
+  width: 50%;
+  max-height: 4.58rem;
+  overflow: auto;
+  position: fixed;
+  left: 0.44rem;
+  bottom: 2.83rem;
+  .chat-item {
+  }
+  .chat-msg {
+    width: auto;
+    border-radius: 0.44rem;
+    background: rgba(0, 0, 0, 0.3);
+    padding: 0.17rem 0.28rem;
+    box-sizing: border-box;
+
+    display: inline-block;
+    // margin-bottom: 0.17rem;
+    margin-bottom: 0.1rem;
+    .chat-name {
+      color: #70bbff;
+      font-size: 0.28rem;
+      &.my {
+        color: #ff9a6a;
+      }
+    }
+    .chat-content {
+      color: #fff;
+      font-size: 0.28rem;
+      word-break: break-all;
+    }
+  }
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+</style>

+ 94 - 0
src/components/RTC/dialog/checkBrowser.vue

@@ -0,0 +1,94 @@
+<template>
+  <div id="checkBrowser">
+    <i class="iconfont iconshow_cancel" @click="closeCheckBrowser"></i>
+    <p class="title">建議使用以下最新版本的瀏覽器用於通話</p>
+    <div class="browser_list">
+      <div class="item" v-for="i,index in browserList" :key="index">
+        <div class="browser_icon">
+          <img :src="$config.getStaticResource(`img/apps/rtcLive/${i.icon}.png`)" alt="">
+        </div>
+        <div class="browser_name">{{i.name}}</div>
+        <!-- <div class="browser_version ">{{i.version}}</div> -->
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      browserList: [
+        {
+          icon: "chrome",
+          name: "Chrome",
+          version: "60",
+        },
+        {
+          icon: "firefox",
+          name: "Firefox",
+          version: "55",
+        },
+        {
+          icon: "edge",
+          name: "Edge",
+          version: "40",
+        },
+        {
+          icon: "safari",
+          name: "Safari",
+          version: "11",
+        },
+      ],
+    };
+  },
+  components: {},
+  methods: {
+    closeCheckBrowser() {
+      this.$parent.showCheckBrowser = false;
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+#checkBrowser {
+  position: fixed;
+
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 1000;
+  background: rgba(0, 0, 0, 0.5);
+  padding: 1.11rem;
+  border-radius: 0.14rem;
+  text-align: center;
+  z-index: 10000;
+  .iconshow_cancel {
+    font-size: 0.42rem;
+    position: absolute;
+    right: 0.33rem;
+    top: 0.33rem;
+  }
+  .title {
+    font-size: 0.28rem;
+    color: #fff;
+  }
+  .browser_list {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0.42rem 0 0 0;
+    .item {
+      img {
+        width: 1.39rem;
+        height: auto;
+      }
+      margin-right: 0.28rem;
+      &:last-of-type {
+        margin-right: 0;
+      }
+    }
+  }
+}
+</style>

+ 279 - 0
src/components/RTC/dialog/createdRoom.vue

@@ -0,0 +1,279 @@
+<template>
+  <div id="createdRoom" @click.stop>
+    <div class="created_dialog">
+      <div class="blurBox"></div>
+      <div class="content">
+        <div class="dialog_title" v-if="role == 'leader'">創建帶看場景</div>
+        <div class="dialog_title" v-else>進入帶看場景</div>
+        <div class="user_name">
+          <input
+            class="input_name"
+            maxlength="20"
+            v-model.trim="userName"
+            type="text"
+            :placeholder="role == 'leader' ? ' 請輸入帶看主持人名稱' : '請輸入您的昵稱'"
+          />
+          <span class="limitNum">{{ userName.length }}/20</span>
+        </div>
+        <!-- <div v-if="role!='customer'" class="mode_btn">
+          <div @click="chooseMode(i.mode)" v-for="i,index in modeList" :key="index" :class="{ active: mode==i.mode }" class="mode">{{i.title}}</div>
+        </div> -->
+        <div class="created_btn">
+          <div class="created_cancel" @click="closeCreated">取消</div>
+          <div class="created_confirm" @click="createdConfirm">確認</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Dialog } from "@/global_components/";
+import browser from "@/utils/browser";
+
+export default {
+  data() {
+    return {
+      role: browser.getURLParam("role") || "leader",
+      mode: browser.getURLParam("mode") || 2,
+      modeList: [
+        {
+          mode: 1,
+          title: "1V1",
+        },
+        {
+          mode: 2,
+          title: "多人模式",
+        },
+      ],
+      userName: "",
+      roomId: browser.getURLParam("roomId"),
+    };
+  },
+
+  mounted() {},
+  components: {},
+  // created: {},
+  // mounted:{},
+  methods: {
+    getUrl(href,queryArr){
+
+      queryArr.forEach(item=>{
+        if (!browser.hasURLParam(item.key)) {
+          href += `&${item.key}=${item.val}`;
+        } else {
+          href = browser.replaceQueryString(href, item.key, item.val);
+        }
+      })
+      
+      return href
+    },
+
+    chooseMode(mode) {
+      this.mode = mode;
+    },
+    closeCreated() {
+      this.$emit("closeCreated");
+      !browser.hasURLParam("pose")
+    },
+    createdConfirm() {
+      if (this.userName == "") {
+        Dialog.toast({ content: '請輸入您的昵稱', type: 'error' })
+        return;
+      }
+      let name = encodeURIComponent(this.userName);
+      let hh = window.location.href
+
+
+      if (this.role == "customer") {
+        let tempUrl = this.getUrl(hh, [{
+          key:'mode',
+          val:this.mode
+        },{
+          key:'name',
+          val:name
+        },{
+          key:'role',
+          val:'customer'
+        },{
+          key:'roomId',
+          val:this.roomId
+        }])
+        // history.replaceState(null, null, hh + "&mode=" + this.mode + "&name=" + name + "&role=customer&roomId=" + this.roomId);
+        history.replaceState(null, null, tempUrl);
+      } else {
+
+        let tempUrl = this.getUrl(hh, [{
+          key:'mode',
+          val:this.mode
+        },{
+          key:'name',
+          val:name
+        },{
+          key:'role',
+          val:'leader'
+        }])
+
+        console.log(tempUrl);
+     
+        // history.replaceState(null, null,hh + "&mode=" + this.mode + "&name=" + name + "&role=leader");
+        history.replaceState(null, null, tempUrl);
+
+      }
+      this.$nextTick(() => {
+        this.$emit("createdConfirm");
+      });
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+#createdRoom {
+  width: 100vw;
+  height: 100%;
+  // background: rgba(0, 0, 0, 0.5);
+  background: transparent;
+  position: fixed;
+  left: 0;
+  top: 0;
+  z-index: 1000000;
+  // pointer-events: none;
+  .created_dialog {
+    width: 8.64rem;
+    min-height: 5rem;
+    // background: #ffffff;
+    pointer-events: auto;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    // overflow: hidden;
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 4px;
+
+    .blurBox {
+      position: absolute;
+      z-index: 1;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.7);
+      filter: blur(1px);
+    }
+    .content {
+      position: relative;
+      z-index: 2;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+    }
+    .dialog_title {
+      font-size: 0.39rem;
+      width: 100%;
+      height: 1.39rem;
+      padding: 0 0.56rem;
+      box-sizing: border-box;
+      font-size: 0.39rem;
+      color: #fff;
+      line-height: 1.39rem;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      border-bottom-style: solid;
+      border-bottom-width: 1px;
+      border-bottom-color: rgba(255, 255, 255, 0.1);
+    }
+    .user_name {
+      width: 100%;
+      height: 1.11rem;
+      padding: 0 0.56rem;
+      box-sizing: border-box;
+      font-size: 0.39rem;
+      line-height: 1.11rem;
+      margin: 0.56rem 0;
+      position: relative;
+      .limitNum {
+        position: absolute;
+        right: 0.64rem;
+        top: 50%;
+        transform: translateY(-50%);
+        font-size: 0.33rem;
+        color: #b9bdbc;
+      }
+      .input_name {
+        font-size: 0.39rem;
+        width: 100%;
+        height: 100%;
+        line-height: 1.11rem;
+        padding: 0 1.066667rem 0 0.28rem;
+        box-sizing: border-box;
+        background: rgba(0, 0, 0, 0.5);
+        border-radius: 4px;
+        color: #fff;
+        border: none;
+        outline: none;
+        &::placeholder {
+          color: rgba(255, 255, 255, 0.3);
+        }
+      }
+    }
+    .mode_btn {
+      width: 100%;
+      height: 1.11rem;
+      padding: 0 0.56rem;
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 0.56rem;
+      > div.mode {
+        width: 3.61rem;
+        height: 100%;
+        border-radius: 0.65rem;
+        border: 0.03rem solid #fff;
+        color: #fff;
+        font-size: 0.39rem;
+        line-height: 1.11rem;
+        text-align: center;
+        box-sizing: border-box;
+        &.active {
+          color: #ed5d18;
+          border: 0.03rem solid #ed5d18;
+        }
+      }
+    }
+    .created_btn {
+      width: 100%;
+      height: 1.36rem;
+      border-top-style: solid;
+      border-top-width: 1px;
+      border-top-color: rgba(255, 255, 255, 0.1);
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 0.39rem;
+      > div {
+        width: 50%;
+        height: 1.36rem;
+        text-align: center;
+        line-height: 1.36rem;
+        font-size: 0.39rem;
+        box-sizing: border-box;
+        &.created_cancel {
+          color: #fff;
+          border-right-style: solid;
+          border-right-width: 1px;
+          border-right-color: rgba(255, 255, 255, 0.1);
+        }
+        &.created_confirm {
+          color: #ed5d18;
+        }
+      }
+    }
+  }
+}
+</style>

+ 143 - 0
src/components/RTC/dialog/index.vue

@@ -0,0 +1,143 @@
+<template>
+  <div id="dialog_index">
+    <div class="created_dialog">
+      <div class="blurBox"></div>
+      <div class="content">
+        <div class="dialog_title">{{ props.title }}</div>
+        <p class="dialog_desc">{{ props.desc }}</p>
+        <div class="created_btn">
+          <div class="end_cancel" @click="endLiveCancel">取消</div>
+          <div class="end_confirm" @click="endLiveConfirm">確認</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import browser from "@/utils/browser";
+import { onMounted, watch, defineProps, defineEmits, ref, computed } from "vue";
+import { useStore } from "vuex";
+const store = useStore();
+
+const emit = defineEmits(["closeDialog","confirmDialog"]);
+
+const role = ref(browser.urlHashValue("role"));
+const socket = computed(() => store.getters["rtc/socket"]);
+
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: "溫馨提示",
+  },
+  desc: {
+    type: String,
+    default: "是否結束帶看",
+  },
+});
+
+const endLiveCancel = () => {
+  emit("closeDialog");
+};
+
+const endLiveConfirm = () => {
+  // socket.value.emit("disconnect");
+  emit("confirmDialog");
+};
+</script>
+
+<style scoped lang="scss">
+#dialog_index {
+  width: 100vw;
+  height: 100%;
+  // background: rgba(0, 0, 0, 0.5);
+  position: fixed;
+  left: 0;
+  top: 0;
+  z-index: 100000;
+  pointer-events: none;
+  .created_dialog {
+    pointer-events: auto;
+    width: 8.64rem;
+    // min-height: 5rem;
+    // background: #ffffff;
+    border-radius: 8px;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 4px;
+    .blurBox {
+      position: absolute;
+      z-index: 1;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.7);
+      filter: blur(1px);
+    }
+    .content {
+      position: relative;
+      z-index: 2;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+    }
+    .dialog_title {
+      width: 100%;
+      height: 1.39rem;
+      padding: 0 0.56rem;
+      box-sizing: border-box;
+      font-size: 0.39rem;
+      color: #fff;
+      line-height: 1.39rem;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      border-bottom-style: solid;
+      border-bottom-width: 1px;
+      border-bottom-color: rgba(255, 255, 255, 0.1);
+    }
+    .dialog_desc {
+      font-size: 0.42rem;
+      color: #fff;
+      padding: 0.56rem 0;
+      text-align: center;
+    }
+
+    .created_btn {
+      width: 100%;
+      height: 1.36rem;
+      border-top-style: solid;
+      border-top-width: 1px;
+      border-top-color: rgba(255, 255, 255, 0.1);
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 0.39rem;
+      > div {
+        width: 50%;
+        height: 1.36rem;
+        text-align: center;
+        line-height: 1.36rem;
+        font-size: 0.39rem;
+        box-sizing: border-box;
+        &.end_cancel {
+          color: #fff;
+          border-right-style: solid;
+          border-right-width: 1px;
+          border-right-color: rgba(255, 255, 255, 0.1);
+        }
+        &.end_confirm {
+          color: #ed5d18;
+        }
+      }
+    }
+  }
+}
+</style>

+ 169 - 0
src/components/RTC/dialog/share.vue

@@ -0,0 +1,169 @@
+<template>
+  <div id="dialog_index">
+    <div class="created_dialog">
+      <div class="blurBox"></div>
+      <div class="content">
+        <div class="dialog_title">{{ title }}</div>
+        <div class="dialog_link">
+          <p>
+            {{ shareLink }}
+          </p>
+        </div>
+
+        <div class="created_btn">
+          <div class="created_cancel" @click="closeCreated">取消</div>
+          <div class="created_confirm"  ref="copylink$" :data-clipboard-text="shareLink" @click="createdConfirm">復製分享</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, watch, defineProps, defineEmits, ref, nextTick } from "vue";
+import ClipboardJS from 'clipboard'
+import { Dialog } from '@/global_components/'
+
+const emit = defineEmits(["closeDialog"]);
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: "邀請好友",
+  },
+  shareLink: {
+    type: String,
+    default: "",
+  },
+});
+const copylink$ = ref(null)
+
+const closeCreated = () => {
+  emit("closeDialog");
+};
+
+const createdConfirm = () => {
+  emit("closeDialog");
+};
+
+onMounted(() => {
+   nextTick(()=>{
+     new ClipboardJS(copylink$.value).on('success', function (e) {
+        e.clearSelection()
+        Dialog.toast({ content: '鏈接復製成功', type: 'success' })
+    })
+   })
+})
+
+</script>
+
+<style scoped lang="scss">
+#dialog_index {
+  width: 100vw;
+  height: 100%;
+  // background: rgba(0, 0, 0, 0.5);
+  position: fixed;
+  left: 0;
+  top: 0;
+  z-index: 100000;
+  pointer-events: none;
+  .created_dialog {
+    width: 8.64rem;
+    // min-height: 5rem;
+    // background: #ffffff;
+    border-radius: 8px;
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    pointer-events: auto;
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 4px;
+    .blurBox {
+      position: absolute;
+      z-index: 1;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.7);
+      filter: blur(1px);
+    }
+    .content {
+      position: relative;
+      z-index: 2;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+    }
+    .dialog_title {
+      width: 100%;
+      height: 1.39rem;
+      padding: 0 0.56rem;
+      box-sizing: border-box;
+      font-size: 0.39rem;
+      color: #fff;
+      line-height: 1.39rem;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      border-bottom-style: solid;
+      border-bottom-width: 1px;
+      border-bottom-color: rgba(255, 255, 255, 0.1);
+    }
+    .dialog_link {
+      width: 100%;
+      font-size: 0.39rem;
+      color: rgba(255, 255, 255, 0.5);
+      padding: 0.53rem 0.56rem;
+      box-sizing: border-box;
+      text-align: justify;
+      text-align: left;
+      > p {
+        background: rgba(0, 0, 0, 0.5);
+        padding: 0.15rem 0.28rem;
+        word-break: break-all;
+        word-wrap: break-word;
+        text-overflow: -o-ellipsis-lastline;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        display: -webkit-box;
+        -webkit-line-clamp: 2;
+        line-clamp: 2;
+        -webkit-box-orient: vertical;
+        line-height: 0.72rem;
+      }
+    }
+
+    .created_btn {
+      width: 100%;
+      height: 1.36rem;
+      border-top-style: solid;
+      border-top-width: 1px;
+      border-top-color: rgba(255, 255, 255, 0.1);
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      > div {
+        width: 50%;
+        height: 1.36rem;
+        text-align: center;
+        line-height: 1.36rem;
+        font-size: 0.39rem;
+        box-sizing: border-box;
+        &.created_cancel {
+          color: #fff;
+          border-right-style: solid;
+          border-right-width: 1px;
+          border-right-color: rgba(0, 0, 0, 0.05);
+        }
+        &.created_confirm {
+          color: #ed5d18;
+        }
+      }
+    }
+  }
+}
+</style>

+ 91 - 0
src/components/RTC/index.vue

@@ -0,0 +1,91 @@
+<template>
+  <transition mode="out-in">
+    <div>
+      <dialogIndex @closeDialog="closeDialog" @confirmDialog="confirmDialog" v-if="dialog == 'dialogIndex'"></dialogIndex>
+      <dialogShare :shareLink="shareLink" @closeDialog="closeDialog" v-if="dialog == 'dialogShare'"></dialogShare>
+      <createdRoom v-if="showCreated" @closeCreated="closeCreated" @createdConfirm="createdConfirm()"></createdRoom>
+      <PageRtcLive @closeSocket="confirmDialog" @openDialog="openDialog" v-if="show"></PageRtcLive>
+    </div>
+  </transition>
+</template>
+
+<script setup>
+import PageRtcLive from "./PageRtcLive";
+// import Draw from "./paint/Draw";
+import createdRoom from "./dialog/createdRoom";
+import dialogIndex from "./dialog/index.vue";
+import dialogShare from "./dialog/share.vue";
+import browser from "@/utils/browser";
+import { onMounted, watch, computed, ref, nextTick } from "vue";
+import { useStore } from "vuex";
+import { useApp, getApp } from "@/app";
+
+const store = useStore();
+
+const shareLink = ref("");
+const dialog = ref("");
+const show = ref(false);
+const showPaint = ref(true);
+const showCreated = ref(false);
+const roomId = ref(browser.getURLParam("roomId"));
+const role = ref(browser.getURLParam("role"));
+const userName = ref(browser.getURLParam("name"));
+const socket = computed(() => store.getters["rtc/socket"]);
+
+
+const openDialog = (str, link) => {
+  shareLink.value = link;
+  dialog.value = str;
+};
+const closeDialog = (str, link) => {
+  dialog.value = "";
+  dialog.value = str;
+};
+
+const confirmDialog = async () => {
+  await getApp().Connect.follow.exit();
+  store.commit("rtc/setIsJoined", false);
+  if (socket.value) {
+    if(role.value == 'leader'){
+      socket.value.emit("action", { type: "leader-dismiss" });
+    }
+
+    socket.value.close();
+    store.commit("rtc/setSocket", null);
+  }
+
+  
+  let tempUrl = window.location.href;
+  ["mode", "name", "role", "roomId", "userId"].forEach((item) => {
+    tempUrl = browser.replaceQueryString(tempUrl, item, "");
+  });
+  history.replaceState(null, null, tempUrl);
+
+  store.commit("showShoppingguide", false);
+  dialog.value = "";
+};
+
+const closeCreated = (str, link) => {
+  store.commit("showShoppingguide", false);
+  showCreated.value = false;
+};
+
+const createdConfirm = (str, link) => {
+  showCreated.value = false;
+  show.value = true;
+};
+
+onMounted(() => {
+  useApp().then(async (sdk) => {
+    await nextTick();
+    if (userName.value) {
+      createdConfirm();
+    } else {
+      showCreated.value = true;
+    }
+  });
+  // showCreated.value = true;
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 481 - 0
src/components/RTC/paint/Draw.vue

@@ -0,0 +1,481 @@
+<template>
+  <div class="paint">
+    <canvas ref="canvas" v-show="show"></canvas>
+    <!-- <div class="toolbar" v-show="showPaint">
+      <ul>
+        <li @click="onDraw('drawStart')" v-show="show == false">
+          <i class="iconfont icontagging"></i>
+          <div>画笔</div>
+        </li>
+        <li @click="onDraw('drawUndo')" v-if="show" :class="{ disable: !canUndo }">
+          <i class="iconfont icon_cancel"></i>
+          <div>撤回</div>
+        </li>
+        <li @click="onDraw('drawStop')" v-if="show">
+          <i class="iconfont iconclose"></i>
+          <div>关闭</div>
+        </li>
+      </ul>
+    </div> -->
+  </div>
+</template>
+<script>
+import { getRole, sendToH5 } from "../../../rtc_socket";
+import { objects } from "@/core/base";
+import math from "@/core/util/math";
+import convertTool from "@/core/util/convertTool";
+
+export default {
+  props: {
+    showPaint: Boolean,
+  },
+  data() {
+    return {
+      role: getRole(),
+      show: false,
+      canUndo: false,
+      colorA: "#02c8ae",
+      colorB: "#2e98fe",
+    };
+  },
+  watch: {
+    showPaint() {
+      if (this.showPaint) {
+        this.role = getRole();
+      }
+    },
+    show() {
+      this.$bus.emit("shop/header/disable", this.show);
+    },
+  },
+  created() {
+    this.$bus.on("shop/sync/action", (data) => {
+      if (data.type == "drawStart") {
+        this.show = true;
+        this.draw = [];
+        this.drawHistory = [];
+        this.$nextTick(() => {
+          this.onDrawStart();
+        });
+      } else if (data.type == "drawStop") {
+        this.show = false;
+        this.draw = null;
+        this.drawHistory = null;
+      } else if (data.type == "drawing") {
+        const draw = this.transformTo2d(data.data.drawing);
+        if (data.data.role != this.role) {
+          this.drawHistory.push(JSON.parse(JSON.stringify(draw)));
+          this.drawing(draw);
+        }
+      } else if (data.type == "drawUndo") {
+        this.drawUndo(data.data.role);
+      }
+    });
+  },
+  mounted() {
+    this.canvas = this.$refs.canvas;
+    this.context = this.canvas.getContext("2d");
+    this.canvas.onmousedown = (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      this.beginStroke({
+        x: e.clientX,
+        y: e.clientY,
+      });
+    };
+    this.canvas.onmouseup = (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      this.endStroke();
+    };
+    this.canvas.onmouseout = (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      this.endStroke();
+    };
+    this.canvas.onmousemove = (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      if (this._mouseDown) {
+        this.moveStroke({
+          x: e.clientX,
+          y: e.clientY,
+        });
+      }
+    };
+
+    let touch;
+
+    // 移动端触控
+    this.canvas.addEventListener("touchstart", (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      touch = e.touches[0];
+      this.beginStroke({
+        x: touch.pageX,
+        y: touch.pageY,
+      });
+    });
+    this.canvas.addEventListener("touchmove", (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      if (this._mouseDown) {
+        touch = e.touches[0];
+        this.moveStroke({
+          x: touch.pageX,
+          y: touch.pageY,
+        });
+      }
+    });
+
+    this.canvas.addEventListener("touchend", (e) => {
+      if (!this.show || this.role != "leader") {
+        return;
+      }
+      e.preventDefault();
+      this.endStroke();
+    });
+
+    this.mouse = new THREE.Vector2();
+  },
+  methods: {
+    /**
+     * 2d数据转3d数据
+     */
+    transformTo3d(draw) {
+      const data = [];
+      if (draw.length == 0) {
+        return [];
+      }
+      draw.forEach((item, index) => {
+        math.convertScreenPositionToNDC(item.x, item.y, this.mouse);
+
+        var intersect = convertTool.getMouseIntersect(
+          objects.player.camera,
+          [this.intersectPlane],
+          this.mouse
+        );
+
+        if (!intersect) {
+          console.error("no intersect ??");
+        } else {
+          item.pos3d = intersect.point;
+          data.push(item);
+        }
+      });
+
+      return data;
+    },
+    /**
+     * 3d数据转2d数据
+     */
+    transformTo2d(draw) {
+      const data = [];
+      draw.forEach((item) => {
+        var pos3d = new THREE.Vector3(item.pos3d.x, item.pos3d.y, item.pos3d.z);
+        var pos2d = convertTool.getPos2d(pos3d, objects.player.camera);
+        //delete item.pos3d;
+        item.x = pos2d.pos.x;
+        item.y = pos2d.pos.y;
+        data.push(item);
+      });
+      return data;
+    },
+    onDraw(type) {
+      if (type == "drawStart") {
+        this.show = true;
+        this.draw = [];
+        this.drawHistory = [];
+        this.$nextTick(() => {
+          this.onDrawStart();
+        });
+      } else if (type == "drawStop") {
+        this.show = false;
+        this.draw = null;
+        this.drawHistory = null;
+      } else if (type == "drawing") {
+        const draw = this.transformTo2d(data.content.drawing);
+        if (data.role != role) {
+          this.drawHistory.push(JSON.parse(JSON.stringify(draw)));
+          //this.drawing(draw);
+        }
+      } else if (type == "drawUndo") {
+        this.drawUndo(this.role);
+      }
+      sendToH5({
+        type,
+        data: {
+          role: this.role,
+        },
+      });
+    },
+    onPainting() {
+      const draw = this.transformTo3d(this.draw);
+      this.drawHistory.push(JSON.parse(JSON.stringify(draw)));
+      sendToH5({
+        type: "drawing",
+        data: {
+          drawing: draw,
+        },
+      });
+      this.draw = [];
+      this.canUndo = true;
+      this._endTime = 0;
+      this._mouseDown = false;
+      this._lastTimestamp = 0;
+      this.$emit("sendCanUndo", this.canUndo);
+    },
+    beginStroke(point) {
+      this._mouseDown = true;
+      this._lastTimestamp = Date.now();
+      this._lastPosition = this.windowToCanvas(point.x, point.y);
+      this.draw.push({
+        role: this.role,
+        width: 0,
+        x: this._lastPosition.x,
+        y: this._lastPosition.y,
+        t: 5, //this._lastTimestamp - this._endTime
+      });
+    },
+    onDrawStart() {
+      let dpr = window.devicePixelRatio || 1;
+      let rect = this.canvas.getBoundingClientRect();
+      this.ratio = 1; // window.innerWidth / 375;
+      this.canvas.width = rect.width * dpr;
+      this.canvas.height = rect.height * dpr;
+      this.context.scale(dpr, dpr);
+      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+      this._endTime = 0;
+      this._mouseDown = false;
+      this._lastTimestamp = 0;
+      this._lastLineWidth = -1;
+      this._lastPosition = {
+        x: 0,
+        y: 0,
+      };
+
+      math.convertScreenPositionToNDC(0, 0, this.mouse); //取屏幕中点
+      var intersect = convertTool.getMouseIntersect(
+        objects.player.camera,
+        [objects.model.skybox, objects.sceneRenderer.scene.skyboxBG],
+        this.mouse
+      );
+      this.placeIntersectPlane(intersect && intersect.point);
+    },
+    moveStroke(point) {
+      let timestamp = Date.now();
+      let position = this.windowToCanvas(point.x, point.y);
+      let s = this.calcDistance(position, this._lastPosition);
+      let t = timestamp - this._lastTimestamp;
+      let lineWidth = this.calcLineWidth(t, s);
+
+      //draw
+      this.context.beginPath();
+      this.context.moveTo(this._lastPosition.x, this._lastPosition.y);
+      this.context.lineTo(position.x, position.y);
+
+      this.draw.push({
+        role: this.role,
+        width: lineWidth,
+        x: position.x,
+        y: position.y,
+        t: 5, //t
+      });
+
+      this.context.strokeStyle = this.colorA;
+      this.context.lineWidth = lineWidth;
+      this.context.lineCap = "round";
+      this.context.linJoin = "round";
+      this.context.stroke();
+
+      //每次过程结束时,将结束值赋给初始值,一直延续
+      this._lastPosition = position;
+      this._lastTimestamp = timestamp;
+      this._lastLineWidth = lineWidth;
+    },
+    endStroke() {
+      this.draw.push({
+        role: this.role,
+        width: 0,
+        x: this._lastPosition.x,
+        y: this._lastPosition.y,
+        t: 0,
+      });
+      this.onPainting();
+      this._mouseDown = false;
+      this._endTime = Date.now();
+    },
+    calcLineWidth(t, s) {
+      let v = s / t;
+      let resultLineWidth;
+
+      if (v <= 0.1) {
+        resultLineWidth = 6;
+      } else if (v >= 3) {
+        resultLineWidth = 2;
+      } else {
+        resultLineWidth = 6 - ((v - 0.1) / (3 - 0.1)) * (6 - 4);
+      }
+      if (this._lastLineWidth == -1) {
+        return resultLineWidth;
+      }
+      return (this._lastLineWidth * 2) / 3 + (resultLineWidth * 1) / 3;
+    },
+    calcDistance(pos1, pos2) {
+      return Math.sqrt(
+        (pos1.x - pos2.x) * (pos1.x - pos2.x) +
+          (pos1.y - pos2.y) * (pos1.y - pos2.y)
+      ); //通过起始结束坐标x,y值计算路程长度
+    },
+    windowToCanvas(x, y) {
+      var bbox = this.canvas.getBoundingClientRect(); //获取canvas的位置信息
+      return {
+        x: Math.round(x - bbox.left),
+        y: Math.round(y - bbox.top),
+      }; //返回当前鼠标相对于canvas的位置
+    },
+    drawing(draw) {
+      for (let i = 0; i < draw.length - 1; i++) {
+        draw[i].t &&
+          setTimeout(() => {
+            this.context.beginPath();
+            this.context.strokeStyle =
+              draw[i].role == this.role ? this.colorA : this.colorB;
+            this.context.moveTo(draw[i].x * this.ratio, draw[i].y * this.ratio);
+            this.context.lineTo(
+              draw[i + 1].x * this.ratio,
+              draw[i + 1].y * this.ratio
+            );
+            this.context.lineWidth = draw[i].width * this.ratio;
+            this.context.lineCap = "round";
+            this.context.linJoin = "round";
+            this.context.stroke();
+          }, 5);
+      }
+    },
+    drawUndo(sender) {
+      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+      for (let i = this.drawHistory.length - 1; i >= 0; i--) {
+        if (this.drawHistory[i][0].role == sender) {
+          this.drawHistory.splice(i, 1);
+          break;
+        }
+      }
+      this.drawHistory.forEach((draw) => {
+        for (let i = 0; i < draw.length - 1; i++) {
+          if (draw[i].t) {
+            this.context.beginPath();
+            this.context.strokeStyle =
+              draw[i].role == this.role ? this.colorA : this.colorB;
+            this.context.moveTo(draw[i].x * this.ratio, draw[i].y * this.ratio);
+            this.context.lineTo(
+              draw[i + 1].x * this.ratio,
+              draw[i + 1].y * this.ratio
+            );
+            this.context.lineWidth = draw[i].width * this.ratio;
+            this.context.lineCap = "round";
+            this.context.linJoin = "round";
+            this.context.stroke();
+          }
+        }
+      });
+      this.canUndo = this.drawHistory.some((item) => item[0].role == this.role);
+    },
+    placeIntersectPlane(pos) {
+      //用于判断mesh拖拽移动距离的平面 需要和视线垂直,以保证遮住视野范围
+      if (!this.intersectPlane) {
+        var geo = new THREE.PlaneGeometry(8000, 80000, 1, 1);
+        //var geo = new THREE.PlaneGeometry(3,3,1,1);
+        this.intersectPlane = new THREE.Mesh(
+          geo,
+          new THREE.MeshBasicMaterial({
+            transparent: true,
+            wireframe: false,
+            opacity: 0,
+            side: THREE.DoubleSide,
+            depthTest: false,
+          })
+        );
+        this.intersectPlane.lookAt(new THREE.Vector3(0, 1, 0));
+        this.intersectPlane.name = "intersectPlane";
+        objects.model.add(this.intersectPlane);
+      }
+      if (pos) {
+        this.intersectPlane.position.copy(pos);
+        var cameraDir = objects.player.getDirection(
+          null,
+          objects.player.camera
+        ); //向里
+        this.intersectPlane.lookAt(pos.clone().add(cameraDir)); //看向相机
+      }
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.paint {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 2000;
+  pointer-events: none !important;
+  canvas {
+    width: 100vw;
+    height: 100vh;
+    image-rendering: pixelated;
+    image-rendering: crisp-edges;
+    pointer-events: auto;
+    position: fixed;
+    top: 0;
+    left: 0;
+  }
+  .toolbar {
+    pointer-events: auto;
+    position: absolute;
+    right: 0.35rem;
+    bottom: 4.5rem;
+    padding: 0.4rem 0.2rem;
+    border-radius: 30px;
+    z-index: 100;
+    background-color: rgba(0, 0, 0, 0.3);
+    ul,
+    li {
+      margin: 0;
+      padding: 0;
+      list-style: none;
+    }
+    li {
+      padding: 0.3px;
+      text-align: center;
+      font-size: 14px;
+      margin-bottom: 0.5rem;
+      &:last-child {
+        margin-bottom: 0;
+      }
+      i {
+        font-size: 20px;
+        &.icon_cancel {
+          font-size: 22px;
+        }
+        &.iconclose {
+          font-size: 14px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 308 - 0
src/components/RTC/paint/index.vue

@@ -0,0 +1,308 @@
+<template>
+    <div class="paint" v-show="showPaint && role == 'leader'">
+        <div class="toolbar">
+            <ul>
+                <li v-show="show == false" @click="start">
+                    <i class="iconfont iconkd_tagging"></i>
+                    <div>标记</div>
+                </li>
+                <li v-if="show" @click="end">
+                    <i class="iconfont iconclose"></i>
+                    <div>关闭</div>
+                </li>
+            </ul>
+        </div>
+    </div>
+</template>
+<script>
+import { app } from "@/core"
+import { objects } from "@/core/base"
+import config from "@/config"
+import { getRole, sendToH5,sendToApp } from "../../../socket"
+import settings from "@/core/settings"
+import math from "@/core/util/math"
+import convertTool from "@/core/util/convertTool"
+import texture from "@/core/util/texture.js"
+import lerp from "@/core/util/lerp"
+import transitions from "@/core/util/transitions"
+import easing from "@/core/util/easing"
+
+let meshGroup = null //new THREE.Object3D()
+let circles = []
+const map = texture.load(config.getStaticResource("img/scene_tabele.png?v=1"), null, null, {
+    antialias: false,
+})
+
+export default {
+    props: {
+        showPaint: Boolean,
+    },
+    data() {
+        return {
+            role: getRole(),
+            show: false,
+            flags: [],
+            canUndo: false,
+            colorA: "#02c8ae",
+            colorB: "#2e98fe",
+        }
+    },
+    created() {
+        const self = this
+
+        if (meshGroup == null) {
+            meshGroup = new THREE.Object3D()
+            meshGroup.name = "shop-circles"
+            if (app.ready) {
+                app.model.add(meshGroup)
+            } else {
+                this.$bus.on("ready", function() {
+                    app.model.add(meshGroup)
+                })
+            }
+        }
+
+        function remove() {
+            setTimeout(() => {
+                self.flags.shift()
+                if (self.flags.length) {
+                    remove()
+                }
+                self.updateCirclesDrawing(self.flags)
+            }, 1000)
+        }
+
+        function syncAction(data) {
+            if (data.type == "flags") {
+                self.flags.push(data.data)
+                self.updateCirclesDrawing(self.flags)
+                remove()
+            }
+        }
+
+        this.$bus.on("shop/sync/action", syncAction)
+        this.$once("hook:beforeDestroy", () => {
+            this.$bus.off("shop/sync/action", syncAction)
+            this.show = false
+            this.flags = []
+            this.updateCirclesDrawing(this.flags)
+        })
+    },
+    watch: {
+        show() {
+            sendToApp('cart',!this.show)
+            this.$bus.emit("shop/header/disable", this.show)
+            setTimeout(() => {
+                app.play.control.noFly = this.show
+            }, 300);
+        },
+    },
+    mounted() {
+        var self = this
+        function draw(e) {
+            if (!self.show) {
+                return
+            }
+            const x = (e.touches[0] || e).clientX
+            const y = (e.touches[0] || e).clientY
+            const f = {
+                sid: Date.now(),
+                pos: self.transformTo3d({ x, y }),
+                color: self.colorA,
+            }
+            self.flags.push(f)
+            self.updateCirclesDrawing(self.flags)
+
+             setTimeout(() => {
+                self.flags.shift()
+                self.updateCirclesDrawing(self.flags)
+            }, 1000)
+
+            sendToH5({ type: "flags", data: f })
+           
+        }
+        const $player = document.getElementById("player")
+        $player.addEventListener("mousedown", draw)
+        $player.addEventListener("touchstart", draw)
+        this.role = getRole()
+    },
+    methods: {
+        start() {
+            this.show = true
+        },
+        undo() {
+            this.flags.pop()
+            this.updateCirclesDrawing(this.flags)
+        },
+        end() {
+            this.show = false
+            this.flags = []
+            this.updateCirclesDrawing(this.flags)
+        },
+        updateCirclesDrawing(flags) {
+            //呈现一系列circles  其中包含已经绘制好的
+
+            var dels = []
+            circles.forEach(circle => {
+                //检查是否有要删除的
+                var find = flags.find(info => circle.sid == info.sid)
+                if (!find) {
+                    dels.push(circle)
+                }
+            })
+            dels.forEach(circle => this.fadeCircle(circle))
+
+            flags.forEach(info => {
+                //再添加新的
+                var circle = meshGroup.children.find(circle => circle.sid == info.sid)
+                if (circle) {
+                    //如果要修改什么属性在这修改
+                } else {
+                    this.createCircle(info.sid, info.pos, info.color)
+                }
+            })
+        },
+
+        transformTo3d(pos2d) {
+            //获取三维坐标
+            const data = new THREE.Vector3()
+            const mouse = new THREE.Vector2()
+
+            math.convertScreenPositionToNDC(pos2d.x, pos2d.y, mouse)
+
+            var intersect = convertTool.getMouseIntersect(
+                objects.player.camera,
+                [objects.model.skybox],
+                mouse
+            )
+
+            if (!intersect) {
+                console.error("no intersect ??")
+            } else {
+                data.copy(intersect.point)
+                var dir = data
+                    .clone()
+                    .sub(objects.player.position)
+                    .normalize()
+                data.copy(objects.player.position.clone().add(dir))
+            }
+
+            return data
+        },
+        createCircle(sid, pos, color) {
+            //绘制新的圈
+
+            var circle = new THREE.Sprite(this.getMat(color))
+            circle.sid = sid
+            circle.position.copy(pos)
+            circle.quaternion.copy(objects.player.camera.quaternion)
+            circle.scale.set(0.05, 0.05, 0.05)
+            meshGroup.add(circle)
+            circles.push(circle)
+            /*var scale = math.getScaleForConstantSize({
+                width2d:100,
+                camera:objects.player.camera,
+                position: pos
+            })
+            bubble.scale.copy(scale)*/
+        },
+
+        fadeCircle(circle) {
+            //开始消失
+            //console.log("fadeCircle"+circle.sid)
+            var _duration = 800
+            transitions.start(
+                lerp.property(circle.material, "opacity", 0),
+                _duration,
+                () => {
+                    this.removeCircle(circle)
+                },
+                0,
+                easing[settings.transition.blendEasing],
+                "shop-circles-fade",
+                settings.freeze.shopCircle
+            )
+
+            //从列表中删除
+            var index = circles.indexOf(circle)
+            circles.splice(index, 1)
+        },
+
+        removeCircle(circle) {
+            //移除某个圈 <!-- sid -->
+            //var circle = meshGroup.children.find(circle=>circle.sid == sid);
+            //console.log("removeC " + circle.sid)
+            circle.material.dispose()
+            circle.parent.remove(circle)
+        },
+
+        getMat(color) {
+            /*var mat = this.materials[color]
+            if (mat) return mat
+            else {
+                mat = new THREE.SpriteMaterial({
+                    transparent: true,
+                    depthTest: false,
+                    map: map,
+                    side: THREE.DoubleSide,
+                    color: color,
+                })
+                this.materials[color] = mat
+                return mat
+            }*/
+            return new THREE.SpriteMaterial({
+                transparent: true,
+                depthTest: false,
+                map: map,
+                side: THREE.DoubleSide,
+                color: color,
+            })
+        },
+    },
+}
+</script>
+<style lang="scss" scoped>
+.paint {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 99;
+    pointer-events: none !important;
+    .toolbar {
+        pointer-events: auto;
+        position: absolute;
+        right: 0.35rem;
+        bottom: 4.5rem;
+        padding: 0.4rem 0.2rem;
+        border-radius: 30px;
+        z-index: 100;
+        background-color: rgba(0, 0, 0, 0.3);
+        ul,
+        li {
+            margin: 0;
+            padding: 0;
+            list-style: none;
+        }
+        li {
+            padding: 0.3px;
+            text-align: center;
+            font-size: 14px;
+            margin-bottom: 0.5rem;
+            &:last-child {
+                margin-bottom: 0;
+            }
+            i {
+                font-size: 20px;
+                &.icon_cancel {
+                    font-size: 22px;
+                }
+                // &.iconclose {
+                //     font-size: 14px;
+                // }
+            }
+        }
+    }
+}
+</style>

+ 9 - 0
src/components/RTC/socket/index.js

@@ -0,0 +1,9 @@
+import browser from "@/utils/browser"
+
+
+export let userName = decodeURIComponent(browser.urlHashValue("name"))
+export let roomId = browser.urlHashValue("roomId") || uuid(24)
+export let userId = browser.urlHashValue("userId") || uuid(12)
+export let mode = browser.urlHashValue("mode") //
+export let role = browser.urlHashValue("role") || "leader" //customer 普通用户
+export let muted = false //默认不静音

+ 81 - 0
src/components/RTC/trtc/Device.vue

@@ -0,0 +1,81 @@
+<template>
+  <div></div>
+</template>
+
+<script setup>
+import { defineEmits, computed, ref } from "vue";
+import TRTC from "trtc-js-sdk";
+import { useStore } from "vuex";
+import { Dialog } from "@/global_components/";
+import browser from "@/utils/browser";
+
+const store = useStore();
+
+const videoDeviceId = computed(() => store.getters["rtc/videoDeviceId"]);
+const audioDeviceId = computed(() => store.getters["rtc/audioDeviceId"]);
+const role = ref(browser.getURLParam("role"));
+
+const emit = defineEmits(["switchDevice", "canUseDevice"]);
+
+const updateDevice = async () => {
+  console.log("updateDevice");
+  const cameraItems = await TRTC.getCameras();
+  cameraItems.forEach((item) => {
+    item.value = item.deviceId;
+  });
+  const microphoneItems = await TRTC.getMicrophones();
+  microphoneItems.forEach((item) => {
+    item.value = item.deviceId;
+  });
+
+  store.commit("rtc/setDeviceList", {
+    cameraList: cameraItems,
+    microphoneList: microphoneItems,
+  });
+
+  if (!videoDeviceId.value) {
+    if (cameraItems[0]) {
+      store.commit("rtc/setVideoDeviceId", cameraItems[0].deviceId);
+    } else {
+      Dialog.toast({ content: `無法獲取您的攝像頭權限`, type: "error" });
+    }
+  }
+
+  if (!audioDeviceId.value) {
+    if (microphoneItems[0]) {
+      store.commit("rtc/setAudioDeviceId", microphoneItems[0].deviceId);
+    } else {
+      Dialog.toast({ content: `無法獲取您的麥克風權限`, type: "error" });
+    }
+  }
+};
+
+let quxian = { audio: true };
+
+if (role.value == "leader") {
+  quxian["video"] == true;
+}
+
+navigator.mediaDevices
+  .getUserMedia(quxian)
+  .then((stream) => {
+    stream.getTracks().forEach((track) => {
+      track.stop();
+    });
+    updateDevice();
+    emit("canUseDevice", true);
+  })
+  .catch((error) => {
+    console.log(error, "error");
+    Dialog.toast({ content: `請授權您的麥克風${role.value == "leader" ? "和攝像頭" : ""}權限`, type: "error" });
+  });
+
+navigator.mediaDevices.ondevicechange = updateDevice;
+
+const handleDeviceChange = () => {
+  emit("switchDevice", {
+    videoId: store.videoDeviceId,
+    audioId: store.audioDeviceId,
+  });
+};
+</script>

+ 1 - 1
src/components/Tags/goods-list.vue

@@ -89,7 +89,7 @@ const emit = defineEmits(["close"]);
 const current = ref(0);
 
 const viewDetail = (item) => {
-  apis.burying_point({ type: 3 , productId: item.id});
+  apis.burying_point({ type: 3, productId: item.id });
   browser.openLink(
   `/pages/item/item?productId=${item.id}`, 
   `https://m.cdfmembers.com/shop/600667208/item?productId=${item.id}`,

+ 1 - 2
src/components/shared/Guide.vue

@@ -4,7 +4,6 @@
       <div class="zh">
         <div class="btn" @click="onSet"></div>
       </div>
-      <div class="guide-tips">隱私條款:<br />開發者已遵守收集、使用最終用戶個人信息有關的所有可適用法律、政策和法規,保護用戶個人信息安全。</div>
     </div>
   </div>
 </template>
@@ -41,7 +40,7 @@ useApp().then((app) => {
   top: 0;
   width: 100%;
   height: 100%;
-  z-index: 109;
+  z-index: 10999999999;
   background-color: rgba(0, 0, 0, 0.7);
 }
 .user-guide {

+ 1 - 1
src/global_components/components/dialog/Alert.vue

@@ -4,7 +4,7 @@
             <span>{{ title }}</span>
             <i v-if="showCloseIcon" class="iconfont icon-close" @click="close"></i>
         </template>
-        {{ content }}
+        <span v-html="content"></span>
         <template v-slot:footer v-if="showFooter">
             <ui-button type="submit" @click="close">{{ okText }}</ui-button>
         </template>

+ 5 - 0
src/store/index.js

@@ -20,6 +20,7 @@ const store = createStore({
             },
             router: {},
             controlsBottom: '20px',
+            shoppingguide: false
         }
     },
     getters: {
@@ -29,8 +30,12 @@ const store = createStore({
         flying: state => state.flying,
         floorId: state => state.floorId || 0,
         controlsBottom: state => state.controlsBottom,
+        shoppingguide: state => state.shoppingguide
     },
     mutations: {
+        showShoppingguide(state, payload) {
+            state.shoppingguide = payload
+        },
         /**
          * 设置相机模式状态
          * @param {*} state

+ 2 - 0
src/store/modules/index.js

@@ -1,8 +1,10 @@
 import scene from './scene'
 import tag from './tag'
 import tour from './tour'
+import rtc from './rtc'
 export default {
     scene,
     tag,
     tour,
+    rtc
 }

+ 112 - 0
src/store/modules/rtc.js

@@ -0,0 +1,112 @@
+
+
+import { genTestUserSig } from '@/utils/generateTestUserSig';
+// import { apis } from '@/apis';
+
+export default {
+    namespaced: true,
+    state() {
+        return {
+            socket: null,
+            showdaogou: false,
+            sdkAppId: '1400709402',
+            userId: '',
+            roomId: '',
+            secretKey: 'def391b02e6423a6db15eea3d9a0c131f2abac921204246bbe3f36fcea7d111d',
+            userSig: 'eJw1jlELgjAURv-LnkPunXNToZceosB6SPsBwVbelrLpFCH675XW43c*Dpwnq4oyMpOjzrBcgkgBVjMbTcdyxiNgy*61vThHmuUoABRkAvjykDZtoCvNAsbAUaWA8u-R7YttFno-JNPou7LBuH0MQR6t2tp6cz-5sy75zrXV3heH9U8M1HySUCapirnC7PUGl3Ix6g__',
+            audioDeviceId: '',
+            videoDeviceId: '',
+            cameraList: [],
+            microphoneList: [],
+            logs: [],
+            isJoined: false,
+            isPublished: false,
+            isShared: false,
+            remoteStreams: [],
+            invitedRemoteStreams: [],
+        }
+    },
+    getters: {
+        socket: state => {
+            return state.socket
+        },
+        showdaogou: state => {
+            return state.showdaogou
+        },
+        sdkAppId: state => state.sdkAppId,
+        userId: state => state.userId,
+        roomId: state => state.roomId,
+        secretKey: state => state.secretKey,
+        userSig: state => {
+            const { userSig } = genTestUserSig({
+                sdkAppId: parseInt(state.sdkAppId, 10),
+                userId:state.userId,
+                secretKey: state.secretKey,
+            });
+            return userSig
+        },
+        audioDeviceId: state => state.audioDeviceId,
+        videoDeviceId: state => state.videoDeviceId,
+        cameraList: state => state.cameraList,
+        microphoneList: state => state.microphoneList,
+        logs: state => state.logs,
+        isJoined: state => state.isJoined,
+        isPublished: state => state.isPublished,
+        isShared: state => state.isShared,
+        remoteStreams: state => state.remoteStreams,
+        invitedRemoteStreams: state => state.invitedRemoteStreams
+    },
+    mutations: {
+        setSocket(state, payload) {
+            state.socket = payload
+        },
+        setShowdaogou(state, payload) {
+            state.showdaogou = payload
+        },
+
+        setUserId(state, payload) {
+            state.userId=payload
+        },
+
+        setRoomId(state, payload) {
+            state.roomId=payload
+        },
+
+        setDeviceList(state, payload) {
+            state.cameraList=payload.cameraItems
+            state.microphoneList=payload.microphoneItems
+        },
+
+        setVideoDeviceId(state, payload) {
+            state.videoDeviceId=payload
+        },
+
+        setAudioDeviceId(state, payload) {
+            state.audioDeviceId=payload
+        },
+
+        setIsJoined(state, payload){
+            state.isJoined = payload
+        },
+        setIsPublished(state, payload){
+            state.isPublished = payload
+        },
+       
+    
+        // createShareLink(state, payload) {
+        //     const userId = `Guest_${Math.floor(Math.random() * 1000000)}`;
+        //     const { userSig } = genTestUserSig({
+        //         sdkAppId: parseInt(state.sdkAppId, 10),
+        //         userId,
+        //         secretKey: state.secretKey,
+        //     });
+        //     const { origin } = window.location;
+        //     const { pathname } = window.location;
+        //     return `${origin}${pathname}#/invite?userSig=${userSig}&&SDKAppId=${state.sdkAppId}&&userId=${userId}&&roomId=${state.roomId}`;
+        // },
+        
+    },
+    actions: {
+      
+    },
+}

+ 3 - 1
src/store/modules/scene.js

@@ -7,7 +7,7 @@ export default {
         return {
             tags: [],
             floors: [],
-            metadata: {},
+            metadata: {}
         }
     },
     getters: {
@@ -20,6 +20,7 @@ export default {
         metadata: state => {
             return state.metadata
         },
+        
         musicURL: (state, getters, rootState, rootGetters) => {
             let metadata = getters.metadata
             if (metadata.music) {
@@ -39,6 +40,7 @@ export default {
         },
     },
     mutations: {
+       
         load(state, payload) {
             state.metadata = payload
             document.title = payload.title

+ 19 - 15
src/utils/browser.js

@@ -37,10 +37,10 @@ var browser = {
     dom.requestFullscreen
       ? dom.requestFullscreen()
       : dom.mozRequestFullScreen
-      ? dom.mozRequestFullScreen()
-      : dom.webkitRequestFullscreen
-      ? dom.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)
-      : dom.msRequestFullscreen && dom.msRequestFullscreen(),
+        ? dom.mozRequestFullScreen()
+        : dom.webkitRequestFullscreen
+          ? dom.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)
+          : dom.msRequestFullscreen && dom.msRequestFullscreen(),
       t && $(document).on("fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange", browser.requestPointerLock);
   },
   requestPointerLock: function () {
@@ -63,19 +63,19 @@ var browser = {
     document.exitFullscreen
       ? document.exitFullscreen()
       : document.msExitFullscreen
-      ? document.msExitFullscreen()
-      : document.mozCancelFullScreen
-      ? document.mozCancelFullScreen()
-      : document.webkitExitFullscreen && document.webkitExitFullscreen();
+        ? document.msExitFullscreen()
+        : document.mozCancelFullScreen
+          ? document.mozCancelFullScreen()
+          : document.webkitExitFullscreen && document.webkitExitFullscreen();
   },
   details: function () {
     var e = navigator.userAgent.match("(Firefox|Chrome|Safari)/([\\d]+)");
     return e
       ? {
-          name: e[1],
-          version: parseInt(e[2]),
-          platform: navigator.platform,
-        }
+        name: e[1],
+        version: parseInt(e[2]),
+        platform: navigator.platform,
+      }
       : {};
   },
   is: function (e) {
@@ -178,8 +178,8 @@ var browser = {
     var e = $.Deferred();
     return this.detectWebVR()
       ? (navigator.getVRDisplays().then(function (t) {
-          t.length >= 1 && e.resolve(t[0]), e.reject(null);
-        }),
+        t.length >= 1 && e.resolve(t[0]), e.reject(null);
+      }),
         e)
       : e.reject(null);
   },
@@ -354,7 +354,11 @@ var browser = {
   },
 
   replaceQueryString(url, name, value) {
-    const re = new RegExp(name + "=[^&]*", "gi");
+    let re = new RegExp(name + "=[^&]*", "gi");
+    if (!value) {
+      let ttt = url.replace(new RegExp('&' + name + "=[^&]*", "gi"), '')
+      return ttt.replace(re, '');
+    }
     return url.replace(re, name + "=" + value);
   },
   openLink(mglink, h5link, appLink) {

+ 52 - 0
src/utils/common.js

@@ -106,4 +106,56 @@ export default {
         }
         return str
     },
+    uuid(len, radix) {
+
+        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+    
+        var uuid = [], i;
+    
+        radix = radix || chars.length;
+    
+     
+    
+        if (len) {
+    
+          // Compact form
+    
+          for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
+    
+        } else {
+    
+          // rfc4122, version 4 form
+    
+          var r;
+    
+     
+    
+          // rfc4122 requires these characters
+    
+          uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+    
+          uuid[14] = '4';
+    
+     
+    
+          // Fill in random data.  At i==19 set the high bits of clock sequence as
+    
+          // per rfc4122, sec. 4.1.5
+    
+          for (i = 0; i < 36; i++) {
+    
+            if (!uuid[i]) {
+    
+              r = 0 | Math.random()*16;
+    
+              uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+    
+            }
+    
+          }
+    
+        }
+        return uuid.join('');
+    
+    }
 }

+ 64 - 0
src/utils/generateTestUserSig.js

@@ -0,0 +1,64 @@
+/* eslint-disable */
+
+/*
+ * Module:   GenerateTestUserSig
+ *
+ * Function: 用于生成测试用的 UserSig,UserSig 是腾讯云为其云服务设计的一种安全保护签名。
+ *           其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
+ *
+ * Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
+ *
+ *            本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
+ *            这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
+ *            一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
+ *
+ *            正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
+ *            由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
+ *
+ * Reference:https://cloud.tencent.com/document/product/647/17275#Server
+ */
+
+
+export function genTestUserSig({ sdkAppId, userId, secretKey }) {
+  /**
+   * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
+   *
+   * 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId,
+   * 它是腾讯云用于区分客户的唯一标识。
+   */
+  const SDKAPPID = sdkAppId;
+
+  /**
+   * 签名过期时间,建议不要设置的过短
+   * <p>
+   * 时间单位:秒
+   * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
+   */
+  const EXPIRETIME = 604800;
+
+  /**
+   * 计算签名用的加密密钥,获取步骤如下:
+   *
+   * step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
+   * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
+   * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
+   *
+   * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
+   * 文档:https://cloud.tencent.com/document/product/647/17275#Server
+   */
+  const SECRETKEY = secretKey;
+
+  // a soft reminder to guide developer to configure sdkAppId/secretKey
+  if (SDKAPPID == undefined || SECRETKEY === '') {
+    alert(
+      '请先配置好您的账号信息: SDKAPPID 及 SECRETKEY ' +
+      '\r\n\r\nPlease configure your SDKAPPID/SECRETKEY in js/debug/GenerateTestUserSig.js'
+    );
+  }
+  const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
+  const userSig = generator.genTestUserSig(userId);
+  return {
+    sdkAppId: SDKAPPID,
+    userSig: userSig
+  };
+}

+ 1 - 0
src/utils/rtc_socket.js

@@ -0,0 +1 @@
+

+ 1 - 1
vue.config.js

@@ -11,7 +11,7 @@ module.exports = defineConfig({
   },
   devServer: {
     // port: 443,
-    // https: true,
+    https: true,
     // disableHostCheck: true,
     headers: {
       "Cache-Control": "no-store",