chenlei 10 months ago
parent
commit
701d38d64c
54 changed files with 2521 additions and 59 deletions
  1. 2 0
      packages/base/src/theme.scss
  2. 2 2
      packages/pc/index.html
  3. 326 0
      packages/pc/public/three/PanoramaControls.js
  4. BIN
      packages/pc/public/three/assets/0.jpg
  5. BIN
      packages/pc/public/three/assets/1.jpg
  6. BIN
      packages/pc/public/three/assets/10.jpg
  7. BIN
      packages/pc/public/three/assets/11.jpg
  8. BIN
      packages/pc/public/three/assets/12.jpg
  9. BIN
      packages/pc/public/three/assets/13.jpg
  10. BIN
      packages/pc/public/three/assets/14.jpg
  11. BIN
      packages/pc/public/three/assets/2.jpg
  12. BIN
      packages/pc/public/three/assets/3.jpg
  13. BIN
      packages/pc/public/three/assets/4.jpg
  14. BIN
      packages/pc/public/three/assets/5.jpg
  15. BIN
      packages/pc/public/three/assets/6.jpg
  16. BIN
      packages/pc/public/three/assets/7.jpg
  17. BIN
      packages/pc/public/three/assets/8.jpg
  18. BIN
      packages/pc/public/three/assets/9.jpg
  19. BIN
      packages/pc/public/three/background.jpg
  20. 34 0
      packages/pc/public/three/click.js
  21. 103 0
      packages/pc/public/three/index.html
  22. 504 0
      packages/pc/public/three/index.js
  23. 4 0
      packages/pc/public/three/jquery-2.1.1.min.js
  24. 2 0
      packages/pc/public/three/three.min.js
  25. 636 0
      packages/pc/public/three/utils.js
  26. 4 0
      packages/pc/src/assets/main.css
  27. 11 0
      packages/pc/src/assets/svgs/icon_comment_yellow.svg
  28. 5 0
      packages/pc/src/assets/svgs/icon_eyes.svg
  29. 5 0
      packages/pc/src/assets/svgs/icon_time.svg
  30. 6 2
      packages/pc/src/components/BookCard/index.vue
  31. 7 0
      packages/pc/src/components/PagePane.vue
  32. 52 0
      packages/pc/src/components/TopNav/components/LoginDialog.vue
  33. 18 0
      packages/pc/src/components/TopNav/index.scss
  34. 54 19
      packages/pc/src/components/TopNav/index.vue
  35. 9 1
      packages/pc/src/router/index.js
  36. 6 2
      packages/pc/src/stores/detail.js
  37. 90 0
      packages/pc/src/views/Detail/components/Comment.vue
  38. 124 0
      packages/pc/src/views/Detail/components/IntroductionDialog.vue
  39. 73 0
      packages/pc/src/views/Detail/components/Photocopy/index.scss
  40. 20 0
      packages/pc/src/views/Detail/components/Photocopy/index.vue
  41. 8 1
      packages/pc/src/views/Detail/components/Setting.vue
  42. 13 1
      packages/pc/src/views/Detail/components/Toolbar/index.vue
  43. 41 0
      packages/pc/src/views/Detail/components/Video/index.vue
  44. BIN
      packages/pc/src/views/Detail/images/icon_left.png
  45. BIN
      packages/pc/src/views/Detail/images/icon_right.png
  46. BIN
      packages/pc/src/views/Detail/images/icon_scale.png
  47. 25 4
      packages/pc/src/views/Detail/index.vue
  48. 40 5
      packages/pc/src/views/Home/index.scss
  49. 73 22
      packages/pc/src/views/Home/index.vue
  50. 122 0
      packages/pc/src/views/Home2/components/News.vue
  51. 99 0
      packages/pc/src/views/Home2/components/NewsDialog.vue
  52. BIN
      packages/pc/src/views/Home2/images/bg2-min.png
  53. BIN
      packages/pc/src/views/Home2/images/text_news-min.png
  54. 3 0
      packages/pc/src/views/Home2/index.vue

+ 2 - 0
packages/base/src/theme.scss

@@ -2,6 +2,7 @@ html {
   --topnav-bg-color: #f8f6f2;
   --page-bg-color: #f3f3f3;
   --pane-bg-color: white;
+  --pane2-bg-color: #fbfbfa;
   --text-color: #464646;
   --text-color-secondary: rgba(70, 70, 70, 0.8);
   --text-color-placeholder: rgba(70, 70, 70, 0.5);
@@ -18,6 +19,7 @@ html.dark {
   --topnav-bg-color: #414141;
   --page-bg-color: #373737;
   --pane-bg-color: #2c2c2c;
+  --pane2-bg-color: #3a3a39;
   --text-color: #ececec;
   --text-color-secondary: rgba(236, 236, 236, 0.8);
   --text-color-placeholder: rgba(236, 236, 236, 0.5);

+ 2 - 2
packages/pc/index.html

@@ -9,7 +9,7 @@
   <body>
     <div id="app"></div>
     <script type="module" src="/src/main.js"></script>
-    <script src="./jszip.min.js"></script>
-    <script src="./epub.min.js"></script>
+    <script type="text/javascript" src="./jszip.min.js"></script>
+    <script type="text/javascript" src="./epub.min.js"></script>
   </body>
 </html>

+ 326 - 0
packages/pc/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
packages/pc/public/three/assets/0.jpg


BIN
packages/pc/public/three/assets/1.jpg


BIN
packages/pc/public/three/assets/10.jpg


BIN
packages/pc/public/three/assets/11.jpg


BIN
packages/pc/public/three/assets/12.jpg


BIN
packages/pc/public/three/assets/13.jpg


BIN
packages/pc/public/three/assets/14.jpg


BIN
packages/pc/public/three/assets/2.jpg


BIN
packages/pc/public/three/assets/3.jpg


BIN
packages/pc/public/three/assets/4.jpg


BIN
packages/pc/public/three/assets/5.jpg


BIN
packages/pc/public/three/assets/6.jpg


BIN
packages/pc/public/three/assets/7.jpg


BIN
packages/pc/public/three/assets/8.jpg


BIN
packages/pc/public/three/assets/9.jpg


BIN
packages/pc/public/three/background.jpg


+ 34 - 0
packages/pc/public/three/click.js

@@ -0,0 +1,34 @@
+// 点击
+viewer.addEventListener('clickObject', e => {
+  window.top.clickObject(e.imgName);
+  // 暂停动画
+  viewer.setAutoMove(false)
+})
+
+// 继续动画 - 给 父页面调用
+window.stareMove = (val) => {
+  viewer.setAutoMove(val)
+}
+
+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);
+}

+ 103 - 0
packages/pc/public/three/index.html

@@ -0,0 +1,103 @@
+<!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>artsandculture</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>
+      const cardNames = [
+        "1.jpg",
+        "2.jpg",
+        "3.jpg",
+        "4.jpg",
+        "5.jpg",
+        "6.jpg",
+        "7.jpg",
+        "8.jpg",
+        "9.jpg",
+        "10.jpg",
+        "11.jpg",
+        "12.jpg",
+        "13.jpg",
+        "14.jpg",
+      ];
+
+      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米处时最高可以多少才能在视线内
+        },
+      };
+
+      /* var textarea = document.createElement('textarea');
+    textarea.id = "consoleLog";
+
+    document.getElementsByTagName("body")[0].appendChild(textarea);
+    var list = ["log", "error", "warn", "debug", "info", "time", "timeEnd"]
+    var exchange = function (o) {
+    console["old" + o] = console[o];
+    console[o] = function (str) {
+      console["old" + o](str);
+      var t = document.getElementById("consoleLog").innerHTML;
+      document.getElementById("consoleLog").innerHTML = str + "\n\n" + t;
+    }
+    }
+
+    for (var i = 0; i < list.length; i++) {
+    exchange(list[i])
+    }
+
+    */
+    </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>

File diff suppressed because it is too large
+ 504 - 0
packages/pc/public/three/index.js


File diff suppressed because it is too large
+ 4 - 0
packages/pc/public/three/jquery-2.1.1.min.js


File diff suppressed because it is too large
+ 2 - 0
packages/pc/public/three/three.min.js


+ 636 - 0
packages/pc/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
+    }
+}

+ 4 - 0
packages/pc/src/assets/main.css

@@ -125,6 +125,10 @@ table {
   border-collapse: collapse;
   border-spacing: 0;
 }
+a {
+  color: inherit;
+  text-decoration: none;
+}
 
 .limit-line {
   display: -webkit-box;

File diff suppressed because it is too large
+ 11 - 0
packages/pc/src/assets/svgs/icon_comment_yellow.svg


File diff suppressed because it is too large
+ 5 - 0
packages/pc/src/assets/svgs/icon_eyes.svg


File diff suppressed because it is too large
+ 5 - 0
packages/pc/src/assets/svgs/icon_time.svg


+ 6 - 2
packages/pc/src/components/BookCard/index.vue

@@ -1,5 +1,9 @@
 <template>
-  <div class="book-card" :class="{ row: isRow, large: size === 'large' }">
+  <router-link
+    :to="{ name: 'detail', params: { id: 1, type: 'reader' } }"
+    class="book-card"
+    :class="{ row: isRow, large: size === 'large' }"
+  >
     <div class="book-card-img">
       <el-image src="" fit="cover" />
 
@@ -16,7 +20,7 @@
       <p v-if="isRow" class="limit-line">刘少奇</p>
       <p class="limit-line">中共中央文献研究室 编</p>
     </div>
-  </div>
+  </router-link>
 </template>
 
 <script setup>

+ 7 - 0
packages/pc/src/components/PagePane.vue

@@ -1,16 +1,23 @@
 <template>
   <div class="page-pane">
     <div
+      v-if="!simple"
       class="page-pane-container"
       :style="containerColor ? { background: containerColor } : ''"
     >
       <slot />
     </div>
+
+    <slot v-else />
   </div>
 </template>
 
 <script setup>
 defineProps({
+  simple: {
+    type: Boolean,
+    default: false,
+  },
   containerColor: {
     type: String,
     required: false,

+ 52 - 0
packages/pc/src/components/TopNav/components/LoginDialog.vue

@@ -0,0 +1,52 @@
+<template>
+  <el-dialog v-model="show" class="login-dialog" width="425px">
+    <el-image class="login-dialog__scan" src="" />
+    <span>手机扫描二维码</span>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed } from "vue";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+});
+const emits = defineEmits(["update:visible"]);
+
+const show = computed({
+  get() {
+    return props.visible;
+  },
+  set(v) {
+    emits("update:visible", v);
+  },
+});
+</script>
+
+<style lang="scss">
+.login-dialog {
+  height: 517px;
+  background: rgba(255, 255, 255, 0.9);
+
+  .el-dialog__body {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 32px;
+    height: 100%;
+
+    span {
+      font-size: 24px;
+      font-family: "Source Han Serif CN-Bold";
+    }
+  }
+  &__scan {
+    width: 321px;
+    height: 321px;
+  }
+}
+</style>

+ 18 - 0
packages/pc/src/components/TopNav/index.scss

@@ -92,8 +92,26 @@
     gap: 30px;
 
     h1 {
+      display: flex;
+      align-items: center;
+      gap: 20px;
       font-size: 24px;
       font-family: "Source Han Serif CN-Bold";
+
+      span {
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        width: 70px;
+        height: 25px;
+        font-size: 12px;
+        font-family: "Source Han Sans CN-Regular";
+        color: var(--el-color-primary);
+        border: 1px solid var(--el-color-primary);
+        border-radius: 3px;
+        box-sizing: border-box;
+        cursor: pointer;
+      }
     }
     p {
       color: var(--text-color-secondary);

+ 54 - 19
packages/pc/src/components/TopNav/index.vue

@@ -7,18 +7,28 @@
     >
       <div>
         <div class="top-nav-lf">
-          <img
-            v-if="showLogo"
-            class="top-nav__logo"
-            draggable="false"
-            src="@/assets/images/logo_02-min.png"
-            @click="$router.push({ name: 'home' })"
-          />
+          <router-link v-if="showLogo" :to="{ name: 'home' }">
+            <img
+              class="top-nav__logo"
+              draggable="false"
+              src="@/assets/images/logo_02-min.png"
+            />
+          </router-link>
 
           <div v-if="isDetail" class="top-nav-detail">
-            <img :src="isDark ? LogoWhiteIcon : LogoIcon" draggable="false" />
+            <router-link :to="{ name: 'home' }">
+              <img
+                :src="isDark ? LogoWhiteIcon : LogoIcon"
+                draggable="false"
+                style="display: block"
+              />
+            </router-link>
             <div class="top-nav-detail__inner">
-              <h1>刘少奇传</h1>
+              <h1>
+                刘少奇传<span @click="introductionVisible = true"
+                  >查看简介</span
+                >
+              </h1>
               <p>金冲及 中共中央文献研究室 编</p>
             </div>
           </div>
@@ -27,8 +37,8 @@
         <div class="top-nav-rg">
           <ul class="top-nav-rg-first">
             <li>手机版</li>
-            <li v-if="!isDetail" @click="$router.push({ name: 'stack' })">
-              书库
+            <li v-if="!isDetail">
+              <router-link :to="{ name: 'stack' }">书库</router-link>
             </li>
             <li
               v-else
@@ -44,17 +54,33 @@
           <template v-if="isDetail">
             <div class="top-nav-detail-nav">
               <div
-                v-for="(item, index) in DETAIL_NAV"
-                :key="item.text"
+                v-for="item in DETAIL_NAV"
+                :key="item.key"
                 class="top-nav-detail-nav__item"
-                :class="{ active: index === curNav }"
+                :class="{ active: $route.params.type === item.key }"
+                @click="
+                  $router.push({
+                    name: 'detail',
+                    params: {
+                      id: route.params.id,
+                      type: item.key,
+                    },
+                  })
+                "
               >
-                <img :src="index === curNav ? item.activeIcon : item.icon" />
+                <img
+                  :src="
+                    $route.params.type === item.key
+                      ? item.activeIcon
+                      : item.icon
+                  "
+                />
                 <p>{{ item.text }}</p>
               </div>
 
               <!-- 简/繁体 -->
               <img
+                v-show="$route.params.type === 'reader'"
                 :src="isSimplified ? jtIcon : ftIcon"
                 @click="epubStore.toggleText"
               />
@@ -89,11 +115,15 @@
             </ul>
           </el-popover>
 
-          <div class="top-nav__login">微信登录</div>
+          <div class="top-nav__login" @click="loginVisible = true">
+            微信登录
+          </div>
         </div>
       </div>
     </div>
   </div>
+
+  <login-dialog v-model:visible="loginVisible" />
 </template>
 
 <script setup>
@@ -101,7 +131,8 @@ import { watch, ref } from "vue";
 import { useRoute } from "vue-router";
 import { useDark } from "@vueuse/core";
 import { storeToRefs } from "pinia";
-import { useBaseStore, useDetailStore, useEpubStore } from "@/stores";
+import { useBaseStore, useEpubStore, useDetailStore } from "@/stores";
+import LoginDialog from "./components/LoginDialog.vue";
 
 import LogoIcon from "@/assets/images/logo_03-min.png";
 import LogoWhiteIcon from "@/assets/images/logo-min.png";
@@ -118,16 +149,19 @@ import skDarkIcon from "./images/icon_book_dark-min.png";
 
 const DETAIL_NAV = [
   {
+    key: "reader",
     icon: TextIcon,
     activeIcon: TextActiveIcon,
     text: "文本",
   },
   {
+    key: "video",
     icon: VideoIcon,
     activeIcon: VideoActiveIcon,
     text: "视频",
   },
   {
+    key: "photocopy",
     icon: ImgIcon,
     activeIcon: ImgActiveIcon,
     text: "影印",
@@ -137,10 +171,10 @@ const DETAIL_NAV = [
 const route = useRoute();
 const isDark = useDark();
 
+const detailStore = useDetailStore();
+const { introductionVisible } = storeToRefs(detailStore);
 const baseStore = useBaseStore();
 const { isLogin } = storeToRefs(baseStore);
-const detailStore = useDetailStore();
-const { curNav } = storeToRefs(detailStore);
 const epubStore = useEpubStore();
 const { isSimplified } = storeToRefs(epubStore);
 
@@ -149,6 +183,7 @@ const showLogo = ref(false);
 const hideBgColor = ref(false);
 const bgColor = ref("");
 const joinLoading = ref(false);
+const loginVisible = ref(false);
 
 watch(route, (v) => {
   isDetail.value = v.name === "detail";

+ 9 - 1
packages/pc/src/router/index.js

@@ -37,7 +37,7 @@ const router = createRouter({
       },
     },
     {
-      path: "/detail/:id",
+      path: "/detail/:id/:type",
       name: "detail",
       component: () => import("@/views/Detail/index.vue"),
       meta: {},
@@ -45,4 +45,12 @@ const router = createRouter({
   ],
 });
 
+router.beforeEach((to) => {
+  if (to.name !== "detail") {
+    // 除了 detail 页面,其他页面不需要暗黑模式
+    localStorage.removeItem("vueuse-color-scheme");
+    document.documentElement.className = "";
+  }
+});
+
 export default router;

+ 6 - 2
packages/pc/src/stores/detail.js

@@ -2,14 +2,18 @@ import { ref } from "vue";
 import { defineStore } from "pinia";
 
 export const useDetailStore = defineStore("detail", () => {
-  const curNav = ref(0);
   const searchVisible = ref(false);
   const searchKey = ref("");
 
+  /**
+   * 是否显示简介
+   */
+  const introductionVisible = ref(false);
+
   const openSearchDrawer = (key = "") => {
     searchKey.value = key;
     searchVisible.value = true;
   };
 
-  return { curNav, searchVisible, searchKey, openSearchDrawer };
+  return { searchVisible, searchKey, introductionVisible, openSearchDrawer };
 });

+ 90 - 0
packages/pc/src/views/Detail/components/Comment.vue

@@ -0,0 +1,90 @@
+<template>
+  <Drawer v-model:visible="show" title="评论" @close="emits('close')">
+    <div class="comment">
+      <el-input
+        type="textarea"
+        :autosize="{ minRows: 8 }"
+        placeholder="请留下您的评论,最多200字"
+        :maxlength="200"
+        show-word-limit
+      />
+
+      <el-button
+        type="primary"
+        style="margin: 10px 0 70px; width: 100%; height: 50px"
+        >发表</el-button
+      >
+
+      <div class="comment-list">
+        <div class="comment-item">
+          <div class="comment-item-header">
+            <p>怕黑的cat</p>
+            <p class="comment-item-header__date">2024-08-18</p>
+            <svg-icon
+              class="comment-item-header__del"
+              name="icon_delete"
+              width="16px"
+              height="16px"
+              color="var(--el-color-primary)"
+            />
+          </div>
+          <p class="comment-item__inner">
+            像一条绿色的玉带,从南到北缓缓穿越湖南全省,注入中国第二大淡水湖洞庭湖。
+          </p>
+        </div>
+      </div>
+    </div>
+  </Drawer>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import Drawer from "./Drawer.vue";
+
+const props = defineProps(["visible"]);
+const emits = defineEmits(["update:visible", "close"]);
+
+const show = computed({
+  get() {
+    return props.visible;
+  },
+  set(v) {
+    emits("update:visible", v);
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.comment {
+  padding: 0 25px;
+
+  &-item {
+    &:not(:last-child) {
+      border-bottom: 1px solid #d9d9d9;
+    }
+    &-header {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+
+      p {
+        opacity: 0.5;
+      }
+      &__date {
+        flex: 1;
+        text-align: right;
+      }
+      &__del {
+        position: relative;
+        top: -2px;
+        cursor: pointer;
+      }
+    }
+    &__inner {
+      margin: 8px 0 25px;
+      padding-left: 12px;
+      border-left: 5px solid var(--el-color-primary);
+    }
+  }
+}
+</style>

+ 124 - 0
packages/pc/src/views/Detail/components/IntroductionDialog.vue

@@ -0,0 +1,124 @@
+<template>
+  <el-dialog
+    v-model="show"
+    top="10vh"
+    class="introduction-dialog"
+    width="1174px"
+  >
+    <div class="introduction-dialog-left">
+      <el-image class="introduction-dialog-left__cover" src="" />
+      <div class="introduction-dialog-left-info">
+        <h3>刘少奇传</h3>
+        <p class="introduction-dialog-left-info__auther">金冲及</p>
+        <p>中共中央文献研究室 编</p>
+      </div>
+    </div>
+    <div class="introduction-dialog-right">
+      <div class="introduction-dialog-right__title"><span>作品简介</span></div>
+      <div style="margin: 0 -10px">
+        <el-scrollbar
+          style="margin-top: 30px; padding: 0 10px; font-size: 18px"
+          :height="610 - 65"
+        >
+          蜿蜒曲折的湘江,像一条绿色的玉带,从南到北缓缓穿越湖南全省,注入中国第二大淡水湖洞庭湖。在湘江西侧的宁乡县境内,有一个普普通通的小山村,叫炭子冲。相传在很久以前,这一带有不少人以伐木烧炭为生,是烧炭人居住和落脚的地方,因此得名炭子冲。
+          “冲”​,是湖南老百姓对山间小块平原的称呼。炭子冲,就是一块夹在两座山岭之间的平地。它的北面背靠着连绵不绝的丘陵,东西两边是长满了密密层层各色杂树的山坡,南面是平坦的农田和宁静的池塘。湘江的支流靳江,在它的西南角不远处淙淙流过。顺着冲口的大路往东北方向行进,约莫四十来公里,便到了湘江。湘江对岸,就是湖南省省会长沙。
+          炭子冲在行政建制上属于湖南省宁乡县花明楼乡。这一带有山有水,盛产稻米、林木、烟叶,是湖南中部较为富庶的地区。由于这里离省会和县城都不远,交通便利,外面的信息容易传播进来,文化也比较发达。
+        </el-scrollbar>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed } from "vue";
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+});
+const emits = defineEmits(["update:visible"]);
+
+const show = computed({
+  get() {
+    return props.visible;
+  },
+  set(v) {
+    emits("update:visible", v);
+  },
+});
+</script>
+
+<style lang="scss">
+.introduction-dialog {
+  --el-message-close-size: 20px;
+
+  padding: 0;
+  height: 745px;
+  border-radius: 10px;
+  overflow: hidden;
+
+  .el-dialog__header {
+    padding-bottom: 0;
+  }
+  .el-dialog__headerbtn {
+    top: 10px;
+    right: 10px;
+  }
+  .el-dialog__body {
+    display: flex;
+    height: 100%;
+  }
+  &-left {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    background: var(--pane2-bg-color);
+
+    &__cover {
+      width: 262px;
+      height: 353px;
+    }
+    &-info {
+      margin-top: 80px;
+      text-align: center;
+
+      h3 {
+        font-size: 24px;
+        font-family: "Source Han Serif CN-Bold";
+      }
+      &__auther {
+        margin-top: 14px;
+      }
+    }
+  }
+  &-right {
+    flex: 1;
+    padding: 90px 70px 45px;
+
+    &__title {
+      display: inline-block;
+      position: relative;
+      font-size: 24px;
+      font-family: "Source Han Serif CN-Bold";
+
+      &::after {
+        content: "";
+        position: absolute;
+        right: 0;
+        bottom: 2px;
+        width: 48px;
+        height: 7px;
+        background: var(--el-color-primary);
+      }
+      span {
+        position: relative;
+        z-index: 1;
+      }
+    }
+  }
+}
+</style>

+ 73 - 0
packages/pc/src/views/Detail/components/Photocopy/index.scss

@@ -0,0 +1,73 @@
+.photocopy {
+  display: flex;
+  gap: 42px;
+  margin: 0 auto;
+  padding: 0 21px;
+  width: 1434px;
+  height: calc(100vh - var(--topnav-height) - 54px);
+  box-sizing: border-box;
+
+  &-left,
+  &-right {
+    position: relative;
+    flex: 1;
+    height: 100%;
+    background: var(--el-bg-color);
+    border-radius: 10px;
+    box-shadow: 0px 0px 21px 0px rgba(0, 0, 0, 0.1);
+    box-sizing: border-box;
+  }
+
+  &-left {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 30px;
+
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+    &__prev,
+    &__next {
+      position: absolute;
+      top: 50%;
+      width: 40px;
+      height: 40px;
+      transform: translateY(-50%);
+      z-index: 1;
+      cursor: pointer;
+    }
+    &__prev {
+      left: -20px;
+      background: url("../../images/icon_left.png") no-repeat center / contain;
+    }
+    &__next {
+      right: -20px;
+      background: url("../../images/icon_right.png") no-repeat center / contain;
+    }
+    &__magnify {
+      position: absolute;
+      right: 37px;
+      bottom: 37px;
+      width: 40px;
+      height: 40px;
+      background: url("../../images/icon_scale.png") no-repeat center / contain;
+      z-index: 1;
+      cursor: pointer;
+    }
+    &__pagination {
+      position: absolute;
+      left: 50%;
+      bottom: 37px;
+      width: 90px;
+      height: 32px;
+      line-height: 32px;
+      text-align: center;
+      transform: translateX(-50%);
+      font-size: 16px;
+      border-radius: 25px;
+      background: rgba(169, 146, 113, 0.3);
+    }
+  }
+}

+ 20 - 0
packages/pc/src/views/Detail/components/Photocopy/index.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="photocopy">
+    <div class="photocopy-left">
+      <el-image src="" fit="contain" />
+
+      <div class="photocopy-left__prev" />
+      <div class="photocopy-left__next" />
+
+      <div class="photocopy-left__magnify" />
+
+      <div class="photocopy-left__pagination">12/24</div>
+    </div>
+
+    <div class="photocopy-right"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 8 - 1
packages/pc/src/views/Detail/components/Setting.vue

@@ -31,7 +31,7 @@
 </template>
 
 <script setup>
-import { computed, ref } from "vue";
+import { computed, ref, onMounted } from "vue";
 import { useDark, useToggle } from "@vueuse/core";
 import {
   THEMES,
@@ -39,6 +39,7 @@ import {
   FONT_SIZES,
   EPUB_FONT_KEY,
   EPUB_FONTSIZE_KEY,
+  EPUB_THEME_KEY,
 } from "@lsq/base";
 import { useEpubStore } from "@/stores";
 import SelectItem from "@/components/SelectItem/index.vue";
@@ -71,6 +72,12 @@ const handleTheme = (item) => {
   epubStore.toggleTheme(item.key);
   toggleDark(item.key === "dark");
 };
+
+onMounted(() => {
+  if (localStorage.getItem(EPUB_THEME_KEY) === "dark") {
+    toggleDark(true);
+  }
+});
 </script>
 
 <style lang="scss" scoped>

+ 13 - 1
packages/pc/src/views/Detail/components/Toolbar/index.vue

@@ -3,7 +3,7 @@
     v-for="(item, idx) in list"
     :key="item.key"
     class="detail-toolbar-item"
-    :class="{ active: active === idx && active !== 5 }"
+    :class="{ active: active === idx && active !== list.length - 1 }"
     :style="{
       top: item.top + 'px',
     }"
@@ -23,6 +23,7 @@
   <search v-model:visible="detailStore.searchVisible" @close="handleClose" />
   <note-drawer v-model:visible="noteVisible" @close="handleClose" />
   <bookmark v-model:visible="bookmarkVisible" @close="handleClose" />
+  <comment v-model:visible="commentVisible" @close="handleClose" />
 </template>
 
 <script setup>
@@ -34,6 +35,7 @@ import Setting from "../Setting.vue";
 import Search from "../Search.vue";
 import NoteDrawer from "../Note.vue";
 import Bookmark from "../Bookmark.vue";
+import Comment from "../Comment.vue";
 
 const { isSupported, toggle } = useFullscreen();
 const epubStore = useEpubStore();
@@ -43,6 +45,7 @@ const directoryVisible = ref(false);
 const settingVisible = ref(false);
 const noteVisible = ref(false);
 const bookmarkVisible = ref(false);
+const commentVisible = ref(false);
 const list = computed(() => {
   const stack = [
     {
@@ -75,6 +78,12 @@ const list = computed(() => {
       icon: "icon_setting_yellow",
       key: "setting",
     },
+    {
+      top: 0,
+      label: "评论",
+      icon: "icon_comment_yellow",
+      key: "comment",
+    },
   ];
   if (isSupported.value) {
     stack.push({
@@ -113,6 +122,9 @@ const handleToolbar = (key, idx) => {
     case "bookmark":
       bookmarkVisible.value = true;
       break;
+    case "comment":
+      commentVisible.value = true;
+      break;
     case "fullscreen":
       toggle().then(() => {
         setTimeout(() => {

+ 41 - 0
packages/pc/src/views/Detail/components/Video/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <div class="detail-video">
+    <div class="detail-video-item">
+      <el-image src="" fit="cover" />
+
+      <div class="detail-video-item-inner">
+        <p>学习求索</p>
+        <p>
+          刘少奇(1898年11月24日-1969年11月12日),生于湖南省宁乡县,伟大的马克思主义者,伟大的无产阶级革命家、政治家、理论家,党和国家主要领导人之一,中华人民共和国开国元勋,是以毛泽东同志为核心的党的第一代中央领导集体的重要成员
+        </p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.detail-video {
+  &-item {
+    margin: 20px 0;
+    display: flex;
+    align-items: center;
+    gap: 80px;
+
+    .el-image {
+      flex-shrink: 0;
+      width: 547px;
+      height: 295px;
+    }
+    &-inner {
+      p:first-child {
+        font-size: 32px;
+        font-family: "Source Han Serif CN-Bold";
+      }
+      p:last-child {
+        margin-top: 15px;
+        font-size: 18px;
+      }
+    }
+  }
+}
+</style>

BIN
packages/pc/src/views/Detail/images/icon_left.png


BIN
packages/pc/src/views/Detail/images/icon_right.png


BIN
packages/pc/src/views/Detail/images/icon_scale.png


+ 25 - 4
packages/pc/src/views/Detail/index.vue

@@ -1,23 +1,44 @@
 <template>
-  <page-pane :container-color="paneBgColor">
+  <page-pane :container-color="paneBgColor" :simple="isPhotocopy">
     <component :is="comp" />
   </page-pane>
 
-  <toolbar />
+  <toolbar v-if="route.params.type === 'reader'" />
+  <introduction-dialog v-model:visible="introductionVisible" />
 </template>
 
 <script setup>
 import { computed } from "vue";
+import { useRoute } from "vue-router";
 import { THEMES } from "@lsq/base";
-import { useEpubStore } from "@/stores";
+import { storeToRefs } from "pinia";
+import { useEpubStore, useDetailStore } from "@/stores";
 import Toolbar from "./components/Toolbar/index.vue";
 import Reader from "./components/Reader/index.vue";
+import Photocopy from "./components/Photocopy/index.vue";
+import DetailVideo from "./components/Video/index.vue";
+import IntroductionDialog from "./components/IntroductionDialog.vue";
 
+const route = useRoute();
 const epubStore = useEpubStore();
+const detailStore = useDetailStore();
+const { introductionVisible } = storeToRefs(detailStore);
+
 const paneBgColor = computed(
   () => THEMES.find((theme) => theme.key === epubStore.curTheme).color
 );
-const comp = Reader;
+const isPhotocopy = computed(() => route.params.type === "photocopy");
+
+const comp = computed(() => {
+  switch (route.params.type) {
+    case "photocopy":
+      return Photocopy;
+    case "video":
+      return DetailVideo;
+    default:
+      return Reader;
+  }
+});
 </script>
 
 <style lang="scss" scoped>

+ 40 - 5
packages/pc/src/views/Home/index.scss

@@ -1,10 +1,14 @@
 .home {
-  margin-top: calc(var(--topnav-height) * -1);
-  height: 100vh;
-  overflow: hidden;
-  background: url("@/assets/images/bg-min.jpg") no-repeat top center / cover;
-
+  &-iframe {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 1;
+  }
   &-main {
+    position: relative;
     display: flex;
     flex-direction: column;
     align-items: center;
@@ -82,9 +86,40 @@
     color: var(--el-color-primary);
     transform: translateX(-50%);
     cursor: pointer;
+    z-index: 2;
 
     img {
       width: 30px;
     }
   }
 }
+
+.txt {
+  position: absolute;
+  z-index: 3;
+  top: 0;
+  left: 0;
+  width: 200px;
+  // height: 185px;
+  text-align: center;
+  pointer-events: none;
+  font-size: 18px;
+  opacity: 0;
+  // transition: all 0.5s;
+
+  // display: -webkit-box;
+  // overflow: hidden;
+  // white-space: normal !important;
+  // text-overflow: ellipsis;
+  // word-wrap: break-word;
+  // -webkit-line-clamp: 5;
+  // -webkit-box-orient: vertical;
+  h2 {
+    font-size: 24px;
+    font-weight: 700;
+    margin-bottom: 15px;
+  }
+  p {
+    line-height: 24px;
+  }
+}

+ 73 - 22
packages/pc/src/views/Home/index.vue

@@ -1,33 +1,84 @@
 <template>
-  <div class="home">
-    <div class="home-main">
-      <img
-        class="home-main__logo"
-        draggable="false"
-        src="@/assets/images/logo_01-min.png"
-      />
+  <iframe class="home-iframe" src="./three/index.html" frameborder="0"></iframe>
+  <div ref="txtDom" class="txt" :style="{ opacity: txt.show ? '0.8' : '0' }">
+    <h2>{{ txt.title }}</h2>
+    <p>{{ txt.con }}</p>
+  </div>
+
+  <div class="home-main">
+    <img
+      class="home-main__logo"
+      draggable="false"
+      src="@/assets/images/logo_01-min.png"
+    />
 
-      <div class="home-search">
-        <input
-          class="home-search__input"
-          type="text"
-          placeholder="请输入关键词..."
-        />
-        <button class="home-search__btn">搜索</button>
-      </div>
-
-      <div class="home-view">
-        <p class="limit-line">共收录132件藏品,查看书库</p>
-      </div>
+    <div class="home-search">
+      <input
+        class="home-search__input"
+        type="text"
+        placeholder="请输入关键词..."
+      />
+      <button class="home-search__btn">搜索</button>
     </div>
 
-    <div class="home-more" @click="$router.push({ name: 'home2' })">
-      <img draggable="false" src="@/assets/images/icon_click.png" />
-      <p>查看更多</p>
+    <div class="home-view">
+      <p class="limit-line">共收录132件藏品,查看书库</p>
     </div>
   </div>
+
+  <router-link class="home-more" :to="{ name: 'home2' }">
+    <img draggable="false" src="@/assets/images/icon_click.png" />
+    <p>查看更多</p>
+  </router-link>
 </template>
 
+<script setup>
+import { onMounted, ref } from "vue";
+
+const txt = ref({ show: false });
+const txtDom = ref(null);
+
+onMounted(() => {
+  // 点击图片
+  window.clickObject = (val) => {
+    console.log("000", val);
+  };
+  // 鼠标移入
+  window.hoverObject = (val) => {
+    let con =
+      "我是一个文物介绍,我是一个文物介绍,我是一个文物我是一个一个文物介绍,我是一个文物介绍我是一个文物介绍,我是一个文物介绍,我是一个文物我是一个一个文物介绍,我是一个文物介绍";
+    if (val.imgName.includes("1")) {
+      con = "xxxxxxxxxx阿三大苏打xxxxxxxxxx阿三大苏打";
+    }
+    txt.value = {
+      title: "文物" + val.imgName,
+      con,
+      show: true,
+    };
+  };
+  // 鼠标移出
+  window.mouseoutObject = (val) => {
+    txt.value.show = false;
+  };
+
+  // 获取鼠标坐标
+  window.mouseLoc = (x, y) => {
+    // console.log("ppp", x, y);
+    // 最大X值
+    const maxX = window.innerWidth - 200;
+    let xRes = x >= maxX ? maxX : x;
+    // xRes = xRes - 100 <= 0 ? 0 : xRes - 100;
+    // 最大y值
+    const domHeight = txtDom.value.clientHeight;
+    const maxY = window.innerHeight - domHeight;
+    let yRes = y >= maxY ? maxY : y;
+    // yRes = yRes - domHeight / 2 <= 0 ? 0 : yRes - domHeight / 2;
+    txtDom.value.style.top = yRes + "px";
+    txtDom.value.style.left = xRes + "px";
+  };
+});
+</script>
+
 <style lang="scss" scoped>
 @import "./index.scss";
 </style>

+ 122 - 0
packages/pc/src/views/Home2/components/News.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="home2-news">
+    <div class="w1100">
+      <p class="home2-news__title">
+        <img draggable="false" src="../images/text_news-min.png" />公告
+      </p>
+
+      <div class="home2-news-list">
+        <div
+          v-for="item in 19"
+          :key="item"
+          class="home2-news-item"
+          @click="dialogVisible = true"
+        >
+          <span class="home2-news-item__tag">【公告公示】</span>
+          <p class="limit-line">
+            2023年度 刘少奇同志纪念馆(刘少奇故里管理局)部门决算
+          </p>
+          <span class="home2-news-item__date">2024-09-07</span>
+        </div>
+      </div>
+
+      <div class="home2-news-pagination">
+        <el-pagination
+          layout="prev, pager, next"
+          :total="50"
+          prev-text="上一页"
+          next-text="下一页"
+        />
+      </div>
+    </div>
+  </div>
+
+  <news-dialog v-model:visible="dialogVisible" />
+</template>
+
+<script setup>
+import { ref } from "vue";
+import NewsDialog from "./NewsDialog.vue";
+
+const dialogVisible = ref(false);
+</script>
+
+<style lang="scss" scoped>
+.home2-news {
+  position: relative;
+  margin-top: 40px;
+  height: 605px;
+  background: #f5f3ec;
+
+  &::before {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 880px;
+    height: 605px;
+    background: url("../images/bg2-min.png") no-repeat left center / contain;
+  }
+  &__title {
+    display: flex;
+    align-items: flex-end;
+    margin-top: 50px;
+    padding-bottom: 20px;
+    font-size: 24px;
+    font-family: "Source Han Serif CN-Bold";
+    border-bottom: 1px solid #dddddd;
+  }
+  &-list {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    position: relative;
+    margin-top: 10px;
+    z-index: 1;
+  }
+  &-item {
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+    width: calc(50% - 50px);
+    cursor: pointer;
+
+    .limit-line {
+      flex: 1;
+    }
+    &__date,
+    &__tag {
+      white-space: nowrap;
+    }
+    &__tag {
+      color: #a99271;
+    }
+    &__date {
+      margin-left: 20px;
+    }
+  }
+  &-pagination {
+    position: relative;
+    display: flex;
+    justify-content: center;
+    margin-top: 20px;
+    z-index: 1;
+
+    :deep(.el-pagination) {
+      --el-pagination-bg-color: transparent;
+      --el-pagination-button-disabled-bg-color: transparent;
+
+      button {
+        margin: 0 36px;
+      }
+      .el-pager li {
+        &.is-active {
+          color: white;
+          border-radius: 50%;
+          background: var(--el-pagination-hover-color);
+        }
+      }
+    }
+  }
+}
+</style>

File diff suppressed because it is too large
+ 99 - 0
packages/pc/src/views/Home2/components/NewsDialog.vue


BIN
packages/pc/src/views/Home2/images/bg2-min.png


BIN
packages/pc/src/views/Home2/images/text_news-min.png


+ 3 - 0
packages/pc/src/views/Home2/index.vue

@@ -36,12 +36,15 @@
         </template>
       </rank-panel>
     </div>
+
+    <news />
   </div>
 </template>
 
 <script setup>
 import BookCard from "@/components/BookCard/index.vue";
 import RankPanel from "@/components/RankPanel/index.vue";
+import News from "./components/News.vue";
 </script>
 
 <style lang="scss" scoped>