mirror of
https://gitee.com/bootx/dax-pay-h5
synced 2026-08-12 15:35:39 +08:00
feat(h5): 重构深色模式与首页, 统一协议与版本号
清理脚手架冗余; 深色模式全局跟随系统 + iOS 深灰背景; 首页 logo/欢迎语/版本号; DaxPay logo; 版本 4.0.0 + 改名 daxpay-h5
This commit is contained in:
16
package.json
16
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "daxpay-gateway-ui",
|
||||
"name": "daxpay-h5",
|
||||
"type": "module",
|
||||
"version": "3.1.0",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"license": "LGPL-3.0-or-later",
|
||||
@@ -27,16 +27,13 @@
|
||||
"lint:lint-staged": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"axios": "^1.18.1",
|
||||
"date-fns": "^3.0.6",
|
||||
"lodash-es": "^4.18.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"qs": "^6.15.2",
|
||||
"vant": "^4.9.24",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.5.38",
|
||||
"vue-router": "^5.1.0"
|
||||
},
|
||||
@@ -59,7 +56,6 @@
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"autoprefixer": "^10.5.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-git": "^1.13.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-format": "^2.0.1",
|
||||
@@ -71,7 +67,6 @@
|
||||
"postcss": "^8.5.15",
|
||||
"postcss-mobile-forever": "^4.0.0",
|
||||
"rimraf": "^6.1.3",
|
||||
"rollup": "^4.62.2",
|
||||
"rollup-plugin-visualizer": "^5.11.0",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"terser": "^5.48.0",
|
||||
@@ -80,8 +75,8 @@
|
||||
"unocss": "^66.7.2",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vconsole": "^3.15.1",
|
||||
"vite": "^8.1.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^3.3.5"
|
||||
@@ -99,10 +94,5 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "eslint --fix"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-git"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 87 100"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient23" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#AAE8FF" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#BFDCFF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient23)" d="M 86.45 25.05Q 86.05 25.9 85.5 26.25L 44.9 49.7 44.9 51.75 41.9 51.75 41.9 49.7 1.25 26.25Q 0.9 26.05 0.65 25.55L 0.45 25.1 0.45 75.2 0.5 75.75Q 0.7 76.35 1.1 76.55L 41.75 100 41.85 59.85 44.95 59.85 45.05 100 85.65 76.55Q 86.15 76.2 86.35 75.7L 86.45 75.2 86.45 25.05 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient24" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#C2FFF8" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#AADAFF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient24)" d="M 86.6 25.1Q 86.6 24.4 85.8 23.95L 45.15 0.5Q 44.35 0 43.15 0 42 0 41.2 0.5L 0.85 23.95Q 0 24.4 0 25.1 0 25.75 0.85 26.25L 41.45 49.7Q 42.3 50.15 43.45 50.15 44.65 50.15 45.45 49.7L 85.8 26.25Q 86.6 25.75 86.6 25.1 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient25" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#4573FF" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#005FB9" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient25)" d="M 67.9 35.85Q 67.8 36.35 67.5 36.55L 43.25 50.2 43.25 79.3 67.6 65.3Q 67.95 65.1 67.9 64.5L 67.9 35.85 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient26" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient26)" d="M 19.2 36.55Q 19 36.4 18.9 36.1L 18.8 35.85 18.85 64.85Q 18.9 65.15 19.15 65.3L 43.45 79.3 43.45 50.2 19.2 36.55 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient27" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient27)" d="M 67.6 35.2L 44.4 21.8Q 43.9 21.55 43.25 21.55 42.55 21.55 42.1 21.8L 19.05 35.2Q 18.6 35.5 18.6 35.9 18.6 36.25 19.05 36.55L 42.25 49.95Q 42.7 50.2 43.4 50.2 44.1 50.2 44.55 49.95L 67.6 36.55Q 68.1 36.25 68.05 35.9 68.05 35.5 67.6 35.2 Z"></path>
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 513" width="567" height="513">
|
||||
<title>dax-pay-无汉字-svg</title>
|
||||
<style>
|
||||
.s0 { fill: #ebb54b }
|
||||
.s1 { fill: #403f3b }
|
||||
</style>
|
||||
<g id="#ffffffff">
|
||||
</g>
|
||||
<g id="#ebb54bff">
|
||||
<path id="Layer" class="s0" d="m286.3 29.2c28.6-12.8 60.3-20.1 91.7-17.2 22.5 2.3 44.5 10.6 62.5 24.4 16 11.9 28.2 28.3 37.3 45.9 17.9 35.1 23 75.4 21.3 114.4-7.5 0.4-15 0.1-22.5 0-0.5-8.5-0.1-17.1-0.7-25.7-0.7-21.2-4.4-42.3-11.7-62.2-6.1-16.7-15.2-32.5-27.8-45.2-16.3-16.2-38.4-26.7-61.4-28.4-27.6-3-55.2 4.4-80.4 15.5-18.5 8.1-35.5 19.2-52.1 30.7-40.8 29-74.9 66.7-103.4 107.6-2.1 2.8-4.1 6.1-7.5 7.3-8.2 2-16.5 0.5-24.7 0.3 13.3-22.4 29-43.2 45.8-63 13.4-15.8 27.7-30.9 43.3-44.6 26.6-24.6 57-45.3 90.3-59.8z"/>
|
||||
<path id="Layer" class="s0" d="m71.9 333.6c7.3-1.4 14.8-0.9 22.2-0.2 1.1 22.7 3.8 45.6 11.4 67.2 6.5 18.4 16.4 35.8 30.6 49.4 12.9 12.3 29.4 20.9 47 24.5 13.7 3.1 28 3 41.9 1.3 18.1-2.4 35.6-8.3 52.3-15.4 18.1-7.7 34.5-18.5 50.5-29.6 35.6-25.6 66.2-57.6 92.4-92.6 2.4-3.7 6.6-5.3 10.9-5.3 6.7-0.6 13.4 0.5 20.2 0.6-7.6 13.2-17.2 25.2-26.8 37-23.5 28.7-50.1 55.1-80 77.1-31.3 22.8-66.3 41.6-104.5 49.5-16.1 3.3-32.6 3-48.9 2.2-19.8-1.7-39.2-8.6-55.5-19.9-23.7-16.2-40.9-40.5-50.7-67.2-7.5-19.9-11.5-41.1-13.2-62.2-0.5-5.5-0.3-10.9 0.2-16.4z"/>
|
||||
</g>
|
||||
<g id="#403f3bff">
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m29.1 203.6c7.2-1.3 14.6-0.9 21.9-0.9 12.3 0.2 25.1-0.8 36.8 3.5 7.2 2.5 12.8 8.5 15.2 15.7 3.8 11.1 2.2 23 0 34.3-2.9 13.8-8.8 27.7-20.1 36.6-11 8.6-25.4 10.7-38.9 10.5-10.9 0.2-21.7-0.1-32.6-0.1 3.7-25.5 8.8-50.7 13.1-76.1 1.4-7.9 2.3-15.8 4.6-23.5zm16.5 27.8c-3 17.7-6 35.3-9.1 53 10.2 0.1 21.5 1.3 30.5-4.5 8.5-5.8 11.8-16.2 13.7-25.8 1.2-6.3 1.6-12.8 0.8-19.2-0.6-4.9-3.8-9.7-8.6-11.5-7.9-2.8-16.3-2.4-24.5-2q-1.9 4.9-2.8 10z"/>
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m308.3 203.5c12.5-1.7 25.2-0.5 37.8-0.7 9 0.6 19.3 1.3 25.9 8.2 5.8 6.2 5.2 15.3 4.3 23.1-1.6 9.6-4.1 19.8-11 27.1-6.5 6.9-16.1 9.7-25.2 10.6-7.5 0.7-14.9 0.7-22.3 1-1.9 10.1-3.7 20.3-5.4 30.4q-11.1 0.1-22.2 0c4.5-27.1 9.6-54.2 14.2-81.3 1-6.2 1.8-12.5 3.9-18.4zm19.2 17c-3.7 11.1-4.6 22.8-6.6 34.2 7.8-0.1 16 0.9 23.3-2.3 6.8-3.3 9.2-11.4 9.5-18.4 0.4-4.2-0.3-9.2-4.3-11.5-6.8-3.1-14.6-3-21.9-2z"/>
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m142.3 225.4c2.6-1.5 5.8-1.1 8.6-1.5 10.4-0.3 21.3-0.8 31.1 3.2 4.9 2 9 6.3 9.6 11.7 1 8.5-1.2 16.8-2.6 25.1-2.3 13.1-4.8 26.2-7 39.4q-9.4-0.1-18.8 0c-0.3-2.5-0.5-5-0.8-7.5-6.2 3.8-12.7 8-20.2 8.7-7.5 0.6-15.6 0.6-22.2-3.4-4-2.2-6.6-6.6-7-11.1-1.1-9.6 1.9-20.1 9.5-26.4 6.6-5.3 15.3-6.7 23.6-7.2 7.8-0.2 15.6 0 23.4-0.1-0.4-4.2 1.1-9.3-2.1-12.6-9.2-4.1-19.6-1.7-29.2-1 1.1-5.8 1.3-12 4.1-17.3zm-3.6 47.4c-4.6 2.7-7.2 11.1-2 14.5 7.9 3.1 17.3 1.3 24.2-3.4 4.3-3 4.9-8.5 6.1-13.2-9.4-0.3-19.4-1.7-28.3 2.1z"/>
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m409.2 225.5c8.1-2.3 16.6-1.7 24.8-1.6 8.3 0.6 17.8 2 23 9.1 4.1 6.7 2.4 14.8 1.3 22.1-3 16-5.8 32.1-8.7 48.1q-9.4 0.1-18.8 0-0.3-3.7-0.6-7.5c-10.9 8-25.4 11.5-38.5 7.5-5.6-1.6-10.3-6.4-11.1-12.2-1.9-11.8 3.3-25.6 14.6-30.7 13.3-5.9 28-3.5 42-4-0.3-4.3 1.2-10.5-3.3-13.1-9-3.3-18.7-1.1-28-0.6 0.7-5.8 1.7-11.5 3.3-17.1zm-2.6 47c-3.9 2.3-5.7 7.3-4.8 11.6 0.9 3.5 4.9 4.4 8.1 4.6 6.5 0.3 13.3-1.3 18.7-5.1 4.2-2.9 4.7-8.4 6.1-12.9-9.3-0.3-19.2-1.7-28.1 1.8z"/>
|
||||
<path id="Layer" class="s1" d="m210.3 225.4c7.7-0.6 15.5-1.1 23.2 0 5.5 7.5 7.7 16.8 11.7 25.1 5.7-6.1 10.5-13 15.6-19.6 2.1-2.6 4.6-5.6 8.2-5.8 6.7-0.7 13.5-0.3 20.2 0.4-9.7 13-20.9 24.9-31 37.6 6.2 13.5 13.3 26.5 19.3 40.1-8.3 0.1-16.5 0-24.7 0.1-3.9-9-7.7-18.1-12.1-26.8-5.8 6.6-11.1 13.7-16.5 20.6-2.1 2.6-4.4 5.8-8.1 6.1-7.1 0.6-14.2 0.2-21.4 0 10.6-13.6 22.2-26.3 33.4-39.3-5.2-13.2-12.4-25.4-17.8-38.5z"/>
|
||||
<path id="Layer" class="s1" d="m476.1 225c7.4-0.3 14.8-0.1 22.2-0.2 3.4 18.6 5.5 37.5 9.1 56.1 10-18.4 18.2-37.7 28.1-56.1 7.2-0.1 14.4 0.3 21.6 0.6-10 20.5-21.7 40.2-32.4 60.3-4.6 8.4-9 16.8-14.3 24.7-5 7.2-11.1 14.2-19.3 17.8-7.8 3.5-16.5 3.9-24.9 4.3 1-5.9 2.1-11.7 3.2-17.6 5.6-1.1 11.6-1.8 16.5-5 2.6-1.8 5-4.5 5-7.8 0.1-4.5-1-8.9-1.9-13.2-3.4-15-6.7-30-10-44.9-1.4-6.3-3.1-12.6-2.9-19z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,38 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 87 100"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient23" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#AAE8FF" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#BFDCFF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient23)" d="M 86.45 25.05Q 86.05 25.9 85.5 26.25L 44.9 49.7 44.9 51.75 41.9 51.75 41.9 49.7 1.25 26.25Q 0.9 26.05 0.65 25.55L 0.45 25.1 0.45 75.2 0.5 75.75Q 0.7 76.35 1.1 76.55L 41.75 100 41.85 59.85 44.95 59.85 45.05 100 85.65 76.55Q 86.15 76.2 86.35 75.7L 86.45 75.2 86.45 25.05 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient24" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#C2FFF8" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#AADAFF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient24)" d="M 86.6 25.1Q 86.6 24.4 85.8 23.95L 45.15 0.5Q 44.35 0 43.15 0 42 0 41.2 0.5L 0.85 23.95Q 0 24.4 0 25.1 0 25.75 0.85 26.25L 41.45 49.7Q 42.3 50.15 43.45 50.15 44.65 50.15 45.45 49.7L 85.8 26.25Q 86.6 25.75 86.6 25.1 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient25" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#4573FF" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#005FB9" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient25)" d="M 67.9 35.85Q 67.8 36.35 67.5 36.55L 43.25 50.2 43.25 79.3 67.6 65.3Q 67.95 65.1 67.9 64.5L 67.9 35.85 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient26" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient26)" d="M 19.2 36.55Q 19 36.4 18.9 36.1L 18.8 35.85 18.85 64.85Q 18.9 65.15 19.15 65.3L 43.45 79.3 43.45 50.2 19.2 36.55 Z"></path>
|
||||
<defs>
|
||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient27" gradientTransform="rotate(-90 .5 .5)">
|
||||
<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
|
||||
<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#gradient27)" d="M 67.6 35.2L 44.4 21.8Q 43.9 21.55 43.25 21.55 42.55 21.55 42.1 21.8L 19.05 35.2Q 18.6 35.5 18.6 35.9 18.6 36.25 19.05 36.55L 42.25 49.95Q 42.7 50.2 43.4 50.2 44.1 50.2 44.55 49.95L 67.6 36.55Q 68.1 36.25 68.05 35.9 68.05 35.5 67.6 35.2 Z"></path>
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 567 513" width="567" height="513">
|
||||
<title>dax-pay-无汉字-svg</title>
|
||||
<style>
|
||||
.s0 { fill: #ebb54b }
|
||||
.s1 { fill: #403f3b }
|
||||
</style>
|
||||
<g id="#ffffffff">
|
||||
</g>
|
||||
<g id="#ebb54bff">
|
||||
<path id="Layer" class="s0" d="m286.3 29.2c28.6-12.8 60.3-20.1 91.7-17.2 22.5 2.3 44.5 10.6 62.5 24.4 16 11.9 28.2 28.3 37.3 45.9 17.9 35.1 23 75.4 21.3 114.4-7.5 0.4-15 0.1-22.5 0-0.5-8.5-0.1-17.1-0.7-25.7-0.7-21.2-4.4-42.3-11.7-62.2-6.1-16.7-15.2-32.5-27.8-45.2-16.3-16.2-38.4-26.7-61.4-28.4-27.6-3-55.2 4.4-80.4 15.5-18.5 8.1-35.5 19.2-52.1 30.7-40.8 29-74.9 66.7-103.4 107.6-2.1 2.8-4.1 6.1-7.5 7.3-8.2 2-16.5 0.5-24.7 0.3 13.3-22.4 29-43.2 45.8-63 13.4-15.8 27.7-30.9 43.3-44.6 26.6-24.6 57-45.3 90.3-59.8z"/>
|
||||
<path id="Layer" class="s0" d="m71.9 333.6c7.3-1.4 14.8-0.9 22.2-0.2 1.1 22.7 3.8 45.6 11.4 67.2 6.5 18.4 16.4 35.8 30.6 49.4 12.9 12.3 29.4 20.9 47 24.5 13.7 3.1 28 3 41.9 1.3 18.1-2.4 35.6-8.3 52.3-15.4 18.1-7.7 34.5-18.5 50.5-29.6 35.6-25.6 66.2-57.6 92.4-92.6 2.4-3.7 6.6-5.3 10.9-5.3 6.7-0.6 13.4 0.5 20.2 0.6-7.6 13.2-17.2 25.2-26.8 37-23.5 28.7-50.1 55.1-80 77.1-31.3 22.8-66.3 41.6-104.5 49.5-16.1 3.3-32.6 3-48.9 2.2-19.8-1.7-39.2-8.6-55.5-19.9-23.7-16.2-40.9-40.5-50.7-67.2-7.5-19.9-11.5-41.1-13.2-62.2-0.5-5.5-0.3-10.9 0.2-16.4z"/>
|
||||
</g>
|
||||
<g id="#403f3bff">
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m29.1 203.6c7.2-1.3 14.6-0.9 21.9-0.9 12.3 0.2 25.1-0.8 36.8 3.5 7.2 2.5 12.8 8.5 15.2 15.7 3.8 11.1 2.2 23 0 34.3-2.9 13.8-8.8 27.7-20.1 36.6-11 8.6-25.4 10.7-38.9 10.5-10.9 0.2-21.7-0.1-32.6-0.1 3.7-25.5 8.8-50.7 13.1-76.1 1.4-7.9 2.3-15.8 4.6-23.5zm16.5 27.8c-3 17.7-6 35.3-9.1 53 10.2 0.1 21.5 1.3 30.5-4.5 8.5-5.8 11.8-16.2 13.7-25.8 1.2-6.3 1.6-12.8 0.8-19.2-0.6-4.9-3.8-9.7-8.6-11.5-7.9-2.8-16.3-2.4-24.5-2q-1.9 4.9-2.8 10z"/>
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m308.3 203.5c12.5-1.7 25.2-0.5 37.8-0.7 9 0.6 19.3 1.3 25.9 8.2 5.8 6.2 5.2 15.3 4.3 23.1-1.6 9.6-4.1 19.8-11 27.1-6.5 6.9-16.1 9.7-25.2 10.6-7.5 0.7-14.9 0.7-22.3 1-1.9 10.1-3.7 20.3-5.4 30.4q-11.1 0.1-22.2 0c4.5-27.1 9.6-54.2 14.2-81.3 1-6.2 1.8-12.5 3.9-18.4zm19.2 17c-3.7 11.1-4.6 22.8-6.6 34.2 7.8-0.1 16 0.9 23.3-2.3 6.8-3.3 9.2-11.4 9.5-18.4 0.4-4.2-0.3-9.2-4.3-11.5-6.8-3.1-14.6-3-21.9-2z"/>
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m142.3 225.4c2.6-1.5 5.8-1.1 8.6-1.5 10.4-0.3 21.3-0.8 31.1 3.2 4.9 2 9 6.3 9.6 11.7 1 8.5-1.2 16.8-2.6 25.1-2.3 13.1-4.8 26.2-7 39.4q-9.4-0.1-18.8 0c-0.3-2.5-0.5-5-0.8-7.5-6.2 3.8-12.7 8-20.2 8.7-7.5 0.6-15.6 0.6-22.2-3.4-4-2.2-6.6-6.6-7-11.1-1.1-9.6 1.9-20.1 9.5-26.4 6.6-5.3 15.3-6.7 23.6-7.2 7.8-0.2 15.6 0 23.4-0.1-0.4-4.2 1.1-9.3-2.1-12.6-9.2-4.1-19.6-1.7-29.2-1 1.1-5.8 1.3-12 4.1-17.3zm-3.6 47.4c-4.6 2.7-7.2 11.1-2 14.5 7.9 3.1 17.3 1.3 24.2-3.4 4.3-3 4.9-8.5 6.1-13.2-9.4-0.3-19.4-1.7-28.3 2.1z"/>
|
||||
<path id="Layer" fill-rule="evenodd" class="s1" d="m409.2 225.5c8.1-2.3 16.6-1.7 24.8-1.6 8.3 0.6 17.8 2 23 9.1 4.1 6.7 2.4 14.8 1.3 22.1-3 16-5.8 32.1-8.7 48.1q-9.4 0.1-18.8 0-0.3-3.7-0.6-7.5c-10.9 8-25.4 11.5-38.5 7.5-5.6-1.6-10.3-6.4-11.1-12.2-1.9-11.8 3.3-25.6 14.6-30.7 13.3-5.9 28-3.5 42-4-0.3-4.3 1.2-10.5-3.3-13.1-9-3.3-18.7-1.1-28-0.6 0.7-5.8 1.7-11.5 3.3-17.1zm-2.6 47c-3.9 2.3-5.7 7.3-4.8 11.6 0.9 3.5 4.9 4.4 8.1 4.6 6.5 0.3 13.3-1.3 18.7-5.1 4.2-2.9 4.7-8.4 6.1-12.9-9.3-0.3-19.2-1.7-28.1 1.8z"/>
|
||||
<path id="Layer" class="s1" d="m210.3 225.4c7.7-0.6 15.5-1.1 23.2 0 5.5 7.5 7.7 16.8 11.7 25.1 5.7-6.1 10.5-13 15.6-19.6 2.1-2.6 4.6-5.6 8.2-5.8 6.7-0.7 13.5-0.3 20.2 0.4-9.7 13-20.9 24.9-31 37.6 6.2 13.5 13.3 26.5 19.3 40.1-8.3 0.1-16.5 0-24.7 0.1-3.9-9-7.7-18.1-12.1-26.8-5.8 6.6-11.1 13.7-16.5 20.6-2.1 2.6-4.4 5.8-8.1 6.1-7.1 0.6-14.2 0.2-21.4 0 10.6-13.6 22.2-26.3 33.4-39.3-5.2-13.2-12.4-25.4-17.8-38.5z"/>
|
||||
<path id="Layer" class="s1" d="m476.1 225c7.4-0.3 14.8-0.1 22.2-0.2 3.4 18.6 5.5 37.5 9.1 56.1 10-18.4 18.2-37.7 28.1-56.1 7.2-0.1 14.4 0.3 21.6 0.6-10 20.5-21.7 40.2-32.4 60.3-4.6 8.4-9 16.8-14.3 24.7-5 7.2-11.1 14.2-19.3 17.8-7.8 3.5-16.5 3.9-24.9 4.3 1-5.9 2.1-11.7 3.2-17.6 5.6-1.1 11.6-1.8 16.5-5 2.6-1.8 5-4.5 5-7.8 0.1-4.5-1-8.9-1.9-13.2-3.4-15-6.7-30-10-44.9-1.4-6.3-3.1-12.6-2.9-19z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,28 +0,0 @@
|
||||
export enum sizeEnum {
|
||||
XS = 'XS',
|
||||
SM = 'SM',
|
||||
MD = 'MD',
|
||||
LG = 'LG',
|
||||
XL = 'XL',
|
||||
XXL = 'XXL',
|
||||
}
|
||||
|
||||
export enum screenEnum {
|
||||
XS = 480,
|
||||
SM = 576,
|
||||
MD = 768,
|
||||
LG = 992,
|
||||
XL = 1200,
|
||||
XXL = 1600,
|
||||
}
|
||||
|
||||
const screenMap = new Map<sizeEnum, number>()
|
||||
|
||||
screenMap.set(sizeEnum.XS, screenEnum.XS)
|
||||
screenMap.set(sizeEnum.SM, screenEnum.SM)
|
||||
screenMap.set(sizeEnum.MD, screenEnum.MD)
|
||||
screenMap.set(sizeEnum.LG, screenEnum.LG)
|
||||
screenMap.set(sizeEnum.XL, screenEnum.XL)
|
||||
screenMap.set(sizeEnum.XXL, screenEnum.XXL)
|
||||
|
||||
export { screenMap }
|
||||
@@ -1,14 +0,0 @@
|
||||
// token key
|
||||
export const TOKEN_KEY = 'TOKEN'
|
||||
|
||||
// user info key
|
||||
export const USER_INFO_KEY = 'USER__INFO__'
|
||||
|
||||
// role info key
|
||||
export const ROLES_KEY = 'ROLES__KEY__'
|
||||
|
||||
// base global local key
|
||||
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'
|
||||
|
||||
// base global session key
|
||||
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'
|
||||
@@ -1,48 +0,0 @@
|
||||
import { tryOnUnmounted } from '@vueuse/core'
|
||||
import { ref, watch } from 'vue'
|
||||
import { isFunction } from '@/utils/is'
|
||||
|
||||
export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) {
|
||||
if (!isFunction(handle)) {
|
||||
throw new Error('handle is not Function!')
|
||||
}
|
||||
|
||||
const { readyRef, stop, start } = useTimeoutRef(wait)
|
||||
if (native) {
|
||||
handle()
|
||||
}
|
||||
else {
|
||||
watch(
|
||||
readyRef,
|
||||
(maturity) => {
|
||||
maturity && handle()
|
||||
},
|
||||
{ immediate: false },
|
||||
)
|
||||
}
|
||||
return { readyRef, stop, start }
|
||||
}
|
||||
|
||||
export function useTimeoutRef(wait: number) {
|
||||
const readyRef = ref(false)
|
||||
|
||||
let timer: TimeoutHandle
|
||||
|
||||
function stop(): void {
|
||||
readyRef.value = false
|
||||
timer && window.clearTimeout(timer)
|
||||
}
|
||||
|
||||
function start(): void {
|
||||
stop()
|
||||
timer = setTimeout(() => {
|
||||
readyRef.value = true
|
||||
}, wait)
|
||||
}
|
||||
|
||||
start()
|
||||
|
||||
tryOnUnmounted(stop)
|
||||
|
||||
return { readyRef, stop, start }
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { computed, ref, unref } from 'vue'
|
||||
import { screenEnum, screenMap, sizeEnum } from '@/enums/breakpointEnum'
|
||||
import { useEventListener } from '@/hooks/event/useEventListener'
|
||||
|
||||
let globalScreenRef: ComputedRef<sizeEnum | undefined>
|
||||
let globalWidthRef: ComputedRef<number>
|
||||
let globalRealWidthRef: ComputedRef<number>
|
||||
|
||||
export interface CreateCallbackParams {
|
||||
screen: ComputedRef<sizeEnum | undefined>
|
||||
width: ComputedRef<number>
|
||||
realWidth: ComputedRef<number>
|
||||
screenEnum: typeof screenEnum
|
||||
screenMap: Map<sizeEnum, number>
|
||||
sizeEnum: typeof sizeEnum
|
||||
}
|
||||
|
||||
export function useBreakpoint() {
|
||||
return {
|
||||
screenRef: computed(() => unref(globalScreenRef)),
|
||||
widthRef: globalWidthRef,
|
||||
screenEnum,
|
||||
realWidthRef: globalRealWidthRef,
|
||||
}
|
||||
}
|
||||
|
||||
// Just call it once
|
||||
export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) {
|
||||
const screenRef = ref<sizeEnum>(sizeEnum.XL)
|
||||
const realWidthRef = ref(window.innerWidth)
|
||||
|
||||
function getWindowWidth() {
|
||||
const width = document.body.clientWidth
|
||||
const xs = screenMap.get(sizeEnum.XS)!
|
||||
const sm = screenMap.get(sizeEnum.SM)!
|
||||
const md = screenMap.get(sizeEnum.MD)!
|
||||
const lg = screenMap.get(sizeEnum.LG)!
|
||||
const xl = screenMap.get(sizeEnum.XL)!
|
||||
if (width < xs) {
|
||||
screenRef.value = sizeEnum.XS
|
||||
}
|
||||
else if (width < sm) {
|
||||
screenRef.value = sizeEnum.SM
|
||||
}
|
||||
else if (width < md) {
|
||||
screenRef.value = sizeEnum.MD
|
||||
}
|
||||
else if (width < lg) {
|
||||
screenRef.value = sizeEnum.LG
|
||||
}
|
||||
else if (width < xl) {
|
||||
screenRef.value = sizeEnum.XL
|
||||
}
|
||||
else {
|
||||
screenRef.value = sizeEnum.XXL
|
||||
}
|
||||
realWidthRef.value = width
|
||||
}
|
||||
|
||||
useEventListener({
|
||||
el: window,
|
||||
name: 'resize',
|
||||
|
||||
listener: () => {
|
||||
getWindowWidth()
|
||||
resizeFn()
|
||||
},
|
||||
// wait: 100,
|
||||
})
|
||||
|
||||
getWindowWidth()
|
||||
globalScreenRef = computed(() => unref(screenRef))
|
||||
globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!)
|
||||
globalRealWidthRef = computed((): number => unref(realWidthRef))
|
||||
|
||||
function resizeFn() {
|
||||
fn?.({
|
||||
screen: globalScreenRef,
|
||||
width: globalWidthRef,
|
||||
realWidth: globalRealWidthRef,
|
||||
screenEnum,
|
||||
screenMap,
|
||||
sizeEnum,
|
||||
})
|
||||
}
|
||||
|
||||
resizeFn()
|
||||
return {
|
||||
screenRef: globalScreenRef,
|
||||
screenEnum,
|
||||
widthRef: globalWidthRef,
|
||||
realWidthRef: globalRealWidthRef,
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
|
||||
import { ref, unref, watch } from 'vue'
|
||||
|
||||
export type RemoveEventFn = () => void
|
||||
|
||||
export interface UseEventParams {
|
||||
el?: Element | Ref<Element | undefined> | Window | any
|
||||
name: string
|
||||
listener: EventListener
|
||||
options?: boolean | AddEventListenerOptions
|
||||
autoRemove?: boolean
|
||||
isDebounce?: boolean
|
||||
wait?: number
|
||||
}
|
||||
|
||||
export function useEventListener({
|
||||
el = window,
|
||||
name,
|
||||
listener,
|
||||
options,
|
||||
autoRemove = true,
|
||||
isDebounce = true,
|
||||
wait = 80,
|
||||
}: UseEventParams): { removeEvent: RemoveEventFn } {
|
||||
let remove: RemoveEventFn = () => {
|
||||
}
|
||||
const isAddRef = ref(false)
|
||||
|
||||
if (el) {
|
||||
const element: Ref<Element> = ref(el as Element)
|
||||
|
||||
const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait)
|
||||
const realHandler = wait ? handler : listener
|
||||
const removeEventListener = (e: Element) => {
|
||||
isAddRef.value = true
|
||||
e.removeEventListener(name, realHandler, options)
|
||||
}
|
||||
const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options)
|
||||
|
||||
const removeWatch = watch(
|
||||
element,
|
||||
(v, _ov, cleanUp) => {
|
||||
if (v) {
|
||||
!unref(isAddRef) && addEventListener(v)
|
||||
cleanUp(() => {
|
||||
autoRemove && removeEventListener(v)
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
remove = () => {
|
||||
removeEventListener(element.value)
|
||||
removeWatch()
|
||||
}
|
||||
}
|
||||
return { removeEvent: remove }
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'
|
||||
|
||||
interface WindowSizeOptions {
|
||||
once?: boolean
|
||||
immediate?: boolean
|
||||
listenerOptions?: AddEventListenerOptions | boolean
|
||||
}
|
||||
|
||||
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
|
||||
let handler = () => {
|
||||
fn()
|
||||
}
|
||||
const handleSize = useDebounceFn(handler, wait)
|
||||
handler = handleSize
|
||||
|
||||
const start = () => {
|
||||
if (options && options.immediate) {
|
||||
handler()
|
||||
}
|
||||
window.addEventListener('resize', handler)
|
||||
}
|
||||
|
||||
const stop = () => {
|
||||
window.removeEventListener('resize', handler)
|
||||
}
|
||||
|
||||
tryOnMounted(() => {
|
||||
start()
|
||||
})
|
||||
|
||||
tryOnUnmounted(() => {
|
||||
stop()
|
||||
})
|
||||
return [start, stop]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { useAsync } from './use-async'
|
||||
|
||||
export { useAsync }
|
||||
@@ -4,7 +4,8 @@ import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
export function useDesignSetting() {
|
||||
const designStore = useDesignSettingStore()
|
||||
|
||||
const getDarkMode = computed(() => designStore.darkMode)
|
||||
// 当前实际生效主题(跟随系统 prefers-color-scheme 派生)
|
||||
const getDarkMode = computed(() => designStore.getDarkMode)
|
||||
|
||||
const getAppTheme = computed(() => designStore.appTheme)
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { isReactive, isRef } from 'vue'
|
||||
|
||||
function setLoading(loading, val) {
|
||||
if (loading !== undefined && isRef(loading)) {
|
||||
loading.value = val
|
||||
}
|
||||
else if (loading !== undefined && isReactive(loading)) {
|
||||
loading.loading = val
|
||||
}
|
||||
}
|
||||
|
||||
export async function useAsync(func: Promise<any>, loading: any): Promise<any> {
|
||||
setLoading(loading, true)
|
||||
|
||||
return await func.finally(() => setLoading(loading, false))
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { debounce } from 'lodash-es'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* description: 获取页面宽度
|
||||
*/
|
||||
|
||||
export function useDomWidth() {
|
||||
const domWidth = ref(window.innerWidth)
|
||||
|
||||
function resize() {
|
||||
domWidth.value = document.body.clientWidth
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', debounce(resize, 80))
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
})
|
||||
|
||||
return domWidth
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* @description 用户网络是否可用
|
||||
*/
|
||||
export function useOnline() {
|
||||
const online = ref(true)
|
||||
|
||||
const showStatus = (val) => {
|
||||
online.value = typeof val == 'boolean' ? val : val.target.online
|
||||
}
|
||||
|
||||
// 在页面加载后,设置正确的网络状态
|
||||
navigator.onLine ? showStatus(true) : showStatus(false)
|
||||
|
||||
onMounted(() => {
|
||||
// 开始监听网络状态的变化
|
||||
window.addEventListener('online', showStatus)
|
||||
|
||||
window.addEventListener('offline', showStatus)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
// 移除监听网络状态的变化
|
||||
window.removeEventListener('online', showStatus)
|
||||
|
||||
window.removeEventListener('offline', showStatus)
|
||||
})
|
||||
|
||||
return { online }
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* @description 获取本地时间
|
||||
*/
|
||||
export function useTime() {
|
||||
let timer // 定时器
|
||||
const year = ref(0) // 年份
|
||||
const month = ref(0) // 月份
|
||||
const week = ref('') // 星期几
|
||||
const day = ref(0) // 天数
|
||||
const hour = ref<number | string>(0) // 小时
|
||||
const minute = ref<number | string>(0) // 分钟
|
||||
const second = ref(0) // 秒
|
||||
|
||||
// 更新时间
|
||||
const updateTime = () => {
|
||||
const date = new Date()
|
||||
year.value = date.getFullYear()
|
||||
month.value = date.getMonth() + 1
|
||||
week.value = '日一二三四五六'.charAt(date.getDay())
|
||||
day.value = date.getDate()
|
||||
hour.value
|
||||
= (`${date.getHours()}`)?.padStart(2, '0')
|
||||
|| new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
|
||||
minute.value
|
||||
= (`${date.getMinutes()}`)?.padStart(2, '0')
|
||||
|| new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
|
||||
second.value = date.getSeconds()
|
||||
}
|
||||
|
||||
// 原生时间格式化
|
||||
// new Intl.DateTimeFormat('zh', {
|
||||
// year: 'numeric',
|
||||
// month: '2-digit',
|
||||
// day: '2-digit',
|
||||
// hour: '2-digit',
|
||||
// minute: '2-digit',
|
||||
// second: '2-digit',
|
||||
// hour12: false
|
||||
// }).format(new Date())
|
||||
|
||||
updateTime()
|
||||
|
||||
onMounted(() => {
|
||||
clearInterval(timer)
|
||||
timer = setInterval(updateTime, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
|
||||
return { month, day, hour, minute, second, week }
|
||||
}
|
||||
@@ -1,525 +0,0 @@
|
||||
<template>
|
||||
<div class="floating-toolbar-wrap">
|
||||
<nav
|
||||
ref="toolbarRef"
|
||||
class="floating-toolbar"
|
||||
:class="{ dark: isDarkTheme }"
|
||||
:style="toolbarCssVars"
|
||||
aria-label="主导航"
|
||||
>
|
||||
<div class="ambient-glow" />
|
||||
<div class="film-grain" />
|
||||
|
||||
<div
|
||||
ref="indicatorRef"
|
||||
class="active-indicator"
|
||||
:style="{
|
||||
transform: `translateX(${indicatorX}px)`,
|
||||
opacity: activeNavIndex >= 0 ? 1 : 0,
|
||||
}"
|
||||
>
|
||||
<div class="ring-glow" />
|
||||
<div class="ring-clip">
|
||||
<div class="ring-spin" />
|
||||
</div>
|
||||
<div class="inner-plate" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-for="(item, idx) in navItems"
|
||||
:key="item.path"
|
||||
:ref="(el) => setNavRef(el, idx)"
|
||||
type="button"
|
||||
class="nav-btn"
|
||||
:class="{
|
||||
'active': idx === activeNavIndex,
|
||||
'with-divider': idx < navItems.length - 1,
|
||||
'nav-bounce': navBounceIndex === idx,
|
||||
}"
|
||||
:aria-label="item.label"
|
||||
@click="handleNavClick(idx, item.path)"
|
||||
>
|
||||
<i :class="item.icon" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="props.showDarkModeToggle"
|
||||
type="button"
|
||||
class="nav-btn toggle-btn"
|
||||
:class="{ bounce: toggleBouncing }"
|
||||
aria-label="切换主题"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<span class="toggle-icon-wrap">
|
||||
<i class="i-ph:sun-dim sun-icon" :class="{ hidden: darkModel }" />
|
||||
<i class="i-ph:moon moon-icon" :class="{ show: darkModel }" />
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDark } from '@vueuse/core'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { isNavigationFailure, NavigationFailureType, useRoute, useRouter } from 'vue-router'
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
|
||||
interface NavItem {
|
||||
label: string
|
||||
path: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items?: NavItem[]
|
||||
showDarkModeToggle?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
items: () => [
|
||||
{ label: 'Home', path: '/home/index', icon: 'i-ph:house-line' },
|
||||
],
|
||||
showDarkModeToggle: true,
|
||||
})
|
||||
|
||||
const designStore = useDesignSettingStore()
|
||||
const currentRoute = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const navItems = computed(() => props.items)
|
||||
|
||||
const activeNavIndex = computed(() => {
|
||||
const path = currentRoute.path
|
||||
const items = navItems.value
|
||||
if (items.length === 0) {
|
||||
return -1
|
||||
}
|
||||
const index = items.findIndex((item) => {
|
||||
const basePath = item.path.replace(/\/index$/, '')
|
||||
return path === item.path || path.startsWith(basePath)
|
||||
})
|
||||
return index >= 0 ? index : 0
|
||||
})
|
||||
|
||||
const toolbarRef = ref<HTMLElement | null>(null)
|
||||
const indicatorRef = ref<HTMLElement | null>(null)
|
||||
const navRefs = ref<(HTMLElement | null)[]>([])
|
||||
const indicatorX = ref(0)
|
||||
|
||||
const isDark = useDark({
|
||||
valueDark: 'dark',
|
||||
valueLight: 'light',
|
||||
disableTransition: false,
|
||||
})
|
||||
|
||||
const darkModel = computed({
|
||||
get: () => isDark.value || designStore.darkMode === 'dark',
|
||||
set: (value: boolean) => {
|
||||
isDark.value = value
|
||||
designStore.setDarkMode(value ? 'dark' : 'light')
|
||||
},
|
||||
})
|
||||
const isDarkTheme = computed(() => darkModel.value)
|
||||
const toolbarColumns = computed(() => Math.max(1, navItems.value.length + (props.showDarkModeToggle ? 1 : 0)))
|
||||
const toolbarCssVars = computed(() => ({
|
||||
'--accent-color': designStore.appTheme,
|
||||
'--accent-soft-color': `${designStore.appTheme}33`,
|
||||
'--toolbar-max-width': toolbarColumns.value >= 5 ? '430px' : '340px',
|
||||
'gridTemplateColumns': `repeat(${toolbarColumns.value}, 1fr)`,
|
||||
}))
|
||||
|
||||
const toggleBouncing = ref(false)
|
||||
const navBounceIndex = ref<number | null>(null)
|
||||
let toggleTimer: number | null = null
|
||||
let navBounceTimer: number | null = null
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
|
||||
function setNavRef(el: Element | { $el?: Element } | null, index: number) {
|
||||
if (!el) {
|
||||
navRefs.value[index] = null
|
||||
return
|
||||
}
|
||||
const element = el instanceof HTMLElement
|
||||
? el
|
||||
: ('$el' in el ? (el.$el as HTMLElement | undefined) : undefined)
|
||||
navRefs.value[index] = element ?? null
|
||||
}
|
||||
|
||||
function updateIndicatorPosition() {
|
||||
const indicator = indicatorRef.value
|
||||
const target = navRefs.value[activeNavIndex.value]
|
||||
if (!indicator || !target) {
|
||||
return
|
||||
}
|
||||
const indicatorWidth = indicator.offsetWidth || 48
|
||||
indicatorX.value = target.offsetLeft + target.offsetWidth / 2 - indicatorWidth / 2
|
||||
}
|
||||
|
||||
function scheduleIndicatorUpdate() {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
updateIndicatorPosition()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function bindResizeObserver() {
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
return
|
||||
}
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
scheduleIndicatorUpdate()
|
||||
})
|
||||
if (toolbarRef.value) {
|
||||
resizeObserver.observe(toolbarRef.value)
|
||||
}
|
||||
if (indicatorRef.value) {
|
||||
resizeObserver.observe(indicatorRef.value)
|
||||
}
|
||||
for (const navEl of navRefs.value) {
|
||||
if (navEl) {
|
||||
resizeObserver.observe(navEl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePath(path: string) {
|
||||
return path.replace(/\/+$/, '') || '/'
|
||||
}
|
||||
|
||||
function goNav(path: string) {
|
||||
// Avoid redundant navigation warnings when clicking current tab.
|
||||
if (normalizePath(currentRoute.path) === normalizePath(path)) {
|
||||
return
|
||||
}
|
||||
router.push(path).catch((err) => {
|
||||
if (!isNavigationFailure(err, NavigationFailureType.duplicated)) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleNavClick(index: number, path: string) {
|
||||
navBounceIndex.value = null
|
||||
if (navBounceTimer) {
|
||||
window.clearTimeout(navBounceTimer)
|
||||
}
|
||||
void nextTick(() => {
|
||||
navBounceIndex.value = index
|
||||
navBounceTimer = window.setTimeout(() => {
|
||||
navBounceIndex.value = null
|
||||
}, 420)
|
||||
})
|
||||
goNav(path)
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if (!props.showDarkModeToggle) {
|
||||
return
|
||||
}
|
||||
toggleBouncing.value = false
|
||||
if (toggleTimer) {
|
||||
window.clearTimeout(toggleTimer)
|
||||
}
|
||||
void nextTick(() => {
|
||||
toggleBouncing.value = true
|
||||
toggleTimer = window.setTimeout(() => {
|
||||
toggleBouncing.value = false
|
||||
}, 420)
|
||||
})
|
||||
darkModel.value = !darkModel.value
|
||||
}
|
||||
|
||||
watch(
|
||||
() => currentRoute.path,
|
||||
async () => {
|
||||
await nextTick()
|
||||
bindResizeObserver()
|
||||
scheduleIndicatorUpdate()
|
||||
},
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
watch(activeNavIndex, async () => {
|
||||
await nextTick()
|
||||
scheduleIndicatorUpdate()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
bindResizeObserver()
|
||||
scheduleIndicatorUpdate()
|
||||
window.addEventListener('resize', scheduleIndicatorUpdate)
|
||||
const storeDark = designStore.darkMode === 'dark'
|
||||
if (isDark.value !== storeDark) {
|
||||
isDark.value = storeDark
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (toggleTimer) {
|
||||
window.clearTimeout(toggleTimer)
|
||||
}
|
||||
if (navBounceTimer) {
|
||||
window.clearTimeout(navBounceTimer)
|
||||
}
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver = null
|
||||
}
|
||||
window.removeEventListener('resize', scheduleIndicatorUpdate as () => void)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.floating-toolbar-wrap {
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: calc(14px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.floating-toolbar {
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
width: min(94vw, var(--toolbar-max-width));
|
||||
height: 62px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 31px;
|
||||
background: rgba(16, 20, 30, 0.86);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(20px) saturate(140%);
|
||||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.floating-toolbar.dark {
|
||||
background: rgba(16, 20, 30, 0.86);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-color: rgba(24, 39, 75, 0.12);
|
||||
box-shadow: 0 6px 16px rgba(41, 58, 88, 0.08);
|
||||
}
|
||||
|
||||
.ambient-glow {
|
||||
position: absolute;
|
||||
inset: -18px;
|
||||
background: radial-gradient(72% 100% at 50% 112%, var(--accent-soft-color), transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.film-grain {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.06;
|
||||
pointer-events: none;
|
||||
mix-blend-mode: soft-light;
|
||||
background-image:
|
||||
radial-gradient(circle at 22% 28%, rgba(255, 255, 255, 0.38) 0.35px, transparent 1px),
|
||||
radial-gradient(circle at 78% 72%, rgba(255, 255, 255, 0.26) 0.4px, transparent 1px);
|
||||
background-size:
|
||||
4px 4px,
|
||||
5px 5px;
|
||||
}
|
||||
|
||||
.active-indicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 62px;
|
||||
height: 38px;
|
||||
margin-top: -19px;
|
||||
transition: transform 0.28s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ring-glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 22px;
|
||||
background: var(--accent-soft-color);
|
||||
opacity: 0.22;
|
||||
filter: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ring-clip {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 22px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ring-spin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 22px;
|
||||
border: 1px solid var(--accent-color);
|
||||
opacity: 0.35;
|
||||
animation: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.inner-plate {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 22px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .inner-plate {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(24, 39, 75, 0.1);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8),
|
||||
0 2px 8px rgba(31, 41, 55, 0.12);
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
transition: color 0.25s ease;
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .nav-btn {
|
||||
color: rgba(44, 59, 88, 0.72);
|
||||
}
|
||||
|
||||
.nav-btn.active {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .nav-btn.active {
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.nav-btn.with-divider::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 15px;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.14), transparent);
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .nav-btn.with-divider::after {
|
||||
background: linear-gradient(180deg, transparent, rgba(36, 54, 88, 0.12), transparent);
|
||||
}
|
||||
|
||||
.theme-switch-wrap {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .toggle-btn {
|
||||
color: rgba(44, 59, 88, 0.78);
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .ring-glow {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.floating-toolbar:not(.dark) .ring-spin {
|
||||
border-color: rgba(24, 39, 75, 0.14);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toggle-icon-wrap {
|
||||
position: relative;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.sun-icon,
|
||||
.moon-icon {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transition:
|
||||
opacity 0.28s ease,
|
||||
transform 0.32s ease;
|
||||
}
|
||||
|
||||
.sun-icon.hidden {
|
||||
opacity: 0;
|
||||
transform: rotate(90deg) scale(0.6);
|
||||
}
|
||||
|
||||
.moon-icon {
|
||||
opacity: 0;
|
||||
transform: rotate(-90deg) scale(0.6);
|
||||
}
|
||||
|
||||
.moon-icon.show {
|
||||
opacity: 1;
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
.toggle-btn.bounce {
|
||||
animation: toggle-bounce 0.42s cubic-bezier(0.34, 1.2, 0.64, 1);
|
||||
}
|
||||
|
||||
.nav-btn.nav-bounce i {
|
||||
animation: nav-icon-bounce 0.42s cubic-bezier(0.34, 1.2, 0.64, 1);
|
||||
}
|
||||
|
||||
@keyframes toggle-bounce {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
35% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
70% {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nav-icon-bounce {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
35% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
70% {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="layout-shell h-screen flex flex-col"
|
||||
:class="{ dark: designStore.darkMode === 'dark' }"
|
||||
:class="{ dark: designStore.getDarkMode === 'dark' }"
|
||||
>
|
||||
<RouterView class="flex-1 overflow-x-hidden">
|
||||
<template #default="{ Component, route }">
|
||||
@@ -11,8 +11,6 @@
|
||||
<component :is="Component" v-else :key="route.fullPath" />
|
||||
</template>
|
||||
</RouterView>
|
||||
|
||||
<FloatingNavBar :items="tabbarItems" :show-dark-mode-toggle="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,17 +18,11 @@
|
||||
import { computed } from 'vue'
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
import { useRouteStore } from '@/store/modules/route'
|
||||
import FloatingNavBar from './components/FloatingNavBar.vue'
|
||||
|
||||
const routeStore = useRouteStore()
|
||||
const designStore = useDesignSettingStore()
|
||||
|
||||
const keepAliveComponents = computed(() => routeStore.keepAliveComponents)
|
||||
|
||||
const tabbarItems = [
|
||||
{ label: 'Home', path: '/home/index', icon: 'i-ph:house-line' },
|
||||
{ label: 'Theme', path: '/themeSetting', icon: 'i-ph:palette' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -39,6 +31,6 @@ const tabbarItems = [
|
||||
}
|
||||
|
||||
.layout-shell.dark {
|
||||
background: #000000;
|
||||
background: #1c1c1e;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createApp } from 'vue'
|
||||
import { setupStore } from '@/store'
|
||||
import { useDesignSettingWithOut } from '@/store/modules/designSetting'
|
||||
import App from './App.vue'
|
||||
import router, { setupRouter } from './router'
|
||||
import 'virtual:uno.css'
|
||||
@@ -27,6 +28,8 @@ async function bootstrap() {
|
||||
const app = createApp(App)
|
||||
// 挂载状态管理
|
||||
setupStore(app)
|
||||
// 初始化全局主题:跟随系统 prefers-color-scheme(不支持手动修改)
|
||||
useDesignSettingWithOut().initSystemListener()
|
||||
// 挂载路由
|
||||
setupRouter(app)
|
||||
await router.isReady()
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export default {
|
||||
upload: {
|
||||
// 考虑接口规范不同
|
||||
apiSetting: {
|
||||
// 集合字段名
|
||||
infoField: 'result',
|
||||
// 图片地址字段名
|
||||
imgField: 'imagePath',
|
||||
},
|
||||
// 最大上传图片大小
|
||||
maxSize: 1,
|
||||
// 图片上传类型
|
||||
fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
|
||||
},
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// app theme preset color
|
||||
|
||||
export interface DesignSettingState {
|
||||
// 系统主题
|
||||
darkMode: 'light' | 'dark'
|
||||
// 系统是否深色(跟随 prefers-color-scheme,只读派生,不持久化)
|
||||
systemPrefersDark: boolean
|
||||
// 系统风格
|
||||
appTheme: string
|
||||
// 系统内置风格
|
||||
@@ -36,8 +36,8 @@ export const appThemeList: string[] = [
|
||||
]
|
||||
|
||||
const setting: DesignSettingState = {
|
||||
// 深色主题
|
||||
darkMode: 'dark',
|
||||
// 系统是否深色(初始 false,由 initSystemListener 读取系统并覆盖)
|
||||
systemPrefersDark: false,
|
||||
// 系统主题色
|
||||
appTheme: '#5d9dfe',
|
||||
// 系统内置主题色列表
|
||||
|
||||
@@ -3,19 +3,30 @@ import { defineStore } from 'pinia'
|
||||
import designSetting from '@/settings/designSetting'
|
||||
import { store } from '@/store'
|
||||
|
||||
const { darkMode, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
|
||||
const { systemPrefersDark, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
|
||||
|
||||
// 是否已绑定系统主题监听,避免重复绑定
|
||||
let systemListenerBound = false
|
||||
|
||||
/** 同步 <html> 的 dark / light class,驱动全局深色样式(common.less 的 html.dark / html.light) */
|
||||
function syncHtmlThemeClass(isDark: boolean) {
|
||||
const html = document.documentElement
|
||||
html.classList.toggle('dark', isDark)
|
||||
html.classList.toggle('light', !isDark)
|
||||
}
|
||||
|
||||
export const useDesignSettingStore = defineStore('app-design-setting', {
|
||||
state: (): DesignSettingState => ({
|
||||
darkMode,
|
||||
systemPrefersDark,
|
||||
appTheme,
|
||||
appThemeList,
|
||||
isPageAnimate,
|
||||
pageAnimateType,
|
||||
}),
|
||||
getters: {
|
||||
// 当前实际生效的主题(跟随系统 prefers-color-scheme)
|
||||
getDarkMode(): 'light' | 'dark' {
|
||||
return this.darkMode
|
||||
return this.systemPrefersDark ? 'dark' : 'light'
|
||||
},
|
||||
getAppTheme(): string {
|
||||
return this.appTheme
|
||||
@@ -31,17 +42,40 @@ export const useDesignSettingStore = defineStore('app-design-setting', {
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setDarkMode(mode: 'light' | 'dark'): void {
|
||||
this.darkMode = mode
|
||||
/**
|
||||
* 初始化系统主题监听:读取 prefers-color-scheme 并监听其变化
|
||||
* 全局跟随系统,不支持手动修改
|
||||
*/
|
||||
initSystemListener() {
|
||||
// 非浏览器环境(如 SSR)仅按当前 state 同步一次
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
||||
syncHtmlThemeClass(this.systemPrefersDark)
|
||||
return
|
||||
}
|
||||
// 首次读取系统主题并同步到 <html>
|
||||
this.systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
syncHtmlThemeClass(this.systemPrefersDark)
|
||||
// 已绑定过则不再重复添加监听(应用生命周期内常驻)
|
||||
if (systemListenerBound) {
|
||||
return
|
||||
}
|
||||
systemListenerBound = true
|
||||
const mql = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const handler = (e: MediaQueryListEvent) => {
|
||||
this.systemPrefersDark = e.matches
|
||||
syncHtmlThemeClass(this.systemPrefersDark)
|
||||
}
|
||||
mql.addEventListener('change', handler)
|
||||
},
|
||||
setPageAnimateType(type: string): void {
|
||||
this.pageAnimateType = type
|
||||
},
|
||||
},
|
||||
// 持久化
|
||||
// 仅持久化用户可配置项,跟随系统的 systemPrefersDark 不持久化
|
||||
persist: {
|
||||
key: 'DESIGN-SETTING',
|
||||
storage: localStorage,
|
||||
pick: ['appTheme', 'appThemeList', 'isPageAnimate', 'pageAnimateType'],
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const DESIGN_SETTING = 'DESIGN-SETTING' // 当前用户主题信息
|
||||
@@ -16,8 +16,13 @@ html.dark {
|
||||
color-scheme: dark !important;
|
||||
}
|
||||
|
||||
// 覆盖 vant 深色背景变量(vant 默认 --van-background 为 #000,对比度过强)
|
||||
--van-background: #1c1c1e;
|
||||
--van-background-2: #1c1c1e;
|
||||
--van-background-3: #2c2c2e;
|
||||
|
||||
body {
|
||||
background-color: #000000;
|
||||
background-color: #1c1c1e;
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
}
|
||||
@@ -108,9 +113,9 @@ html.light {
|
||||
html.dark {
|
||||
.my-card {
|
||||
backdrop-filter: blur(12px);
|
||||
background: rgba(18, 18, 18, 0.85);
|
||||
background: rgba(44, 44, 46, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.van-cell-group--inset {
|
||||
@@ -120,7 +125,7 @@ html.dark {
|
||||
}
|
||||
|
||||
.van-cell-group--inset .van-cell {
|
||||
background: rgba(18, 18, 18, 0.6);
|
||||
background: #2c2c2e;
|
||||
}
|
||||
|
||||
.van-cell-group .van-cell::after {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { format } from 'date-fns'
|
||||
|
||||
const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss'
|
||||
const DATE_FORMAT = 'YYYY-MM-DD '
|
||||
|
||||
export function formatToDateTime(date: number | Date, formatStr = DATE_TIME_FORMAT): string {
|
||||
return format(date, formatStr)
|
||||
}
|
||||
|
||||
export function formatToDate(date: number | Date, formatStr = DATE_FORMAT): string {
|
||||
return format(date, formatStr)
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/* eslint-disable ts/ban-ts-comment */
|
||||
import type { FunctionArgs } from '@vueuse/core'
|
||||
import { upperFirst } from 'lodash-es'
|
||||
|
||||
export interface ViewportOffsetResult {
|
||||
left: number
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
rightIncludeBody: number
|
||||
bottomIncludeBody: number
|
||||
}
|
||||
|
||||
export function getBoundingClientRect(element: Element): DOMRect | number {
|
||||
if (!element || !element.getBoundingClientRect) {
|
||||
return 0
|
||||
}
|
||||
return element.getBoundingClientRect()
|
||||
}
|
||||
|
||||
function trim(string: string) {
|
||||
return (string || '').replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function hasClass(el: Element, cls: string) {
|
||||
if (!el || !cls) {
|
||||
return false
|
||||
}
|
||||
if (cls.includes(' ')) {
|
||||
throw new Error('className should not contain space.')
|
||||
}
|
||||
if (el.classList) {
|
||||
return el.classList.contains(cls)
|
||||
}
|
||||
else {
|
||||
return (` ${el.className} `).includes(` ${cls} `)
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addClass(el: Element, cls: string) {
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
let curClass = el.className
|
||||
const classes = (cls || '').split(' ')
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i]
|
||||
if (!clsName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(clsName)
|
||||
}
|
||||
else if (!hasClass(el, clsName)) {
|
||||
curClass += ` ${clsName}`
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = curClass
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeClass(el: Element, cls: string) {
|
||||
if (!el || !cls) {
|
||||
return
|
||||
}
|
||||
const classes = cls.split(' ')
|
||||
let curClass = ` ${el.className} `
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i]
|
||||
if (!clsName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(clsName)
|
||||
}
|
||||
else if (hasClass(el, clsName)) {
|
||||
curClass = curClass.replace(` ${clsName} `, ' ')
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = trim(curClass)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the left and top offset of the current element
|
||||
* left: the distance between the leftmost element and the left side of the document
|
||||
* top: the distance from the top of the element to the top of the document
|
||||
* right: the distance from the far right of the element to the right of the document
|
||||
* bottom: the distance from the bottom of the element to the bottom of the document
|
||||
* rightIncludeBody: the distance between the leftmost element and the right side of the document
|
||||
* bottomIncludeBody: the distance from the bottom of the element to the bottom of the document
|
||||
*
|
||||
* @description:
|
||||
*/
|
||||
export function getViewportOffset(element: Element): ViewportOffsetResult {
|
||||
const doc = document.documentElement
|
||||
|
||||
const docScrollLeft = doc.scrollLeft
|
||||
const docScrollTop = doc.scrollTop
|
||||
const docClientLeft = doc.clientLeft
|
||||
const docClientTop = doc.clientTop
|
||||
|
||||
const pageXOffset = window.pageXOffset
|
||||
const pageYOffset = window.pageYOffset
|
||||
|
||||
const box = getBoundingClientRect(element)
|
||||
|
||||
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect
|
||||
|
||||
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)
|
||||
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)
|
||||
const offsetLeft = retLeft + pageXOffset
|
||||
const offsetTop = rectTop + pageYOffset
|
||||
|
||||
const left = offsetLeft - scrollLeft
|
||||
const top = offsetTop - scrollTop
|
||||
|
||||
const clientWidth = window.document.documentElement.clientWidth
|
||||
const clientHeight = window.document.documentElement.clientHeight
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
right: clientWidth - rectWidth - left,
|
||||
bottom: clientHeight - rectHeight - top,
|
||||
rightIncludeBody: clientWidth - left,
|
||||
bottomIncludeBody: clientHeight - top,
|
||||
}
|
||||
}
|
||||
|
||||
export function hackCss(attr: string, value: string) {
|
||||
const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']
|
||||
|
||||
const styleObj: any = {}
|
||||
prefix.forEach((item) => {
|
||||
styleObj[`${item}${upperFirst(attr)}`] = value
|
||||
})
|
||||
return {
|
||||
...styleObj,
|
||||
[attr]: value,
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function on(
|
||||
element: Element | HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject,
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false)
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function off(
|
||||
element: Element | HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: Fn,
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, false)
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function once(el: HTMLElement, event: string, fn: EventListener): void {
|
||||
const listener = function (this: any, ...args: unknown[]) {
|
||||
if (fn) {
|
||||
// @ts-expect-error
|
||||
fn.apply(this, args)
|
||||
}
|
||||
off(el, event, listener)
|
||||
}
|
||||
on(el, event, listener)
|
||||
}
|
||||
|
||||
export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
|
||||
let locked = false
|
||||
// @ts-expect-error
|
||||
return function (...args: any[]) {
|
||||
if (locked) {
|
||||
return
|
||||
}
|
||||
locked = true
|
||||
window.requestAnimationFrame(() => {
|
||||
// @ts-expect-error
|
||||
fn.apply(this, args)
|
||||
locked = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,29 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<van-nav-bar title="DaxPay" />
|
||||
<div class="home__body">
|
||||
<van-empty description="DaxPay H5 — 等待业务开发" />
|
||||
<div class="home__welcome">
|
||||
<!-- DaxPay 文字 logo(默认主题色时显示) -->
|
||||
<Logo />
|
||||
<!-- 欢迎语 -->
|
||||
<div class="home__title">
|
||||
欢迎使用 DaxPay
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部项目信息 -->
|
||||
<div class="home__footer">
|
||||
<p>版本号: v{{ version }}</p>
|
||||
<p>构建时间: {{ lastBuildTime }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 占位首页:业务开发时替换为实际首页
|
||||
import Logo from '@/components/Logo.vue'
|
||||
|
||||
// 由 vite define 注入的项目信息
|
||||
const { pkg, lastBuildTime } = __APP_INFO__
|
||||
const version = pkg.version
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -23,5 +38,36 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// logo + 欢迎语纵向居中
|
||||
&__welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 欢迎语(参考商业版 text-2xl font-black mt-12)
|
||||
&__title {
|
||||
margin-top: 12px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
// 底部项目信息(版本号 / 构建时间,参考商业版 fixed bottom-6 text-xs opacity-50)
|
||||
&__footer {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<van-divider>主题模式</van-divider>
|
||||
<van-cell-group inset>
|
||||
<van-cell center title="暗黑模式">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="getDarkMode" size="22" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
|
||||
<van-divider>系统主题色</van-divider>
|
||||
<div flex="~" justify="center">
|
||||
<div grid="~ cols-8 gap-2">
|
||||
@@ -67,26 +58,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDark } from '@vueuse/core'
|
||||
import { animates as animateOptions } from '@/settings/animateSetting'
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting'
|
||||
|
||||
const designStore = useDesignSettingStore()
|
||||
|
||||
const isDark = useDark({
|
||||
valueDark: 'dark',
|
||||
valueLight: 'light',
|
||||
disableTransition: false,
|
||||
})
|
||||
|
||||
const getDarkMode = computed({
|
||||
get: () => isDark.value,
|
||||
set: (value: boolean) => {
|
||||
isDark.value = value
|
||||
designStore.setDarkMode(value ? 'dark' : 'light')
|
||||
},
|
||||
})
|
||||
|
||||
function togTheme(color: string) {
|
||||
designStore.appTheme = color
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user