提交 3327d190 authored 作者: 龙菲's avatar 龙菲

优化web端的导航组件

上级 66b75ffd
......@@ -17,6 +17,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"sass-embedded": "^1.89.1",
"unplugin-auto-import": "^19.1.2",
"unplugin-vue-components": "^28.5.0",
"vite": "^6.2.4",
......@@ -461,6 +462,12 @@
"node": ">=6.9.0"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
"integrity": "sha512-lut4UTvKL8tqtend0UDu7R79/n9jA7Jtxf77RNPbxtmWqfWI4qQ9bTjf7KCS4vfqLmpQbuHr1ciqJumAgJODdw==",
"dev": true
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
......@@ -1726,6 +1733,12 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buffer-builder": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/buffer-builder/-/buffer-builder-0.2.0.tgz",
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
"dev": true
},
"node_modules/bundle-name": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz",
......@@ -1799,6 +1812,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/colorjs.io": {
"version": "0.5.2",
"resolved": "https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz",
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
......@@ -2363,6 +2382,15 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
......@@ -2417,6 +2445,12 @@
"node": ">=18.18.0"
}
},
"node_modules/immutable": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.2.tgz",
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
"dev": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
......@@ -3061,6 +3095,311 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/sass-embedded": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.89.1.tgz",
"integrity": "sha512-alvGGlyYdkSXYKOfS/TTxUD0993EYOe3adIPtwCWEg037qe183p2dkYnbaRsCLJFKt+QoyRzhsrbCsK7sbR6MA==",
"dev": true,
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0",
"colorjs.io": "^0.5.0",
"immutable": "^5.0.2",
"rxjs": "^7.4.0",
"supports-color": "^8.1.1",
"sync-child-process": "^1.0.2",
"varint": "^6.0.0"
},
"bin": {
"sass": "dist/bin/sass.js"
},
"engines": {
"node": ">=16.0.0"
},
"optionalDependencies": {
"sass-embedded-android-arm": "1.89.1",
"sass-embedded-android-arm64": "1.89.1",
"sass-embedded-android-riscv64": "1.89.1",
"sass-embedded-android-x64": "1.89.1",
"sass-embedded-darwin-arm64": "1.89.1",
"sass-embedded-darwin-x64": "1.89.1",
"sass-embedded-linux-arm": "1.89.1",
"sass-embedded-linux-arm64": "1.89.1",
"sass-embedded-linux-musl-arm": "1.89.1",
"sass-embedded-linux-musl-arm64": "1.89.1",
"sass-embedded-linux-musl-riscv64": "1.89.1",
"sass-embedded-linux-musl-x64": "1.89.1",
"sass-embedded-linux-riscv64": "1.89.1",
"sass-embedded-linux-x64": "1.89.1",
"sass-embedded-win32-arm64": "1.89.1",
"sass-embedded-win32-x64": "1.89.1"
}
},
"node_modules/sass-embedded-android-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.89.1.tgz",
"integrity": "sha512-wVchZSz8zbJBwwOs9/iwco/M5G3L5BaeqwUF1EC3Gtzn1BsXYUEkJfftW2HxGl4hQz2YlpR7BY1GRN817uxADA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-android-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.89.1.tgz",
"integrity": "sha512-Je6x7uuJRGQdr5ziSJdaPA4NhBSO26BU/E55qiuMUZpjq2EWBEJPbNeugu/cWlCEmfqoVuxj37r8aEU+KG0H1g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-android-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.89.1.tgz",
"integrity": "sha512-DhWe+A4RVtpHMVaQgdzRpiczAXKPl7XhyY9USkY9Xkhv94+csTfjyuFmsUuCpKSiQDQkD+rGByfg+9yQIk/RgQ==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-android-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.89.1.tgz",
"integrity": "sha512-LTEzxTXrv3evPiHBmDMtJtO5tEprg7bvNOwYTjDEhE9ZCYdb70l+haIY0dVyhGxyeaBJlyvatjWOKEduPP3Lyw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-darwin-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.89.1.tgz",
"integrity": "sha512-7qMO4BLdIOFMMc1M+hg5iWEjPxbPlH1XTPUCwyuXYqubz6kXkdrrtJXolNAAey/0ZOE6uXk0APugm93a/veQdQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-darwin-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.89.1.tgz",
"integrity": "sha512-Jzuws3NNx4YtDdL2/skP8BvGqMBKn26XINehwLnD2kgbh0+k+vKNWt5JDomvIuZVLsK8zWrMoRkXpk4wuHdqrw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.89.1.tgz",
"integrity": "sha512-8TvFr/lh7FARtNr9mM57m7NNvtSZwnlkXtfY1D48B81Ve6GgtLqQhELNzvTcfQ0WZa0aNnVjq9XUuWLlrMDaZQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.89.1.tgz",
"integrity": "sha512-h967EV2armjV+Re+hHv7LaIzCOvV6DoFod9GJhXTdnPvilqs7DAPTUfN07wOqbzjlaGEnITZXzLsWAoZ1Z7tWQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.89.1.tgz",
"integrity": "sha512-Tl8wDL+3qFa/AhvZZBb1OvhN1SvIsRSLaPdGP8cv3VmKKVBdlLp2zedPTlcLJpR9dG/bjtGJYGX15kWHAvZ6mQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.89.1.tgz",
"integrity": "sha512-l4TrsUmE3AEPy2gDThb+OQV5xSyrb807DJbkQiFtTwvtOZAAkoVl1v2QeocW0npgKjc/W7nHMiSempJe0UcV7w==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.89.1.tgz",
"integrity": "sha512-YJVZmz032U7dv4RW3u+SJGp+DQWmYWc5fX/aXzLuoL6PPUPon1/Sseaf/5YGtcuQf8RnxZBbM2nFHFVHDJfsQw==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.89.1.tgz",
"integrity": "sha512-67ijpk87V0VlpdVTtgnfIzRkVUMtEH79nvGctvNpk0XT6v+oxoFRljFRiYItZOxb5gRZMnvtkgaz1VHVcMrhtg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.89.1.tgz",
"integrity": "sha512-SQNWy5kUvlQJUKRXFy8jS05DBik+2ERIWDxOBk+QuJYEIktlA9fKKBU8c7RkgpZFNXSXZa0W1Gy27oOFCzhhuA==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.89.1.tgz",
"integrity": "sha512-KUqGzBvTDZG6D3Pq41sCzqO1wkxM0WmxxlI7PTuVkvgciTywHf8F7mkg2alMLVZQ6APJEYtlnCGQgn4cCgYsqw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-win32-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.89.1.tgz",
"integrity": "sha512-Lk6dYA18RasZxQhShT91G7Z2o7+F9necTNJ951a5AICsSJpTbg3tTnAGB7Rvd6xB5reQSZoXfB/zXKEKwtzaow==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-win32-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.89.1.tgz",
"integrity": "sha512-YlvzrzFPHd4GKa04jMfP0t2DGJHPTm7zN4GEYtaOFqeS6BoEAUY5kBNYFy7zhwKesN3kGyU/D9rz1MfLRgGv0g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/scule": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
......@@ -3185,6 +3524,42 @@
"node": ">=16"
}
},
"node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/sync-child-process": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz",
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
"dev": true,
"dependencies": {
"sync-message-port": "^1.0.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/sync-message-port": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.1.3.tgz",
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
"dev": true,
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz",
......@@ -3223,6 +3598,12 @@
"node": ">=6"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true
},
"node_modules/turn.js": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/turn.js/-/turn.js-1.0.5.tgz",
......@@ -3426,6 +3807,12 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/varint": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
"dev": true
},
"node_modules/vite": {
"version": "6.3.2",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.2.tgz",
......@@ -3969,6 +4356,12 @@
"@babel/helper-validator-identifier": "^7.25.9"
}
},
"@bufbuild/protobuf": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
"integrity": "sha512-lut4UTvKL8tqtend0UDu7R79/n9jA7Jtxf77RNPbxtmWqfWI4qQ9bTjf7KCS4vfqLmpQbuHr1ciqJumAgJODdw==",
"dev": true
},
"@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
......@@ -4707,6 +5100,12 @@
"update-browserslist-db": "^1.1.1"
}
},
"buffer-builder": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/buffer-builder/-/buffer-builder-0.2.0.tgz",
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
"dev": true
},
"bundle-name": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz",
......@@ -4747,6 +5146,12 @@
"readdirp": "~3.6.0"
}
},
"colorjs.io": {
"version": "0.5.2",
"resolved": "https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz",
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
"dev": true
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
......@@ -5124,6 +5529,12 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
......@@ -5157,6 +5568,12 @@
"integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
"dev": true
},
"immutable": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.2.tgz",
"integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==",
"dev": true
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
......@@ -5583,6 +6000,159 @@
"integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==",
"dev": true
},
"rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"sass-embedded": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.89.1.tgz",
"integrity": "sha512-alvGGlyYdkSXYKOfS/TTxUD0993EYOe3adIPtwCWEg037qe183p2dkYnbaRsCLJFKt+QoyRzhsrbCsK7sbR6MA==",
"dev": true,
"requires": {
"@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0",
"colorjs.io": "^0.5.0",
"immutable": "^5.0.2",
"rxjs": "^7.4.0",
"sass-embedded-android-arm": "1.89.1",
"sass-embedded-android-arm64": "1.89.1",
"sass-embedded-android-riscv64": "1.89.1",
"sass-embedded-android-x64": "1.89.1",
"sass-embedded-darwin-arm64": "1.89.1",
"sass-embedded-darwin-x64": "1.89.1",
"sass-embedded-linux-arm": "1.89.1",
"sass-embedded-linux-arm64": "1.89.1",
"sass-embedded-linux-musl-arm": "1.89.1",
"sass-embedded-linux-musl-arm64": "1.89.1",
"sass-embedded-linux-musl-riscv64": "1.89.1",
"sass-embedded-linux-musl-x64": "1.89.1",
"sass-embedded-linux-riscv64": "1.89.1",
"sass-embedded-linux-x64": "1.89.1",
"sass-embedded-win32-arm64": "1.89.1",
"sass-embedded-win32-x64": "1.89.1",
"supports-color": "^8.1.1",
"sync-child-process": "^1.0.2",
"varint": "^6.0.0"
}
},
"sass-embedded-android-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.89.1.tgz",
"integrity": "sha512-wVchZSz8zbJBwwOs9/iwco/M5G3L5BaeqwUF1EC3Gtzn1BsXYUEkJfftW2HxGl4hQz2YlpR7BY1GRN817uxADA==",
"dev": true,
"optional": true
},
"sass-embedded-android-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.89.1.tgz",
"integrity": "sha512-Je6x7uuJRGQdr5ziSJdaPA4NhBSO26BU/E55qiuMUZpjq2EWBEJPbNeugu/cWlCEmfqoVuxj37r8aEU+KG0H1g==",
"dev": true,
"optional": true
},
"sass-embedded-android-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.89.1.tgz",
"integrity": "sha512-DhWe+A4RVtpHMVaQgdzRpiczAXKPl7XhyY9USkY9Xkhv94+csTfjyuFmsUuCpKSiQDQkD+rGByfg+9yQIk/RgQ==",
"dev": true,
"optional": true
},
"sass-embedded-android-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.89.1.tgz",
"integrity": "sha512-LTEzxTXrv3evPiHBmDMtJtO5tEprg7bvNOwYTjDEhE9ZCYdb70l+haIY0dVyhGxyeaBJlyvatjWOKEduPP3Lyw==",
"dev": true,
"optional": true
},
"sass-embedded-darwin-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.89.1.tgz",
"integrity": "sha512-7qMO4BLdIOFMMc1M+hg5iWEjPxbPlH1XTPUCwyuXYqubz6kXkdrrtJXolNAAey/0ZOE6uXk0APugm93a/veQdQ==",
"dev": true,
"optional": true
},
"sass-embedded-darwin-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.89.1.tgz",
"integrity": "sha512-Jzuws3NNx4YtDdL2/skP8BvGqMBKn26XINehwLnD2kgbh0+k+vKNWt5JDomvIuZVLsK8zWrMoRkXpk4wuHdqrw==",
"dev": true,
"optional": true
},
"sass-embedded-linux-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.89.1.tgz",
"integrity": "sha512-8TvFr/lh7FARtNr9mM57m7NNvtSZwnlkXtfY1D48B81Ve6GgtLqQhELNzvTcfQ0WZa0aNnVjq9XUuWLlrMDaZQ==",
"dev": true,
"optional": true
},
"sass-embedded-linux-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.89.1.tgz",
"integrity": "sha512-h967EV2armjV+Re+hHv7LaIzCOvV6DoFod9GJhXTdnPvilqs7DAPTUfN07wOqbzjlaGEnITZXzLsWAoZ1Z7tWQ==",
"dev": true,
"optional": true
},
"sass-embedded-linux-musl-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.89.1.tgz",
"integrity": "sha512-Tl8wDL+3qFa/AhvZZBb1OvhN1SvIsRSLaPdGP8cv3VmKKVBdlLp2zedPTlcLJpR9dG/bjtGJYGX15kWHAvZ6mQ==",
"dev": true,
"optional": true
},
"sass-embedded-linux-musl-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.89.1.tgz",
"integrity": "sha512-l4TrsUmE3AEPy2gDThb+OQV5xSyrb807DJbkQiFtTwvtOZAAkoVl1v2QeocW0npgKjc/W7nHMiSempJe0UcV7w==",
"dev": true,
"optional": true
},
"sass-embedded-linux-musl-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.89.1.tgz",
"integrity": "sha512-YJVZmz032U7dv4RW3u+SJGp+DQWmYWc5fX/aXzLuoL6PPUPon1/Sseaf/5YGtcuQf8RnxZBbM2nFHFVHDJfsQw==",
"dev": true,
"optional": true
},
"sass-embedded-linux-musl-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.89.1.tgz",
"integrity": "sha512-67ijpk87V0VlpdVTtgnfIzRkVUMtEH79nvGctvNpk0XT6v+oxoFRljFRiYItZOxb5gRZMnvtkgaz1VHVcMrhtg==",
"dev": true,
"optional": true
},
"sass-embedded-linux-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.89.1.tgz",
"integrity": "sha512-SQNWy5kUvlQJUKRXFy8jS05DBik+2ERIWDxOBk+QuJYEIktlA9fKKBU8c7RkgpZFNXSXZa0W1Gy27oOFCzhhuA==",
"dev": true,
"optional": true
},
"sass-embedded-linux-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.89.1.tgz",
"integrity": "sha512-KUqGzBvTDZG6D3Pq41sCzqO1wkxM0WmxxlI7PTuVkvgciTywHf8F7mkg2alMLVZQ6APJEYtlnCGQgn4cCgYsqw==",
"dev": true,
"optional": true
},
"sass-embedded-win32-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.89.1.tgz",
"integrity": "sha512-Lk6dYA18RasZxQhShT91G7Z2o7+F9necTNJ951a5AICsSJpTbg3tTnAGB7Rvd6xB5reQSZoXfB/zXKEKwtzaow==",
"dev": true,
"optional": true
},
"sass-embedded-win32-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.89.1.tgz",
"integrity": "sha512-YlvzrzFPHd4GKa04jMfP0t2DGJHPTm7zN4GEYtaOFqeS6BoEAUY5kBNYFy7zhwKesN3kGyU/D9rz1MfLRgGv0g==",
"dev": true,
"optional": true
},
"scule": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
......@@ -5670,6 +6240,30 @@
"copy-anything": "^3.0.2"
}
},
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"sync-child-process": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz",
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
"dev": true,
"requires": {
"sync-message-port": "^1.0.0"
}
},
"sync-message-port": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.1.3.tgz",
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
"dev": true
},
"tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz",
......@@ -5695,6 +6289,12 @@
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true
},
"tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true
},
"turn.js": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/turn.js/-/turn.js-1.0.5.tgz",
......@@ -5815,6 +6415,12 @@
"picocolors": "^1.1.1"
}
},
"varint": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
"dev": true
},
"vite": {
"version": "6.3.2",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.2.tgz",
......
......@@ -18,6 +18,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"sass-embedded": "^1.89.1",
"unplugin-auto-import": "^19.1.2",
"unplugin-vue-components": "^28.5.0",
"vite": "^6.2.4",
......
......@@ -9,10 +9,12 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
BookReader: typeof import('./components/BookReader.vue')['default']
ElBacktop: typeof import('element-plus/es')['ElBacktop']
ElButton: typeof import('element-plus/es')['ElButton']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElIcon: typeof import('element-plus/es')['ElIcon']
FileUpload: typeof import('./components/FileUpload.vue')['default']
Guide: typeof import('./components/Guide.vue')['default']
IconCommunity: typeof import('./components/icons/IconCommunity.vue')['default']
IconDocumentation: typeof import('./components/icons/IconDocumentation.vue')['default']
IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default']
......
<template>
<div class="book-reader"
<div
class="book-reader"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd">
<div v-if="loading" class="loading-overlay">
@touchend="handleTouchEnd"
>
<!-- <div v-if="loading" class="loading-overlay">
<div class="loader"></div>
<p>正在加载图片...</p>
</div>
</div> -->
<div class="magazine-viewport">
<div ref="magazine" class="magazine">
<div v-for="(page, index) in processedPages" :key="index" :class="['page', `p${index + 1}`, {
'hard': !page.isWide && index % 2 === 0,
'odd': !page.isWide && index % 2 !== 0,
'even': !page.isWide && index % 2 === 1,
'mobile': isMobile
}]">
<img
<div
v-for="(page, index) in processedPages"
:key="index"
:class="[
'page',
`p${index + 1}`,
{
hard: !page.isWide && index % 2 === 0,
odd: !page.isWide && index % 2 !== 0,
even: !page.isWide && index % 2 === 1,
mobile: isMobile,
},
]"
>
<img
:data-page-index="index"
:src="page.src"
:alt="`第 ${page.page_num} 页`"
......@@ -24,13 +34,26 @@
/>
<!-- 添加小图叠加层 -->
<div v-if="page.images && page.images.length > 0" class="small-images-overlay">
<div v-for="(smallImage, imgIndex) in page.images" :key="imgIndex" class="small-image-container" :style="{
left: `${smallImage.position.x1 * 100}%`,
top: `${smallImage.position.y1 * 100}%`,
width: `${(smallImage.position.x2 - smallImage.position.x1) * 100}%`,
height: `${(smallImage.position.y2 - smallImage.position.y1) * 100}%`
}" @click="handleSmallImageClick(smallImage, page.page_num)">
<div
v-if="page.images && page.images.length > 0"
class="small-images-overlay"
>
<div
v-for="(smallImage, imgIndex) in page.images"
:key="imgIndex"
class="small-image-container"
:style="{
left: `${smallImage.position.x1 * 100}%`,
top: `${smallImage.position.y1 * 100}%`,
width: `${
(smallImage.position.x2 - smallImage.position.x1) * 100
}%`,
height: `${
(smallImage.position.y2 - smallImage.position.y1) * 100
}%`,
}"
@click="handleSmallImageClick(smallImage, page.page_num)"
>
<!-- <img :src="smallImage.url" :alt="`小图 ${imgIndex + 1}`" class="small-image" /> -->
</div>
</div>
......@@ -40,281 +63,298 @@
<div class="controls" :class="{ 'mobile-controls': isMobile }">
<el-button class="control-button" @click="previous" circle>
<el-icon><ArrowLeft /></el-icon>
<el-icon>
<ArrowLeft />
</el-icon>
</el-button>
<el-button class="control-button" @click="next" circle>
<el-icon><ArrowRight /></el-icon>
<el-icon>
<ArrowRight />
</el-icon>
</el-button>
<el-button class="control-button" @click="zoomIn" circle>
<el-icon><ZoomIn /></el-icon>
<el-icon>
<ZoomIn />
</el-icon>
</el-button>
<el-button class="control-button" @click="zoomOut" circle>
<el-icon><ZoomOut /></el-icon>
<el-icon>
<ZoomOut />
</el-icon>
</el-button>
<el-button class="control-button" @click="showDirectory = true" circle>
<el-icon><Menu /></el-icon>
<el-button class="control-button" @click="showGuide" circle>
<el-icon>
<Menu />
</el-icon>
</el-button>
</div>
<div v-if="showExitMessage" class="exit-message">
按 ESC 键退出阅读
</div>
<div v-if="showExitMessage" class="exit-message">按 ESC 键退出阅读</div>
<!-- 替换为 vue-easy-lightbox 预览组件 -->
<vue-easy-lightbox :visible="showViewer" :imgs="previewImages" :index="currentImageIndex"
@hide="showViewer = false" />
<vue-easy-lightbox
:visible="showViewer"
:imgs="previewImages"
:index="currentImageIndex"
@hide="showViewer = false"
/>
<div class="mobile-gesture-hint" v-if="isMobile">
左右滑动切换页面
</div>
<div class="mobile-gesture-hint" v-if="isMobile">左右滑动切换页面</div>
<!-- Replace directory modal with Element Plus dialog -->
<el-dialog
v-model="showDirectory"
title="目录"
width="90%"
:close-on-click-modal="true"
:close-on-press-escape="true"
class="directory-dialog"
>
<div class="thumbnail-grid">
<div v-for="(img, index) in directoryImages" :key="index"
class="thumbnail-item" @click="goToPage(img.pageNum)">
<img :src="img.src" :alt="`第 ${img.pageNum} 页`" />
<span class="page-number">{{ img.pageNum }}</span>
</div>
</div>
</el-dialog>
<Guide
:directoryImages="directoryImages"
ref="guideRef"
:currentPage="currentPage"
@goToPage="goToPage"
></Guide>
</div>
</template>
<script setup>
import $ from 'jquery'
import 'turn.js'
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'
import { ArrowLeft, ArrowRight, ZoomIn, ZoomOut, Menu } from '@element-plus/icons-vue'
import VueEasyLightbox from 'vue-easy-lightbox'
import $ from "jquery";
import "turn.js";
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from "vue";
import {
ArrowLeft,
ArrowRight,
ZoomIn,
ZoomOut,
Menu,
} from "@element-plus/icons-vue";
import VueEasyLightbox from "vue-easy-lightbox";
import Guide from "./Guide.vue";
const props = defineProps({
pages: {
type: Array,
default: () => [],
required: true
}
})
const magazine = ref(null)
const loading = ref(true)
const showExitMessage = ref(false)
const zoomLevel = ref(1)
const isInitialized = ref(false)
const imageMetadata = ref([])
const imageObserver = ref(null)
const loadingQueue = ref(new Set())
const maxConcurrentLoads = 5
const imageCache = ref(new Map())
const loadingTimeouts = ref(new Map())
const isMobile = ref(false)
const touchStartX = ref(0)
const touchStartY = ref(0)
required: true,
},
});
const magazine = ref(null);
const loading = ref(true);
const showExitMessage = ref(false);
const zoomLevel = ref(1);
const isInitialized = ref(false);
const imageMetadata = ref([]);
const imageObserver = ref(null);
const loadingQueue = ref(new Set());
const maxConcurrentLoads = 5;
const imageCache = ref(new Map());
const loadingTimeouts = ref(new Map());
const isMobile = ref(false);
const touchStartX = ref(0);
const touchStartY = ref(0);
// 修改预览相关的状态
const showViewer = ref(false)
const currentImageIndex = ref(0)
const previewImages = ref([])
const showViewer = ref(false);
const currentImageIndex = ref(0);
const previewImages = ref([]);
// 将 processedPages 改为普通数据属性
const processedPages = ref([])
const processedPages = ref([]);
// Add new refs for sound and directory
const pageTurnSound = ref(null)
const showDirectory = ref(false)
const directoryImages = ref([])
const pageTurnSound = ref(null);
const directoryImages = ref([]);
const guideRef = ref(null);
const currentPage = ref(0);
// 检测是否为移动设备
const checkMobile = () => {
isMobile.value = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
}
isMobile.value =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
};
// 处理触摸事件
const handleTouchStart = (e) => {
if (!isMobile.value) return
touchStartX.value = e.touches[0].clientX
touchStartY.value = e.touches[0].clientY
}
if (!isMobile.value) return;
touchStartX.value = e.touches[0].clientX;
touchStartY.value = e.touches[0].clientY;
};
const handleTouchEnd = (e) => {
if (!isMobile.value) return
const touchEndX = e.changedTouches[0].clientX
const touchEndY = e.changedTouches[0].clientY
const deltaX = touchEndX - touchStartX.value
const deltaY = touchEndY - touchStartY.value
if (!isMobile.value) return;
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const deltaX = touchEndX - touchStartX.value;
const deltaY = touchEndY - touchStartY.value;
// 如果水平滑动距离大于垂直滑动距离,且大于50px,则触发翻页
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
if (deltaX > 0) {
previous()
previous();
} else {
next()
next();
}
}
}
};
// 修改处理页面的函数,只处理必要的属性
const processPages = (pages) => {
return pages.map((item, index) => ({
src: '', // 初始不设置src
src: "", // 初始不设置src
originalIndex: index,
page_num: item.page_num,
images: item.images || [],
isWide: false, // 初始设置为false
isLoaded: false,
url: item.page_url // 保存原始URL
}))
}
url: item.page_url, // 保存原始URL
}));
};
// 修改图片加载函数
const loadImage = async (pageIndex) => {
const page = processedPages.value[pageIndex]
if (!page || page.isLoaded) return true
const page = processedPages.value[pageIndex];
if (!page || page.isLoaded) return true;
const src = page.url
const src = page.url;
if (imageCache.value.has(src)) {
page.isLoaded = true
page.src = src
return true
page.isLoaded = true;
page.src = src;
return true;
}
if (loadingQueue.value.size >= maxConcurrentLoads) {
await new Promise(resolve => setTimeout(resolve, 50))
return loadImage(pageIndex)
await new Promise((resolve) => setTimeout(resolve, 50));
return loadImage(pageIndex);
}
loadingQueue.value.add(pageIndex)
loadingQueue.value.add(pageIndex);
const timeoutId = setTimeout(() => {
loadingQueue.value.delete(pageIndex)
loadingTimeouts.value.delete(pageIndex)
}, 5000)
loadingTimeouts.value.set(pageIndex, timeoutId)
loadingQueue.value.delete(pageIndex);
loadingTimeouts.value.delete(pageIndex);
}, 5000);
loadingTimeouts.value.set(pageIndex, timeoutId);
try {
const img = new Image()
const img = new Image();
await new Promise((resolve, reject) => {
img.onload = () => {
clearTimeout(timeoutId)
loadingTimeouts.value.delete(pageIndex)
imageCache.value.set(src, true)
page.isLoaded = true
page.src = src
resolve()
}
clearTimeout(timeoutId);
loadingTimeouts.value.delete(pageIndex);
imageCache.value.set(src, true);
page.isLoaded = true;
page.src = src;
resolve();
};
img.onerror = () => {
clearTimeout(timeoutId)
loadingTimeouts.value.delete(pageIndex)
reject(new Error('Image load failed'))
}
img.src = src
})
return true
clearTimeout(timeoutId);
loadingTimeouts.value.delete(pageIndex);
reject(new Error("Image load failed"));
};
img.src = src;
});
return true;
} catch (error) {
return false
return false;
} finally {
loadingQueue.value.delete(pageIndex)
loadingQueue.value.delete(pageIndex);
}
}
};
// 修改加载可见页面的函数
const loadVisiblePages = async (currentPage) => {
if (!magazine.value) return
if (!magazine.value) return;
const $magazine = $(magazine.value)
const totalPages = $magazine.turn('pages')
const $magazine = $(magazine.value);
const totalPages = $magazine.turn("pages");
// 只加载当前页和下一页
const startPage = currentPage
const endPage = Math.min(totalPages, currentPage + 1)
const startPage = currentPage;
const endPage = Math.min(totalPages, currentPage + 1);
const loadTasks = []
const loadTasks = [];
for (let i = startPage; i <= endPage; i++) {
const pageIndex = i - 1
if (pageIndex >= 0 && pageIndex < processedPages.value.length && !processedPages.value[pageIndex].isLoaded) {
loadTasks.push(loadImage(pageIndex))
const pageIndex = i - 1;
if (
pageIndex >= 0 &&
pageIndex < processedPages.value.length &&
!processedPages.value[pageIndex].isLoaded
) {
loadTasks.push(loadImage(pageIndex));
}
}
try {
await Promise.all(loadTasks)
await Promise.all(loadTasks);
} catch (error) {
console.error('加载页面失败:', error)
console.error("加载页面失败:", error);
}
}
};
// 修改初始化函数
const initBook = async () => {
if (!magazine.value || !processedPages.value.length) return
if (!magazine.value || !processedPages.value.length) return;
destroyTurn()
checkMobile()
destroyTurn();
checkMobile();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
// 移动端使用不同的尺寸计算
let pageWidth, pageHeight
let pageWidth, pageHeight;
if (isMobile.value) {
// 移动端:宽度等于设备宽度减去边距
pageWidth = viewportWidth - 20 // 左右各留10px边距
pageWidth = viewportWidth - 20; // 左右各留10px边距
// 高度按比例计算(假设标准比例为 4:3)
pageHeight = (pageWidth * 4) / 3
pageHeight = (pageWidth * 4) / 3;
// 确保高度不超过视口高度
if (pageHeight > viewportHeight - 100) { // 上下各留50px边距
pageHeight = viewportHeight - 100
if (pageHeight > viewportHeight - 100) {
// 上下各留50px边距
pageHeight = viewportHeight - 100;
// 重新计算宽度以保持比例
pageWidth = (pageHeight * 3) / 4
pageWidth = (pageHeight * 3) / 4;
}
} else {
// 桌面端保持原有逻辑
pageWidth = Math.min(600, viewportWidth / 2 - 50)
pageHeight = Math.min(800, viewportHeight - 100)
pageWidth = Math.min(600, viewportWidth / 2 - 50);
pageHeight = Math.min(800, viewportHeight - 100);
}
await nextTick()
await nextTick();
try {
const $magazine = $(magazine.value)
const $viewport = $('.magazine-viewport')
const $magazine = $(magazine.value);
const $viewport = $(".magazine-viewport");
$viewport.css({
'overflow': 'hidden',
'position': 'relative',
'width': '100%',
'height': '100%'
})
overflow: "hidden",
position: "relative",
width: "100%",
height: "100%",
});
$magazine.children().each(function () {
const $page = $(this)
const $page = $(this);
$page.css({
position: 'absolute',
overflow: 'hidden',
position: "absolute",
overflow: "hidden",
width: pageWidth,
height: pageHeight
})
height: pageHeight,
});
const $img = $page.find('img')
const $img = $page.find("img");
if ($img.length) {
$img.css({
width: '100%',
height: '100%',
objectFit: 'contain'
})
width: "100%",
height: "100%",
objectFit: "contain",
});
}
})
});
// 移动端使用单页显示
$magazine.turn({
width: isMobile.value ? pageWidth : pageWidth * 2,
height: pageHeight,
display: isMobile.value ? 'single' : 'double',
display: isMobile.value ? "single" : "double",
acceleration: false,
gradients: true,
elevation: 50,
......@@ -323,375 +363,382 @@ const initBook = async () => {
page: 1,
when: {
turning: async (event, page, view) => {
loading.value = true
await loadVisiblePages(page)
loading.value = true;
await loadVisiblePages(page);
},
turned: async (event, page, view) => {
await loadVisiblePages(page)
loading.value = false
}
}
})
isInitialized.value = true
await loadVisiblePages(1)
currentPage.value = page; // 更新当前页码
await loadVisiblePages(page);
loading.value = false;
},
},
});
isInitialized.value = true;
await loadVisiblePages(1);
} catch (error) {
console.error('Turn.js initialization error:', error)
console.error("Turn.js initialization error:", error);
}
}
};
// 修改加载图片函数
const loadImages = async () => {
loading.value = true
loading.value = true;
try {
await initBook()
await initBook();
} catch (error) {
console.error('加载图片失败:', error)
console.error("加载图片失败:", error);
} finally {
loading.value = false
loading.value = false;
}
}
};
// 监听 props.pages 的变化
watch(() => props.pages, (newPages) => {
if (newPages && newPages.length > 0) {
processedPages.value = processPages(newPages)
loadImages()
}
}, { immediate: true })
watch(
() => props.pages,
(newPages) => {
if (newPages && newPages.length > 0) {
processedPages.value = processPages(newPages);
loadImages();
}
},
{ immediate: true }
);
const checkImageDimensions = async (src) => {
return new Promise((resolve) => {
const img = new Image()
const img = new Image();
img.onload = () => {
// 如果宽高比大于 1.8,认为是宽图
const isWide = (img.width / img.height) > 1.8
const isWide = img.width / img.height > 1.8;
resolve({
width: img.width,
height: img.height,
isWide
})
}
isWide,
});
};
img.onerror = () => {
resolve({ width: 0, height: 0, isWide: false })
}
img.src = src
})
}
resolve({ width: 0, height: 0, isWide: false });
};
img.src = src;
});
};
const destroyTurn = () => {
if (magazine.value && isInitialized.value) {
try {
const $magazine = $(magazine.value)
const $magazine = $(magazine.value);
// 移除所有 turn.js 相关的事件和数据
$magazine.off('.turn')
$magazine.removeData('turn')
isInitialized.value = false
$magazine.off(".turn");
$magazine.removeData("turn");
isInitialized.value = false;
} catch (error) {
console.error('Error destroying turn.js:', error)
console.error("Error destroying turn.js:", error);
}
}
}
};
// 初始化 Intersection Observer
const initImageObserver = () => {
imageObserver.value = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
const pageIndex = parseInt(img.dataset.pageIndex)
if (!processedPages.value[pageIndex].isLoaded) {
img.src = processedPages.value[pageIndex].src
processedPages.value[pageIndex].isLoaded = true
imageObserver.value = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
const pageIndex = parseInt(img.dataset.pageIndex);
if (!processedPages.value[pageIndex].isLoaded) {
img.src = processedPages.value[pageIndex].src;
processedPages.value[pageIndex].isLoaded = true;
}
}
}
})
}, {
root: null,
rootMargin: '50px',
threshold: 0.1
})
}
});
},
{
root: null,
rootMargin: "50px",
threshold: 0.1,
}
);
};
// 修改图片错误处理函数
const handleImageError = async (index) => {
const src = processedPages.value[index].src
const src = processedPages.value[index].src;
// 清除超时
if (loadingTimeouts.value.has(index)) {
clearTimeout(loadingTimeouts.value.get(index))
loadingTimeouts.value.delete(index)
clearTimeout(loadingTimeouts.value.get(index));
loadingTimeouts.value.delete(index);
}
// 尝试重新加载
const retryCount = 3
const retryCount = 3;
for (let i = 0; i < retryCount; i++) {
try {
const success = await loadImage(index)
const success = await loadImage(index);
if (success) {
return
return;
}
} catch (error) {
// 忽略错误
}
// 递增重试延迟
const delay = 1000 * (i + 1)
await new Promise(resolve => setTimeout(resolve, delay))
const delay = 1000 * (i + 1);
await new Promise((resolve) => setTimeout(resolve, delay));
}
// 如果所有重试都失败,使用占位图
const $img = $(magazine.value).find(`.p${index + 1} img`)
const $img = $(magazine.value).find(`.p${index + 1} img`);
if ($img.length) {
$img[0].src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiM5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lvIDlp4vmlbDmja7lupM8L3RleHQ+PC9zdmc+'
$img[0].src =
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiM5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj7lvIDlp4vmlbDmja7lupM8L3RleHQ+PC9zdmc+";
}
}
};
const next = () => {
if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value)
const currentPage = $magazine.turn('page')
const nextPage = currentPage + 1
const $magazine = $(magazine.value);
const currentPage = $magazine.turn("page");
const nextPage = currentPage + 1;
// 预加载下一页
loadVisiblePages(nextPage)
loadVisiblePages(nextPage);
// 延迟执行翻页,确保图片已加载
setTimeout(() => {
$magazine.turn('next', { duration: 1500 })
playPageTurnSound()
}, 100)
$magazine.turn("next", { duration: 1500 });
playPageTurnSound();
}, 100);
}
}
};
const previous = () => {
if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value)
const currentPage = $magazine.turn('page')
const prevPage = currentPage - 1
const $magazine = $(magazine.value);
const currentPage = $magazine.turn("page");
const prevPage = currentPage - 1;
// 预加载上一页
loadVisiblePages(prevPage)
loadVisiblePages(prevPage);
// 延迟执行翻页,确保图片已加载
setTimeout(() => {
$magazine.turn('previous', { duration: 1500 })
playPageTurnSound()
}, 100)
$magazine.turn("previous", { duration: 1500 });
playPageTurnSound();
}, 100);
}
}
};
const zoomIn = () => {
zoomLevel.value = Math.min(zoomLevel.value + 0.2, 2)
updateZoom()
}
zoomLevel.value = Math.min(zoomLevel.value + 0.2, 2);
updateZoom();
};
const zoomOut = () => {
zoomLevel.value = Math.max(zoomLevel.value - 0.2, 0.5)
updateZoom()
}
zoomLevel.value = Math.max(zoomLevel.value - 0.2, 0.5);
updateZoom();
};
const updateZoom = () => {
if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value)
const currentPage = $magazine.turn('page')
const $magazine = $(magazine.value);
const currentPage = $magazine.turn("page");
// 保存当前可见页面的图片状态
const visiblePages = []
const totalPages = $magazine.turn('pages')
const startPage = Math.max(1, currentPage - 2)
const endPage = Math.min(totalPages, currentPage + 2)
const visiblePages = [];
const totalPages = $magazine.turn("pages");
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
for (let i = startPage; i <= endPage; i++) {
const $page = $magazine.find(`.p${i}`)
const $img = $page.find('img')
const $page = $magazine.find(`.p${i}`);
const $img = $page.find("img");
if ($img.length) {
visiblePages.push({
page: i,
src: $img[0].src,
isLoaded: $img[0].complete
})
isLoaded: $img[0].complete,
});
}
}
// 计算新的尺寸
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
const baseWidth = Math.min(600, viewportWidth / 2 - 50)
const baseHeight = Math.min(800, viewportHeight - 100)
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const baseWidth = Math.min(600, viewportWidth / 2 - 50);
const baseHeight = Math.min(800, viewportHeight - 100);
// 应用缩放
const scaledWidth = baseWidth * zoomLevel.value
const scaledHeight = baseHeight * zoomLevel.value
const scaledWidth = baseWidth * zoomLevel.value;
const scaledHeight = baseHeight * zoomLevel.value;
// 直接更新 turn.js 尺寸
$magazine.turn('size', scaledWidth * 2, scaledHeight)
$magazine.turn("size", scaledWidth * 2, scaledHeight);
// 更新页面尺寸
$magazine.children().each(function () {
const $page = $(this)
const $page = $(this);
$page.css({
width: scaledWidth,
height: scaledHeight
})
})
height: scaledHeight,
});
});
// 恢复已加载的图片
visiblePages.forEach(page => {
const $page = $magazine.find(`.p${page.page}`)
const $img = $page.find('img')
visiblePages.forEach((page) => {
const $page = $magazine.find(`.p${page.page}`);
const $img = $page.find("img");
if ($img.length && page.isLoaded) {
$img[0].src = page.src
$img[0].src = page.src;
}
})
});
// 更新视口样式
const $viewport = $('.magazine-viewport')
const $viewport = $(".magazine-viewport");
$viewport.css({
'width': '100%',
'height': '100%',
'overflow': 'auto'
})
width: "100%",
height: "100%",
overflow: "auto",
});
}
}
};
const handleKeyDown = (e) => {
switch (e.key) {
case 'ArrowRight':
next()
break
case 'ArrowLeft':
previous()
break
case 'Escape':
showExitMessage.value = true
case "ArrowRight":
next();
break;
case "ArrowLeft":
previous();
break;
case "Escape":
showExitMessage.value = true;
setTimeout(() => {
showExitMessage.value = false
}, 2000)
break
showExitMessage.value = false;
}, 2000);
break;
}
}
};
const handleResize = async () => {
if (magazine.value && isInitialized.value) {
try {
const $magazine = $(magazine.value)
const currentPage = $magazine.turn('page')
const currentZoom = zoomLevel.value
const $magazine = $(magazine.value);
const currentPage = $magazine.turn("page");
const currentZoom = zoomLevel.value;
// 保存当前可见页面的图片状态
const visiblePages = []
const totalPages = $magazine.turn('pages')
const startPage = Math.max(1, currentPage - 2)
const endPage = Math.min(totalPages, currentPage + 2)
const visiblePages = [];
const totalPages = $magazine.turn("pages");
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
for (let i = startPage; i <= endPage; i++) {
const $page = $magazine.find(`.p${i}`)
const $img = $page.find('img')
const $page = $magazine.find(`.p${i}`);
const $img = $page.find("img");
if ($img.length) {
visiblePages.push({
page: i,
src: $img[0].src,
isLoaded: $img[0].complete
})
isLoaded: $img[0].complete,
});
}
}
// 计算新的尺寸
const viewportWidth = window.innerWidth
const viewportHeight = window.innerHeight
const baseWidth = Math.min(600, viewportWidth / 2 - 50)
const baseHeight = Math.min(800, viewportHeight - 100)
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const baseWidth = Math.min(600, viewportWidth / 2 - 50);
const baseHeight = Math.min(800, viewportHeight - 100);
// 应用缩放
const scaledWidth = baseWidth * currentZoom
const scaledHeight = baseHeight * currentZoom
const scaledWidth = baseWidth * currentZoom;
const scaledHeight = baseHeight * currentZoom;
// 直接更新 turn.js 尺寸
$magazine.turn('size', scaledWidth * 2, scaledHeight)
$magazine.turn("size", scaledWidth * 2, scaledHeight);
// 更新页面尺寸
$magazine.children().each(function () {
const $page = $(this)
const $page = $(this);
$page.css({
width: scaledWidth,
height: scaledHeight
})
})
height: scaledHeight,
});
});
// 恢复已加载的图片
visiblePages.forEach(page => {
const $page = $magazine.find(`.p${page.page}`)
const $img = $page.find('img')
visiblePages.forEach((page) => {
const $page = $magazine.find(`.p${page.page}`);
const $img = $page.find("img");
if ($img.length && page.isLoaded) {
$img[0].src = page.src
$img[0].src = page.src;
}
})
});
// 更新视口样式
const $viewport = $('.magazine-viewport')
const $viewport = $(".magazine-viewport");
$viewport.css({
'width': '100%',
'height': '100%',
'overflow': 'auto'
})
width: "100%",
height: "100%",
overflow: "auto",
});
} catch (error) {
console.error('Error handling resize:', error)
console.error("Error handling resize:", error);
}
}
}
};
// 添加防抖处理
let resizeTimeout
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout)
let resizeTimeout;
window.addEventListener("resize", () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
handleResize()
}, 250)
})
handleResize();
}, 250);
});
onMounted(async () => {
await loadImages()
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('resize', handleResize)
await loadImages();
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("resize", handleResize);
// Initialize page turn sound
pageTurnSound.value = new Audio('/src/assets/sounds/page-turn.mp3')
pageTurnSound.value.load()
pageTurnSound.value = new Audio("/src/assets/sounds/page-turn.mp3");
pageTurnSound.value.load();
// Load directory images
loadDirectoryImages()
})
loadDirectoryImages();
});
onUnmounted(() => {
// 清除所有超时
loadingTimeouts.value.forEach(timeoutId => clearTimeout(timeoutId))
loadingTimeouts.value.clear()
imageCache.value.clear()
loadingQueue.value.clear()
loadingTimeouts.value.forEach((timeoutId) => clearTimeout(timeoutId));
loadingTimeouts.value.clear();
imageCache.value.clear();
loadingQueue.value.clear();
if (imageObserver.value) {
imageObserver.value.disconnect()
imageObserver.value.disconnect();
}
destroyTurn()
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('resize', handleResize)
})
destroyTurn();
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("resize", handleResize);
});
// 修改小图点击事件处理函数
const handleSmallImageClick = (smallImage, pageNum) => {
// 获取当前页面所有小图的URL
const currentPage = props.pages.find(page => page.page_num === pageNum);
const currentPage = props.pages.find((page) => page.page_num === pageNum);
if (currentPage && currentPage.images) {
previewImages.value = currentPage.images.map(img => ({
previewImages.value = currentPage.images.map((img) => ({
src: img.url,
title: `第 ${pageNum} 页`
title: `第 ${pageNum} 页`,
}));
// 设置当前点击的图片索引
currentImageIndex.value = currentPage.images.findIndex(img => img.url === smallImage.url);
currentImageIndex.value = currentPage.images.findIndex(
(img) => img.url === smallImage.url
);
// 显示预览组件
showViewer.value = true;
......@@ -701,29 +748,34 @@ const handleSmallImageClick = (smallImage, pageNum) => {
// Add new methods for sound and directory
const playPageTurnSound = () => {
if (pageTurnSound.value) {
pageTurnSound.value.currentTime = 0
pageTurnSound.value.play().catch(e => console.log('Audio play failed:', e))
pageTurnSound.value.currentTime = 0;
pageTurnSound.value
.play()
.catch((e) => console.log("Audio play failed:", e));
}
}
};
const generateThumbnailUrl = (url) => {
return url.replace(/(\.[^.]+)$/, '_low$1')
}
return url.replace(/(\.[^.]+)$/, "_low$1");
};
const loadDirectoryImages = () => {
directoryImages.value = props.pages.map(page => ({
directoryImages.value = props.pages.map((page) => ({
src: generateThumbnailUrl(page.page_url),
pageNum: page.page_num
}))
}
pageNum: page.page_num,
}));
};
const goToPage = (pageNum) => {
if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value)
$magazine.turn('page', pageNum)
showDirectory.value = false
const $magazine = $(magazine.value);
$magazine.turn("page", pageNum);
}
}
};
const showGuide = () => {
guideRef.value.show();
};
</script>
<style scoped>
......@@ -735,7 +787,8 @@ const goToPage = (pageNum) => {
align-items: center;
background-color: #333;
position: relative;
background-image: url('../assets/images/bg2.jpg');
background-image: url("../assets/images/2.jpg");
background-size: 100% 100%;
touch-action: pan-y pinch-zoom;
}
......@@ -762,16 +815,18 @@ const goToPage = (pageNum) => {
}
.magazine::after {
content: '';
content: "";
position: absolute;
top: 0;
left: 50%;
width: 1px;
height: 100%;
background: linear-gradient(to bottom,
rgba(0, 0, 0, 0.1) 0%,
rgba(0, 0, 0, 0.2) 50%,
rgba(0, 0, 0, 0.1) 100%);
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.1) 0%,
rgba(0, 0, 0, 0.2) 50%,
rgba(0, 0, 0, 0.1) 100%
);
z-index: 10;
pointer-events: none;
}
......@@ -791,7 +846,7 @@ const goToPage = (pageNum) => {
}
.page:before {
content: '';
content: "";
position: absolute;
width: 100px;
height: 100px;
......@@ -929,7 +984,7 @@ const goToPage = (pageNum) => {
/* 确保宽图页面的阴影效果 */
.page.wide-page::after {
content: '';
content: "";
position: absolute;
top: 0;
left: 0;
......@@ -942,7 +997,7 @@ const goToPage = (pageNum) => {
/* 角落悬停效果 */
.page.hover::before {
content: '';
content: "";
position: absolute;
width: 100px;
height: 100px;
......@@ -1143,6 +1198,8 @@ const goToPage = (pageNum) => {
background-color: rgba(255, 255, 255, 0.9);
border: none;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
font-size: 24px;
/* 增大字体大小 */
}
.mobile-controls .el-icon {
......@@ -1176,32 +1233,6 @@ const goToPage = (pageNum) => {
font-weight: 600;
}
.thumbnail-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(25vw, 1fr));
gap: 10px;
max-height: 60vh;
overflow-y: auto;
}
.thumbnail-item {
position: relative;
cursor: pointer;
transition: transform 0.2s;
border-radius: 4px;
overflow: hidden;
}
.thumbnail-item:hover {
transform: scale(1.05);
}
.thumbnail-item img {
width: 100%;
height: auto;
display: block;
}
.page-number {
position: absolute;
bottom: 0;
......@@ -1219,13 +1250,13 @@ const goToPage = (pageNum) => {
.controls:not(.mobile-controls) {
display: none;
}
.mobile-controls {
display: flex;
}
.thumbnail-grid {
grid-template-columns: repeat(auto-fill, minmax(20vw, 1fr)) !important;
}
}
</style>
\ No newline at end of file
</style>
<template>
<el-dialog
v-model="showDirectory"
title="目录"
width="50%"
:close-on-click-modal="true"
:close-on-press-escape="true"
class="directory-dialog"
>
<div class="content">
<div class="thumbnail-grid" ref="thumbnailGrid" @scroll="handleScroll">
<div
v-for="(img, index) in directoryImages"
:key="index"
:class="['thumbnail-item', { active: currentPage == index + 2 }]"
@click="goToPage(img.pageNum)"
:ref="setThumbnailRef"
>
<img :src="img.src" :alt="`第 ${img.pageNum} 页`" />
<span class="page-number">{{ img.pageNum }}</span>
</div>
</div>
<el-button
v-show="showBackTop"
:icon="ArrowUpBold"
class="back-top"
color="#000"
circle
size="large"
type="info"
@click="scrollToTop"
></el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ArrowUpBold } from "@element-plus/icons-vue";
const $emit = defineEmits(["goToPage"]);
const props = defineProps({
directoryImages: {
type: Array,
default: () => [],
},
currentPage: {
type: Number,
default: 0,
},
});
const showDirectory = ref(false);
const thumbnailGrid = ref(null); // 获取滚动容器的引用
const showBackTop = ref(false); // 控制按钮显示
const thumbnailRefs = ref([]); // 存储所有缩略图的DOM引用
const setThumbnailRef = (el) => {
if (el) {
thumbnailRefs.value.push(el);
}
};
function show() {
showDirectory.value = true;
nextTick(() => {
scrollToCurrentPage();
});
}
function scrollToCurrentPage() {
if (
props.currentPage > 0 &&
thumbnailGrid.value &&
thumbnailRefs.value.length > 0
) {
// 找到当前页码对应的缩略图索引
const currentIndex = props.directoryImages.findIndex(
(img) => img.pageNum === props.currentPage
);
if (currentIndex >= 0) {
const thumbnailElement = thumbnailRefs.value[currentIndex + 2];
if (thumbnailElement) {
// 计算滚动位置
const gridRect = thumbnailGrid.value.getBoundingClientRect();
const thumbRect = thumbnailElement.getBoundingClientRect();
const scrollTop =
thumbRect.top - gridRect.top + thumbnailGrid.value.scrollTop;
// 平滑滚动到该位置
thumbnailGrid.value.scrollTo({
top: scrollTop - 50, // 减去50px让缩略图不是紧贴顶部
behavior: "smooth",
});
}
}
}
}
function goToPage(pageNum) {
$emit("goToPage", pageNum);
showDirectory.value = false;
}
// 返回顶部函数
function scrollToTop() {
if (thumbnailGrid.value) {
thumbnailGrid.value.scrollTo({
top: 0,
behavior: "smooth", // 平滑滚动
});
}
}
// 监听滚动
function handleScroll() {
if (thumbnailGrid.value) {
showBackTop.value = thumbnailGrid.value.scrollTop > 100; // 滚动超过 100px 时显示
}
}
defineExpose({
show,
});
</script>
<style lang="scss" scoped>
.thumbnail-grid {
max-height: 70vh;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
gap: 10px; /* 添加行间距和列间距 */
position: relative;
}
.content {
.back-top {
position: absolute;
bottom: 40px;
right: 40px;
}
}
.thumbnail-item {
position: relative;
cursor: pointer;
transition: transform 0.2s;
overflow: hidden;
width: calc(33.333% - 10px); /* 调整宽度,减去间距 */
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid transparent;
&:hover {
border: 1px solid #000;
}
img {
width: 100%;
height: 200px;
object-fit: contain;
}
.page-number {
font-size: 20px;
position: absolute;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.65);
width: 100%;
text-align: center;
color: #fff;
}
}
.active {
border: 1px solid #000;
.page-number {
color: var(--el-color-success);
background-color: #fff;
}
}
@media (max-width: 768px) {
.thumbnail-grid {
gap: 10px; /* 保持间距 */
}
.thumbnail-item {
width: calc(33.333% - 10px); /* 保持宽度调整 */
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论