Browse Source

Merge branch 'release/1.7.0'

gemercheung 1 year ago
parent
commit
e803d8c46d
100 changed files with 7425 additions and 4254 deletions
  1. 2 1
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 25 0
      .vscode/settings.json
  4. 29 0
      1.6-default.json
  5. 0 12
      lerna.json
  6. 10 14
      package.json
  7. 1 1
      packages/qjkankan-editor/.env
  8. 3 0
      packages/qjkankan-editor/.env.eurdev
  9. 4 1
      packages/qjkankan-editor/.env.eurtestdev
  10. 1 1
      packages/qjkankan-editor/.env.eurtestprod
  11. 3 2
      packages/qjkankan-editor/.env.prod
  12. 3 2
      packages/qjkankan-editor/.env.testdev
  13. 3 2
      packages/qjkankan-editor/.env.testprod
  14. 5 2
      packages/qjkankan-editor/package.json
  15. BIN
      packages/qjkankan-editor/public/static/template/skin/masking.png
  16. 369 361
      packages/qjkankan-editor/public/static/template/skin/vtourskin.xml
  17. 5 0
      packages/qjkankan-editor/src/Store/index.js
  18. 21 0
      packages/qjkankan-editor/src/Store/modules/navigation.js
  19. 2 2
      packages/qjkankan-editor/src/Store/modules/notice.js
  20. 120 17
      packages/qjkankan-editor/src/Store/modules/scene.js
  21. 592 376
      packages/qjkankan-editor/src/api/index.js
  22. 13 0
      packages/qjkankan-editor/src/assets/images/icons/navs/mask_normal.svg
  23. 13 0
      packages/qjkankan-editor/src/assets/images/icons/navs/mask_selected.svg
  24. 2 2
      packages/qjkankan-editor/src/components/RichTextEditor.vue
  25. 1 1
      packages/qjkankan-editor/src/components/materialListInMaterialSelector.vue
  26. 2 3
      packages/qjkankan-editor/src/components/materialSelector.vue
  27. 2 2
      packages/qjkankan-editor/src/components/materialSelectorFromWork.vue
  28. 1 1
      packages/qjkankan-editor/src/components/sceneGroupInEditor.vue
  29. 1 1
      packages/qjkankan-editor/src/components/shared/loading/smallWaiting.vue
  30. 22 14
      packages/qjkankan-editor/src/config/menu.js
  31. 1 14
      packages/qjkankan-editor/src/core/utils.js
  32. 7 1
      packages/qjkankan-editor/src/directives/vTooltipInEditor.js
  33. 10 1
      packages/qjkankan-editor/src/framework/EditorHead.vue
  34. 3 0
      packages/qjkankan-editor/src/framework/EditorMain.vue
  35. 9 6
      packages/qjkankan-editor/src/framework/core/index.vue
  36. 46 41
      packages/qjkankan-editor/src/framework/material/aside.vue
  37. 73 45
      packages/qjkankan-editor/src/framework/material/header.vue
  38. 26 22
      packages/qjkankan-editor/src/framework/material/index.vue
  39. 0 456
      packages/qjkankan-editor/src/framework/play/pano/components/list copy.vue
  40. 19 11
      packages/qjkankan-editor/src/framework/play/pano/components/list.vue
  41. 135 28
      packages/qjkankan-editor/src/framework/play/pano/index.vue
  42. 0 979
      packages/qjkankan-editor/src/lang/_en.json
  43. 0 963
      packages/qjkankan-editor/src/lang/_zh.json
  44. 2 2
      packages/qjkankan-editor/src/lang/index.js
  45. 975 0
      packages/qjkankan-editor/src/lang/weblate/en.json
  46. 975 0
      packages/qjkankan-editor/src/lang/weblate/zh.json
  47. 1 1
      packages/qjkankan-editor/src/mixins/debuggerHelper.js
  48. 10 2
      packages/qjkankan-editor/src/pages/Edit.vue
  49. 47 57
      packages/qjkankan-editor/src/router/editorRouter.js
  50. 16 20
      packages/qjkankan-editor/src/router/material.js
  51. 112 71
      packages/qjkankan-editor/src/utils/request.js
  52. 4 4
      packages/qjkankan-editor/src/views/base/Toolbar.vue
  53. 1 1
      packages/qjkankan-editor/src/views/base/backgroundMusicSettings.vue
  54. 3 3
      packages/qjkankan-editor/src/views/base/coverBase.vue
  55. 1 1
      packages/qjkankan-editor/src/views/base/customLogoSettings.vue
  56. 1 1
      packages/qjkankan-editor/src/views/base/customMaskSettings.vue
  57. 1 1
      packages/qjkankan-editor/src/views/base/openingAnimationSettings.vue
  58. 1 1
      packages/qjkankan-editor/src/views/base/openingTipSettings.vue
  59. 1 1
      packages/qjkankan-editor/src/views/explanation/explanationSettings.vue
  60. 1 1
      packages/qjkankan-editor/src/views/hotspot/hotspotIconType/custom_image.vue
  61. 1 1
      packages/qjkankan-editor/src/views/hotspot/hotspotIconType/serial_frame.vue
  62. 1 1
      packages/qjkankan-editor/src/views/hotspot/hotspotType/audio.vue
  63. 1 1
      packages/qjkankan-editor/src/views/hotspot/hotspotType/image.vue
  64. 2 2
      packages/qjkankan-editor/src/views/hotspot/hotspotType/imageText.vue
  65. 1 1
      packages/qjkankan-editor/src/views/hotspot/hotspotType/scene.vue
  66. 1 1
      packages/qjkankan-editor/src/views/hotspot/hotspotType/video.vue
  67. 31 0
      packages/qjkankan-editor/src/views/mask/index.vue
  68. 456 0
      packages/qjkankan-editor/src/views/mask/setting.vue
  69. 7 2
      packages/qjkankan-editor/src/views/material/audio/index.vue
  70. 1 1
      packages/qjkankan-editor/src/views/material/folderMixinFactory.js
  71. 6 1
      packages/qjkankan-editor/src/views/material/image/index.vue
  72. 25 8
      packages/qjkankan-editor/src/views/material/pano/index.vue
  73. 46 46
      packages/qjkankan-editor/src/views/material/popup/share.vue
  74. 6 1
      packages/qjkankan-editor/src/views/material/video/index.vue
  75. 877 0
      packages/qjkankan-editor/src/views/material/works/cam.vue
  76. 104 421
      packages/qjkankan-editor/src/views/material/works/index.vue
  77. 830 0
      packages/qjkankan-editor/src/views/material/works/list copy.vue
  78. 785 0
      packages/qjkankan-editor/src/views/material/works/list.vue
  79. 46 36
      packages/qjkankan-editor/src/views/navigation/initialSceneSettings.vue
  80. 216 24
      packages/qjkankan-editor/src/views/screen/Setting.vue
  81. 1 1
      packages/qjkankan-editor/src/views/screen/index.vue
  82. 1 1
      packages/qjkankan-kankan-view/.env.testprod
  83. 1 1
      packages/qjkankan-kankan-view/.env.testserve
  84. 1 1
      packages/qjkankan-kankan-view/package.json
  85. 1 1
      packages/qjkankan-kankan-view/src/app.js
  86. 43 0
      packages/qjkankan-kankan-view/src/components/Camera/index.vue
  87. 1 1
      packages/qjkankan-kankan-view/src/components/Controls/FloorSwitch.vue
  88. 7 7
      packages/qjkankan-kankan-view/src/components/Controls/tours.mobile.vue
  89. 9 6
      packages/qjkankan-kankan-view/src/components/Controls/tours.vue
  90. 63 63
      packages/qjkankan-kankan-view/src/locales/en.json
  91. 63 63
      packages/qjkankan-kankan-view/src/locales/zh.json
  92. 11 0
      packages/qjkankan-kankan-view/src/pages/SMG.vue
  93. 29 7
      packages/qjkankan-kankan-view/src/pages/SPG.vue
  94. 1 0
      packages/qjkankan-kankan-view/src/pages/smg.js
  95. 1 0
      packages/qjkankan-kankan-view/src/pages/spg.js
  96. 5 0
      packages/qjkankan-kankan-view/src/utils/browser.js
  97. 3 1
      packages/qjkankan-view/.env.eurprod
  98. 1 1
      packages/qjkankan-view/.env.eurtestprod
  99. 1 1
      packages/qjkankan-view/.env.prod
  100. 0 0
      packages/qjkankan-view/.env.testdev

+ 2 - 1
.gitignore

@@ -15,9 +15,10 @@ pnpm-debug.log*
 
 
 # Editor directories and files
 # Editor directories and files
 .idea
 .idea
-.vscode
+
 *.suo
 *.suo
 *.ntvs*
 *.ntvs*
 *.njsproj
 *.njsproj
 *.sln
 *.sln
 *.sw?
 *.sw?
+!.vscode/extensions.json

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["lokalise.i18n-ally"]
+}

+ 25 - 0
.vscode/settings.json

@@ -0,0 +1,25 @@
+{
+  "auto-close-tag.activationOnLanguage": ["xml", "html", "krpano-xml"],
+  "cSpell.words": ["handleclearify", "Krpano"],
+
+  "i18n-ally.localesPaths": [
+    "packages/qjkankan-editor/src/lang/weblate",
+    "packages/qjkankan-view/src/locales"
+  ],
+
+  "i18n-ally.keystyle": "nested",
+  // "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
+  "i18n-ally.namespace": true,
+
+  "i18n-ally.enabledParsers": ["json", "js"],
+
+  "i18n-ally.sortKeys": true,
+
+  "i18n-ally.enabledFrameworks": ["vue", "react"],
+
+  "i18n-ally.sourceLanguage": "zh",
+
+  "i18n-ally.displayLanguage": "zh",
+
+  "i18n-ally.extract.keygenStyle": "camelCase"
+}

+ 29 - 0
1.6-default.json

@@ -0,0 +1,29 @@
+{
+    "scenes": [
+        {
+            // 1.6加 (整个)
+            "customMasks": {
+                "sky": {
+                    "fodderId": "",
+                    "icon": "",
+                    "scale": 1,
+                    "antidistorted": true,
+                    "isShow": false
+                },
+                "earth": {
+                    "fodderId": "",
+                    "icon": "",
+                    "scale": 1,
+                    "antidistorted": true,
+                    "isShow": false
+                }
+            },
+            "initVisual": {
+                "vlookat": 0,
+                "hlookat": 0,
+                "vlookatmin": -90, // 1.6加
+                "vlookatmax": 90 // 1.6加
+            }
+        }
+    ]
+}

+ 0 - 12
lerna.json

@@ -1,12 +0,0 @@
-{
-  "packages": [
-    "packages/qjkankan-components",
-    "packages/qjkankan-editor",
-    "packages/qjkankan-kankan-view",
-    "packages/qjkankan-view"
-  ],
-  "useWorkspaces": true,
-  "npmClient": "yarn",
-  "useNx": false,
-  "version": "1.2.0"
-}

+ 10 - 14
package.json

@@ -5,22 +5,18 @@
     "packages/*"
     "packages/*"
   ],
   ],
   "devDependencies": {
   "devDependencies": {
-    "lerna": "^5.1.6",
+    "get-json": "^1.0.1",
+    "cross-env": "^7.0.3",
     "lint-staged": "^10.5.4"
     "lint-staged": "^10.5.4"
   },
   },
   "scripts": {
   "scripts": {
-    "edit:serve": "yarn workspace  @qjkankan/editor run serve-testdev",
-    "edit:serve-testprod": "yarn workspace  @qjkankan/editor run serve-testprod",
-    "edit:build": "yarn workspace  @qjkankan/editor run build",
-    "edit:build-testprod": "yarn workspace  @qjkankan/editor run build-testprod",
-    "kankan-view:run": "yarn workspace @qjkankan/kankan-view run serve",
-    "kankan-view:dev": "yarn workspace @qjkankan/kankan-view run serve",
-    "kankan-view:build": "yarn workspace @qjkankan/kankan-view run build",
-    "view:run": "yarn workspace @qjkankan/view run serve",
-    "view:dev": "yarn workspace @qjkankan/view run serve",
-    "view:build": "yarn workspace @qjkankan/view run build",
-    "build": "npm-run-all -s edit:build kankan-view:build",
-    "build-testprod": "npm-run-all -s edit:build-testprod view:build"
+    "translate": "node translate.js",
+    "edit:test-serve": "pnpm --filter @qjkankan/editor run serve-testdev",
+    "edit:build-testprod": "pnpm --filter  @qjkankan/editor run build-testprod",
+    "view:serve": "pnpm --filter @qjkankan/view run serve",
+    "view:build-testprod": "pnpm --filter @qjkankan/view run build-testprod",
+    "kankan-view:serve": "pnpm --filter @qjkankan/kankan-view run serve",
+    "kankan-view:build-testprod": "pnpm --filter @qjkankan/kankan-view run build-testprod"
   },
   },
   "dependencies": {
   "dependencies": {
     "npm-run-all": "^4.1.5"
     "npm-run-all": "^4.1.5"
@@ -36,4 +32,4 @@
       "prettier --write"
       "prettier --write"
     ]
     ]
   }
   }
-}
+}

+ 1 - 1
packages/qjkankan-editor/.env

@@ -4,5 +4,5 @@ VUE_APP_CDN=https://4dkk.4dage.com
 VUE_APP_PROXY_URL_ROOT='https://www.4dkankan.com'
 VUE_APP_PROXY_URL_ROOT='https://www.4dkankan.com'
 VUE_APP_PROXY_URL='https://www.4dkankan.com/qjkankan/'
 VUE_APP_PROXY_URL='https://www.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=
 VUE_APP_ORIGIN=
-
+VUE_APP_MEGA_LIST_URL='https://uat-laser.4dkankan.com/laser/init/list'
 VUE_APP_URL_FILL=
 VUE_APP_URL_FILL=

+ 3 - 0
packages/qjkankan-editor/.env.eurdev

@@ -6,3 +6,6 @@ VUE_APP_PROXY_URL='https://eur.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=aws
 VUE_APP_ORIGIN=aws
 
 
 VUE_APP_URL_FILL=
 VUE_APP_URL_FILL=
+VUE_APP_DEBBUG_FLAG=0112-02
+VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_MEGA_LIST_URL='https://uat-laser.4dkankan.com/laser/init/list'

+ 4 - 1
packages/qjkankan-editor/.env.eurtestdev

@@ -5,4 +5,7 @@ VUE_APP_PROXY_URL_ROOT='https://testeur.4dkankan.com'
 VUE_APP_PROXY_URL='https://testeur.4dkankan.com/qjkankan/'
 VUE_APP_PROXY_URL='https://testeur.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=aws
 VUE_APP_ORIGIN=aws
 
 
-VUE_APP_URL_FILL=
+VUE_APP_URL_FILL=
+VUE_APP_DEBBUG_FLAG=0112-02
+VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_MEGA_LIST_URL='https://uat-laser.4dkankan.com/laser/init/list'

+ 1 - 1
packages/qjkankan-editor/.env.eurtestprod

@@ -8,4 +8,4 @@ VUE_APP_PROXY_URL='https://testeur.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=aws
 VUE_APP_ORIGIN=aws
 
 
 VUE_APP_URL_FILL=/qjkankan
 VUE_APP_URL_FILL=/qjkankan
-VUE_APP_DEBBUG_FLAG=0824-01
+VUE_APP_DEBBUG_FLAG=0124-01

+ 3 - 2
packages/qjkankan-editor/.env.prod

@@ -7,5 +7,6 @@ VUE_APP_PROXY_URL_ROOT='https://www.4dkankan.com'
 VUE_APP_PROXY_URL='https://www.4dkankan.com/qjkankan/'
 VUE_APP_PROXY_URL='https://www.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=
 VUE_APP_ORIGIN=
 VUE_APP_URL_FILL=/qjkankan
 VUE_APP_URL_FILL=/qjkankan
-VUE_APP_DEBBUG_FLAG=0824-01
-VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_DEBBUG_FLAG=0122-01
+VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_MEGA_LIST_URL='https://uat-laser.4dkankan.com/laser/init/list'

+ 3 - 2
packages/qjkankan-editor/.env.testdev

@@ -6,5 +6,6 @@ VUE_APP_PROXY_URL_ROOT='https://test.4dkankan.com'
 VUE_APP_PROXY_URL='https://test.4dkankan.com/qjkankan/'
 VUE_APP_PROXY_URL='https://test.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=
 VUE_APP_ORIGIN=
 VUE_APP_URL_FILL=
 VUE_APP_URL_FILL=
-VUE_APP_DEBBUG_FLAG=0425-02
-VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_DEBBUG_FLAG=0112-02
+VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_MEGA_LIST_URL='https://uat-laser.4dkankan.com/laser/init/list'

+ 3 - 2
packages/qjkankan-editor/.env.testprod

@@ -7,5 +7,6 @@ VUE_APP_PROXY_URL_ROOT='https://test.4dkankan.com'
 VUE_APP_PROXY_URL='https://test.4dkankan.com/qjkankan/'
 VUE_APP_PROXY_URL='https://test.4dkankan.com/qjkankan/'
 VUE_APP_ORIGIN=
 VUE_APP_ORIGIN=
 VUE_APP_URL_FILL=/qjkankan
 VUE_APP_URL_FILL=/qjkankan
-VUE_APP_DEBBUG_FLAG=0807-01
-VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_DEBBUG_FLAG=20240401
+VUE_APP_DEBBUG_NOTIFY=0
+VUE_APP_MEGA_LIST_URL='https://uat-laser.4dkankan.com/laser/init/list'

+ 5 - 2
packages/qjkankan-editor/package.json

@@ -26,20 +26,23 @@
     "html2canvas": "^1.4.1",
     "html2canvas": "^1.4.1",
     "is-chinese-phone-number": "^0.1.9",
     "is-chinese-phone-number": "^0.1.9",
     "libphonenumber-js": "^1.10.19",
     "libphonenumber-js": "^1.10.19",
+    "lodash": "^4.17.21",
     "log-beautify": "^1.2.0",
     "log-beautify": "^1.2.0",
     "photoswipe": "^4.1.3",
     "photoswipe": "^4.1.3",
     "quill": "^1.3.7",
     "quill": "^1.3.7",
     "swiper": "^5.3.8",
     "swiper": "^5.3.8",
     "v-viewer": "^1.5.1",
     "v-viewer": "^1.5.1",
     "video.js": "^7.11.8",
     "video.js": "^7.11.8",
+    "viewerjs": "^1.11.6",
     "vue": "^2.6.12",
     "vue": "^2.6.12",
     "vue-awesome-swiper": "^4.1.1",
     "vue-awesome-swiper": "^4.1.1",
     "vue-cropperjs": "^4.2.0",
     "vue-cropperjs": "^4.2.0",
     "vue-demi": "^0.14.5",
     "vue-demi": "^0.14.5",
-    "vue-i18n": "^8.22.3",
+    "vue-i18n": "^8.25.0",
     "vue-marquee-text-component": "1.2.0",
     "vue-marquee-text-component": "1.2.0",
     "vue-photo-preview": "^1.1.3",
     "vue-photo-preview": "^1.1.3",
     "vue-router": "^3.4.9",
     "vue-router": "^3.4.9",
+    "vue-slider-component": "^3.2.24",
     "vue-toast-notification": "^3.1.1",
     "vue-toast-notification": "^3.1.1",
     "vue-toastification": "^1.7.14",
     "vue-toastification": "^1.7.14",
     "vuex": "^3.6.0"
     "vuex": "^3.6.0"
@@ -57,4 +60,4 @@
     "less-loader": "^5.0.0",
     "less-loader": "^5.0.0",
     "vue-template-compiler": "^2.6.12"
     "vue-template-compiler": "^2.6.12"
   }
   }
-}
+}

BIN
packages/qjkankan-editor/public/static/template/skin/masking.png


File diff suppressed because it is too large
+ 369 - 361
packages/qjkankan-editor/public/static/template/skin/vtourskin.xml


+ 5 - 0
packages/qjkankan-editor/src/Store/index.js

@@ -36,6 +36,8 @@ const store = new Vuex.Store({
       type: "", // 'topologyGroupLevel1': 拓扑结构中一级分组;'topologyGroupLevel2': 拓扑结构中二级分组;'scene': 场景(全景图和三维场景)
       type: "", // 'topologyGroupLevel1': 拓扑结构中一级分组;'topologyGroupLevel2': 拓扑结构中二级分组;'scene': 场景(全景图和三维场景)
       node: {},
       node: {},
     },
     },
+    // 1.6.0 导航锁V4不能选
+    isLockV4Scene: false,
   },
   },
   getters: {
   getters: {
     userAvatar: (state) => state.userAvatar,
     userAvatar: (state) => state.userAvatar,
@@ -249,6 +251,9 @@ const store = new Vuex.Store({
       state.editorNavDragInfo.type = "";
       state.editorNavDragInfo.type = "";
       state.editorNavDragInfo.node = {};
       state.editorNavDragInfo.node = {};
     },
     },
+    lockNavV4Scene(state, status) {
+      state.isLockV4Scene = status;
+    },
   },
   },
   actions: {
   actions: {
     refreshUserInfo(context) {
     refreshUserInfo(context) {

+ 21 - 0
packages/qjkankan-editor/src/Store/modules/navigation.js

@@ -0,0 +1,21 @@
+export default {
+  namespaced: true,
+  state() {
+    return {
+      catalogTopology: [],
+
+      currentCatalogRoot: {},
+      //当前二级分组
+      currentSecondary: {},
+      //二级分组
+      secondaryList: {},
+      //当前场景分组
+      currentScenesList: {},
+    };
+  },
+  getters: {},
+  mutations: {},
+  actions: {
+    
+  },
+};

+ 2 - 2
packages/qjkankan-editor/src/Store/modules/notice.js

@@ -36,7 +36,7 @@ export default {
   actions: {
   actions: {
     async getNotice({ commit, state }) {
     async getNotice({ commit, state }) {
       const data = await getNoticeApi();
       const data = await getNoticeApi();
-      console.log("state", state);
+      // console.log("state", state);
       const preState = state.data;
       const preState = state.data;
       data.data &&
       data.data &&
         Object.keys(data.data).forEach((i) => {
         Object.keys(data.data).forEach((i) => {
@@ -65,7 +65,7 @@ export default {
           commit("setStatus", false);
           commit("setStatus", false);
         }
         }
       }
       }
-      console.log("preState", preState);
+      // console.log("preState", preState);
       commit("setData", preState);
       commit("setData", preState);
     },
     },
   },
   },

+ 120 - 17
packages/qjkankan-editor/src/Store/modules/scene.js

@@ -1,5 +1,27 @@
 import { i18n } from "@/lang";
 import { i18n } from "@/lang";
 
 
+const initCustomMask = {
+  sky: {
+    isShow: true,
+    icon: "",
+    scale: 1,
+    fodderId: "",
+    antidistorted: true,
+  },
+  earth: {
+    isShow: false,
+    icon: "",
+    scale: 1,
+    fodderId: "",
+    antidistorted: true,
+  },
+};
+const initVisual = {
+  vlookat: 0,
+  hlookat: 0,
+  vlookatmin: -90,
+  vlookatmax: 90,
+};
 
 
 export default {
 export default {
   namespaced: true,
   namespaced: true,
@@ -8,20 +30,22 @@ export default {
       // 场景列表
       // 场景列表
       list: [],
       list: [],
       //当前场景
       //当前场景
-      currentScene: {},
+      currentScene: {
+        customMask: initCustomMask,
+        initVisual: initVisual,
+      },
       //访问密码
       //访问密码
       password: "",
       password: "",
       //场景数据
       //场景数据
       metadata: {},
       metadata: {},
       //当前一级分组
       //当前一级分组
-      currentCatalogRoot:{},
+      currentCatalogRoot: {},
       //当前二级分组
       //当前二级分组
-      currentSecondary:{},
+      currentSecondary: {},
       //二级分组
       //二级分组
-      secondaryList:{},
+      secondaryList: {},
       //当前场景分组
       //当前场景分组
-      currentScenesList:{},
-
+      currentScenesList: {},
     };
     };
   },
   },
   getters: {
   getters: {
@@ -36,6 +60,21 @@ export default {
   },
   },
   mutations: {
   mutations: {
     setScenes(state, payload) {
     setScenes(state, payload) {
+      /**
+       * 1.6.0 补数据 customMask数据体
+       */
+      if (Array.isArray(payload)) {
+        payload.forEach((item, index) => {
+          if (!("customMask" in item)) {
+            console.log("item", item);
+            payload[index]["customMask"] = initCustomMask;
+          }
+          if (!("initVisual" in item)) {
+            payload[index]["initVisual"] = initVisual;
+          }
+        });
+      }
+
       state.list = payload;
       state.list = payload;
     },
     },
     setPassword(state, payload) {
     setPassword(state, payload) {
@@ -53,7 +92,10 @@ export default {
         return state.currentSecondary.id == item.category;
         return state.currentSecondary.id == item.category;
       });
       });
 
 
-      this.commit('scene/setCurrentScenesList', arr.sort((a, b) => a.weight - b.weight))
+      this.commit(
+        "scene/setCurrentScenesList",
+        arr.sort((a, b) => a.weight - b.weight)
+      );
     },
     },
 
 
     // 设置当前场景列表
     // 设置当前场景列表
@@ -69,30 +111,91 @@ export default {
         payload.children.forEach((item) => {
         payload.children.forEach((item) => {
           state.metadata.catalogs.forEach((sub) => {
           state.metadata.catalogs.forEach((sub) => {
             if (item == sub.id) {
             if (item == sub.id) {
-              if (state.list.some(iii=>iii.category == sub.id)) {
+              if (state.list.some((iii) => iii.category == sub.id)) {
                 temp.push(sub);
                 temp.push(sub);
               }
               }
             }
             }
           });
           });
         });
         });
-        
-      this.commit('scene/setSecondaryList', temp)
+
+      this.commit("scene/setSecondaryList", temp);
     },
     },
 
 
     // 设置当前二级分组列表
     // 设置当前二级分组列表
-    setSecondaryList(state, payload){
-      state.secondaryList = payload
-      if (payload.length>0) {
-        this.commit('scene/setCurrentSecondary', payload[0])
+    setSecondaryList(state, payload) {
+      state.secondaryList = payload;
+      if (payload.length > 0) {
+        this.commit("scene/setCurrentSecondary", payload[0]);
       } else {
       } else {
-        this.commit('scene/setCurrentSecondary', {})
+        this.commit("scene/setCurrentSecondary", {});
       }
       }
     },
     },
     setMetaData(state, payload) {
     setMetaData(state, payload) {
       state.metadata = payload;
       state.metadata = payload;
-      if (location.href.includes('show.html')) {
-        document.title = payload.name || i18n.t('common.notit')
+      if (location.href.includes("show.html")) {
+        document.title = payload.name || i18n.t("gather.no_title");
       }
       }
     },
     },
   },
   },
+  actions: {
+    syncCurrentSceneToStore({ commit, state, rootState }) {
+      const currentScene = state.currentScene;
+      const updateSceneIndex = Array.from(rootState.info.scenes).findIndex(
+        (item) => item.id === currentScene.id
+      );
+      if (updateSceneIndex > -1) {
+        rootState.info.scenes[updateSceneIndex] = currentScene;
+      }
+    },
+    /**
+     * 1.6.0 将遮罩层到所有图层
+     * @param {*} param0
+     * @param {*} type
+     */
+    applycustomMaskToAll({ commit, state, rootState }, type) {
+      const currentScene = state.currentScene;
+      const { sky, earth } = currentScene.customMask;
+      if (type === "sky") {
+        Array.from(rootState.info.scenes).forEach((item, index) => {
+          if (item.type === "pano") {
+            rootState.info.scenes[index]["customMask"]["sky"] = { ...sky };
+          }
+        });
+      }
+      if (type === "earth") {
+        // console.log("当前场景-earth", earth);
+        Array.from(rootState.info.scenes).forEach((item, index) => {
+          if (item.type === "pano") {
+            rootState.info.scenes[index]["customMask"]["earth"] = { ...earth };
+            // console.log("rootState.info.scenes", rootState.info.scenes);
+          }
+        });
+      }
+    },
+    applyInitVisualToAll({ commit, state, rootState }, { vlookat, hlookat }) {
+      const currentScene = state.currentScene;
+      Array.from(rootState.info.scenes).forEach((item, index) => {
+        if (item.type === "pano") {
+          const isHasVlookat = () =>
+            rootState.info.scenes[index]["initVisual"].vlookat &&
+            !isNaN(rootState.info.scenes[index]["initVisual"].vlookat);
+          const isHasHlookat = () =>
+            rootState.info.scenes[index]["initVisual"].hlookat &&
+            !isNaN(rootState.info.scenes[index]["initVisual"].hlookat);
+
+          console.log("isHasVlookat", isHasVlookat(), isHasHlookat());
+
+          rootState.info.scenes[index]["initVisual"] = {
+            ...currentScene.initVisual,
+            vlookat: isHasVlookat()
+              ? rootState.info.scenes[index]["initVisual"].vlookat
+              : vlookat || 0,
+            hlookat: isHasHlookat()
+              ? rootState.info.scenes[index]["initVisual"].hlookat
+              : hlookat || 0,
+          };
+        }
+      });
+    },
+  },
 };
 };

File diff suppressed because it is too large
+ 592 - 376
packages/qjkankan-editor/src/api/index.js


+ 13 - 0
packages/qjkankan-editor/src/assets/images/icons/navs/mask_normal.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>mask_normal</title>
+    <g id="编辑器" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="画板" transform="translate(-182, -48)">
+            <g id="mask_normal" transform="translate(182, 48)">
+                <circle id="椭圆形" stroke="#767676" stroke-width="1.5" cx="9" cy="9" r="8.25"></circle>
+                <path d="M9,14 C11.7614237,14 14,11.7614237 14,9 C14,6.23857625 11.7614237,4 9,4 C6.23857625,4 4,6.23857625 4,9 C4,11.7614237 6.23857625,14 9,14 Z" id="椭圆形" stroke="#767676" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="1,0.5"></path>
+                <circle id="椭圆形" fill="#4E4E4E" cx="9" cy="9" r="2"></circle>
+            </g>
+        </g>
+    </g>
+</svg>

+ 13 - 0
packages/qjkankan-editor/src/assets/images/icons/navs/mask_selected.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>mask_selected</title>
+    <g id="编辑器" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="画板" transform="translate(-182, -80)">
+            <g id="mask_selected" transform="translate(182, 80)">
+                <circle id="椭圆形" stroke="#0076F6" stroke-width="1.5" cx="9" cy="9" r="8.25"></circle>
+                <circle id="椭圆形" stroke="#0076F6" stroke-width="1.5" cx="9" cy="9" r="6"></circle>
+                <circle id="椭圆形" fill="#7FBAFA" cx="9" cy="9" r="4"></circle>
+            </g>
+        </g>
+    </g>
+</svg>

+ 2 - 2
packages/qjkankan-editor/src/components/RichTextEditor.vue

@@ -36,7 +36,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowImageSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowImageSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('gather.select_material')"
         :title="$i18n.t('gather.select_material')"
-        @cancle="isShowImageSelectionWindow = false"
+        @cancel="isShowImageSelectionWindow = false"
         @submit="onSubmitFromImageMaterialSelector"
         @submit="onSubmitFromImageMaterialSelector"
         :selectableType="['image']"
         :selectableType="['image']"
         :initialMaterialType="'image'"
         :initialMaterialType="'image'"
@@ -47,7 +47,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowVideoSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowVideoSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('gather.select_material')"
         :title="$i18n.t('gather.select_material')"
-        @cancle="isShowVideoSelectionWindow = false"
+        @cancel="isShowVideoSelectionWindow = false"
         @submit="onSubmitFromVideoMaterialSelector"
         @submit="onSubmitFromVideoMaterialSelector"
         :selectableType="['video']"
         :selectableType="['video']"
         :initialMaterialType="'video'"
         :initialMaterialType="'video'"

+ 1 - 1
packages/qjkankan-editor/src/components/materialListInMaterialSelector.vue

@@ -671,7 +671,7 @@ export default {
           .concat(uploadlist)
           .concat(uploadlist)
           .filter((a) => !res.has(a.backendId) && res.set(a.backendId, 1))
           .filter((a) => !res.has(a.backendId) && res.set(a.backendId, 1))
           .filter((i) => i.parentFolderId === lastItem.id);
           .filter((i) => i.parentFolderId === lastItem.id);
-        console.log("latestUploadlist", latestUploadlist);
+        // console.log("latestUploadlist", latestUploadlist);
 
 
         console.log("lastItem", lastItem);
         console.log("lastItem", lastItem);
 
 

+ 2 - 3
packages/qjkankan-editor/src/components/materialSelector.vue

@@ -2,7 +2,7 @@
   <div class="table-select" :class="isDarkTheme ? '' : 'bright'">
   <div class="table-select" :class="isDarkTheme ? '' : 'bright'">
     <span class="title">{{ title }}</span>
     <span class="title">{{ title }}</span>
     <div class="close-btn">
     <div class="close-btn">
-      <i class="iconfont icon-close" @click="$emit('cancle')"></i>
+      <i class="iconfont icon-close" @click="$emit('cancel')"></i>
     </div>
     </div>
 
 
     <div class="material-tab">
     <div class="material-tab">
@@ -120,7 +120,6 @@
         <img
         <img
           src="@/assets/images/icons/upload-file-type-icon-image@2x.png"
           src="@/assets/images/icons/upload-file-type-icon-image@2x.png"
           style="object-fit: contain"
           style="object-fit: contain"
-          |
           alt=""
           alt=""
         />
         />
       </template>
       </template>
@@ -347,7 +346,7 @@
       <button
       <button
         class="ui-button"
         class="ui-button"
         :class="isDarkTheme ? 'deepcancel' : 'cancel'"
         :class="isDarkTheme ? 'deepcancel' : 'cancel'"
-        @click="$emit('cancle')"
+        @click="$emit('cancel')"
       >
       >
         {{ $i18n.t("gather.cancel") }}
         {{ $i18n.t("gather.cancel") }}
       </button>
       </button>

+ 2 - 2
packages/qjkankan-editor/src/components/materialSelectorFromWork.vue

@@ -2,7 +2,7 @@
   <div class="table-select">
   <div class="table-select">
     <span class="title">{{ $i18n.t(`gather.select_material`) }}</span>
     <span class="title">{{ $i18n.t(`gather.select_material`) }}</span>
     <div class="close-btn">
     <div class="close-btn">
-      <i class="iconfont icon-close" @click="$emit('cancle')"></i>
+      <i class="iconfont icon-close" @click="$emit('cancel')"></i>
     </div>
     </div>
 
 
     <div class="material-tab">
     <div class="material-tab">
@@ -153,7 +153,7 @@
 
 
     <div class="btns">
     <div class="btns">
       <div>
       <div>
-        <button class="ui-button deepcancel" @click="$emit('cancle')">
+        <button class="ui-button deepcancel" @click="$emit('cancel')">
           {{ $i18n.t(`gather.cancel`) }}
           {{ $i18n.t(`gather.cancel`) }}
         </button>
         </button>
         <button
         <button

+ 1 - 1
packages/qjkankan-editor/src/components/sceneGroupInEditor.vue

@@ -179,7 +179,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('gather.select_material')"
         :title="$i18n.t('gather.select_material')"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="onSubmitFromMaterialSelector"
         @submit="onSubmitFromMaterialSelector"
         :selectableType="['pano', '3D']"
         :selectableType="['pano', '3D']"
         :initialMaterialType="'pano'"
         :initialMaterialType="'pano'"

+ 1 - 1
packages/qjkankan-editor/src/components/shared/loading/smallWaiting.vue

@@ -23,7 +23,7 @@ export default {
         Popup
         Popup
     },
     },
     data() {
     data() {
-        console.log(i18n);
+        // console.log(i18n);
         return {
         return {
             display: false,
             display: false,
             duration: 0,
             duration: 0,

+ 22 - 14
packages/qjkankan-editor/src/config/menu.js

@@ -1,5 +1,4 @@
-import {i18n} from "@/lang"
-
+import { i18n } from "@/lang";
 
 
 // 编辑器主菜单
 // 编辑器主菜单
 const PCMenu = [
 const PCMenu = [
@@ -27,7 +26,7 @@ const PCMenu = [
     name: "screen",
     name: "screen",
     hasPreviewArea: true,
     hasPreviewArea: true,
     previewAreaExtraLeft: 0,
     previewAreaExtraLeft: 0,
-    hidden: false
+    hidden: false,
   },
   },
   {
   {
     text: i18n.t("edit_page.hotspot"),
     text: i18n.t("edit_page.hotspot"),
@@ -36,7 +35,7 @@ const PCMenu = [
     name: "hotspot",
     name: "hotspot",
     hasPreviewArea: true,
     hasPreviewArea: true,
     previewAreaExtraLeft: 0,
     previewAreaExtraLeft: 0,
-    hidden: false
+    hidden: false,
   },
   },
   {
   {
     text: i18n.t("edit_page.explanation"),
     text: i18n.t("edit_page.explanation"),
@@ -45,8 +44,17 @@ const PCMenu = [
     name: "explanation",
     name: "explanation",
     hasPreviewArea: true,
     hasPreviewArea: true,
     previewAreaExtraLeft: 0,
     previewAreaExtraLeft: 0,
-    hidden: false
-  }
+    hidden: false,
+  },
+  {
+    text: i18n.t("edit_page.mask"),
+    icon: "mask",
+    link: "/mask",
+    name: "mask",
+    hasPreviewArea: true,
+    previewAreaExtraLeft: 0,
+    hidden: false,
+  },
 ];
 ];
 
 
 // 管理平台主菜单
 // 管理平台主菜单
@@ -56,37 +64,37 @@ const MATERIALMenu = [
     icon: "icon_base",
     icon: "icon_base",
     link: "/works",
     link: "/works",
     name: "works",
     name: "works",
-    belong:'works'
+    belong: "works",
   },
   },
+
   {
   {
     text: "全景图片",
     text: "全景图片",
     icon: "icon_base",
     icon: "icon_base",
     link: "/pano",
     link: "/pano",
     name: "pano",
     name: "pano",
-    belong:'material'
+    belong: "material",
   },
   },
   {
   {
     text: "图片",
     text: "图片",
     icon: "iconchangjingdaohang",
     icon: "iconchangjingdaohang",
     link: "/image",
     link: "/image",
     name: "image",
     name: "image",
-    belong:'material'
+    belong: "material",
   },
   },
   {
   {
     text: "音频",
     text: "音频",
     icon: "icon_screen",
     icon: "icon_screen",
     link: "/audio",
     link: "/audio",
     name: "audio",
     name: "audio",
-    belong:'material'
+    belong: "material",
   },
   },
   {
   {
     text: "视频",
     text: "视频",
     icon: "icon_hotpoint",
     icon: "icon_hotpoint",
     link: "/video",
     link: "/video",
     name: "video",
     name: "video",
-    belong:'material'
-  }
+    belong: "material",
+  },
 ];
 ];
 
 
-
-export {PCMenu,MATERIALMenu}
+export { PCMenu, MATERIALMenu };

+ 1 - 14
packages/qjkankan-editor/src/core/utils.js

@@ -227,24 +227,11 @@ export default class Utils {
    */
    */
 
 
   toggleHotspot(krpano, toggle) {
   toggleHotspot(krpano, toggle) {
+    // console.error("toggleHotspot");
     try {
     try {
       krpano.call(`set_hotspot_visible(${toggle})`);
       krpano.call(`set_hotspot_visible(${toggle})`);
     } catch (e) {
     } catch (e) {
       e;
       e;
     }
     }
   }
   }
-
-  /**
-   * 显示隐藏热点
-   */
 }
 }
-
-// toggleHotspot(krpano,someData,toggle){
-//   if (!someData) {
-//     return
-//   }
-//   let mysd = JSON.parse(someData)
-//   mysd.hotspots.forEach(item => {
-//     krpano.set(`hotspot[${item.name}].visible`,toggle);
-//   });
-// }

+ 7 - 1
packages/qjkankan-editor/src/directives/vTooltipInEditor.js

@@ -8,6 +8,11 @@ function removeTooltip() {
   try {
   try {
     intervalId && clearInterval(intervalId);
     intervalId && clearInterval(intervalId);
     tooltipNode && document.body.removeChild(tooltipNode);
     tooltipNode && document.body.removeChild(tooltipNode);
+    const allTooltips = document.querySelectorAll(".v-gem-tooltip");
+    // console.log("removeTooltip", allTooltips.length);
+    if (allTooltips.length) {
+      Array.from(allTooltips).map((node) => document.body.removeChild(node));
+    }
   } catch (e) {
   } catch (e) {
     // console.log(
     // console.log(
     //   "尝试从DOM上移除tooltip元素失败,通常是因为已经在其他回调中被移除了,不需处理:",
     //   "尝试从DOM上移除tooltip元素失败,通常是因为已经在其他回调中被移除了,不需处理:",
@@ -24,7 +29,9 @@ Vue.directive("tooltip", {
     el.addEventListener(
     el.addEventListener(
       "mouseenter",
       "mouseenter",
       function (e) {
       function (e) {
+        removeTooltip();
         tooltipNode = document.createElement("div");
         tooltipNode = document.createElement("div");
+        tooltipNode.className = "v-gem-tooltip";
         tooltipNode.style.position = "fixed";
         tooltipNode.style.position = "fixed";
         tooltipNode.style.zIndex = 2147483647;
         tooltipNode.style.zIndex = 2147483647;
         tooltipNode.style.backgroundColor = "#191A1C";
         tooltipNode.style.backgroundColor = "#191A1C";
@@ -109,7 +116,6 @@ Vue.directive("tooltip", {
     el.addEventListener("keydown", removeTooltip);
     el.addEventListener("keydown", removeTooltip);
     el.addEventListener("scroll", removeTooltip);
     el.addEventListener("scroll", removeTooltip);
     el.addEventListener("dragstart", removeTooltip);
     el.addEventListener("dragstart", removeTooltip);
-    el.addEventListener("dragstart", removeTooltip);
     el.addEventListener("dragleave", removeTooltip);
     el.addEventListener("dragleave", removeTooltip);
   },
   },
 });
 });

+ 10 - 1
packages/qjkankan-editor/src/framework/EditorHead.vue

@@ -1,6 +1,9 @@
 <template>
 <template>
   <header class="app-head" app-border dir-bottom>
   <header class="app-head" app-border dir-bottom>
-    <a class="app-head-back" :href="`./material.html?lang=${$lang}#/works`">
+    <a
+      class="app-head-back"
+      :href="`./material.html?lang=${$lang}&from=${routerForm}#/works`"
+    >
       <i class="iconfont icon-editor_return"></i>
       <i class="iconfont icon-editor_return"></i>
       {{ back_myworks }}
       {{ back_myworks }}
     </a>
     </a>
@@ -39,6 +42,7 @@ import { mapGetters } from "vuex";
 import preview from "@/components/preview";
 import preview from "@/components/preview";
 import { i18n } from "@/lang";
 import { i18n } from "@/lang";
 import { $waiting } from "@/components/shared/loading";
 import { $waiting } from "@/components/shared/loading";
+import browser from "@/utils/browser";
 
 
 let hhhreg = /\\\\\\\\/g;
 let hhhreg = /\\\\\\\\/g;
 
 
@@ -78,6 +82,10 @@ export default {
       currentScene: "scene/currentScene",
       currentScene: "scene/currentScene",
       isEditing: "isEditing",
       isEditing: "isEditing",
     }),
     }),
+    routerForm() {
+      const from = browser.urlQueryValue("from");
+      return from;
+    },
   },
   },
   methods: {
   methods: {
     checkParams() {
     checkParams() {
@@ -159,6 +167,7 @@ export default {
         );
         );
       }
       }
       const scenes = this.info.scenes;
       const scenes = this.info.scenes;
+      // debugger
       if (scenes && scenes.length > 0) {
       if (scenes && scenes.length > 0) {
         scenes.forEach((scene, index) => {
         scenes.forEach((scene, index) => {
           if (
           if (

+ 3 - 0
packages/qjkankan-editor/src/framework/EditorMain.vue

@@ -46,6 +46,9 @@ export default {
     min-height: 100%;
     min-height: 100%;
     overflow-y: scroll;
     overflow-y: scroll;
     display: flex;
     display: flex;
+    &::-webkit-scrollbar {
+      width: 1px;
+    }
     > div {
     > div {
       flex: 1;
       flex: 1;
     }
     }

+ 9 - 6
packages/qjkankan-editor/src/framework/core/index.vue

@@ -1,4 +1,5 @@
 <template>
 <template>
+  <!-- 没引用 -->
   <div class="pano-body">
   <div class="pano-body">
     <div class="none-center" v-show="!activeItem">请先上传或添加场景素材</div>
     <div class="none-center" v-show="!activeItem">请先上传或添加场景素材</div>
 
 
@@ -105,10 +106,13 @@ export default {
           try {
           try {
             let tmp = newVal.initVisual;
             let tmp = newVal.initVisual;
             var krpano = document.getElementById("krpanoSWFObject");
             var krpano = document.getElementById("krpanoSWFObject");
-            krpano.set("view.vlookat", tmp.vlookat);
-            krpano.set("view.hlookat", tmp.hlookat);
+            krpano.set("view.vlookat", tmp ? tmp.vlookat : "");
+            krpano.set("view.hlookat", tmp ? tmp.hlookat : "");
+            krpano.set("view.limitview", "lookat");
+            krpano.set("view.vlookatmin", tmp ? tmp.vlookatmin : "-90");
+            krpano.set("view.vlookatmax", tmp ? tmp.vlookatmax : "90");
           } catch (error) {
           } catch (error) {
-            error;
+            console.error(error);
           }
           }
         };
         };
 
 
@@ -119,7 +123,7 @@ export default {
         if (newVal) {
         if (newVal) {
           removepano("#pano");
           removepano("#pano");
           $waiting.show();
           $waiting.show();
-  
+          // debugger;
           embedpano({
           embedpano({
             // http://ossxiaoan.4dage.com/720yun_fd_manage/fd720_Va0LrkXW3/vtour/tour.xml
             // http://ossxiaoan.4dage.com/720yun_fd_manage/fd720_Va0LrkXW3/vtour/tour.xml
             // xml: "%HTMLPATH%/static/template/tour.xml",
             // xml: "%HTMLPATH%/static/template/tour.xml",
@@ -143,7 +147,7 @@ export default {
   },
   },
   mounted() {
   mounted() {
     window.__krfn = __krfn;
     window.__krfn = __krfn;
-
+    debugger;
     this.$bus.on("currentPcode", (data) => {
     this.$bus.on("currentPcode", (data) => {
       this.activeItem = data;
       this.activeItem = data;
     });
     });
@@ -151,7 +155,6 @@ export default {
     this.$bus.on("addhotspot", (data) => {
     this.$bus.on("addhotspot", (data) => {
       this.addhotspot(data);
       this.addhotspot(data);
     });
     });
-   
   },
   },
 };
 };
 </script>
 </script>

+ 46 - 41
packages/qjkankan-editor/src/framework/material/aside.vue

@@ -1,78 +1,83 @@
 <template>
 <template>
   <div class="aside">
   <div class="aside">
     <ul>
     <ul>
-      <router-link tag="li" :to="item.link" v-for="(item,i) in list" :key="i">
-        <i class="iconfont" :class="item.icon"></i>{{item.name}}
+      <router-link tag="li" :to="item.link" v-for="(item, i) in list" :key="i">
+        <i class="iconfont" :class="item.icon"></i>{{ item.name }}
       </router-link>
       </router-link>
     </ul>
     </ul>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
-import {i18n} from "@/lang"
+import { i18n } from "@/lang";
 
 
 export default {
 export default {
-  data(){
+  data() {
     return {
     return {
-
-      list:[{
-        name:i18n.t("gather.panorama"),
-        id:'pano',
-        link:{path:'/pano'},
-        icon:'icon-material_panoramic_picture'
-      },{
-        name:i18n.t("gather.image"),
-        id:'image',
-        link:{path:'/image'},
-        icon:'icon-material_image'
-      },{
-        name:i18n.t("gather.audio"),
-        id:'audio',
-        link:{path:'/audio'},
-        icon:'icon-material_music'
-      },{
-        name:i18n.t("gather.video"),
-        id:'video',
-        link:{path:'/video'},
-        icon:'icon-material_video'
-      }]
-    }
+      list: [
+        {
+          name: i18n.t("gather.panorama"),
+          id: "pano",
+          link: { path: "/pano" },
+          icon: "icon-material_panoramic_picture",
+        },
+        {
+          name: i18n.t("gather.image"),
+          id: "image",
+          link: { path: "/image" },
+          icon: "icon-material_image",
+        },
+        {
+          name: i18n.t("gather.audio"),
+          id: "audio",
+          link: { path: "/audio" },
+          icon: "icon-material_music",
+        },
+        {
+          name: i18n.t("gather.video"),
+          id: "video",
+          link: { path: "/video" },
+          icon: "icon-material_video",
+        },
+      ],
+    };
   },
   },
-  methods:{
-    handleItem(data){
-      this.active = data
-      this.$router.push(data.link)
-    }
-  }
-}
+  methods: {
+    handleItem(data) {
+      this.active = data;
+      this.$router.push(data.link);
+    },
+  },
+};
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-.aside{
+.aside {
   color: #969799;
   color: #969799;
   background: #fff;
   background: #fff;
   width: 200px;
   width: 200px;
   margin-right: 30px;
   margin-right: 30px;
   flex-shrink: 0;
   flex-shrink: 0;
   max-height: 232px;
   max-height: 232px;
-  >ul{
+  > ul {
     padding: 20px 0;
     padding: 20px 0;
-    >li{
+    > li {
       padding-left: 50px;
       padding-left: 50px;
       cursor: pointer;
       cursor: pointer;
       line-height: 48px;
       line-height: 48px;
       height: 48px;
       height: 48px;
       color: #969799;
       color: #969799;
       font-size: 16px;
       font-size: 16px;
-      &:hover, &.router-link-exact-active,
+      &:hover,
+      &.router-link-exact-active,
       &.router-link-active {
       &.router-link-active {
         color: #323233;
         color: #323233;
-        background: #F7F8FA;
+        background: #f7f8fa;
       }
       }
-      .iconfont{
+      .iconfont {
         margin-right: 10px;
         margin-right: 10px;
       }
       }
     }
     }
   }
   }
 }
 }
-</style>
+</style>

+ 73 - 45
packages/qjkankan-editor/src/framework/material/header.vue

@@ -2,23 +2,44 @@
   <div class="header">
   <div class="header">
     <div class="con">
     <div class="con">
       <a :href="homeLink" class="logo">
       <a :href="homeLink" class="logo">
-        <img :src="require(`@/assets/images/icons/logo_black_${$lang}.svg`)" alt="" />
+        <img
+          :src="require(`@/assets/images/icons/logo_black_${$lang}.svg`)"
+          alt=""
+        />
       </a>
       </a>
       <ul class="tab">
       <ul class="tab">
-        <li @click="handleItem(item)" :class="{ active: active.id == item.id }" v-for="(item, i) in tab" :key="i">
+        <li
+          @click="handleItem(item)"
+          :class="{ active: active.id == item.id }"
+          v-for="(item, i) in tab"
+          :key="i"
+        >
           {{ item.name }}
           {{ item.name }}
         </li>
         </li>
       </ul>
       </ul>
 
 
       <div class="language-w">
       <div class="language-w">
         <div class="list">
         <div class="list">
-          <a class="header-item" :class="{ 'is-hover': showLangList }" @touchstart="showLangList = !showLangList">
-            <p class="guoqi" :style="{ 'background-image': `url(${languageObj.img})` }">
+          <a
+            class="header-item"
+            :class="{ 'is-hover': showLangList }"
+            @touchstart="showLangList = !showLangList"
+          >
+            <p
+              class="guoqi"
+              :style="{ 'background-image': `url(${languageObj.img})` }"
+            >
               {{ languageObj.name }}
               {{ languageObj.name }}
             </p>
             </p>
             <ul class="child-list">
             <ul class="child-list">
-              <li v-for="item in languageList" :key="item.name" :style="{ 'background-image': `url(${item.img})` }"
-                @click="changeLanguage(item.value)">{{ item.name }}</li>
+              <li
+                v-for="item in languageList"
+                :key="item.name"
+                :style="{ 'background-image': `url(${item.img})` }"
+                @click="changeLanguage(item.value)"
+              >
+                {{ item.name }}
+              </li>
             </ul>
             </ul>
           </a>
           </a>
         </div>
         </div>
@@ -32,7 +53,8 @@
 
 
 <script>
 <script>
 import UserInfo from "@/components/userInfo.vue";
 import UserInfo from "@/components/userInfo.vue";
-import { i18n } from "@/lang"
+import { i18n } from "@/lang";
+import browser from "@/utils/browser";
 
 
 export default {
 export default {
   components: {
   components: {
@@ -41,18 +63,18 @@ export default {
   data() {
   data() {
     return {
     return {
       homeLink: process.env.VUE_APP_PROXY_URL_ROOT,
       homeLink: process.env.VUE_APP_PROXY_URL_ROOT,
-      langauge: localStorage.getItem('language'),
+      langauge: localStorage.getItem("language"),
       languageList: [
       languageList: [
         {
         {
-          name: '简体中文',
-          img: require('@/assets/images/icons/china@2x.png'),
-          value: 'zh'
+          name: "简体中文",
+          img: require("@/assets/images/icons/china@2x.png"),
+          value: "zh",
         },
         },
         {
         {
-          name: 'English',
-          img: require('@/assets/images/icons/USA@2x.jpg'),
-          value: 'en'
-        }
+          name: "English",
+          img: require("@/assets/images/icons/USA@2x.jpg"),
+          value: "en",
+        },
       ],
       ],
       showLangList: false,
       showLangList: false,
       active: {},
       active: {},
@@ -61,45 +83,51 @@ export default {
           name: i18n.t("gather.my_works"),
           name: i18n.t("gather.my_works"),
           id: "works",
           id: "works",
           path: {
           path: {
-            path: '/works'
-          }
+            path: "/works",
+          },
         },
         },
         {
         {
           name: i18n.t("gather.my_material"),
           name: i18n.t("gather.my_material"),
           id: "material",
           id: "material",
           path: {
           path: {
-            path: '/pano'
-          }
-        }
+            path: "/pano",
+          },
+        },
       ],
       ],
     };
     };
   },
   },
   computed: {
   computed: {
     languageObj() {
     languageObj() {
       console.log(i18n.locale);
       console.log(i18n.locale);
-      return this.languageList.find(item => item.value === i18n.locale)
+      return this.languageList.find((item) => item.value === i18n.locale);
     },
     },
   },
   },
   watch: {
   watch: {
-    '$route.meta': {
+    "$route.meta": {
       deep: true,
       deep: true,
       handler: function (newVal) {
       handler: function (newVal) {
-        this.active = this.tab.find(item => {
-          return item.id == newVal.belong
-        })
-      }
-    }
+        this.active = this.tab.find((item) => {
+          return item.id == newVal.belong;
+        });
+      },
+    },
   },
   },
   methods: {
   methods: {
     changeLanguage(lang) {
     changeLanguage(lang) {
-      let arr = location.href.split('#/')
-      location.href = `material.html?lang=${lang}#/${arr[1]}`
-      localStorage.language = lang
+      let arr = location.href.split("#/");
+      const from = browser.urlQueryValue("from");
+      if (from) {
+        location.href = `material.html?lang=${lang}&from=${from}#/${arr[1]}`;
+      } else {
+        location.href = `material.html?lang=${lang}#/${arr[1]}`;
+      }
+
+      localStorage.language = lang;
     },
     },
     handleItem(item) {
     handleItem(item) {
-      this.$router.push(item.path)
-    }
-  }
+      this.$router.push(item.path);
+    },
+  },
 };
 };
 </script>
 </script>
 
 
@@ -127,7 +155,7 @@ export default {
       font-weight: bold;
       font-weight: bold;
       height: 100%;
       height: 100%;
 
 
-      >img {
+      > img {
         height: 100%;
         height: 100%;
         vertical-align: middle;
         vertical-align: middle;
       }
       }
@@ -138,7 +166,7 @@ export default {
       align-items: center;
       align-items: center;
       margin-left: 116px;
       margin-left: 116px;
 
 
-      >li {
+      > li {
         cursor: pointer;
         cursor: pointer;
         margin-right: 50px;
         margin-right: 50px;
         text-align: left;
         text-align: left;
@@ -148,14 +176,14 @@ export default {
         &.active {
         &.active {
           font-size: 16px;
           font-size: 16px;
           font-weight: bold;
           font-weight: bold;
-          color: #0076F6;
+          color: #0076f6;
           position: relative;
           position: relative;
 
 
           &::before {
           &::before {
-            content: '';
+            content: "";
             width: 20px;
             width: 20px;
             height: 2px;
             height: 2px;
-            background: #0076F6;
+            background: #0076f6;
             display: inline-block;
             display: inline-block;
             position: absolute;
             position: absolute;
             left: 50%;
             left: 50%;
@@ -168,7 +196,7 @@ export default {
 
 
     .language-w {
     .language-w {
       margin-left: auto;
       margin-left: auto;
-      .list{
+      .list {
         height: 100%;
         height: 100%;
         display: flex;
         display: flex;
       }
       }
@@ -191,7 +219,8 @@ export default {
           font-size: 14px;
           font-size: 14px;
           font-weight: normal;
           font-weight: normal;
           cursor: pointer;
           cursor: pointer;
-          background: url(~@/assets/images/icons/china.png) no-repeat left center;
+          background: url(~@/assets/images/icons/china.png) no-repeat left
+            center;
           background-size: 20px 14px;
           background-size: 20px 14px;
           padding: 10px 0 10px 28px;
           padding: 10px 0 10px 28px;
 
 
@@ -199,7 +228,6 @@ export default {
             font-size: 10px;
             font-size: 10px;
             color: rgb(144, 144, 144);
             color: rgb(144, 144, 144);
           }
           }
-
         }
         }
 
 
         &:hover p .iconfont::before {
         &:hover p .iconfont::before {
@@ -228,7 +256,7 @@ export default {
           margin-left: -2px;
           margin-left: -2px;
           width: 0;
           width: 0;
           height: 0px;
           height: 0px;
-          content: '';
+          content: "";
           background: #fff;
           background: #fff;
           border-style: solid;
           border-style: solid;
           border-width: 4px;
           border-width: 4px;
@@ -253,16 +281,16 @@ export default {
           font-size: 14px;
           font-size: 14px;
           font-weight: normal;
           font-weight: normal;
           padding: 0 13px 0 40px;
           padding: 0 13px 0 40px;
-          background: url(~@/assets/images/icons/china.png) no-repeat 13px center;
+          background: url(~@/assets/images/icons/china.png) no-repeat 13px
+            center;
           background-size: 20px 14px;
           background-size: 20px 14px;
 
 
           &:hover {
           &:hover {
-            background-color: #EBEBEB;
+            background-color: #ebebeb;
             color: #202020;
             color: #202020;
           }
           }
         }
         }
       }
       }
-
     }
     }
 
 
     .user-info {
     .user-info {

+ 26 - 22
packages/qjkankan-editor/src/framework/material/index.vue

@@ -1,7 +1,7 @@
 <template>
 <template>
   <div class="material">
   <div class="material">
     <app-header></app-header>
     <app-header></app-header>
-    <router-view class="body" v-if="$route.name=='works'"/>
+    <router-view class="body" v-if="isFilterAside" />
     <div class="body" v-else>
     <div class="body" v-else>
       <app-aside></app-aside>
       <app-aside></app-aside>
       <app-main></app-main>
       <app-main></app-main>
@@ -15,38 +15,43 @@ import AppHeader from "./header.vue";
 import AppMain from "./Main.vue";
 import AppMain from "./Main.vue";
 
 
 export default {
 export default {
-  components:{
+  components: {
     AppAside,
     AppAside,
     AppHeader,
     AppHeader,
-    AppMain
+    AppMain,
+  },
+  computed: {
+    isFilterAside: function () {
+      return ["works", "camList"].includes(this.$route.name);
+    },
   },
   },
   mounted() {
   mounted() {
-    this.$nextTick(()=>{
-      this.$bus.on('refreshTips',(data)=>{
-      setTimeout(() => {
-        let lastinstances = $.tooltipster.instancesLatest();
-        $.each(lastinstances, function(i, instance){
-          instance.destroy()
+    this.$nextTick(() => {
+      this.$bus.on("refreshTips", (data) => {
+        setTimeout(() => {
+          let lastinstances = $.tooltipster.instancesLatest();
+          $.each(lastinstances, function (i, instance) {
+            instance.destroy();
+          });
+
+          $(".tttttt").tooltipster({
+            delay: 300,
+            ...data,
+          });
         });
         });
-        
-        $('.tttttt').tooltipster({
-          delay: 300,
-          ...data
-        }); 
       });
       });
-    })
-    })
+    });
   },
   },
-}
+};
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-.material{
+.material {
   overflow: hidden;
   overflow: hidden;
   position: relative;
   position: relative;
-  background: #EFF2F4;
+  background: #eff2f4;
   height: 100vh;
   height: 100vh;
-  .body{
+  .body {
     max-width: 1280px;
     max-width: 1280px;
     margin: 0 auto;
     margin: 0 auto;
     padding-bottom: 30px;
     padding-bottom: 30px;
@@ -55,5 +60,4 @@ export default {
     height: 100%;
     height: 100%;
   }
   }
 }
 }
-
-</style>
+</style>

+ 0 - 456
packages/qjkankan-editor/src/framework/play/pano/components/list copy.vue

@@ -1,456 +0,0 @@
-<template>
-  <div class="bar-list" v-if="show" :class="{ disable: isEditing }">
-    <div
-      class="top-con"
-      v-show="currentScenesList.length > 0"
-      :style="`width:${
-        Math.max(scenesListW, secondaryW) > 1150
-          ? '100%'
-          : Math.max(scenesListW, secondaryW) + 120 + 'px'
-      }`"
-    >
-      <div
-        class="swiper-container"
-        :style="`width:${scenesListW > 1150 ? '100%' : scenesListW + 'px'}`"
-        id="swScenes"
-        ref="sw"
-        v-swiper:mySwiper="swiperOptions"
-        v-if="currentScenesList.length > 0"
-      >
-        <ul class="swiper-wrapper">
-          <li
-            v-tooltip="
-              item.type === '4dkk' ? $i18n.t('navigation.scene_edit_tips') : ''
-            "
-            @click="tabCurrentScene(item)"
-            class="swiper-slide"
-            :class="{
-              active: currentScene.id == item.id,
-              loopspan:
-                item.sceneTitle.length > spanlength &&
-                currentScene.id == item.id,
-            }"
-            :style="{ backgroundImage: `url(${item.icon})` }"
-            v-for="(item, i) in currentScenesList"
-            :key="item.id"
-          >
-            <i
-              class="iconfont icon-edit_type_3d"
-              :class="{ iconedit_type_panorama: item.type !== '4dkk' }"
-            ></i>
-            <div>
-              <span v-if="currentScene.id == item.id">{{
-                item.sceneTitle
-              }}</span>
-              <span v-else>{{
-                item.sceneTitle.length > spanlength
-                  ? item.sceneTitle.slice(0, spanlength)
-                  : item.sceneTitle
-              }}</span>
-            </div>
-          </li>
-        </ul>
-      </div>
-
-      <div
-        class="swiper-container"
-        :style="`width:${Math.min(secondaryW, innerW)}px`"
-        id="swSecondary"
-        ref="sw1"
-        v-swiper:mySwipera="swiperOptions"
-        v-if="
-          !(
-            secondaryList.length === 1 &&
-            (secondaryList[0].name === '默认二级分组' ||
-              secondaryList[0].name === $i18n.t('navigation.default_group_two'))
-          )
-        "
-      >
-        <ul class="swiper-wrapper">
-          <li
-            class="swiper-slide"
-            @click="tabSecondary(item)"
-            :class="{
-              active: currentSecondary.id == item.id,
-              loopspan:
-                fixTitle(item.name).length > spanlength &&
-                currentSecondary.id == item.id,
-            }"
-            v-for="(item, i) in secondaryList"
-            :key="i"
-          >
-            <span v-if="currentSecondary.id == item.id">{{
-              fixTitle(item.name)
-            }}</span>
-            <span v-else>{{
-              fixTitle(item.name).length > spanlength
-                ? fixTitle(item.name).slice(0, spanlength)
-                : fixTitle(item.name)
-            }}</span>
-          </li>
-        </ul>
-      </div>
-    </div>
-
-    <div
-      class="swiper-container"
-      :style="`width:${catalogRootW > innerW ? '100%' : catalogRootW + 'px'}`"
-      id="swcatalogRoot"
-      ref="sw2"
-      v-swiper:mySwiperb="swiperOptions"
-      v-if="metadata.catalogRoot.length > 0 && metadata.catalogs.length > 1"
-    >
-      <ul class="swiper-wrapper" v-if="metadata.catalogRoot.length > 1">
-        <li
-          class="swiper-slide"
-          :class="{
-            active: currentCatalogRoot.id == item.id,
-            loopspan:
-              fixTitle(item.name).length > spanlength &&
-              currentCatalogRoot.id == item.id,
-          }"
-          @click="tabRoot(item)"
-          v-for="(item, i) in metadata.catalogRoot"
-          :key="i"
-        >
-          <span v-if="currentCatalogRoot.id == item.id">{{
-            fixTitle(item.name)
-          }}</span>
-          <span v-else>{{
-            fixTitle(item.name).length > spanlength
-              ? fixTitle(item.name).slice(0, spanlength)
-              : fixTitle(item.name)
-          }}</span>
-        </li>
-      </ul>
-    </div>
-  </div>
-</template>
-
-<script>
-import { mapGetters } from "vuex";
-
-import { directive } from "vue-awesome-swiper";
-// import style (<= Swiper 5.x)
-import "swiper/css/swiper.css";
-
-export default {
-  data() {
-    return {
-      spanlength: 6,
-      show: false,
-      innerW: 1150,
-      swidth: {
-        swcatalogRoot: 104,
-        swSecondary: 84,
-        swScenes: 72,
-      },
-    };
-  },
-  directives: {
-    swiper: directive,
-  },
-
-  watch: {
-    currentSecondary() {
-      this.loadList();
-    },
-    currentScenesList() {
-      this.loadList();
-    },
-    metadata: {
-      deep: true,
-      handler: function (newVal) {
-        if (newVal.scenes) {
-          this.$store.commit("scene/setScenes", newVal.scenes);
-          if (!this.show) {
-            this.show = true;
-            this.loadList();
-          } else {
-            // 更新列表
-            this.tabRoot(this.currentCatalogRoot);
-          }
-        }
-      },
-    },
-  },
-  computed: {
-    ...mapGetters({
-      metadata: "scene/metadata",
-      scenes: "scene/list",
-      currentScene: "scene/currentScene",
-      currentCatalogRoot: "scene/currentCatalogRoot",
-      currentSecondary: "scene/currentSecondary",
-      secondaryList: "scene/secondaryList",
-      isShowScenesList: "scene/isShowScenesList",
-      currentScenesList: "scene/currentScenesList",
-      isEditing: "isEditing",
-    }),
-
-    scenesListW() {
-      return this.currentScenesList.length * (this.swidth["swScenes"] + 10);
-    },
-    secondaryW() {
-      return this.secondaryList.length * (this.swidth["swSecondary"] + 10);
-    },
-    catalogRootW() {
-      return (
-        this.metadata.catalogRoot.length * (this.swidth["swcatalogRoot"] + 10)
-      );
-    },
-    swiperOptions() {
-      return {
-        slidesPerView: "auto",
-        centeredSlides: true,
-        centerInsufficientSlides: true,
-        centeredSlidesBounds: true,
-        freeMode: {
-          enabled: true,
-          sticky: false,
-        },
-      };
-    },
-  },
-  methods: {
-    fixTitle(name) {
-      if (name == "默认二级分组") {
-        name = this.$i18n.t("navigation.default_group_two");
-      } else if (name == "一级分组") {
-        name = this.$i18n.t("navigation.group_one");
-      } else {
-        name = name;
-      }
-      return name;
-    },
-    loadList() {
-      this.$nextTick(() => {
-        // let t = setTimeout(() => {
-        //   clearTimeout(t);
-        //   ["#swcatalogRoot", "#swSecondary", "#swScenes"].forEach((item) => {
-        //     new Swiper(item, {
-        //       slidesPerView: "auto",
-        //       spaceBetween: 10,
-        //       centeredSlides: true,
-        //       centerInsufficientSlides: true,
-        //       centeredSlidesBounds: true,
-        //       freeMode: true,
-        //     });
-        //   });
-        // }, 100);
-      });
-    },
-
-    tabCurrentScene(data) {
-      this.$store.commit("scene/setCurrentScene", data);
-    },
-
-    tabSecondary(data) {
-      this.$store.commit("scene/setCurrentSecondary", data);
-    },
-
-    tabRoot(data) {
-      this.$store.commit("scene/setCurrentCatalogRoot", data);
-    },
-  },
-
-  mounted() {
-    // console.log(this.metadata,'metadata');
-    // this.loadList();
-  },
-};
-</script>
-
-<style lang="less" scoped>
-@width: 1150px;
-
-.bar-list {
-  position: absolute;
-  bottom: 28px;
-  left: 50%;
-  transform: translateX(-50%);
-  text-align: center;
-  max-width: @width;
-  overflow: hidden;
-  transition: 0.3s all ease;
-  .swiper-container {
-    width: 100%;
-    position: relative;
-
-    > ul {
-      margin: 0 auto;
-      > li {
-        margin: 0 5px;
-        white-space: nowrap;
-
-        > span,
-        > div > span {
-          cursor: pointer;
-          display: inline-block;
-          color: rgba(255, 255, 255, 0.6);
-        }
-
-        &.loopspan {
-          > span,
-          > div > span {
-            animation: 5s wordsLoop linear infinite normal;
-          }
-        }
-
-        &.active {
-          > span,
-          > div > span {
-            color: rgba(255, 255, 255, 1);
-          }
-        }
-      }
-    }
-  }
-
-  .top-con {
-    margin: 0 auto 10px;
-    padding: 10px 0;
-    background: linear-gradient(
-      268deg,
-      rgba(0, 0, 0, 0) 0%,
-      rgba(0, 0, 0, 0.4) 25%,
-      rgba(0, 0, 0, 0.4) 75%,
-      rgba(0, 0, 0, 0) 100%
-    );
-  }
-
-  #swcatalogRoot {
-    > ul {
-      > li {
-        width: 104px;
-        background: rgba(0, 0, 0, 0.5);
-        border-radius: 4px;
-        padding: 4px 10px;
-        border: 1px solid rgba(255, 255, 255, 0.5);
-        box-sizing: border-box;
-        overflow: hidden;
-
-        > span {
-          width: 100%;
-          word-break: keep-all;
-        }
-
-        &.active {
-          border: 1px solid rgba(255, 255, 255, 1);
-        }
-      }
-    }
-  }
-
-  #swSecondary {
-    margin: 20px auto 10px;
-
-    > ul {
-      > li {
-        width: 84px;
-        box-sizing: border-box;
-        overflow: hidden;
-        padding-bottom: 6px;
-
-        > span {
-          width: 100%;
-          word-break: keep-all;
-        }
-
-        &.active {
-          position: relative;
-
-          &::before {
-            content: "";
-            display: inline-block;
-            position: absolute;
-            bottom: 0;
-            width: 20px;
-            height: 2px;
-            z-index: 9999;
-            left: 50%;
-            transform: translateX(-50%);
-            background: @color;
-          }
-        }
-      }
-    }
-  }
-
-  #swScenes {
-    > ul {
-      > li {
-        cursor: pointer;
-        width: 72px;
-        height: 72px;
-        border-radius: 6px;
-        border: 1px solid #ffffff;
-        background-size: cover;
-        position: relative;
-        overflow: hidden;
-
-        .iconfont {
-          position: absolute;
-          left: 4px;
-          top: 4px;
-          z-index: 99;
-
-          &::after {
-            background: rgba(0, 0, 0, 0.3);
-            content: "";
-            width: 14px;
-            height: 14px;
-            display: inline-block;
-            position: absolute;
-            top: 50%;
-            left: 50%;
-            transform: translate(-50%, -50%);
-            z-index: -1;
-            filter: blur(4px);
-          }
-        }
-
-        > div {
-          position: absolute;
-          bottom: 0;
-          left: 0;
-          height: 20px;
-          background: rgba(0, 0, 0, 0.5);
-          width: 100%;
-          overflow: hidden;
-
-          > span {
-            width: 100%;
-            line-height: 20px;
-            word-break: keep-all;
-          }
-        }
-
-        &.active {
-          border: 1px solid @color;
-
-          > div {
-            > span {
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-.barshow {
-  max-height: 520px;
-}
-
-@keyframes wordsLoop {
-  0% {
-    transform: translateX(100%);
-    -webkit-transform: translateX(100%);
-  }
-
-  100% {
-    transform: translateX(-100%);
-    -webkit-transform: translateX(-100%);
-  }
-}
-</style>

+ 19 - 11
packages/qjkankan-editor/src/framework/play/pano/components/list.vue

@@ -25,6 +25,7 @@
             class="swiper-slide"
             class="swiper-slide"
             :class="{
             :class="{
               active: currentScene.id == item.id,
               active: currentScene.id == item.id,
+              disabled: isLockV4Scene,
               // loopspan:
               // loopspan:
               //   item.sceneTitle.length > spanlength &&
               //   item.sceneTitle.length > spanlength &&
               //   currentScene.id == item.id,
               //   currentScene.id == item.id,
@@ -34,8 +35,10 @@
             :key="item.id"
             :key="item.id"
           >
           >
             <i
             <i
-              class="iconfont icon-edit_type_3d"
-              :class="{ iconedit_type_panorama: item.type !== '4dkk' }"
+              class="iconfont"
+              :class="
+                item.type == '4dkk' ? 'icon-editor_3d' : 'icon-editor_panoramic'
+              "
             ></i>
             ></i>
             <!-- <div>
             <!-- <div>
               <span v-if="currentScene.id == item.id">{{
               <span v-if="currentScene.id == item.id">{{
@@ -86,6 +89,7 @@
             @click="tabSecondary(item)"
             @click="tabSecondary(item)"
             :class="{
             :class="{
               active: currentSecondary.id == item.id,
               active: currentSecondary.id == item.id,
+              disabled: isLockV4Scene,
               loopspan:
               loopspan:
                 fixTitle(item.name).length > spanlength &&
                 fixTitle(item.name).length > spanlength &&
                 currentSecondary.id == item.id,
                 currentSecondary.id == item.id,
@@ -93,7 +97,7 @@
             v-for="(item, i) in secondaryList"
             v-for="(item, i) in secondaryList"
             :key="i"
             :key="i"
           >
           >
-          <marquee-text
+            <marquee-text
               :duration="Math.ceil(fixTitle(item.name).length / 10) * 5"
               :duration="Math.ceil(fixTitle(item.name).length / 10) * 5"
               :key="item.id"
               :key="item.id"
               :repeat="1"
               :repeat="1"
@@ -124,6 +128,7 @@
           class="swiper-slide"
           class="swiper-slide"
           :class="{
           :class="{
             active: currentCatalogRoot.id == item.id,
             active: currentCatalogRoot.id == item.id,
+            disabled: isLockV4Scene,
             loopspan:
             loopspan:
               fixTitle(item.name).length > spanlength &&
               fixTitle(item.name).length > spanlength &&
               currentCatalogRoot.id == item.id,
               currentCatalogRoot.id == item.id,
@@ -132,7 +137,7 @@
           v-for="(item, i) in metadata.catalogRoot"
           v-for="(item, i) in metadata.catalogRoot"
           :key="i"
           :key="i"
         >
         >
-        <marquee-text
+          <marquee-text
             :duration="Math.ceil(fixTitle(item.name).length / 10) * 5"
             :duration="Math.ceil(fixTitle(item.name).length / 10) * 5"
             :key="item.id"
             :key="item.id"
             :repeat="1"
             :repeat="1"
@@ -153,7 +158,7 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import { mapGetters } from "vuex";
+import { mapGetters, mapState } from "vuex";
 import { debounce } from "lodash";
 import { debounce } from "lodash";
 import MarqueeText from "vue-marquee-text-component";
 import MarqueeText from "vue-marquee-text-component";
 
 
@@ -186,7 +191,7 @@ export default {
     currentSecondary(val) {},
     currentSecondary(val) {},
     secondaryList(val) {
     secondaryList(val) {
       if (val.length > 1) {
       if (val.length > 1) {
-        console.log("secondaryList", val);
+        // console.log("secondaryList", val);
         this.initsencordNatSwiper();
         this.initsencordNatSwiper();
       } else {
       } else {
         if (window.sencordNatSwiper) {
         if (window.sencordNatSwiper) {
@@ -223,6 +228,9 @@ export default {
     },
     },
   },
   },
   computed: {
   computed: {
+    ...mapState({
+      isLockV4Scene: "isLockV4Scene",
+    }),
     ...mapGetters({
     ...mapGetters({
       metadata: "scene/metadata",
       metadata: "scene/metadata",
       scenes: "scene/list",
       scenes: "scene/list",
@@ -291,7 +299,7 @@ export default {
     },
     },
     initMainSwiper() {
     initMainSwiper() {
       this.$nextTick(() => {
       this.$nextTick(() => {
-        console.warn("initMainSwiper");
+        // console.warn("initMainSwiper");
         if (window.mainNatSwiper) {
         if (window.mainNatSwiper) {
           window.mainNatSwiper = null;
           window.mainNatSwiper = null;
         }
         }
@@ -303,10 +311,10 @@ export default {
     },
     },
     mainNatSwiperFocus() {
     mainNatSwiperFocus() {
       this.$nextTick(() => {
       this.$nextTick(() => {
-        console.warn(
-          "catalogRoot-mainNatSwiperFocus",
-          this.metadata.catalogRoot
-        );
+        // console.warn(
+        //   "catalogRoot-mainNatSwiperFocus",
+        //   this.metadata.catalogRoot
+        // );
         const current = Array.from(this.metadata.catalogRoot).findIndex(
         const current = Array.from(this.metadata.catalogRoot).findIndex(
           (item) => item.id === this.currentCatalogRoot.id
           (item) => item.id === this.currentCatalogRoot.id
         );
         );

+ 135 - 28
packages/qjkankan-editor/src/framework/play/pano/index.vue

@@ -87,6 +87,7 @@ export default {
   watch: {
   watch: {
     "$route.name": function (newVal) {
     "$route.name": function (newVal) {
       __krfn.utils.toggleHotspot(this.$getKrpano(), newVal == "hotspot");
       __krfn.utils.toggleHotspot(this.$getKrpano(), newVal == "hotspot");
+      this.handleRouterCoverForCap();
     },
     },
     currentScene(newVal) {
     currentScene(newVal) {
       if (newVal) {
       if (newVal) {
@@ -114,6 +115,7 @@ export default {
         } else {
         } else {
           this.currentSceneVersion = newVal.version;
           this.currentSceneVersion = newVal.version;
         }
         }
+        this.handleIFrameCss();
       } else {
       } else {
         $("#pano").empty();
         $("#pano").empty();
         window.vrInitFn = () => {
         window.vrInitFn = () => {
@@ -130,11 +132,13 @@ export default {
         };
         };
         window.vrViewFn = () => {
         window.vrViewFn = () => {
           try {
           try {
-            let tmp = newVal.initVisual;
-            this.$getKrpano().set("view.vlookat", tmp.vlookat);
-            this.$getKrpano().set("view.hlookat", tmp.hlookat);
+            let visual = newVal.initVisual;
+            const { sky, earth } = newVal.customMask;
+            this.handleVisual(visual);
+            this.handleSkyCover(sky);
+            this.handleEarthCover(earth);
           } catch (error) {
           } catch (error) {
-            error;
+            console.error(error);
           }
           }
         };
         };
 
 
@@ -142,10 +146,10 @@ export default {
           "events[skin_events].onxmlcomplete": "js(window.vrViewFn());",
           "events[skin_events].onxmlcomplete": "js(window.vrViewFn());",
           "events[skin_events].onloadcomplete": "js(window.vrInitFn());",
           "events[skin_events].onloadcomplete": "js(window.vrInitFn());",
         };
         };
-
         if (newVal) {
         if (newVal) {
           removepano("#pano");
           removepano("#pano");
           $waiting.show();
           $waiting.show();
+
           embedpano({
           embedpano({
             // http://oss-xiaoan.oss-cn-shenzhen.aliyuncs.com/720yun_fd_manage/fd720_Va0LrkXW3/vtour/tour.xml
             // http://oss-xiaoan.oss-cn-shenzhen.aliyuncs.com/720yun_fd_manage/fd720_Va0LrkXW3/vtour/tour.xml
             // xml: "%HTMLPATH%/static/template/tour.xml",
             // xml: "%HTMLPATH%/static/template/tour.xml",
@@ -160,28 +164,6 @@ export default {
           });
           });
         }
         }
       }
       }
-
-      // var settings = {
-      //   "events[skin_events].onxmlcomplete": "js(window.vrViewFn());",
-      //   "events[skin_events].onloadcomplete": "js(window.vrInitFn());",
-      // };
-
-      // if (newVal) {
-      //   removepano("#pano");
-      //   $waiting.show();
-      //   embedpano({
-      //     // http://ossxiaoan.4dage.com/720yun_fd_manage/fd720_Va0LrkXW3/vtour/tour.xml
-      //     // xml: "%HTMLPATH%/static/template/tour.xml",
-      //     xml: `${this.$cdn}/720yun_fd_manage/${newVal.sceneCode}/vtour/tour.xml`,
-      //     swf: "%HTMLPATH%/static/template/tour.swf",
-      //     target: "pano",
-      //     html5: "auto",
-      //     mobilescale: 1,
-      //     vars: settings,
-      //     webglsettings: { preserveDrawingBuffer: true },
-      //     passQueryParameters: true,
-      //   });
-      // }
     },
     },
     isConfirmingPosi(newVal) {
     isConfirmingPosi(newVal) {
       this.inter && clearInterval(this.inter);
       this.inter && clearInterval(this.inter);
@@ -231,12 +213,137 @@ export default {
       __krfn.utils.addhotspot(this.$getKrpano(), param, true);
       __krfn.utils.addhotspot(this.$getKrpano(), param, true);
       this.$store.commit("tags/setIsConfirmingPosi", param.name);
       this.$store.commit("tags/setIsConfirmingPosi", param.name);
     },
     },
+
+    handleVisual(visual) {
+      this.$getKrpano().set(
+        "view.vlookat",
+        "vlookat" in visual ? visual.vlookat : ""
+      );
+      this.$getKrpano().set(
+        "view.hlookat",
+        "hlookat" in visual ? visual.hlookat : ""
+      );
+      this.$getKrpano().set("view.limitview", "lookat");
+      this.$getKrpano().set(
+        "view.vlookatmin",
+        "vlookatmin" in visual ? visual.vlookatmin : "-90"
+      );
+      this.$getKrpano().set(
+        "view.vlookatmax",
+        "vlookatmax" in visual ? visual.vlookatmax : "90"
+      );
+    },
+    /**
+     * 1.6 处理遮罩层
+     * @param {*} sky
+     */
+    handleSkyCover(skyMask) {
+      // console.log("handleSkyCover", skyMask);
+      if (skyMask) {
+        if ("isShow" in skyMask) {
+          this.$getKrpano().set(
+            `hotspot[peaklogo].visible`,
+            Boolean(skyMask.isShow)
+          );
+        }
+
+        if (skyMask.icon) {
+          this.$getKrpano().set(`hotspot[peaklogo].url`, skyMask.icon);
+        } 
+        if ("scale" in skyMask) {
+          this.$getKrpano().set(`hotspot[peaklogo].scale`, skyMask.scale);
+        }
+        if ("antidistorted" in skyMask) {
+          this.$getKrpano().set(
+            `hotspot[peaklogo].distorted`,
+            skyMask.antidistorted
+          );
+          if (!skyMask.antidistorted) {
+            this.$getKrpano().set(
+              `hotspot[peaklogo].scale`,
+              skyMask.scale * 0.5
+            );
+          }
+        }
+      }
+    },
+    /**
+     * 1.6 处理遮罩层
+     * @param {*} sky
+     */
+    handleEarthCover(earthMask) {
+      if (earthMask) {
+        if ("isShow" in earthMask) {
+          this.$getKrpano().set(
+            `hotspot[nadirlogo].visible`,
+            Boolean(earthMask.isShow)
+          );
+        }
+        if (earthMask.icon) {
+          this.$getKrpano().set(`hotspot[nadirlogo].url`, earthMask.icon);
+        }
+        if ("scale" in earthMask) {
+          this.$getKrpano().set(`hotspot[nadirlogo].scale`, earthMask.scale);
+        }
+        if ("antidistorted" in earthMask) {
+          this.$getKrpano().set(
+            `hotspot[nadirlogo].distorted`,
+            earthMask.antidistorted
+          );
+          if (!earthMask.antidistorted) {
+            this.$getKrpano().set(
+              `hotspot[nadirlogo].scale`,
+              earthMask.scale * 0.5
+            );
+          }
+        }
+      }
+    },
+    handleRouterCoverForCap() {
+      const { sky, earth } = this.currentScene.customMask;
+      sky.isShow &&
+        this.$getKrpano() &&
+        this.$getKrpano().set("hotspot[peaklogo].visible", true);
+      earth.isShow &&
+        this.$getKrpano() &&
+        this.$getKrpano().set("hotspot[nadirlogo].visible", true);
+    },
+    handleIFrameCss() {
+      setTimeout(() => {
+        //   const css = `
+        // <style type="text/css">
+        // .controls-right-buttons{display:none !important};
+        // .controls-left-buttons .play-control {display: none !important;}
+        // </style>
+        // `;
+        const iframe = document.getElementById("iframe-4dkk");
+        const doc = iframe.contentWindow.document;
+        if (doc) {
+          console.log("存在-doc");
+          var css =
+            ".controls-right-buttons>div:not(:has(.icon-music_s)){display:none !important} " +
+            ".controls-left-buttons .play-control{display:none !important} " +
+            ".link-btn{display:none !important} " +
+            ".clear-box{display:none !important} " +
+            ".camera-btn{display:none !important} ";
+          var head = doc.head || doc.getElementsByTagName("head")[0];
+          var style = doc.createElement("style");
+          style.type = "text/css";
+          if (style.styleSheet) {
+            style.styleSheet.cssText = css;
+          } else {
+            style.appendChild(doc.createTextNode(css));
+          }
+
+          head.appendChild(style);
+        }
+      }, 1000);
+    },
   },
   },
   mounted() {
   mounted() {
     window.__krfn = __krfn;
     window.__krfn = __krfn;
 
 
     this.$bus.on("addhotspot", (data) => {
     this.$bus.on("addhotspot", (data) => {
-      // debugger
       this.addhotspot(data);
       this.addhotspot(data);
     });
     });
 
 

+ 0 - 979
packages/qjkankan-editor/src/lang/_en.json

@@ -1,979 +0,0 @@
-{
-  "menu": {
-    "__name": "菜单",
-    "music": "BGM",
-    "base": "Basic Setting",
-    "information": "Scene Info",
-    "screen": "Scene Cover",
-    "hotspot": "Add Hotspot",
-    "guide": "Tour Guide",
-    "sign": "Spot Logo",
-    "walk": "Roaming Possibility",
-    "model": "Model Edit",
-    "custom": "Upload Download",
-    "videos": "Add Videos",
-    "vrhouse": "VR house",
-    "business": "Business part",
-    "scene": "Scene transition",
-    "video": "Add video",
-    "decor": "Decor",
-    "link": "Scene associate"
-  },
-  "modules": {
-    "__name": "模块",
-    "base": {
-      "__name": "基础设置",
-      "qrcode": "QR Code",
-      "qrcode_download": "Download the QR code",
-      "qrcode_tips": "Customize your logo",
-      "scene_link": "Scene Link",
-      "scene_link_copy": "Copy the link",
-      "scene_link_copy_tips": "Copied",
-      "bgm": "BGM",
-      "pano_text": "Roaming",
-      "mode_2d_text": "Layout Plan",
-      "mode_3d_text": "3D Model",
-      "map_text": "Mini-map",
-      "vr_text": "VR Mode",
-      "vr_tips": "Please view the VR effect on the mobile display page",
-      "guide_text": "Tour Guide",
-      "rule_text": "Measurements",
-      "cad_text": "Floor Layout",
-      "measure_text": "Ruler",
-      "measure_tips": "The measuring function cannot be used in edit mode, please use it on display page",
-      "turned_vr": "VR turned {status}",
-      "turned_map": "Mini-map feature  {status}",
-      "turned_cad": "Layout plan {status}",
-      "turned_m2d": "Plane view {status}",
-      "turned_m3d": "3D view {status}",
-      "turned_pano": "Roaming view {status}",
-      "turned_rule": "Measuring function {status}",
-      "turned_guide": "Tour guide {status}",
-      "turned_measure": "Ruler {status}",
-      "shortcut_copy": "One-click copy",
-      "share_link": "Share links to friends",
-      "measure_show_tips": "Please use the measuring function on the display page",
-      "delete_measure_line": "Delete measurement line",
-      "please_click_tips": "'Please click“allow”'",
-      "vr_fail_app_tips": "The browser failed to detect the rotation. Please enable the settings such as motion and direction access in the phone or browser settings, and then refresh this page.",
-      "vr_fail_safari_tips": "The browser failed to detect the rotation. To fully experience the VR effect, please open the \"Motion and Direction Access\" switch under \"Settings\"> \"Safari\"> \"Privacy and Security\", and then refresh this page.",
-      "loading_bottom_text": "4Dage provides technical support",
-      "vr_fail_reopen_tips": "Can't access motion and orientation, please restart App and try again.",
-      "add_music_title": "Add BGM",
-      "add_music_tips": "Support MP3, WAV and other audio formats, no more than 5MB",
-      "re_add_music": "Re-add",
-      "re_add_title": "Re-add BGM",
-      "re_add_tips": "The latest music will replace the added music, <br>Are you sure to continue adding?",
-      "re_add_mobile_tips": "Re-adding will replace the added music",
-      "delete_tips": "Are you sure to delete the current BGM?",
-      "delete_title": "Delete BGM",
-      "bgm_empty_tips": "Please select BGM",
-      "wechat": "Wechat",
-      "friend_circle": "Moments"
-    },
-    "information": {
-      "__name": "场景信息",
-      "title": "Title",
-      "title_tips": "Please enter a title",
-      "title_require": "Please add a title (max {limit} characters).",
-      "description": "Description",
-      "description_tips": "Please enter a description.",
-      "link": "Add a link",
-      "link_text_tips": "Link title",
-      "link_href_tips": "Link",
-      "link_text_require": "Please fill in the text.",
-      "link_href_require": "Please fill in the link.",
-      "classify": "Choose a category",
-      "upload_time": "Upload date",
-      "record": "Not recorded",
-      "shoot_count": "Number of shots",
-      "password": "Set a password",
-      "password_tips": "4 characters",
-      "password_desc": "Set a password if you don't want it to be accessible for the public.",
-      "password_require": "Please enter a password with {limit} digits",
-      "logo_edit": "Edit the logo",
-      "logo_exit": "Finish editting logo",
-      "logo_show_bottom": "Show original logo",
-      "logo_style1": "Logo style 1",
-      "logo_style2": "Logo style 2",
-      "logo_delete": "Delete the uploaded logo?"
-    },
-    "screen": {
-      "__name": "初始画面",
-      "current": "Current cover",
-      "current_set": "Set as scene cover",
-      "tips": "Drag the screen, click and save your scene cover."
-    },
-    "hotspot": {
-      "__name": "添加热点",
-      "add": "Add hotspot",
-      "edit": "Edit hotspot",
-      "count": "Added hotspots",
-      "location": "Hotspot Location",
-      "location_tips": "Drag the hotspot to another position.",
-      "location_modify": "Modify hotspot location",
-      "location_confirm": "Confirm hotspot location",
-      "location_desc": "Please drag the hot spot in the two scene areas on the left and align it to the desired point.",
-      "style": "Choose hotspot style",
-      "style_desc": "Choose the default style, or manually upload the picture to customize the style, upload the picture in PNG/JPG format",
-      "style_name": "Style",
-      "style_dele": "Sure to delete this style?",
-      "style_manage": "Manage",
-      "style_exit": "Quit",
-      "media_photo": "Add pictures to enrich hot content",
-      "media_video": "Upload local videos for more display",
-      "media_voice": "Upload local audio content for hotspot explanation",
-      "media_link": "You can add a hyperlink to the video, and the video will be played in the hotspot",
-      "title": "Title",
-      "title_tips": "Please enter a title",
-      "title_require": "Please add a title (max {limit} characters).",
-      "description": "Description",
-      "description_tips": "Please enter a description.",
-      "text_link": "Add a link",
-      "text_link_text_tips": "Please fill in the link text",
-      "text_link_href_tips": "Please fill in the link address",
-      "text_link_text_require": "Link text cannot be empty",
-      "text_link_href_require": "Link address cannot be empty",
-      "link": "Add a link",
-      "link_require": "Please add an external link",
-      "photo": "Photo",
-      "voice": "Audio",
-      "video": "Video",
-      "photo_tips": "Support JPG, PNG and other image formats",
-      "voice_tips": "Support MP3, WAV and other audio formats",
-      "video_tips": "Support MP4 format. <20M",
-      "photo_require": "Please add photos",
-      "voice_require": "Please add audio",
-      "video_require": "Please add videos",
-      "m_location_tips1": "Point split-screen hotspots above and below to the marked target",
-      "m_location_tips2": "Determine the hotspot location, click next to edit content",
-      "m_location_up": "Above",
-      "m_location_left": "Left side",
-      "m_location_move_tips": "The auxiliary calibration position may be inaccurate,<br>please check and drag to {direction} the same location",
-      "set_visible_btn": "Hotspot Visiblity",
-      "save_visible_btn": "Save",
-      "save_hotspot_done": "Successfully saved hotspot",
-      "save_hotspot_fail": "Failed to save hotspot",
-      "delete_hotspot_tips": "Do you want to delete the current hotspot?",
-      "delete_hotspot_done": "Successfully deleted hotspot",
-      "delete_hotspot_fail": "Failed to delete hotspot",
-      "cant_add_hotspot_tips": "Unable to add hotspot",
-      "cant_add_hotspot_content": "The number of hotspots has reached the maximum: {limit}",
-      "link_text_tips": "Link title",
-      "link_href_tips": "Link",
-      "link_text_require": "Please fill in the text",
-      "link_href_require": "Please fill in the link",
-      "add_media": "Add Content"
-    },
-    "guide": {
-      "__name": "自动导览",
-      "route": "Tour Route",
-      "view": "Switch View",
-      "record": "Start recording",
-      "record_audio": "Record",
-      "pause": "Pause",
-      "stop": "Stop",
-      "end": "End",
-      "delete": "Delete",
-      "continue": "Continue recording",
-      "preview": "View",
-      "clear": "Clear",
-      "sync": "Audiovisual synchronization",
-      "sound": "Record audio",
-      "file": "Upload audio",
-      "file_add": "Add audio",
-      "tips": "Click \"Start\" to record the tour.",
-      "start": "Start",
-      "finish": "Finish",
-      "less": "Less",
-      "replace_tips": "Do you want to start over the tour recording?",
-      "replace_content": "Redo the recording will overwrite previous data.",
-      "sound_open_fail_tips": "Failed to turn on the microphone",
-      "sound_tips": "Failed to turn on the microphone, continue recording?",
-      "sound_content": "Please allow this site to use the microphone in your browser settings and refresh the page.",
-      "upload_sound_done": "Upload sound explanation successfully",
-      "upload_sound_fail": "Failed to upload sound explanation",
-      "delete_sound_done": "Delete sound explanation successfully",
-      "delete_sound_fail": "Failed to delete sound explanation",
-      "room_title": "Title",
-      "room_title_tips": "Please enter the room title",
-      "room_title_require": "Please enter a title",
-      "room_panel_title": "Guide information",
-      "room_sound_title": "Record audio",
-      "delete_video_content": "Your current recording will be deleted",
-      "delete_file_content": "Your currently uploaded sound explanation will be deleted",
-      "delete_sound_content": "Your recorded audio will be deleted",
-      "camera_save_success": "Successfully saved camera",
-      "clear_video_tip": "Your current recording will be cleared",
-      "saving_sound": "Saving recording",
-      "save_sound_done": "Successfully saved recording",
-      "save_sound_fail": "Failed to save recording",
-      "no_sound_tips": "Currently does not support recordig.<br>Please record on WeChat or computer",
-      "sound_success_tips": "Successfully opened the microphone",
-      "sound_fail_tips": "Currently does not support recording.<br>Official account information configuration error",
-      "wechat_sound_fail_tips": "Failed to open WeChat microphone",
-      "open_sound_guide_tips": "If you want to record, please enable it in the WeChat settings-privacy-authorization management. And ensure that the recording equipment is normal",
-      "sound_cant_open_tips": "This browser does not support recording. <br>It is recommended to replace other mainstream browsers for a better experience",
-      "replace_sound_tips": "Re-record?",
-      "replace_sound_content": "Current sound explanation will be replaced",
-      "merge_sound_fail": "Fail to merge sound explanation",
-      "sound_limit": "The recording duration cannot exceed {time} minutes",
-      "select_to_record": "Please select the corresponding point to record the guide in this area"
-    },
-    "sign": {
-      "title": "Logo style",
-      "size": "Size",
-      "style0": "Style one",
-      "style1": "Style two",
-      "style2": "Style three",
-      "style3": "Upload"
-    },
-    "walk": {
-      "title": "Roaming Possibility",
-      "tips1": "Further optimize the experience during roaming by setting roaming possibility, such as cases in which one may penetrate through the walls. ",
-      "tips2": "Set the roaming possibility by clicking and setting the connection of each roaming point.",
-      "save": "Save current settings",
-      "hide": "Hide the spot",
-      "show": "Show the spot",
-      "pano_tips": "Tip: You hide the initial point, so you can't roam in this scene."
-    },
-    "model": {
-      "__name": "修整模型",
-      "cad": "CAD View",
-      "cad_download": "FloorPlan Download",
-      "view": "Switch View",
-      "title": "Add structure",
-      "title_door": "Doors and windows",
-      "title_component": "Components",
-      "title_other": "Others",
-      "attribute": "Attributes",
-      "door": "Door",
-      "slideDoor": "Slidedoor",
-      "casement": "Window",
-      "bayCase": "Bay Window",
-      "groundCase": "French Sash",
-      "column": "Column",
-      "furnColumn": "Frame Column",
-      "furnFlue": "Flue",
-      "point": "Point",
-      "line": "Interior Wall",
-      "tagging": "Tag",
-      "tagging_name_tips": "Tag name",
-      "tagging_area_tips": "Enter area",
-      "direction": "Compass",
-      "wallLine": "Interior wall",
-      "widget_delete": "{widget} will be deleted",
-      "panel_btn_default": "Reset",
-      "panel_btn_delete": "Delete",
-      "attr_angle": "Rotation angle",
-      "attr_within": "Flip direction",
-      "attr_ewidth": "Width",
-      "attr_eheight": "Height",
-      "attr_tick": "Tickness",
-      "attr_showTitle": "Tag name",
-      "attr_showContent": "Tag area",
-      "attr_top": "Top",
-      "attr_bottom": "Bottom",
-      "success": "Add successfully {widget}",
-      "error_location": "Current location cannot be added {widget}",
-      "error_outdoor": "Outdoor cannot be added {widget}",
-      "error_something": "Building at the current location cannot be added {widget}",
-      "error_widget": "Current location cannot be added {widget}",
-      "enter_adjust_floor": "Ground adjustment",
-      "exit_adjust_floor": "Exit adjustment mode",
-      "color_title": "Protractor Color:"
-    },
-    "videos": {
-      "__name": "添加视频",
-      "tips": "Please click on the reference plane to set the video position.",
-      "panel_title": "Video attributes",
-      "panel_preview": "Preview",
-      "panel_upload": "Upload",
-      "panel_upload_tips": "Support MP4 format",
-      "panel_move": "Move",
-      "panel_zoom": "Zoom",
-      "panel_thickness": "Tickness",
-      "recoverRatio": "Restore original aspect ratio",
-      "recoverRatioTip": "Restore original aspect ratio of the video"
-    },
-    "custom": {
-      "model_title": "Download model",
-      "uploading": "Uploading…",
-      "download": "Original model download",
-      "download_panos": "Download panos",
-      "upload": "Modified model upload",
-      "title1": "Model download/upload tutorial",
-      "tips11": "1.After downloading the ZIP package of the scene model, open the decompressed file”mesh.obj” to edit it.",
-      "tips12": "2.After the edit is completed, it is recommended to render the model texture. The render texture should be controlled within 1.5M while the saved obj files should be controlled within 3M.",
-      "tips13": "3.After completing steps 1 and 2, package the obj, mtl, and texture into a zip package and upload it.",
-      "title2": "Notes:",
-      "tips21": "1.Please control the file size, in order not to freeze when you are browsing which will affect your experience.",
-      "tips22": "2.Please use the same name in both obj and ZIP package, otherwise the replacement will fail.",
-      "get_image_fail": "Failed to get image. Please check your network settings and try again.",
-      "download_model_fail": "Fail to download model",
-      "reupload_tips": "Upload {type}",
-      "panoramic_upload": "Pano Upload",
-      "panoramic_upload_tips": "Panorama upload",
-      "panoramic_upload_box_tips": "Please upload JPG image<br>with corresponding point name.",
-      "panoramic_download": "Pano Download",
-      "panoramic_download_tips": "Panorama download",
-      "panoramic": "Panorama",
-      "ball_video": "Video",
-      "ball_video_upload_tips": "Video Upload",
-      "ball_video_download_tips": "Video Download",
-      "ball_video_upload_box_tips": "The panoramic video supports MP4 format<br>which should not exceed 1024M.",
-      "upload_title": "Upload files",
-      "upload_format_error": "File format error, please upload again",
-      "upload_name_error": "File name error, please upload again",
-      "upload_success": "The upload is successful and will take effect<br>after saving and publishing",
-      "upload_code_5017": "Failed to upload the model, <br>please refer tothe tutorial on the right",
-      "upload_code_5018": "The zip file can only have<br>one level of directory or no directory",
-      "upload_code_5019": "There must be only one obj and mtl file",
-      "upload_code_5020": "The texture needs to be controlled within 1.5M, and the obj file needs to be controlled within 3M",
-      "upload_code_5012": "The data is abnormal",
-      "upload_code_5023": "The upload file format is incorrect, <br>only jpg or mp4 format.",
-      "download_tips": "Download {type}",
-      "download_fail": "Failed to download"
-    },
-    "vrhouse": {
-      "__name": "VR看房",
-      "linkto_management": "Go to the VR house management background"
-    },
-    "business": {
-      "__name": "商圈模块",
-      "list_title": "Scene hotspot list"
-    },
-    "link": {
-      "__name": "跳转关联",
-      "title1": "Save the link",
-      "title2": "Edit the link",
-      "list_text": "Added links",
-      "btn_new_text": "Add a link",
-      "btn_add_title": "关联点定位",
-      "btn_add_text": "确定关联点位置",
-      "btn_add_desc": "将关联点标记并拖动到合适的位置。",
-      "btn_edit_text": "Adjust the link position",
-      "style": "Link icon",
-      "style_name": "icon",
-      "style_desc": "upport customized icon with recommended size of 100*100 pixels. Support jpg/png/gif format.",
-      "style_dele": "Delete this icon? <br /> The same icon used for other links will also be deleted.",
-      "desc_title": "Description",
-      "desc_tips": "Please enter a description.",
-      "desc_require": "Please enter a description.",
-      "type": "Choose the type of link",
-      "type_photo": "upport uploading panoramic pictures with an aspect ratio of 2:1, recommended pixel size should not be less than 6000 × 3000 pixels, and the file should not exceed 120M",
-      "type_photo_require": "Upload panoramic pictures.",
-      "type_link": "Please input the scene link.",
-      "type_link_tips": "http(s)://",
-      "type_link_require": "Please input the scene link.",
-      "enter_title": "Link cover",
-      "enter_require": "Please set a link cover",
-      "enter_btn_text": "Set a link cover",
-      "outer_title": "Exit spot position",
-      "outer_style": "Exit spot icon",
-      "outer_desc": "Support customized icon with recommended size of 100*100 pixels. Support jpg/png/gif format.",
-      "outer_require": "Please set the position of exit spot.",
-      "outer_btn_text": "Set the position of exit spot."
-    }
-  },
-  "login": {
-    "__name": "登录",
-    "title": "Message",
-    "login": "Log in",
-    "logon": "Log in and continue",
-    "login_tips": "You are not logged in. Please log in to edit."
-  },
-  "common": {
-    "__name": "公用",
-    "on": "on",
-    "off": "off",
-    "add": "Add",
-    "set": "OK",
-    "ok": "OK",
-    "save": "Save",
-    "cancel": "Cancel",
-    "complete": "Complete",
-    "edit": "Modify",
-    "giveup": "Give up",
-    "commit": "Commit",
-    "photo": "Photo",
-    "voice": "Audio",
-    "video": "Video",
-    "bgm": "BGM",
-    "crop": "Crop",
-    "upload": "Upload",
-    "download": "Download",
-    "change": "Change",
-    "unnamed": "Unnamed",
-    "publish": "Save and publish",
-    "publish_text": "Save and publish successfully!",
-    "publish_tips": "Check your scene immediately?",
-    "publish_btn_ok": "Now",
-    "publish_btn_no": "Later",
-    "public": "Public",
-    "private": "Private",
-    "waiting": "Waiting...",
-    "audio": "Audio",
-    "second": "s",
-    "frame": "frame",
-    "delete": "Delete",
-    "prev": "Go back",
-    "next": "Next",
-    "meter": "≈{meter}m",
-    "guide": "Tour Guide",
-    "rule": "Ruler",
-    "roaming": "Roaming",
-    "continue": "Continue",
-    "ensure_delete": "Delete",
-    "text_limit": "Limit within {limit} characters",
-    "default": "Default",
-    "custom": "Customize",
-    "back": "Back",
-    "will_delete": "Will be deleted",
-    "setup": "Set",
-    "exit": "Done",
-    "music": {
-      "__name": "背景音乐",
-      "none": "No music",
-      "cheerfu": "Cheerful",
-      "ethereal": "Ethereal",
-      "rhythmic": "Rhythmic",
-      "nostalgic": "Nostalgic",
-      "missing": "Missing",
-      "retro": "Retro",
-      "strings": "Strings",
-      "happy": "Happy"
-    },
-    "category": {
-      "__name": "分类",
-      "museum": "Museum",
-      "estate": "Real estate",
-      "eshop": "E-Commerce",
-      "catering": "Catering",
-      "home": "Home",
-      "other": "Other"
-    },
-    "uploads": {
-      "__name": "文件上传",
-      "uploading": "Uploading",
-      "uploaded": "Uploaded",
-      "wait": "Wait...",
-      "error": "Error",
-      "add": "Add",
-      "start": "Start",
-      "file_require": "Please add file",
-      "cant_upload": "Your browser does not support uploading files",
-      "not_support": "The file you selected is not {fileType} supported by the browser, please select again",
-      "too_large": "The file is too large and cannot be larger than {size} trillion",
-      "too_large_reupload_tips": "The uploaded video is too large, please upload again"
-    },
-    "tips": {
-      "__name": "提示",
-      "wait": "Please wait..."
-    }
-  },
-  "tips": {
-    "__name": "提示",
-    "title": "Tip",
-    "set_done": "Set up successfully",
-    "set_fail": "Set up failed",
-    "save_done": "Save successfully",
-    "save_fail": "Save failed",
-    "delete": "Delete or not?",
-    "delete_done": "Delete successfully",
-    "delete_fail": "Fail to delete",
-    "upload_done": "Upload successfully",
-    "upload_fail": "Failed to upload",
-    "exception": "Error",
-    "network_error": "The internet is disconnected, please try it again",
-    "file_notfound": "The file is not found",
-    "scene_notfound": "The scene is not found",
-    "params_notfound": "The parameter is not found",
-    "camera_notfound": "The matched camera is not found",
-    "password_error": "Incorrect password",
-    "data_error": "The data is not found",
-    "auth_deny": "Unathorized to operate the scene",
-    "clear": "Sure to clear?",
-    "upload_pic_fail": "Failed to upload pictures",
-    "wait": "Please waiting...",
-    "house_type_save_fail": "Failed to save house type"
-  },
-  "show": {
-    "__name": "展示页面",
-    "on": "On",
-    "off": "Off",
-    "measure": "Measuring tool",
-    "measure_start": "Start",
-    "measure_end": "End",
-    "measure_button": "Confirm {status}",
-    "location_up": "Above",
-    "location_left": "Left",
-    "location_start_tips": "Please click on {direction}",
-    "location_end_tips": "The starting point is determined, please locate the end point",
-    "vr": "VR mode",
-    "share": "Share it",
-    "music": "Turn {status} music",
-    "password_tips": "Password",
-    "password_require": "Please enter the password"
-  },
-  "components": {
-    "user_guid": {
-      "__name": "用户引导",
-      "title": "Operation Tips",
-      "pano": "Walk<br />Click to move.",
-      "rotate": "Roam<br />Swipe the screen to roam.",
-      "zoom": "Zoom<br />Zoom in or out.",
-      "set": "Got it",
-      "seeMore": "More Details",
-      "upgradeInfo": "Content"
-    }
-  },
-  "material": {
-    "works": {
-      "my": "My Project",
-      "create": "Create a project",
-      "search": "Keywords",
-      "preview": "View",
-      "edit": "Edit",
-      "share": "Share",
-      "delete": "Delete",
-      "no_works": "No results found, you do not yet have a project, please create one first~",
-      "work_link": "Project link",
-      "work_qrCode": "Project QR Code",
-      "download_qrCode": "Download the QR code",
-      "copy_link": "Copy link",
-      "work_preview": "View Project",
-      "new_blank": "Open in a new window",
-      "cancel": "Cancel",
-      "no_link": "Link not generated, please edit your project to include material",
-      "had_created": "You have created your project with success!",
-      "goto_preview": "View",
-      "continue_edit": "Continue to edit",
-      "had_created_but_no_link": "New project has been created successfully, but refreshing the project list failed; please try again later.",
-      "delete_work": "Delete project",
-      "comfirm_delete": "Confirm to delete the project?"
-    },
-    "components": {
-      "prev": "Previous",
-      "next": "Next",
-      "zoom_in": "Zoom in",
-      "zoom_out": "Zoom out",
-      "delete": "Delete",
-      "fullscreen": "Full screen",
-      "cancel_fullscreen": "Exit full screen",
-      "pano_setting": "Thumbnail Settings",
-      "drag_to_cut": "Drag the screen to have a screenshot",
-      "cutting": "Screenshot",
-      "preview_cover": "View",
-      "rename_material": "Rename the material"
-    }
-  },
-  "gather": {
-    "select_material": "Select the materials",
-    "no_title": "No title",
-    "name": "4DPano",
-    "editpage_name": "Editing platform",
-    "my_works": "My Project",
-    "my_material": "My Material",
-    "panorama": "Panorama",
-    "pano": "Panorama",
-    "image": "Pictures",
-    "audio": "Audio",
-    "video": "Video",
-    "scene": "3D Scene",
-    "siweikankan": "4DKanKan",
-    "siweikanjian": "4DMinion",
-    "keywords": "keywords",
-    "how_to_shoot": "How to shoot a 3D scene?",
-    "pano_size": "Support 2:1 jpg files ≤ 120MB",
-    "cancel": "Cancel",
-    "comfirm": "Confirm",
-    "pano_fail": "Format error. Support 2:1 jpg files ≤ 120MB",
-    "pano_limit": "The file is too large. Support 2:1 jpg files ≤ 120MB",
-    "edit_cover": "Thumbnail Settings",
-    "img_fail": "Format error.Support jpg/png/gif files: ≤ 10MB",
-    "img_limit": "The file is too large.Support jpg/png/gif files: ≤ 10MB",
-    "img_size": "Support jpg/png/gif files: ≤ 10MB",
-    "audio_size": "Support MP3 files: ≤ 20MB",
-    "audio_limit": "The file is too large. Support MP3 files: ≤ 20MB",
-    "audio_fail": "Format error. Support MP3 files: ≤ 20MB",
-    "upload_material": "Upload materials",
-    "new_folder": "New Folder",
-    "new_folder_placeholder": "Please enter",
-    "folder_name_already_used": "Folder already exists",
-    "move_folder": "Move",
-    "move_folder_to": "Move to",
-    "no_folder_need_create": "Please create a folder first",
-    "rename_folder_placeholder": "Please enter",
-    "video_size": "Support MP4 files: ≤ 200MB",
-    "video_limit": "The file is too large.Support MP4 files: ≤ 200MB",
-    "video_fail": "Format error. Support MP4 files: ≤ 200MB",
-    "search_material": "Keywords",
-    "rename": "Rename",
-    "delete": "Delete",
-    "no_search_result": "No results found",
-    "no_material_result": "No materials found",
-    "had_load": "{msg} pieces of data have been loaded",
-    "uploading": "Uploading {msg}",
-    "success": "Operation succeeded",
-    "delete_success": "Deletion succeeded",
-    "delete_fail": "Delete failed",
-    "loading": "Loading",
-    "too_long_word": "Title is too long; please upload an image with a 50-word or shorter title.",
-    "too_long_word_audio": "Title is too long; please upload an audio with a 50-word or shorter title",
-    "too_long_word_video": "Title is too long; please upload a video with a 50-word or shorter title",
-    "uploading_material": "Uploading material",
-    "cutting": "Cropping",
-    "material_upload_fail": "Failed to upload material",
-    "material_cutting_fail": "Failed to crop image",
-    "fill_complete": "Please fill in the complete information.",
-    "fill_phone": "Please fill in the phone number correctly",
-    "edit_success": "Modification succeeded",
-    "setting_success": "Setting succeeded",
-    "scene_link_copy_tips": "Copied",
-    "scene_link_copy_failed": "Copy failed",
-    "delete_material": "Delete material",
-    "comfirm_delete_material": "Confirm to delete the material?",
-    "can_not_delete_folder_when_uploading": "Please do not delete the folder while the material is being uploaded.",
-    "delete_folder": "Delete folder",
-    "comfirm_delete_folder": "Are you sure you wish to delete the folder and its contents?",
-    "save_done": "Save successfully",
-    "nothing_edit": "You haven't created any content yet",
-    "at_least_one_scene": "At least one scene can be previewed. Please go to \"Scene Navigation\" to add",
-    "exitVr": "Exit VR",
-    "dir": "Directory",
-    "root_dir": "Root Directory",
-    "no_more_data": "no more results.",
-    "converinfo_no_valid": "Please fill out the content for the opening cover."
-  },
-  "personal_center": {
-    "personal_center": "Personal Center",
-    "logout": "Log out",
-    "login": "Login",
-    "register": "Sign up"
-  },
-  "edit_page": {
-    "back_myworks": "Back",
-    "preview": "View",
-    "settings": "Basic Settings",
-    "navigation": "Navigation",
-    "viewpoint": "Starting Screen",
-    "hotspot": "Hotspots",
-    "explanation": "Voice Guide",
-    "save": "Save"
-  },
-  "edit_settings": {
-    "coverBase_button": "Opening Cover",
-    "coverBase_button_tips": "The opening content of the entire project may be presented as an image or video.",
-    "cover_show": "Show opening cover",
-    "cover_pull_tit": "Cover Type",
-    "coverSelecVideoAndImg": "Image + Video",
-    "coverSelecImg": "Image",
-    "coverSelecVideo": "Video",
-    "coverImgTit": "Image Settings",
-    "coverUpTit1": "1920*1080 pixels, within 2M, jpg/gif/png format is recommended",
-    "coverUpTit2": "750*1624 pixels ,within 1M, jpg/gif/png format is recommended",
-    "coverUpTit3": "300*300 pixels, within 100kb, jpg format is recommended",
-    "coverUpTit4": "1920*1080 pixels, within 5M, MP4 format is recommended",
-    "coverUpTit5": "750*1624 pixels, within 5M, MP4 format is recommended",
-    "coverImgLoc1": "Center",
-    "coverImgLoc2": "Full",
-    "coverImgBacTit": "Background Settings",
-    "coverImgBacSelec1": "Fill Color",
-    "coverImgBacSelec2": "Background-repeat",
-    "coverVideoTit": "Video Settings",
-    "select_video": "Select Video",
-    "coverImageInWay": "Entry Method",
-    "coverImageInWayTit": "Automatic entry after 3 seconds countdown",
-    "coverVideoInWay": "Entry Method",
-    "coverVideoInWayTit": "Auto-entry after video playback",
-    "coverVideoControl": "Video Control",
-    "coverVideoControlTit": "Display the video control or not",
-    "coverImageOrder": "Displaying Order",
-    "coverImageOrderTit1": "Images will appear before the video begins to play",
-    "coverImageOrderTit2": "Images will appear after the video begins to play",
-    "auto_pano": "Rotation",
-    "enter_auto": "Enter the rotation mode (takes 3 mins to complete per rotation)",
-    "set_bgm": "Set the BGM",
-    "add_audio": "Add the audio",
-    "change_audio": "Change the audio",
-    "custom_button": "Custom Button",
-    "custom_button_tips": "Custom buttons provide contact info, website URLs, etc. to a project, which can subsequently be made visible and displayed as well.",
-    "edit": "Edit",
-    "hide": "Hide",
-    "show": "Display",
-    "button_name": "Button name",
-    "button_type": "Button type",
-    "button_open_method": "Open methods",
-    "button_placeholder": "Please enter the button name",
-    "please_input": "Please enter",
-    "phone": "Phone",
-    "link": "URL Address",
-    "phone_short": "Tel",
-    "link_short": "Link",
-    "custom_logo": "Custom Logo",
-    "show_logo": "Display Logo",
-    "logo_size": "300*300 pixels, within 600kb, <br>jpg/png/gif format support",
-    "sky_mask": "Top mask",
-    "sky_tips": "The top mask is exhibited at the scene's top, while the bottom mask is presented at the scene's bottom.",
-    "mask_setting": "Mask Settings",
-    "mask_size": "500*500 pixels, <br>jpg/png/gif format support",
-    "bottom_mask": "Bottom mask",
-    "opening_setting": "Set the animations",
-    "opening_animation_show": "Switch the animations",
-    "opening_tips_setting": "Notifications Settings",
-    "opening_tips": "The notifications only apply to panoramic images. The following notifications are inapplicable if the start screen is a 3D model.",
-    "pc": "PC",
-    "mobile": "Mobile",
-    "select_image": "Select pictures",
-    "opening_size": "Recommend 300*300 pixels, within 600kb, jpg/png/gif format support",
-    "show_setting": "Display settings",
-    "first_notice": "Prompt only when the link is opened for the first time",
-    "password_setting": "Set an access code",
-    "password_placeholder": "Please enter the access password, limited to 20-character",
-    "base_setting": "Basic Settings",
-    "setting_cover": "Thumbnail Settings",
-    "cover_size": "512*512px, support jpg/png/gif format",
-    "title": "Title",
-    "description": "Description",
-    "work_placeholder": "Please enter the project title",
-    "intro_placeholder": "Please input no more than 2000 words of text content",
-    "help_center": "Help Center",
-    "display_time": "Display duration",
-    "no_display": "(No display)",
-    "second": " Sec"
-  },
-  "table": {
-    "material": "Material",
-    "name": "Title",
-    "fileSize": "Size",
-    "detail": "",
-    "dpi": "Resolution",
-    "createTime": "Created",
-    "updateTime": "Modified"
-  },
-  "baseSetting": {
-    "电话": "Tel",
-    "链接": "Link",
-    "弹出层打开": "Pop-up Notification",
-    "新窗口打开": "New Tab",
-    "opa1": "Start with asteroid",
-    "opa2": "Start with asteroid cruise",
-    "opa3": "Start with asteroid zoom",
-    "opa4": "Start with horizontal cruise",
-    "opa5": "Start with crystal ball",
-    "openTips": "Notifications",
-    "openAnimate": "Animations",
-    "password": "Access Code",
-    "cruise": "Rotation",
-    "bgm": "BGM",
-    "logo": "Custom Logo",
-    "customCover": "Custom Mask",
-    "customButton": "Custom Button",
-    "openCover": "Opening Cover",
-    "素材": "Material",
-    "名称": "Title",
-    "大小": "Size",
-    "分辨率": "Resolution",
-    "创建时间": "Created",
-    "修改时间": "Modified",
-    "封面": "Cover",
-    "场景标题": "Scene Title",
-    "拍摄时间": "Shooting Time",
-    "一级分组": "1st Grouping",
-    "二级分组": "2nd Grouping",
-    "默认二级分组": "Default as 2nd grouping"
-  },
-  "customButton": {
-    "phone": "Tel",
-    "link": "Link",
-    "self": "Pop-up Notification",
-    "target": "New Tab"
-  },
-  "navigation": {
-    "scene_edit_tips": "Please direct to the 4Dage Personal Center scene editing platform",
-    "go_scene_editor": "Get started now",
-    "scene_navigation": "Navigation",
-    "add_group": "Add a grouping",
-    "scene_tips": "You can customize the grouping and ordering of scene material, which includes panorama and 3D scenes",
-    "add_two_group": "Add 2nd Grouping",
-    "add_pano_or_scene": "Add panorama or 3D scene",
-    "group_one": "1st Grouping",
-    "group_two": "2nd Grouping",
-    "default_group_two": "Default as 2nd grouping",
-    "init_scene": "Initial Scene",
-    "setting_init_scene": "Initial scene settings",
-    "init_scene_tips": "The initial scene is the enter screen when a link is viewed, and it will not applied to a specific scene when \n is not set.",
-    "edit_init_scene": "Edit",
-    "delete_init_scene": "Delete",
-    "keep_one_scene": "Please retain at least one scene",
-    "keep_one_group": "Please retain at least one grouping",
-    "enter_name": "Please enter the project title",
-    "rename": "Rename",
-    "delete": "Delete",
-    "pano": "Panorama",
-    "scene": "3D Scene",
-    "scene_name": "Scene",
-    "already_exists": "{msg} already exists, can not be repeatedly added"
-  },
-  "screen": {
-    "init_screen": "Start Screen",
-    "screen_tips": "The start screen is the initial screen that appears when entering a scene; please drag the panorama to select the appropriate screen setting.",
-    "setting_screen": "Set the current view to the start screen",
-    "goto_4dkk_edit_tips": "4Dage Personal Center~"
-  },
-  "hotspot": {
-    "hotspot_type": "Hotspots Style",
-    "img_size": "Upload up to 20 images",
-    "hotspot_tips": "Add icon hotspots to the panorama and configure their effect",
-    "add_hotspot": "Add hotspot",
-    "current_hotspots": "Current Pano Hotspots",
-    "delete": "Delete",
-    "add": "Add",
-    "edit": "Edit",
-    "success": "Success",
-    "click_to_comfirm": "Click to determine the location of hotspots",
-    "no_hotspot": "No hotspot information at this time~",
-    "close_dialog": "Hotspot content is not fully edited, confirm to close it",
-    "hotspot_name": "Hotspots",
-    "hotspot_icon": "Select Icon",
-    "hotspot_icon_type": {
-      "system_icon": "Default icon",
-      "custom_image": "Custom images",
-      "serial_frame": "Sequential frames",
-      "personalized_tag": "Custom labels"
-    },
-    "add_icon": "Add Icon",
-    "add_custom_icon_rule": "300*300 pixels, jpg/png/gif format is recommended",
-    "add_serial_frame_rule": "Maximum width: 300 pixels, unlimited height",
-    "frame_total_number": "Total Frames",
-    "frame_duration": "Total Play Time",
-    "select_icon": "Change",
-    "if_show_marking_line": "Display dimension line",
-    "shape_filling_color": "Shape Color",
-    "shape_border_color": "Shape Stroke",
-    "text_color": "Text Color",
-    "text_direction": "文字排序",
-    "left_to_right": "从左到右",
-    "top_to_down": "从上到下",
-    "if_wrap": "是否换行显示",
-    "text_num_per_line": "每行显示",
-    "words": "字",
-    "hotspot_title": "Title",
-    "show_hotspot_title":"Show Title",
-    "title_show_mode": "Display Methods",
-    "show_on_hover": "Hovering",
-    "always_show": "Show",
-    "never_show": "Hide",
-    "title_position": "Relative Position",
-    "title_top": "Up",
-    "title_bottom": "Down",
-    "title_left": "Left",
-    "title_right": "Right",
-    "title_custom": "自定义拖动标题",
-    "title_placeholder": "Enter a title",
-    "effect_settings": "Effect settings",
-    "cancel": "Cancel",
-    "finish": "Finish",
-    "scene": "Scene",
-    "image": "Image",
-    "video": "Video",
-    "audio": "Audio",
-    "link": "Hyperlink",
-    "textarea": "Text",
-    "tag": "Label",
-    "image_text": "Image&text",
-    "article": "Paragraph",
-    "pdf": "PDF",
-    "phone": "Tel",
-    "hotspot_type_specific_settings_title": {
-      "scene": "Scene",
-      "image": "Image",
-      "video": "Video",
-      "audio": "Audio",
-      "link": "Link",
-      "textarea": "Text",
-      "tag": "",
-      "imageText": "Image&text",
-      "article": "Paragraph",
-      "pdf": "PDF",
-      "phone": "Tel"
-    },
-    "icon_size": "Icon Size",
-    "unit": "times",
-    "add_audio": "Add audio",
-    "change_audio": "Change the audio",
-    "select_audio": "Select audio",
-    "add_image": "Add images",
-    "add_tooltips": "Add",
-    "select_image": "Select images",
-    "popup": "Pop-up Notification",
-    "newTab": "New Tab",
-    "add_scene": "Add a scene",
-    "change_scene": "Change a scene",
-    "text_placeholder": "Please enter no more than 2000 words of text.",
-    "add_video": "Add a video",
-    "select_video": "Add a video",
-    "change_video": "Change a video",
-    "MB_limit": "Within {value}MB",
-    "text_content": "Text",
-    "apply_to_all": "应用到所有",
-    "phone_placeholder": "Please enter",
-    "phone_error_tip": "Please enter the correct telephone number",
-    "select_pdf": "Add a PDF",
-    "change_pdf": "Change the PDF",
-    "pdf_invalid_tip": "Please upload PDF files within 50M",
-    "add_article": "Add Paragraph",
-    "edit_article_title": "Edit paragraph",
-    "edit_article": "Edit paragraph",
-    "edit_text_title": "Edit text",
-    "edit_text": "Edit text Content",
-    "cancel_add_hotspot": "The edited content will not be saved, confirm to close it"
-  },
-  "explanation": {
-    "explanation_settings": "Voice Guide",
-    "explanation_tips": "You can add voice guide for the current panorama.",
-    "add_audio": "Add audio",
-    "change_audio": "Change audio",
-    "default_open": "Enable",
-    "loop": "Loop",
-    "disabled_tip": "三维场景暂不支持添加讲解"
-  },
-  "style_key": {
-    "menu_width": "88px",
-    "play_width": "31"
-  },
-  "tips_code": {
-    "FAILURE_2020": "appId is absent",
-    "FAILURE_2021": "The appId was wrongly parsed",
-    "FAILURE_5001": "token is empty",
-    "FAILURE_5002": "redis token does not exist",
-    "FAILURE_5003": "token invalid(invalid)",
-    "FAILURE_3001": "Object does not exist",
-    "FAILURE_3011": "No operation authorization",
-    "FAILURE_3020": "Empty file",
-    "FAILURE_3021": "Illegal file",
-    "FAILURE_3022": "Upload file exceeds maximum",
-    "FAILURE_3023": "Illegal format",
-    "FAILURE_3025": "Upload error",
-    "FAILURE_3101": "The referenced material cannot be erased",
-    "FAILURE_3102": "The computation scene cannot be erased",
-    "FAILURE_3103": "Server someData.json file does not exist",
-    "FAILURE_3200": "Failed to request for third-party API",
-    "FAILURE_3201": "Failed to request the interface",
-    "FAILURE_3104": "The folder with the same name has already existed, <br/> please move the material directly",
-    "FAILURE_error": "Network exception, please try again later",
-    "tips": "Tips",
-    "relogin": "Invalid login status; please log in again",
-    "goto_login": "Go to login",
-    "login_success": "Login completed, please continue",
-    "need_one": "Please keep at least one scene",
-    "not_less_than": "File title may not exceed 50 characters in length",
-    "work_had_delete": "The project has been deleted and cannot be edited",
-    "material_can_not_delete": "The material cannot be removed because it has been cited.",
-    "illegality_image": "This image is not supported",
-    "password_error": "Password error",
-    "FAILURE_3024": "Storage is at capacity",
-    "loading_fail": "Failed to load"
-  }
-}

+ 0 - 963
packages/qjkankan-editor/src/lang/_zh.json

@@ -1,963 +0,0 @@
-{
-  "menu": {
-    "__name": "菜单",
-    "music": "背景音乐",
-    "base": "基础设置",
-    "information": "场景信息",
-    "screen": "初始画面",
-    "hotspot": "添加热点",
-    "guide": "自动导览",
-    "sign": "地面Logo",
-    "walk": "漫游可行",
-    "model": "修整模型",
-    "custom": "上传下载",
-    "videos": "添加视频",
-    "vrhouse": "VR看房",
-    "business": "商圈模块",
-    "scene": "场景跳转",
-    "video": "添加视频",
-    "decor": "一键换装",
-    "link": "场景关联"
-  },
-  "modules": {
-    "__name": "模块",
-    "base": {
-      "__name": "基础设置",
-      "qrcode": "场景二维码",
-      "qrcode_download": "下载二维码",
-      "qrcode_tips": "自定义logo",
-      "scene_link": "场景地址",
-      "scene_link_copy": "复制链接",
-      "scene_link_copy_tips": "复制成功",
-      "bgm": "背景音乐",
-      "pano_text": "漫游视角可视",
-      "mode_2d_text": "平面图可视",
-      "mode_3d_text": "三维模型可视",
-      "map_text": "小地图预览可视",
-      "vr_text": "VR模式可视",
-      "vr_tips": "请在手机展示页面观看VR效果",
-      "guide_text": "自动导览可视",
-      "rule_text": "标尺可视",
-      "cad_text": "俯视图户型可视",
-      "measure_text": "测量工具可视",
-      "measure_tips": "编辑模式下无法使用测距功能,请在展示页面操作",
-      "turned_vr": "VR功能已{status}",
-      "turned_map": "小地图功能已{status}",
-      "turned_cad": "俯视图户型功能已{status}",
-      "turned_m2d": "平面视角功能已{status}",
-      "turned_m3d": "三维视角功能已{status}",
-      "turned_pano": "漫游视角功能已{status}",
-      "turned_rule": "标尺功能已{status}",
-      "turned_guide": "自动导览功能已{status}",
-      "turned_measure": "测距功能已{status}",
-      "shortcut_copy": "一键复制",
-      "share_link": "分享链接给好友",
-      "measure_show_tips": "请在展示页面使用测距功能",
-      "delete_measure_line": "删除测量线",
-      "please_click_tips": "'请点击“允许”'",
-      "vr_fail_app_tips": "浏览器未能检测到转动。请在手机或浏览器设置中开启了运动和方向访问等设置,然后刷新此页面。",
-      "vr_fail_safari_tips": "浏览器未能检测到转动。为完整体验VR效果,请打开 “设置” > “Safari” > “隐私和安全” 下的 “运动和方向访问” 开关,然后刷新此页面。",
-      "loading_bottom_text": "四维时代提供技术支持",
-      "vr_fail_reopen_tips": "运动和方向访问失败。您需要完全关闭此应用,然后再次打开,并允许访问运动与方向",
-      "add_music_title": "添加背景音乐",
-      "add_music_tips": "支持MP3、WAV等音频格式,不超过5MB",
-      "re_add_music": "重新添加",
-      "re_add_title": "重新添加背景音乐",
-      "re_add_tips": "新添加的音乐会替换已添加的音乐,<br>确定继续添加吗?",
-      "re_add_mobile_tips": "重新添加将会覆盖已添加音乐",
-      "delete_tips": "您确定删除当前音乐吗?",
-      "delete_title": "删除背景音乐",
-      "bgm_empty_tips": "请选择背景音乐",
-      "wechat": "微信",
-      "friend_circle": "朋友圈"
-    },
-    "information": {
-      "__name": "场景信息",
-      "title": "标题",
-      "title_tips": "请填写标题",
-      "title_require": "请添加标题({limit}字以内)",
-      "": "简介",
-      "description_tips": "请填写简介",
-      "link": "添加链接",
-      "link_text_tips": "请填写链接文本",
-      "link_href_tips": "请填写链接地址",
-      "link_text_require": "请填写链接文本",
-      "link_href_require": "请填写链接地址",
-      "classify": "分类",
-      "upload_time": "上传时间",
-      "record": "未记录",
-      "shoot_count": "拍摄数量",
-      "password": "访问密码",
-      "password_tips": "访问密码",
-      "password_desc": "设置完密码后,当其他人访问您的场景时,需要输入您设置的密码才能访问。如无需设置点击“公开”即可。",
-      "password_require": "请输入{limit}位数的密码",
-      "logo_edit": "编辑页面Logo",
-      "logo_exit": "退出页面Logo编辑",
-      "logo_show_bottom": "显示初始Logo",
-      "logo_style1": "顶部Logo-方",
-      "logo_style2": "顶部Logo-长",
-      "logo_delete": "是否删除已上传Logo?"
-    },
-    "screen": {
-      "__name": "初始画面",
-      "current": "当前初始视角",
-      "current_set": "设置为初始画面",
-      "tips": "移动屏幕,点击保存您的初始画面。"
-    },
-    "hotspot": {
-      "__name": "添加热点",
-      "add": "添加热点",
-      "edit": "编辑热点",
-      "count": "已添加热点",
-      "location": "热点定位",
-      "location_tips": "将热点标记并拖动到合适的位置。",
-      "location_modify": "修改热点位置",
-      "location_confirm": "确定热点位置",
-      "location_desc": "请于左方两个场景区域拖动热点并对准所需标记的位置。",
-      "style": "选择热点样式",
-      "style_desc": "选择默认样式,或者手动上传图片自定义样式,上传图片格式PNG/JPG",
-      "style_name": "样式",
-      "style_dele": "是否删除该样式?",
-      "style_manage": "管理",
-      "style_exit": "退出",
-      "media_photo": "可添加图片以丰富热点内容",
-      "media_video": "可上传本地视频,进行更多的展示",
-      "media_voice": "可上传本地音频内容进行热点讲解",
-      "media_link": "可添加视频的超链接,视频将在热点里播放",
-      "title": "标题",
-      "title_tips": "请填写标题",
-      "title_require": "请添加标题({limit}字以内)",
-      "description": "简介",
-      "description_tips": "请填写简介",
-      "text_link": "添加链接",
-      "text_link_text_tips": "请填写链接文本",
-      "text_link_href_tips": "请填写链接地址",
-      "text_link_text_require": "链接文本不能为空",
-      "text_link_href_require": "链接地址不能为空",
-      "link": "嵌入式链接",
-      "link_require": "请添加外链",
-      "photo": "图片",
-      "voice": "音频",
-      "video": "视频",
-      "photo_tips": "支持JPG、PNG等图片格式,不超过{size}MB",
-      "voice_tips": "支持MP3、WAV等音频格式,不超过{size}MB",
-      "video_tips": "支持MP4、MOV等视频格式,不超过{size}MB",
-      "photo_require": "请添加图片",
-      "voice_require": "请添加音频",
-      "video_require": "请添加视频",
-      "m_location_tips1": "将上下分屏热点对准同一个所标记目标",
-      "m_location_tips2": "确定热点位置,点击下一步编辑内容",
-      "m_location_up": "上方",
-      "m_location_left": "左侧",
-      "m_location_move_tips": "辅助校准位置可能不准确,<br>请检查并拖动到{direction}相同位置",
-      "set_visible_btn": "设置热点可视",
-      "save_visible_btn": "保存当前设置",
-      "save_hotspot_done": "保存热点成功",
-      "save_hotspot_fail": "保存热点失败",
-      "delete_hotspot_tips": "是否删除当前热点?",
-      "delete_hotspot_done": "热点删除成功",
-      "delete_hotspot_fail": "热点删除失败",
-      "cant_add_hotspot_tips": "无法添加热点",
-      "cant_add_hotspot_content": "热点数目已达最大:{limit}",
-      "link_text_tips": "链接标题",
-      "link_href_tips": "链接",
-      "link_text_require": "请填写链接文本",
-      "link_href_require": "请填写链接地址",
-      "add_media": "添加多媒体"
-    },
-    "guide": {
-      "__name": "自动导览",
-      "route": "导航路线",
-      "view": "切换视角",
-      "record": "开始录制",
-      "record_audio": "录音",
-      "pause": "暂停",
-      "stop": "停止",
-      "end": "结束",
-      "delete": "删除",
-      "continue": "继续录制",
-      "preview": "查看",
-      "clear": "清空",
-      "sync": "声画同步",
-      "sound": "录制音频",
-      "file": "上传音频",
-      "file_add": "添加音频",
-      "tips": "点击开始录制导览",
-      "start": "开始",
-      "finish": "完成录制",
-      "less": "小于",
-      "replace_tips": "是否重新录制导览?",
-      "replace_content": "重新录制将覆盖之前的数据",
-      "sound_open_fail_tips": "麦克风开启失败",
-      "sound_tips": "麦克风开启失败,是否继续录制?",
-      "sound_content": "您需要在浏览器的设置中允许此网站使用麦克风,并且添加麦克风设备,然后刷新该页面。",
-      "upload_sound_done": "上传语音讲解成功",
-      "upload_sound_fail": "上传语音讲解失败",
-      "delete_sound_done": "删除语音讲解成功",
-      "delete_sound_fail": "删除语音讲解失败",
-      "room_title": "标题",
-      "room_title_tips": "请输入导览标题",
-      "room_title_require": "请输入标题",
-      "room_panel_title": "导览信息",
-      "room_sound_title": "录制音频",
-      "delete_video_content": "您当前录制的画面将会被删除",
-      "delete_file_content": "您当前上传的语音讲解将会被删除",
-      "delete_sound_content": "已录制配音将会被删除",
-      "camera_save_success": "镜头保存成功",
-      "clear_video_tip": "您当前录制的内容将会被清空",
-      "saving_sound": "正在保存录音",
-      "save_sound_done": "录音保存成功",
-      "save_sound_fail": "录音保存失败",
-      "no_sound_tips": "当前不支持录音<br>可在微信或电脑端录音",
-      "sound_success_tips": "麦克风开启成功",
-      "sound_fail_tips": "当前不支持录音<br>公众号信息配置错误",
-      "wechat_sound_fail_tips": "微信麦克风开启失败",
-      "open_sound_guide_tips": "可能您若要开启录音,请于微信的设置—隐私—授权管理中开启。且保证录音设备正常",
-      "sound_cant_open_tips": "此浏览器不支持录音。<br>建议更换其他主流浏览器,体验更佳",
-      "replace_sound_tips": "是否重新录制?",
-      "replace_sound_content": "已有语音讲解将会被替换",
-      "merge_sound_fail": "合并语音讲解失败",
-      "sound_limit": "录制时长为{time}分钟,当前已经达到上限",
-      "select_to_record": "请选择对应点位进行本区域的导览录制"
-    },
-    "sign": {
-      "title": "Logo样式",
-      "size": "Logo大小",
-      "style0": "样式一",
-      "style1": "样式二",
-      "style2": "样式三",
-      "style3": "手动上传"
-    },
-    "walk": {
-      "title": "漫游可行",
-      "tips1": "通过设置漫游可行,进一步优化在漫游时出现的体验;例如,您在漫游时,出现穿透房间的情况。",
-      "tips2": "通过点选各个漫游点的连线即可设置漫游点的可行性。",
-      "save": "保存当前设置",
-      "hide": "隐藏该点位置",
-      "show": "显示该点位置",
-      "pano_tips": "提示:您隐藏了初始画面点位,此操作将使进入场景后无法漫游。"
-    },
-    "model": {
-      "__name": "修整模型",
-      "cad": "CAD视角",
-      "cad_download": "平面图下载",
-      "view": "切换视角",
-      "title": "增添结构",
-      "title_door": "门窗类",
-      "title_component": "构建类",
-      "title_other": "其他",
-      "attribute": "属性",
-      "door": "门",
-      "slideDoor": "移门",
-      "casement": "窗",
-      "bayCase": "飘窗",
-      "groundCase": "落地窗",
-      "column": "柱子",
-      "furnColumn": "框架柱",
-      "furnFlue": "烟道",
-      "point": "点",
-      "line": "墙",
-      "tagging": "标注",
-      "tagging_name_tips": "请输入名称",
-      "tagging_area_tips": "输入面积,支持小数点后面两位",
-      "direction": "指南针",
-      "wallLine": "墙关联房间",
-      "widget_delete": "{widget}将被删除",
-      "panel_btn_default": "恢复默认",
-      "panel_btn_delete": "删除部件",
-      "attr_angle": "旋转角度",
-      "attr_within": "翻转方向",
-      "attr_ewidth": "宽度",
-      "attr_eheight": "高度",
-      "attr_tick": "厚度",
-      "attr_showTitle": "标注名称",
-      "attr_showContent": "标注面积",
-      "attr_top": "顶部",
-      "attr_bottom": "底部",
-      "success": "成功添加{widget}",
-      "error_location": "当前位置无法添加{widget}",
-      "error_outdoor": "户外无法添加{widget}",
-      "error_something": "当前位置点有建筑,无法添加{widget}",
-      "error_widget": "当前位置不可添加{widget}",
-      "enter_adjust_floor": "进入地面高度调节模式",
-      "exit_adjust_floor": "退出地面高度调节模式",
-      "color_title": "量角器颜色:"
-    },
-    "videos": {
-      "__name": "添加视频",
-      "tips": "请先点击场景的基准面,确定视频位置",
-      "panel_title": "视频属性",
-      "panel_preview": "预览",
-      "panel_upload": "上传视频",
-      "panel_upload_tips": "支持MP4视频格式",
-      "panel_move": "位移",
-      "panel_zoom": "缩放",
-      "panel_thickness": "厚度",
-      "recoverRatio": "恢复原始比例",
-      "recoverRatioTip": "恢复视频文件原始长宽比"
-    },
-    "custom": {
-      "model_title": "模型下载",
-      "uploading": "文件上传中...",
-      "download": "原始模型下载",
-      "download_panos": "下载点位图",
-      "upload": "修改模型上传",
-      "title1": "模型下载/上传功能教程",
-      "tips11": "1.下载场景模型的压缩包后,使用三维软件打开解压后文件“mesh.obj”,即可开展编辑;",
-      "tips12": "2.编辑完成后,建议将模型贴图进行烘焙,烘焙贴图需控制在1.5M以内,同时,保存的obj文件需要控制在3M以内;",
-      "tips13": "3.完成1、2步骤后,将obj,mtl,贴图打包为zip压缩包上传即可。",
-      "title2": "注:",
-      "tips21": "1.请尽量控制文件大小,以免浏览的时候卡顿,影响体验。",
-      "tips22": "2.请将obj及压缩包的名称保持一致,否则会替换失败。",
-      "get_image_fail": "获取贴图失败,可能网络状态不佳,请检查您的网络设置并重新尝试。",
-      "download_model_fail": "模型下载失败",
-      "reupload_tips": "上传后点击保存并发布生效",
-      "panoramic_upload": "全景照片上传",
-      "panoramic_upload_tips": "上传后点击“保存并发布”即生效",
-      "panoramic_upload_box_tips": "请上传对应点位名称的JPG图片",
-      "panoramic_download": "全景照片下载",
-      "panoramic_download_tips": "请勿修改照片名称和格式",
-      "panoramic": "全景图片",
-      "ball_video": "球幕视频",
-      "ball_video_upload_tips": "上传后点击“保存并发布”即生效",
-      "ball_video_download_tips": "请勿修改视频名称和格式",
-      "ball_video_upload_box_tips": "球幕视频支持MP4格式,不超过1024M",
-      "upload_title": "上传文件",
-      "upload_format_error": "文件格式错误,请重新上传",
-      "upload_name_error": "文件名称错误,请重新上传",
-      "upload_success": "上传成功,保存并发布后才能生效",
-      "upload_code_5017": "上传模型失败,请参照右侧教程",
-      "upload_code_5018": "zip文件只能有一层目录或无目录",
-      "upload_code_5019": "必须有且仅有一个obj和mtl文件",
-      "upload_code_5020": "贴图需控制在1.5M以内,obj文件需要控制在3M以内。",
-      "upload_code_5012": "数据不正常",
-      "upload_code_5023": "上传文件格式不正确,只能是jpg或mp4格式",
-      "download_tips": "下载后名称与格式请勿修改",
-      "download_fail": "下载失败"
-    },
-    "vrhouse": {
-      "__name": "VR看房",
-      "linkto_management": "前往云看房管理后台"
-    },
-    "business": {
-      "__name": "商圈模块",
-      "list_title": "场景热点列表"
-    },
-    "link": {
-      "__name": "跳转关联",
-      "title1": "锁定关联点",
-      "title2": "编辑关联点",
-      "list_text": "已添加关联点",
-      "btn_new_text": "添加关联点",
-      "btn_add_title": "关联点定位",
-      "btn_add_text": "确定关联点位置",
-      "btn_add_desc": "将关联点标记并拖动到合适的位置。",
-      "btn_edit_text": "修改关联点位置",
-      "style": "关联点样式",
-      "style_name": "样式",
-      "style_desc": "支持自定义图标,建议尺寸为100x100 像素,格式为jpg/png/gif。",
-      "style_dele": "是否删除该样式?<br />如该样式在其它关联点使用,也将被删除",
-      "desc_title": "描述",
-      "desc_tips": "请输入描述内容",
-      "desc_require": "请输入描述内容",
-      "type": "选择关联类型",
-      "type_photo": "支持上传宽高比为2:1的单张全景图片,建议像素不小于6000x3000像素,文件不超过120M。",
-      "type_photo_require": "请上传全景图",
-      "type_link": "请输入目标场景链接",
-      "type_link_tips": "http(s)://",
-      "type_link_require": "请输入场景链接",
-      "enter_title": "进入画面",
-      "enter_require": "请设置进入画面",
-      "enter_btn_text": "设置进入画面",
-      "outer_title": "跳出点位置",
-      "outer_style": "跳出点样式",
-      "outer_desc": "支持自定义图标,建议尺寸为100x100像素,格式为jpg/png/gif。",
-      "outer_require": "请设置跳出点位置",
-      "outer_btn_text": "设置跳出点位置"
-    }
-  },
-  "login": {
-    "__name": "登录",
-    "title": "提示",
-    "login": "去登录",
-    "logon": "登录完毕,继续",
-    "login_tips": "您没有登录,请于主页登录后再编辑"
-  },
-  "common": {
-    "__name": "公用",
-    "on": "打开",
-    "off": "关闭",
-    "add": "添加",
-    "set": "确定",
-    "ok": "确定",
-    "save": "保存",
-    "cancel": "取消",
-    "complete": "完成",
-    "edit": "修改",
-    "giveup": "取消",
-    "commit": "提交",
-    "photo": "图片",
-    "voice": "音频",
-    "video": "视频",
-    "bgm": "音乐",
-    "crop": "裁剪",
-    "upload": "上传",
-    "download": "下载",
-    "change": "更改",
-    "unnamed": "未命名",
-    "publish": "保存并发布",
-    "publish_text": "保存并发布成功!",
-    "publish_tips": "是否立刻前往观看您的场景?",
-    "publish_btn_ok": "立刻前往",
-    "publish_btn_no": "暂时不去",
-    "public": "公开",
-    "private": "加密",
-    "waiting": "请稍等...",
-    "audio": "音频",
-    "second": "秒",
-    "frame": "帧",
-    "delete": "删除",
-    "prev": "上一步",
-    "next": "下一步",
-    "meter": "约{meter}米",
-    "guide": "导览",
-    "rule": "测量工具",
-    "roaming": "漫游",
-    "continue": "继续",
-    "ensure_delete": "确定删除",
-    "text_limit": "限制{limit}字内",
-    "default": "默认",
-    "custom": "自定义",
-    "back": "返回",
-    "will_delete": "将被删除",
-    "setup": "设置",
-    "exit": "退出",
-    "music": {
-      "__name": "背景音乐",
-      "none": "无",
-      "cheerfu": "欢快",
-      "ethereal": "空灵",
-      "rhythmic": "节奏",
-      "nostalgic": "怀旧",
-      "missing": "想念",
-      "retro": "复古",
-      "strings": "琴弦",
-      "happy": "愉快"
-    },
-    "category": {
-      "__name": "分类",
-      "museum": "文博",
-      "estate": "地产",
-      "eshop": "电商",
-      "catering": "餐饮",
-      "home": "家居",
-      "other": "其他"
-    },
-    "uploads": {
-      "__name": "文件上传",
-      "uploading": "上传中",
-      "uploaded": "已上传",
-      "wait": "等待上传...",
-      "error": "上传出错",
-      "add": "添加文件",
-      "start": "开始上传",
-      "file_require": "请添加文件",
-      "cant_upload": "您的浏览器不支持上传文件",
-      "not_support": "您选择的不是浏览器支持的{fileType}文件,请重新选择",
-      "too_large": "文件过大,不能大于{size}MB",
-      "too_large_reupload_tips": "上传视频过大,请重新上传"
-    },
-    "tips": {
-      "__name": "提示",
-      "wait": "请等待..."
-    }
-  },
-  "tips": {
-    "__name": "弹窗、提示",
-    "title": "提示",
-    "set_done": "设置成功",
-    "set_fail": "设置失败",
-    "save_done": "保存成功",
-    "save_fail": "保存失败",
-    "delete": "是否删除?",
-    "delete_done": "删除成功",
-    "delete_fail": "删除失败",
-    "upload_done": "上传成功",
-    "upload_fail": "上传失败",
-    "exception": "异常错误",
-    "network_error": "网络异常,请稍后再试",
-    "file_notfound": "文件不存在",
-    "scene_notfound": "场景不存在",
-    "params_notfound": "缺少必要参数",
-    "camera_notfound": "找不到该场景对应的相机",
-    "password_error": "密码错误",
-    "data_error": "数据不正常",
-    "auth_deny": "无权操作该场景",
-    "clear": "是否清空?",
-    "upload_pic_fail": "上传图片失败",
-    "wait": "请稍后...",
-    "house_type_save_fail": "户型保存失败"
-  },
-  "show": {
-    "__name": "展示页面",
-    "on": "开启",
-    "off": "关闭",
-    "measure": "测距工具",
-    "measure_start": "起点",
-    "measure_end": "终点",
-    "measure_button": "确定{status}",
-    "location_up": "上方",
-    "location_left": "左侧",
-    "location_start_tips": "请先在{direction}点击",
-    "location_end_tips": "起点确定完毕,请定位终点",
-    "vr": "VR模式",
-    "share": "分享",
-    "music": "{status}音乐",
-    "password_tips": "密码",
-    "password_require": "请输入密码"
-  },
-  "components": {
-    "user_guid": {
-      "__name": "用户引导",
-      "title": "操作提示",
-      "pano": "行走<br />点击任意方向移动",
-      "rotate": "旋转视角<br />左右滑动屏幕,旋转视觉",
-      "zoom": "缩放<br />双指滑动放大或缩小视图",
-      "set": "我知道了",
-      "seeMore": "查看详情",
-      "upgradeInfo": "更新内容"
-    }
-  },
-  "material": {
-    "works": {
-      "my": "我的作品",
-      "create": "创建作品",
-      "search": "请输入关键字",
-      "preview": "查看",
-      "edit": "编辑",
-      "share": "分享",
-      "delete": "删除",
-      "no_works": "您还没有作品,请先创建作品~",
-      "work_link": "作品链接",
-      "work_qrCode": "作品二维码",
-      "download_qrCode": "下载二维码",
-      "copy_link": "复制链接",
-      "work_preview": "查看作品",
-      "new_blank": "新窗口打开",
-      "cancel": "取消",
-      "no_link": "链接未生成,请编辑作品添加素材",
-      "had_created": "您已成功创建作品!",
-      "goto_preview": "查看",
-      "continue_edit": "继续编辑",
-      "had_created_but_no_link": "已成功新建作品,但刷新作品列表失败,请稍后手动刷新。",
-      "delete_work": "删除作品",
-      "comfirm_delete": "确定要删除作品吗?"
-    },
-    "components": {
-      "prev": "上一张",
-      "next": "下一张",
-      "zoom_in": "放大",
-      "zoom_out": "缩小",
-      "delete": "删除",
-      "fullscreen": "全屏",
-      "cancel_fullscreen": "取消全屏",
-      "pano_setting": "全景图封面设置",
-      "drag_to_cut": "拖动画面截取封面",
-      "cutting": "截图",
-      "preview_cover": "封面预览",
-      "rename_material": "重命名素材"
-    }
-  },
-  "gather": {
-    "select_material": "选择素材",
-    "no_title": "无标题",
-    "name": "四维全景",
-    "editpage_name": "四维全景编辑器",
-    "my_works": "我的作品",
-    "my_material": "我的素材",
-    "panorama": "全景图",
-    "pano": "全景图",
-    "image": "图片",
-    "audio": "音频",
-    "video": "视频",
-    "scene": "三维场景",
-    "siweikankan": "四维看看",
-    "siweikanjian": "四维看见",
-    "keywords": "请输入关键字",
-    "how_to_shoot": "如何拍摄三维场景",
-    "pano_size": "请上传2:1、120MB以内、jpg格式的图片",
-    "cancel": "取消",
-    "comfirm": "确定",
-    "pano_fail": "格式错误,请上传2:1、120MB以内、jpg格式的全景图片",
-    "pano_limit": "过大,请上传2:1、120MB以内、jpg格式的全景图片",
-    "edit_cover": "编辑封面",
-    "img_fail": "格式错误,请上传10MB以内、jpg/png/gif格式的图片",
-    "img_limit": "过大,请上传10MB以内、jpg/png/gif格式的图片",
-    "img_size": "请上传10MB以内、jpg/png/gif格式的图片",
-    "audio_size": "请上传20MB以内、mp3格式的音频",
-    "audio_limit": "过大,请上传20MB以内、mp3格式的音频",
-    "audio_fail": "格式错误,请上传20MB以内、mp3格式的音频",
-    "upload_material": "上传素材",
-    "new_folder": "新建文件夹",
-    "new_folder_placeholder": "请输入文件夹名,限15字",
-    "folder_name_already_used": "文件夹已存在",
-    "move_folder": "移动",
-    "move_folder_to": "移动到",
-    "no_folder_need_create": "暂无文件夹,请先创建文件夹",
-    "rename_folder_placeholder": "请输入文件夹名,限15字",
-    "video_size": "请上传200MB以内、mp4格式的视频",
-    "video_limit": "过大,请上传200MB以内、mp4格式的视频",
-    "video_fail": "格式错误,请上传200MB以内、mp4格式的视频",
-    "search_material": "请输入关键字",
-    "rename": "重命名",
-    "delete": "删除",
-    "no_search_result": "未搜索到结果",
-    "no_material_result": "暂无素材~",
-    "had_load": "已加载{msg}条",
-    "uploading": "正在上传 {msg}",
-    "success": "操作成功",
-    "delete_success": "删除成功",
-    "delete_fail": "删除失败",
-    "loading": "加载中",
-    "too_long_word": "名称过长,请上传标题在50字以内的图片",
-    "too_long_word_audio": "名称过长,请上传标题在50字以内的音频",
-    "too_long_word_video": "名称过长,请上传标题在50字以内的视频",
-    "uploading_material": "正在上传素材",
-    "cutting": "正在切图处理",
-    "material_upload_fail": "素材上传失败",
-    "material_cutting_fail": "素材切图失败",
-    "fill_complete": "请填写完整信息",
-    "fill_phone": "请正确填写电话号码",
-    "edit_success": "修改成功",
-    "setting_success": "设置成功",
-    "scene_link_copy_tips": "复制成功",
-    "scene_link_copy_failed": "复制失败",
-    "delete_material": "删除素材",
-    "comfirm_delete_material": "确定要删除素材吗?",
-    "can_not_delete_folder_when_uploading": "正在上传素材,请不要删除文件夹。",
-    "delete_folder": "删除文件夹",
-    "comfirm_delete_folder": "确定要删除文件夹以及包含的素材吗?",
-    "save_done": "保存成功",
-    "nothing_edit": "您还未创建任何内容哦",
-    "at_least_one_scene": "至少添加一个场景才可预览,请前往“场景导航”添加",
-    "exitVr": "退出VR模式",
-    "dir": "目录",
-    "root_dir": "根目录",
-    "no_more_data": "没有更多数据了",
-    "converinfo_no_valid": "请将开场封面内容填写完整"
-  },
-  "personal_center": {
-    "personal_center": "个人中心",
-    "logout": "退出登录",
-    "login": "登录",
-    "register": "注册"
-  },
-  "edit_page": {
-    "back_myworks": "返回我的作品",
-    "preview": "查看",
-    "settings": "基础",
-    "navigation": "导航",
-    "viewpoint": "视角",
-    "hotspot": "热点",
-    "explanation": "讲解",
-    "save": "保存"
-  },
-  "edit_settings": {
-    "coverBase_button": "开场封面",
-    "coverBase_button_tips": "整个作品的开场内容,可以设置为图片或视频形式。",
-    "cover_pull_tit": "封面类型",
-    "cover_show": "显示开场封面",
-    "coverSelecVideoAndImg": "图片+视频",
-    "coverSelecImg": "图片",
-    "coverSelecVideo": "视频",
-    "coverImgTit": "图片设置",
-    "coverUpTit1": "建议1920*1080px,2M以内,jpg / gif / png格式",
-    "coverUpTit2": "建议750*1624px,1M以内,jpg / gif / png格式",
-    "coverUpTit3": "建议300*300px,100kb以内,jpg格式",
-    "coverUpTit4": "建议1920*1080px,5M以内,mp4格式",
-    "coverUpTit5": "建议750*1624px,5M以内,mp4格式",
-    "coverImgLoc1": "居中",
-    "coverImgLoc2": "全屏",
-    "coverImgBacTit": "背景设置",
-    "coverImgBacSelec1": "纯色填充",
-    "coverImgBacSelec2": "图片平铺",
-    "coverVideoTit": "视频设置",
-    "select_video": "选择视频",
-    "coverImageInWay": "进入方式",
-    "coverImageInWayTit": "是否开启3秒倒计时后自动进入",
-    "coverVideoInWay": "进入方式",
-    "coverVideoInWayTit": "视频播放完自动进入",
-    "coverVideoControl": "视频控件",
-    "coverVideoControlTit": "视频控件是否显示",
-    "coverImageOrder": "出现顺序",
-    "coverImageOrderTit1": "开场图片先出现",
-    "coverImageOrderTit2": "开场图片后出现",
-    "auto_pano": "自动巡游",
-    "enter_auto": "进入全景图自动巡游(3分钟完整巡游一次)",
-    "set_bgm": "设置背景音乐",
-    "add_audio": "添加音频",
-    "change_audio": "更换音频",
-    "custom_button": "自定义按钮",
-    "custom_button_tips": "自定义按钮可为作品添加联系方式或网站链接等,设置可见后即可在作品显示。",
-    "edit": "编辑",
-    "hide": "隐藏",
-    "show": "显示",
-    "button_name": "按钮名称",
-    "button_type": "按钮类型",
-    "button_open_method": "打开方式",
-    "button_placeholder": "请输入按钮名称",
-    "please_input": "请输入",
-    "phone": "电话号码",
-    "link": "链接地址",
-    "phone_short": "电话",
-    "link_short": "链接",
-    "custom_logo": "自定义LOGO",
-    "show_logo": "显示LOGO",
-    "logo_size": "300*300px,600kb以内,支持<br>jpg/png/gif格式",
-    "mask_setting": "遮罩设置",
-    "sky_mask": "天空遮罩",
-    "sky_tips": "天空遮罩显示在场景的顶部,地面遮罩显示在场景的底部。",
-    "mask_size": "建议500*500px,<br/>支持jpg/png/gif格式",
-    "bottom_mask": "地面遮罩",
-    "opening_setting": "设置开场动画",
-    "opening_animation_show": "显示开场动画",
-    "opening_tips_setting": "提示设置",
-    "opening_tips": "开场提示仅适用于全景图。若初始场景为三维模\n型,以下开场提示不适用。",
-    "pc": "PC端",
-    "mobile": "移动端",
-    "select_image": "选择图片",
-    "opening_size": "建议300*300px,600kb以内,<br/>支持jpg/png/gif格式",
-    "show_setting": "显示设置",
-    "first_notice": "仅首次打开链接时,提示",
-    "password_setting": "设置访问密码",
-    "password_placeholder": "请输入访问密码,限20位",
-    "base_setting": "基础设置",
-    "setting_cover": "设置封面",
-    "cover_size": "512*512px,支持jpg/png/gif格式",
-    "title": "标题",
-    "description": "简介",
-    "work_placeholder": "请输入作品标题",
-    "intro_placeholder": "请输入文字内容,限2000字",
-    "help_center": "帮助中心",
-    "display_time": "显示时间",
-    "no_display": "(不显示)",
-    "second": "秒"
-  },
-  "table": {
-    "material": "素材",
-    "name": "名称",
-    "fileSize": "大小",
-    "detail": "",
-    "dpi": "分辨率",
-    "createTime": "创建时间",
-    "updateTime": "修改时间"
-  },
-  "baseSetting": {
-    "opa1": "小行星开场",
-    "opa2": "小行星巡游开场",
-    "opa3": "小行星缩放开场",
-    "opa4": "水平巡游开场",
-    "opa5": "水晶球开场",
-    "openTips": "开场提示",
-    "openAnimate": "开场动画",
-    "password": "访问密码",
-    "cruise": "自动巡游",
-    "bgm": "背景音乐",
-    "logo": "自定义LOGO",
-    "customCover": "自定义遮罩",
-    "customButton": "自定义按钮",
-    "openCover": "开场封面"
-  },
-  "customButton": {
-    "phone": "电话",
-    "link": "链接",
-    "self": "弹出层打开",
-    "target": "新窗口打开"
-  },
-  "navigation": {
-    "scene_edit_tips": "请前往四维时代个人中心编辑场景",
-    "go_scene_editor": "立即前往",
-    "scene_navigation": "场景导航",
-    "add_group": "新建分组",
-    "scene_tips": "场景素材包括全景图和三维场景,您可自定义分组及场景的排列顺序。",
-    "add_two_group": "新建二级分组",
-    "add_pano_or_scene": "新增全景图或三维场景",
-    "group_one": "一级分组",
-    "group_two": "二级分组",
-    "default_group_two": "默认二级分组",
-    "init_scene": "初始场景",
-    "setting_init_scene": "设置初始场景",
-    "init_scene_tips": "初始场景为查看链接时进入的第一个场景,未设\n置时,不固定从某一场景打开。",
-    "edit_init_scene": "修改场景",
-    "delete_init_scene": "删除场景",
-    "keep_one_scene": "请至少保留一个场景",
-    "keep_one_group": "请至少保留一个分组",
-    "enter_name": "输入名字",
-    "rename": "重命名",
-    "delete": "删除",
-    "pano": "全景",
-    "scene": "三维",
-    "scene_name": "场景",
-    "already_exists": "{msg}已存在,不可重复添加"
-  },
-  "screen": {
-    "init_screen": "初始画面",
-    "screen_tips": "初始画面为进入场景时第一画面,请拖动全景图选择合适的画面设置。",
-    "setting_screen": "将当前视角设为初始画面",
-    "goto_4dkk_edit_tips": "请前往四维时代个人中心编辑~"
-  },
-  "hotspot": {
-    "hotspot_type": "热点类型",
-    "img_size": "最多添加20张图片",
-    "hotspot_tips": "在全景图中添加图标热点,并设置热点的效果。",
-    "add_hotspot": "添加热点",
-    "current_hotspots": "当前全景图热点",
-    "delete": "删除",
-    "add": "新增",
-    "edit": "编辑",
-    "success": "成功",
-    "click_to_comfirm": "单击确定热点位置",
-    "no_hotspot": "暂无热点信息~",
-    "close_dialog": "热点内容未编辑完,确定要关闭吗",
-    "hotspot_name": "热点",
-    "hotspot_icon": "热点图标",
-    "hotspot_icon_type": {
-      "system_icon": "系统图标",
-      "custom_image": "自定义图片",
-      "serial_frame": "序列帧",
-      "personalized_tag": "个性标签"
-    },
-    "add_icon": "添加图标",
-    "add_custom_icon_rule": "300*300px,支持jpg/png/gif格式",
-    "add_serial_frame_rule": "最大宽度:300px,高度不限",
-    "frame_total_number": "序列帧总帧数",
-    "frame_duration": "总播放时长",
-    "select_icon": "选择图标",
-    "if_show_marking_line": "是否显示标注线",
-    "shape_filling_color": "形状填充",
-    "shape_border_color": "形状描边",
-    "text_color": "文字颜色",
-    "text_direction": "文字排序",
-    "left_to_right": "从左到右",
-    "top_to_down": "从上到下",
-    "if_wrap": "是否换行显示",
-    "text_num_per_line": "每行显示",
-    "words": "字",
-    "hotspot_title": "热点标题",
-    "show_hotspot_title":"是否显示标题",
-    "title_show_mode": "显示方式",
-    "show_on_hover": "鼠标悬浮",
-    "always_show": "一直显示",
-    "never_show": "不显示",
-    "title_position": "相对图标位置",
-    "title_top": "上",
-    "title_bottom": "下",
-    "title_left": "左",
-    "title_right": "右",
-    "title_custom": "自定义拖动标题",
-    "title_placeholder": "请输入标题",
-    "effect_settings": "效果设置",
-    "cancel": "取消",
-    "finish": "完成",
-    "scene": "切换场景",
-    "image": "图片",
-    "video": "视频",
-    "audio": "音频",
-    "link": "链接",
-    "textarea": "文本",
-    "tag": "标签",
-    "image_text": "图文",
-    "article": "文章",
-    "pdf": "PDF",
-    "phone": "电话",
-    "hotspot_type_specific_settings_title": {
-      "scene": "热点场景",
-      "image": "热点图片",
-      "video": "热点视频",
-      "audio": "热点音频",
-      "link": "热点链接",
-      "textarea": "热点文本",
-      "tag": "",
-      "imageText": "热点图文",
-      "article": "热点文章",
-      "pdf": "热点PDF",
-      "phone": "热点电话"
-    },
-    "icon_size": "图标大小",
-    "unit": "倍",
-    "add_audio": "添加音频",
-    "change_audio": "更换音频",
-    "select_audio": "选择音频",
-    "add_image": "添加图片",
-    "add_tooltips": "添加",
-    "select_image": "选择图片",
-    "popup": "弹出层打开",
-    "newTab": "新窗口打开",
-    "add_scene": "添加场景",
-    "change_scene": "更换场景",
-    "text_placeholder": "请输入文字内容,限2000字",
-    "add_video": "添加视频",
-    "select_video": "添加视频",
-    "change_video": "更换视频",
-    "MB_limit": "不超过{value}MB",
-    "text_content": "文字内容",
-    "apply_to_all": "应用到所有",
-    "phone_placeholder": "请输入电话号码",
-    "phone_error_tip": "请输入正确的电话号码",
-    "select_pdf": "添加PDF",
-    "change_pdf": "更换PDF",
-    "pdf_invalid_tip": "请选择50MB以内、PDF格式的文件",
-    "add_article": "添加文章内容",
-    "edit_article_title": "编辑文章",
-    "edit_article": "编辑文章内容",
-    "edit_text_title": "编辑文本",
-    "edit_text": "编辑文本内容",
-    "cancel_add_hotspot": "已编辑内容将不会保存,确定要关闭吗?"
-  },
-  "explanation": {
-    "explanation_settings": "语音讲解",
-    "explanation_tips": "您可以为当前全景图添加语音讲解音频。",
-    "add_audio": "添加音频",
-    "change_audio": "更换音频",
-    "default_open": "默认开启",
-    "loop": "循环播放",
-    "disabled_tip": "三维场景暂不支持添加讲解"
-  },
-  "style_key": {
-    "menu_width": "58px",
-    "play_width": "0"
-  },
-  "tips_code": {
-    "FAILURE_2020": "appId为空",
-    "FAILURE_2021": "appId解析有误",
-    "FAILURE_5001": "token为空",
-    "FAILURE_5002": "redis token不存在",
-    "FAILURE_5003": "token invalid(无效)",
-    "FAILURE_3001": "对象不存在",
-    "FAILURE_3011": "当前无操作权限",
-    "FAILURE_3020": "空文件",
-    "FAILURE_3021": "非法文件",
-    "FAILURE_3022": "上传文件超过最大值",
-    "FAILURE_3023": "非法格式",
-    "FAILURE_3025": "上传失败",
-    "FAILURE_3101": "素材已经被引用, 不能删除",
-    "FAILURE_3102": "计算中的场景不能删除",
-    "FAILURE_3103": "服务器someData.json文件不存在",
-    "FAILURE_3200": "请求第三方API失败",
-    "FAILURE_3201": "请求第三方API返回失败",
-    "FAILURE_3104": "存在同名文件夹,请直接移动素材",
-    "FAILURE_error": "网络异常,请稍后再试",
-    "tips": "提示",
-    "relogin": "登录状态失效,请重新登录",
-    "goto_login": "去登录",
-    "login_success": "登录完毕,继续",
-    "need_one": "请至少保留一个场景。",
-    "not_less_than": "文件名称不允许超过50个字符",
-    "work_had_delete": "作品已被删除,无法编辑",
-    "material_can_not_delete": "素材已被引用,无法删除。",
-    "illegality_image": "不支持此图片",
-    "password_error": "密码错误",
-    "FAILURE_3024": "存储空间已满",
-    "loading_fail": "加载失败"
-  }
-}

+ 2 - 2
packages/qjkankan-editor/src/lang/index.js

@@ -1,7 +1,7 @@
 import Vue from 'vue'
 import Vue from 'vue'
 import VueI18n from 'vue-i18n'
 import VueI18n from 'vue-i18n'
-import zh from './_zh.json'
-import en from './_en.json'
+import zh from './weblate/zh.json'
+import en from './weblate/en.json'
 import config from '@/config'
 import config from '@/config'
 import browser from '@/utils/browser'
 import browser from '@/utils/browser'
 
 

+ 975 - 0
packages/qjkankan-editor/src/lang/weblate/en.json

@@ -0,0 +1,975 @@
+{
+    "menu": {
+        "__name": "菜单",
+        "music": "BGM",
+        "base": "Basic Setting",
+        "information": "Scene Info",
+        "screen": "Scene Cover",
+        "hotspot": "Add Hotspot",
+        "guide": "Tour Guide",
+        "sign": "Spot Logo",
+        "walk": "Roaming Possibility",
+        "model": "Model Edit",
+        "custom": "Upload Download",
+        "videos": "Add Videos",
+        "vrhouse": "VR house",
+        "business": "Business part",
+        "scene": "Scene transition",
+        "video": "Add video",
+        "decor": "Decor",
+        "link": "Scene associate"
+    },
+    "modules": {
+        "__name": "模块",
+        "base": {
+            "__name": "基础设置",
+            "qrcode": "QR Code",
+            "qrcode_download": "Download the QR code",
+            "qrcode_tips": "Customize your logo",
+            "scene_link": "Scene Link",
+            "scene_link_copy": "Copy the link",
+            "scene_link_copy_tips": "Copied",
+            "bgm": "BGM",
+            "pano_text": "Roaming",
+            "mode_2d_text": "Layout Plan",
+            "mode_3d_text": "3D Model",
+            "map_text": "Mini-map",
+            "vr_text": "VR Mode",
+            "vr_tips": "Please view the VR effect on the mobile display page",
+            "guide_text": "Tour Guide",
+            "rule_text": "Measurements",
+            "cad_text": "Floor Layout",
+            "measure_text": "Ruler",
+            "measure_tips": "The measuring function cannot be used in edit mode, please use it on display page",
+            "turned_vr": "VR turned {status}",
+            "turned_map": "Mini-map feature  {status}",
+            "turned_cad": "Layout plan {status}",
+            "turned_m2d": "Plane view {status}",
+            "turned_m3d": "3D view {status}",
+            "turned_pano": "Roaming view {status}",
+            "turned_rule": "Measuring function {status}",
+            "turned_guide": "Tour guide {status}",
+            "turned_measure": "Ruler {status}",
+            "shortcut_copy": "One-click copy",
+            "share_link": "Share links to friends",
+            "measure_show_tips": "Please use the measuring function on the display page",
+            "delete_measure_line": "Delete measurement line",
+            "please_click_tips": "'Please click“allow”'",
+            "vr_fail_app_tips": "The browser failed to detect the rotation. Please enable the settings such as motion and direction access in the phone or browser settings, and then refresh this page.",
+            "vr_fail_safari_tips": "The browser failed to detect the rotation. To fully experience the VR effect, please open the \"Motion and Direction Access\" switch under \"Settings\"> \"Safari\"> \"Privacy and Security\", and then refresh this page.",
+            "loading_bottom_text": "4Dage provides technical support",
+            "vr_fail_reopen_tips": "Can't access motion and orientation, please restart App and try again.",
+            "add_music_title": "Add BGM",
+            "add_music_tips": "Support MP3, WAV and other audio formats, no more than 5MB",
+            "re_add_music": "Re-add",
+            "re_add_title": "Re-add BGM",
+            "re_add_tips": "The latest music will replace the added music, <br>Are you sure to continue adding?",
+            "re_add_mobile_tips": "Re-adding will replace the added music",
+            "delete_tips": "Are you sure to delete the current BGM?",
+            "delete_title": "Delete BGM",
+            "bgm_empty_tips": "Please select BGM",
+            "wechat": "Wechat",
+            "friend_circle": "Moments"
+        },
+        "information": {
+            "description_tips": "Please enter a description.",
+            "link": "Add a link",
+            "link_text_tips": "Link title",
+            "link_href_tips": "Link",
+            "link_text_require": "Please fill in the text.",
+            "link_href_require": "Please fill in the link.",
+            "classify": "Choose a category",
+            "upload_time": "Upload date",
+            "record": "Not recorded",
+            "shoot_count": "Number of shots",
+            "password": "Set a password",
+            "password_tips": "4 characters",
+            "password_desc": "Set a password if you don't want it to be accessible for the public.",
+            "password_require": "Please enter a password with {limit} digits",
+            "logo_edit": "Edit the logo",
+            "logo_exit": "Finish editting logo",
+            "logo_show_bottom": "Show original logo",
+            "logo_style1": "Logo style 1",
+            "logo_style2": "Logo style 2",
+            "logo_delete": "Delete the uploaded logo?"
+        },
+        "screen": {
+            "__name": "初始画面",
+            "current": "Current cover",
+            "current_set": "Set as scene cover",
+            "tips": "Drag the screen, click and save your scene cover.",
+            "v_angle_re": "Vertical viewing angle restrictions"
+        },
+        "hotspot": {
+            "__name": "添加热点",
+            "add": "Add hotspot",
+            "edit": "Edit hotspot",
+            "count": "Added hotspots",
+            "location": "Hotspot Location",
+            "location_tips": "Drag the hotspot to another position.",
+            "location_modify": "Modify hotspot location",
+            "location_confirm": "Confirm hotspot location",
+            "location_desc": "Please drag the hot spot in the two scene areas on the left and align it to the desired point.",
+            "style": "Choose hotspot style",
+            "style_name": "Style",
+            "style_dele": "Sure to delete this style?",
+            "style_manage": "Manage",
+            "style_exit": "Quit",
+            "media_photo": "Add pictures to enrich hot content",
+            "media_voice": "Upload local audio content for hotspot explanation",
+            "media_link": "You can add a hyperlink to the video, and the video will be played in the hotspot",
+            "title": "Title",
+            "title_tips": "Please enter a title",
+            "title_require": "Please add a title (max {limit} characters).",
+            "description": "Description",
+            "description_tips": "Please enter a description.",
+            "text_link": "Add a link",
+            "text_link_text_tips": "Please fill in the link text",
+            "text_link_href_tips": "Please fill in the link address",
+            "text_link_href_require": "Link address cannot be empty",
+            "link": "Add a link",
+            "link_require": "Please add an external link",
+            "photo": "Photo",
+            "voice": "Audio",
+            "video": "Video",
+            "photo_tips": "Support JPG, PNG and other image formats",
+            "voice_tips": "Support MP3, WAV and other audio formats",
+            "video_tips": "Support MP4 format. <20M",
+            "photo_require": "Please add photos",
+            "voice_require": "Please add audio",
+            "video_require": "Please add videos",
+            "m_location_tips1": "Point split-screen hotspots above and below to the marked target",
+            "m_location_tips2": "Determine the hotspot location, click next to edit content",
+            "m_location_up": "Above",
+            "m_location_left": "Left side",
+            "m_location_move_tips": "The auxiliary calibration position may be inaccurate,<br>please check and drag to {direction} the same location",
+            "set_visible_btn": "Hotspot Visiblity",
+            "save_visible_btn": "Save",
+            "save_hotspot_done": "Successfully saved hotspot",
+            "save_hotspot_fail": "Failed to save hotspot",
+            "delete_hotspot_tips": "Do you want to delete the current hotspot?",
+            "delete_hotspot_done": "Successfully deleted hotspot",
+            "delete_hotspot_fail": "Failed to delete hotspot",
+            "cant_add_hotspot_tips": "Unable to add hotspot",
+            "cant_add_hotspot_content": "The number of hotspots has reached the maximum: {limit}",
+            "link_text_tips": "Link title",
+            "link_href_tips": "Link",
+            "link_text_require": "Please fill in the text",
+            "link_href_require": "Please fill in the link",
+            "add_media": "Add Content",
+            "style_desc": "Choose the default style, or manually upload the picture to customize the style, upload the picture in PNG/JPG format",
+            "media_video": "Upload local videos for more display",
+            "text_link_text_require": "Link text cannot be empty"
+        },
+        "guide": {
+            "__name": "自动导览",
+            "route": "Tour Route",
+            "view": "Switch View",
+            "record": "Start recording",
+            "record_audio": "Record",
+            "pause": "Pause",
+            "stop": "Stop",
+            "end": "End",
+            "delete": "Delete",
+            "continue": "Continue recording",
+            "preview": "View",
+            "clear": "Clear",
+            "sync": "Audiovisual synchronization",
+            "sound": "Record audio",
+            "file": "Upload audio",
+            "file_add": "Add audio",
+            "tips": "Click \"Start\" to record the tour.",
+            "start": "Start",
+            "finish": "Finish",
+            "less": "Less",
+            "replace_tips": "Do you want to start over the tour recording?",
+            "replace_content": "Redo the recording will overwrite previous data.",
+            "sound_open_fail_tips": "Failed to turn on the microphone",
+            "sound_tips": "Failed to turn on the microphone, continue recording?",
+            "sound_content": "Please allow this site to use the microphone in your browser settings and refresh the page.",
+            "upload_sound_done": "Upload sound explanation successfully",
+            "upload_sound_fail": "Failed to upload sound explanation",
+            "delete_sound_done": "Delete sound explanation successfully",
+            "delete_sound_fail": "Failed to delete sound explanation",
+            "room_title": "Title",
+            "room_title_tips": "Please enter the room title",
+            "room_title_require": "Please enter a title",
+            "room_panel_title": "Guide information",
+            "room_sound_title": "Record audio",
+            "delete_video_content": "Your current recording will be deleted",
+            "delete_file_content": "Your currently uploaded sound explanation will be deleted",
+            "delete_sound_content": "Your recorded audio will be deleted",
+            "camera_save_success": "Successfully saved camera",
+            "clear_video_tip": "Your current recording will be cleared",
+            "saving_sound": "Saving recording",
+            "save_sound_done": "Successfully saved recording",
+            "save_sound_fail": "Failed to save recording",
+            "no_sound_tips": "Currently does not support recordig.<br>Please record on WeChat or computer",
+            "sound_success_tips": "Successfully opened the microphone",
+            "sound_fail_tips": "Currently does not support recording.<br>Official account information configuration error",
+            "wechat_sound_fail_tips": "Failed to open WeChat microphone",
+            "open_sound_guide_tips": "If you want to record, please enable it in the WeChat settings-privacy-authorization management. And ensure that the recording equipment is normal",
+            "sound_cant_open_tips": "This browser does not support recording. <br>It is recommended to replace other mainstream browsers for a better experience",
+            "replace_sound_tips": "Re-record?",
+            "replace_sound_content": "Current sound explanation will be replaced",
+            "sound_limit": "The recording duration cannot exceed {time} minutes",
+            "select_to_record": "Please select the corresponding point to record the guide in this area",
+            "merge_sound_fail": "Fail to merge sound explanation"
+        },
+        "sign": {
+            "title": "Logo style",
+            "size": "Size",
+            "style0": "Style one",
+            "style1": "Style two",
+            "style2": "Style three",
+            "style3": "Upload"
+        },
+        "walk": {
+            "title": "Roaming Possibility",
+            "tips1": "Further optimize the experience during roaming by setting roaming possibility, such as cases in which one may penetrate through the walls.",
+            "tips2": "Set the roaming possibility by clicking and setting the connection of each roaming point.",
+            "save": "Save current settings",
+            "hide": "Hide the spot",
+            "show": "Show the spot",
+            "pano_tips": "Tip: You hide the initial point, so you can't roam in this scene."
+        },
+        "model": {
+            "__name": "修整模型",
+            "cad": "CAD View",
+            "cad_download": "FloorPlan Download",
+            "view": "Switch View",
+            "title": "Add structure",
+            "title_door": "Doors and windows",
+            "title_component": "Components",
+            "title_other": "Others",
+            "attribute": "Attributes",
+            "door": "Door",
+            "slideDoor": "Slidedoor",
+            "casement": "Window",
+            "bayCase": "Bay Window",
+            "groundCase": "French Sash",
+            "column": "Column",
+            "furnColumn": "Frame Column",
+            "furnFlue": "Flue",
+            "point": "Point",
+            "line": "Interior Wall",
+            "tagging": "Tag",
+            "tagging_name_tips": "Tag name",
+            "tagging_area_tips": "Enter area",
+            "direction": "Compass",
+            "wallLine": "Interior wall",
+            "widget_delete": "{widget} will be deleted",
+            "panel_btn_default": "Reset",
+            "panel_btn_delete": "Delete",
+            "attr_angle": "Rotation angle",
+            "attr_within": "Flip direction",
+            "attr_ewidth": "Width",
+            "attr_eheight": "Height",
+            "attr_tick": "Tickness",
+            "attr_showTitle": "Tag name",
+            "attr_showContent": "Tag area",
+            "attr_top": "Top",
+            "attr_bottom": "Bottom",
+            "success": "Add successfully {widget}",
+            "error_location": "Current location cannot be added {widget}",
+            "error_outdoor": "Outdoor cannot be added {widget}",
+            "error_something": "Building at the current location cannot be added {widget}",
+            "error_widget": "Current location cannot be added {widget}",
+            "enter_adjust_floor": "Ground adjustment",
+            "exit_adjust_floor": "Exit adjustment mode",
+            "color_title": "Protractor Color:"
+        },
+        "videos": {
+            "__name": "添加视频",
+            "tips": "Please click on the reference plane to set the video position.",
+            "panel_title": "Video attributes",
+            "panel_preview": "Preview",
+            "panel_upload": "Upload",
+            "panel_upload_tips": "Support MP4 format",
+            "panel_move": "Move",
+            "panel_zoom": "Zoom",
+            "panel_thickness": "Tickness",
+            "recoverRatio": "Restore original aspect ratio",
+            "recoverRatioTip": "Restore original aspect ratio of the video"
+        },
+        "custom": {
+            "model_title": "Download model",
+            "uploading": "Uploading…",
+            "download": "Original model download",
+            "download_panos": "Download panos",
+            "upload": "Modified model upload",
+            "title1": "Model download/upload tutorial",
+            "tips11": "1.After downloading the ZIP package of the scene model, open the decompressed file”mesh.obj” to edit it.",
+            "tips12": "2.After the edit is completed, it is recommended to render the model texture. The render texture should be controlled within 1.5M while the saved obj files should be controlled within 3M.",
+            "tips13": "3.After completing steps 1 and 2, package the obj, mtl, and texture into a zip package and upload it.",
+            "title2": "Notes:",
+            "tips21": "1.Please control the file size, in order not to freeze when you are browsing which will affect your experience.",
+            "tips22": "2.Please use the same name in both obj and ZIP package, otherwise the replacement will fail.",
+            "get_image_fail": "Failed to get image. Please check your network settings and try again.",
+            "download_model_fail": "Fail to download model",
+            "reupload_tips": "Upload {type}",
+            "panoramic_upload": "Pano Upload",
+            "panoramic_upload_tips": "Panorama upload",
+            "panoramic_upload_box_tips": "Please upload JPG image<br>with corresponding point name.",
+            "panoramic_download": "Pano Download",
+            "panoramic_download_tips": "Panorama download",
+            "panoramic": "Panorama",
+            "ball_video": "Video",
+            "ball_video_upload_tips": "Video Upload",
+            "ball_video_download_tips": "Video Download",
+            "ball_video_upload_box_tips": "The panoramic video supports MP4 format<br>which should not exceed 1024M.",
+            "upload_title": "Upload files",
+            "upload_format_error": "File format error, please upload again",
+            "upload_name_error": "File name error, please upload again",
+            "upload_success": "The upload is successful and will take effect<br>after saving and publishing",
+            "upload_code_5017": "Failed to upload the model, <br>please refer tothe tutorial on the right",
+            "upload_code_5018": "The zip file can only have<br>one level of directory or no directory",
+            "upload_code_5019": "There must be only one obj and mtl file",
+            "upload_code_5020": "The texture needs to be controlled within 1.5M, and the obj file needs to be controlled within 3M",
+            "upload_code_5012": "The data is abnormal",
+            "upload_code_5023": "The upload file format is incorrect, <br>only jpg or mp4 format.",
+            "download_tips": "Download {type}",
+            "download_fail": "Failed to download"
+        },
+        "vrhouse": {
+            "__name": "VR看房",
+            "linkto_management": "Go to the VR house management background"
+        },
+        "business": {
+            "__name": "商圈模块",
+            "list_title": "Scene hotspot list"
+        },
+        "link": {
+            "__name": "跳转关联",
+            "title1": "Save the link",
+            "title2": "Edit the link",
+            "list_text": "Added links",
+            "btn_new_text": "Add a link",
+            "btn_add_title": "关联点定位",
+            "btn_add_text": "确定关联点位置",
+            "btn_add_desc": "将关联点标记并拖动到合适的位置。",
+            "btn_edit_text": "Adjust the link position",
+            "style": "Link icon",
+            "style_name": "icon",
+            "style_desc": "upport customized icon with recommended size of 100*100 pixels. Support jpg/png/gif format.",
+            "style_dele": "Delete this icon? <br /> The same icon used for other links will also be deleted.",
+            "desc_title": "Description",
+            "desc_tips": "Please enter a description.",
+            "desc_require": "Please enter a description.",
+            "type": "Choose the type of link",
+            "type_photo": "upport uploading panoramic pictures with an aspect ratio of 2:1, recommended pixel size should not be less than 6000 × 3000 pixels, and the file should not exceed 120M",
+            "type_photo_require": "Upload panoramic pictures.",
+            "type_link": "Please input the scene link.",
+            "type_link_tips": "http(s)://",
+            "type_link_require": "Please input the scene link.",
+            "enter_title": "Link cover",
+            "enter_require": "Please set a link cover",
+            "enter_btn_text": "Set a link cover",
+            "outer_title": "Exit spot position",
+            "outer_style": "Exit spot icon",
+            "outer_desc": "Support customized icon with recommended size of 100*100 pixels. Support jpg/png/gif format.",
+            "outer_require": "Please set the position of exit spot.",
+            "outer_btn_text": "Set the position of exit spot."
+        }
+    },
+    "login": {
+        "__name": "登录",
+        "title": "Message",
+        "login": "Log in",
+        "logon": "Log in and continue",
+        "login_tips": "You are not logged in. Please log in to edit."
+    },
+    "common": {
+        "__name": "公用",
+        "on": "on",
+        "off": "off",
+        "add": "Add",
+        "set": "OK",
+        "ok": "OK",
+        "save": "Save",
+        "cancel": "Cancel",
+        "complete": "Complete",
+        "edit": "Modify",
+        "giveup": "Give up",
+        "commit": "Commit",
+        "photo": "Photo",
+        "voice": "Audio",
+        "video": "Video",
+        "bgm": "BGM",
+        "crop": "Crop",
+        "upload": "Upload",
+        "download": "Download",
+        "change": "Change",
+        "unnamed": "Unnamed",
+        "publish": "Save and publish",
+        "publish_text": "Save and publish successfully!",
+        "publish_tips": "Check your scene immediately?",
+        "publish_btn_ok": "Now",
+        "publish_btn_no": "Later",
+        "public": "Public",
+        "private": "Private",
+        "waiting": "Waiting...",
+        "audio": "Audio",
+        "second": "s",
+        "frame": "frame",
+        "delete": "Delete",
+        "prev": "Go back",
+        "next": "Next",
+        "meter": "≈{meter}m",
+        "guide": "Tour Guide",
+        "rule": "Ruler",
+        "roaming": "Roaming",
+        "continue": "Continue",
+        "ensure_delete": "Delete",
+        "text_limit": "Limit within {limit} characters",
+        "default": "Default",
+        "custom": "Customize",
+        "back": "Back",
+        "will_delete": "Will be deleted",
+        "setup": "Set",
+        "exit": "Done",
+        "music": {
+            "__name": "背景音乐",
+            "none": "No music",
+            "cheerfu": "Cheerful",
+            "ethereal": "Ethereal",
+            "rhythmic": "Rhythmic",
+            "nostalgic": "Nostalgic",
+            "missing": "Missing",
+            "retro": "Retro",
+            "strings": "Strings",
+            "happy": "Happy"
+        },
+        "category": {
+            "__name": "分类",
+            "museum": "Museum",
+            "estate": "Real estate",
+            "eshop": "E-Commerce",
+            "catering": "Catering",
+            "home": "Home",
+            "other": "Other"
+        },
+        "uploads": {
+            "__name": "文件上传",
+            "uploading": "Uploading",
+            "uploaded": "Uploaded",
+            "wait": "Wait...",
+            "error": "Error",
+            "add": "Add",
+            "start": "Start",
+            "file_require": "Please add file",
+            "cant_upload": "Your browser does not support uploading files",
+            "not_support": "The file you selected is not {fileType} supported by the browser, please select again",
+            "too_large": "The file is too large and cannot be larger than {size} trillion",
+            "too_large_reupload_tips": "The uploaded video is too large, please upload again"
+        },
+        "tips": {
+            "__name": "提示",
+            "wait": "Please wait..."
+        }
+    },
+    "tips": {
+        "__name": "提示",
+        "title": "Tip",
+        "set_done": "Set up successfully",
+        "set_fail": "Set up failed",
+        "save_done": "Save successfully",
+        "save_fail": "Save failed",
+        "delete": "Delete or not?",
+        "delete_done": "Delete successfully",
+        "delete_fail": "Fail to delete",
+        "upload_done": "Upload successfully",
+        "upload_fail": "Failed to upload",
+        "exception": "Error",
+        "network_error": "The internet is disconnected, please try it again",
+        "file_notfound": "The file is not found",
+        "scene_notfound": "The scene is not found",
+        "params_notfound": "The parameter is not found",
+        "camera_notfound": "The matched camera is not found",
+        "password_error": "Incorrect password",
+        "data_error": "The data is not found",
+        "auth_deny": "Unathorized to operate the scene",
+        "clear": "Sure to clear?",
+        "upload_pic_fail": "Failed to upload pictures",
+        "wait": "Please waiting...",
+        "house_type_save_fail": "Failed to save house type"
+    },
+    "show": {
+        "__name": "展示页面",
+        "on": "On",
+        "off": "Off",
+        "measure": "Measuring tool",
+        "measure_start": "Start",
+        "measure_end": "End",
+        "measure_button": "Confirm {status}",
+        "location_up": "Above",
+        "location_left": "Left",
+        "location_start_tips": "Please click on {direction}",
+        "location_end_tips": "The starting point is determined, please locate the end point",
+        "vr": "VR mode",
+        "share": "Share it",
+        "music": "Turn {status} music",
+        "password_tips": "Password",
+        "password_require": "Please enter the password"
+    },
+    "components": {
+        "user_guid": {
+            "__name": "用户引导",
+            "title": "Operation Tips",
+            "pano": "Walk<br />Click to move.",
+            "rotate": "Roam<br />Swipe the screen to roam.",
+            "zoom": "Zoom<br />Zoom in or out.",
+            "set": "Got it",
+            "seeMore": "More Details",
+            "upgradeInfo": "Content"
+        }
+    },
+    "material": {
+        "works": {
+            "my": "My Project",
+            "create": "Create a project",
+            "search": "Keywords",
+            "preview": "View",
+            "edit": "Edit",
+            "share": "Share",
+            "delete": "Delete",
+            "no_works": "No results found, you do not yet have a project, please create one first~",
+            "work_link": "Project link",
+            "work_qrCode": "Project QR Code",
+            "download_qrCode": "Download the QR code",
+            "copy_link": "Copy link",
+            "work_preview": "View Project",
+            "new_blank": "Open in a new window",
+            "cancel": "Cancel",
+            "no_link": "Link not generated, please edit your project to include material",
+            "had_created": "You have created your project with success!",
+            "goto_preview": "View",
+            "continue_edit": "Continue to edit",
+            "had_created_but_no_link": "New project has been created successfully, but refreshing the project list failed; please try again later.",
+            "delete_work": "Delete project",
+            "comfirm_delete": "Confirm to delete the project?",
+            "cam_gen": "Camera generation",
+            "online_gen": "Online production",
+            "calcing": "Calculating",
+            "no_works_cam": "You haven't captured any works with your camera yet, go and shoot them~"
+        },
+        "components": {
+            "prev": "Previous",
+            "next": "Next",
+            "zoom_in": "Zoom in",
+            "zoom_out": "Zoom out",
+            "delete": "Delete",
+            "fullscreen": "Full screen",
+            "cancel_fullscreen": "Exit full screen",
+            "pano_setting": "Thumbnail Settings",
+            "drag_to_cut": "Drag the screen to have a screenshot",
+            "cutting": "Screenshot",
+            "preview_cover": "View",
+            "rename_material": "Rename the material"
+        }
+    },
+    "gather": {
+        "select_material": "Select the materials",
+        "no_title": "No title",
+        "name": "4DPano",
+        "editpage_name": "Editing platform",
+        "my_works": "My Project",
+        "my_material": "My Material",
+        "panorama": "Panorama",
+        "pano": "Panorama",
+        "image": "Pictures",
+        "audio": "Audio",
+        "video": "Video",
+        "scene": "3D Scene",
+        "siweikankan": "4DKanKan",
+        "siweikanjian": "4DMinion",
+        "siweiSS": "4DMega",
+        "siweiSG": "4DMeta",
+        "keywords": "keywords",
+        "how_to_shoot": "How to shoot a 3D scene?",
+        "pano_size": "Support 2:1 jpg files ≤ 120MB",
+        "cancel": "Cancel",
+        "comfirm": "Confirm",
+        "pano_fail": "Format error. Support 2:1 jpg files ≤ 120MB",
+        "pano_limit": "The file is too large. Support 2:1 jpg files ≤ 120MB",
+        "edit_cover": "Thumbnail Settings",
+        "img_fail": "Format error.Support jpg/png/gif files: ≤ 10MB",
+        "img_limit": "The file is too large.Support jpg/png/gif files: ≤ 10MB",
+        "img_size": "Support jpg/png/gif files: ≤ 10MB",
+        "audio_size": "Support MP3 files: ≤ 20MB",
+        "audio_limit": "The file is too large. Support MP3 files: ≤ 20MB",
+        "audio_fail": "Format error. Support MP3 files: ≤ 20MB",
+        "upload_material": "Upload materials",
+        "new_folder": "New Folder",
+        "new_folder_placeholder": "Please enter",
+        "folder_name_already_used": "Folder already exists",
+        "move_folder": "Move",
+        "move_folder_to": "Move to",
+        "no_folder_need_create": "Please create a folder first",
+        "rename_folder_placeholder": "Please enter",
+        "video_size": "Support MP4 files: ≤ 200MB",
+        "video_limit": "The file is too large.Support MP4 files: ≤ 200MB",
+        "video_fail": "Format error. Support MP4 files: ≤ 200MB",
+        "search_material": "Keywords",
+        "rename": "Rename",
+        "delete": "Delete",
+        "no_search_result": "No results found",
+        "no_material_result": "No materials found",
+        "had_load": "{msg} pieces of data have been loaded",
+        "uploading": "Uploading {msg}",
+        "success": "Operation succeeded",
+        "delete_success": "Deletion succeeded",
+        "delete_fail": "Delete failed",
+        "loading": "Loading",
+        "too_long_word": "Title is too long; please upload an image with a 50-word or shorter title.",
+        "too_long_word_audio": "Title is too long; please upload an audio with a 50-word or shorter title",
+        "too_long_word_video": "Title is too long; please upload a video with a 50-word or shorter title",
+        "uploading_material": "Uploading material",
+        "cutting": "Cropping",
+        "material_upload_fail": "Failed to upload material",
+        "material_cutting_fail": "Failed to crop image",
+        "fill_complete": "Please fill in the complete information.",
+        "fill_phone": "Please fill in the phone number correctly",
+        "edit_success": "Modification succeeded",
+        "setting_success": "Setting succeeded",
+        "scene_link_copy_tips": "Copied",
+        "scene_link_copy_failed": "Copy failed",
+        "delete_material": "Delete material",
+        "comfirm_delete_material": "Confirm to delete the material?",
+        "can_not_delete_folder_when_uploading": "Please do not delete the folder while the material is being uploaded.",
+        "delete_folder": "Delete folder",
+        "comfirm_delete_folder": "Are you sure you wish to delete the folder and its contents?",
+        "save_done": "Save successfully",
+        "nothing_edit": "You haven't created any content yet",
+        "at_least_one_scene": "At least one scene can be previewed. Please go to \"Scene Navigation\" to add",
+        "exitVr": "Exit VR",
+        "dir": "Directory",
+        "root_dir": "Root Directory",
+        "no_more_data": "no more results.",
+        "converinfo_no_valid": "Please fill out the content for the opening cover."
+    },
+    "personal_center": {
+        "personal_center": "Personal Center",
+        "logout": "Log out",
+        "login": "Login",
+        "register": "Sign up"
+    },
+    "edit_page": {
+        "back_myworks": "Back",
+        "preview": "View",
+        "settings": "Basic Settings",
+        "navigation": "Navigation",
+        "viewpoint": "Starting Screen",
+        "hotspot": "Hotspots",
+        "explanation": "Voice Guide",
+        "mask": "Mask",
+        "save": "Save"
+    },
+    "edit_settings": {
+        "coverBase_button": "Opening Cover",
+        "coverBase_button_tips": "The opening content of the entire project may be presented as an image or video.",
+        "cover_pull_tit": "Cover Type",
+        "cover_show": "Show opening cover",
+        "coverSelecVideoAndImg": "Image + Video",
+        "coverSelecImg": "Image",
+        "coverSelecVideo": "Video",
+        "coverImgTit": "Image Settings",
+        "coverUpTit1": "1920*1080 pixels, within 2M, jpg/gif/png format is recommended",
+        "coverUpTit2": "750*1624 pixels ,within 1M, jpg/gif/png format is recommended",
+        "coverUpTit3": "300*300 pixels, within 100kb, jpg format is recommended",
+        "coverUpTit4": "1920*1080 pixels, within 5M, MP4 format is recommended",
+        "coverUpTit5": "750*1624 pixels, within 5M, MP4 format is recommended",
+        "coverImgLoc1": "Center",
+        "coverImgLoc2": "Full",
+        "coverImgBacTit": "Background Settings",
+        "coverImgBacSelec1": "Fill Color",
+        "coverImgBacSelec2": "Background-repeat",
+        "coverVideoTit": "Video Settings",
+        "select_video": "Select Video",
+        "coverImageInWay": "Entry Method",
+        "coverImageInWayTit": "Automatic entry after 3 seconds countdown",
+        "coverVideoInWay": "Entry Method",
+        "coverVideoInWayTit": "Auto-entry after video playback",
+        "coverVideoControl": "Video Control",
+        "coverVideoControlTit": "Display the video control or not",
+        "coverImageOrder": "Displaying Order",
+        "coverImageOrderTit1": "Images will appear before the video begins to play",
+        "coverImageOrderTit2": "Images will appear after the video begins to play",
+        "auto_pano": "Rotation",
+        "enter_auto": "Enter the rotation mode (takes 3 mins to complete per rotation)",
+        "set_bgm": "Set the BGM",
+        "add_audio": "Add the audio",
+        "change_audio": "Change the audio",
+        "custom_button": "Custom Button",
+        "custom_button_tips": "Custom buttons provide contact info, website URLs, etc. to a project, which can subsequently be made visible and displayed as well.",
+        "edit": "Edit",
+        "hide": "Hide",
+        "show": "Display",
+        "button_name": "Button name",
+        "button_type": "Button type",
+        "button_open_method": "Open methods",
+        "button_placeholder": "Please enter the button name",
+        "please_input": "Please enter",
+        "phone": "Phone",
+        "link": "URL Address",
+        "phone_short": "Tel",
+        "link_short": "Link",
+        "custom_logo": "Custom Logo",
+        "show_logo": "Display Logo",
+        "logo_size": "300*300 pixels, within 600kb, <br>jpg/png/gif format support",
+        "mask_setting": "Mask Settings",
+        "sky_mask": "Top mask",
+        "sky_tips": "The top mask is exhibited at the scene's top, while the bottom mask is presented at the scene's bottom.",
+        "mask_size": "500*500 pixels, <br>jpg/png/gif format support",
+        "bottom_mask": "Bottom mask",
+        "opening_setting": "Set the animations",
+        "opening_animation_show": "Switch the animations",
+        "opening_tips_setting": "Notifications Settings",
+        "opening_tips": "The notifications only apply to panoramic images. The following notifications are inapplicable if the start screen is a 3D model.",
+        "pc": "PC",
+        "mobile": "Mobile",
+        "select_image": "Select pictures",
+        "opening_size": "Recommend 300*300 pixels, within 600kb, jpg/png/gif format support",
+        "show_setting": "Display settings",
+        "first_notice": "Prompt only when the link is opened for the first time",
+        "password_setting": "Set an access code",
+        "password_placeholder": "Please enter the access password, limited to 20-character",
+        "base_setting": "Basic Settings",
+        "setting_cover": "Thumbnail Settings",
+        "cover_size": "512*512px, support jpg/png/gif format",
+        "title": "Title",
+        "description": "Description",
+        "work_placeholder": "Please enter the project title",
+        "intro_placeholder": "Please input no more than 2000 words of text content",
+        "help_center": "Help Center",
+        "display_time": "Display duration",
+        "no_display": "(No display)",
+        "second": "Sec"
+    },
+    "table": {
+        "material": "Material",
+        "name": "Title",
+        "fileSize": "Size",
+        "dpi": "Resolution",
+        "createTime": "Created",
+        "updateTime": "Modified",
+        "detail": "detail"
+    },
+    "baseSetting": {
+        "opa1": "Start with asteroid",
+        "opa2": "Start with asteroid cruise",
+        "opa3": "Start with asteroid zoom",
+        "opa4": "Start with horizontal cruise",
+        "opa5": "Start with crystal ball",
+        "openTips": "Notifications",
+        "openAnimate": "Animations",
+        "password": "Access Code",
+        "cruise": "Rotation",
+        "bgm": "BGM",
+        "logo": "Custom Logo",
+        "customCover": "Custom Mask",
+        "customButton": "Custom Button",
+        "openCover": "Opening Cover"
+    },
+    "customButton": {
+        "phone": "Tel",
+        "link": "Link",
+        "self": "Pop-up Notification",
+        "target": "New Tab"
+    },
+    "navigation": {
+        "scene_edit_tips": "Please direct to the 4Dage Personal Center scene editing platform",
+        "go_scene_editor": "Get started now",
+        "scene_navigation": "Navigation",
+        "add_group": "Add a grouping",
+        "scene_tips": "You can customize the grouping and ordering of scene material, which includes panorama and 3D scenes",
+        "add_two_group": "Add 2nd Grouping",
+        "add_pano_or_scene": "Add panorama or 3D scene",
+        "group_one": "1st Grouping",
+        "group_two": "2nd Grouping",
+        "default_group_two": "Default as 2nd grouping",
+        "init_scene": "Initial Scene",
+        "setting_init_scene": "Initial scene settings",
+        "init_scene_tips": "The initial scene is the enter screen when a link is viewed, and it will not applied to a specific scene when \n is not set.",
+        "edit_init_scene": "Edit",
+        "delete_init_scene": "Delete",
+        "keep_one_scene": "Please retain at least one scene",
+        "keep_one_group": "Please retain at least one grouping",
+        "enter_name": "Please enter the project title",
+        "rename": "Rename",
+        "delete": "Delete",
+        "pano": "Panorama",
+        "scene": "3D Scene",
+        "scene_name": "Scene",
+        "already_exists": "{msg} already exists, can not be repeatedly added"
+    },
+    "screen": {
+        "init_screen": "Start Screen",
+        "screen_tips": "The start screen is the initial screen that appears when entering a scene; please drag the panorama to select the appropriate screen setting.",
+        "setting_screen": "Set the current view to the start screen",
+        "goto_4dkk_edit_tips": "4Dage Personal Center~"
+    },
+    "hotspot": {
+        "hotspot_type": "Hotspots Style",
+        "img_size": "Upload up to 20 images",
+        "hotspot_tips": "Add icon hotspots to the panorama and configure their effect",
+        "add_hotspot": "Add hotspot",
+        "current_hotspots": "Current Pano Hotspots",
+        "delete": "Delete",
+        "add": "Add",
+        "edit": "Edit",
+        "success": "Success",
+        "click_to_comfirm": "Click to determine the location of hotspots",
+        "no_hotspot": "No hotspot information at this time~",
+        "close_dialog": "Hotspot content is not fully edited, confirm to close it",
+        "hotspot_name": "Hotspots",
+        "hotspot_icon": "Select Icon",
+        "hotspot_icon_type": {
+            "system_icon": "Default icon",
+            "custom_image": "Custom images",
+            "serial_frame": "Sequential frames",
+            "personalized_tag": "Custom labels"
+        },
+        "add_icon": "Add Icon",
+        "add_custom_icon_rule": "300*300 pixels, jpg/png/gif format is recommended",
+        "add_serial_frame_rule": "Maximum width: 300 pixels, unlimited height",
+        "frame_total_number": "Total Frames",
+        "frame_duration": "Total Play Time",
+        "select_icon": "Change",
+        "if_show_marking_line": "Display dimension line",
+        "shape_filling_color": "Shape Color",
+        "shape_border_color": "Shape Stroke",
+        "text_color": "Text Color",
+        "text_direction": "文字排序",
+        "left_to_right": "从左到右",
+        "top_to_down": "从上到下",
+        "if_wrap": "是否换行显示",
+        "text_num_per_line": "每行显示",
+        "words": "字",
+        "hotspot_title": "Title",
+        "show_hotspot_title": "Show Title",
+        "title_show_mode": "Display Methods",
+        "show_on_hover": "Hovering",
+        "always_show": "Show",
+        "never_show": "Hide",
+        "title_position": "Relative Position",
+        "title_top": "Up",
+        "title_bottom": "Down",
+        "title_left": "Left",
+        "title_right": "Right",
+        "title_custom": "自定义拖动标题",
+        "title_placeholder": "Enter a title",
+        "effect_settings": "Effect settings",
+        "cancel": "Cancel",
+        "finish": "Finish",
+        "scene": "Scene",
+        "image": "Image",
+        "video": "Video",
+        "audio": "Audio",
+        "link": "Hyperlink",
+        "textarea": "Text",
+        "tag": "Label",
+        "image_text": "Image&text",
+        "article": "Paragraph",
+        "pdf": "PDF",
+        "phone": "Tel",
+        "hotspot_type_specific_settings_title": {
+            "scene": "Scene",
+            "image": "Image",
+            "video": "Video",
+            "audio": "Audio",
+            "link": "Link",
+            "textarea": "Text",
+            "tag": " ",
+            "imageText": "Image&text",
+            "article": "Paragraph",
+            "pdf": "PDF",
+            "phone": "Tel"
+        },
+        "icon_size": "Icon Size",
+        "unit": "times",
+        "add_audio": "Add audio",
+        "change_audio": "Change the audio",
+        "select_audio": "Select audio",
+        "add_image": "Add images",
+        "add_tooltips": "Add",
+        "select_image": "Select images",
+        "popup": "Pop-up Notification",
+        "newTab": "New Tab",
+        "add_scene": "Add a scene",
+        "change_scene": "Change a scene",
+        "text_placeholder": "Please enter no more than 2000 words of text.",
+        "add_video": "Add a video",
+        "select_video": "Add a video",
+        "change_video": "Change a video",
+        "MB_limit": "Within {value}MB",
+        "text_content": "Text",
+        "apply_to_all": "应用到所有",
+        "phone_placeholder": "Please enter",
+        "phone_error_tip": "Please enter the correct telephone number",
+        "select_pdf": "Add a PDF",
+        "change_pdf": "Change the PDF",
+        "pdf_invalid_tip": "Please upload PDF files within 50M",
+        "add_article": "Add Paragraph",
+        "edit_article_title": "Edit paragraph",
+        "edit_article": "Edit paragraph",
+        "edit_text_title": "Edit text",
+        "edit_text": "Edit text Content",
+        "cancel_add_hotspot": "The edited content will not be saved, confirm to close it"
+    },
+    "explanation": {
+        "explanation_settings": "Voice Guide",
+        "explanation_tips": "You can add voice guide for the current panorama.",
+        "add_audio": "Add audio",
+        "change_audio": "Change audio",
+        "default_open": "Enable",
+        "loop": "Loop",
+        "disabled_tip": "三维场景暂不支持添加讲解"
+    },
+    "mask": {
+        "cover_tips": "You can add a mask to the current panorama.",
+        "select_image": "select",
+        "apply_to_all_pano": " Apply to all panos",
+        "follow_the_scene": " Follow the scene",
+        "scaling_ratio": "Scaling Ratio",
+        "is_apply_to_all_pano": "Apply to all panoramas?",
+        "unit": " "
+    },
+    "style_key": {
+        "menu_width": "88px",
+        "play_width": "31"
+    },
+    "tips_code": {
+        "FAILURE_2020": "appId is absent",
+        "FAILURE_2021": "The appId was wrongly parsed",
+        "FAILURE_5001": "token is empty",
+        "FAILURE_5002": "redis token does not exist",
+        "FAILURE_5003": "token invalid(invalid)",
+        "FAILURE_3001": "Object does not exist",
+        "FAILURE_3011": "No operation authorization",
+        "FAILURE_3020": "Empty file",
+        "FAILURE_3021": "Illegal file",
+        "FAILURE_3022": "Upload file exceeds maximum",
+        "FAILURE_3023": "Illegal format",
+        "FAILURE_3025": "Upload error",
+        "FAILURE_3101": "The referenced material cannot be erased",
+        "FAILURE_3102": "The computation scene cannot be erased",
+        "FAILURE_3103": "Server someData.json file does not exist",
+        "FAILURE_3200": "Failed to request for third-party API",
+        "FAILURE_3201": "Failed to request the interface",
+        "FAILURE_3104": "The folder with the same name has already existed, <br/> please move the material directly",
+        "FAILURE_error": "Network exception, please try again later",
+        "tips": "Tips",
+        "relogin": "Invalid login status; please log in again",
+        "goto_login": "Go to login",
+        "login_success": "Login completed, please continue",
+        "need_one": "Please keep at least one scene",
+        "not_less_than": "File title may not exceed 50 characters in length",
+        "work_had_delete": "The project has been deleted and cannot be edited",
+        "material_can_not_delete": "The material cannot be removed because it has been cited.",
+        "illegality_image": "This image is not supported",
+        "password_error": "Password error",
+        "FAILURE_3024": "Storage is at capacity",
+        "loading_fail": "Failed to load"
+    }
+}

+ 975 - 0
packages/qjkankan-editor/src/lang/weblate/zh.json

@@ -0,0 +1,975 @@
+{
+    "menu": {
+        "__name": "菜单",
+        "music": "背景音乐",
+        "base": "基础设置",
+        "information": "场景信息",
+        "screen": "初始画面",
+        "hotspot": "添加热点",
+        "guide": "自动导览",
+        "sign": "地面Logo",
+        "walk": "漫游可行",
+        "model": "修整模型",
+        "custom": "上传下载",
+        "videos": "添加视频",
+        "vrhouse": "VR看房",
+        "business": "商圈模块",
+        "scene": "场景跳转",
+        "video": "添加视频",
+        "decor": "一键换装",
+        "link": "场景关联"
+    },
+    "modules": {
+        "__name": "模块",
+        "base": {
+            "__name": "基础设置",
+            "qrcode": "场景二维码",
+            "qrcode_download": "下载二维码",
+            "qrcode_tips": "自定义logo",
+            "scene_link": "场景地址",
+            "scene_link_copy": "复制链接",
+            "scene_link_copy_tips": "复制成功",
+            "bgm": "背景音乐",
+            "pano_text": "漫游视角可视",
+            "mode_2d_text": "平面图可视",
+            "mode_3d_text": "三维模型可视",
+            "map_text": "小地图预览可视",
+            "vr_text": "VR模式可视",
+            "vr_tips": "请在手机展示页面观看VR效果",
+            "guide_text": "自动导览可视",
+            "rule_text": "标尺可视",
+            "cad_text": "俯视图户型可视",
+            "measure_text": "测量工具可视",
+            "measure_tips": "编辑模式下无法使用测距功能,请在展示页面操作",
+            "turned_vr": "VR功能已{status}",
+            "turned_map": "小地图功能已{status}",
+            "turned_cad": "俯视图户型功能已{status}",
+            "turned_m2d": "平面视角功能已{status}",
+            "turned_m3d": "三维视角功能已{status}",
+            "turned_pano": "漫游视角功能已{status}",
+            "turned_rule": "标尺功能已{status}",
+            "turned_guide": "自动导览功能已{status}",
+            "turned_measure": "测距功能已{status}",
+            "shortcut_copy": "一键复制",
+            "share_link": "分享链接给好友",
+            "measure_show_tips": "请在展示页面使用测距功能",
+            "delete_measure_line": "删除测量线",
+            "please_click_tips": "'请点击“允许”'",
+            "vr_fail_app_tips": "浏览器未能检测到转动。请在手机或浏览器设置中开启了运动和方向访问等设置,然后刷新此页面。",
+            "vr_fail_safari_tips": "浏览器未能检测到转动。为完整体验VR效果,请打开 “设置” > “Safari” > “隐私和安全” 下的 “运动和方向访问” 开关,然后刷新此页面。",
+            "loading_bottom_text": "四维时代提供技术支持",
+            "vr_fail_reopen_tips": "运动和方向访问失败。您需要完全关闭此应用,然后再次打开,并允许访问运动与方向",
+            "add_music_title": "添加背景音乐",
+            "add_music_tips": "支持MP3、WAV等音频格式,不超过5MB",
+            "re_add_music": "重新添加",
+            "re_add_title": "重新添加背景音乐",
+            "re_add_tips": "新添加的音乐会替换已添加的音乐,<br>确定继续添加吗?",
+            "re_add_mobile_tips": "重新添加将会覆盖已添加音乐",
+            "delete_tips": "您确定删除当前音乐吗?",
+            "delete_title": "删除背景音乐",
+            "bgm_empty_tips": "请选择背景音乐",
+            "wechat": "微信",
+            "friend_circle": "朋友圈"
+        },
+        "information": {
+            "description_tips": "请填写简介",
+            "link": "添加链接",
+            "link_text_tips": "请填写链接文本",
+            "link_href_tips": "请填写链接地址",
+            "link_text_require": "请填写链接文本",
+            "link_href_require": "请填写链接地址",
+            "classify": "分类",
+            "upload_time": "上传时间",
+            "record": "未记录",
+            "shoot_count": "拍摄数量",
+            "password": "访问密码",
+            "password_tips": "访问密码",
+            "password_desc": "设置完密码后,当其他人访问您的场景时,需要输入您设置的密码才能访问。如无需设置点击“公开”即可。",
+            "password_require": "请输入{limit}位数的密码",
+            "logo_edit": "编辑页面Logo",
+            "logo_exit": "退出页面Logo编辑",
+            "logo_show_bottom": "显示初始Logo",
+            "logo_style1": "顶部Logo-方",
+            "logo_style2": "顶部Logo-长",
+            "logo_delete": "是否删除已上传Logo?"
+        },
+        "screen": {
+            "__name": "初始画面",
+            "current": "当前初始视角",
+            "current_set": "设置为初始画面",
+            "tips": "移动屏幕,点击保存您的初始画面。",
+            "v_angle_re": "垂直视角限制"
+        },
+        "hotspot": {
+            "__name": "添加热点",
+            "add": "添加热点",
+            "edit": "编辑热点",
+            "count": "已添加热点",
+            "location": "热点定位",
+            "location_tips": "将热点标记并拖动到合适的位置。",
+            "location_modify": "修改热点位置",
+            "location_confirm": "确定热点位置",
+            "location_desc": "请于左方两个场景区域拖动热点并对准所需标记的位置。",
+            "style": "选择热点样式",
+            "style_desc": "选择默认样式,或者手动上传图片自定义样式,上传图片格式PNG/JPG",
+            "style_name": "样式",
+            "style_dele": "是否删除该样式?",
+            "style_manage": "管理",
+            "style_exit": "退出",
+            "media_photo": "可添加图片以丰富热点内容",
+            "media_video": "可上传本地视频,进行更多的展示",
+            "media_voice": "可上传本地音频内容进行热点讲解",
+            "media_link": "可添加视频的超链接,视频将在热点里播放",
+            "title": "标题",
+            "title_tips": "请填写标题",
+            "title_require": "请添加标题({limit}字以内)",
+            "description": "简介",
+            "description_tips": "请填写简介",
+            "text_link": "添加链接",
+            "text_link_text_tips": "请填写链接文本",
+            "text_link_href_tips": "请填写链接地址",
+            "text_link_text_require": "链接文本不能为空",
+            "text_link_href_require": "链接地址不能为空",
+            "link": "嵌入式链接",
+            "link_require": "请添加外链",
+            "photo": "图片",
+            "voice": "音频",
+            "video": "视频",
+            "photo_tips": "支持JPG、PNG等图片格式,不超过{size}MB",
+            "voice_tips": "支持MP3、WAV等音频格式,不超过{size}MB",
+            "video_tips": "支持MP4、MOV等视频格式,不超过{size}MB",
+            "photo_require": "请添加图片",
+            "voice_require": "请添加音频",
+            "video_require": "请添加视频",
+            "m_location_tips1": "将上下分屏热点对准同一个所标记目标",
+            "m_location_tips2": "确定热点位置,点击下一步编辑内容",
+            "m_location_up": "上方",
+            "m_location_left": "左侧",
+            "m_location_move_tips": "辅助校准位置可能不准确,<br>请检查并拖动到{direction}相同位置",
+            "set_visible_btn": "设置热点可视",
+            "save_visible_btn": "保存当前设置",
+            "save_hotspot_done": "保存热点成功",
+            "save_hotspot_fail": "保存热点失败",
+            "delete_hotspot_tips": "是否删除当前热点?",
+            "delete_hotspot_done": "热点删除成功",
+            "delete_hotspot_fail": "热点删除失败",
+            "cant_add_hotspot_tips": "无法添加热点",
+            "cant_add_hotspot_content": "热点数目已达最大:{limit}",
+            "link_text_tips": "链接标题",
+            "link_href_tips": "链接",
+            "link_text_require": "请填写链接文本",
+            "link_href_require": "请填写链接地址",
+            "add_media": "添加多媒体"
+        },
+        "guide": {
+            "__name": "自动导览",
+            "route": "导航路线",
+            "view": "切换视角",
+            "record": "开始录制",
+            "record_audio": "录音",
+            "pause": "暂停",
+            "stop": "停止",
+            "end": "结束",
+            "delete": "删除",
+            "continue": "继续录制",
+            "preview": "查看",
+            "clear": "清空",
+            "sync": "声画同步",
+            "sound": "录制音频",
+            "file": "上传音频",
+            "file_add": "添加音频",
+            "tips": "点击开始录制导览",
+            "start": "开始",
+            "finish": "完成录制",
+            "less": "小于",
+            "replace_tips": "是否重新录制导览?",
+            "replace_content": "重新录制将覆盖之前的数据",
+            "sound_open_fail_tips": "麦克风开启失败",
+            "sound_tips": "麦克风开启失败,是否继续录制?",
+            "sound_content": "您需要在浏览器的设置中允许此网站使用麦克风,并且添加麦克风设备,然后刷新该页面。",
+            "upload_sound_done": "上传语音讲解成功",
+            "upload_sound_fail": "上传语音讲解失败",
+            "delete_sound_done": "删除语音讲解成功",
+            "delete_sound_fail": "删除语音讲解失败",
+            "room_title": "标题",
+            "room_title_tips": "请输入导览标题",
+            "room_title_require": "请输入标题",
+            "room_panel_title": "导览信息",
+            "room_sound_title": "录制音频",
+            "delete_video_content": "您当前录制的画面将会被删除",
+            "delete_file_content": "您当前上传的语音讲解将会被删除",
+            "delete_sound_content": "已录制配音将会被删除",
+            "camera_save_success": "镜头保存成功",
+            "clear_video_tip": "您当前录制的内容将会被清空",
+            "saving_sound": "正在保存录音",
+            "save_sound_done": "录音保存成功",
+            "save_sound_fail": "录音保存失败",
+            "no_sound_tips": "当前不支持录音<br>可在微信或电脑端录音",
+            "sound_success_tips": "麦克风开启成功",
+            "sound_fail_tips": "当前不支持录音<br>公众号信息配置错误",
+            "wechat_sound_fail_tips": "微信麦克风开启失败",
+            "open_sound_guide_tips": "可能您若要开启录音,请于微信的设置—隐私—授权管理中开启。且保证录音设备正常",
+            "sound_cant_open_tips": "此浏览器不支持录音。<br>建议更换其他主流浏览器,体验更佳",
+            "replace_sound_tips": "是否重新录制?",
+            "replace_sound_content": "已有语音讲解将会被替换",
+            "merge_sound_fail": "合并语音讲解失败",
+            "sound_limit": "录制时长为{time}分钟,当前已经达到上限",
+            "select_to_record": "请选择对应点位进行本区域的导览录制"
+        },
+        "sign": {
+            "title": "Logo样式",
+            "size": "Logo大小",
+            "style0": "样式一",
+            "style1": "样式二",
+            "style2": "样式三",
+            "style3": "手动上传"
+        },
+        "walk": {
+            "title": "漫游可行",
+            "tips1": "通过设置漫游可行,进一步优化在漫游时出现的体验;例如,您在漫游时,出现穿透房间的情况。",
+            "tips2": "通过点选各个漫游点的连线即可设置漫游点的可行性。",
+            "save": "保存当前设置",
+            "hide": "隐藏该点位置",
+            "show": "显示该点位置",
+            "pano_tips": "提示:您隐藏了初始画面点位,此操作将使进入场景后无法漫游。"
+        },
+        "model": {
+            "__name": "修整模型",
+            "cad": "CAD视角",
+            "cad_download": "平面图下载",
+            "view": "切换视角",
+            "title": "增添结构",
+            "title_door": "门窗类",
+            "title_component": "构建类",
+            "title_other": "其他",
+            "attribute": "属性",
+            "door": "门",
+            "slideDoor": "移门",
+            "casement": "窗",
+            "bayCase": "飘窗",
+            "groundCase": "落地窗",
+            "column": "柱子",
+            "furnColumn": "框架柱",
+            "furnFlue": "烟道",
+            "point": "点",
+            "line": "墙",
+            "tagging": "标注",
+            "tagging_name_tips": "请输入名称",
+            "tagging_area_tips": "输入面积,支持小数点后面两位",
+            "direction": "指南针",
+            "wallLine": "墙关联房间",
+            "widget_delete": "{widget}将被删除",
+            "panel_btn_default": "恢复默认",
+            "panel_btn_delete": "删除部件",
+            "attr_angle": "旋转角度",
+            "attr_within": "翻转方向",
+            "attr_ewidth": "宽度",
+            "attr_eheight": "高度",
+            "attr_tick": "厚度",
+            "attr_showTitle": "标注名称",
+            "attr_showContent": "标注面积",
+            "attr_top": "顶部",
+            "attr_bottom": "底部",
+            "success": "成功添加{widget}",
+            "error_location": "当前位置无法添加{widget}",
+            "error_outdoor": "户外无法添加{widget}",
+            "error_something": "当前位置点有建筑,无法添加{widget}",
+            "error_widget": "当前位置不可添加{widget}",
+            "enter_adjust_floor": "进入地面高度调节模式",
+            "exit_adjust_floor": "退出地面高度调节模式",
+            "color_title": "量角器颜色:"
+        },
+        "videos": {
+            "__name": "添加视频",
+            "tips": "请先点击场景的基准面,确定视频位置",
+            "panel_title": "视频属性",
+            "panel_preview": "预览",
+            "panel_upload": "上传视频",
+            "panel_upload_tips": "支持MP4视频格式",
+            "panel_move": "位移",
+            "panel_zoom": "缩放",
+            "panel_thickness": "厚度",
+            "recoverRatio": "恢复原始比例",
+            "recoverRatioTip": "恢复视频文件原始长宽比"
+        },
+        "custom": {
+            "model_title": "模型下载",
+            "uploading": "文件上传中...",
+            "download": "原始模型下载",
+            "download_panos": "下载点位图",
+            "upload": "修改模型上传",
+            "title1": "模型下载/上传功能教程",
+            "tips11": "1.下载场景模型的压缩包后,使用三维软件打开解压后文件“mesh.obj”,即可开展编辑;",
+            "tips12": "2.编辑完成后,建议将模型贴图进行烘焙,烘焙贴图需控制在1.5M以内,同时,保存的obj文件需要控制在3M以内;",
+            "tips13": "3.完成1、2步骤后,将obj,mtl,贴图打包为zip压缩包上传即可。",
+            "title2": "注:",
+            "tips21": "1.请尽量控制文件大小,以免浏览的时候卡顿,影响体验。",
+            "tips22": "2.请将obj及压缩包的名称保持一致,否则会替换失败。",
+            "get_image_fail": "获取贴图失败,可能网络状态不佳,请检查您的网络设置并重新尝试。",
+            "download_model_fail": "模型下载失败",
+            "reupload_tips": "上传后点击保存并发布生效",
+            "panoramic_upload": "全景照片上传",
+            "panoramic_upload_tips": "上传后点击“保存并发布”即生效",
+            "panoramic_upload_box_tips": "请上传对应点位名称的JPG图片",
+            "panoramic_download": "全景照片下载",
+            "panoramic_download_tips": "请勿修改照片名称和格式",
+            "panoramic": "全景图片",
+            "ball_video": "球幕视频",
+            "ball_video_upload_tips": "上传后点击“保存并发布”即生效",
+            "ball_video_download_tips": "请勿修改视频名称和格式",
+            "ball_video_upload_box_tips": "球幕视频支持MP4格式,不超过1024M",
+            "upload_title": "上传文件",
+            "upload_format_error": "文件格式错误,请重新上传",
+            "upload_name_error": "文件名称错误,请重新上传",
+            "upload_success": "上传成功,保存并发布后才能生效",
+            "upload_code_5017": "上传模型失败,请参照右侧教程",
+            "upload_code_5018": "zip文件只能有一层目录或无目录",
+            "upload_code_5019": "必须有且仅有一个obj和mtl文件",
+            "upload_code_5020": "贴图需控制在1.5M以内,obj文件需要控制在3M以内。",
+            "upload_code_5012": "数据不正常",
+            "upload_code_5023": "上传文件格式不正确,只能是jpg或mp4格式",
+            "download_tips": "下载后名称与格式请勿修改",
+            "download_fail": "下载失败"
+        },
+        "vrhouse": {
+            "__name": "VR看房",
+            "linkto_management": "前往云看房管理后台"
+        },
+        "business": {
+            "__name": "商圈模块",
+            "list_title": "场景热点列表"
+        },
+        "link": {
+            "__name": "跳转关联",
+            "title1": "锁定关联点",
+            "title2": "编辑关联点",
+            "list_text": "已添加关联点",
+            "btn_new_text": "添加关联点",
+            "btn_add_title": "关联点定位",
+            "btn_add_text": "确定关联点位置",
+            "btn_add_desc": "将关联点标记并拖动到合适的位置。",
+            "btn_edit_text": "修改关联点位置",
+            "style": "关联点样式",
+            "style_name": "样式",
+            "style_desc": "支持自定义图标,建议尺寸为100x100 像素,格式为jpg/png/gif。",
+            "style_dele": "是否删除该样式?<br />如该样式在其它关联点使用,也将被删除",
+            "desc_title": "描述",
+            "desc_tips": "请输入描述内容",
+            "desc_require": "请输入描述内容",
+            "type": "选择关联类型",
+            "type_photo": "支持上传宽高比为2:1的单张全景图片,建议像素不小于6000x3000像素,文件不超过120M。",
+            "type_photo_require": "请上传全景图",
+            "type_link": "请输入目标场景链接",
+            "type_link_tips": "http(s)://",
+            "type_link_require": "请输入场景链接",
+            "enter_title": "进入画面",
+            "enter_require": "请设置进入画面",
+            "enter_btn_text": "设置进入画面",
+            "outer_title": "跳出点位置",
+            "outer_style": "跳出点样式",
+            "outer_desc": "支持自定义图标,建议尺寸为100x100像素,格式为jpg/png/gif。",
+            "outer_require": "请设置跳出点位置",
+            "outer_btn_text": "设置跳出点位置"
+        }
+    },
+    "login": {
+        "__name": "登录",
+        "title": "提示",
+        "login": "去登录",
+        "logon": "登录完毕,继续",
+        "login_tips": "您没有登录,请于主页登录后再编辑"
+    },
+    "common": {
+        "__name": "公用",
+        "on": "打开",
+        "off": "关闭",
+        "add": "添加",
+        "set": "确定",
+        "ok": "确定",
+        "save": "保存",
+        "cancel": "取消",
+        "complete": "完成",
+        "edit": "修改",
+        "giveup": "取消",
+        "commit": "提交",
+        "photo": "图片",
+        "voice": "音频",
+        "video": "视频",
+        "bgm": "音乐",
+        "crop": "裁剪",
+        "upload": "上传",
+        "download": "下载",
+        "change": "更改",
+        "unnamed": "未命名",
+        "publish": "保存并发布",
+        "publish_text": "保存并发布成功!",
+        "publish_tips": "是否立刻前往观看您的场景?",
+        "publish_btn_ok": "立刻前往",
+        "publish_btn_no": "暂时不去",
+        "public": "公开",
+        "private": "加密",
+        "waiting": "请稍等...",
+        "audio": "音频",
+        "second": "秒",
+        "frame": "帧",
+        "delete": "删除",
+        "prev": "上一步",
+        "next": "下一步",
+        "meter": "约{meter}米",
+        "guide": "导览",
+        "rule": "测量工具",
+        "roaming": "漫游",
+        "continue": "继续",
+        "ensure_delete": "确定删除",
+        "text_limit": "限制{limit}字内",
+        "default": "默认",
+        "custom": "自定义",
+        "back": "返回",
+        "will_delete": "将被删除",
+        "setup": "设置",
+        "exit": "退出",
+        "music": {
+            "__name": "背景音乐",
+            "none": "无",
+            "cheerfu": "欢快",
+            "ethereal": "空灵",
+            "rhythmic": "节奏",
+            "nostalgic": "怀旧",
+            "missing": "想念",
+            "retro": "复古",
+            "strings": "琴弦",
+            "happy": "愉快"
+        },
+        "category": {
+            "__name": "分类",
+            "museum": "文博",
+            "estate": "地产",
+            "eshop": "电商",
+            "catering": "餐饮",
+            "home": "家居",
+            "other": "其他"
+        },
+        "uploads": {
+            "__name": "文件上传",
+            "uploading": "上传中",
+            "uploaded": "已上传",
+            "wait": "等待上传...",
+            "error": "上传出错",
+            "add": "添加文件",
+            "start": "开始上传",
+            "file_require": "请添加文件",
+            "cant_upload": "您的浏览器不支持上传文件",
+            "not_support": "您选择的不是浏览器支持的{fileType}文件,请重新选择",
+            "too_large": "文件过大,不能大于{size}MB",
+            "too_large_reupload_tips": "上传视频过大,请重新上传"
+        },
+        "tips": {
+            "__name": "提示",
+            "wait": "请等待..."
+        }
+    },
+    "tips": {
+        "__name": "弹窗、提示",
+        "title": "提示",
+        "set_done": "设置成功",
+        "set_fail": "设置失败",
+        "save_done": "保存成功",
+        "save_fail": "保存失败",
+        "delete": "是否删除?",
+        "delete_done": "删除成功",
+        "delete_fail": "删除失败",
+        "upload_done": "上传成功",
+        "upload_fail": "上传失败",
+        "exception": "异常错误",
+        "network_error": "网络异常,请稍后再试",
+        "file_notfound": "文件不存在",
+        "scene_notfound": "场景不存在",
+        "params_notfound": "缺少必要参数",
+        "camera_notfound": "找不到该场景对应的相机",
+        "password_error": "密码错误",
+        "data_error": "数据不正常",
+        "auth_deny": "无权操作该场景",
+        "clear": "是否清空?",
+        "upload_pic_fail": "上传图片失败",
+        "wait": "请稍后...",
+        "house_type_save_fail": "户型保存失败"
+    },
+    "show": {
+        "__name": "展示页面",
+        "on": "开启",
+        "off": "关闭",
+        "measure": "测距工具",
+        "measure_start": "起点",
+        "measure_end": "终点",
+        "measure_button": "确定{status}",
+        "location_up": "上方",
+        "location_left": "左侧",
+        "location_start_tips": "请先在{direction}点击",
+        "location_end_tips": "起点确定完毕,请定位终点",
+        "vr": "VR模式",
+        "share": "分享",
+        "music": "{status}音乐",
+        "password_tips": "密码",
+        "password_require": "请输入密码"
+    },
+    "components": {
+        "user_guid": {
+            "__name": "用户引导",
+            "title": "操作提示",
+            "pano": "行走<br />点击任意方向移动",
+            "rotate": "旋转视角<br />左右滑动屏幕,旋转视觉",
+            "zoom": "缩放<br />双指滑动放大或缩小视图",
+            "set": "我知道了",
+            "seeMore": "查看详情",
+            "upgradeInfo": "更新内容"
+        }
+    },
+    "material": {
+        "works": {
+            "my": "我的作品",
+            "create": "创建作品",
+            "search": "请输入关键字",
+            "preview": "查看",
+            "edit": "编辑",
+            "share": "分享",
+            "delete": "删除",
+            "no_works": "您还没有作品,请先创建作品~",
+            "work_link": "作品链接",
+            "work_qrCode": "作品二维码",
+            "download_qrCode": "下载二维码",
+            "copy_link": "复制链接",
+            "work_preview": "查看作品",
+            "new_blank": "新窗口打开",
+            "cancel": "取消",
+            "no_link": "链接未生成,请编辑作品添加素材",
+            "had_created": "您已成功创建作品!",
+            "goto_preview": "查看",
+            "continue_edit": "继续编辑",
+            "had_created_but_no_link": "已成功新建作品,但刷新作品列表失败,请稍后手动刷新。",
+            "delete_work": "删除作品",
+            "comfirm_delete": "确定要删除作品吗?",
+            "online_gen": "在线制作",
+            "cam_gen": "相机生成",
+            "calcing": "场景计算中...",
+            "no_works_cam": "您还没有相机拍摄生成的作品,快去拍摄吧~"
+        },
+        "components": {
+            "prev": "上一张",
+            "next": "下一张",
+            "zoom_in": "放大",
+            "zoom_out": "缩小",
+            "delete": "删除",
+            "fullscreen": "全屏",
+            "cancel_fullscreen": "取消全屏",
+            "pano_setting": "全景图封面设置",
+            "drag_to_cut": "拖动画面截取封面",
+            "cutting": "截图",
+            "preview_cover": "封面预览",
+            "rename_material": "重命名素材"
+        }
+    },
+    "gather": {
+        "select_material": "选择素材",
+        "no_title": "无标题",
+        "name": "四维全景",
+        "editpage_name": "四维全景编辑器",
+        "my_works": "我的作品",
+        "my_material": "我的素材",
+        "panorama": "全景图",
+        "pano": "全景图",
+        "image": "图片",
+        "audio": "音频",
+        "video": "视频",
+        "scene": "三维场景",
+        "siweikankan": "四维看看",
+        "siweikanjian": "四维看见",
+        "siweiSS": "四维深时",
+        "siweiSG": "四维深光",
+        "keywords": "请输入关键字",
+        "how_to_shoot": "如何拍摄三维场景",
+        "pano_size": "请上传2:1、120MB以内、jpg格式的图片",
+        "cancel": "取消",
+        "comfirm": "确定",
+        "pano_fail": "格式错误,请上传2:1、120MB以内、jpg格式的全景图片",
+        "pano_limit": "过大,请上传2:1、120MB以内、jpg格式的全景图片",
+        "edit_cover": "编辑封面",
+        "img_fail": "格式错误,请上传10MB以内、jpg/png/gif格式的图片",
+        "img_limit": "过大,请上传10MB以内、jpg/png/gif格式的图片",
+        "img_size": "请上传10MB以内、jpg/png/gif格式的图片",
+        "audio_size": "请上传20MB以内、mp3格式的音频",
+        "audio_limit": "过大,请上传20MB以内、mp3格式的音频",
+        "audio_fail": "格式错误,请上传20MB以内、mp3格式的音频",
+        "upload_material": "上传素材",
+        "new_folder": "新建文件夹",
+        "new_folder_placeholder": "请输入文件夹名,限15字",
+        "folder_name_already_used": "文件夹已存在",
+        "move_folder": "移动",
+        "move_folder_to": "移动到",
+        "no_folder_need_create": "暂无文件夹,请先创建文件夹",
+        "rename_folder_placeholder": "请输入文件夹名,限15字",
+        "video_size": "请上传200MB以内、mp4格式的视频",
+        "video_limit": "过大,请上传200MB以内、mp4格式的视频",
+        "video_fail": "格式错误,请上传200MB以内、mp4格式的视频",
+        "search_material": "请输入关键字",
+        "rename": "重命名",
+        "delete": "删除",
+        "no_search_result": "未搜索到结果",
+        "no_material_result": "暂无素材~",
+        "had_load": "已加载{msg}条",
+        "uploading": "正在上传 {msg}",
+        "success": "操作成功",
+        "delete_success": "删除成功",
+        "delete_fail": "删除失败",
+        "loading": "加载中",
+        "too_long_word": "名称过长,请上传标题在50字以内的图片",
+        "too_long_word_audio": "名称过长,请上传标题在50字以内的音频",
+        "too_long_word_video": "名称过长,请上传标题在50字以内的视频",
+        "uploading_material": "正在上传素材",
+        "cutting": "正在切图处理",
+        "material_upload_fail": "素材上传失败",
+        "material_cutting_fail": "素材切图失败",
+        "fill_complete": "请填写完整信息",
+        "fill_phone": "请正确填写电话号码",
+        "edit_success": "修改成功",
+        "setting_success": "设置成功",
+        "scene_link_copy_tips": "复制成功",
+        "scene_link_copy_failed": "复制失败",
+        "delete_material": "删除素材",
+        "comfirm_delete_material": "确定要删除素材吗?",
+        "can_not_delete_folder_when_uploading": "正在上传素材,请不要删除文件夹。",
+        "delete_folder": "删除文件夹",
+        "comfirm_delete_folder": "确定要删除文件夹以及包含的素材吗?",
+        "save_done": "保存成功",
+        "nothing_edit": "您还未创建任何内容哦",
+        "at_least_one_scene": "至少添加一个场景才可预览,请前往“场景导航”添加",
+        "exitVr": "退出VR模式",
+        "dir": "目录",
+        "root_dir": "根目录",
+        "no_more_data": "没有更多数据了",
+        "converinfo_no_valid": "请将开场封面内容填写完整"
+    },
+    "personal_center": {
+        "personal_center": "个人中心",
+        "logout": "退出登录",
+        "login": "登录",
+        "register": "注册"
+    },
+    "edit_page": {
+        "back_myworks": "返回我的作品",
+        "preview": "查看",
+        "settings": "基础",
+        "navigation": "导航",
+        "viewpoint": "视角",
+        "hotspot": "热点",
+        "explanation": "讲解",
+        "mask": "遮罩",
+        "save": "保存"
+    },
+    "edit_settings": {
+        "coverBase_button": "开场封面",
+        "coverBase_button_tips": "整个作品的开场内容,可以设置为图片或视频形式。",
+        "cover_pull_tit": "封面类型",
+        "cover_show": "显示开场封面",
+        "coverSelecVideoAndImg": "图片+视频",
+        "coverSelecImg": "图片",
+        "coverSelecVideo": "视频",
+        "coverImgTit": "图片设置",
+        "coverUpTit1": "建议1920*1080px,2M以内,jpg / gif / png格式",
+        "coverUpTit2": "建议750*1624px,1M以内,jpg / gif / png格式",
+        "coverUpTit3": "建议300*300px,100kb以内,jpg格式",
+        "coverUpTit4": "建议1920*1080px,5M以内,mp4格式",
+        "coverUpTit5": "建议750*1624px,5M以内,mp4格式",
+        "coverImgLoc1": "居中",
+        "coverImgLoc2": "全屏",
+        "coverImgBacTit": "背景设置",
+        "coverImgBacSelec1": "纯色填充",
+        "coverImgBacSelec2": "图片平铺",
+        "coverVideoTit": "视频设置",
+        "select_video": "选择视频",
+        "coverImageInWay": "进入方式",
+        "coverImageInWayTit": "是否开启3秒倒计时后自动进入",
+        "coverVideoInWay": "进入方式",
+        "coverVideoInWayTit": "视频播放完自动进入",
+        "coverVideoControl": "视频控件",
+        "coverVideoControlTit": "视频控件是否显示",
+        "coverImageOrder": "出现顺序",
+        "coverImageOrderTit1": "开场图片先出现",
+        "coverImageOrderTit2": "开场图片后出现",
+        "auto_pano": "自动巡游",
+        "enter_auto": "进入全景图自动巡游(3分钟完整巡游一次)",
+        "set_bgm": "设置背景音乐",
+        "add_audio": "添加音频",
+        "change_audio": "更换音频",
+        "custom_button": "自定义按钮",
+        "custom_button_tips": "自定义按钮可为作品添加联系方式或网站链接等,设置可见后即可在作品显示。",
+        "edit": "编辑",
+        "hide": "隐藏",
+        "show": "显示",
+        "button_name": "按钮名称",
+        "button_type": "按钮类型",
+        "button_open_method": "打开方式",
+        "button_placeholder": "请输入按钮名称",
+        "please_input": "请输入",
+        "phone": "电话号码",
+        "link": "链接地址",
+        "phone_short": "电话",
+        "link_short": "链接",
+        "custom_logo": "自定义LOGO",
+        "show_logo": "显示LOGO",
+        "logo_size": "300*300px,600kb以内,支持<br>jpg/png/gif格式",
+        "mask_setting": "遮罩设置",
+        "sky_mask": "天空遮罩",
+        "sky_tips": "天空遮罩显示在场景的顶部,地面遮罩显示在场景的底部。",
+        "mask_size": "建议500*500px,<br/>支持jpg/png/gif格式",
+        "bottom_mask": "地面遮罩",
+        "opening_setting": "设置开场动画",
+        "opening_animation_show": "显示开场动画",
+        "opening_tips_setting": "提示设置",
+        "opening_tips": "开场提示仅适用于全景图。若初始场景为三维模\n型,以下开场提示不适用。",
+        "pc": "PC端",
+        "mobile": "移动端",
+        "select_image": "选择图片",
+        "opening_size": "建议300*300px,600kb以内,<br/>支持jpg/png/gif格式",
+        "show_setting": "显示设置",
+        "first_notice": "仅首次打开链接时,提示",
+        "password_setting": "设置访问密码",
+        "password_placeholder": "请输入访问密码,限20位",
+        "base_setting": "基础设置",
+        "setting_cover": "设置封面",
+        "cover_size": "512*512px,支持jpg/png/gif格式",
+        "title": "标题",
+        "description": "简介",
+        "work_placeholder": "请输入作品标题",
+        "intro_placeholder": "请输入文字内容,限2000字",
+        "help_center": "帮助中心",
+        "display_time": "显示时间",
+        "no_display": "(不显示)",
+        "second": "秒"
+    },
+    "table": {
+        "material": "素材",
+        "name": "名称",
+        "fileSize": "大小",
+        "detail": "细节",
+        "dpi": "分辨率",
+        "createTime": "创建时间",
+        "updateTime": "修改时间"
+    },
+    "baseSetting": {
+        "opa1": "小行星开场",
+        "opa2": "小行星巡游开场",
+        "opa3": "小行星缩放开场",
+        "opa4": "水平巡游开场",
+        "opa5": "水晶球开场",
+        "openTips": "开场提示",
+        "openAnimate": "开场动画",
+        "password": "访问密码",
+        "cruise": "自动巡游",
+        "bgm": "背景音乐",
+        "logo": "自定义LOGO",
+        "customCover": "自定义遮罩",
+        "customButton": "自定义按钮",
+        "openCover": "开场封面"
+    },
+    "customButton": {
+        "phone": "电话",
+        "link": "链接",
+        "self": "弹出层打开",
+        "target": "新窗口打开"
+    },
+    "navigation": {
+        "scene_edit_tips": "请前往四维时代个人中心编辑场景",
+        "go_scene_editor": "立即前往",
+        "scene_navigation": "场景导航",
+        "add_group": "新建分组",
+        "scene_tips": "场景素材包括全景图和三维场景,您可自定义分组及场景的排列顺序。",
+        "add_two_group": "新建二级分组",
+        "add_pano_or_scene": "新增全景图或三维场景",
+        "group_one": "一级分组",
+        "group_two": "二级分组",
+        "default_group_two": "默认二级分组",
+        "init_scene": "初始场景",
+        "setting_init_scene": "设置初始场景",
+        "init_scene_tips": "初始场景为查看链接时进入的第一个场景,未设\n置时,不固定从某一场景打开。",
+        "edit_init_scene": "修改场景",
+        "delete_init_scene": "删除场景",
+        "keep_one_scene": "请至少保留一个场景",
+        "keep_one_group": "请至少保留一个分组",
+        "enter_name": "输入名字",
+        "rename": "重命名",
+        "delete": "删除",
+        "pano": "全景",
+        "scene": "三维",
+        "scene_name": "场景",
+        "already_exists": "{msg}已存在,不可重复添加"
+    },
+    "screen": {
+        "init_screen": "初始画面",
+        "screen_tips": "初始画面为进入场景时第一画面,请拖动全景图选择合适的画面设置。",
+        "setting_screen": "将当前视角设为初始画面",
+        "goto_4dkk_edit_tips": "请前往四维时代个人中心编辑~"
+    },
+    "hotspot": {
+        "hotspot_type": "热点类型",
+        "img_size": "最多添加20张图片",
+        "hotspot_tips": "在全景图中添加图标热点,并设置热点的效果。",
+        "add_hotspot": "添加热点",
+        "current_hotspots": "当前全景图热点",
+        "delete": "删除",
+        "add": "新增",
+        "edit": "编辑",
+        "success": "成功",
+        "click_to_comfirm": "单击确定热点位置",
+        "no_hotspot": "暂无热点信息~",
+        "close_dialog": "热点内容未编辑完,确定要关闭吗",
+        "hotspot_name": "热点",
+        "hotspot_icon": "热点图标",
+        "hotspot_icon_type": {
+            "system_icon": "系统图标",
+            "custom_image": "自定义图片",
+            "serial_frame": "序列帧",
+            "personalized_tag": "个性标签"
+        },
+        "add_icon": "添加图标",
+        "add_custom_icon_rule": "300*300px,支持jpg/png/gif格式",
+        "add_serial_frame_rule": "最大宽度:300px,高度不限",
+        "frame_total_number": "序列帧总帧数",
+        "frame_duration": "总播放时长",
+        "select_icon": "选择图标",
+        "if_show_marking_line": "是否显示标注线",
+        "shape_filling_color": "形状填充",
+        "shape_border_color": "形状描边",
+        "text_color": "文字颜色",
+        "text_direction": "文字排序",
+        "left_to_right": "从左到右",
+        "top_to_down": "从上到下",
+        "if_wrap": "是否换行显示",
+        "text_num_per_line": "每行显示",
+        "words": "字",
+        "hotspot_title": "热点标题",
+        "show_hotspot_title": "是否显示标题",
+        "title_show_mode": "显示方式",
+        "show_on_hover": "鼠标悬浮",
+        "always_show": "一直显示",
+        "never_show": "不显示",
+        "title_position": "相对图标位置",
+        "title_top": "上",
+        "title_bottom": "下",
+        "title_left": "左",
+        "title_right": "右",
+        "title_custom": "自定义拖动标题",
+        "title_placeholder": "请输入标题",
+        "effect_settings": "效果设置",
+        "cancel": "取消",
+        "finish": "完成",
+        "scene": "切换场景",
+        "image": "图片",
+        "video": "视频",
+        "audio": "音频",
+        "link": "链接",
+        "textarea": "文本",
+        "tag": "标签",
+        "image_text": "图文",
+        "article": "文章",
+        "pdf": "PDF",
+        "phone": "电话",
+        "hotspot_type_specific_settings_title": {
+            "scene": "热点场景",
+            "image": "热点图片",
+            "video": "热点视频",
+            "audio": "热点音频",
+            "link": "热点链接",
+            "textarea": "热点文本",
+            "tag": " ",
+            "imageText": "热点图文",
+            "article": "热点文章",
+            "pdf": "热点PDF",
+            "phone": "热点电话"
+        },
+        "icon_size": "图标大小",
+        "unit": "倍",
+        "add_audio": "添加音频",
+        "change_audio": "更换音频",
+        "select_audio": "选择音频",
+        "add_image": "添加图片",
+        "add_tooltips": "添加",
+        "select_image": "选择图片",
+        "popup": "弹出层打开",
+        "newTab": "新窗口打开",
+        "add_scene": "添加场景",
+        "change_scene": "更换场景",
+        "text_placeholder": "请输入文字内容,限2000字",
+        "add_video": "添加视频",
+        "select_video": "添加视频",
+        "change_video": "更换视频",
+        "MB_limit": "不超过{value}MB",
+        "text_content": "文字内容",
+        "apply_to_all": "应用到所有",
+        "phone_placeholder": "请输入电话号码",
+        "phone_error_tip": "请输入正确的电话号码",
+        "select_pdf": "添加PDF",
+        "change_pdf": "更换PDF",
+        "pdf_invalid_tip": "请选择50MB以内、PDF格式的文件",
+        "add_article": "添加文章内容",
+        "edit_article_title": "编辑文章",
+        "edit_article": "编辑文章内容",
+        "edit_text_title": "编辑文本",
+        "edit_text": "编辑文本内容",
+        "cancel_add_hotspot": "已编辑内容将不会保存,确定要关闭吗?"
+    },
+    "explanation": {
+        "explanation_settings": "语音讲解",
+        "explanation_tips": "您可以为当前全景图添加语音讲解音频。",
+        "add_audio": "添加音频",
+        "change_audio": "更换音频",
+        "default_open": "默认开启",
+        "loop": "循环播放",
+        "disabled_tip": "三维场景暂不支持添加讲解"
+    },
+    "mask": {
+        "cover_tips": "您可以为当前全景图添加遮罩。",
+        "select_image": "选择图片",
+        "apply_to_all_pano": " 应用到全部全景图",
+        "follow_the_scene": " 跟随场景转动",
+        "scaling_ratio": "缩放比例",
+        "unit": "倍",
+        "is_apply_to_all_pano": "是否应用到全部全景图?"
+    },
+    "style_key": {
+        "menu_width": "58px",
+        "play_width": "0"
+    },
+    "tips_code": {
+        "FAILURE_2020": "appId为空",
+        "FAILURE_2021": "appId解析有误",
+        "FAILURE_5001": "token为空",
+        "FAILURE_5002": "redis token不存在",
+        "FAILURE_5003": "token invalid(无效)",
+        "FAILURE_3001": "对象不存在",
+        "FAILURE_3011": "当前无操作权限",
+        "FAILURE_3020": "空文件",
+        "FAILURE_3021": "非法文件",
+        "FAILURE_3022": "上传文件超过最大值",
+        "FAILURE_3023": "非法格式",
+        "FAILURE_3025": "上传失败",
+        "FAILURE_3101": "素材已经被引用, 不能删除",
+        "FAILURE_3102": "计算中的场景不能删除",
+        "FAILURE_3103": "服务器someData.json文件不存在",
+        "FAILURE_3200": "请求第三方API失败",
+        "FAILURE_3201": "请求第三方API返回失败",
+        "FAILURE_3104": "存在同名文件夹,请直接移动素材",
+        "FAILURE_error": "网络异常,请稍后再试",
+        "tips": "提示",
+        "relogin": "登录状态失效,请重新登录",
+        "goto_login": "去登录",
+        "login_success": "登录完毕,继续",
+        "need_one": "请至少保留一个场景。",
+        "not_less_than": "文件名称不允许超过50个字符",
+        "work_had_delete": "作品已被删除,无法编辑",
+        "material_can_not_delete": "素材已被引用,无法删除。",
+        "illegality_image": "不支持此图片",
+        "password_error": "密码错误",
+        "FAILURE_3024": "存储空间已满",
+        "loading_fail": "加载失败"
+    }
+}

+ 1 - 1
packages/qjkankan-editor/src/mixins/debuggerHelper.js

@@ -1,6 +1,6 @@
 import Vue from "vue";
 import Vue from "vue";
 export const debuggerHelper = (show = true) => {
 export const debuggerHelper = (show = true) => {
-  const version = "1.5.0--" + process.env.VUE_APP_DEBBUG_FLAG;
+  const version = "1.7.0--" + process.env.VUE_APP_DEBBUG_FLAG;
   if (show) {
   if (show) {
     // log.setColors({
     // log.setColors({
     var css = "background: #28b561; color:#fff;font-size:14px;padding:5px;";
     var css = "background: #28b561; color:#fff;font-size:14px;padding:5px;";

+ 10 - 2
packages/qjkankan-editor/src/pages/Edit.vue

@@ -15,9 +15,17 @@ export default {
   computed: {
   computed: {
     ...mapGetters(["isInfoChangedSinceSave"]),
     ...mapGetters(["isInfoChangedSinceSave"]),
   },
   },
+  watch: {
+    $route() {
+      const allTooltips = document.querySelectorAll(".el-tooltip__popper");
+      if (allTooltips.length) {
+        Array.from(allTooltips).map((node) => document.body.removeChild(node));
+      }
+    },
+  },
   mounted() {
   mounted() {
     document.title = this.$i18n.t("gather.editpage_name");
     document.title = this.$i18n.t("gather.editpage_name");
-
+    window.store = this.$store;
     window.addEventListener(
     window.addEventListener(
       "beforeunload",
       "beforeunload",
       (e) => {
       (e) => {
@@ -30,7 +38,7 @@ export default {
         capture: true,
         capture: true,
       }
       }
     );
     );
-    this.$store.dispatch('notice/getNotice')
+    this.$store.dispatch("notice/getNotice");
   },
   },
 };
 };
 </script>
 </script>

+ 47 - 57
packages/qjkankan-editor/src/router/editorRouter.js

@@ -1,24 +1,23 @@
-import Vue from 'vue'
-import Router from 'vue-router'
+import Vue from "vue";
+import Router from "vue-router";
 import { PCMenu } from "../config/menu.js";
 import { PCMenu } from "../config/menu.js";
-import { checkWork, checkLogin, getPanoInfo } from '@/api'
-import store from '../Store'
+import { checkWork, checkLogin, getPanoInfo } from "@/api";
+import store from "../Store";
 
 
-import {i18n} from '@/lang'
+import { i18n } from "@/lang";
 
 
+let vue = new Vue();
 
 
-let vue = new Vue()
+import { LoginDetector, OnlineDetector } from "@/utils/starter";
+Vue.use(Router);
 
 
-import { LoginDetector,OnlineDetector } from "@/utils/starter";
-Vue.use(Router)
-
-const originalPush = Router.prototype.push
-Router.prototype.push = function push (location) {
-  return originalPush.call(this, location).catch(err => err)
-}
+const originalPush = Router.prototype.push;
+Router.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch((err) => err);
+};
 
 
 let routes = [];
 let routes = [];
-PCMenu.forEach(item => {
+PCMenu.forEach((item) => {
   routes.push({
   routes.push({
     name: item.name,
     name: item.name,
     path: `${item.link}`,
     path: `${item.link}`,
@@ -27,78 +26,69 @@ PCMenu.forEach(item => {
       hasPreviewArea: item.hasPreviewArea,
       hasPreviewArea: item.hasPreviewArea,
       previewAreaExtraLeft: item.previewAreaExtraLeft,
       previewAreaExtraLeft: item.previewAreaExtraLeft,
     },
     },
-    component: () => import(`../views/${item.name}/index.vue`)
+    component: () => import(`../views/${item.name}/index.vue`),
   });
   });
 });
 });
 
 
-
 LoginDetector.register(
 LoginDetector.register(
-  detector => new Promise(resolve => detector.resolve(resolve))
+  (detector) => new Promise((resolve) => detector.resolve(resolve))
 );
 );
 
 
 OnlineDetector.register(
 OnlineDetector.register(
-  detector => new Promise(resolve => detector.resolve(resolve))
+  (detector) => new Promise((resolve) => detector.resolve(resolve))
 );
 );
 
 
 //检验是不是该用户作品
 //检验是不是该用户作品
 
 
-
-checkWork().then(res=>{
+checkWork().then((res) => {
   if (res.data) {
   if (res.data) {
-    checkLogin().then(response => {
+    checkLogin().then((response) => {
       if (response.code == 3005) {
       if (response.code == 3005) {
-        store.commit('UpdateIsShowState', false)
-        vue.$bus.emit('canLoad',false) 
-        return vue.$alert({content: '当前无操作权限'});
-      } else{
-        vue.$bus.emit('canLoad',true) 
+        store.commit("UpdateIsShowState", false);
+        vue.$bus.emit("canLoad", false);
+        return vue.$alert({ content: "当前无操作权限" });
+      } else {
+        vue.$bus.emit("canLoad", true);
         LoginDetector.valid();
         LoginDetector.valid();
       }
       }
     });
     });
-  } 
-  else{
-    return vue.$alert({content: '该作品已被删除'});
+  } else {
+    return vue.$alert({ content: "该作品已被删除" });
   }
   }
-  
-})
+});
 
 
 getPanoInfo().then(() => {
 getPanoInfo().then(() => {
-  store.commit('UpdateIsShowState', true)
+  store.commit("UpdateIsShowState", true);
   // if(response&&response.status == 1){
   // if(response&&response.status == 1){
   // }
   // }
   OnlineDetector.valid();
   OnlineDetector.valid();
 });
 });
 
 
-
 const router = new Router({
 const router = new Router({
-  routes: routes
-})
-
+  routes: routes,
+});
 
 
 router.beforeEach(async (to, from, next) => {
 router.beforeEach(async (to, from, next) => {
-  
-    await LoginDetector.listener();
-    await OnlineDetector.listener();
-    if (from.name == 'hotspot') {
-      if (store.getters.isEditing) {
-        vue.$confirm({
-          content: i18n.t('hotspot.close_dialog'),
-          ok: () => {
-            vue.$bus.emit('delhotspot') 
-            return next()
-          }
-        });
-        return
-      }
+  await LoginDetector.listener();
+  await OnlineDetector.listener();
+  if (from.name == "hotspot") {
+    if (store.getters.isEditing) {
+      vue.$confirm({
+        content: i18n.t("hotspot.close_dialog"),
+        ok: () => {
+          vue.$bus.emit("delhotspot");
+          return next();
+        },
+      });
+      return;
     }
     }
+  }
 
 
+  if (to.path == "/") {
+    return next({ path: "/base" });
+  }
 
 
-    if (to.path == '/') {
-        return next({path: "/base" })
-    }
-    
-    next()
-})
-
+  next();
+});
 
 
 export default router;
 export default router;

+ 16 - 20
packages/qjkankan-editor/src/router/material.js

@@ -1,39 +1,35 @@
-import Vue from 'vue'
-import Router from 'vue-router'
+import Vue from "vue";
+import Router from "vue-router";
 import { MATERIALMenu } from "../config/menu";
 import { MATERIALMenu } from "../config/menu";
 
 
-Vue.use(Router)
+Vue.use(Router);
 
 
-const originalPush = Router.prototype.push
-Router.prototype.push = function push (location) {
-  return originalPush.call(this, location).catch(err => err)
-}
+const originalPush = Router.prototype.push;
+Router.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch((err) => err);
+};
 
 
 let routes = [];
 let routes = [];
-MATERIALMenu.forEach(item => {
+MATERIALMenu.forEach((item) => {
   routes.push({
   routes.push({
     name: item.name,
     name: item.name,
     path: `${item.link}`,
     path: `${item.link}`,
     meta: {
     meta: {
-      belong: item.belong
+      belong: item.belong,
     },
     },
-    component: () => import(`../views/material/${item.name}/index.vue`)
+    component: () => import(`../views/material/${item.name}/index.vue`),
   });
   });
 });
 });
 
 
 const router = new Router({
 const router = new Router({
-  routes: routes
-})
-
+  routes: routes,
+});
 
 
 router.beforeEach(async (to, from, next) => {
 router.beforeEach(async (to, from, next) => {
-  if (to.path == '/') {
-      return next({path: "/works" })
+  if (to.path == "/") {
+    return next({ path: "/works" });
   }
   }
-  next()
-})
-
-
-
+  next();
+});
 
 
 export default router;
 export default router;

+ 112 - 71
packages/qjkankan-editor/src/utils/request.js

@@ -13,21 +13,20 @@ import { base64ToBlob } from "./file";
 import { checkLogin } from "@/api";
 import { checkLogin } from "@/api";
 import { LoginDetector } from "@/utils/starter";
 import { LoginDetector } from "@/utils/starter";
 import { $alert, $confirm } from "@/components/shared/message";
 import { $alert, $confirm } from "@/components/shared/message";
-import {i18n} from '@/lang'
+import { i18n } from "@/lang";
 
 
 let vue = new Vue();
 let vue = new Vue();
 
 
-
 // 空函数
 // 空函数
-const noop = function() {};
+const noop = function () {};
 // 请求回调队列
 // 请求回调队列
 let postQueue = [];
 let postQueue = [];
 
 
 // 阻止多个弹窗
 // 阻止多个弹窗
-let isDiglog = false
+let isDiglog = false;
 
 
 const statusCode = {
 const statusCode = {
-  NEXT: '__not_important__', //继续执行
+  NEXT: "__not_important__", //继续执行
   SUCCESS: 0, //成功
   SUCCESS: 0, //成功
 
 
   FAILURE_CODE_3001: 3001, // "对象不存在"
   FAILURE_CODE_3001: 3001, // "对象不存在"
@@ -37,7 +36,6 @@ const statusCode = {
   FAILURE_CODE_3006: 3006, //作品已被删除,无法编辑
   FAILURE_CODE_3006: 3006, //作品已被删除,无法编辑
   FAILURE_CODE_3007: 3007, //素材已被引用,无法删除。
   FAILURE_CODE_3007: 3007, //素材已被引用,无法删除。
 
 
-
   // 301X -> 权限问题类
   // 301X -> 权限问题类
   FAILURE_CODE_3010: 3010, // "Token为空"
   FAILURE_CODE_3010: 3010, // "Token为空"
   FAILURE_CODE_3011: 3011, // "没有操作权限"
   FAILURE_CODE_3011: 3011, // "没有操作权限"
@@ -80,26 +78,29 @@ export const showLoginTips = () => {
   showLoginTips.__is_show = true;
   showLoginTips.__is_show = true;
 
 
   return $confirm({
   return $confirm({
-    title: i18n.t('tips_code.tips'),
-    content: i18n.t('tips_code.relogin'),
-    okText: i18n.t('tips_code.goto_login'),
-    noText: i18n.t('tips_code.login_success'),
+    title: i18n.t("tips_code.tips"),
+    content: i18n.t("tips_code.relogin"),
+    okText: i18n.t("tips_code.goto_login"),
+    noText: i18n.t("tips_code.login_success"),
     okLink: "/",
     okLink: "/",
-    ok: function() {
+    ok: function () {
       showLoginTips.__is_show = false;
       showLoginTips.__is_show = false;
       return false;
       return false;
     },
     },
-    no: function() {
+    no: function () {
       checkLogin().then((response) => {
       checkLogin().then((response) => {
         if (response.code === statusCode.SUCCESS) {
         if (response.code === statusCode.SUCCESS) {
           postQueue.length && postQueue.forEach((item) => item());
           postQueue.length && postQueue.forEach((item) => item());
           postQueue = [];
           postQueue = [];
           LoginDetector.valid();
           LoginDetector.valid();
-          location.reload()
-        } else if (response.code === statusCode.FAILURE_CODE_5001||response.code === statusCode.FAILURE_CODE_5002) {
+          location.reload();
+        } else if (
+          response.code === statusCode.FAILURE_CODE_5001 ||
+          response.code === statusCode.FAILURE_CODE_5002
+        ) {
           showLoginTips();
           showLoginTips();
         }
         }
-      })
+      });
       showLoginTips.__is_show = false;
       showLoginTips.__is_show = false;
     },
     },
   });
   });
@@ -115,107 +116,126 @@ function getCookie(objname) {
 }
 }
 
 
 export function getToken() {
 export function getToken() {
-  return (
-     localStorage.getItem("token") || getCookie("fdkankantoken") || ""
-  );
+  return localStorage.getItem("token") || getCookie("fdkankantoken") || "";
 }
 }
 
 
 export function statusCodesHandler(result, callback) {
 export function statusCodesHandler(result, callback) {
-  
-
   if (result.code == statusCode.FAILURE_CODE_3002) {
   if (result.code == statusCode.FAILURE_CODE_3002) {
-    $alert({ content: i18n.t('tips_code.need_one') });
-    return false
+    $alert({ content: i18n.t("tips_code.need_one") });
+    return false;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_3003) {
   if (result.code == statusCode.FAILURE_CODE_3003) {
-    $alert({ content: i18n.t('tips_code.not_less_than') })
-    return false
+    $alert({ content: i18n.t("tips_code.not_less_than") });
+    return false;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_3005) {
   if (result.code == statusCode.FAILURE_CODE_3005) {
-    $alert({ content: i18n.t('tips_code.FAILURE_3011') });
-    return false
+    $alert({
+      content: i18n.t("tips_code.FAILURE_3011"),
+      ok: () => {
+        const lang = browser.urlQueryValue("lang");
+        location.replace(`./material.html?lang=${lang}#/works`);
+      },
+      no: () => {
+        const lang = browser.urlQueryValue("lang");
+        location.replace(`./material.html?lang=${lang}#/works`);
+      },
+    });
+    return false;
   }
   }
   if (result.code == statusCode.FAILURE_CODE_3011) {
   if (result.code == statusCode.FAILURE_CODE_3011) {
-    $alert({ content: i18n.t('tips_code.FAILURE_3011') });
-    return false
+    // debugger
+    $alert({
+      content: i18n.t("tips_code.FAILURE_3011"),
+      ok: () => {
+        const lang = browser.urlQueryValue("lang");
+        location.replace(`./material.html?lang=${lang}#/works`);
+      },
+      no: () => {
+        const lang = browser.urlQueryValue("lang");
+        location.replace(`./material.html?lang=${lang}#/works`);
+      },
+    });
+    return false;
   }
   }
-  
-  
+
   if (result.code == statusCode.FAILURE_CODE_3006) {
   if (result.code == statusCode.FAILURE_CODE_3006) {
-    $alert({ content: i18n.t('tips_code.work_had_delete') });
-    return
+    $alert({ content: i18n.t("tips_code.work_had_delete") });
+    return;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_3007) {
   if (result.code == statusCode.FAILURE_CODE_3007) {
-    $alert({ content: i18n.t('tips_code.material_can_not_delete') });
-    return
+    $alert({ content: i18n.t("tips_code.material_can_not_delete") });
+    return;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_3101) {
   if (result.code == statusCode.FAILURE_CODE_3101) {
-    $alert({ content: i18n.t('tips_code.FAILURE_3101') });
-    return
+    $alert({ content: i18n.t("tips_code.FAILURE_3101") });
+    return;
   }
   }
   if (result.code == statusCode.FAILURE_CODE_3104) {
   if (result.code == statusCode.FAILURE_CODE_3104) {
-    $alert({ content: i18n.t('tips_code.FAILURE_3104') });
-    return
+    $alert({ content: i18n.t("tips_code.FAILURE_3104") });
+    return;
   }
   }
 
 
-  if (result.code == statusCode.FAILURE_CODE_5001||result.code === statusCode.FAILURE_CODE_5002) {
+  if (
+    result.code == statusCode.FAILURE_CODE_5001 ||
+    result.code === statusCode.FAILURE_CODE_5002
+  ) {
     callback(result.code);
     callback(result.code);
     showLoginTips();
     showLoginTips();
-    return false
+    return false;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_5003) {
   if (result.code == statusCode.FAILURE_CODE_5003) {
-    $alert({ content: i18n.t('tips_code.illegality_image') })
-    return false
+    $alert({ content: i18n.t("tips_code.illegality_image") });
+    return false;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_5004) {
   if (result.code == statusCode.FAILURE_CODE_5004) {
-    vue.$msg.error(i18n.t('tips_code.password_error'));
-    return false
+    vue.$msg.error(i18n.t("tips_code.password_error"));
+    return false;
   }
   }
 
 
   if (result.code == statusCode.FAILURE_CODE_3024) {
   if (result.code == statusCode.FAILURE_CODE_3024) {
     if (!isDiglog) {
     if (!isDiglog) {
-      isDiglog = true
+      isDiglog = true;
       $alert({
       $alert({
-        content: i18n.t('tips_code.FAILURE_3024'),
+        content: i18n.t("tips_code.FAILURE_3024"),
         forceOK: true,
         forceOK: true,
         ok: () => {
         ok: () => {
-          isDiglog = false
+          isDiglog = false;
         },
         },
       });
       });
     }
     }
-    return false
+    return false;
   }
   }
 
 
-  if (result.code != statusCode.SUCCESS) {
+  if (![statusCode.SUCCESS, 200].includes(result.code)) {
     console.log(result.msg);
     console.log(result.msg);
     $alert({ content: `${result.msg}` });
     $alert({ content: `${result.msg}` });
-    return false
+    return false;
   }
   }
   return statusCode.NEXT;
   return statusCode.NEXT;
 }
 }
 
 
 $.ajaxSetup({
 $.ajaxSetup({
   headers: {},
   headers: {},
-  beforeSend: function(xhr) {
+  beforeSend: function (xhr) {
     const token = getToken();
     const token = getToken();
     if (token) {
     if (token) {
       xhr.setRequestHeader("token", token);
       xhr.setRequestHeader("token", token);
     }
     }
   },
   },
-  error: function() {
+  error: function () {
     // 出错时默认的处理函数
     // 出错时默认的处理函数
     if (__showNetworkError) {
     if (__showNetworkError) {
       return;
       return;
     }
     }
     __showNetworkError = true;
     __showNetworkError = true;
     $alert({
     $alert({
-      content: i18n.t('tips_code.FAILURE_error'),
+      content: i18n.t("tips_code.FAILURE_error"),
       forceOK: true,
       forceOK: true,
       ok: () => {
       ok: () => {
         __showNetworkError = false;
         __showNetworkError = false;
@@ -223,9 +243,8 @@ $.ajaxSetup({
     });
     });
     return;
     return;
   },
   },
-  success: function() {},
-  complete: function(data) {
-  },
+  success: function () {},
+  complete: function (data) {},
 });
 });
 
 
 export const http = {
 export const http = {
@@ -239,25 +258,28 @@ export const http = {
     }
     }
 
 
     xhr.done((result) => {
     xhr.done((result) => {
-      if (typeof result.code !== "undefined") { 
-        const flag = statusCodesHandler(result, function(code) {
+      if (typeof result.code !== "undefined") {
+        const flag = statusCodesHandler(result, function (code) {
           // 需要登录的状态
           // 需要登录的状态
-          if (code == statusCode.FAILURE_CODE_5001||code === statusCode.FAILURE_CODE_5002) {
-            postQueue.push(function() {
+          if (
+            code == statusCode.FAILURE_CODE_5001 ||
+            code === statusCode.FAILURE_CODE_5002
+          ) {
+            postQueue.push(function () {
               http[method](url, data, done, fail);
               http[method](url, data, done, fail);
             });
             });
           }
           }
         });
         });
 
 
         if (!flag) {
         if (!flag) {
-          fail(result)
+          fail(result);
         }
         }
 
 
         if (flag === statusCode.NEXT) {
         if (flag === statusCode.NEXT) {
           done(result, result.code == 0);
           done(result, result.code == 0);
         }
         }
       } else {
       } else {
-        console.log('ajax返回数据里没有code.')
+        console.log("ajax返回数据里没有code.");
         done(result);
         done(result);
       }
       }
     });
     });
@@ -384,6 +406,26 @@ export const http = {
       fail
       fail
     );
     );
   },
   },
+  postJsonWithHeader(url, data = {}, headers, done, fail) {
+    return this.__request(
+      $.ajax({
+        type: "POST",
+        url: url,
+        contentType: "application/json",
+        data: JSON.stringify(data),
+        headers: headers,
+        beforeSend: function (xhr) {
+          xhr.headers = null;
+          //  console.log(xhr.headers)
+        },
+      }),
+      "postJson",
+      url,
+      data,
+      done,
+      fail
+    );
+  },
   /**
   /**
    * Post 表单 支持文件上传
    * Post 表单 支持文件上传
    * @param {String} url 请求地址
    * @param {String} url 请求地址
@@ -400,19 +442,18 @@ export const http = {
           processData: false,
           processData: false,
           contentType: false,
           contentType: false,
           data: formData,
           data: formData,
-          xhr: function() {
+          xhr: function () {
             const xhr = new XMLHttpRequest();
             const xhr = new XMLHttpRequest();
-            xhr.upload.addEventListener("progress", function(e) {
+            xhr.upload.addEventListener("progress", function (e) {
               onProgress(e.loaded / e.total);
               onProgress(e.loaded / e.total);
             });
             });
             xhr.onabort = () => {
             xhr.onabort = () => {
-              console.log('xhr aborted.');
-            }
+              console.log("xhr aborted.");
+            };
             return xhr;
             return xhr;
           },
           },
           // 覆盖全局配置的失败回调。因为无论是上传子集失败还是用户取消了上传,都不需要弹窗。
           // 覆盖全局配置的失败回调。因为无论是上传子集失败还是用户取消了上传,都不需要弹窗。
-          error: function() {
-          },
+          error: function () {},
         }),
         }),
         "postForm",
         "postForm",
         url,
         url,
@@ -454,13 +495,13 @@ export const http = {
         .fail(def.reject.bind(def));
         .fail(def.reject.bind(def));
     };
     };
 
 
-    img.onerror = function() {
+    img.onerror = function () {
       retry > 0
       retry > 0
         ? setTimeout(() => load(), 1e3)
         ? setTimeout(() => load(), 1e3)
-        : def.reject(`[${url}]${i18n.t('tips_code.loading_fail')}`);
+        : def.reject(`[${url}]${i18n.t("tips_code.loading_fail")}`);
     };
     };
 
 
-    img.onload = function() {
+    img.onload = function () {
       def.resolve(img);
       def.resolve(img);
     };
     };
 
 

+ 4 - 4
packages/qjkankan-editor/src/views/base/Toolbar.vue

@@ -99,9 +99,9 @@
           <CustomLogoSettings
           <CustomLogoSettings
             v-show="activeTab === 'logo'"
             v-show="activeTab === 'logo'"
           ></CustomLogoSettings>
           ></CustomLogoSettings>
-          <CustomMaskSettings
+          <!-- <CustomMaskSettings
             v-show="activeTab === 'customCover'"
             v-show="activeTab === 'customCover'"
-          ></CustomMaskSettings>
+          ></CustomMaskSettings> -->
           <CustomButtonSettings
           <CustomButtonSettings
             v-show="activeTab === 'customButton'"
             v-show="activeTab === 'customButton'"
           ></CustomButtonSettings>
           ></CustomButtonSettings>
@@ -114,7 +114,7 @@
       <MaterialSelector
       <MaterialSelector
         :selectableType="['image', 'pano', '3D']"
         :selectableType="['image', 'pano', '3D']"
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
-        @cancle="isShowSettingCoverWindow = false"
+        @cancel="isShowSettingCoverWindow = false"
         @submit="onCoverSelected"
         @submit="onCoverSelected"
       />
       />
     </div>
     </div>
@@ -172,7 +172,7 @@ export default {
         "cruise" /**自动巡游 */,
         "cruise" /**自动巡游 */,
         "bgm" /**背景音乐 */,
         "bgm" /**背景音乐 */,
         "logo" /**自定义LOGO */,
         "logo" /**自定义LOGO */,
-        "customCover" /**自定义遮罩 */,
+        // "customCover" /**自定义遮罩 */,
         "customButton" /**自定义按钮 */,
         "customButton" /**自定义按钮 */,
       ],
       ],
       activeTab: "openTips",
       activeTab: "openTips",

+ 1 - 1
packages/qjkankan-editor/src/views/base/backgroundMusicSettings.vue

@@ -21,7 +21,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="select_material"
         :title="select_material"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="['audio']"
         :selectableType="['audio']"
         initialMaterialType="audio"
         initialMaterialType="audio"

+ 3 - 3
packages/qjkankan-editor/src/views/base/coverBase.vue

@@ -503,7 +503,7 @@ pc端视频位置 videoPcLoc
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
         :isMultiSelection="false"
         :isMultiSelection="false"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="[upType]"
         :selectableType="[upType]"
         :initialMaterialType="upType"
         :initialMaterialType="upType"
@@ -636,7 +636,7 @@ export default {
             val.coverImgBacId,
             val.coverImgBacId,
             val.videoBacImgId,
             val.videoBacImgId,
           ].filter((i) => i);
           ].filter((i) => i);
-          console.log("folderIds", folderIds);
+          // console.log("folderIds", folderIds);
           this.folderIds = folderIds;
           this.folderIds = folderIds;
           // this.info.coverInfo.fodderId = folderIds;
           // this.info.coverInfo.fodderId = folderIds;
         }
         }
@@ -770,7 +770,7 @@ export default {
     }
     }
   },
   },
   mounted() {
   mounted() {
-    console.log("this.info.coverInfo", this.info.coverInfo);
+    // console.log("this.info.coverInfo", this.info.coverInfo);
   },
   },
   beforeCreate() {}, //生命周期 - 创建之前
   beforeCreate() {}, //生命周期 - 创建之前
   beforeMount() {}, //生命周期 - 挂载之前
   beforeMount() {}, //生命周期 - 挂载之前

+ 1 - 1
packages/qjkankan-editor/src/views/base/customLogoSettings.vue

@@ -34,7 +34,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="['image']"
         :selectableType="['image']"
       />
       />

+ 1 - 1
packages/qjkankan-editor/src/views/base/customMaskSettings.vue

@@ -62,7 +62,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="['image']"
         :selectableType="['image']"
       />
       />

+ 1 - 1
packages/qjkankan-editor/src/views/base/openingAnimationSettings.vue

@@ -108,7 +108,7 @@ export default {
       this.$set(this.info, "openingAnimationType", this.openingTypeList[0]);
       this.$set(this.info, "openingAnimationType", this.openingTypeList[0]);
     }
     }
     this.$nextTick(() => {
     this.$nextTick(() => {
-      console.log(this.$refs.opvideo);
+      // console.log(this.$refs.opvideo);
 
 
       this.$refs.opvideo &&
       this.$refs.opvideo &&
         this.$refs.opvideo.addEventListener("playing", () => {
         this.$refs.opvideo.addEventListener("playing", () => {

+ 1 - 1
packages/qjkankan-editor/src/views/base/openingTipSettings.vue

@@ -46,7 +46,7 @@
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
         :isMultiSelection="false"
         :isMultiSelection="false"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="['image']"
         :selectableType="['image']"
       />
       />

+ 1 - 1
packages/qjkankan-editor/src/views/explanation/explanationSettings.vue

@@ -46,7 +46,7 @@
       <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
         <MaterialSelector
         <MaterialSelector
           :title="$i18n.t('gather.select_material')"
           :title="$i18n.t('gather.select_material')"
-          @cancle="isShowSelectionWindow = false"
+          @cancel="isShowSelectionWindow = false"
           @submit="handleSubmitFromMaterialSelector"
           @submit="handleSubmitFromMaterialSelector"
           :selectableType="['audio']"
           :selectableType="['audio']"
           initialMaterialType="audio"
           initialMaterialType="audio"

+ 1 - 1
packages/qjkankan-editor/src/views/hotspot/hotspotIconType/custom_image.vue

@@ -27,7 +27,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="['image']"
         :selectableType="['image']"
       />
       />

+ 1 - 1
packages/qjkankan-editor/src/views/hotspot/hotspotIconType/serial_frame.vue

@@ -75,7 +75,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t(`gather.select_material`)"
         :title="$i18n.t(`gather.select_material`)"
-        @cancle="isShowSelectionWindow = false"
+        @cancel="isShowSelectionWindow = false"
         @submit="handleSubmitFromMaterialSelector"
         @submit="handleSubmitFromMaterialSelector"
         :selectableType="['image']"
         :selectableType="['image']"
       />
       />

+ 1 - 1
packages/qjkankan-editor/src/views/hotspot/hotspotType/audio.vue

@@ -20,7 +20,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelect">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelect">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('hotspot.select_audio')"
         :title="$i18n.t('hotspot.select_audio')"
-        @cancle="isShowSelect = false"
+        @cancel="isShowSelect = false"
         @submit="handleSelect"
         @submit="handleSelect"
         :selectableType="['audio']"
         :selectableType="['audio']"
         initialMaterialType="audio"
         initialMaterialType="audio"

+ 1 - 1
packages/qjkankan-editor/src/views/hotspot/hotspotType/image.vue

@@ -33,7 +33,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelect">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelect">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('hotspot.select_image')"
         :title="$i18n.t('hotspot.select_image')"
-        @cancle="isShowSelect = false"
+        @cancel="isShowSelect = false"
         @submit="handleSelect"
         @submit="handleSelect"
         :selectableType="['image']"
         :selectableType="['image']"
         :isMultiSelection="true"
         :isMultiSelection="true"

+ 2 - 2
packages/qjkankan-editor/src/views/hotspot/hotspotType/imageText.vue

@@ -101,7 +101,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowImageSelect">
     <div class="dialog" style="z-index: 2000" v-if="isShowImageSelect">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('hotspot.select_image')"
         :title="$i18n.t('hotspot.select_image')"
-        @cancle="isShowImageSelect = false"
+        @cancel="isShowImageSelect = false"
         @submit="onImageSelected"
         @submit="onImageSelected"
         :selectableType="['image']"
         :selectableType="['image']"
         :isMultiSelection="true"
         :isMultiSelection="true"
@@ -111,7 +111,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowAudioSelect">
     <div class="dialog" style="z-index: 2000" v-if="isShowAudioSelect">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('hotspot.select_audio')"
         :title="$i18n.t('hotspot.select_audio')"
-        @cancle="isShowAudioSelect = false"
+        @cancel="isShowAudioSelect = false"
         @submit="onAudioSelected"
         @submit="onAudioSelected"
         :selectableType="['audio']"
         :selectableType="['audio']"
         initialMaterialType="audio"
         initialMaterialType="audio"

+ 1 - 1
packages/qjkankan-editor/src/views/hotspot/hotspotType/scene.vue

@@ -24,7 +24,7 @@
     <div class="dialog" style="z-index: 2000" v-if="showScene">
     <div class="dialog" style="z-index: 2000" v-if="showScene">
       <Selector
       <Selector
         :selected="selected"
         :selected="selected"
-        @cancle="showScene = false"
+        @cancel="showScene = false"
         @submit="handleSelect"
         @submit="handleSelect"
       />
       />
     </div>
     </div>

+ 1 - 1
packages/qjkankan-editor/src/views/hotspot/hotspotType/video.vue

@@ -25,7 +25,7 @@
     <div class="dialog" style="z-index: 2000" v-if="isShowSelect">
     <div class="dialog" style="z-index: 2000" v-if="isShowSelect">
       <MaterialSelector
       <MaterialSelector
         :title="$i18n.t('hotspot.change_video')"
         :title="$i18n.t('hotspot.change_video')"
-        @cancle="isShowSelect = false"
+        @cancel="isShowSelect = false"
         @submit="handleSelect"
         @submit="handleSelect"
         :selectableType="['video']"
         :selectableType="['video']"
         initialMaterialType="video"
         initialMaterialType="video"

+ 31 - 0
packages/qjkankan-editor/src/views/mask/index.vue

@@ -0,0 +1,31 @@
+<template>
+  <!-- 编辑器-初始 -->
+  <div class="cover-screen">
+    <div class="panel">
+      <setting />
+    </div>
+  </div>
+</template>
+<script>
+import setting from "./setting";
+export default {
+  name: "cover-setting",
+  components: {
+    setting,
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.cover-screen {
+  height: 100%;
+  position: relative;
+  .panel {
+    position: absolute;
+    top: 0;
+    height: 100%;
+    right: 0;
+    width: 274px;
+  }
+}
+</style>

+ 456 - 0
packages/qjkankan-editor/src/views/mask/setting.vue

@@ -0,0 +1,456 @@
+<template>
+  <div class="cover-panel">
+    <template v-if="currentScene.type !== '4dkk'">
+      <div class="title">
+        {{ $i18n.t("edit_settings.mask_setting") }}
+        <i
+          class="iconfont icon-help_i tool-tip-for-editor"
+          v-tooltip="$i18n.t('mask.cover_tips')"
+        />
+      </div>
+      <!-- sky start -->
+      <div class="swi-col">
+        <span> {{ $i18n.t("edit_settings.sky_mask") }} </span>
+        <switcher
+          :value="sky.isShow"
+          @change="(value) => (sky.isShow = Boolean(value))"
+        ></switcher>
+      </div>
+      <template v-if="sky.isShow">
+        <div class="swi-col update-col">
+          <SelectedImage
+            :imgSrc="sky.icon"
+            :defaultImgSrc="
+              require(`@/assets/images/default/mask_bg_${$lang}.png`)
+            "
+            @cancel="onClickCancelSkyLogo"
+          ></SelectedImage>
+          <div class="action">
+            <button
+              @click="onSelectPic('sky')"
+              class="ui-button submit"
+              :class="[$lang]"
+            >
+              <!-- 选择图片 -->
+              {{ $i18n.t(`mask.select_image`) }}
+            </button>
+            <div
+              class="ui-remark"
+              v-html="$i18n.t(`edit_settings.mask_size`)"
+            ></div>
+          </div>
+        </div>
+
+        <div class="up-col">
+          <span> {{ $i18n.t(`mask.scaling_ratio`) }} </span>
+
+          <slider
+            v-model="sky.scale"
+            show-stops
+            :marks="scaleMarks"
+            :step="0.1"
+            :min="0.5"
+            :max="2"
+          />
+        </div>
+        <div class="swi-col">
+          <span> {{ $i18n.t("mask.follow_the_scene") }} </span>
+          <switcher
+            :value="sky.antidistorted"
+            @change="(sa) => (sky.antidistorted = Boolean(sa))"
+          ></switcher>
+        </div>
+        <div class="swi-col">
+          <span> {{ $i18n.t("mask.apply_to_all_pano") }} </span>
+          <switcher
+            :value="isApplySkyToAll"
+            @change="(sat) => (isApplySkyToAll = Boolean(sat))"
+          ></switcher>
+        </div>
+      </template>
+      <!-- sky end -->
+      <div style="margin: 30px 0"></div>
+      <!-- earth start -->
+      <div class="swi-col">
+        <span> {{ $i18n.t(`edit_settings.bottom_mask`) }} </span>
+        <switcher
+          :value="earth.isShow"
+          @change="(value) => (earth.isShow = Boolean(value))"
+        ></switcher>
+      </div>
+      <template v-if="earth.isShow">
+        <div class="swi-col update-col">
+          <SelectedImage
+            :imgSrc="earth.icon"
+            :defaultImgSrc="
+              require(`@/assets/images/default/mask_bg_${$lang}.png`)
+            "
+            @cancel="onClickCancelEarthLogo"
+          ></SelectedImage>
+          <div class="action">
+            <button @click="onSelectPic('earth')" class="ui-button submit">
+              {{ $i18n.t(`mask.select_image`) }}
+            </button>
+            <div
+              class="ui-remark"
+              v-html="$i18n.t(`edit_settings.mask_size`)"
+            ></div>
+          </div>
+        </div>
+
+        <div class="up-col">
+          <span> {{ $i18n.t(`mask.scaling_ratio`) }} </span>
+          <slider
+            v-model="earth.scale"
+            show-stops
+            :marks="scaleMarks"
+            :step="0.1"
+            :min="0.5"
+            :max="2"
+          />
+        </div>
+        <div class="swi-col">
+          <span> {{ $i18n.t("mask.follow_the_scene") }} </span>
+          <switcher
+            :value="earth.antidistorted"
+            @change="(ve) => (earth.antidistorted = Boolean(ve))"
+          ></switcher>
+        </div>
+        <div class="swi-col">
+          <span> {{ $i18n.t("mask.apply_to_all_pano") }} </span>
+          <switcher
+            :value="isApplyEarthToAll"
+            @change="(vet) => (isApplyEarthToAll = Boolean(vet))"
+          ></switcher>
+        </div>
+      </template>
+      <!-- earth end -->
+    </template>
+
+    <div class="goto-4dkk-tip" v-if="currentScene.type === '4dkk'">
+      <div class="img-wrap">
+        <img
+          class=""
+          src="@/assets/images/default/goto-4dage.png"
+          alt=""
+          draggable="false"
+        />
+        <div class="tip-text">
+          {{ $i18n.t("screen.goto_4dkk_edit_tips") }}
+        </div>
+      </div>
+      <button class="ui-button submit" @click="onClickGo4dkk">
+        {{ $i18n.t("navigation.go_scene_editor") }}
+      </button>
+    </div>
+
+    <div class="dialog" style="z-index: 2000" v-if="isShowSelectionWindow">
+      <MaterialSelector
+        :title="$i18n.t(`gather.select_material`)"
+        @cancel="isShowSelectionWindow = false"
+        @submit="handleSubmitFromMaterialSelector"
+        :selectableType="['image']"
+      />
+    </div>
+  </div>
+</template>
+<script>
+import { mapGetters } from "vuex";
+import Switcher from "@/components/shared/Switcher";
+import { Slider } from "element-ui";
+import SelectedImage from "@/components/selectedImageInEditor.vue";
+import MaterialSelector from "@/components/materialSelector.vue";
+export default {
+  name: "cover-setting-panel",
+  components: {
+    Switcher,
+    Slider,
+    SelectedImage,
+    MaterialSelector,
+  },
+  computed: {
+    ...mapGetters({
+      currentScene: "scene/currentScene",
+    }),
+  },
+  data() {
+    return {
+      isApplySkyToAll: false,
+      isApplyEarthToAll: false,
+      isShowSelectionWindow: false,
+      isSelect: "",
+      scaleMarks: {
+        0.52: "0.5" + this.$i18n.t("mask.unit"),
+        1: "1" + this.$i18n.t("mask.unit"),
+        1.99: "2" + this.$i18n.t("mask.unit"),
+      },
+      sky: {
+        isShow: false,
+        icon: "",
+        scale: 1,
+        fodderId: "",
+        antidistorted: false,
+      },
+      earth: {
+        isShow: false,
+        icon: "",
+        scale: 1,
+        fodderId: "",
+        antidistorted: false,
+      },
+    };
+  },
+  watch: {
+    sky: {
+      handler: function (val) {
+        if (this.currentScene.type === "pano" && val) {
+          this.currentScene.customMask.sky = val;
+          this.updateCurrentScene();
+          this.handlePeakStatus(val.isShow);
+          this.handlePeakScale(val.scale);
+          this.handlePeakURL(val.icon);
+          this.handlePeakDistorted(val.antidistorted);
+        }
+      },
+      deep: true,
+    },
+    earth: {
+      handler: function (val) {
+        if (this.currentScene.type === "pano" && val) {
+          this.currentScene.customMask.earth = val;
+          this.updateCurrentScene();
+          this.handleNadirStatus(val.isShow);
+          this.handleNadirScale(val.scale);
+          this.handleNadirURL(val.icon);
+          this.handleNadirDistorted(val.antidistorted);
+        }
+      },
+      deep: true,
+    },
+    isApplySkyToAll: {
+      handler: function (val) {
+        if (val) {
+          this.$confirm({
+            content: this.$i18n.t("mask.is_apply_to_all_pano"),
+            ok: () => {
+              this.$store.dispatch("scene/applycustomMaskToAll", "sky");
+              this.$msg.success(this.$i18n.t("gather.edit_success"));
+              setTimeout(() => (this.isApplySkyToAll = false), 1000);
+            },
+            no: () => {
+              this.isApplySkyToAll = false;
+            },
+          });
+        }
+      },
+      immediate: true,
+    },
+    isApplyEarthToAll: {
+      handler: function (val) {
+        if (val) {
+          this.$confirm({
+            content: this.$i18n.t("mask.is_apply_to_all_pano"),
+            ok: () => {
+              this.$store.dispatch("scene/applycustomMaskToAll", "earth");
+              this.$msg.success(this.$i18n.t("gather.edit_success"));
+              setTimeout(() => (this.isApplyEarthToAll = false), 1000);
+            },
+            no: () => {
+              this.isApplyEarthToAll = false;
+            },
+          });
+        }
+      },
+      immediate: true,
+    },
+    "currentScene.customMask": {
+      handler: function (newVal, oldVal) {
+        if (newVal && newVal !== oldVal) {
+          const { sky, earth } = newVal;
+          // this.isApplyEarthToAll = false;
+          // this.isApplySkyToAll = false;
+          this.sky = { ...sky };
+          this.earth = { ...earth };
+        }
+      },
+      deep: true,
+      immediate: true,
+    },
+    "currentScene.id": {
+      handler(val, oldVal) {
+        if (val && val !== oldVal) {
+          this.isApplyEarthToAll = false;
+          this.isApplySkyToAll = false;
+        }
+      },
+      immediate: true,
+    },
+  },
+  mounted() {},
+  methods: {
+    onClickGo4dkk() {
+      window.open("/#/scene");
+    },
+    onClickCancelSkyLogo() {
+      this.handlePeakURL("static/template/skin/masking.png");
+      this.sky.fodderId = "";
+      this.sky.icon = "";
+    },
+    onClickCancelEarthLogo() {
+      this.handleNadirURL("static/template/skin/masking.png");
+      this.earth.fodderId = "";
+      this.earth.icon = "";
+    },
+    onSelectPic(type) {
+      this.isSelect = type;
+      this.isShowSelectionWindow = true;
+    },
+    handleSubmitFromMaterialSelector(selected) {
+      this[this.isSelect].icon = selected[0].icon;
+      this[this.isSelect].fodderId = selected[0].id;
+      this.isShowSelectionWindow = false;
+    },
+    handlePeakStatus(status) {
+      const status_b = !!status;
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("hotspot[peaklogo].visible", status_b);
+      }
+    },
+    handlePeakURL(url) {
+      const kr = this.$getKrpano();
+      if (kr && url) {
+        kr.set("hotspot[peaklogo].url", url);
+      }
+    },
+    handlePeakScale(scale) {
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("hotspot[peaklogo].scale", scale);
+      }
+    },
+    handlePeakDistorted(anti) {
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("hotspot[peaklogo].distorted", anti);
+        if (!anti) {
+          kr.set("hotspot[peaklogo].scale", this.sky.scale * 0.5);
+        }
+      }
+    },
+    handleNadirStatus(status) {
+      const status_b = !!status;
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("hotspot[nadirlogo].visible", status_b);
+      }
+    },
+    handleNadirURL(url) {
+      const kr = this.$getKrpano();
+      if (kr && url) {
+        kr.set("hotspot[nadirlogo].url", url);
+      }
+    },
+    handleNadirScale(scale) {
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("hotspot[nadirlogo].scale", scale);
+      }
+    },
+    handleNadirDistorted(anti) {
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("hotspot[nadirlogo].distorted", anti);
+        if (!anti) {
+          kr.set("hotspot[nadirlogo].scale", this.earth.scale * 0.5);
+        }
+      }
+    },
+    updateCurrentScene() {
+      this.$store.dispatch("scene/syncCurrentSceneToStore");
+    },
+  },
+};
+</script>
+<style>
+.cover-panel .el-slider__marks-text {
+  white-space: nowrap;
+}
+</style>
+
+<style lang="less" scoped>
+.cover-panel {
+  padding: 20px;
+  height: 100%;
+  overflow-y: scroll;
+  > .title {
+    font-size: 18px;
+    color: #fff;
+    margin-bottom: 16px;
+    > i {
+      font-size: 12px;
+      position: relative;
+      top: -2px;
+    }
+  }
+  .swi-col,
+  .up-col {
+    display: inline-flex;
+    width: 100%;
+    justify-content: space-between;
+    align-items: center;
+    flex-direction: row;
+    margin: 12px 0;
+  }
+  .up-col {
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    flex-wrap: nowrap;
+    > * {
+      flex: 1;
+      width: 100%;
+    }
+  }
+  .update-col {
+    justify-content: flex-start;
+    align-items: flex-start;
+  }
+  .action {
+    .ui-button {
+      margin-bottom: 10px;
+    }
+  }
+  .el-slider {
+    padding: 0 8px;
+    color: #fff;
+  }
+  .img-wrapper {
+    width: 120px;
+    height: 120px;
+  }
+  .goto-4dkk-tip {
+    > .img-wrap {
+      position: relative;
+      width: 100%;
+      > .img {
+        width: 100%;
+      }
+      > .tip-text {
+        position: absolute;
+        left: 50%;
+        bottom: 32px;
+        transform: translateX(-50%);
+        font-size: 14px;
+        color: #fff;
+        opacity: 0.6;
+        white-space: pre;
+      }
+    }
+    > button {
+      margin-top: 16px;
+      width: 100%;
+    }
+  }
+}
+</style>

+ 7 - 2
packages/qjkankan-editor/src/views/material/audio/index.vue

@@ -515,7 +515,7 @@ export default {
           eachFile.name,
           eachFile.name,
           eachFile.name.toLowerCase().indexOf("mp3")
           eachFile.name.toLowerCase().indexOf("mp3")
         );
         );
-   
+
         if (eachFile.name.toLowerCase().indexOf("mp3") <= -1) {
         if (eachFile.name.toLowerCase().indexOf("mp3") <= -1) {
           setTimeout(() => {
           setTimeout(() => {
             this.$msg({
             this.$msg({
@@ -590,14 +590,19 @@ export default {
         this.uploadListForUI.push(itemInUploadList);
         this.uploadListForUI.push(itemInUploadList);
       });
       });
     },
     },
-    onCancelTask(uid) {
+    async onCancelTask(uid) {
       const index = this.uploadListForUI.findIndex((eachItem) => {
       const index = this.uploadListForUI.findIndex((eachItem) => {
         return eachItem.uid === uid;
         return eachItem.uid === uid;
       });
       });
       if (this.uploadListForUI[index].status === "LOADING") {
       if (this.uploadListForUI[index].status === "LOADING") {
         this.uploadListForUI[index].abortHandler.abort();
         this.uploadListForUI[index].abortHandler.abort();
       } else {
       } else {
+        const item = this.uploadListForUI[index];
         this.uploadListForUI.splice(index, 1);
         this.uploadListForUI.splice(index, 1);
+        if (item && item.backendId) {
+          console.log("新增取消增加删除通知");
+          await delMaterial(item.backendId);
+        }
       }
       }
     },
     },
     getMoreMaterialItem() {
     getMoreMaterialItem() {

+ 1 - 1
packages/qjkankan-editor/src/views/material/folderMixinFactory.js

@@ -257,7 +257,7 @@ export default function (materialType) {
               return Number(item);
               return Number(item);
             })
             })
             .filter((i) => i);
             .filter((i) => i);
-          console.log("targetPathIdList", targetPathIdList);
+          // console.log("targetPathIdList", targetPathIdList);
           targetPathIdList.push(folder.id);
           targetPathIdList.push(folder.id);
           this.folderPath = nodeIdList2nodeInfoListByNodeTree(
           this.folderPath = nodeIdList2nodeInfoListByNodeTree(
             targetPathIdList,
             targetPathIdList,

+ 6 - 1
packages/qjkankan-editor/src/views/material/image/index.vue

@@ -600,14 +600,19 @@ export default {
         this.uploadListForUI.push(itemInUploadList);
         this.uploadListForUI.push(itemInUploadList);
       });
       });
     },
     },
-    onCancelTask(uid) {
+    async onCancelTask(uid) {
       const index = this.uploadListForUI.findIndex((eachItem) => {
       const index = this.uploadListForUI.findIndex((eachItem) => {
         return eachItem.uid === uid;
         return eachItem.uid === uid;
       });
       });
       if (this.uploadListForUI[index].status === "LOADING") {
       if (this.uploadListForUI[index].status === "LOADING") {
         this.uploadListForUI[index].abortHandler.abort();
         this.uploadListForUI[index].abortHandler.abort();
       } else {
       } else {
+        const item = this.uploadListForUI[index];
         this.uploadListForUI.splice(index, 1);
         this.uploadListForUI.splice(index, 1);
+        if (item && item.backendId) {
+          console.log("新增取消增加删除");
+          await delMaterial(item.backendId);
+        }
       }
       }
     },
     },
     getMoreMaterialItem() {
     getMoreMaterialItem() {

+ 25 - 8
packages/qjkankan-editor/src/views/material/pano/index.vue

@@ -215,6 +215,7 @@
           <!-- end of 文字型单元格 -->
           <!-- end of 文字型单元格 -->
         </div>
         </div>
       </tableList>
       </tableList>
+
       <UploadTaskList
       <UploadTaskList
         class="upload-task-list"
         class="upload-task-list"
         fileType="IMAGE"
         fileType="IMAGE"
@@ -316,8 +317,10 @@ const LONG_POLLING_INTERVAL = 5;
 const folderMixin = folderMixinFactory(TYPE);
 const folderMixin = folderMixinFactory(TYPE);
 
 
 export default {
 export default {
+  // eslint-disable-next-line vue/multi-word-component-names
+  name: "pano",
   mixins: [folderMixin],
   mixins: [folderMixin],
-  name: "Pano",
+
   components: {
   components: {
     tableList,
     tableList,
     crumbs,
     crumbs,
@@ -429,7 +432,7 @@ export default {
         const allList = this.uploadListForUI
         const allList = this.uploadListForUI
           .concat(uploadMark)
           .concat(uploadMark)
           .filter((a) => !res.has(a.backendId) && res.set(a.backendId, 1));
           .filter((a) => !res.has(a.backendId) && res.set(a.backendId, 1));
-        console.log("allList", uploadMark);
+        // console.log("allList", uploadMark);
         if (allList.length > 0) {
         if (allList.length > 0) {
           this.uploadListForUI = allList;
           this.uploadListForUI = allList;
           this._checkMStatus();
           this._checkMStatus();
@@ -562,11 +565,17 @@ export default {
                 const index = this.uploadListForUI.findIndex(
                 const index = this.uploadListForUI.findIndex(
                   (eachTask) => eachTask.backendId === eachRes.id
                   (eachTask) => eachTask.backendId === eachRes.id
                 );
                 );
-                index >= 0 && (this.uploadListForUI[index].status = "FAIL");
-                index >= 0 &&
-                  (this.uploadListForUI[index].statusText = this.$msg.success(
-                    i18n.t("gather.material_cutting_fail")
-                  ));
+                if (index >= 0) {
+                  this.uploadListForUI[index].status = "FAIL";
+                  const text = i18n.t("gather.material_cutting_fail");
+                  this.debounceErrMsg(text);
+                  this.uploadListForUI[index].statusText = text;
+                }
+                // index >= 0 && (this.uploadListForUI[index].status = "FAIL");
+                // index >= 0 &&
+                //   (this.uploadListForUI[index].statusText = this.$msg.success(
+                //     i18n.t("gather.material_cutting_fail")
+                //   ));
               } else if (eachRes.status === 3) {
               } else if (eachRes.status === 3) {
                 const index = this.uploadListForUI.findIndex(
                 const index = this.uploadListForUI.findIndex(
                   (eachTask) => eachTask.backendId === eachRes.id
                   (eachTask) => eachTask.backendId === eachRes.id
@@ -806,14 +815,19 @@ export default {
         this.uploadListForUI.push(itemInUploadList);
         this.uploadListForUI.push(itemInUploadList);
       });
       });
     },
     },
-    onCancelTask(uid) {
+    async onCancelTask(uid) {
       const index = this.uploadListForUI.findIndex((eachItem) => {
       const index = this.uploadListForUI.findIndex((eachItem) => {
         return eachItem.uid === uid;
         return eachItem.uid === uid;
       });
       });
       if (this.uploadListForUI[index].status === "LOADING") {
       if (this.uploadListForUI[index].status === "LOADING") {
         this.uploadListForUI[index].abortHandler.abort();
         this.uploadListForUI[index].abortHandler.abort();
       } else {
       } else {
+        const item = this.uploadListForUI[index];
         this.uploadListForUI.splice(index, 1);
         this.uploadListForUI.splice(index, 1);
+        if (item && item.backendId) {
+          console.log("新增取消增加删除通知");
+          await delMaterial(item.backendId);
+        }
       }
       }
     },
     },
     getMoreMaterialItem(islongpolling = null) {
     getMoreMaterialItem(islongpolling = null) {
@@ -851,6 +865,9 @@ export default {
     onClickDeleteInPreview(index) {
     onClickDeleteInPreview(index) {
       this.del(this.list[index]);
       this.del(this.list[index]);
     },
     },
+    debounceErrMsg: debounce(function (txt) {
+      this.$msg.error(txt);
+    }, 100),
   },
   },
 };
 };
 </script>
 </script>

+ 46 - 46
packages/qjkankan-editor/src/views/material/popup/share.vue

@@ -20,19 +20,30 @@
               disabled
               disabled
               type="text"
               type="text"
               maxlength="15"
               maxlength="15"
-              v-model="item.share"
+              :value="item.share + `&vr=${defaultscenesCode}&lang=${$lang}`"
             />
             />
           </li>
           </li>
           <li>
           <li>
             <span>{{ work_qrCode }} </span>
             <span>{{ work_qrCode }} </span>
-
-            <img :src="item.qrCode + `?${Math.random()}` || $thumb" alt="" />
+            <!-- :src="currentQRcode || 'assets/svg/loading.svg'" -->
+            <div
+              class="qrcode"
+              :style="{
+                backgroundImage: `url(${
+                  currentQRcode || require('@/assets/svg/loading.svg')
+                })`,
+              }"
+            />
           </li>
           </li>
         </ul>
         </ul>
       </div>
       </div>
 
 
       <div class="ui-message-footer footer-material">
       <div class="ui-message-footer footer-material">
-        <button @click="downloadImg(item)" class="ui-button">
+        <button
+          @click="downloadImg(item)"
+          class="ui-button"
+          :class="currentQRcode.length === 0 ? 'disable' : ''"
+        >
           {{ download_qrCode }}
           {{ download_qrCode }}
         </button>
         </button>
         <button
         <button
@@ -48,20 +59,9 @@
 
 
 <script>
 <script>
 import Popup from "@/components/shared/popup";
 import Popup from "@/components/shared/popup";
+import { getQrCode } from "@/api";
 import { i18n } from "@/lang";
 import { i18n } from "@/lang";
 
 
-let dataUrlToBold = function (url) {
-  let arr = url.split(","),
-    mime = arr[0].match(/:(.*?);/)[1],
-    bStr = atob(arr[1]),
-    n = bStr.length,
-    u8arr = new Uint8Array(n);
-  while (n--) {
-    u8arr[n] = bStr.charCodeAt(n);
-  }
-  return new Blob([u8arr], { type: mime });
-};
-
 export default {
 export default {
   props: ["show", "item"],
   props: ["show", "item"],
   components: {
   components: {
@@ -74,8 +74,8 @@ export default {
       share: i18n.t("material.works.share"),
       share: i18n.t("material.works.share"),
       download_qrCode: i18n.t("material.works.download_qrCode"),
       download_qrCode: i18n.t("material.works.download_qrCode"),
       copy_link: i18n.t("material.works.copy_link"),
       copy_link: i18n.t("material.works.copy_link"),
-
       key: "",
       key: "",
+      currentQRcode: "",
     };
     };
   },
   },
   computed: {
   computed: {
@@ -89,37 +89,34 @@ export default {
       }
       }
     },
     },
   },
   },
+  watch: {
+    item: {
+      handler: function (val) {
+        this.currentQRcode = "";
+        if (val.share) {
+          const shareLink =
+            val.share + `&vr=${this.defaultscenesCode}&lang=${this.$lang}`;
+          console.log("当前分享", shareLink);
+          const llogo = val.logo || "";
+          getQrCode({ logo: llogo, text: shareLink }).then((res) => {
+            if (res.code === 0) {
+              this.currentQRcode = res.data.img;
+            }
+          });
+        }
+      },
+    },
+  },
   mounted() {},
   mounted() {},
   methods: {
   methods: {
     downloadImg(workItem) {
     downloadImg(workItem) {
-      let val = workItem.qrCode;
-      // var a = document.createElement("a");
-      // a.download = 'qrcode';
-      // a.href = 'https://4dkk.4dage.com/720yun_fd_manage/620/qrCode.jpg';
-      // a.target
-      // console.log(workItem,11222);
-      // val 为传入的图片地址
-      let _type_index = val.lastIndexOf(".");
-      let _type = val.substr(_type_index + 1); //原始图片类型
-      let image = new Image();
-      image.setAttribute("crossOrigin", "anonymous"); //消除跨域
-      image.src = val;
-      image.onload = function () {
-        //借助canvas实现 消除 图片地址会先直接窗口打开图片地址
-        let canvas = document.createElement("canvas");
-        canvas.width = image.width;
-        canvas.height = image.height;
-        let context = canvas.getContext("2d");
-        context.drawImage(image, 0, 0, image.width, image.height);
-        let url = canvas.toDataURL("image/" + _type);
-        let blob = dataUrlToBold(url);
-        let obj_url = URL.createObjectURL(blob); // 消除Chrome下文件太大 导致下载失败(网络失败)的问题
-        let a = document.createElement("a");
-        let event = new MouseEvent("click");
-        a.download = workItem.name || "qrcode";
-        a.href = obj_url;
-        a.dispatchEvent(event);
-      };
+      if (this.currentQRcode) {
+        var a = document.createElement("a"); //Create <a>
+        a.href = this.currentQRcode; //Image Base64 Goes here
+        const defaultTitle = this.$lang === "zh" ? "无标题" : "No title";
+        a.download = `${workItem.name || defaultTitle}.png`; //File name Here
+        a.click();
+      }
     },
     },
     copy(text) {
     copy(text) {
       var textArea = document.createElement("textarea");
       var textArea = document.createElement("textarea");
@@ -179,10 +176,13 @@ export default {
           text-align: right;
           text-align: right;
           margin-right: 16px;
           margin-right: 16px;
         }
         }
-        > img {
+        > .qrcode {
           width: 120px;
           width: 120px;
+          height: 120px;
           border: 1px solid #ebedf0;
           border: 1px solid #ebedf0;
           border-radius: 4px;
           border-radius: 4px;
+          background-size: contain;
+          // background-image: url("~@/assets/svg/loading.svg");
         }
         }
         .ui-input {
         .ui-input {
           width: 362px;
           width: 362px;

+ 6 - 1
packages/qjkankan-editor/src/views/material/video/index.vue

@@ -591,14 +591,19 @@ export default {
         this.uploadListForUI.push(itemInUploadList);
         this.uploadListForUI.push(itemInUploadList);
       });
       });
     },
     },
-    onCancelTask(uid) {
+    async onCancelTask(uid) {
       const index = this.uploadListForUI.findIndex((eachItem) => {
       const index = this.uploadListForUI.findIndex((eachItem) => {
         return eachItem.uid === uid;
         return eachItem.uid === uid;
       });
       });
       if (this.uploadListForUI[index].status === "LOADING") {
       if (this.uploadListForUI[index].status === "LOADING") {
         this.uploadListForUI[index].abortHandler.abort();
         this.uploadListForUI[index].abortHandler.abort();
       } else {
       } else {
+        const item = this.uploadListForUI[index];
         this.uploadListForUI.splice(index, 1);
         this.uploadListForUI.splice(index, 1);
+        if (item && item.backendId) {
+          console.log("新增取消增加删除通知");
+          await delMaterial(item.backendId);
+        }
       }
       }
     },
     },
     getMoreMaterialItem() {
     getMoreMaterialItem() {

+ 877 - 0
packages/qjkankan-editor/src/views/material/works/cam.vue

@@ -0,0 +1,877 @@
+<template>
+  <div
+    class="scroll-container"
+    ref="w-list-ref"
+    @scroll.self="onWorkListScroll"
+  >
+    <div class="back-top" @click="onClickBackTop" v-show="isShowBackTopBtn">
+      <i class="iconfont icon-top"></i>
+    </div>
+    <div class="mask" v-show="isShowMask"></div>
+    <!-- 断网时,输入关键字触发网络请求后,骨架图的隐藏会触发v-infinite-scroll,原因未知。进而导致循环触发,因此list为空时要禁用v-infinite-scroll -->
+    <ul
+      class="w-list"
+      v-if="!(list.length === 0 && !hasMoreData)"
+      v-infinite-scroll="requestMoreData"
+      :infinite-scroll-disabled="
+        !hasMoreData || isRequestingMoreData || list.length === 0
+      "
+    >
+      <!-- <li class="add-work" @click="add">
+        <div class="wrapper">
+          <div class="add-con">
+            <div>
+              <i class="iconfont icon-works_add"></i>
+            </div>
+            <span>{{ create }}</span>
+          </div>
+        </div>
+      </li> -->
+      <!-- 骨架图 -->
+      <template v-if="isRequestingMoreData && list.length === 0">
+        <li v-for="index in 19" :key="index">
+          <div class="wrapper">
+            <workCardSkeleton></workCardSkeleton>
+          </div>
+        </li>
+      </template>
+      <li
+        v-for="(item, i) in list"
+        :key="i"
+        :class="{ 'has-more-data': hasMoreData }"
+      >
+        <div class="wrapper" :class="{ disabled: item.calcStatus === 0 }">
+          <div class="li-hover">
+            <span class="lipreview" @click="handlePreview(item)">{{
+              preview
+            }}</span>
+            <ul class="oper">
+              <li class="comfirmhover" @click="edit(item)">
+                <i class="iconfont icon-works_editor"></i>{{ edittips }}
+              </li>
+              <li class="comfirmhover" @click="openShare(item)">
+                <i class="iconfont icon-works_share"></i>{{ share }}
+              </li>
+              <li class="cancelhover" @click="del(item, i)">
+                <i class="iconfont icon-works_delete"></i>{{ deltips }}
+              </li>
+            </ul>
+          </div>
+          <div class="img" @click="handlePreview(item)">
+            <img class="real" :src="item.icon || $thumb" alt="" />
+            <span class="snCode" v-if="item.snCode">
+              SN: {{ item.snCode }}</span
+            >
+            <div class="calc-mask" v-if="item.calcStatus === 0">
+              <span>{{ calcing }}</span>
+              <svg
+                xmlns="http://www.w3.org/2000/svg"
+                width="1em"
+                height="1em"
+                viewBox="0 0 24 24"
+              >
+                <circle cx="4" cy="12" r="3" fill="currentColor">
+                  <animate
+                    id="svgSpinners3DotsFade0"
+                    fill="freeze"
+                    attributeName="opacity"
+                    begin="0;svgSpinners3DotsFade1.end-0.25s"
+                    dur="0.75s"
+                    values="1;0.2"
+                  />
+                </circle>
+                <circle cx="12" cy="12" r="3" fill="currentColor" opacity="0.4">
+                  <animate
+                    fill="freeze"
+                    attributeName="opacity"
+                    begin="svgSpinners3DotsFade0.begin+0.15s"
+                    dur="0.75s"
+                    values="1;0.2"
+                  />
+                </circle>
+                <circle cx="20" cy="12" r="3" fill="currentColor" opacity="0.3">
+                  <animate
+                    id="svgSpinners3DotsFade1"
+                    fill="freeze"
+                    attributeName="opacity"
+                    begin="svgSpinners3DotsFade0.begin+0.3s"
+                    dur="0.75s"
+                    values="1;0.2"
+                  />
+                </circle>
+              </svg>
+            </div>
+          </div>
+          <div class="li-info">
+            <div>
+              <span class="shenglve tttttt" :title="item.name || no_title">{{
+                item.name || no_title
+              }}</span>
+            </div>
+            <div>
+              <span>{{ item.createTime.split(" ")[0] }}</span>
+              <div :title="item.visit">
+                <i class="iconfont icon-works_look"></i
+                >{{ item.visit > 10000 ? "1w+" : item.visit }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </li>
+      <div
+        class="work-list-loading-wrapper"
+        v-show="isRequestingMoreData && list.length !== 0"
+      >
+        <img
+          class="work-list-loading"
+          :src="require('@/assets/images/icons/work-list-loading.gif')"
+        />
+      </div>
+    </ul>
+    <div
+      class="nodata"
+      v-if="list.length == 0 && !hasMoreData && lastestUsedSearchKey"
+    >
+      <img :src="$noresult" alt="" />
+      <span>{{ no_search_result }}~</span>
+    </div>
+    <div
+      class="nodata"
+      v-if="list.length == 0 && !hasMoreData && !lastestUsedSearchKey"
+    >
+      <img :src="config.empty" alt="" />
+      <span>{{ no_works }}</span>
+      <!-- <button @click="add" class="upload-btn-in-table">{{ create }}</button> -->
+    </div>
+
+    <share
+      :show="showShare"
+      :item="shareItem"
+      @close="showShare = false"
+    ></share>
+
+    <preview
+      v-if="showItem"
+      :name="showItem.name"
+      :show="showPreview"
+      :ifr="`./show.html?id=${showItem.id}&lang=${$lang}`"
+      :dark="false"
+      @close="showPreview = false"
+    />
+
+    <div class="dialog" style="z-index: 1000" v-if="isShowMaterialSelector">
+      <MaterialSelector
+        :isDarkTheme="false"
+        :title="select_material"
+        :selectableType="['pano', '3D']"
+        :isMultiSelection="true"
+        initialMaterialType="pano"
+        @cancel="isShowMaterialSelector = false"
+        @submit="handleSubmitFromMaterialSelector"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import share from "../popup/share";
+import preview from "@/components/preview";
+import workCardSkeleton from "@/components/workCardSkeleton.vue";
+import config from "@/config";
+import { debounce } from "@/utils/other.js";
+import MaterialSelector from "@/components/materialSelector.vue";
+import { mapGetters } from "vuex";
+import { i18n } from "@/lang";
+import { $waiting } from "@/components/shared/loading";
+
+import {
+  addWorks,
+  getCamWorksList,
+  delWorks,
+  getPanoInfo,
+  saveWorks,
+} from "@/api";
+
+export default {
+  components: {
+    share,
+    preview,
+    workCardSkeleton,
+    MaterialSelector,
+  },
+  computed: {
+    ...mapGetters(["info"]),
+  },
+  props: {
+    searchKey: String,
+  },
+  data() {
+    return {
+      myWorks: i18n.t("material.works.my"),
+      create: i18n.t("material.works.create"),
+      search: i18n.t("material.works.search"),
+      preview: i18n.t("material.works.preview"),
+      edittips: i18n.t("material.works.edit"),
+      share: i18n.t("material.works.share"),
+      deltips: i18n.t("material.works.delete"),
+      no_works: i18n.t("material.works.no_works_cam"),
+      no_title: i18n.t("gather.no_title"),
+      no_search_result: i18n.t("gather.no_search_result"),
+      select_material: i18n.t("gather.select_material"),
+      calcing: i18n.t("material.works.calcing"),
+
+      config,
+      list: [],
+      workTotalNum: undefined,
+      hasMoreData: true,
+      isRequestingMoreData: false,
+
+      lastestUsedSearchKey: "",
+      isFilterFocus: false,
+
+      showShare: false,
+      showPreview: false,
+      showItem: "",
+      shareItem: "",
+      isBackingTop: false,
+      isShowBackTopBtn: false,
+      isShowMask: false,
+      isShowMaterialSelector: false,
+      newWorkId: "",
+    };
+  },
+  mounted() {
+    this.requestMoreData();
+  },
+  watch: {
+    searchKey: {
+      handler: function (val) {
+        if (val.length > 0) {
+          this.selectedList = [];
+        }
+        this.refreshListDebounced();
+      },
+      immediate: false,
+    },
+    workTotalNum: {
+      handler: function (val) {
+        if (val) {
+          this.$emit("updateNum", val);
+        }
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+    onFilterFocus() {
+      this.isFilterFocus = true;
+    },
+    onFilterBlur() {
+      this.isFilterFocus = false;
+    },
+    refreshListDebounced: debounce(
+      function () {
+        this.list = [];
+        this.isRequestingMoreData = false;
+        this.hasMoreData = true;
+        this.requestMoreData();
+      },
+      500,
+      false
+    ),
+    openShare(data) {
+      console.log(data);
+      getPanoInfo(data.id, (data) => {
+        if (data.scenes.length <= 0) {
+          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
+        }
+        this.showShare = true;
+        this.shareItem = data;
+      });
+    },
+
+    handlePreview(item) {
+      getPanoInfo(item.id, (data) => {
+        if (data.scenes.length <= 0) {
+          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
+        }
+        this.showItem = {
+          ...item,
+          ...data,
+        };
+        this.showPreview = true;
+      });
+    },
+    add() {
+      // 新建作品,弹窗让用户给作品选择素材。
+      $waiting.show();
+      addWorks({}, (res) => {
+        $waiting.hide();
+        this.newWorkId = res.data.id;
+        this.isShowMaterialSelector = true;
+      });
+    },
+    handleSubmitFromMaterialSelector(selected) {
+      $waiting.show();
+      // 拿新作品的初始数据
+      getPanoInfo(
+        this.newWorkId,
+        // 拿到了。
+        (data) => {
+          // 往里边添加用户选中的素材。
+          this.$store.commit("SetInfo", data);
+          console.log("selected", selected);
+          for (const [key, item] of Object.entries(selected)) {
+            if (item.materialType === "pano") {
+              let newScene = {
+                icon: item.icon,
+                sceneCode: item.sceneCode,
+                sceneTitle: item.name,
+                category: this.info.catalogs[0].id,
+                type: "pano",
+                id: "s_" + this.$randomWord(true, 8, 8),
+              };
+
+              console.log("key", key);
+              if (Number(key) === 0) {
+                //新建时开天空mask
+                newScene = Object.assign(newScene, this.info.scenes[0]);
+                newScene.customMask.sky.isShow = true;
+                this.info.scenes[0] = newScene;
+              } else {
+                newScene = Object.assign(newScene, {
+                  customMask: this.info.scenes[0].customMask,
+                  initVisual: this.info.scenes[0].initVisual,
+                });
+                newScene.customMask.sky.isShow = true;
+                this.info.scenes.push(newScene);
+              }
+            } else if (item.materialType === "3D") {
+              let newScene = {
+                icon: item.thumb,
+                sceneCode: item.num,
+                sceneTitle: item.sceneName,
+                category: this.info.catalogs[0].id,
+                type: "4dkk",
+                id: "s_" + this.$randomWord(true, 8, 8),
+              };
+              if (Number(key) === 0) {
+                this.info.scenes[0] = null;
+                this.info.scenes[0] = newScene;
+              } else {
+                this.info.scenes.push(newScene);
+              }
+            }
+          }
+
+          // 保存新作品
+          saveWorks(
+            {
+              id: this.newWorkId,
+              password: "",
+              someData: {
+                ...this.info,
+                status: 1,
+                icon: this.info.scenes[0].icon,
+              },
+            },
+            // 保存成功
+            () => {
+              $waiting.hide();
+              // 隐藏素材选择弹窗
+              this.isShowMaterialSelector = false;
+
+              // 刷新作品列表
+              this.list = [];
+              this.isRequestingMoreData = false;
+              this.hasMoreData = true;
+              this.requestMoreData()
+                .then(() => {
+                  // 刷新成功
+
+                  // 弹出提示窗口
+                  this.$confirm({
+                    title: this.$i18n.t("tips_code.tips"),
+                    content: this.$i18n.t("material.works.had_created"),
+                    okText: this.$i18n.t("material.works.goto_preview"),
+                    ok: () => {
+                      this.handlePreview(this.list[0]);
+                      this.newWorkId = "";
+                      this.$store.commit("SetInfo", {});
+                    },
+                    ok2Text: this.$i18n.t("material.works.continue_edit"),
+                    ok2: () => {
+                      window.open(
+                        `./edit.html?id=${this.newWorkId}&lang=${this.$lang}`
+                      );
+                      this.newWorkId = "";
+                      this.$store.commit("SetInfo", {});
+                    },
+                  });
+                })
+                .catch(() => {
+                  this.$msg.message(
+                    this.$i18n.t("material.works.had_created_but_no_link")
+                  );
+                  console.error("已成功新建作品,但刷新作品列表失败。");
+                });
+            },
+            // 保存失败,删除新建的作品。
+            (error) => {
+              $waiting.hide();
+              console.error("保存失败:", error);
+              delWorks(this.newWorkId);
+              this.newWorkId = "";
+              this.$store.commit("SetInfo", {});
+            }
+          );
+        },
+        // 没拿到,删除新建的作品。
+        (error) => {
+          console.error("没拿到新建的作品数据:", error);
+          delWorks(this.newWorkId);
+          this.newWorkId = "";
+        }
+      );
+    },
+
+    edit(item) {
+      const from = this.$route.name;
+      window.open(`./edit.html?id=${item.id}&lang=${this.$lang}&from=cam`);
+    },
+
+    del(item, index) {
+      this.$confirm({
+        title: this.$i18n.t("material.works.delete_work"),
+        content: this.$i18n.t("material.works.comfirm_delete"),
+        ok: () => {
+          $waiting.show();
+
+          delWorks(item.id, () => {
+            this.$msg.success(this.$i18n.t("gather.delete_success"));
+            this.isRequestingMoreData = true;
+            const lastestUsedSearchKey = this.searchKey;
+            getCamWorksList(
+              {
+                pageNum: this.list.length,
+                pageSize: 1,
+                searchKey: this.searchKey,
+              },
+              (data) => {
+                $waiting.hide();
+                this.list.splice(index, 1);
+                this.list = this.list.concat(data.data.list);
+                if (this.list.length === data.data.total) {
+                  this.hasMoreData = false;
+                }
+                this.isRequestingMoreData = false;
+                this.lastestUsedSearchKey = lastestUsedSearchKey;
+                if (!lastestUsedSearchKey) {
+                  this.workTotalNum = data.data.total;
+                }
+                // TODO: 这是干啥呢?
+                this.$nextTick(() => {
+                  this.$bus.emit("refreshTips");
+                });
+              },
+              () => {
+                $waiting.hide();
+                this.lastestUsedSearchKey = lastestUsedSearchKey;
+                this.isRequestingMoreData = false;
+              }
+            );
+          });
+        },
+      });
+    },
+
+    requestMoreData() {
+      this.isRequestingMoreData = true;
+
+      const lastestUsedSearchKey = this.searchKey;
+      return new Promise((resolve, reject) => {
+        getCamWorksList(
+          {
+            pageNum: Math.floor(this.list.length / config.PAGE_SIZE) + 1,
+            pageSize: config.PAGE_SIZE,
+            searchKey: this.searchKey,
+          },
+          (data) => {
+            this.list = this.list.concat(data.data.list);
+            if (this.list.length === data.data.total) {
+              this.hasMoreData = false;
+            }
+            this.isRequestingMoreData = false;
+            this.lastestUsedSearchKey = lastestUsedSearchKey;
+            if (!lastestUsedSearchKey) {
+              this.workTotalNum = data.data.total;
+            }
+            // TODO: 这是干啥呢?
+            this.$nextTick(() => {
+              this.$bus.emit("refreshTips");
+            });
+            resolve();
+          },
+          () => {
+            this.isRequestingMoreData = false;
+            this.lastestUsedSearchKey = lastestUsedSearchKey;
+            reject();
+          }
+        );
+      });
+    },
+
+    onClickBackTop() {
+      if (this.isBackingTop) {
+        return;
+      }
+      this.isBackingTop = true;
+
+      const startTime = Date.now();
+      const totalScroll = this.$refs["w-list-ref"].scrollTop;
+      const fn = () => {
+        if (this.$refs["w-list-ref"].scrollTop === 0) {
+          this.isBackingTop = false;
+          return;
+        }
+
+        const nowTime = Date.now();
+        const assumeScrollTop =
+          totalScroll - ((nowTime - startTime) * totalScroll) / 500;
+        this.$refs["w-list-ref"].scrollTop =
+          assumeScrollTop > 0 ? assumeScrollTop : 0;
+        requestAnimationFrame(fn);
+      };
+      requestAnimationFrame(fn);
+    },
+    onWorkListScroll(e) {
+      if (e.target.scrollTop >= 30) {
+        !this.isShowMask && (this.isShowMask = true);
+      } else {
+        this.isShowMask && (this.isShowMask = false);
+      }
+
+      if (e.target.scrollTop >= 600) {
+        this.isShowBackTopBtn = true;
+      } else {
+        this.isShowBackTopBtn = false;
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.scroll-container {
+  flex: 1 1 auto;
+  overflow: auto;
+  margin-left: calc((100vw - 100%) / -2);
+  padding-left: calc((100vw - 100%) / 2);
+  margin-right: calc((100vw - 100%) / -2);
+  padding-right: calc((100vw - 100%) / 2);
+  &::-webkit-scrollbar {
+    width: 0;
+    height: 0;
+  }
+  .back-top {
+    position: absolute;
+    right: -80px;
+    bottom: 30px;
+    width: 60px;
+    height: 60px;
+    border-radius: 8px;
+    background-color: #fff;
+    z-index: 1;
+    color: #c8c9cc;
+
+    &:hover {
+      color: #323233;
+    }
+
+    cursor: pointer;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    i {
+      font-size: 20px;
+    }
+  }
+
+  .mask {
+    position: absolute;
+    width: 100%;
+    top: 180px;
+    height: 30px;
+    background: linear-gradient(rgb(239, 242, 244), rgba(255, 255, 255, 0));
+    z-index: 1;
+    pointer-events: none;
+  }
+
+  .w-list {
+    margin-top: 22px;
+    padding-top: 8px;
+    width: 100%;
+    @gap: 20px;
+    display: flex;
+    flex-wrap: wrap;
+    align-content: flex-start;
+
+    > li {
+      width: calc((100% - @gap * 4) / 5);
+      height: 322px;
+      margin-bottom: @gap;
+      margin-right: @gap;
+
+      &:nth-of-type(5n) {
+        margin-right: 0;
+      }
+
+      // 因为有个“创建作品”card占着空间,每次拿20个数据,每行五个,又不想每次拿到数据后最后一行只有一个card,所以把最后那个card隐藏掉。
+      &:last-of-type.has-more-data {
+        display: none;
+      }
+
+      .wrapper {
+        height: 100%;
+        background: #fff;
+        position: relative;
+        border-radius: 6px;
+        overflow: hidden;
+        .calc-mask {
+          display: none;
+          width: 100%;
+          height: 100%;
+          top: 0;
+          position: absolute;
+          left: 0;
+          z-index: 10;
+          pointer-events: none;
+          justify-content: center;
+          align-items: center;
+          color: #fff;
+          display: flex;
+          flex-direction: column;
+          font-size: 15px;
+          svg {
+            width: 28px;
+            height: 28px;
+          }
+        }
+        &.disabled {
+          > * {
+            pointer-events: none;
+          }
+          .calc-mask {
+            background: rgba(0, 0, 0, 0.6);
+            display: flex;
+          }
+        }
+
+        .li-hover {
+          display: none;
+          width: 100%;
+          height: 240px;
+          position: absolute;
+          top: 0;
+          left: 0;
+          z-index: 99;
+          background: rgba(0, 0, 0, 0.6);
+
+          .lipreview {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            color: #fff;
+            display: inline-block;
+            line-height: 40px;
+            height: 40px;
+            width: 100px;
+            text-align: center;
+            border-radius: 22px;
+            cursor: pointer;
+            background-color: transparent;
+            border: 1px solid #fff;
+
+            &:hover {
+              border: none;
+              background: #1983f6;
+            }
+          }
+
+          .oper {
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+            position: absolute;
+            bottom: 10px;
+            left: 0;
+            width: 100%;
+
+            > li {
+              color: #fff;
+              font-size: 13px;
+              display: flex;
+              align-items: center;
+              cursor: pointer;
+
+              > i {
+                font-size: 20px;
+                margin-right: 4px;
+              }
+            }
+          }
+        }
+
+        .img {
+          width: 100%;
+          height: 240px;
+          position: relative;
+          overflow: hidden;
+          cursor: pointer;
+          .snCode {
+            position: absolute;
+            z-index: 100;
+            color: #fff;
+            min-width: 116px;
+            height: 36px;
+            background: rgba(0, 0, 0, 0.6);
+            border-radius: 0px 0px 0px 10px;
+            top: 0;
+            right: 0;
+            font-size: 13px;
+            text-align: center;
+            line-height: 36px;
+            white-space: nowrap;
+          }
+
+          .real {
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 50%;
+            transform: translateX(-50%);
+            z-index: 0;
+            transition: all ease 0.3s;
+          }
+        }
+
+        .li-info {
+          font-size: 14px;
+          padding: 10px;
+
+          > div {
+            text-align: left;
+
+            &:first-of-type {
+              > span {
+                font-weight: bold;
+                margin-bottom: 10px;
+                display: inline-block;
+                text-overflow: ellipsis;
+                overflow: hidden;
+                white-space: nowrap;
+                cursor: pointer;
+                color: #323233;
+                font-size: 16px;
+              }
+            }
+
+            &:last-of-type {
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+
+              > span {
+                font-size: 14px;
+                color: #969799;
+              }
+
+              > div {
+                color: #969799;
+
+                i {
+                  margin-right: 6px;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      &:hover {
+        .wrapper:not(.disabled) {
+          box-shadow: 0px 2px 12px 0px rgba(50, 50, 51, 0.12);
+          transform: translateY(-6px);
+
+          .li-hover {
+            display: block;
+          }
+
+          .img {
+            .real {
+              height: 108%;
+            }
+          }
+        }
+      }
+    }
+
+    .add-work {
+      .wrapper {
+        .add-con {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          text-align: center;
+
+          div {
+            width: 60px;
+            height: 60px;
+            border-radius: 50%;
+            background: linear-gradient(144deg, #00aefb 0%, #0076f6 100%);
+            position: relative;
+            cursor: pointer;
+            margin: 0 auto;
+
+            > i {
+              font-size: 16px;
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+              color: #fff;
+            }
+          }
+
+          span {
+            color: #333333;
+            display: inline-block;
+            margin-top: 8px;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .work-list-loading-wrapper {
+      width: 100%;
+      margin-top: 20px;
+      margin-bottom: 22px;
+
+      .work-list-loading {
+        display: block;
+        margin: 0 auto;
+        width: 50px;
+        height: 8px;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="less" scoped>
+@import "../style.less";
+</style>

+ 104 - 421
packages/qjkankan-editor/src/views/material/works/index.vue

@@ -1,446 +1,115 @@
 <template>
 <template>
   <div class="works con">
   <div class="works con">
-    <div class="back-top" @click="onClickBackTop" v-show="isShowBackTopBtn">
-      <i class="iconfont icon-top"></i>
-    </div>
     <div class="tab">
     <div class="tab">
-      <span>{{ myWorks }} {{ workTotalNum !== undefined ? `(${workTotalNum})` : '' }}</span>
+      <div class="tab-l">
+        <span @click="changeTab('list')" :class="{ active: isActive('list') }">
+          {{ online_gen }}
+          {{ work1TotalNum ? `(${work1TotalNum})` : "" }}
+        </span>
+        <span @click="changeTab('cam')" :class="{ active: isActive('cam') }">
+          {{ cam_gen }}
+          {{ work2TotalNum ? `(${work2TotalNum})` : "" }}
+        </span>
+      </div>
       <div class="tab-r">
       <div class="tab-r">
         <div class="filter">
         <div class="filter">
-          <div :class="{ active: isFilterFocus }" @focusin="onFilterFocus" @focusout="onFilterBlur">
+          <div
+            :class="{ active: isFilterFocus }"
+            @focusin="onFilterFocus"
+            @focusout="onFilterBlur"
+          >
             <i class="iconfont iconworks_search search"></i>
             <i class="iconfont iconworks_search search"></i>
-            <input type="text" :placeholder="search" v-model="searchKey">
-            <i v-if="searchKey" @click="searchKey = ''" class="iconfont icon-toast_red del"></i>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div class="mask" v-show="isShowMask"></div>
-    <!-- 断网时,输入关键字触发网络请求后,骨架图的隐藏会触发v-infinite-scroll,原因未知。进而导致循环触发,因此list为空时要禁用v-infinite-scroll -->
-    <ul class="w-list" v-if="!(list.length === 0 && !hasMoreData)" v-infinite-scroll="requestMoreData"
-      :infinite-scroll-disabled="!hasMoreData || isRequestingMoreData || (list.length === 0)" ref="w-list-ref"
-      @scroll.self="onWorkListScroll">
-      <li class="add-work" @click="add">
-        <div class="wrapper">
-          <div class="add-con">
-            <div>
-              <i class="iconfont icon-works_add"></i>
-            </div>
-            <span>{{ create }}</span>
-          </div>
-        </div>
-      </li>
-      <!-- 骨架图 -->
-      <template v-if="isRequestingMoreData && (list.length === 0)">
-        <li v-for="index in 19" :key="index">
-          <div class="wrapper">
-            <workCardSkeleton></workCardSkeleton>
-          </div>
-        </li>
-      </template>
-      <li v-for="(item, i) in list" :key="i" :class="{ 'has-more-data': hasMoreData }">
-        <div class="wrapper">
-          <div class="li-hover">
-            <span class="lipreview" @click="handlePreview(item)">{{ preview }}</span>
-            <ul class="oper">
-              <li class="comfirmhover" @click="edit(item)"><i class="iconfont icon-works_editor"></i>{{ edittips }}</li>
-              <li class="comfirmhover" @click="openShare(item)"><i class="iconfont icon-works_share"></i>{{ share }}</li>
-              <li class="cancelhover" @click="del(item, i)"><i class="iconfont icon-works_delete"></i>{{ deltips }}</li>
-            </ul>
-          </div>
-          <div class="img" @click="handlePreview(item)">
-            <img class="real" :src="item.icon || $thumb" alt="" />
-          </div>
-          <div class="li-info">
-            <div>
-              <span class="shenglve tttttt" :title="item.name || no_title">{{ item.name || no_title }}</span>
-            </div>
-            <div>
-              <span>{{ item.createTime.split(' ')[0] }}</span>
-              <div :title="item.visit">
-                <i class="iconfont icon-works_look"></i>{{ item.visit > 10000 ? '1w+' : item.visit }}
-              </div>
-            </div>
+            <input type="text" :placeholder="search" v-model="searchKey" />
+            <i
+              v-if="searchKey"
+              @click="searchKey = ''"
+              class="iconfont icon-toast_red del"
+            ></i>
           </div>
           </div>
         </div>
         </div>
-      </li>
-      <div class="work-list-loading-wrapper" v-show="isRequestingMoreData && (list.length !== 0)">
-        <img class="work-list-loading" :src="require('@/assets/images/icons/work-list-loading.gif')" />
       </div>
       </div>
-    </ul>
-    <div class="nodata" v-if="list.length == 0 && !hasMoreData && lastestUsedSearchKey">
-      <img :src="$noresult" alt="" />
-      <span>{{ no_search_result }}~</span>
-    </div>
-    <div class="nodata" v-if="list.length == 0 && !hasMoreData && !lastestUsedSearchKey">
-      <img :src="config.empty" alt="" />
-      <span>{{ no_works }}</span>
-      <button @click="add" class="upload-btn-in-table">{{ create }}</button>
-    </div>
-    <share :show='showShare' :item="shareItem" @close="showShare = false"></share>
-    <preview v-if="showItem" :name="showItem.name" :show="showPreview"
-      :ifr="`./show.html?id=${showItem.id}&lang=${$lang}`" :dark="false" @close="showPreview = false" />
-    <div class="dialog" style="z-index: 1000" v-if="isShowMaterialSelector">
-      <MaterialSelector
-        :isDarkTheme="false"
-        :title="select_material"
-        :selectableType="['pano', '3D']"
-        :isMultiSelection="true"
-        initialMaterialType="pano"
-        @cancle="isShowMaterialSelector = false"
-        @submit="handleSubmitFromMaterialSelector"
-      />
     </div>
     </div>
+
+    <template v-if="isActive('list')">
+      <list @updateNum="handleWorkNum($event, 1)" :searchKey="searchKey"></list>
+    </template>
+    <template v-if="isActive('cam')">
+      <CamList
+        @updateNum="handleWorkNum($event, 2)"
+        :searchKey="searchKey"
+      ></CamList>
+    </template>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
-import share from '../popup/share'
-import preview from "@/components/preview";
-import workCardSkeleton from "@/components/workCardSkeleton.vue";
-import config from "@/config";
-import { debounce } from "@/utils/other.js"
-import MaterialSelector from "@/components/materialSelector.vue";
 import { mapGetters } from "vuex";
 import { mapGetters } from "vuex";
-import { i18n } from "@/lang"
-import { $waiting } from "@/components/shared/loading";
+import { i18n } from "@/lang";
 
 
+import List from "./list";
+import CamList from "./cam";
+import browser from "@/utils/browser";
 
 
-import {
-  addWorks,
-  getWorksList,
-  delWorks,
-  getPanoInfo,
-  saveWorks,
-} from "@/api";
+const allTabType = ["list", "cam"];
 
 
 export default {
 export default {
   components: {
   components: {
-    share,
-    preview,
-    workCardSkeleton,
-    MaterialSelector,
+    List,
+    CamList,
   },
   },
   computed: {
   computed: {
-    ...mapGetters([
-      'info',
-    ])
+    ...mapGetters(["info"]),
+    isActive: function () {
+      return (key) => {
+        return this.currentTab === key;
+      };
+    },
   },
   },
   data() {
   data() {
     return {
     return {
-      myWorks: i18n.t("material.works.my"),
-      create: i18n.t("material.works.create"),
-      search: i18n.t("material.works.search"),
-      preview: i18n.t("material.works.preview"),
-      edittips: i18n.t("material.works.edit"),
-      share: i18n.t("material.works.share"),
-      deltips: i18n.t("material.works.delete"),
-      no_works: i18n.t("material.works.no_works"),
-      no_title: i18n.t("gather.no_title"),
-      no_search_result: i18n.t("gather.no_search_result"),
-      select_material: i18n.t("gather.select_material"),
-
-
-      config,
-      list: [],
-      workTotalNum: undefined,
-      hasMoreData: true,
-      isRequestingMoreData: false,
-
-      searchKey: '',
-      // 因为searchKey的变化经过debounce、异步请求的延时,才会反映到数据列表的变化上,所以是否显示、显示哪种无数据提示,也要等到数据列表变化后,根据数据列表是否为空,以及引发本次变化的那个searchKey瞬时值来决定。本变量就是用来保存那个瞬时值。
-      lastestUsedSearchKey: '',
+      searchKey: "",
+      work1TotalNum: "",
+      work2TotalNum: "",
       isFilterFocus: false,
       isFilterFocus: false,
-
-      showShare: false,
-      showPreview: false,
-      showItem: '',
-      shareItem: '',
-
-      isBackingTop: false,
-      isShowBackTopBtn: false,
-
-      isShowMask: false,
-
-      isShowMaterialSelector: false,
-      newWorkId: '',
-    }
+      search: i18n.t("material.works.search"),
+      searchKey: "",
+      currentTab: "list",
+      online_gen: i18n.t("material.works.online_gen"),
+      cam_gen: i18n.t("material.works.cam_gen"),
+    };
   },
   },
+
   mounted() {
   mounted() {
-    this.requestMoreData()
-  },
-  watch: {
-    searchKey: {
-      handler: function (val) {
-        if (val.length > 0) {
-          this.selectedList = [];
-        }
-        this.refreshListDebounced()
-      },
-      immediate: false,
-    },
+    const from = browser.urlQueryValue("from") || "list";
+    console.log("from", from);
+    this.changeTab(from);
   },
   },
   methods: {
   methods: {
     onFilterFocus() {
     onFilterFocus() {
-      this.isFilterFocus = true
+      this.isFilterFocus = true;
     },
     },
     onFilterBlur() {
     onFilterBlur() {
-      this.isFilterFocus = false
-    },
-    refreshListDebounced: debounce(function () {
-      this.list = []
-      this.isRequestingMoreData = false
-      this.hasMoreData = true
-      this.requestMoreData()
-    }, 500, false),
-    openShare(data) {
-      console.log(data);
-      getPanoInfo(data.id, (data) => {
-        if (data.scenes.length <= 0) {
-          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
-        }
-        this.showShare = true
-        this.shareItem = data
-      })
-    },
-
-    handlePreview(item) {
-      getPanoInfo(item.id, (data) => {
-        if (data.scenes.length <= 0) {
-          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
-        }
-        this.showItem = {
-          ...item,
-          ...data
-        }
-        this.showPreview = true
-      })
+      this.isFilterFocus = false;
     },
     },
-    add() {
-      // 新建作品,弹窗让用户给作品选择素材。
-      $waiting.show();
-      addWorks(
-        {},
-        (res) => {
-          $waiting.hide();
-          this.newWorkId = res.data.id
-          this.isShowMaterialSelector = true
-        },
-      )
-    },
-    handleSubmitFromMaterialSelector(selected) {
-      $waiting.show();
-      // 拿新作品的初始数据
-      getPanoInfo(
-        this.newWorkId,
-        // 拿到了。
-        (data) => {
-          // 往里边添加用户选中的素材。
-          this.$store.commit("SetInfo", data);
-          for (const item of selected) {
-            if (item.materialType === 'pano') {
-              this.info.scenes.push({
-                icon: item.icon,
-                sceneCode: item.sceneCode,
-                sceneTitle: item.name,
-                category: this.info.catalogs[0].id,
-                type: "pano",
-                id: 's_' + this.$randomWord(true, 8, 8)
-              })
-            } else if (item.materialType === '3D') {
-              this.info.scenes.push({
-                icon: item.thumb,
-                sceneCode: item.num,
-                sceneTitle: item.sceneName,
-                category: this.info.catalogs[0].id,
-                type: "4dkk",
-                id: 's_' + this.$randomWord(true, 8, 8)
-              })
-            }
-          }
-
-          // 保存新作品
-          saveWorks(
-            {
-              id: this.newWorkId,
-              password: '',
-              someData: {
-                ...this.info,
-                status: 1,
-                icon: this.info.scenes[0].icon
-              },
-            },
-            // 保存成功
-            () => {
-              $waiting.hide();
-              // 隐藏素材选择弹窗
-              this.isShowMaterialSelector = false
-
-              // 刷新作品列表
-              this.list = []
-              this.isRequestingMoreData = false
-              this.hasMoreData = true
-              this.requestMoreData().then(() => {
-                // 刷新成功
-
-                // 弹出提示窗口
-                this.$confirm({
-                  title: this.$i18n.t('tips_code.tips'),
-                  content: this.$i18n.t("material.works.had_created"),
-                  okText: this.$i18n.t("material.works.goto_preview"),
-                  ok: () => {
-                    this.handlePreview(this.list[0])
-                    this.newWorkId = ''
-                    this.$store.commit("SetInfo", {});
-                  },
-                  ok2Text: this.$i18n.t("material.works.continue_edit"),
-                  ok2: () => {
-                    window.open(`./edit.html?id=${this.newWorkId}&lang=${this.$lang}`)
-                    this.newWorkId = ''
-                    this.$store.commit("SetInfo", {});
-                  },
-                });
-              }).catch(() => {
-                this.$msg.message(this.$i18n.t("material.works.had_created_but_no_link"))
-                console.error('已成功新建作品,但刷新作品列表失败。')
-              })
-            },
-            // 保存失败,删除新建的作品。
-            (error) => {
-              $waiting.hide();
-              console.error('保存失败:', error);
-              delWorks(this.newWorkId)
-              this.newWorkId = ''
-              this.$store.commit("SetInfo", {});
-            }
-          );
-        },
-        // 没拿到,删除新建的作品。
-        (error) => {
-          console.error('没拿到新建的作品数据:', error);
-          delWorks(this.newWorkId)
-          this.newWorkId = ''
-        }
-      )
-    },
-    edit(item) {
-      window.open(`./edit.html?id=${item.id}&lang=${this.$lang}`)
-    },
-    del(item, index) {
-      this.$confirm({
-        title: this.$i18n.t("material.works.delete_work"),
-        content: this.$i18n.t("material.works.comfirm_delete"),
-        ok: () => {
-
-          $waiting.show();
-
-          delWorks(item.id, () => {
-            this.$msg.success(this.$i18n.t("gather.delete_success"));
-            this.isRequestingMoreData = true
-            const lastestUsedSearchKey = this.searchKey
-            getWorksList(
-              {
-                pageNum: this.list.length,
-                pageSize: 1,
-                searchKey: this.searchKey
-              },
-              (data) => {
-                $waiting.hide();
-                this.list.splice(index, 1)
-                this.list = this.list.concat(data.data.list)
-                if (this.list.length === data.data.total) {
-                  this.hasMoreData = false
-                }
-                this.isRequestingMoreData = false
-                this.lastestUsedSearchKey = lastestUsedSearchKey
-                if (!lastestUsedSearchKey) {
-                  this.workTotalNum = data.data.total
-                }
-                // TODO: 这是干啥呢?
-                this.$nextTick(() => {
-                  this.$bus.emit('refreshTips')
-                })
-              },
-              () => {
-                $waiting.hide();
-                this.lastestUsedSearchKey = lastestUsedSearchKey
-                this.isRequestingMoreData = false
-              }
-            )
-          });
-        },
-      });
-    },
-    requestMoreData() {
-      this.isRequestingMoreData = true
-      const lastestUsedSearchKey = this.searchKey
-      return new Promise((resolve, reject) => {
-        getWorksList(
-          {
-            pageNum: Math.floor(this.list.length / config.PAGE_SIZE) + 1,
-            pageSize: config.PAGE_SIZE,
-            searchKey: this.searchKey,
-          },
-          (data) => {
-            this.list = this.list.concat(data.data.list)
-            if (this.list.length === data.data.total) {
-              this.hasMoreData = false
-            }
-            this.isRequestingMoreData = false
-            this.lastestUsedSearchKey = lastestUsedSearchKey
-            if (!lastestUsedSearchKey) {
-              this.workTotalNum = data.data.total
-            }
-            // TODO: 这是干啥呢?
-            this.$nextTick(() => {
-              this.$bus.emit('refreshTips')
-            })
-            resolve()
-          },
-          () => {
-            this.isRequestingMoreData = false
-            this.lastestUsedSearchKey = lastestUsedSearchKey
-            reject()
-          }
+    changeTab(type) {
+      if (allTabType.includes(type)) {
+        this.currentTab = type;
+        location.replace(
+          `./material.html?lang=${this.$lang}&from=${type}#/works`
         );
         );
-      })
-    },
-    onClickBackTop() {
-      if (this.isBackingTop) {
-        return
       }
       }
-      this.isBackingTop = true
-
-      const startTime = Date.now()
-      const totalScroll = this.$refs['w-list-ref'].scrollTop
-      const fn = () => {
-        if (this.$refs['w-list-ref'].scrollTop === 0) {
-          this.isBackingTop = false
-          return
-        }
-
-        const nowTime = Date.now()
-        const assumeScrollTop = totalScroll - (nowTime - startTime) * totalScroll / 500
-        this.$refs['w-list-ref'].scrollTop = assumeScrollTop > 0 ? assumeScrollTop : 0
-        requestAnimationFrame(fn)
-      }
-      requestAnimationFrame(fn)
     },
     },
-    onWorkListScroll(e) {
-      if (e.target.scrollTop >= 30) {
-        !this.isShowMask && (this.isShowMask = true)
-      } else {
-        this.isShowMask && (this.isShowMask = false)
+    handleWorkNum(val, type) {
+      console.log("handleWorkNum", val, type);
+      if (type === 1) {
+        this.work1TotalNum = val;
       }
       }
-
-      if (e.target.scrollTop >= 600) {
-        this.isShowBackTopBtn = true
-      } else {
-        this.isShowBackTopBtn = false
+      if (type === 2) {
+        this.work2TotalNum = val;
       }
       }
-    }
-  }
-}
+    },
+  },
+};
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
@@ -458,7 +127,7 @@ export default {
     border-radius: 8px;
     border-radius: 8px;
     background-color: #fff;
     background-color: #fff;
     z-index: 1;
     z-index: 1;
-    color: #C8C9CC;
+    color: #c8c9cc;
 
 
     &:hover {
     &:hover {
       color: #323233;
       color: #323233;
@@ -483,9 +152,23 @@ export default {
     align-items: center;
     align-items: center;
     padding: 20px 30px;
     padding: 20px 30px;
 
 
-    >span {
-      font-size: 18px;
-      font-weight: bold;
+    .tab-l {
+      > span {
+        font-size: 18px;
+        font-weight: bold;
+
+        cursor: pointer;
+        padding: 10px 20px;
+        height: 40px;
+        &:hover {
+          color: #0076f6;
+        }
+        &.active {
+          color: #0076f6;
+          background: rgba(0, 118, 246, 0.1);
+          border-radius: 22px;
+        }
+      }
     }
     }
 
 
     .tab-r {
     .tab-r {
@@ -528,7 +211,7 @@ export default {
       height: 0;
       height: 0;
     }
     }
 
 
-    >li {
+    > li {
       width: calc((100% - @gap * 4) / 5);
       width: calc((100% - @gap * 4) / 5);
       height: 322px;
       height: 322px;
       margin-bottom: @gap;
       margin-bottom: @gap;
@@ -578,7 +261,7 @@ export default {
 
 
             &:hover {
             &:hover {
               border: none;
               border: none;
-              background: #1983F6;
+              background: #1983f6;
             }
             }
           }
           }
 
 
@@ -591,14 +274,14 @@ export default {
             left: 0;
             left: 0;
             width: 100%;
             width: 100%;
 
 
-            >li {
+            > li {
               color: #fff;
               color: #fff;
               font-size: 13px;
               font-size: 13px;
               display: flex;
               display: flex;
               align-items: center;
               align-items: center;
               cursor: pointer;
               cursor: pointer;
 
 
-              >i {
+              > i {
                 font-size: 20px;
                 font-size: 20px;
                 margin-right: 4px;
                 margin-right: 4px;
               }
               }
@@ -628,11 +311,11 @@ export default {
           font-size: 14px;
           font-size: 14px;
           padding: 10px;
           padding: 10px;
 
 
-          >div {
+          > div {
             text-align: left;
             text-align: left;
 
 
             &:first-of-type {
             &:first-of-type {
-              >span {
+              > span {
                 font-weight: bold;
                 font-weight: bold;
                 margin-bottom: 10px;
                 margin-bottom: 10px;
                 display: inline-block;
                 display: inline-block;
@@ -650,12 +333,12 @@ export default {
               justify-content: space-between;
               justify-content: space-between;
               align-items: center;
               align-items: center;
 
 
-              >span {
+              > span {
                 font-size: 14px;
                 font-size: 14px;
                 color: #969799;
                 color: #969799;
               }
               }
 
 
-              >div {
+              > div {
                 color: #969799;
                 color: #969799;
 
 
                 i {
                 i {
@@ -698,18 +381,18 @@ export default {
             width: 60px;
             width: 60px;
             height: 60px;
             height: 60px;
             border-radius: 50%;
             border-radius: 50%;
-            background: linear-gradient(144deg, #00AEFB 0%, #0076F6 100%);
+            background: linear-gradient(144deg, #00aefb 0%, #0076f6 100%);
             position: relative;
             position: relative;
             cursor: pointer;
             cursor: pointer;
             margin: 0 auto;
             margin: 0 auto;
 
 
-            >i {
+            > i {
               font-size: 16px;
               font-size: 16px;
               position: absolute;
               position: absolute;
               top: 50%;
               top: 50%;
               left: 50%;
               left: 50%;
               transform: translate(-50%, -50%);
               transform: translate(-50%, -50%);
-              color: #fff
+              color: #fff;
             }
             }
           }
           }
 
 
@@ -740,5 +423,5 @@ export default {
 </style>
 </style>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-@import '../style.less';
-</style>
+@import "../style.less";
+</style>

+ 830 - 0
packages/qjkankan-editor/src/views/material/works/list copy.vue

@@ -0,0 +1,830 @@
+<template>
+  <div class="works con">
+    <div class="back-top" @click="onClickBackTop" v-show="isShowBackTopBtn">
+      <i class="iconfont icon-top"></i>
+    </div>
+    <div class="tab">
+      <span
+        >{{ myWorks }}
+        {{ workTotalNum !== undefined ? `(${workTotalNum})` : "" }}</span
+      >
+      <div class="tab-r">
+        <div class="filter">
+          <div
+            :class="{ active: isFilterFocus }"
+            @focusin="onFilterFocus"
+            @focusout="onFilterBlur"
+          >
+            <i class="iconfont iconworks_search search"></i>
+            <input type="text" :placeholder="search" v-model="searchKey" />
+            <i
+              v-if="searchKey"
+              @click="searchKey = ''"
+              class="iconfont icon-toast_red del"
+            ></i>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="mask" v-show="isShowMask"></div>
+    <!-- 断网时,输入关键字触发网络请求后,骨架图的隐藏会触发v-infinite-scroll,原因未知。进而导致循环触发,因此list为空时要禁用v-infinite-scroll -->
+    <ul
+      class="w-list"
+      v-if="!(list.length === 0 && !hasMoreData)"
+      v-infinite-scroll="requestMoreData"
+      :infinite-scroll-disabled="
+        !hasMoreData || isRequestingMoreData || list.length === 0
+      "
+      ref="w-list-ref"
+      @scroll.self="onWorkListScroll"
+    >
+      <li class="add-work" @click="add">
+        <div class="wrapper">
+          <div class="add-con">
+            <div>
+              <i class="iconfont icon-works_add"></i>
+            </div>
+            <span>{{ create }}</span>
+          </div>
+        </div>
+      </li>
+      <!-- 骨架图 -->
+      <template v-if="isRequestingMoreData && list.length === 0">
+        <li v-for="index in 19" :key="index">
+          <div class="wrapper">
+            <workCardSkeleton></workCardSkeleton>
+          </div>
+        </li>
+      </template>
+      <li
+        v-for="(item, i) in list"
+        :key="i"
+        :class="{ 'has-more-data': hasMoreData }"
+      >
+        <div class="wrapper">
+          <div class="li-hover">
+            <span class="lipreview" @click="handlePreview(item)">{{
+              preview
+            }}</span>
+            <ul class="oper">
+              <li class="comfirmhover" @click="edit(item)">
+                <i class="iconfont icon-works_editor"></i>{{ edittips }}
+              </li>
+              <li class="comfirmhover" @click="openShare(item)">
+                <i class="iconfont icon-works_share"></i>{{ share }}
+              </li>
+              <li class="cancelhover" @click="del(item, i)">
+                <i class="iconfont icon-works_delete"></i>{{ deltips }}
+              </li>
+            </ul>
+          </div>
+          <div class="img" @click="handlePreview(item)">
+            <img class="real" :src="item.icon || $thumb" alt="" />
+          </div>
+          <div class="li-info">
+            <div>
+              <span class="shenglve tttttt" :title="item.name || no_title">{{
+                item.name || no_title
+              }}</span>
+            </div>
+            <div>
+              <span>{{ item.createTime.split(" ")[0] }}</span>
+              <div :title="item.visit">
+                <i class="iconfont icon-works_look"></i
+                >{{ item.visit > 10000 ? "1w+" : item.visit }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </li>
+      <div
+        class="work-list-loading-wrapper"
+        v-show="isRequestingMoreData && list.length !== 0"
+      >
+        <img
+          class="work-list-loading"
+          :src="require('@/assets/images/icons/work-list-loading.gif')"
+        />
+      </div>
+    </ul>
+    <div
+      class="nodata"
+      v-if="list.length == 0 && !hasMoreData && lastestUsedSearchKey"
+    >
+      <img :src="$noresult" alt="" />
+      <span>{{ no_search_result }}~</span>
+    </div>
+    <div
+      class="nodata"
+      v-if="list.length == 0 && !hasMoreData && !lastestUsedSearchKey"
+    >
+      <img :src="config.empty" alt="" />
+      <span>{{ no_works }}</span>
+      <button @click="add" class="upload-btn-in-table">{{ create }}</button>
+    </div>
+    <share
+      :show="showShare"
+      :item="shareItem"
+      @close="showShare = false"
+    ></share>
+    <preview
+      v-if="showItem"
+      :name="showItem.name"
+      :show="showPreview"
+      :ifr="`./show.html?id=${showItem.id}&lang=${$lang}`"
+      :dark="false"
+      @close="showPreview = false"
+    />
+    <div class="dialog" style="z-index: 1000" v-if="isShowMaterialSelector">
+      <MaterialSelector
+        :isDarkTheme="false"
+        :title="select_material"
+        :selectableType="['pano', '3D']"
+        :isMultiSelection="true"
+        initialMaterialType="pano"
+        @cancel="isShowMaterialSelector = false"
+        @submit="handleSubmitFromMaterialSelector"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import share from "../popup/share";
+import preview from "@/components/preview";
+import workCardSkeleton from "@/components/workCardSkeleton.vue";
+import config from "@/config";
+import { debounce } from "@/utils/other.js";
+import MaterialSelector from "@/components/materialSelector.vue";
+import { mapGetters } from "vuex";
+import { i18n } from "@/lang";
+import { $waiting } from "@/components/shared/loading";
+
+import {
+  addWorks,
+  getWorksList,
+  delWorks,
+  getPanoInfo,
+  saveWorks,
+} from "@/api";
+
+export default {
+  components: {
+    share,
+    preview,
+    workCardSkeleton,
+    MaterialSelector,
+  },
+  computed: {
+    ...mapGetters(["info"]),
+  },
+  data() {
+    return {
+      myWorks: i18n.t("material.works.my"),
+      create: i18n.t("material.works.create"),
+      search: i18n.t("material.works.search"),
+      preview: i18n.t("material.works.preview"),
+      edittips: i18n.t("material.works.edit"),
+      share: i18n.t("material.works.share"),
+      deltips: i18n.t("material.works.delete"),
+      no_works: i18n.t("material.works.no_works"),
+      no_title: i18n.t("gather.no_title"),
+      no_search_result: i18n.t("gather.no_search_result"),
+      select_material: i18n.t("gather.select_material"),
+
+      config,
+      list: [],
+      workTotalNum: undefined,
+      hasMoreData: true,
+      isRequestingMoreData: false,
+
+      searchKey: "",
+      // 因为searchKey的变化经过debounce、异步请求的延时,才会反映到数据列表的变化上,所以是否显示、显示哪种无数据提示,也要等到数据列表变化后,根据数据列表是否为空,以及引发本次变化的那个searchKey瞬时值来决定。本变量就是用来保存那个瞬时值。
+      lastestUsedSearchKey: "",
+      isFilterFocus: false,
+
+      showShare: false,
+      showPreview: false,
+      showItem: "",
+      shareItem: "",
+
+      isBackingTop: false,
+      isShowBackTopBtn: false,
+
+      isShowMask: false,
+
+      isShowMaterialSelector: false,
+      newWorkId: "",
+    };
+  },
+  mounted() {
+    this.requestMoreData();
+  },
+  watch: {
+    searchKey: {
+      handler: function (val) {
+        if (val.length > 0) {
+          this.selectedList = [];
+        }
+        this.refreshListDebounced();
+      },
+      immediate: false,
+    },
+  },
+  methods: {
+    onFilterFocus() {
+      this.isFilterFocus = true;
+    },
+    onFilterBlur() {
+      this.isFilterFocus = false;
+    },
+    refreshListDebounced: debounce(
+      function () {
+        this.list = [];
+        this.isRequestingMoreData = false;
+        this.hasMoreData = true;
+        this.requestMoreData();
+      },
+      500,
+      false
+    ),
+    openShare(data) {
+      console.log(data);
+      getPanoInfo(data.id, (data) => {
+        if (data.scenes.length <= 0) {
+          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
+        }
+        this.showShare = true;
+        this.shareItem = data;
+      });
+    },
+
+    handlePreview(item) {
+      getPanoInfo(item.id, (data) => {
+        if (data.scenes.length <= 0) {
+          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
+        }
+        this.showItem = {
+          ...item,
+          ...data,
+        };
+        this.showPreview = true;
+      });
+    },
+    add() {
+      // 新建作品,弹窗让用户给作品选择素材。
+      $waiting.show();
+      addWorks({}, (res) => {
+        $waiting.hide();
+        this.newWorkId = res.data.id;
+        this.isShowMaterialSelector = true;
+      });
+    },
+    handleSubmitFromMaterialSelector(selected) {
+      $waiting.show();
+      // 拿新作品的初始数据
+      getPanoInfo(
+        this.newWorkId,
+        // 拿到了。
+        (data) => {
+          // 往里边添加用户选中的素材。
+          this.$store.commit("SetInfo", data);
+          console.log("selected", selected);
+          for (const [key, item] of Object.entries(selected)) {
+            if (item.materialType === "pano") {
+              let newScene = {
+                icon: item.icon,
+                sceneCode: item.sceneCode,
+                sceneTitle: item.name,
+                category: this.info.catalogs[0].id,
+                type: "pano",
+                id: "s_" + this.$randomWord(true, 8, 8),
+              };
+
+              console.log("key", key);
+              if (Number(key) === 0) {
+                //新建时开天空mask
+                newScene = Object.assign(newScene, this.info.scenes[0]);
+                newScene.customMask.sky.isShow = true;
+                this.info.scenes[0] = newScene;
+              } else {
+                newScene = Object.assign(newScene, {
+                  customMask: this.info.scenes[0].customMask,
+                  initVisual: this.info.scenes[0].initVisual,
+                });
+                newScene.customMask.sky.isShow = true;
+                this.info.scenes.push(newScene);
+              }
+            } else if (item.materialType === "3D") {
+              let newScene = {
+                icon: item.thumb,
+                sceneCode: item.num,
+                sceneTitle: item.sceneName,
+                category: this.info.catalogs[0].id,
+                type: "4dkk",
+                id: "s_" + this.$randomWord(true, 8, 8),
+              };
+              if (Number(key) === 0) {
+                this.info.scenes[0] = null;
+                this.info.scenes[0] = newScene;
+              } else {
+                this.info.scenes.push(newScene);
+              }
+            }
+          }
+
+          // 保存新作品
+          saveWorks(
+            {
+              id: this.newWorkId,
+              password: "",
+              someData: {
+                ...this.info,
+                status: 1,
+                icon: this.info.scenes[0].icon,
+              },
+            },
+            // 保存成功
+            () => {
+              $waiting.hide();
+              // 隐藏素材选择弹窗
+              this.isShowMaterialSelector = false;
+
+              // 刷新作品列表
+              this.list = [];
+              this.isRequestingMoreData = false;
+              this.hasMoreData = true;
+              this.requestMoreData()
+                .then(() => {
+                  // 刷新成功
+
+                  // 弹出提示窗口
+                  this.$confirm({
+                    title: this.$i18n.t("tips_code.tips"),
+                    content: this.$i18n.t("material.works.had_created"),
+                    okText: this.$i18n.t("material.works.goto_preview"),
+                    ok: () => {
+                      this.handlePreview(this.list[0]);
+                      this.newWorkId = "";
+                      this.$store.commit("SetInfo", {});
+                    },
+                    ok2Text: this.$i18n.t("material.works.continue_edit"),
+                    ok2: () => {
+                      window.open(
+                        `./edit.html?id=${this.newWorkId}&lang=${this.$lang}`
+                      );
+                      this.newWorkId = "";
+                      this.$store.commit("SetInfo", {});
+                    },
+                  });
+                })
+                .catch(() => {
+                  this.$msg.message(
+                    this.$i18n.t("material.works.had_created_but_no_link")
+                  );
+                  console.error("已成功新建作品,但刷新作品列表失败。");
+                });
+            },
+            // 保存失败,删除新建的作品。
+            (error) => {
+              $waiting.hide();
+              console.error("保存失败:", error);
+              delWorks(this.newWorkId);
+              this.newWorkId = "";
+              this.$store.commit("SetInfo", {});
+            }
+          );
+        },
+        // 没拿到,删除新建的作品。
+        (error) => {
+          console.error("没拿到新建的作品数据:", error);
+          delWorks(this.newWorkId);
+          this.newWorkId = "";
+        }
+      );
+    },
+    
+    edit(item) {
+      const from = this.$route.name;
+      window.open(`./edit.html?id=${item.id}&lang=${this.$lang}&from=${from}`);
+    },
+
+    del(item, index) {
+      this.$confirm({
+        title: this.$i18n.t("material.works.delete_work"),
+        content: this.$i18n.t("material.works.comfirm_delete"),
+        ok: () => {
+          $waiting.show();
+
+          delWorks(item.id, () => {
+            this.$msg.success(this.$i18n.t("gather.delete_success"));
+            this.isRequestingMoreData = true;
+            const lastestUsedSearchKey = this.searchKey;
+            getWorksList(
+              {
+                pageNum: this.list.length,
+                pageSize: 1,
+                searchKey: this.searchKey,
+              },
+              (data) => {
+                $waiting.hide();
+                this.list.splice(index, 1);
+                this.list = this.list.concat(data.data.list);
+                if (this.list.length === data.data.total) {
+                  this.hasMoreData = false;
+                }
+                this.isRequestingMoreData = false;
+                this.lastestUsedSearchKey = lastestUsedSearchKey;
+                if (!lastestUsedSearchKey) {
+                  this.workTotalNum = data.data.total;
+                }
+                // TODO: 这是干啥呢?
+                this.$nextTick(() => {
+                  this.$bus.emit("refreshTips");
+                });
+              },
+              () => {
+                $waiting.hide();
+                this.lastestUsedSearchKey = lastestUsedSearchKey;
+                this.isRequestingMoreData = false;
+              }
+            );
+          });
+        },
+      });
+    },
+
+    requestMoreData() {
+      this.isRequestingMoreData = true;
+      const lastestUsedSearchKey = this.searchKey;
+      return new Promise((resolve, reject) => {
+        getWorksList(
+          {
+            pageNum: Math.floor(this.list.length / config.PAGE_SIZE) + 1,
+            pageSize: config.PAGE_SIZE,
+            searchKey: this.searchKey,
+          },
+          (data) => {
+            this.list = this.list.concat(data.data.list);
+            if (this.list.length === data.data.total) {
+              this.hasMoreData = false;
+            }
+            this.isRequestingMoreData = false;
+            this.lastestUsedSearchKey = lastestUsedSearchKey;
+            if (!lastestUsedSearchKey) {
+              this.workTotalNum = data.data.total;
+            }
+            // TODO: 这是干啥呢?
+            this.$nextTick(() => {
+              this.$bus.emit("refreshTips");
+            });
+            resolve();
+          },
+          () => {
+            this.isRequestingMoreData = false;
+            this.lastestUsedSearchKey = lastestUsedSearchKey;
+            reject();
+          }
+        );
+      });
+    },
+
+    onClickBackTop() {
+      if (this.isBackingTop) {
+        return;
+      }
+      this.isBackingTop = true;
+
+      const startTime = Date.now();
+      const totalScroll = this.$refs["w-list-ref"].scrollTop;
+      const fn = () => {
+        if (this.$refs["w-list-ref"].scrollTop === 0) {
+          this.isBackingTop = false;
+          return;
+        }
+
+        const nowTime = Date.now();
+        const assumeScrollTop =
+          totalScroll - ((nowTime - startTime) * totalScroll) / 500;
+        this.$refs["w-list-ref"].scrollTop =
+          assumeScrollTop > 0 ? assumeScrollTop : 0;
+        requestAnimationFrame(fn);
+      };
+      requestAnimationFrame(fn);
+    },
+    onWorkListScroll(e) {
+      if (e.target.scrollTop >= 30) {
+        !this.isShowMask && (this.isShowMask = true);
+      } else {
+        this.isShowMask && (this.isShowMask = false);
+      }
+
+      if (e.target.scrollTop >= 600) {
+        this.isShowBackTopBtn = true;
+      } else {
+        this.isShowBackTopBtn = false;
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.works {
+  width: 100%;
+  flex-direction: column;
+  position: relative;
+
+  .back-top {
+    position: absolute;
+    right: -80px;
+    bottom: 30px;
+    width: 60px;
+    height: 60px;
+    border-radius: 8px;
+    background-color: #fff;
+    z-index: 1;
+    color: #c8c9cc;
+
+    &:hover {
+      color: #323233;
+    }
+
+    cursor: pointer;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    i {
+      font-size: 20px;
+    }
+  }
+
+  .tab {
+    flex: 0 0 auto;
+    width: 100%;
+    display: flex;
+    background: #fff;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px 30px;
+
+    > span {
+      font-size: 18px;
+      font-weight: bold;
+    }
+
+    .tab-r {
+      align-items: center;
+      display: flex;
+
+      .ui-button {
+        margin-right: 20px;
+      }
+    }
+  }
+
+  .mask {
+    position: absolute;
+    width: 100%;
+    top: 200px;
+    height: 30px;
+    background: linear-gradient(rgb(239, 242, 244), rgba(255, 255, 255, 0));
+    z-index: 1;
+    pointer-events: none;
+  }
+
+  .w-list {
+    flex: 1 1 auto;
+    overflow: auto;
+    margin-top: 22px;
+    padding-top: 8px;
+    @gap: 20px;
+    display: flex;
+    flex-wrap: wrap;
+    align-content: flex-start;
+    // 让宽度和视口等宽,为了保证鼠标列表显示区域以外时列表也能响应滚轮事件。
+    margin-left: calc((100vw - 100%) / -2);
+    padding-left: calc((100vw - 100%) / 2);
+    margin-right: calc((100vw - 100%) / -2);
+    padding-right: calc((100vw - 100%) / 2);
+
+    &::-webkit-scrollbar {
+      width: 0;
+      height: 0;
+    }
+
+    > li {
+      width: calc((100% - @gap * 4) / 5);
+      height: 322px;
+      margin-bottom: @gap;
+      margin-right: @gap;
+
+      &:nth-of-type(5n) {
+        margin-right: 0;
+      }
+
+      // 因为有个“创建作品”card占着空间,每次拿20个数据,每行五个,又不想每次拿到数据后最后一行只有一个card,所以把最后那个card隐藏掉。
+      &:last-of-type.has-more-data {
+        display: none;
+      }
+
+      .wrapper {
+        height: 100%;
+        background: #fff;
+        position: relative;
+        border-radius: 6px;
+        overflow: hidden;
+
+        .li-hover {
+          display: none;
+          width: 100%;
+          height: 240px;
+          position: absolute;
+          top: 0;
+          left: 0;
+          z-index: 99;
+          background: rgba(0, 0, 0, 0.6);
+
+          .lipreview {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            color: #fff;
+            display: inline-block;
+            line-height: 40px;
+            height: 40px;
+            width: 100px;
+            text-align: center;
+            border-radius: 22px;
+            cursor: pointer;
+            background-color: transparent;
+            border: 1px solid #fff;
+
+            &:hover {
+              border: none;
+              background: #1983f6;
+            }
+          }
+
+          .oper {
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+            position: absolute;
+            bottom: 10px;
+            left: 0;
+            width: 100%;
+
+            > li {
+              color: #fff;
+              font-size: 13px;
+              display: flex;
+              align-items: center;
+              cursor: pointer;
+
+              > i {
+                font-size: 20px;
+                margin-right: 4px;
+              }
+            }
+          }
+        }
+
+        .img {
+          width: 100%;
+          height: 240px;
+          position: relative;
+          overflow: hidden;
+          cursor: pointer;
+
+          .real {
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 50%;
+            transform: translateX(-50%);
+            z-index: 0;
+            transition: all ease 0.3s;
+          }
+        }
+
+        .li-info {
+          font-size: 14px;
+          padding: 10px;
+
+          > div {
+            text-align: left;
+
+            &:first-of-type {
+              > span {
+                font-weight: bold;
+                margin-bottom: 10px;
+                display: inline-block;
+                text-overflow: ellipsis;
+                overflow: hidden;
+                white-space: nowrap;
+                cursor: pointer;
+                color: #323233;
+                font-size: 16px;
+              }
+            }
+
+            &:last-of-type {
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+
+              > span {
+                font-size: 14px;
+                color: #969799;
+              }
+
+              > div {
+                color: #969799;
+
+                i {
+                  margin-right: 6px;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      &:hover {
+        .wrapper {
+          box-shadow: 0px 2px 12px 0px rgba(50, 50, 51, 0.12);
+          transform: translateY(-6px);
+
+          .li-hover {
+            display: block;
+          }
+
+          .img {
+            .real {
+              height: 108%;
+            }
+          }
+        }
+      }
+    }
+
+    .add-work {
+      .wrapper {
+        .add-con {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          text-align: center;
+
+          div {
+            width: 60px;
+            height: 60px;
+            border-radius: 50%;
+            background: linear-gradient(144deg, #00aefb 0%, #0076f6 100%);
+            position: relative;
+            cursor: pointer;
+            margin: 0 auto;
+
+            > i {
+              font-size: 16px;
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+              color: #fff;
+            }
+          }
+
+          span {
+            color: #333333;
+            display: inline-block;
+            margin-top: 8px;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .work-list-loading-wrapper {
+      width: 100%;
+      margin-top: 20px;
+      margin-bottom: 22px;
+
+      .work-list-loading {
+        display: block;
+        margin: 0 auto;
+        width: 50px;
+        height: 8px;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="less" scoped>
+@import "../style.less";
+</style>

+ 785 - 0
packages/qjkankan-editor/src/views/material/works/list.vue

@@ -0,0 +1,785 @@
+<template>
+  <div
+    class="scroll-container"
+    ref="w-list-ref"
+    @scroll.self="onWorkListScroll"
+  >
+    <div class="back-top" @click="onClickBackTop" v-show="isShowBackTopBtn">
+      <i class="iconfont icon-top"></i>
+    </div>
+    <div class="mask" v-show="isShowMask"></div>
+    <!-- 断网时,输入关键字触发网络请求后,骨架图的隐藏会触发v-infinite-scroll,原因未知。进而导致循环触发,因此list为空时要禁用v-infinite-scroll -->
+    <ul
+      class="w-list"
+      v-if="!(list.length === 0 && !hasMoreData)"
+      v-infinite-scroll="requestMoreData"
+      :infinite-scroll-disabled="
+        !hasMoreData || isRequestingMoreData || list.length === 0
+      "
+    >
+      <li class="add-work" @click="add">
+        <div class="wrapper">
+          <div class="add-con">
+            <div>
+              <i class="iconfont icon-works_add"></i>
+            </div>
+            <span>{{ create }}</span>
+          </div>
+        </div>
+      </li>
+      <!-- 骨架图 -->
+      <template v-if="isRequestingMoreData && list.length === 0">
+        <li v-for="index in 19" :key="index">
+          <div class="wrapper">
+            <workCardSkeleton></workCardSkeleton>
+          </div>
+        </li>
+      </template>
+      <li
+        v-for="(item, i) in list"
+        :key="i"
+        :class="{ 'has-more-data': hasMoreData }"
+      >
+        <div class="wrapper">
+          <div class="li-hover">
+            <span class="lipreview" @click="handlePreview(item)">{{
+              preview
+            }}</span>
+            <ul class="oper">
+              <li class="comfirmhover" @click="edit(item)">
+                <i class="iconfont icon-works_editor"></i>{{ edittips }}
+              </li>
+              <li class="comfirmhover" @click="openShare(item)">
+                <i class="iconfont icon-works_share"></i>{{ share }}
+              </li>
+              <li class="cancelhover" @click="del(item, i)">
+                <i class="iconfont icon-works_delete"></i>{{ deltips }}
+              </li>
+            </ul>
+          </div>
+          <div class="img" @click="handlePreview(item)">
+            <img class="real" :src="item.icon || $thumb" alt="" />
+          </div>
+          <div class="li-info">
+            <div>
+              <span class="shenglve tttttt" :title="item.name || no_title">{{
+                item.name || no_title
+              }}</span>
+            </div>
+            <div>
+              <span>{{ item.createTime.split(" ")[0] }}</span>
+              <div :title="item.visit">
+                <i class="iconfont icon-works_look"></i
+                >{{ item.visit > 10000 ? "1w+" : item.visit }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </li>
+      <div
+        class="work-list-loading-wrapper"
+        v-show="isRequestingMoreData && list.length !== 0"
+      >
+        <img
+          class="work-list-loading"
+          :src="require('@/assets/images/icons/work-list-loading.gif')"
+        />
+      </div>
+    </ul>
+    <div
+      class="nodata"
+      v-if="list.length == 0 && !hasMoreData && lastestUsedSearchKey"
+    >
+      <img :src="$noresult" alt="" />
+      <span>{{ no_search_result }}~</span>
+    </div>
+    <div
+      class="nodata"
+      v-if="list.length == 0 && !hasMoreData && !lastestUsedSearchKey"
+    >
+      <img :src="config.empty" alt="" />
+      <span>{{ no_works }}</span>
+      <button @click="add" class="upload-btn-in-table">{{ create }}</button>
+    </div>
+
+    <share
+      :show="showShare"
+      :item="shareItem"
+      @close="showShare = false"
+    ></share>
+
+    <preview
+      v-if="showItem"
+      :name="showItem.name"
+      :show="showPreview"
+      :ifr="`./show.html?id=${showItem.id}&lang=${$lang}`"
+      :dark="false"
+      @close="showPreview = false"
+    />
+
+    <div class="dialog" style="z-index: 1000" v-if="isShowMaterialSelector">
+      <MaterialSelector
+        :isDarkTheme="false"
+        :title="select_material"
+        :selectableType="['pano', '3D']"
+        :isMultiSelection="true"
+        initialMaterialType="pano"
+        @cancel="isShowMaterialSelector = false"
+        @submit="handleSubmitFromMaterialSelector"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import share from "../popup/share";
+import preview from "@/components/preview";
+import workCardSkeleton from "@/components/workCardSkeleton.vue";
+import config from "@/config";
+import { debounce } from "@/utils/other.js";
+import MaterialSelector from "@/components/materialSelector.vue";
+import { mapGetters } from "vuex";
+import { i18n } from "@/lang";
+import { $waiting } from "@/components/shared/loading";
+
+import {
+  addWorks,
+  getWorksList,
+  delWorks,
+  getPanoInfo,
+  saveWorks,
+} from "@/api";
+
+export default {
+  components: {
+    share,
+    preview,
+    workCardSkeleton,
+    MaterialSelector,
+  },
+  computed: {
+    ...mapGetters(["info"]),
+  },
+  props: {
+    searchKey: String,
+  },
+  data() {
+    return {
+      myWorks: i18n.t("material.works.my"),
+      create: i18n.t("material.works.create"),
+      preview: i18n.t("material.works.preview"),
+      edittips: i18n.t("material.works.edit"),
+      share: i18n.t("material.works.share"),
+      deltips: i18n.t("material.works.delete"),
+      no_works: i18n.t("material.works.no_works"),
+      no_title: i18n.t("gather.no_title"),
+      no_search_result: i18n.t("gather.no_search_result"),
+      select_material: i18n.t("gather.select_material"),
+
+      config,
+      list: [],
+      workTotalNum: undefined,
+      hasMoreData: true,
+      isRequestingMoreData: false,
+
+      // searchKey: "",
+      // 因为searchKey的变化经过debounce、异步请求的延时,才会反映到数据列表的变化上,所以是否显示、显示哪种无数据提示,也要等到数据列表变化后,根据数据列表是否为空,以及引发本次变化的那个searchKey瞬时值来决定。本变量就是用来保存那个瞬时值。
+      lastestUsedSearchKey: "",
+      isFilterFocus: false,
+
+      showShare: false,
+      showPreview: false,
+      showItem: "",
+      shareItem: "",
+      isBackingTop: false,
+      isShowBackTopBtn: false,
+      isShowMask: false,
+      isShowMaterialSelector: false,
+      newWorkId: "",
+    };
+  },
+  mounted() {
+    this.requestMoreData();
+  },
+  watch: {
+    searchKey: {
+      handler: function (val) {
+        if (val.length > 0) {
+          this.selectedList = [];
+        }
+        this.refreshListDebounced();
+      },
+      immediate: false,
+    },
+    workTotalNum: {
+      handler: function (val) {
+        if (val) {
+          this.$emit("updateNum", val);
+        }
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+  
+    refreshListDebounced: debounce(
+      function () {
+        this.list = [];
+        this.isRequestingMoreData = false;
+        this.hasMoreData = true;
+        this.requestMoreData();
+      },
+      500,
+      false
+    ),
+    openShare(data) {
+      console.log(data);
+      getPanoInfo(data.id, (data) => {
+        if (data.scenes.length <= 0) {
+          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
+        }
+        this.showShare = true;
+        this.shareItem = data;
+      });
+    },
+
+    handlePreview(item) {
+      getPanoInfo(item.id, (data) => {
+        if (data.scenes.length <= 0) {
+          return this.$msg.warning(this.$i18n.t("material.works.no_link"));
+        }
+        this.showItem = {
+          ...item,
+          ...data,
+        };
+        this.showPreview = true;
+      });
+    },
+    add() {
+      // 新建作品,弹窗让用户给作品选择素材。
+      $waiting.show();
+      addWorks({}, (res) => {
+        $waiting.hide();
+        this.newWorkId = res.data.id;
+        this.isShowMaterialSelector = true;
+      });
+    },
+    handleSubmitFromMaterialSelector(selected) {
+      $waiting.show();
+      // 拿新作品的初始数据
+      getPanoInfo(
+        this.newWorkId,
+        // 拿到了。
+        (data) => {
+          // 往里边添加用户选中的素材。
+          this.$store.commit("SetInfo", data);
+          console.log("selected", selected);
+          for (const [key, item] of Object.entries(selected)) {
+            if (item.materialType === "pano") {
+              let newScene = {
+                icon: item.icon,
+                sceneCode: item.sceneCode,
+                sceneTitle: item.name,
+                category: this.info.catalogs[0].id,
+                type: "pano",
+                id: "s_" + this.$randomWord(true, 8, 8),
+              };
+
+              console.log("key", key);
+              if (Number(key) === 0) {
+                //新建时开天空mask
+                newScene = Object.assign(newScene, this.info.scenes[0]);
+                newScene.customMask.sky.isShow = true;
+                this.info.scenes[0] = newScene;
+              } else {
+                newScene = Object.assign(newScene, {
+                  customMask: this.info.scenes[0].customMask,
+                  initVisual: this.info.scenes[0].initVisual,
+                });
+                newScene.customMask.sky.isShow = true;
+                this.info.scenes.push(newScene);
+              }
+            } else if (item.materialType === "3D") {
+              let newScene = {
+                icon: item.thumb,
+                sceneCode: item.num,
+                sceneTitle: item.sceneName,
+                category: this.info.catalogs[0].id,
+                type: "4dkk",
+                id: "s_" + this.$randomWord(true, 8, 8),
+              };
+              if (Number(key) === 0) {
+                this.info.scenes[0] = null;
+                this.info.scenes[0] = newScene;
+              } else {
+                this.info.scenes.push(newScene);
+              }
+            }
+          }
+
+          // 保存新作品
+          saveWorks(
+            {
+              id: this.newWorkId,
+              password: "",
+              someData: {
+                ...this.info,
+                status: 1,
+                icon: this.info.scenes[0].icon,
+              },
+            },
+            // 保存成功
+            () => {
+              $waiting.hide();
+              // 隐藏素材选择弹窗
+              this.isShowMaterialSelector = false;
+
+              // 刷新作品列表
+              this.list = [];
+              this.isRequestingMoreData = false;
+              this.hasMoreData = true;
+              this.requestMoreData()
+                .then(() => {
+                  // 刷新成功
+
+                  // 弹出提示窗口
+                  this.$confirm({
+                    title: this.$i18n.t("tips_code.tips"),
+                    content: this.$i18n.t("material.works.had_created"),
+                    okText: this.$i18n.t("material.works.goto_preview"),
+                    ok: () => {
+                      this.handlePreview(this.list[0]);
+                      this.newWorkId = "";
+                      this.$store.commit("SetInfo", {});
+                    },
+                    ok2Text: this.$i18n.t("material.works.continue_edit"),
+                    ok2: () => {
+                      window.open(
+                        `./edit.html?id=${this.newWorkId}&lang=${this.$lang}`
+                      );
+                      this.newWorkId = "";
+                      this.$store.commit("SetInfo", {});
+                    },
+                  });
+                })
+                .catch(() => {
+                  this.$msg.message(
+                    this.$i18n.t("material.works.had_created_but_no_link")
+                  );
+                  console.error("已成功新建作品,但刷新作品列表失败。");
+                });
+            },
+            // 保存失败,删除新建的作品。
+            (error) => {
+              $waiting.hide();
+              console.error("保存失败:", error);
+              delWorks(this.newWorkId);
+              this.newWorkId = "";
+              this.$store.commit("SetInfo", {});
+            }
+          );
+        },
+        // 没拿到,删除新建的作品。
+        (error) => {
+          console.error("没拿到新建的作品数据:", error);
+          delWorks(this.newWorkId);
+          this.newWorkId = "";
+        }
+      );
+    },
+
+    edit(item) {
+      window.open(`./edit.html?id=${item.id}&lang=${this.$lang}&from=list`);
+    },
+
+    del(item, index) {
+      this.$confirm({
+        title: this.$i18n.t("material.works.delete_work"),
+        content: this.$i18n.t("material.works.comfirm_delete"),
+        ok: () => {
+          $waiting.show();
+
+          delWorks(item.id, () => {
+            this.$msg.success(this.$i18n.t("gather.delete_success"));
+            this.isRequestingMoreData = true;
+            const lastestUsedSearchKey = this.searchKey;
+            getWorksList(
+              {
+                pageNum: this.list.length,
+                pageSize: 1,
+                searchKey: this.searchKey,
+              },
+              (data) => {
+                $waiting.hide();
+                this.list.splice(index, 1);
+                this.list = this.list.concat(data.data.list);
+                if (this.list.length === data.data.total) {
+                  this.hasMoreData = false;
+                }
+                this.isRequestingMoreData = false;
+                this.lastestUsedSearchKey = lastestUsedSearchKey;
+                if (!lastestUsedSearchKey) {
+                  this.workTotalNum = data.data.total;
+                }
+                // TODO: 这是干啥呢?
+                this.$nextTick(() => {
+                  this.$bus.emit("refreshTips");
+                });
+              },
+              () => {
+                $waiting.hide();
+                this.lastestUsedSearchKey = lastestUsedSearchKey;
+                this.isRequestingMoreData = false;
+              }
+            );
+          });
+        },
+      });
+    },
+
+    requestMoreData() {
+      this.isRequestingMoreData = true;
+
+      const lastestUsedSearchKey = this.searchKey;
+      return new Promise((resolve, reject) => {
+        getWorksList(
+          {
+            pageNum: Math.floor(this.list.length / config.PAGE_SIZE) + 1,
+            pageSize: config.PAGE_SIZE,
+            searchKey: this.searchKey,
+          },
+          (data) => {
+            this.list = this.list.concat(data.data.list);
+            if (this.list.length === data.data.total) {
+              this.hasMoreData = false;
+            }
+            this.isRequestingMoreData = false;
+            this.lastestUsedSearchKey = lastestUsedSearchKey;
+            if (!lastestUsedSearchKey) {
+              this.workTotalNum = data.data.total;
+            }
+            // TODO: 这是干啥呢?
+            this.$nextTick(() => {
+              this.$bus.emit("refreshTips");
+            });
+            resolve();
+          },
+          () => {
+            this.isRequestingMoreData = false;
+            this.lastestUsedSearchKey = lastestUsedSearchKey;
+            reject();
+          }
+        );
+      });
+    },
+
+    onClickBackTop() {
+      if (this.isBackingTop) {
+        return;
+      }
+      this.isBackingTop = true;
+
+      const startTime = Date.now();
+      const totalScroll = this.$refs["w-list-ref"].scrollTop;
+      const fn = () => {
+        if (this.$refs["w-list-ref"].scrollTop === 0) {
+          this.isBackingTop = false;
+          return;
+        }
+
+        const nowTime = Date.now();
+        const assumeScrollTop =
+          totalScroll - ((nowTime - startTime) * totalScroll) / 500;
+        this.$refs["w-list-ref"].scrollTop =
+          assumeScrollTop > 0 ? assumeScrollTop : 0;
+        requestAnimationFrame(fn);
+      };
+      requestAnimationFrame(fn);
+    },
+    onWorkListScroll(e) {
+      if (e.target.scrollTop >= 30) {
+        !this.isShowMask && (this.isShowMask = true);
+      } else {
+        this.isShowMask && (this.isShowMask = false);
+      }
+
+      if (e.target.scrollTop >= 600) {
+        this.isShowBackTopBtn = true;
+      } else {
+        this.isShowBackTopBtn = false;
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.scroll-container {
+  flex: 1 1 auto;
+  overflow: auto;
+  margin-left: calc((100vw - 100%) / -2);
+  padding-left: calc((100vw - 100%) / 2);
+  margin-right: calc((100vw - 100%) / -2);
+  padding-right: calc((100vw - 100%) / 2);
+  &::-webkit-scrollbar {
+    width: 0;
+    height: 0;
+  }
+  .back-top {
+    position: absolute;
+    right: -80px;
+    bottom: 30px;
+    width: 60px;
+    height: 60px;
+    border-radius: 8px;
+    background-color: #fff;
+    z-index: 1;
+    color: #c8c9cc;
+
+    &:hover {
+      color: #323233;
+    }
+
+    cursor: pointer;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    i {
+      font-size: 20px;
+    }
+  }
+
+  .mask {
+    position: absolute;
+    width: 100%;
+    top: 180px;
+    height: 30px;
+    background: linear-gradient(rgb(239, 242, 244), rgba(255, 255, 255, 0));
+    z-index: 1;
+    pointer-events: none;
+  }
+
+  .w-list {
+    margin-top: 22px;
+    padding-top: 8px;
+    width: 100%;
+    @gap: 20px;
+    display: flex;
+    flex-wrap: wrap;
+    align-content: flex-start;
+
+    > li {
+      width: calc((100% - @gap * 4) / 5);
+      height: 322px;
+      margin-bottom: @gap;
+      margin-right: @gap;
+
+      &:nth-of-type(5n) {
+        margin-right: 0;
+      }
+
+      // 因为有个“创建作品”card占着空间,每次拿20个数据,每行五个,又不想每次拿到数据后最后一行只有一个card,所以把最后那个card隐藏掉。
+      &:last-of-type.has-more-data {
+        display: none;
+      }
+
+      .wrapper {
+        height: 100%;
+        background: #fff;
+        position: relative;
+        border-radius: 6px;
+        overflow: hidden;
+
+        .li-hover {
+          display: none;
+          width: 100%;
+          height: 240px;
+          position: absolute;
+          top: 0;
+          left: 0;
+          z-index: 99;
+          background: rgba(0, 0, 0, 0.6);
+
+          .lipreview {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            color: #fff;
+            display: inline-block;
+            line-height: 40px;
+            height: 40px;
+            width: 100px;
+            text-align: center;
+            border-radius: 22px;
+            cursor: pointer;
+            background-color: transparent;
+            border: 1px solid #fff;
+
+            &:hover {
+              border: none;
+              background: #1983f6;
+            }
+          }
+
+          .oper {
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+            position: absolute;
+            bottom: 10px;
+            left: 0;
+            width: 100%;
+
+            > li {
+              color: #fff;
+              font-size: 13px;
+              display: flex;
+              align-items: center;
+              cursor: pointer;
+
+              > i {
+                font-size: 20px;
+                margin-right: 4px;
+              }
+            }
+          }
+        }
+
+        .img {
+          width: 100%;
+          height: 240px;
+          position: relative;
+          overflow: hidden;
+          cursor: pointer;
+
+          .real {
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 50%;
+            transform: translateX(-50%);
+            z-index: 0;
+            transition: all ease 0.3s;
+          }
+        }
+
+        .li-info {
+          font-size: 14px;
+          padding: 10px;
+
+          > div {
+            text-align: left;
+
+            &:first-of-type {
+              > span {
+                font-weight: bold;
+                margin-bottom: 10px;
+                display: inline-block;
+                text-overflow: ellipsis;
+                overflow: hidden;
+                white-space: nowrap;
+                cursor: pointer;
+                color: #323233;
+                font-size: 16px;
+              }
+            }
+
+            &:last-of-type {
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+
+              > span {
+                font-size: 14px;
+                color: #969799;
+              }
+
+              > div {
+                color: #969799;
+
+                i {
+                  margin-right: 6px;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      &:hover {
+        .wrapper {
+          box-shadow: 0px 2px 12px 0px rgba(50, 50, 51, 0.12);
+          transform: translateY(-6px);
+
+          .li-hover {
+            display: block;
+          }
+
+          .img {
+            .real {
+              height: 108%;
+            }
+          }
+        }
+      }
+    }
+
+    .add-work {
+      .wrapper {
+        .add-con {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          text-align: center;
+
+          div {
+            width: 60px;
+            height: 60px;
+            border-radius: 50%;
+            background: linear-gradient(144deg, #00aefb 0%, #0076f6 100%);
+            position: relative;
+            cursor: pointer;
+            margin: 0 auto;
+
+            > i {
+              font-size: 16px;
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+              color: #fff;
+            }
+          }
+
+          span {
+            color: #333333;
+            display: inline-block;
+            margin-top: 8px;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .work-list-loading-wrapper {
+      width: 100%;
+      margin-top: 20px;
+      margin-bottom: 22px;
+
+      .work-list-loading {
+        display: block;
+        margin: 0 auto;
+        width: 50px;
+        height: 8px;
+      }
+    }
+  }
+}
+</style>
+
+<style lang="less" scoped>
+@import "../style.less";
+</style>

+ 46 - 36
packages/qjkankan-editor/src/views/navigation/initialSceneSettings.vue

@@ -1,35 +1,47 @@
 <template>
 <template>
   <div class="initial-scene-settings" app-border dir-left>
   <div class="initial-scene-settings" app-border dir-left>
     <div class="initial-scene-settings__title">
     <div class="initial-scene-settings__title">
-      {{$i18n.t('navigation.init_scene')}}
-      <i class="iconfont icon-help_i tool-tip-for-editor" v-tooltip="$i18n.t('navigation.init_scene_tips')"/>
+      {{ $i18n.t("navigation.init_scene") }}
+      <i
+        class="iconfont icon-help_i tool-tip-for-editor"
+        v-tooltip="$i18n.t('navigation.init_scene_tips')"
+      />
     </div>
     </div>
-    
-    <img class="preview" v-if="info.firstScene" :src="info.firstScene.icon" alt="">
-    <img class="placeholder" v-else src="@/assets/images/pano-image-placeholder.png" alt="">
+
+    <img
+      class="preview"
+      v-if="info.firstScene"
+      :src="info.firstScene.icon"
+      alt=""
+    />
+    <img
+      class="placeholder"
+      v-else
+      src="@/assets/images/pano-image-placeholder.png"
+      alt=""
+    />
 
 
     <div class="change-init" v-if="info.firstScene">
     <div class="change-init" v-if="info.firstScene">
-      <button class="ui-button deepcancel" @click="deleteIndexInfo">{{$i18n.t('navigation.delete_init_scene')}}</button>
-      <button @click="showInitScene=true" class="ui-button submit">
-        {{$i18n.t('navigation.edit_init_scene')}}
+      <button class="ui-button deepcancel" @click="deleteIndexInfo">
+        {{ $i18n.t("navigation.delete_init_scene") }}
+      </button>
+      <button @click="showInitScene = true" class="ui-button submit">
+        {{ $i18n.t("navigation.edit_init_scene") }}
       </button>
       </button>
     </div>
     </div>
     <div class="set-init" v-else>
     <div class="set-init" v-else>
-      <button @click="showInitScene=true" class="ui-button submit">
-        {{$i18n.t('navigation.setting_init_scene')}}
+      <button @click="showInitScene = true" class="ui-button submit">
+        {{ $i18n.t("navigation.setting_init_scene") }}
       </button>
       </button>
     </div>
     </div>
 
 
     <div class="dialog" style="z-index: 2000" v-if="showInitScene">
     <div class="dialog" style="z-index: 2000" v-if="showInitScene">
-      <Selector
-        @cancle="showInitScene = false"
-        @submit="handleSelect"
-      />
+      <Selector @cancel="showInitScene = false" @submit="handleSelect" />
     </div>
     </div>
 
 
     <!-- <div class="dialog" style="z-index: 2000" v-if="showInitScene">
     <!-- <div class="dialog" style="z-index: 2000" v-if="showInitScene">
       <Select
       <Select
-        @cancle="showInitScene = false"
+        @cancel="showInitScene = false"
         :selected='info.firstScene'
         :selected='info.firstScene'
         :title="'选择素材'"
         :title="'选择素材'"
         @submit="handleSelect"
         @submit="handleSelect"
@@ -44,39 +56,37 @@ import { mapGetters } from "vuex";
 import Selector from "@/components/materialSelectorFromWork.vue";
 import Selector from "@/components/materialSelectorFromWork.vue";
 
 
 export default {
 export default {
-  components:{
+  components: {
     Selector,
     Selector,
   },
   },
-  data(){
+  data() {
     return {
     return {
-      showInitScene:false
-    }
+      showInitScene: false,
+    };
   },
   },
-  methods:{
-    deleteIndexInfo(){
+  methods: {
+    deleteIndexInfo() {
       this.$confirm({
       this.$confirm({
-          content: this.$i18n.t('tips.delete'),
-          ok: () => {
-              this.info.firstScene = ''
-              this.$store.commit("SetInfo", this.info);
-              this.$msg.success(this.$i18n.t('tips.delete_done'))
-          }
+        content: this.$i18n.t("tips.delete"),
+        ok: () => {
+          this.info.firstScene = "";
+          this.$store.commit("SetInfo", this.info);
+          this.$msg.success(this.$i18n.t("tips.delete_done"));
+        },
       });
       });
-      
     },
     },
     handleSelect(data) {
     handleSelect(data) {
-      this.info.firstScene = data[0] // 注意此处是浅拷贝
-      this.showInitScene = false
-    }
+      this.info.firstScene = data[0]; // 注意此处是浅拷贝
+      this.showInitScene = false;
+    },
   },
   },
   computed: {
   computed: {
     ...mapGetters({
     ...mapGetters({
       info: "info",
       info: "info",
-    })
+    }),
   },
   },
-  mounted(){
-  }
-}
+  mounted() {},
+};
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
@@ -130,4 +140,4 @@ export default {
     background-color: rgba(0, 0, 0, 0.5);
     background-color: rgba(0, 0, 0, 0.5);
   }
   }
 }
 }
-</style>
+</style>

+ 216 - 24
packages/qjkankan-editor/src/views/screen/Setting.vue

@@ -1,57 +1,233 @@
 <template>
 <template>
   <div class="view-setting" app-border dir-left>
   <div class="view-setting" app-border dir-left>
     <div class="title">
     <div class="title">
-      {{$i18n.t('screen.init_screen')}}
-      <i class="iconfont icon-help_i tool-tip-for-editor" v-tooltip="$i18n.t('screen.screen_tips')"/>
+      {{ $i18n.t("screen.init_screen") }}
+      <i
+        class="iconfont icon-help_i tool-tip-for-editor"
+        v-tooltip="$i18n.t('screen.screen_tips')"
+      />
     </div>
     </div>
     <template v-if="currentScene.type !== '4dkk'">
     <template v-if="currentScene.type !== '4dkk'">
-      <img class="preview" v-if="initImg" :src="`${initImg}?${Math.random()}`" alt="">
-      <img class="placeholder" v-else src="@/assets/images/pano-image-placeholder.png" alt="">
+      <img
+        class="preview"
+        v-if="initImg"
+        :src="`${initImg}?${Math.random()}`"
+        alt=""
+      />
+      <img
+        class="placeholder"
+        v-else
+        src="@/assets/images/pano-image-placeholder.png"
+        alt=""
+      />
+      <div class="item">
+        <div class="">
+          {{ $i18n.t("modules.screen.v_angle_re") }}
+        </div>
+
+        <vue-slider
+          v-model="vlookat"
+          :min="-90"
+          :max="90"
+          :height="6"
+          :width="`95%`"
+          :marks="marks"
+          direction="rtl"
+          tooltip="active"
+          :tooltip-formatter="formatterMarks"
+          :processStyle="{
+            backgroundColor: '#409eff',
+          }"
+          :enable-cross="false"
+          :minRange="30"
+        />
+
+        <!-- <slider
+          v-model="vlookat"
+          range
+          show-stops
+          :marks="marks"
+          :min="-90"
+          :max="90"
+        >
+        </slider> -->
+      </div>
+
+      <div class="col">
+        <span>{{ $i18n.t("mask.apply_to_all_pano") }}</span>
+        <Switcher :value="applyToAll" @change="onSwitcherChange"></Switcher>
+      </div>
     </template>
     </template>
 
 
     <div class="goto-4dkk-tip" v-if="currentScene.type === '4dkk'">
     <div class="goto-4dkk-tip" v-if="currentScene.type === '4dkk'">
       <div class="img-wrap">
       <div class="img-wrap">
-        <img class="" src="@/assets/images/default/goto-4dage.png" alt="" draggable="false">
+        <img
+          class=""
+          src="@/assets/images/default/goto-4dage.png"
+          alt=""
+          draggable="false"
+        />
         <div class="tip-text">
         <div class="tip-text">
-          {{$i18n.t('screen.goto_4dkk_edit_tips')}}
+          {{ $i18n.t("screen.goto_4dkk_edit_tips") }}
         </div>
         </div>
       </div>
       </div>
-      <button class="ui-button submit" @click="onClickGo4dkk">{{$i18n.t('navigation.go_scene_editor')}}</button>
+      <button class="ui-button submit" @click="onClickGo4dkk">
+        {{ $i18n.t("navigation.go_scene_editor") }}
+      </button>
     </div>
     </div>
-
   </div>
   </div>
 </template>
 </template>
 <script>
 <script>
 import { mapGetters } from "vuex";
 import { mapGetters } from "vuex";
+import { Slider } from "element-ui";
+import VueSlider from "vue-slider-component";
+import "vue-slider-component/theme/antd.css";
 
 
+import Switcher from "@/components/shared/Switcher";
 export default {
 export default {
+  components: {
+    // Slider,
+    Switcher,
+    VueSlider,
+  },
   computed: {
   computed: {
     ...mapGetters({
     ...mapGetters({
       currentScene: "scene/currentScene",
       currentScene: "scene/currentScene",
-    })
+    }),
   },
   },
-  data(){
+  data() {
     return {
     return {
-      initImg:''
-    }
+      formatterMarks: (v) => `${v * -1}`,
+      marks: {
+        "-90": this.$i18n.t("modules.model.attr_top"),
+        90: this.$i18n.t("modules.model.attr_bottom"),
+      },
+      initImg: "",
+      vlookat: [-90, 90],
+      applyToAll: false,
+    };
   },
   },
-  methods:{
+  watch: {
+    "$route.name": {
+      handler() {
+        this.handleHiddenAllMasks();
+      },
+      immediate: true,
+    },
+    "currentScene.id": {
+      handler(val, oldVal) {
+        if (val && val !== oldVal) {
+          this.applyToAll = false;
+          this.handleHiddenAllMasks();
+        }
+      },
+      immediate: true,
+    },
+
+    "currentScene.initVisual": {
+      handler(val) {
+        if (val) {
+          const { vlookatmin, vlookatmax } = val;
+          if (vlookatmin && vlookatmax) {
+            this.vlookat = [vlookatmin, vlookatmax];
+          }
+        }
+      },
+      deep: true,
+      immediate: true,
+    },
+    vlookat: {
+      handler: function (val) {
+        this.handleVlootAt(val);
+      },
+    },
+    applyToAll: {
+      handler: function (val) {
+        if (val) {
+          const kr = this.$getKrpano();
+          const vlookat = kr.get("view.vlookat");
+          const hlookat = kr.get("view.hlookat");
+          console.log("当前视野", vlookat, hlookat);
+          this.$confirm({
+            content: this.$i18n.t("mask.is_apply_to_all_pano"),
+            ok: () => {
+              this.$store.dispatch("scene/applyInitVisualToAll", {
+                vlookat,
+                hlookat,
+              });
+              this.updateCurrentScene();
+              this.$msg.success(this.$i18n.t("gather.edit_success"));
+              setTimeout(() => (this.applyToAll = false), 1000);
+            },
+            no: () => {
+              this.applyToAll = false;
+            },
+          });
+        }
+      },
+      immediate: true,
+    },
+  },
+  methods: {
     onClickGo4dkk() {
     onClickGo4dkk() {
-      window.open('/#/scene')
-    }
+      window.open("/#/scene");
+    },
+    onSwitcherChange(value) {
+      this.applyToAll = value;
+    },
+    handleVlootAt(val) {
+      if (val) {
+        const min = Math.min(...val);
+        const max = Math.max(...val);
+        if (this.currentScene.initVisual) {
+          this.currentScene.initVisual.vlookatmin = min;
+          this.currentScene.initVisual.vlookatmax = max;
+          this.handleKrAction(min, max);
+        }
+      }
+    },
+    handleKrAction(min, max) {
+      const kr = this.$getKrpano();
+      if (kr) {
+        kr.set("view.limitview", "lookat");
+        // const mid = min + max;
+        // console.log("mid", min, max);
+        // kr.set("view.vlookat", min);
+        kr.set("view.vlookatmin", String(min));
+        kr.set("view.vlookatmax", String(max));
+      }
+    },
+    handleHiddenAllMasks() {
+      if (this.$getKrpano()) {
+        if (this.$route.name === "screen") {
+          // console.log('handleHiddenAllMasks')
+          setTimeout(() => {
+            this.$getKrpano().set("hotspot[peaklogo].visible", false);
+            this.$getKrpano().set("hotspot[nadirlogo].visible", false);
+          }, 500);
+        }
+      }
+    },
+    updateCurrentScene() {
+      this.$store.dispatch("scene/syncCurrentSceneToStore");
+    },
   },
   },
-  mounted(){
-    this.$bus.on('initView', data => {
-      this.initImg = data
-    })
+  mounted() {
+    this.$bus.on("initView", (data) => {
+      this.initImg = data;
+    });
 
 
     if (!this.initImg) {
     if (!this.initImg) {
-      this.initImg = this.currentScene.icon
+      this.initImg = this.currentScene.icon;
     }
     }
-    
-  }
-}
+  },
+};
 </script>
 </script>
+<style>
+/* .vue-slider-dot-tooltip {
+  display: none !important;
+} */
+</style>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
 .view-setting {
 .view-setting {
@@ -79,6 +255,22 @@ export default {
     height: 132px;
     height: 132px;
     margin-bottom: 16px;
     margin-bottom: 16px;
   }
   }
+  .item {
+    .el-slider {
+      padding: 0 5px;
+      margin-bottom: 10px;
+    }
+    .vue-slider {
+      margin: 15px auto;
+    }
+  }
+  .col {
+    width: 100%;
+    margin: 15px 0;
+    display: inline-flex;
+    justify-content: space-between;
+    align-items: center;
+  }
 
 
   .goto-4dkk-tip {
   .goto-4dkk-tip {
     > .img-wrap {
     > .img-wrap {
@@ -104,4 +296,4 @@ export default {
     }
     }
   }
   }
 }
 }
-</style>
+</style>

+ 1 - 1
packages/qjkankan-editor/src/views/screen/index.vue

@@ -11,7 +11,7 @@ export default {
   name: "editor-initial-setting",
   name: "editor-initial-setting",
   components: {
   components: {
     Setting,
     Setting,
-  }
+  },
 };
 };
 </script>
 </script>
 
 

+ 1 - 1
packages/qjkankan-kankan-view/.env.testprod

@@ -6,7 +6,7 @@ VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk 正式服
 # sdk 正式服
 # VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/www/sdk/
 # VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/www/sdk/
 # sdk 测试服
 # sdk 测试服
-VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4-test/www/sdk/ 
+VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.10.8/
 
 
 
 
 
 

+ 1 - 1
packages/qjkankan-kankan-view/.env.testserve

@@ -5,7 +5,7 @@ VUE_APP_RESOURCE_URL=https://4dkk.4dage.com/
 VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 VUE_APP_CDN_URL=https://4dkk.4dage.com/v4/www/
 # sdk文件地址
 # sdk文件地址
 # VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/www/sdk/
 # VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/www/sdk/
-VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4-test/www/sdk/
+VUE_APP_SDK_DIR=https://4dkk.4dage.com/v4/sdk/4.10.8/
 
 
 
 
 
 

+ 1 - 1
packages/qjkankan-kankan-view/package.json

@@ -5,7 +5,7 @@
   "scripts": {
   "scripts": {
     "serve": "vue-cli-service serve --mode testserve",
     "serve": "vue-cli-service serve --mode testserve",
     "serve-prod": "vue-cli-service serve --mode prod",
     "serve-prod": "vue-cli-service serve --mode prod",
-    "build-test-prod": "vue-cli-service build --mode testprod",
+    "build-testprod": "vue-cli-service build --mode testprod",
     "build": "vue-cli-service build --mode prod",
     "build": "vue-cli-service build --mode prod",
     "build-eurtestprod": "vue-cli-service build --mode eurtestprod",
     "build-eurtestprod": "vue-cli-service build --mode eurtestprod",
     "build-eurprod": "vue-cli-service build --mode eurprod",
     "build-eurprod": "vue-cli-service build --mode eurprod",

+ 1 - 1
packages/qjkankan-kankan-view/src/app.js

@@ -13,7 +13,7 @@ export function createApp(opitons = {}, forceNew = false) {
         target: 'self',
         target: 'self',
         onAction: function (url) {
         onAction: function (url) {
             const newUrl = new URL(url);
             const newUrl = new URL(url);
-            const isV4 = newUrl.pathname.indexOf('spg.html') > -1;
+            const isV4 = newUrl.pathname.indexOf('spg.html') > -1 || newUrl.pathname.indexOf('smg.html') > -1;
             console.log('场景原Link:', url, isV4);
             console.log('场景原Link:', url, isV4);
             let result
             let result
             if (isV4) {
             if (isV4) {

+ 43 - 0
packages/qjkankan-kankan-view/src/components/Camera/index.vue

@@ -0,0 +1,43 @@
+<template>
+  <Teleport to=".scene" v-if="isShowCamIcon">
+    <i
+      @click="closeCamera"
+      class="iconfont ui-kankan-icon icon tip-h-center tip-v-bottom icon-close camera-close"
+    ></i>
+  </Teleport>
+</template>
+<script setup>
+import { ref } from "vue";
+import { useApp, getApp } from "@/app";
+const isShowCamIcon = ref(false);
+
+const closeCamera = () => {
+  getApp().Camera.monitor.edit.cancel();
+  isShowCamIcon.value = false;
+};
+
+useApp().then((app) => {
+  app.Camera.on("SecurityCamera.watch", (data) => {
+    console.log('打开监控')
+    isShowCamIcon.value = true;
+  });
+});
+</script>
+
+<style>
+.camera-close {
+  width: 48px;
+  height: 48px;
+  font-size: 20px;
+  background: rgba(0, 0, 0, 0.32);
+  border-radius: 50%;
+  position: fixed;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  top: 20px;
+  right: 20px;
+  z-index: 4000;
+  cursor: pointer;
+}
+</style>

+ 1 - 1
packages/qjkankan-kankan-view/src/components/Controls/FloorSwitch.vue

@@ -17,7 +17,7 @@ import { useApp } from '@/app'
 const store = useStore()
 const store = useStore()
 const mode = computed(() => store.getters.mode)
 const mode = computed(() => store.getters.mode)
 const flying = computed(() => store.getters['flying'])
 const flying = computed(() => store.getters['flying'])
-const floors = computed(() => store.getters['scene/floors'].reverse())
+const floors = computed(() => store.getters['scene/floors'])
 const floorId = computed(() => store.getters.floorId)
 const floorId = computed(() => store.getters.floorId)
 const showTours = computed(() => store.getters['tour/showTours'])
 const showTours = computed(() => store.getters['tour/showTours'])
 const onGotoFloor = id => {
 const onGotoFloor = id => {

+ 7 - 7
packages/qjkankan-kankan-view/src/components/Controls/tours.mobile.vue

@@ -76,7 +76,7 @@
   </div>
   </div>
 </template>
 </template>
 <script setup>
 <script setup>
-import { computed, inject, onMounted, watch, ref, nextTick } from "vue";
+import { computed, inject, onMounted, watch,watchEffect, ref, nextTick } from "vue";
 import { Scrollbar, Dialog } from "@/global_components";
 import { Scrollbar, Dialog } from "@/global_components";
 import { useApp, getApp } from "@/app";
 import { useApp, getApp } from "@/app";
 import { useStore } from "vuex";
 import { useStore } from "vuex";
@@ -126,12 +126,6 @@ const isPlay = computed(() => {
 const isInit = ref(false);
 const isInit = ref(false);
 const tours = computed(() => {
 const tours = computed(() => {
   let tours = store.getters["tour/tours"];
   let tours = store.getters["tour/tours"];
-  if (tours.length > 0) {
-    if (tourScroll.value && !isInit.value) {
-      isInit.value = true;
-      new Scrollbar(tourScroll.value, { onlyHorizontal: true });
-    }
-  }
   return tours;
   return tours;
 });
 });
 const onModeChange = (name) => {
 const onModeChange = (name) => {
@@ -361,6 +355,12 @@ onMounted(() => {
     let player = document.querySelector('.player[name="main"]');
     let player = document.querySelector('.player[name="main"]');
     player.addEventListener("click", onClickHandler);
     player.addEventListener("click", onClickHandler);
   });
   });
+  watchEffect(() => {
+    if (tours.length > 0 && tourScroll.value && !isInit.value) {
+      isInit.value = true;
+      new Scrollbar(tourScroll.value, { onlyHorizontal: true });
+    }
+  });
 });
 });
 </script>
 </script>
 
 

+ 9 - 6
packages/qjkankan-kankan-view/src/components/Controls/tours.vue

@@ -82,6 +82,7 @@ import { useApp, getApp } from "@/app";
 import { useStore } from "vuex";
 import { useStore } from "vuex";
 import common from "@/utils/common";
 import common from "@/utils/common";
 import { useMusicPlayer } from "@/utils/sound";
 import { useMusicPlayer } from "@/utils/sound";
+import { watchEffect } from "vue";
 const musicPlayer = useMusicPlayer();
 const musicPlayer = useMusicPlayer();
 
 
 const spanlength = ref(5);
 const spanlength = ref(5);
@@ -127,14 +128,9 @@ const isPlay = computed(() => {
 const isInit = ref(false);
 const isInit = ref(false);
 const tours = computed(() => {
 const tours = computed(() => {
   let tours = store.getters["tour/tours"];
   let tours = store.getters["tour/tours"];
-  if (tours.length > 0) {
-    if (tourScroll.value && !isInit.value) {
-      isInit.value = true;
-      new Scrollbar(tourScroll.value, { onlyHorizontal: true });
-    }
-  }
   return tours;
   return tours;
 });
 });
+
 const onModeChange = (name) => {
 const onModeChange = (name) => {
   store.commit("setMode", name);
   store.commit("setMode", name);
 };
 };
@@ -358,6 +354,13 @@ onMounted(() => {
     let player = document.querySelector('.player[name="main"]');
     let player = document.querySelector('.player[name="main"]');
     player.addEventListener("click", onClickHandler);
     player.addEventListener("click", onClickHandler);
   });
   });
+  
+  watchEffect(() => {
+    if (tours.length > 0 && tourScroll.value && !isInit.value) {
+      isInit.value = true;
+      new Scrollbar(tourScroll.value, { onlyHorizontal: true });
+    }
+  });
 });
 });
 </script>
 </script>
 
 

+ 63 - 63
packages/qjkankan-kankan-view/src/locales/en.json

@@ -1,64 +1,64 @@
 {
 {
-  "common": {
-    "none": "None",
-    "confirm": "Confirm",
-    "cancel": "Cancel",
-    "tips": "Tips",
-    "hide": "Hide",
-    "show": "Show",
-    "review": "Preview",
-    "open": "Open",
-    "close": "Close",
-    "pauseTour": "Pause",
-    "playTour": "Play",
-    "passwordTips": "Please enter the password",
-    "passwordError": "Incorrect Password",
-    "tour": "Tour Planner",
-    "support": "4Dage provides technical support",
-    "all": "All",
-    "model": "Dollhouse"
-  },
-  "limit": {
-    "viewInVr": "Please use your phone to view the VR effect on the Web Interface."
-  },
-  "share": {
-    "wechat": "WeChat",
-    "friends": "Moments",
-    "qq": "QQ",
-    "facebook": "Facebook",
-    "whatsApp": "WhatsApp",
-    "copyLink": "Copy Link",
-    "shareLink": "Share Link",
-    "shareLinkTips": "Share with your friends",
-    "vrMode": "VR Mode",
-    "fastCopy": "One-click Copy"
-  },
-  "toast": {
-    "copySuccess": "Successfully copied",
-    "inputPassword": "Please enter the passward",
-    "checkBrowser": "Please update or change your browser and try to open the page again.",
-    "changeBrowser": "It is recommended to use the following Web Browser "
-  },
-  "mode": {
-    "panorama": "Panorama",
-    "floorplan": "Floorplan",
-    "dollhouse": "Dollhouse",
-    "dollhouseModel": "Dollhouse",
-    "vr": "Features",
-    "music": "Music",
-    "fullScene": "Full Screen",
-    "exitFullScene": "Full Screen"
-  },
-  "model": {
-    "enter": "Main Door"
-  },
-  "guide": {
-    "title": "Operation Tips",
-    "pano": "Walk<br />Click to move",
-    "rotate": "Roam<br />Swipe the screen to roam the screen to roam",
-    "zoom": "Zoom<br />Zoom in or out",
-    "set": "Got it",
-    "no_scene": "The scene is not found"
-  },
-  "code": "System is busy, please try again later."
-}
+    "common": {
+        "none": "None",
+        "confirm": "Confirm",
+        "cancel": "Cancel",
+        "tips": "Tips",
+        "hide": "Hide",
+        "show": "Display",
+        "review": "Preview",
+        "open": "Open",
+        "close": "Close",
+        "pauseTour": "Pause",
+        "playTour": "Play",
+        "passwordTips": "Please enter the access code",
+        "passwordError": "Password incorrect",
+        "tour": "Tour Planner",
+        "support": "4Dage Offers Digital Solutions",
+        "all": "All",
+        "model": "3D Model"
+    },
+    "limit": {
+        "viewInVr": "Please use your phone to view the VR effect on the Web Interface."
+    },
+    "share": {
+        "wechat": "WeChat",
+        "friends": "Moments",
+        "qq": "QQ",
+        "facebook": "Facebook",
+        "whatsApp": "WhatsApp",
+        "copyLink": "Copy Link",
+        "shareLink": "Share Link",
+        "shareLinkTips": "Share with your friends",
+        "vrMode": "VR Mode",
+        "fastCopy": "One-click Copy"
+    },
+    "toast": {
+        "inputPassword": "Please enter the password",
+        "checkBrowser": "Please upgrade or change your browser, then reopen the page.",
+        "changeBrowser": "The following web browsers are recommended:",
+        "copySuccess": "Successfully copied"
+    },
+    "mode": {
+        "panorama": "Panorama",
+        "floorplan": "Floorplan",
+        "dollhouse": "Dollhouse",
+        "dollhouseModel": "Dollhouse",
+        "vr": "Features",
+        "music": "Music",
+        "fullScene": "Full Screen",
+        "exitFullScene": "Full Screen"
+    },
+    "code": "System is busy, please try again later.",
+    "model": {
+        "enter": "Main Door"
+    },
+    "guide": {
+        "title": "Operation Tips",
+        "pano": "Walk<br />Click to move",
+        "rotate": "Roam<br />Swipe the screen to roam the screen to roam",
+        "zoom": "Zoom<br />Zoom in or out",
+        "set": "Got it",
+        "no_scene": "The scene is not found"
+    }
+}

+ 63 - 63
packages/qjkankan-kankan-view/src/locales/zh.json

@@ -1,64 +1,64 @@
 {
 {
-  "common": {
-    "none": "无",
-    "confirm": "确定",
-    "cancel": "取消",
-    "tips": "提示",
-    "hide": "隐藏",
-    "show": "显示",
-    "review": "预览",
-    "open": "开",
-    "close": "关",
-    "pauseTour": "暂停导览",
-    "playTour": "播放导览",
-    "passwordTips": "请输入浏览密码",
-    "passwordError": "密码错误",
-    "tour": "导览",
-    "support": "四维时代提供技术支持",
-    "all": "全部",
-    "model": "三维模式"
-  },
-  "limit": {
-    "viewInVr": "请在手机展示页面观看VR效果"
-  },
-  "share": {
-    "wechat": "微信",
-    "friends": "朋友圈",
-    "qq": "QQ",
-    "facebook": "Facebook",
-    "whatsApp": "WhatsApp",
-    "copyLink": "复制链接",
-    "shareLink": "分享链接",
-    "shareLinkTips": "分享链接给好友",
-    "vrMode": "VR模式",
-    "fastCopy": "一键复制"
-  },
-  "toast": {
-    "copySuccess": "场景链接复制成功",
-    "inputPassword": "请输入密码",
-    "checkBrowser": "无法打开页面,请升级或更换浏览器后重新打开",
-    "changeBrowser": "建议使用以下浏览器"
-  },
-  "mode": {
-    "panorama": "漫游",
-    "floorplan": "平面",
-    "dollhouse": "三维",
-    "dollhouseModel": "三维模型",
-    "vr": "VR功能",
-    "music": "音乐",
-    "fullScene": "全屏",
-    "exitFullScene": "退出全屏"
-  },
-  "code": "操作失败,请稍后再试",
-  "model": {
-    "enter": "入户门"
-  },
-  "guide": {
-    "title": "操作提示",
-    "pano": "行走<br />点击任意方向移动",
-    "rotate": "旋转视角<br />左右滑动屏幕,旋转视觉",
-    "zoom": "缩放<br />双指滑动放大或缩小视图",
-    "set": "我知道了",
-    "no_scene":"场景不存在"
-  }
-}
+    "common": {
+        "none": "无",
+        "confirm": "确定",
+        "cancel": "取消",
+        "tips": "提示",
+        "hide": "隐藏",
+        "show": "显示",
+        "review": "预览",
+        "open": "开",
+        "close": "关",
+        "pauseTour": "暂停导览",
+        "playTour": "播放导览",
+        "passwordTips": "请输入浏览密码",
+        "passwordError": "密码错误",
+        "tour": "导览",
+        "support": "四维时代提供技术支持",
+        "all": "全部",
+        "model": "三维模式"
+    },
+    "limit": {
+        "viewInVr": "请在手机展示页面观看VR效果"
+    },
+    "share": {
+        "wechat": "微信",
+        "friends": "朋友圈",
+        "qq": "QQ",
+        "facebook": "Facebook",
+        "whatsApp": "WhatsApp",
+        "copyLink": "复制链接",
+        "shareLink": "分享链接",
+        "shareLinkTips": "分享链接给好友",
+        "vrMode": "VR模式",
+        "fastCopy": "一键复制"
+    },
+    "toast": {
+        "copySuccess": "场景链接复制成功",
+        "inputPassword": "请输入密码",
+        "checkBrowser": "无法打开页面,请升级或更换浏览器后重新打开",
+        "changeBrowser": "建议使用以下浏览器"
+    },
+    "mode": {
+        "panorama": "漫游",
+        "floorplan": "平面",
+        "dollhouse": "三维",
+        "dollhouseModel": "三维模型",
+        "vr": "VR功能",
+        "music": "音乐",
+        "fullScene": "全屏",
+        "exitFullScene": "退出全屏"
+    },
+    "code": "操作失败,请稍后再试",
+    "model": {
+        "enter": "入户门"
+    },
+    "guide": {
+        "title": "操作提示",
+        "pano": "行走<br />点击任意方向移动",
+        "rotate": "旋转视角<br />左右滑动屏幕,旋转视觉",
+        "zoom": "缩放<br />双指滑动放大或缩小视图",
+        "set": "我知道了",
+        "no_scene": "场景不存在"
+    }
+}

+ 11 - 0
packages/qjkankan-kankan-view/src/pages/SMG.vue

@@ -88,6 +88,17 @@ const { t } = useI18n({ useScope: "global" });
 useAppHeight();
 useAppHeight();
 let app = null;
 let app = null;
 
 
+onMounted(async () => {
+  if (!browser.isMobile()) {
+    window.location.href = window.location.href.replace(
+      "smg.html",
+      "spg.html"
+    );
+    console.warn('重跳后::',window.location.href);
+    return;
+  }
+});
+
 const autoPlayMedia = (player) => {
 const autoPlayMedia = (player) => {
   function onclick() {
   function onclick() {
     window.parent.postMessage(
     window.parent.postMessage(

+ 29 - 7
packages/qjkankan-kankan-view/src/pages/SPG.vue

@@ -8,10 +8,12 @@
       <UiTags />
       <UiTags />
       <Information />
       <Information />
       <BottomControl />
       <BottomControl />
+      <Camera />
     </div>
     </div>
     <!-- </template> -->
     <!-- </template> -->
   </div>
   </div>
   <DesignOption />
   <DesignOption />
+
 </template>
 </template>
 
 
 <script setup>
 <script setup>
@@ -22,10 +24,12 @@ import Information from "@/components/Information";
 import BottomControl from "@/components/Controls/BottomControl";
 import BottomControl from "@/components/Controls/BottomControl";
 import Password from "@/components/shared/Password.vue";
 import Password from "@/components/shared/Password.vue";
 import LoadingLogo from "@/components/shared/Loading.vue";
 import LoadingLogo from "@/components/shared/Loading.vue";
+
+import Camera from "@/components/Camera/index.vue";
+
 import { createApp } from "@/app";
 import { createApp } from "@/app";
 import { listenMessage } from "@/utils/messageHandler";
 import { listenMessage } from "@/utils/messageHandler";
 import { Dialog } from "@/global_components";
 import { Dialog } from "@/global_components";
-
 import { ref, onMounted, computed } from "vue";
 import { ref, onMounted, computed } from "vue";
 import { useStore } from "vuex";
 import { useStore } from "vuex";
 import { useApp, getApp } from "@/app";
 import { useApp, getApp } from "@/app";
@@ -33,6 +37,14 @@ import browser from "@/utils/browser";
 import { useI18n, getLocale } from "@/i18n";
 import { useI18n, getLocale } from "@/i18n";
 const { t } = useI18n({ useScope: "global" });
 const { t } = useI18n({ useScope: "global" });
 
 
+onMounted(async () => {
+  if (browser.isMobile()) {
+    window.location.href = window.location.href.replace("spg.html", "smg.html");
+    console.warn("重跳后::", window.location.href);
+    return;
+  }
+});
+
 const autoPlayMedia = (player) => {
 const autoPlayMedia = (player) => {
   function onclick() {
   function onclick() {
     window.parent.postMessage(
     window.parent.postMessage(
@@ -90,23 +102,29 @@ onMounted(() => {
         );
         );
         break;
         break;
       case 5034:
       case 5034:
-        window.location.replace( `/5034.html?m=${browser.getURLParam("m")}&lang=${browser.getURLParam(
+        window.location.replace(
+          `/5034.html?m=${browser.getURLParam("m")}&lang=${browser.getURLParam(
             "lang"
             "lang"
-          )}`);
+          )}`
+        );
         break;
         break;
       case 5009:
       case 5009:
-        window.location.replace( `/5034.html?m=${browser.getURLParam("m")}&lang=${browser.getURLParam(
+        window.location.replace(
+          `/5034.html?m=${browser.getURLParam("m")}&lang=${browser.getURLParam(
             "lang"
             "lang"
-          )}`);
+          )}`
+        );
         break;
         break;
       case 5005:
       case 5005:
         // Dialog.toast({
         // Dialog.toast({
         //     content: t('guide.no_scene'),
         //     content: t('guide.no_scene'),
         //     type: 'error',
         //     type: 'error',
         // })
         // })
-        window.location.replace( `/5033.html?m=${browser.getURLParam("m")}&lang=${browser.getURLParam(
+        window.location.replace(
+          `/5033.html?m=${browser.getURLParam("m")}&lang=${browser.getURLParam(
             "lang"
             "lang"
-          )}`);
+          )}`
+        );
         break;
         break;
     }
     }
   });
   });
@@ -245,6 +263,10 @@ onMounted(() => {
   app.store.on("flooruser", (floor) =>
   app.store.on("flooruser", (floor) =>
     store.commit("scene/loadFloorData", floor)
     store.commit("scene/loadFloorData", floor)
   ); //4.7以后新楼层方式
   ); //4.7以后新楼层方式
+
+  app.Camera.on("SecurityCamera.watch", (data) => {
+    console.error("SecurityCamera.watch", data);
+  });
   app.render();
   app.render();
 });
 });
 </script>
 </script>

+ 1 - 0
packages/qjkankan-kankan-view/src/pages/smg.js

@@ -13,6 +13,7 @@ let App;
 if (
 if (
   browser.detectChrome() ||
   browser.detectChrome() ||
   browser.detectSafari() ||
   browser.detectSafari() ||
+  browser.detectIosOrWebview() ||
   browser.detectFirefox() ||
   browser.detectFirefox() ||
   browser.detectEdge() ||
   browser.detectEdge() ||
   browser.detectWeixin() ||
   browser.detectWeixin() ||

+ 1 - 0
packages/qjkankan-kankan-view/src/pages/spg.js

@@ -14,6 +14,7 @@ let App;
 if (
 if (
   browser.detectChrome() ||
   browser.detectChrome() ||
   browser.detectSafari() ||
   browser.detectSafari() ||
+  browser.detectIosOrWebview() ||
   browser.detectFirefox() ||
   browser.detectFirefox() ||
   browser.detectEdge() ||
   browser.detectEdge() ||
   browser.detectWeixin() ||
   browser.detectWeixin() ||

+ 5 - 0
packages/qjkankan-kankan-view/src/utils/browser.js

@@ -111,6 +111,11 @@ var browser = {
           t = e.indexOf('Safari')
           t = e.indexOf('Safari')
       return t !== -1 && !this.detectOpera() && !this.detectChrome() //xzw add detectOpera
       return t !== -1 && !this.detectOpera() && !this.detectChrome() //xzw add detectOpera
   },
   },
+  detectIosOrWebview: function () {
+    var e = window.navigator.userAgent;
+    var is_uiWebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Version)/i.test(e);
+    return is_uiWebview
+},
   detectFirefox: function () {
   detectFirefox: function () {
       var e = window.navigator.userAgent
       var e = window.navigator.userAgent
       return e.indexOf('Firefox') !== -1
       return e.indexOf('Firefox') !== -1

+ 3 - 1
packages/qjkankan-view/.env.eurprod

@@ -5,4 +5,6 @@ VUE_APP_PROXY_URL_ROOT='https://eur.4dkankan.com'
 VUE_APP_RESOURCE_URL='https://eur.4dkankan.com/panorama/'
 VUE_APP_RESOURCE_URL='https://eur.4dkankan.com/panorama/'
 VUE_APP_PROXY_URL='https://eur.4dkankan.com/qjkankan/'
 VUE_APP_PROXY_URL='https://eur.4dkankan.com/qjkankan/'
 VUE_APP_URL_FILL=/qjkankan
 VUE_APP_URL_FILL=/qjkankan
-VUE_APP_DEBBUG_FLAG=0824-01
+# 接口请求地址
+VUE_APP_APIS_URL=https://eur.4dkankan.com/
+VUE_APP_DEBBUG_FLAG=0126-01

+ 1 - 1
packages/qjkankan-view/.env.eurtestprod

@@ -7,4 +7,4 @@ VUE_APP_PROXY_URL='https://testeur.4dkankan.com/qjkankan/'
 VUE_APP_URL_FILL=/qjkankan
 VUE_APP_URL_FILL=/qjkankan
 # 接口请求地址
 # 接口请求地址
 VUE_APP_APIS_URL=https://testeur.4dkankan.com/
 VUE_APP_APIS_URL=https://testeur.4dkankan.com/
-VUE_APP_DEBBUG_FLAG=0628-01
+VUE_APP_DEBBUG_FLAG=0124-01

+ 1 - 1
packages/qjkankan-view/.env.prod

@@ -8,4 +8,4 @@ VUE_APP_URL_FILL=/qjkankan
 
 
 # 接口请求地址
 # 接口请求地址
 VUE_APP_APIS_URL=https://www.4dkankan.com/
 VUE_APP_APIS_URL=https://www.4dkankan.com/
-VUE_APP_DEBBUG_FLAG=0807-01
+VUE_APP_DEBBUG_FLAG=0122-01

+ 0 - 0
packages/qjkankan-view/.env.testdev


Some files were not shown because too many files changed in this diff