wangfumin 5 napja
commit
47e7374c1f
100 módosított fájl, 20731 hozzáadás és 0 törlés
  1. 8 0
      mobile/.editorconfig
  2. 18 0
      mobile/.env.development
  3. 16 0
      mobile/.env.production
  4. 1 0
      mobile/.gitattributes
  5. 30 0
      mobile/.gitignore
  6. 6 0
      mobile/.prettierrc.json
  7. 9 0
      mobile/.vscode/extensions.json
  8. 41 0
      mobile/README.md
  9. 32 0
      mobile/eslint.config.js
  10. 13 0
      mobile/index.html
  11. 8 0
      mobile/jsconfig.json
  12. 7626 0
      mobile/package-lock.json
  13. 42 0
      mobile/package.json
  14. 4346 0
      mobile/pnpm-lock.yaml
  15. BIN
      mobile/public/favicon.png
  16. 6352 0
      mobile/public/modelLoad/4dage.js
  17. 46 0
      mobile/public/modelLoad/model.html
  18. 326 0
      mobile/public/three/PanoramaControls.js
  19. BIN
      mobile/public/three/assets/0.jpg
  20. BIN
      mobile/public/three/assets/1.jpg
  21. BIN
      mobile/public/three/assets/10.jpg
  22. BIN
      mobile/public/three/assets/11.jpg
  23. BIN
      mobile/public/three/assets/12.jpg
  24. BIN
      mobile/public/three/assets/13.jpg
  25. BIN
      mobile/public/three/assets/14.jpg
  26. BIN
      mobile/public/three/assets/2.jpg
  27. BIN
      mobile/public/three/assets/3.jpg
  28. BIN
      mobile/public/three/assets/4.jpg
  29. BIN
      mobile/public/three/assets/5.jpg
  30. BIN
      mobile/public/three/assets/6.jpg
  31. BIN
      mobile/public/three/assets/7.jpg
  32. BIN
      mobile/public/three/assets/8.jpg
  33. BIN
      mobile/public/three/assets/9.jpg
  34. BIN
      mobile/public/three/background.jpg
  35. 4 0
      mobile/public/three/click.js
  36. 101 0
      mobile/public/three/index.html
  37. 500 0
      mobile/public/three/index.js
  38. 4 0
      mobile/public/three/jquery-2.1.1.min.js
  39. 2 0
      mobile/public/three/three.min.js
  40. 636 0
      mobile/public/three/utils.js
  41. 35 0
      mobile/src/App.vue
  42. 119 0
      mobile/src/api/index.js
  43. 7 0
      mobile/src/assets/element.scss
  44. BIN
      mobile/src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF
  45. BIN
      mobile/src/assets/fonts/SOURCEHANSERIFCN-REGULAR.OTF
  46. BIN
      mobile/src/assets/fonts/SourceHanSansCN-Regular.otf
  47. BIN
      mobile/src/assets/images/bg@2x-min.png
  48. BIN
      mobile/src/assets/images/icon_like_active@2x-min.png
  49. BIN
      mobile/src/assets/images/icon_like_normal@2x-min.png
  50. BIN
      mobile/src/assets/images/icon_portrait@2x-min.png
  51. BIN
      mobile/src/assets/images/logo2@2x-min.png
  52. BIN
      mobile/src/assets/images/logo@2x-min.png
  53. BIN
      mobile/src/assets/images/title_01.png
  54. BIN
      mobile/src/assets/img/btn_active.png
  55. BIN
      mobile/src/assets/img/btn_more.png
  56. BIN
      mobile/src/assets/img/btn_stoke_01.png
  57. BIN
      mobile/src/assets/img/btn_stoke_02.png
  58. BIN
      mobile/src/assets/img/icon_add.png
  59. BIN
      mobile/src/assets/img/icon_back.png
  60. BIN
      mobile/src/assets/img/icon_collect_more.png
  61. BIN
      mobile/src/assets/img/icon_collection_active.png
  62. BIN
      mobile/src/assets/img/icon_collection_normal.png
  63. BIN
      mobile/src/assets/img/icon_down.png
  64. BIN
      mobile/src/assets/img/icon_home_active.png
  65. BIN
      mobile/src/assets/img/icon_home_normal.png
  66. BIN
      mobile/src/assets/img/icon_left.png
  67. BIN
      mobile/src/assets/img/icon_more.png
  68. BIN
      mobile/src/assets/img/icon_notice_active.png
  69. BIN
      mobile/src/assets/img/icon_notice_normal.png
  70. BIN
      mobile/src/assets/img/icon_recruit_active.png
  71. BIN
      mobile/src/assets/img/icon_recruit_normal.png
  72. BIN
      mobile/src/assets/img/icon_right.png
  73. BIN
      mobile/src/assets/img/icon_search.png
  74. BIN
      mobile/src/assets/img/icon_select.png
  75. BIN
      mobile/src/assets/img/icon_zoomin.png
  76. BIN
      mobile/src/assets/img/icon_zoomout.png
  77. BIN
      mobile/src/assets/img/line.png
  78. BIN
      mobile/src/assets/img/title.png
  79. 177 0
      mobile/src/assets/main.css
  80. 44 0
      mobile/src/assets/styles/cut-corner.scss
  81. 31 0
      mobile/src/assets/styles/element-variables.scss
  82. 44 0
      mobile/src/assets/styles/variable.scss
  83. 11 0
      mobile/src/assets/svgs/icon_comment_yellow.svg
  84. 5 0
      mobile/src/assets/svgs/icon_copy.svg
  85. 5 0
      mobile/src/assets/svgs/icon_delete.svg
  86. 5 0
      mobile/src/assets/svgs/icon_eyes.svg
  87. 16 0
      mobile/src/assets/svgs/icon_fullscreen_yellow.svg
  88. 6 0
      mobile/src/assets/svgs/icon_like_yellow.svg
  89. 8 0
      mobile/src/assets/svgs/icon_mark_yellow.svg
  90. 8 0
      mobile/src/assets/svgs/icon_menu_yellow.svg
  91. 5 0
      mobile/src/assets/svgs/icon_search.svg
  92. 8 0
      mobile/src/assets/svgs/icon_search_yellow.svg
  93. 8 0
      mobile/src/assets/svgs/icon_setting_yellow.svg
  94. 10 0
      mobile/src/assets/svgs/icon_share_yellow.svg
  95. 5 0
      mobile/src/assets/svgs/icon_time.svg
  96. 7 0
      mobile/src/assets/svgs/icon_upload_yellow.svg
  97. BIN
      mobile/src/components/Tabbar/images/icon_book_active@2x-min.png
  98. BIN
      mobile/src/components/Tabbar/images/icon_book_normal@2x-min.png
  99. BIN
      mobile/src/components/Tabbar/images/icon_home_active@2x-min.png
  100. 0 0
      mobile/src/components/Tabbar/images/icon_home_normal@2x-min.png

+ 8 - 0
mobile/.editorconfig

@@ -0,0 +1,8 @@
+[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
+charset = utf-8
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+end_of_line = lf
+max_line_length = 100

+ 18 - 0
mobile/.env.development

@@ -0,0 +1,18 @@
+# 是否使用Hash路由
+VITE_USE_HASH = 'true'
+
+# 资源公共路径,需要以 /开头和结尾
+VITE_PUBLIC_PATH = './'
+
+# Axios 基础路径
+VITE_AXIOS_BASE_URL = '/api'  # 用于代理
+# VITE_AXIOS_BASE_URL = 'https://mock.apipark.cn/m1/3776410-0-default'  # apifox云端mock
+# VITE_AXIOS_BASE_URL = 'http://192.168.0.73:8085'
+# 代理配置-target
+# VITE_PROXY_TARGET = 'http://192.168.0.73:8180'
+VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
+
+# 图片基础
+VITE_COS_BASE_URL = 'https://hybgc.4dage.com/ArtCMS/'
+# 模型基础
+VITE_MODEL_URL = 'https://hybgc.4dage.com/'

+ 16 - 0
mobile/.env.production

@@ -0,0 +1,16 @@
+# 是否使用Hash路由
+VITE_USE_HASH = 'true'
+
+# 资源公共路径,需要以 /开头和结尾
+VITE_PUBLIC_PATH = './'
+
+VITE_AXIOS_BASE_URL = '/api'  # 用于代理
+
+# 代理配置-target
+# VITE_PROXY_TARGET = 'http://localhost:8085'
+VITE_PROXY_TARGET = 'https://sit-huyaobangjng.4dage.com'
+
+# 图片基础
+VITE_COS_BASE_URL = 'https://hybgc.4dage.com/ArtCMS/'
+# 模型基础
+VITE_MODEL_URL = 'https://hybgc.4dage.com/'

+ 1 - 0
mobile/.gitattributes

@@ -0,0 +1 @@
+* text=auto eol=lf

+ 30 - 0
mobile/.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 6 - 0
mobile/.prettierrc.json

@@ -0,0 +1,6 @@
+{
+  "$schema": "https://json.schemastore.org/prettierrc",
+  "semi": false,
+  "singleQuote": true,
+  "printWidth": 100
+}

+ 9 - 0
mobile/.vscode/extensions.json

@@ -0,0 +1,9 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "vitest.explorer",
+    "dbaeumer.vscode-eslint",
+    "EditorConfig.EditorConfig",
+    "esbenp.prettier-vscode"
+  ]
+}

+ 41 - 0
mobile/README.md

@@ -0,0 +1,41 @@
+# hhbang-book
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+pnpm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+pnpm dev
+```
+
+### Compile and Minify for Production
+
+```sh
+pnpm build
+```
+
+### Run Unit Tests with [Vitest](https://vitest.dev/)
+
+```sh
+pnpm test:unit
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+pnpm lint
+```

+ 32 - 0
mobile/eslint.config.js

@@ -0,0 +1,32 @@
+import { defineConfig, globalIgnores } from 'eslint/config'
+import globals from 'globals'
+import js from '@eslint/js'
+import pluginVue from 'eslint-plugin-vue'
+import pluginVitest from '@vitest/eslint-plugin'
+import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
+
+export default defineConfig([
+  {
+    name: 'app/files-to-lint',
+    files: ['**/*.{js,mjs,jsx,vue}'],
+  },
+
+  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
+
+  {
+    languageOptions: {
+      globals: {
+        ...globals.browser,
+      },
+    },
+  },
+
+  js.configs.recommended,
+  ...pluginVue.configs['flat/essential'],
+  
+  {
+    ...pluginVitest.configs.recommended,
+    files: ['src/**/__tests__/*'],
+  },
+  skipFormatting,
+])

+ 13 - 0
mobile/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.png">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>鉴赏系统</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 8 - 0
mobile/jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 7626 - 0
mobile/package-lock.json


+ 42 - 0
mobile/package.json

@@ -0,0 +1,42 @@
+{
+  "name": "hhbang-book",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "engines": {
+    "node": "^20.19.0 || >=22.12.0"
+  },
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview",
+    "test:unit": "vitest",
+    "lint": "eslint . --fix",
+    "format": "prettier --write src/"
+  },
+  "dependencies": {
+    "axios": "^1.9.0",
+    "element-plus": "^2.9.11",
+    "lodash": "^4.17.21",
+    "sass-embedded": "^1.89.2",
+    "vue": "^3.5.18",
+    "vue-router": "^4.5.1",
+    "vuex": "^4.1.0",
+    "v-distpicker": "^2.1.0"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.31.0",
+    "@vitejs/plugin-vue": "^6.0.1",
+    "@vitest/eslint-plugin": "^1.3.4",
+    "@vue/eslint-config-prettier": "^10.2.0",
+    "@vue/test-utils": "^2.4.6",
+    "eslint": "^9.31.0",
+    "eslint-plugin-vue": "~10.3.0",
+    "globals": "^16.3.0",
+    "jsdom": "^26.1.0",
+    "prettier": "3.6.2",
+    "vite": "^7.0.6",
+    "vite-plugin-vue-devtools": "^8.0.0",
+    "vitest": "^3.2.4"
+  }
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4346 - 0
mobile/pnpm-lock.yaml


BIN
mobile/public/favicon.png


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 6352 - 0
mobile/public/modelLoad/4dage.js


+ 46 - 0
mobile/public/modelLoad/model.html

@@ -0,0 +1,46 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <script src="./4dage.js"></script>
+    <title>Document</title>
+    <style>
+      html {
+        overflow: hidden;
+      }
+    </style>
+  </head>
+
+  <body>
+    <div id="ui"></div>
+    <script>
+      function getQueryVariable(variable) {
+        var query = window.location.search.substring(1)
+        var vars = query.split('&')
+        for (var i = 0; i < vars.length; i++) {
+          var pair = vars[i].split('=')
+          if (pair[0] == variable) {
+            return decodeURIComponent(pair[1])
+          }
+        }
+        return null
+      }
+
+      let m = getQueryVariable('m')
+
+      // window.autoRotate = true; // 是否自动旋转
+
+      // fdage.embed( number, {
+      fdage.embed(`${m}`, {
+        transparentBackground: true,
+        width: 800,
+        height: 600,
+        autoStart: true,
+        fullFrame: true,
+        pagePreset: false,
+      })
+    </script>
+  </body>
+</html>

+ 326 - 0
mobile/public/three/PanoramaControls.js

@@ -0,0 +1,326 @@
+
+function PanoramaControls(camera, domElement) {
+
+  
+    // fyz 相机放大缩小
+    this.activationThreshold = 1.1;
+    this.scrollZoomSpeed = 0.001;
+    this.scrollZoomSta = true;
+    this.zoomMin = 0.7;
+    this.zoomMax = 1.5;
+    this.baseFov = 70;
+ 
+    this.camera = camera;
+    this.camera.fov = this.baseFov;
+     
+    this.domElement = domElement; 
+    this.camera.controls = this; 
+    this.enabled = true;
+
+    this.target = new THREE.Vector3(0, 0, 0);
+    this.lookVector = new THREE.Vector3;
+    this.aimFrom = this.camera.position;
+
+
+    this.lat = 0;
+    this.latMin = -60//-40;
+    this.latMax = 60//40;
+    this.lon = 0;
+    this.phi = 0;
+    this.theta = 0;
+    this.lookSpeed = 0.05;
+    this.rotationAcc = new THREE.Vector2;
+    this.rotationSpeed = new THREE.Vector2;
+    this.rotationHistory = [];
+    this.rotationDifference = new THREE.Vector2;
+
+
+    this.pointerDragOn = !1;
+    this.pointer = new THREE.Vector3(0, 0, -1);
+    this.pointerDragStart = new THREE.Vector3(0, 0, -1); 
+    this._wheel = 0;
+    this.zoomLevel = 1; 
+    this.translationWorldDelta = new THREE.Vector3
+    this.bindEvents()
+}
+
+PanoramaControls.prototype.bindEvents = function() {
+
+    window.addEventListener("mousemove", this.onMouseMove.bind(this));
+    this.domElement.addEventListener("mousedown", this.onMouseDown.bind(this));
+    window.addEventListener("mouseup", this.onMouseUp.bind(this));
+    this.domElement.addEventListener("mouseover", (event) => this.pointerDragOn && 0 === event.which && this.onMouseUp(event));
+
+    this.domElement.addEventListener("touchstart", this.onTouchStart.bind(this));
+    this.domElement.addEventListener("touchmove", this.onTouchMove.bind(this));
+    this.domElement.addEventListener("touchend", this.onTouchEnd.bind(this));
+
+    this.domElement.addEventListener("wheel", this.onMouseWheel.bind(this));   // fyz wheel事件代替mousewheel事件
+    this.domElement.addEventListener("DOMMouseScroll", this.onMouseWheel.bind(this));
+    this.domElement.addEventListener("contextmenu", (event) => event.preventDefault());
+
+    //document.addEventListener("keydown", this.onKeyDown.bind(this));
+    //document.addEventListener("keyup", this.onKeyUp.bind(this));
+
+}
+PanoramaControls.prototype.lookAt = function(point) {
+ 
+
+    var directionNegative = this.camera.position.clone().sub(point),
+        theta = Math.atan(directionNegative.z / directionNegative.x);
+
+    theta += directionNegative.x < 0 ? Math.PI : 0;
+    theta += directionNegative.x > 0 && directionNegative.z < 0 ? 2 * Math.PI : 0;
+    this.lon = THREE.Math.radToDeg(theta) + 180;
+
+    let projectorR = Math.sqrt(directionNegative.x * directionNegative.x + directionNegative.z * directionNegative.z),
+        phi = Math.atan(directionNegative.y / projectorR);
+
+    this.lat = -THREE.Math.radToDeg(phi)
+}
+PanoramaControls.prototype.startRotationFrom = function(screenX, screenY) {
+
+    this.updatePointer(screenX, screenY);
+    this.pointerDragOn = true;
+    this.pointerDragStart.copy(this.pointer);
+    //TODO
+    //this.pointerDragStartIntersect = this.player.getMouseIntersect(this.pointer.clone(), [this.scene.skybox]).point;
+    this.rotationHistory = [];
+    this.rotationSpeed.set(0, 0);
+}
+PanoramaControls.prototype.onTouchStart = function(event) {
+
+    if (this.enabled) {
+
+        event.preventDefault();
+        event.stopPropagation();
+        this.startRotationFrom(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
+    }
+
+}
+PanoramaControls.prototype.onMouseDown = function(event) {
+
+    if (this.enabled) {
+
+        event.preventDefault();
+        event.stopPropagation()
+
+        switch (event.button) {
+
+            case 0:
+                this.startRotationFrom(event.clientX, event.clientY);
+                break;
+            case 2:
+
+        }
+    }
+}
+
+PanoramaControls.prototype.updateRotation = function( ) {
+
+
+    if (this.enabled && this.pointerDragOn) {
+
+        var pointerDragStart3D = new THREE.Vector3(this.pointerDragStart.x, this.pointerDragStart.y, 1).unproject(this.camera);
+        var pointer3D = new THREE.Vector3(this.pointer.x, this.pointer.y, 1).unproject(this.camera);
+
+            //两交互点分别到原点的长度
+        var pointerDragStart3DLength = Math.sqrt(pointerDragStart3D.x * pointerDragStart3D.x + pointerDragStart3D.z * pointerDragStart3D.z);
+        var pointer3DLength = Math.sqrt(pointer3D.x * pointer3D.x + pointer3D.z * pointer3D.z);
+
+            //通过Math.atan2计算在XY面上与X轴的夹角弧度。
+            //注:因为 z = -1,所以两者到原点的长度近似为x分量(数值的大小也不需要绝对对应)
+        var anglePointerDragStart3DToX = Math.atan2(pointerDragStart3D.y, pointerDragStart3DLength);       //近似为 anglePointerDragStart3DToX = Math.atan2( pointerDragStart3D.y, pointerDragStart3D.x ) 
+        var anglePointer3DToX = Math.atan2(pointer3D.y, pointer3DLength);                                 //近似为 anglePointer3DToX = Math.atan2( pointer3D.y, pointer3D.x )
+
+        //算出两者角度差,作为竖直方向角度差值(rotationDifference.y)
+        this.rotationDifference.y = THREE.Math.radToDeg(anglePointerDragStart3DToX - anglePointer3DToX);
+
+
+        //y分量清零,原向量等价于在XZ轴上的投影向量
+        pointerDragStart3D.y = 0;
+        pointer3D.y = 0;
+
+        //归一化(/length),求两者夹角作为
+        //判断方向,最后记为水平方向角度差值(rotationDifference.x)
+        var anglePointerDragStart3DToPointer3D = Math.acos(pointerDragStart3D.dot(pointer3D) / pointerDragStart3D.length() / pointer3D.length());
+
+        if (!isNaN(anglePointerDragStart3DToPointer3D)) {
+            this.rotationDifference.x = THREE.Math.radToDeg(anglePointerDragStart3DToPointer3D);
+            if (this.pointerDragStart.x < this.pointer.x) {
+                this.rotationDifference.x *= -1;
+            }
+        }
+
+        //更新pointerDragStart记录当前帧坐标,用于下一帧求帧差值
+        this.pointerDragStart.copy(this.pointer);
+    }
+}
+PanoramaControls.prototype.onMouseMove = function(event) {
+     
+    this.updatePointer(event.clientX, event.clientY);
+
+}
+PanoramaControls.prototype.onTouchMove = function(event) {
+
+    this.updatePointer(event.changedTouches[0].clientX, event.changedTouches[0].clientY)
+
+}
+
+PanoramaControls.prototype.updatePointer = function(screenX, screenY) {
+    this.pointer.x = screenX / this.domElement.clientWidth * 2 - 1;   // 屏幕坐标换算相对于canvas的父级
+    this.pointer.y = 2 * -(screenY / this.domElement.clientHeight) + 1;
+
+}
+PanoramaControls.prototype.endRotation = function() {
+
+
+    this.pointerDragOn = false;
+    try{
+        var rotationHistoryAverage = averageVectors(this.rotationHistory);
+    }catch(e){
+        console.error(e)
+    } 
+    
+    this.rotationSpeed.set(rotationHistoryAverage.x * 30, rotationHistoryAverage.y * 30);
+
+}
+PanoramaControls.prototype.onTouchEnd = function(event) {
+
+    if (this.enabled) {
+
+        event.preventDefault();
+        event.stopPropagation();
+        this.endRotation()
+    }
+}
+PanoramaControls.prototype.onMouseUp = function(event) {
+
+
+    if (this.enabled) {
+
+        event.preventDefault();
+        event.stopPropagation();
+        this.endRotation()
+    }
+}
+PanoramaControls.prototype.update = function(deltaTime) {
+
+    if (this.enabled) {
+
+        this.updateRotation();
+
+        for (this.rotationHistory.push(this.rotationDifference.clone()); this.rotationHistory.length > 5;) {
+
+            this.rotationHistory.shift();
+        }
+
+        this.lon += this.rotationDifference.x;
+        this.lat += this.rotationDifference.y;
+        this.rotationDifference.set(0, 0);
+        this.rotationSpeed.x = this.rotationSpeed.x * (1 - 0.05) + this.rotationAcc.x * 4.5;
+        this.rotationSpeed.y = this.rotationSpeed.y * (1 - 0.05) + this.rotationAcc.y * 4.5;
+
+        this.lon += this.rotationSpeed.x * deltaTime;
+        this.lat += this.rotationSpeed.y * deltaTime;
+
+      
+
+        this.lat = Math.max(this.latMin, Math.min(this.latMax, this.lat));
+        this.phi = THREE.Math.degToRad(90 - this.lat);
+        this.theta = THREE.Math.degToRad(this.lon);
+
+        this.lookVector.x = Math.sin(this.phi) * Math.cos(this.theta);
+        this.lookVector.y = Math.cos(this.phi);
+        this.lookVector.z = Math.sin(this.phi) * Math.sin(this.theta);
+
+
+        this.camera.position.add(this.translationWorldDelta) 
+        this.translationWorldDelta.multiplyScalar(0.9); 
+    
+        this.target.copy(this.lookVector).add(this.aimFrom);
+        this.camera.lookAt(this.target)
+        
+        
+        
+    }
+}
+PanoramaControls.prototype.onMouseWheel = function(event) {
+
+    /* if (this.enabled) {
+
+        // let z = void 0 !== event['wheelDelta'] ? event['wheelDelta'] : 0 !== event.detail && -event.detail;
+        // this.flyDirection(new THREE.Vector3(0, 0, -z).normalize());
+        this._wheel = Math.floor(event['wheelDeltaY'] / 120);
+        this._wheel = Math.abs(this._wheel) > 0.1 ? Math.sign(this._wheel) : 0;
+        if (this._wheel !== 0 && this.scrollZoomSta) {
+            this._wheel > 0 ? this._wheel = 1 + this.scrollZoomSpeed : this._wheel = 1 - this.scrollZoomSpeed;
+            let curZoomLevel = this._wheel * this.zoomLevel;
+            this.zoomTo(curZoomLevel);
+        }
+
+    } */
+    
+    let delta
+    if (event.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9
+        delta = event.wheelDelta;
+    } else if (event.detail !== undefined) { // Firefox
+        delta = -event.detail;
+    }
+        
+    
+    
+    if(delta != void 0){//滚轮缩放 
+        if(delta == 0)return //mac
+        let direction = new THREE.Vector3(0,0,-1).applyQuaternion(this.camera.quaternion)
+        let moveSpeed = 0.03
+        if(delta < 0) moveSpeed *=-1
+        this.translationWorldDelta.add(direction.multiplyScalar(moveSpeed))
+    }
+    
+}
+PanoramaControls.prototype.zoomTo = function(curZoomLevel) {
+    curZoomLevel < this.zoomMin && (curZoomLevel = this.zoomMin);
+    curZoomLevel > this.zoomMax && (curZoomLevel = this.zoomMax);
+    this.zoomLevel = curZoomLevel;
+    this.camera.fov = this.baseFov*(1/this.zoomLevel);
+    this.camera.updateProjectionMatrix();
+} 
+
+/*   
+reset() {
+
+    this.stop()
+}
+
+stop() {
+
+    this.rotationAcc.set(0, 0);
+    this.rotationSpeed.set(0, 0);
+}
+*/
+
+
+
+
+//-------------copyFromPlayer
+PanoramaControls.prototype.handleControlScroll = function(e) {
+     e > 0 ? e = 1 + this.scrollZoomSpeed : e < 0 && (e = 1 - this.scrollZoomSpeed);
+    0 !== e && this.zoomBy(e)
+}
+PanoramaControls.prototype.zoomBy = function(e) {
+     this.zoomTo(this.zoomLevel * e);
+}
+
+
+
+function averageVectors(e, t) {
+    var i = new THREE.Vector3();
+    if (0 === e.length) return i;
+    for (var r = 0, o = 0; o < e.length; o++) {
+        var a = t ? e[o][t] : e[o];
+        i.add(a), r++;
+    }
+    return i.divideScalar(r);
+} 
+ 

BIN
mobile/public/three/assets/0.jpg


BIN
mobile/public/three/assets/1.jpg


BIN
mobile/public/three/assets/10.jpg


BIN
mobile/public/three/assets/11.jpg


BIN
mobile/public/three/assets/12.jpg


BIN
mobile/public/three/assets/13.jpg


BIN
mobile/public/three/assets/14.jpg


BIN
mobile/public/three/assets/2.jpg


BIN
mobile/public/three/assets/3.jpg


BIN
mobile/public/three/assets/4.jpg


BIN
mobile/public/three/assets/5.jpg


BIN
mobile/public/three/assets/6.jpg


BIN
mobile/public/three/assets/7.jpg


BIN
mobile/public/three/assets/8.jpg


BIN
mobile/public/three/assets/9.jpg


BIN
mobile/public/three/background.jpg


+ 4 - 0
mobile/public/three/click.js

@@ -0,0 +1,4 @@
+// 继续动画 - 给 父页面调用
+window.stareMove = (val) => {
+  viewer.setAutoMove(val)
+}

+ 101 - 0
mobile/public/three/index.html

@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="zh">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <title>刘少奇同志纪念馆</title>
+    <style>
+      #player,
+      body,
+      canvas {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        overflow: hidden;
+        margin: 0;
+        padding: 0;
+      }
+
+      /*canvas{
+          background-image: url(background.jpg); 
+          background-repeat: no-repeat;
+          background-position: center;
+          background-size: cover;
+      }*/
+      #consoleLog {
+        width: 120px;
+        height: 153px;
+        position: absolute;
+        left: 0px;
+        bottom: 160px;
+        z-index: 999999;
+        color: black;
+        opacity: 0.9;
+        font-size: 12px;
+      }
+    </style>
+  </head>
+
+  <body>
+    <div id="player">
+      <canvas></canvas>
+    </div>
+
+    <script type="text/javascript" src="./jquery-2.1.1.min.js"></script>
+    <script type="text/javascript" src="./three.min.js"></script>
+    <script>
+      let vfov = 60; //垂直视角范围度数
+      window.setting = {
+        vfov,
+        cards: {
+          far: 15,
+          beginFadeNear: 7,
+          near: 1,
+          fadeInDur: 3000,
+          highest: Math.tan(THREE.Math.degToRad(vfov / 2)), //当card在1米处时最高可以多少才能在视线内
+        },
+      };
+
+      function initViewer(cardNames) {
+        window.cardNames = cardNames;
+        var startTime = new Date().getTime();
+        window.viewer = new Viewer(0, $("#player")[0])
+
+        // 点击
+        viewer.addEventListener('clickObject', e => {
+          window.top.clickObject(e.imgName);
+          // 暂停动画
+          viewer.setAutoMove(false)
+        })
+
+        let flag = false
+
+        // 鼠标移入
+        viewer.addEventListener('hoverObject', e => {
+          // console.log('鼠标移入',e);
+          flag = true
+          window.top.hoverObject(e);
+        })
+
+        // 鼠标移出
+        viewer.addEventListener('mouseoutObject', e => {
+          // console.log('鼠标移出',e);
+          flag = false
+          window.top.mouseoutObject(e);
+        })
+
+        document.querySelector('#player').onmousemove = (event) => {
+          if (!flag) return
+          let e = event || window.event;
+          window.top.mouseLoc(e.clientX, e.clientY);
+        }
+      }
+    </script>
+
+    <script type="text/javascript" src="./utils.js"></script>
+    <script type="text/javascript" src="./PanoramaControls.js"></script>
+    <script type="text/javascript" src="./index.js"></script>
+    <script src="./click.js"></script>
+  </body>
+</html>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 500 - 0
mobile/public/three/index.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4 - 0
mobile/public/three/jquery-2.1.1.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 0
mobile/public/three/three.min.js


+ 636 - 0
mobile/public/three/utils.js

@@ -0,0 +1,636 @@
+var lerp = {
+	vector: function(e, t, f) {//xzw change, add f
+		var i = e.clone();
+		return t = t.clone(),
+		function(n) {
+			e.set(i.x * (1 - n) + t.x * n, i.y * (1 - n) + t.y * n, i.z * (1 - n) + t.z * n)
+			f && f(e,n);
+		}
+	},
+    quaternion: function(e, t, f) {//xzw change, add f
+        var i = e.clone();
+        return function(n) {
+            e.copy(i).slerp(t, n);
+			f && f(e,n);
+        }
+    },
+    property: function(e, t, i, n) {
+        var r = e[t];
+        return function(o) {
+            e[t] = r * (1 - o) + i * o,
+            n && n(e[t])
+        }
+    },
+    uniform: function(e, t, i) {
+        var n = e.material.uniforms[t].value;
+        return function(r) {
+            try{
+                e.material.uniforms[t] && (e.material.uniforms[t].value = n * (1 - r) + i * r)
+            }catch(e){
+                console.log(1)
+            }
+            
+        }
+    },
+    matrix4: function(e, t) {
+        var i = e.clone();
+        return function(n) {
+            for (var r = e.elements, o = i.elements, a = t.elements, s = 0; s < 16; s++)
+                r[s] = o[s] * (1 - n) + a[s] * n
+        }
+    },
+    allUniforms: function(e, t, i) {
+        var n = e.map(function(e) {
+            return this.uniform(e, t, i)
+        }
+        .bind(this));
+        return function(e) {
+            n.forEach(function(t) {
+                t(e)
+            })
+        }
+    }
+};
+
+
+//////
+ 
+var easing = {};
+//渐变曲线函数,反应加速度的变化
+easing.linearTween = function(e, t, i, n) {
+    return i * e / n + t
+}
+,
+easing.easeInQuad = function(e, t, i, n) {
+    return e /= n,
+    i * e * e + t
+}
+,
+easing.easeOutQuad = function(e, t, i, n) {
+    return e /= n,
+    -i * e * (e - 2) + t
+}
+,
+easing.easeInOutQuad = function(e, t, i, n) {
+    return e /= n / 2,
+    e < 1 ? i / 2 * e * e + t : (e--,
+    -i / 2 * (e * (e - 2) - 1) + t)
+}
+,
+easing.easeInCubic = function(e, t, i, n) {
+    return e /= n,
+    i * e * e * e + t
+}
+,
+easing.easeOutCubic = function(e, t, i, n) {
+    return e /= n,
+    e--,
+    i * (e * e * e + 1) + t
+}
+,
+easing.easeInOutCubic = function(e, t, i, n) {
+    return e /= n / 2,
+    e < 1 ? i / 2 * e * e * e + t : (e -= 2,
+    i / 2 * (e * e * e + 2) + t)
+}
+,
+easing.easeInQuart = function(e, t, i, n) {
+    return e /= n,
+    i * e * e * e * e + t
+}
+,
+easing.easeOutQuart = function(e, t, i, n) {
+    return e /= n,
+    e--,
+    -i * (e * e * e * e - 1) + t
+}
+,
+easing.easeInOutQuart = function(e, t, i, n) {
+    return e /= n / 2,
+    e < 1 ? i / 2 * e * e * e * e + t : (e -= 2,
+    -i / 2 * (e * e * e * e - 2) + t)
+}
+,
+easing.easeInQuint = function(e, t, i, n) {
+    return e /= n,
+    i * e * e * e * e * e + t
+}
+,
+easing.easeOutQuint = function(e, t, i, n) {
+    return e /= n,
+    e--,
+    i * (e * e * e * e * e + 1) + t
+}
+,
+easing.easeInOutQuint = function(e, t, i, n) {
+    return e /= n / 2,
+    e < 1 ? i / 2 * e * e * e * e * e + t : (e -= 2,
+    i / 2 * (e * e * e * e * e + 2) + t)
+}
+,
+easing.easeInSine = function(e, t, i, n) {
+    return -i * Math.cos(e / n * (Math.PI / 2)) + i + t
+}
+,
+easing.easeOutSine = function(e, t, i, n) {
+    return i * Math.sin(e / n * (Math.PI / 2)) + t
+}
+,
+easing.easeInOutSine = function(e, t, i, n) {
+    return -i / 2 * (Math.cos(Math.PI * e / n) - 1) + t
+}
+,
+easing.easeInExpo = function(e, t, i, n) {
+    return i * Math.pow(2, 10 * (e / n - 1)) + t
+}
+,
+easing.easeOutExpo = function(e, t, i, n) {
+    return i * (-Math.pow(2, -10 * e / n) + 1) + t
+}
+,
+easing.easeInOutExpo = function(e, t, i, n) {
+    return e /= n / 2,
+    e < 1 ? i / 2 * Math.pow(2, 10 * (e - 1)) + t : (e--,
+    i / 2 * (-Math.pow(2, -10 * e) + 2) + t)
+}
+,
+easing.easeInCirc = function(e, t, i, n) {
+    return e /= n,
+    -i * (Math.sqrt(1 - e * e) - 1) + t
+}
+,
+easing.easeOutCirc = function(e, t, i, n) {
+    return e /= n,
+    e--,
+    i * Math.sqrt(1 - e * e) + t
+}
+,
+easing.easeInOutCirc = function(e, t, i, n) {
+    return e /= n / 2,
+    e < 1 ? -i / 2 * (Math.sqrt(1 - e * e) - 1) + t : (e -= 2,
+    i / 2 * (Math.sqrt(1 - e * e) + 1) + t)
+}
+,
+easing.easeInElastic = function(e, t, i, n) {
+    var r = 1.70158
+      , o = 0
+      , a = i;
+    return 0 === e ? t : 1 === (e /= n) ? t + i : (o || (o = .3 * n),
+    a < Math.abs(i) ? (a = i,
+    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(i / a),
+    -(a * Math.pow(2, 10 * (e -= 1)) * Math.sin((e * n - r) * (2 * Math.PI) / o)) + t)
+}
+,
+easing.easeOutElastic = function(e, t, i, n) {
+    var r = 1.70158
+      , o = 0
+      , a = i;
+    return 0 === e ? t : 1 === (e /= n) ? t + i : (o || (o = .3 * n),
+    a < Math.abs(i) ? (a = i,
+    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(i / a),
+    a * Math.pow(2, -10 * e) * Math.sin((e * n - r) * (2 * Math.PI) / o) + i + t)
+}
+,
+easing.easeInOutElastic = function(e, t, i, n) {
+    var r = 1.70158
+      , o = 0
+      , a = i;
+    return 0 === e ? t : 2 === (e /= n / 2) ? t + i : (o || (o = n * (.3 * 1.5)),
+    a < Math.abs(i) ? (a = i,
+    r = o / 4) : r = o / (2 * Math.PI) * Math.asin(i / a),
+    e < 1 ? -.5 * (a * Math.pow(2, 10 * (e -= 1)) * Math.sin((e * n - r) * (2 * Math.PI) / o)) + t : a * Math.pow(2, -10 * (e -= 1)) * Math.sin((e * n - r) * (2 * Math.PI) / o) * .5 + i + t)
+}
+,
+easing.easeInBack = function(e, t, i, n, r) {
+    return void 0 === r && (r = 1.70158),
+    i * (e /= n) * e * ((r + 1) * e - r) + t
+}
+,
+easing.easeOutBack = function(e, t, i, n, r) {
+    return void 0 === r && (r = 1.70158),
+    i * ((e = e / n - 1) * e * ((r + 1) * e + r) + 1) + t
+}
+,
+easing.easeInOutBack = function(e, t, i, n, r) {
+    return void 0 === r && (r = 1.70158),
+    (e /= n / 2) < 1 ? i / 2 * (e * e * (((r *= 1.525) + 1) * e - r)) + t : i / 2 * ((e -= 2) * e * (((r *= 1.525) + 1) * e + r) + 2) + t
+}
+,
+easing.easeOutBounce = function(e, t, i, n) {
+    return (e /= n) < 1 / 2.75 ? i * (7.5625 * e * e) + t : e < 2 / 2.75 ? i * (7.5625 * (e -= 1.5 / 2.75) * e + .75) + t : e < 2.5 / 2.75 ? i * (7.5625 * (e -= 2.25 / 2.75) * e + .9375) + t : i * (7.5625 * (e -= 2.625 / 2.75) * e + .984375) + t
+}
+,
+easing.easeInBounce = function(e, t, i, r) {
+    return i - easing.easeOutBounce(r - e, 0, i, r) + t
+}
+,
+easing.easeInOutBounce = function(e, t, i, r) {
+    return e < r / 2 ? .5 * easing.easeInBounce(2 * e, 0, i, r) + t : .5 * easing.easeOutBounce(x, 2 * e - r, 0, i, r) + .5 * i + t
+}
+
+ 
+ 
+ 
+ 
+/* 
+    渐变
+    
+
+ */
+
+var transitions = {
+    globalDone: null,
+    funcs: [],
+    counter: 0,
+    uniqueID: 0,
+    start: function(e, t, i, r, o, a, s, cancelFun) {
+        r = r || 0 
+        let info = {
+            func: e,
+            current: -r * Math.abs(t),                      //当前时间
+            duration: (1 - Math.max(r, 0)) * Math.abs(t),   //总时长
+            done: i,
+            easing: o || easing.linearTween,                //渐变曲线
+            cycling: t < 0,
+            running: !0,
+            debug: r < 0,
+            name: a || "T" + this.counter,
+            id: void 0 === s ? this.counter : s,
+            paused: !1,
+			cancelFun : cancelFun,   //取消时执行的函数
+        }  
+        this.funcs.push(info),
+        e(0, 16),
+        this.counter += 1 
+        return info
+    },
+    trigger: function(e) {
+        var t = void 0 === e.delayRatio ? 0 : e.delayRatio
+            , i = e.func || function() {}
+            , r = void 0 === e.duration ? 0 : e.duration;
+        void 0 !== e.cycling && e.cycling && (r = -Math.abs(r));
+        var o = e.done || null
+            , a = e.easing || easing.linearTween
+            , s = e.name || "R" + this.counter
+            , l = void 0 === e.id ? this.counter : e.id;
+        return this.start(i, r, o, t, a, s, l)
+    },
+    setTimeout: function(e, t, i) {
+        var n = void 0 === i ? this.counter : i;
+        return this.trigger({
+            done: e,
+            duration: void 0 === t ? 0 : t,
+            name: "O" + this.counter,
+            id: n
+        })
+    },
+    pause: function() {
+        this.paused = !0
+    },
+    resume: function() {
+        this.paused = !1
+    },
+    update: function(e) {
+        this.funcs.forEach(function(t) {
+            if (!(t.paused || (t.current += 1e3 * e, t.current < 0))){ 
+                if (t.current >= t.duration && !t.cycling) {
+                    var i = t.easing(1, 0, 1, 1);
+                    t.func(i, 1e3 * e),
+                    t.done && t.done(),
+                    t.running = !1 
+                } else {
+                    var n = t.easing(t.current % t.duration / t.duration, 0, 1, 1)
+                        , r = t.func(n, 1e3 * e) || !1;
+                    r && (t.done && t.done(),
+                    t.running = !1)
+                }
+            }
+        });
+        var t = this.funcs.length;
+        this.funcs = this.funcs.filter(function(e) {
+            return e.running
+        }); 
+        var i = this.funcs.length;
+        if (t > 0 && 0 === i && this.globalDone) {
+            var n = this.globalDone;
+            this.globalDone = null,
+            n()
+        }
+    },
+    adjustSpeed: function(e, t) {
+        for (var i = this.getById(e), n = 0; n < i.length; n++) {
+            var r = i[n];
+            r.duration /= t,
+            r.current /= t
+        }
+    },
+    getById: function(e) {
+        return this.funcs.filter(function(t) {
+            return e === t.id
+        })
+    },
+    get: function(e) {
+        for (var t = 0; t < this.funcs.length; t += 1)
+            if (this.funcs[t].func === e)
+                return this.funcs[t];
+        return null
+    },
+    isRunning: function(e) {
+        var t = this.get(e);
+        return null !== t && t.running
+    },
+    countActive: function() {
+        for (var e = 0, t = 0; t < this.funcs.length; t += 1)
+            e += this.funcs[t].running;
+        return e
+    },
+    listActive: function() {
+        for (var e = [], t = 0; t < this.funcs.length; t += 1)
+            this.funcs[t].running && e.push(this.funcs[t].name);
+        return e
+    },
+    done: function(e) {
+        this.globalDone = e
+    },
+    cancelById: function(e, dealCancelFun) { //xzw add dealDone
+        var t = void 0 === e ? 0 : e;
+		 
+        this.funcs = this.funcs.filter(function(e) {
+			var is = e.id == t;
+			
+			if(is && dealCancelFun){
+				e.cancelFun && e.cancelFun()
+			} 
+            return !is
+        })
+    },
+    cancel: function(e) {
+        this.funcs = this.funcs.filter(function(t) {
+            return t.func !== e
+        }) 
+        
+    },
+    getUniqueId: function() {
+        return this.uniqueID -= 1,
+        this.uniqueID
+    }
+};
+
+ 
+let convertTool = {
+    
+	getPos2d : function(point, camera, dom){//获取一个三维坐标对应屏幕中的二维坐标
+	  
+        
+        
+        if(!camera)return
+		var pos = point.clone().project(camera)	//比之前hotspot的计算方式写得简单  project用于3转2(求法同shader); unproject用于2转3 :new r.Vector3(e.x, e.y, -1).unproject(this.camera);
+		
+		var x,y;
+		x = (pos.x + 1) / 2 * dom.clientWidth;
+		y = (1 - (pos.y + 1) / 2) * dom.clientHeight; 
+  
+		var inSight = x <= dom.clientWidth &&  x >= 0    //是否在屏幕中   
+					&& y <= dom.clientHeight &&  y >= 0 
+	 
+	
+		return {
+			pos: new THREE.Vector2(x,y),  // 屏幕像素坐标
+			vector:  pos,   //(范围 -1 ~ 1)
+			trueSide : pos.z<1, //trueSide为false时,即使在屏幕范围内可见,也是反方向的另一个不可以被渲染的点   参见Tag.update
+			inSight : inSight	//在屏幕范围内可见
+		};
+	},
+
+	ifShelter: function (pos3d,  pos2d,  camera, colliders, margin=0  ) {
+        //检测某点在视线中是否被mesh遮挡
+        if (!pos2d) pos2d = convertTool.getPos2d(pos3d )
+        camera = camera || player.camera
+        var ori = new THREE.Vector3(pos2d.x, pos2d.y, -1).unproject(camera) //找到视线原点
+        var dir = pos3d.clone().sub(ori).normalize()
+        var ray = new THREE.Raycaster(ori, dir); //由外向里 因为模型从内侧是可见的所以从外侧
+        var o = ray.intersectObjects(colliders); 
+	 
+		var len = pos3d.distanceTo(ori);
+		if (o && o.length) {
+			for(var i=0;i<o.length;i++){
+				if(o[i].distance < len-margin){  return true;  }//有遮挡
+			} 
+		} 
+	},
+    
+    updateVisible : function(object, reason, ifShow, level=0, type){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的
+        if(!object.unvisibleReasons) object.unvisibleReasons = []; //如果length>0代表不可见
+        if(!object.visibleReasons) object.visibleReasons = []; //在同级时,优先可见
+        
+        
+        var update = function(){
+            
+            //先按从高到低的level排列
+            object.unvisibleReasons = object.unvisibleReasons.sort((a,b)=>b.level-a.level)
+            object.visibleReasons = object.visibleReasons.sort((a,b)=>b.level-a.level)
+            var maxVisiLevel = object.visibleReasons[0] ? object.visibleReasons[0].level : -1
+            var maxunVisiLevel = object.unvisibleReasons[0] ? object.unvisibleReasons[0].level : -1
+            
+            var shouldVisi = maxVisiLevel >= maxunVisiLevel
+            var visiBefore = object.visible
+            
+            
+            if(visiBefore != shouldVisi){
+                object.visible = shouldVisi
+                object.dispatchEvent({
+                    type: 'isVisible',
+                    visible: shouldVisi,
+                    reason,
+                }) 
+            }
+            
+            
+        }    
+        
+        
+        
+        if(ifShow){ 
+
+            var index = object.unvisibleReasons.findIndex(e=>e.reason == reason) 
+            if(index > -1){
+                type = 'cancel'
+                object.unvisibleReasons.splice(index, 1); 
+            }
+            
+            if(type == 'add' ){
+                if(!object.visibleReasons.some(e=>e.reason == reason)){
+                    object.visibleReasons.push({reason,level})
+                }
+            } 
+        }else{ 
+            var index = object.visibleReasons.findIndex(e=>e.reason == reason) 
+            if(index > -1){
+                type = 'cancel'
+                object.visibleReasons.splice(index, 1); 
+            }
+            
+            if(type != 'cancel' ){
+                if(!object.unvisibleReasons.some(e=>e.reason == reason)){
+                    object.unvisibleReasons.push({reason,level})
+                }
+            }
+        }
+          
+        update() 
+        
+    }, 
+     
+
+    toPrecision: function (e, t) {//xzw change 保留小数
+		var f = function (e, t) {
+			var i = Math.pow(10, t);
+			return Math.round(e * i) / i
+		}
+		if (e instanceof Array) {
+			for (var s = 0; s < e.length; s++) {
+				e[s] = f(e[s], t);
+			}
+			return e;
+		} else if (e instanceof Object) {
+			for (var s in e) {
+				e[s] = f(e[s], t);
+			}
+			return e;
+		} else return f(e, t)
+	},
+
+    intervalTool:{  //延时update,防止卡顿
+        list:[],
+        
+        isWaiting:function(name, func, delayTime){
+            if(!this.list.includes(name)){  //如果没有该项, 则开始判断
+                var needWait = func(); //触发了改变,则等待一段时间后再自动判断
+                if(needWait){
+                    this.list.push(name);
+                    setTimeout(()=>{
+                        var a = this.list.indexOf(name);
+                        this.list.splice(a,1);
+                        this.isWaiting(name, func, delayTime) //循环
+                    },delayTime)
+                } 
+            }
+        }, 
+    }
+    ,
+
+}
+ 
+
+
+let math = {
+    closeTo : function(a,b, precision=1e-6){ 
+        let f = (a,b)=>{
+            return Math.abs(a-b) < precision;
+        }; 
+          
+        if(typeof (a) == 'number'){
+            return f(a, b);
+        }else {
+            let judge = (name)=>{
+                if(a[name] == void 0)return true //有值就判断,没值就不判断
+                else return f(a[name],b[name])
+            };
+            return judge('x') && judge('y') && judge('z') && judge('w')  
+        } 
+        
+    }, 
+    linearClamp(value, xArr , yArr){ //xArr需要按顺序从小到大,yArr对应xArr中的值
+	        
+	        let len = xArr.length; 
+	        if(value <= xArr[0]) return yArr[0]
+	        if(value >= xArr[len - 1]) return yArr[len - 1]
+	        let i = 0; 
+	        
+	        while(++i < len ){
+	            if(value < xArr[i]){
+	                let x1 = xArr[i-1], x2 = xArr[i], y1 = yArr[i-1], y2 = yArr[i]; 
+	                value = y1 + ( y2 - y1) * (value - x1)  / (x2 - x1);  
+	                break
+	            }
+	        }
+	        return value
+	        
+	         
+	    } 
+
+
+}
+var cameraLight = {
+    clampVFOV: function(currentFov, maxHFov, width, height) {//限制currentFov, 使之造成的横向fov不大于指定值maxHFov
+        var r = cameraLight.getHFOVFromVFOV(currentFov, width, height);
+        return r > maxHFov ? cameraLight.getVFOVFromHFOV(maxHFov, width, height) : currentFov
+    },
+    getHFOVForCamera: function(camera,  getRad) {
+        return cameraLight.getHFOVByScreenPrecent(camera.fov, camera.aspect, getRad)
+    }, 
+    //add
+    getHFOVByScreenPrecent: function(fov, percent, getRad) { //当fov为占比百分百时,percent代表在屏幕上从中心到边缘的占比
+        let rad = 2 * Math.atan(percent * Math.tan(THREE.Math.degToRad(fov * 2)));
+        if(getRad)return rad 
+        else return rad * MathLight.DEGREES_PER_RADIAN;
+    }
+};
+
+
+let texLoader = new THREE.TextureLoader; 
+let texs = new Map
+let common = {
+    urlHasValue(key, isGetValue) { 
+        let querys = window.location.search.substr(1).split('?')
+        if (isGetValue) {
+            for (let i = 0; i < querys.length; i++) {
+                let keypair = querys[i].split('=')
+                if (keypair.length === 2 && keypair[0] === key) {
+                    return keypair[1]
+                }
+            }
+            return ''
+        } else {
+            //return window.location.search.match("&" + key + "|\\?" + key) != null  有bug
+            for (let i = 0; i < querys.length; i++) {
+                let keypair = querys[i].split('=')
+                if(keypair[0] == key){
+                    return true
+                }
+            }
+            return false
+        }
+    }, 
+
+
+    dataURLtoBlob(dataurl) {//将base64转换blob
+        var arr = dataurl.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 });
+    },
+     
+    loadTexture(src,done){
+        let o = texs.get(src)
+        if(o){
+            if(o.tex.image) done && done(o.tex)//加载完毕
+            else{
+                o.callbacks.push(done)//等待加载
+            }
+            return 
+        }
+        
+        
+        
+        let callbacks = []
+        let tex = texLoader.load(src,(tex)=>{
+            callbacks.forEach(done=>done(tex)) 
+        }) 
+        done && callbacks.push(done)
+        texs.set(src,{tex,callbacks})
+        return tex
+    }
+}

+ 35 - 0
mobile/src/App.vue

@@ -0,0 +1,35 @@
+<script setup>
+import Tabbar from "@/components/Tabbar/index.vue";
+import { computed } from "vue";
+import { useRoute } from "vue-router";
+const route = useRoute();
+const tabbarVisible = computed(() => !route.meta.hideTabbar);
+</script>
+
+<template>
+  <div class="app">
+    <RouterView :key="$route.fullPath" :class="tabbarVisible ? 'page-content' : 'page-content-no-tabbar'" />
+    <Tabbar :visible="tabbarVisible" />
+  </div>
+</template>
+
+<style scoped>
+.app {
+  height: 100vh;
+  overflow: hidden;
+}
+
+.page-content {
+  height: 100vh;
+  overflow-y: auto;
+  padding-bottom: 70px;
+  box-sizing: border-box;
+}
+
+.page-content-no-tabbar {
+  height: 100vh;
+  overflow-y: auto;
+  padding-bottom: 0;
+  box-sizing: border-box;
+}
+</style>

+ 119 - 0
mobile/src/api/index.js

@@ -0,0 +1,119 @@
+import request from '@/utils/request';
+
+const hhbangBookApi = {
+  // 首页获取推荐列表
+  getRecommendListApi(params = {}) {
+    return request({
+      url: '/hyb/artArtworks/index/page',
+      method: 'get',
+      params: {
+        pageNo: Math.floor(Math.random() * 10) + 1, // 随机页码
+        pageSize: 30,
+        ...params
+      }
+    })
+  },
+
+  // 获取文物列表 - 用于收藏页面和详情页上下页切换
+  getArtifactListApi(params = {}) {
+    return request({
+      url: '/hyb/artArtworks/list',
+      method: 'get',
+      params: {
+        agetype: params.era || '', // 年代
+        category: params.category || '', // 分类
+        grade: params.level || '', // 级别
+        searchText: params.keyword || '', // 关键词
+        texture: params.material || '', // 材质
+        ...params
+      }
+    })
+  },
+
+  // 获取分类列表
+  getCategoryListApi() {
+    return request({
+      url: '/hyb/artArtworks/listCategory',
+      method: 'get'
+    })
+  },
+
+  // 获取文物详情
+  getArtifactDetailApi(id) {
+    return request({
+      url: `/hyb/artArtworks/${id}`,
+      method: 'get'
+    })
+  },
+
+  // 获取公告资讯列表
+  getNewsListApi(params = {}) {
+    return request({
+      url: '/hyb/newsPublish/page',
+      method: 'get',
+      params: {
+        searchText: params.searchText || '', // 关键词
+        pageNo: params.pageNo || 1, // 页码
+        pageSize: params.pageSize || 20, // 每页数量
+        ...params
+      }
+    })
+  },
+
+  // 获取新闻详情
+  getNewsDetailApi(id) {
+    return request({
+      url: `/hyb/newsPublish/${id}`,
+      method: 'get'
+    })
+  },
+
+  // 搜索文物 - 专门用于搜索页面
+  searchArtifacts(params = {}) {
+    return request({
+      url: '/hyb/artArtworks/list',
+      method: 'get',
+      params: {
+        agetype: params.era || '', // 年代
+        category: params.category || '', // 分类
+        grade: params.level || '', // 级别
+        searchText: params.keyword || '', // 关键词
+        texture: params.material || '', // 材质
+        pageNo: params.pageNo || 1,
+        pageSize: params.pageSize || 50,
+        ...params
+      }
+    })
+  },
+
+  // 提文物征集
+  submitCollectionClues(data, options = {}) {
+    const config = {
+      url: '/hyb/collCollectionClues/add',
+      method: 'post',
+      data
+    }
+
+    // 如果传入了自定义headers,添加到配置中
+    if (options.headers) {
+      config.customHeaders = options.headers
+    }
+
+    // 如果是FormData但后台不支持multipart/form-data,转换为application/x-www-form-urlencoded
+    if (data instanceof FormData && options.useUrlEncoded) {
+      const urlEncodedData = new URLSearchParams()
+      for (const [key, value] of data.entries()) {
+        urlEncodedData.append(key, value)
+      }
+      config.data = urlEncodedData
+      config.customHeaders = {
+        'Content-Type': 'application/x-www-form-urlencoded',
+        ...options.headers
+      }
+    }
+
+    return request(config)
+  }
+}
+
+export default hhbangBookApi;

+ 7 - 0
mobile/src/assets/element.scss

@@ -0,0 +1,7 @@
+@forward "element-plus/theme-chalk/src/common/var.scss" with (
+  $colors: (
+    "primary": (
+      "base": #b49d7e,
+    ),
+  )
+);

BIN
mobile/src/assets/fonts/SOURCEHANSERIFCN-BOLD.OTF


BIN
mobile/src/assets/fonts/SOURCEHANSERIFCN-REGULAR.OTF


BIN
mobile/src/assets/fonts/SourceHanSansCN-Regular.otf


BIN
mobile/src/assets/images/bg@2x-min.png


BIN
mobile/src/assets/images/icon_like_active@2x-min.png


BIN
mobile/src/assets/images/icon_like_normal@2x-min.png


BIN
mobile/src/assets/images/icon_portrait@2x-min.png


BIN
mobile/src/assets/images/logo2@2x-min.png


BIN
mobile/src/assets/images/logo@2x-min.png


BIN
mobile/src/assets/images/title_01.png


BIN
mobile/src/assets/img/btn_active.png


BIN
mobile/src/assets/img/btn_more.png


BIN
mobile/src/assets/img/btn_stoke_01.png


BIN
mobile/src/assets/img/btn_stoke_02.png


BIN
mobile/src/assets/img/icon_add.png


BIN
mobile/src/assets/img/icon_back.png


BIN
mobile/src/assets/img/icon_collect_more.png


BIN
mobile/src/assets/img/icon_collection_active.png


BIN
mobile/src/assets/img/icon_collection_normal.png


BIN
mobile/src/assets/img/icon_down.png


BIN
mobile/src/assets/img/icon_home_active.png


BIN
mobile/src/assets/img/icon_home_normal.png


BIN
mobile/src/assets/img/icon_left.png


BIN
mobile/src/assets/img/icon_more.png


BIN
mobile/src/assets/img/icon_notice_active.png


BIN
mobile/src/assets/img/icon_notice_normal.png


BIN
mobile/src/assets/img/icon_recruit_active.png


BIN
mobile/src/assets/img/icon_recruit_normal.png


BIN
mobile/src/assets/img/icon_right.png


BIN
mobile/src/assets/img/icon_search.png


BIN
mobile/src/assets/img/icon_select.png


BIN
mobile/src/assets/img/icon_zoomin.png


BIN
mobile/src/assets/img/icon_zoomout.png


BIN
mobile/src/assets/img/line.png


BIN
mobile/src/assets/img/title.png


+ 177 - 0
mobile/src/assets/main.css

@@ -0,0 +1,177 @@
+html,
+body,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  vertical-align: baseline;
+  overflow: hidden;
+}
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+  display: block;
+}
+body,
+input {
+  font-size: 14px;
+  color: #464646;
+  font-family: "Source Han Sans CN-Regular";
+}
+ol,
+ul {
+  list-style: none;
+}
+blockquote,
+q {
+  quotes: none;
+}
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+  content: "";
+  content: none;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+a {
+  color: inherit;
+  text-decoration: none;
+}
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  -webkit-appearance: none;
+  margin: 0;
+}
+
+.limit-line {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+
+.line-2 {
+  -webkit-line-clamp: 2;
+}
+
+.w100 {
+  width: 100%;
+}
+
+.w350 {
+  width: 350px !important;
+}
+
+.w1100 {
+  margin: 0 auto;
+  width: 1100px;
+  overflow: hidden;
+}
+
+@font-face {
+  font-family: "Source Han Sans CN-Regular";
+  src: url("@/assets/fonts/SourceHanSansCN-Regular.otf");
+}
+@font-face {
+  font-family: "Source Han Serif CN-Bold";
+  src: url("@/assets/fonts/SOURCEHANSERIFCN-BOLD.otf");
+}
+@font-face {
+  font-family: "Source Han Serif CN-Regular";
+  src: url("@/assets/fonts/SOURCEHANSERIFCN-REGULAR.otf");
+}

+ 44 - 0
mobile/src/assets/styles/cut-corner.scss

@@ -0,0 +1,44 @@
+// 公共的切角样式
+.cut-corner-select,
+.cut-corner-input {
+  .el-input__wrapper,
+  .el-select__wrapper {
+    position: relative;
+    border: none;
+    border-radius: 0;
+    padding: 2px 8px;
+    box-shadow: none;
+    .el-select__selection{
+      height: 40px;
+    }
+    .el-input__inner,
+    .el-select__selected-label {
+      height: 40px;
+      background: transparent;
+      border: none;
+      color: #333;
+    }
+  }
+  .el-input__wrapper{
+    background-image: url('@/assets/img/btn_stoke_01.png');
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    background-position: center;
+  }
+  .el-select__wrapper{
+    background-image: url('@/assets/img/btn_stoke_01.png');
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    background-position: center;
+  }
+}
+
+// el-select 宽度设置
+.cut-corner-select {
+  width: 140px;
+}
+
+// 搜索关键词输入框宽度
+.search-keyword-input {
+  width: 350px;
+}

+ 31 - 0
mobile/src/assets/styles/element-variables.scss

@@ -0,0 +1,31 @@
+/* 改变主题色 */
+:root {
+  --el-color-primary: #B49D7E;
+
+  /* 自定义border颜色 */
+  --el-border-color: #584735;
+  --el-border-color-light: #584735;
+  --el-border-color-lighter: #584735;
+  --el-border-color-extra-light: #584735;
+  --el-border-color-dark: #584735;
+  --el-border-color-darker: #584735;
+
+  /* Input组件border颜色 */
+  --el-input-border-color: #584735;
+  --el-input-hover-border-color: #e5c890;
+  --el-input-focus-border-color: #e5c890;
+
+  /* Select组件border颜色 */
+  --el-select-border-color-hover: #e5c890;
+  --el-select-input-focus-border-color: #e5c890;
+
+  /* Form组件border颜色 */
+  --el-form-border-color: #584735;
+
+  /* 其他组件的border颜色 */
+  --el-card-border-color: #584735;
+  --el-table-border-color: #584735;
+  --el-menu-border-color: #584735;
+
+  --el-fill-color-light: transparent;
+}

+ 44 - 0
mobile/src/assets/styles/variable.scss

@@ -0,0 +1,44 @@
+// bem.scss
+$namespace: 'xm' !default;
+$block-sel: '-' !default;
+$elem-sel: '__' !default;
+$mod-sel: '--' !default;
+
+@mixin bfc {
+  height: 100%;
+  overflow: hidden;
+}
+
+// block
+@mixin b($block) {
+  $B: #{$namespace + $block-sel + $block};
+  .#{$B} {
+    // 内容占位符
+    @content;
+  }
+}
+
+@mixin e($el) {
+  $selector: &;
+  // @at-root 平铺,编译后不会加父级选择器
+  @at-root {
+    #{$selector + $elem-sel + $el} {
+      @content;
+    }
+  }
+}
+
+@mixin m($m) {
+  $selector: &;
+  @at-root {
+    #{$selector + $mod-sel + $m} {
+      @content;
+    }
+  }
+}
+body {
+  overflow: hidden;
+}
+html {
+  -webkit-tap-highlight-color: transparent;
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 11 - 0
mobile/src/assets/svgs/icon_comment_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
mobile/src/assets/svgs/icon_copy.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
mobile/src/assets/svgs/icon_delete.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
mobile/src/assets/svgs/icon_eyes.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 16 - 0
mobile/src/assets/svgs/icon_fullscreen_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 6 - 0
mobile/src/assets/svgs/icon_like_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 8 - 0
mobile/src/assets/svgs/icon_mark_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 8 - 0
mobile/src/assets/svgs/icon_menu_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
mobile/src/assets/svgs/icon_search.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 8 - 0
mobile/src/assets/svgs/icon_search_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 8 - 0
mobile/src/assets/svgs/icon_setting_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 10 - 0
mobile/src/assets/svgs/icon_share_yellow.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
mobile/src/assets/svgs/icon_time.svg


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 7 - 0
mobile/src/assets/svgs/icon_upload_yellow.svg


BIN
mobile/src/components/Tabbar/images/icon_book_active@2x-min.png


BIN
mobile/src/components/Tabbar/images/icon_book_normal@2x-min.png


BIN
mobile/src/components/Tabbar/images/icon_home_active@2x-min.png


+ 0 - 0
mobile/src/components/Tabbar/images/icon_home_normal@2x-min.png


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott