mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-03-15 04:53:18 +08:00
Merge pull request #72 from Moe-Sakura/dev
feat: enhance SettingsModal with search history management and projec…
This commit is contained in:
20
package.json
20
package.json
@@ -13,29 +13,29 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@types/node": "^25.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
||||
"@typescript-eslint/parser": "^8.52.0",
|
||||
"@types/node": "^25.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"eslint-plugin-vue": "^10.7.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.52.0",
|
||||
"typescript-eslint": "^8.54.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vue-tsc": "^3.2.2",
|
||||
"vue-tsc": "^3.2.4",
|
||||
"workbox-window": "^7.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/noto-sans-sc": "^5.2.8",
|
||||
"@fontsource/noto-sans-sc": "^5.2.9",
|
||||
"@tanstack/vue-virtual": "^3.13.18",
|
||||
"artalk": "^2.9.1",
|
||||
"lucide-vue-next": "^0.562.0",
|
||||
"lucide-vue-next": "^0.563.0",
|
||||
"pinia": "^3.0.4",
|
||||
"prismjs": "^1.30.0",
|
||||
"vue": "^3.5.26",
|
||||
"vue": "^3.5.27",
|
||||
"vue-prism-editor": "2.0.0-alpha.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
||||
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264"
|
||||
}
|
||||
|
||||
375
pnpm-lock.yaml
generated
375
pnpm-lock.yaml
generated
@@ -9,54 +9,54 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@fontsource/noto-sans-sc':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
specifier: ^5.2.9
|
||||
version: 5.2.9
|
||||
'@tanstack/vue-virtual':
|
||||
specifier: ^3.13.18
|
||||
version: 3.13.18(vue@3.5.26(typescript@5.9.3))
|
||||
version: 3.13.18(vue@3.5.27(typescript@5.9.3))
|
||||
artalk:
|
||||
specifier: ^2.9.1
|
||||
version: 2.9.1(marked@14.1.4)
|
||||
lucide-vue-next:
|
||||
specifier: ^0.562.0
|
||||
version: 0.562.0(vue@3.5.26(typescript@5.9.3))
|
||||
specifier: ^0.563.0
|
||||
version: 0.563.0(vue@3.5.27(typescript@5.9.3))
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
|
||||
prismjs:
|
||||
specifier: ^1.30.0
|
||||
version: 1.30.0
|
||||
vue:
|
||||
specifier: ^3.5.26
|
||||
version: 3.5.26(typescript@5.9.3)
|
||||
specifier: ^3.5.27
|
||||
version: 3.5.27(typescript@5.9.3)
|
||||
vue-prism-editor:
|
||||
specifier: 2.0.0-alpha.2
|
||||
version: 2.0.0-alpha.2(vue@3.5.26(typescript@5.9.3))
|
||||
version: 2.0.0-alpha.2(vue@3.5.27(typescript@5.9.3))
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.39.2
|
||||
version: 9.39.2
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))
|
||||
version: 4.1.18(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))
|
||||
'@types/node':
|
||||
specifier: ^25.0.3
|
||||
version: 25.0.3
|
||||
specifier: ^25.1.0
|
||||
version: 25.1.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.52.0
|
||||
version: 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
specifier: ^8.54.0
|
||||
version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.52.0
|
||||
version: 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
specifier: ^8.54.0
|
||||
version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.26(typescript@5.9.3))
|
||||
version: 6.0.3(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.27(typescript@5.9.3))
|
||||
eslint:
|
||||
specifier: ^9.39.2
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
eslint-plugin-vue:
|
||||
specifier: ^10.6.2
|
||||
version: 10.6.2(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
|
||||
specifier: ^10.7.0
|
||||
version: 10.7.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)))
|
||||
tailwindcss:
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18
|
||||
@@ -64,17 +64,17 @@ importers:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.52.0
|
||||
version: 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
specifier: ^8.54.0
|
||||
version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
vite:
|
||||
specifier: ^7.3.1
|
||||
version: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
version: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
vite-plugin-pwa:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0)
|
||||
version: 1.2.0(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0)
|
||||
vue-tsc:
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(typescript@5.9.3)
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(typescript@5.9.3)
|
||||
workbox-window:
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.0
|
||||
@@ -782,8 +782,8 @@ packages:
|
||||
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@fontsource/noto-sans-sc@5.2.8':
|
||||
resolution: {integrity: sha512-8T8HxIS3uAMCfaQawKRH/6yYZ1oRnJZB/CrGwfxGgJa+zAOBgx2lqZMiTY/WbQpLGlPRqX4zHXJYI09CI2q6tA==}
|
||||
'@fontsource/noto-sans-sc@5.2.9':
|
||||
resolution: {integrity: sha512-bTUIWGBgJDpwi5qAr+x0/lcgv80IHTB9vl6s2f6EymZEa7qYV99yNRBZuKFT+SYDKVunZrjCEhWtpxqmbXWl5Q==}
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
@@ -918,56 +918,67 @@ packages:
|
||||
resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.53.2':
|
||||
resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.53.2':
|
||||
resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.53.2':
|
||||
resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.53.2':
|
||||
resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.53.2':
|
||||
resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.53.2':
|
||||
resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.53.2':
|
||||
resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.53.2':
|
||||
resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.53.2':
|
||||
resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.53.2':
|
||||
resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.53.2':
|
||||
resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==}
|
||||
@@ -1035,24 +1046,28 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
||||
@@ -1104,8 +1119,8 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/node@25.0.3':
|
||||
resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
|
||||
'@types/node@25.1.0':
|
||||
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
@@ -1113,63 +1128,63 @@ packages:
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.52.0':
|
||||
resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==}
|
||||
'@typescript-eslint/eslint-plugin@8.54.0':
|
||||
resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.52.0
|
||||
'@typescript-eslint/parser': ^8.54.0
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/parser@8.52.0':
|
||||
resolution: {integrity: sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==}
|
||||
'@typescript-eslint/parser@8.54.0':
|
||||
resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/project-service@8.52.0':
|
||||
resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==}
|
||||
'@typescript-eslint/project-service@8.54.0':
|
||||
resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/scope-manager@8.52.0':
|
||||
resolution: {integrity: sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==}
|
||||
'@typescript-eslint/scope-manager@8.54.0':
|
||||
resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.52.0':
|
||||
resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==}
|
||||
'@typescript-eslint/tsconfig-utils@8.54.0':
|
||||
resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/type-utils@8.52.0':
|
||||
resolution: {integrity: sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==}
|
||||
'@typescript-eslint/type-utils@8.54.0':
|
||||
resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/types@8.52.0':
|
||||
resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==}
|
||||
'@typescript-eslint/types@8.54.0':
|
||||
resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.52.0':
|
||||
resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==}
|
||||
'@typescript-eslint/typescript-estree@8.54.0':
|
||||
resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/utils@8.52.0':
|
||||
resolution: {integrity: sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==}
|
||||
'@typescript-eslint/utils@8.54.0':
|
||||
resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.52.0':
|
||||
resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==}
|
||||
'@typescript-eslint/visitor-keys@8.54.0':
|
||||
resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@vitejs/plugin-vue@6.0.3':
|
||||
@@ -1191,14 +1206,20 @@ packages:
|
||||
'@vue/compiler-core@3.5.26':
|
||||
resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==}
|
||||
|
||||
'@vue/compiler-core@3.5.27':
|
||||
resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==}
|
||||
|
||||
'@vue/compiler-dom@3.5.26':
|
||||
resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.26':
|
||||
resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==}
|
||||
'@vue/compiler-dom@3.5.27':
|
||||
resolution: {integrity: sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.26':
|
||||
resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==}
|
||||
'@vue/compiler-sfc@3.5.27':
|
||||
resolution: {integrity: sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.27':
|
||||
resolution: {integrity: sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==}
|
||||
|
||||
'@vue/devtools-api@7.7.8':
|
||||
resolution: {integrity: sha512-BtFcAmDbtXGwurWUFf8ogIbgZyR+rcVES1TSNEI8Em80fD8Anu+qTRN1Fc3J6vdRHlVM3fzPV1qIo+B4AiqGzw==}
|
||||
@@ -1209,26 +1230,29 @@ packages:
|
||||
'@vue/devtools-shared@7.7.8':
|
||||
resolution: {integrity: sha512-XHpO3jC5nOgYr40M9p8Z4mmKfTvUxKyRcUnpBAYg11pE78eaRFBKb0kG5yKLroMuJeeNH9LWmKp2zMU5LUc7CA==}
|
||||
|
||||
'@vue/language-core@3.2.2':
|
||||
resolution: {integrity: sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ==}
|
||||
'@vue/language-core@3.2.4':
|
||||
resolution: {integrity: sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==}
|
||||
|
||||
'@vue/reactivity@3.5.26':
|
||||
resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==}
|
||||
'@vue/reactivity@3.5.27':
|
||||
resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==}
|
||||
|
||||
'@vue/runtime-core@3.5.26':
|
||||
resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==}
|
||||
'@vue/runtime-core@3.5.27':
|
||||
resolution: {integrity: sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==}
|
||||
|
||||
'@vue/runtime-dom@3.5.26':
|
||||
resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==}
|
||||
'@vue/runtime-dom@3.5.27':
|
||||
resolution: {integrity: sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==}
|
||||
|
||||
'@vue/server-renderer@3.5.26':
|
||||
resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==}
|
||||
'@vue/server-renderer@3.5.27':
|
||||
resolution: {integrity: sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==}
|
||||
peerDependencies:
|
||||
vue: 3.5.26
|
||||
vue: 3.5.27
|
||||
|
||||
'@vue/shared@3.5.26':
|
||||
resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==}
|
||||
|
||||
'@vue/shared@3.5.27':
|
||||
resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -1509,8 +1533,8 @@ packages:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
eslint-plugin-vue@10.6.2:
|
||||
resolution: {integrity: sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==}
|
||||
eslint-plugin-vue@10.7.0:
|
||||
resolution: {integrity: sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
@@ -1960,24 +1984,28 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
@@ -2018,8 +2046,8 @@ packages:
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
lucide-vue-next@0.562.0:
|
||||
resolution: {integrity: sha512-LN0BLGKMFulv0lnfK29r14DcngRUhIqdcaL0zXTt2o0oS9odlrjCGaU3/X9hIihOjjN8l8e+Y9G/famcNYaI7Q==}
|
||||
lucide-vue-next@0.563.0:
|
||||
resolution: {integrity: sha512-zsE/lCKtmaa7bGfhSpN84br1K9YoQ5pCN+2oKWjQQG3Lo6ufUUKBuHSjNFI6RvUevxaajNXb8XwFUKeTXG3sIA==}
|
||||
peerDependencies:
|
||||
vue: '>=3.0.1'
|
||||
|
||||
@@ -2454,8 +2482,8 @@ packages:
|
||||
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typescript-eslint@8.52.0:
|
||||
resolution: {integrity: sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==}
|
||||
typescript-eslint@8.54.0:
|
||||
resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
@@ -2580,14 +2608,14 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
vue-tsc@3.2.2:
|
||||
resolution: {integrity: sha512-r9YSia/VgGwmbbfC06hDdAatH634XJ9nVl6Zrnz1iK4ucp8Wu78kawplXnIDa3MSu1XdQQePTHLXYwPDWn+nyQ==}
|
||||
vue-tsc@3.2.4:
|
||||
resolution: {integrity: sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
|
||||
vue@3.5.26:
|
||||
resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==}
|
||||
vue@3.5.27:
|
||||
resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
@@ -3485,7 +3513,7 @@ snapshots:
|
||||
'@eslint/core': 0.17.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@fontsource/noto-sans-sc@5.2.8': {}
|
||||
'@fontsource/noto-sans-sc@5.2.9': {}
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
@@ -3721,19 +3749,19 @@ snapshots:
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
|
||||
|
||||
'@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))':
|
||||
'@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.18
|
||||
'@tailwindcss/oxide': 4.1.18
|
||||
tailwindcss: 4.1.18
|
||||
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
|
||||
'@tanstack/virtual-core@3.13.18': {}
|
||||
|
||||
'@tanstack/vue-virtual@3.13.18(vue@3.5.26(typescript@5.9.3))':
|
||||
'@tanstack/vue-virtual@3.13.18(vue@3.5.27(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.13.18
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
vue: 3.5.27(typescript@5.9.3)
|
||||
|
||||
'@types/estree@0.0.39': {}
|
||||
|
||||
@@ -3741,7 +3769,7 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/node@25.0.3':
|
||||
'@types/node@25.1.0':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
@@ -3749,14 +3777,14 @@ snapshots:
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
'@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.52.0
|
||||
'@typescript-eslint/type-utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.52.0
|
||||
'@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.54.0
|
||||
'@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.54.0
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
@@ -3765,41 +3793,41 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
'@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.52.0
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.52.0
|
||||
'@typescript-eslint/scope-manager': 8.54.0
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.54.0
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.52.0(typescript@5.9.3)':
|
||||
'@typescript-eslint/project-service@8.54.0(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
debug: 4.4.3
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/scope-manager@8.52.0':
|
||||
'@typescript-eslint/scope-manager@8.54.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/visitor-keys': 8.52.0
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
'@typescript-eslint/visitor-keys': 8.54.0
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)':
|
||||
'@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
'@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||
@@ -3807,14 +3835,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/types@8.52.0': {}
|
||||
'@typescript-eslint/types@8.54.0': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)':
|
||||
'@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/visitor-keys': 8.52.0
|
||||
'@typescript-eslint/project-service': 8.54.0(typescript@5.9.3)
|
||||
'@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3)
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
'@typescript-eslint/visitor-keys': 8.54.0
|
||||
debug: 4.4.3
|
||||
minimatch: 9.0.5
|
||||
semver: 7.7.3
|
||||
@@ -3824,27 +3852,27 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
'@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
||||
'@typescript-eslint/scope-manager': 8.52.0
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.54.0
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.52.0':
|
||||
'@typescript-eslint/visitor-keys@8.54.0':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.52.0
|
||||
'@typescript-eslint/types': 8.54.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@vitejs/plugin-vue@6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.26(typescript@5.9.3))':
|
||||
'@vitejs/plugin-vue@6.0.3(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(vue@3.5.27(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.53
|
||||
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
vue: 3.5.27(typescript@5.9.3)
|
||||
|
||||
'@volar/language-core@2.4.27':
|
||||
dependencies:
|
||||
@@ -3866,27 +3894,40 @@ snapshots:
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-core@3.5.27':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
'@vue/shared': 3.5.27
|
||||
entities: 7.0.0
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-dom@3.5.26':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.26
|
||||
'@vue/shared': 3.5.26
|
||||
|
||||
'@vue/compiler-sfc@3.5.26':
|
||||
'@vue/compiler-dom@3.5.27':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/compiler-sfc@3.5.27':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
'@vue/compiler-core': 3.5.26
|
||||
'@vue/compiler-dom': 3.5.26
|
||||
'@vue/compiler-ssr': 3.5.26
|
||||
'@vue/shared': 3.5.26
|
||||
'@vue/compiler-core': 3.5.27
|
||||
'@vue/compiler-dom': 3.5.27
|
||||
'@vue/compiler-ssr': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.21
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-ssr@3.5.26':
|
||||
'@vue/compiler-ssr@3.5.27':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.26
|
||||
'@vue/shared': 3.5.26
|
||||
'@vue/compiler-dom': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/devtools-api@7.7.8':
|
||||
dependencies:
|
||||
@@ -3906,7 +3947,7 @@ snapshots:
|
||||
dependencies:
|
||||
rfdc: 1.4.1
|
||||
|
||||
'@vue/language-core@3.2.2':
|
||||
'@vue/language-core@3.2.4':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.27
|
||||
'@vue/compiler-dom': 3.5.26
|
||||
@@ -3916,30 +3957,32 @@ snapshots:
|
||||
path-browserify: 1.0.1
|
||||
picomatch: 4.0.3
|
||||
|
||||
'@vue/reactivity@3.5.26':
|
||||
'@vue/reactivity@3.5.27':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.26
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/runtime-core@3.5.26':
|
||||
'@vue/runtime-core@3.5.27':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.26
|
||||
'@vue/shared': 3.5.26
|
||||
'@vue/reactivity': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
|
||||
'@vue/runtime-dom@3.5.26':
|
||||
'@vue/runtime-dom@3.5.27':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.26
|
||||
'@vue/runtime-core': 3.5.26
|
||||
'@vue/shared': 3.5.26
|
||||
'@vue/reactivity': 3.5.27
|
||||
'@vue/runtime-core': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))':
|
||||
'@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.5.26
|
||||
'@vue/shared': 3.5.26
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
'@vue/compiler-ssr': 3.5.27
|
||||
'@vue/shared': 3.5.27
|
||||
vue: 3.5.27(typescript@5.9.3)
|
||||
|
||||
'@vue/shared@3.5.26': {}
|
||||
|
||||
'@vue/shared@3.5.27': {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -4290,9 +4333,9 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))):
|
||||
eslint-plugin-vue@10.7.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1))):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
natural-compare: 1.4.0
|
||||
nth-check: 2.1.1
|
||||
@@ -4301,7 +4344,7 @@ snapshots:
|
||||
vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@2.6.1))
|
||||
xml-name-validator: 4.0.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
|
||||
eslint-scope@8.4.0:
|
||||
dependencies:
|
||||
@@ -4776,9 +4819,9 @@ snapshots:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
||||
lucide-vue-next@0.562.0(vue@3.5.26(typescript@5.9.3)):
|
||||
lucide-vue-next@0.563.0(vue@3.5.27(typescript@5.9.3)):
|
||||
dependencies:
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
vue: 3.5.27(typescript@5.9.3)
|
||||
|
||||
magic-string@0.25.9:
|
||||
dependencies:
|
||||
@@ -4889,10 +4932,10 @@ snapshots:
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)):
|
||||
pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 7.7.8
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
vue: 3.5.27(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
@@ -5270,12 +5313,12 @@ snapshots:
|
||||
possible-typed-array-names: 1.1.0
|
||||
reflect.getprototypeof: 1.0.10
|
||||
|
||||
typescript-eslint@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||
typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.52.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -5323,18 +5366,18 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0):
|
||||
vite-plugin-pwa@1.2.0(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))(workbox-build@7.4.0)(workbox-window@7.4.0):
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
pretty-bytes: 6.1.1
|
||||
tinyglobby: 0.2.15
|
||||
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
||||
workbox-build: 7.4.0
|
||||
workbox-window: 7.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1):
|
||||
vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1):
|
||||
dependencies:
|
||||
esbuild: 0.27.2
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -5343,7 +5386,7 @@ snapshots:
|
||||
rollup: 4.53.2
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 25.0.3
|
||||
'@types/node': 25.1.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
@@ -5363,23 +5406,23 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vue-prism-editor@2.0.0-alpha.2(vue@3.5.26(typescript@5.9.3)):
|
||||
vue-prism-editor@2.0.0-alpha.2(vue@3.5.27(typescript@5.9.3)):
|
||||
dependencies:
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
vue: 3.5.27(typescript@5.9.3)
|
||||
|
||||
vue-tsc@3.2.2(typescript@5.9.3):
|
||||
vue-tsc@3.2.4(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@volar/typescript': 2.4.27
|
||||
'@vue/language-core': 3.2.2
|
||||
'@vue/language-core': 3.2.4
|
||||
typescript: 5.9.3
|
||||
|
||||
vue@3.5.26(typescript@5.9.3):
|
||||
vue@3.5.27(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.26
|
||||
'@vue/compiler-sfc': 3.5.26
|
||||
'@vue/runtime-dom': 3.5.26
|
||||
'@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3))
|
||||
'@vue/shared': 3.5.26
|
||||
'@vue/compiler-dom': 3.5.27
|
||||
'@vue/compiler-sfc': 3.5.27
|
||||
'@vue/runtime-dom': 3.5.27
|
||||
'@vue/server-renderer': 3.5.27(vue@3.5.27(typescript@5.9.3))
|
||||
'@vue/shared': 3.5.27
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
|
||||
@@ -91,6 +91,81 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索历史管理卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center shadow-lg shadow-amber-500/30">
|
||||
<History :size="20" class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">搜索历史</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">
|
||||
共 {{ historyStore.historyCount }} 条记录
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- 导出导入按钮 -->
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-amber-600 dark:text-amber-400 font-medium bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/50 hover:bg-amber-100 dark:hover:bg-amber-950/60 active:scale-[0.98] transition-all text-sm"
|
||||
@click="exportHistory"
|
||||
>
|
||||
<Download :size="18" />
|
||||
<span>导出记录</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-amber-600 dark:text-amber-400 font-medium bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/50 hover:bg-amber-100 dark:hover:bg-amber-950/60 active:scale-[0.98] transition-all text-sm"
|
||||
@click="triggerImport"
|
||||
>
|
||||
<Upload :size="18" />
|
||||
<span>导入记录</span>
|
||||
</button>
|
||||
<!-- 隐藏的文件输入框 -->
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
type="file"
|
||||
accept=".json"
|
||||
class="hidden"
|
||||
@change="handleImportFile"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 状态提示 -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 translate-y-1"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-150 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-1"
|
||||
>
|
||||
<div
|
||||
v-if="importStatus !== 'idle'"
|
||||
:class="[
|
||||
'flex items-center gap-2 px-3 py-2 rounded-lg text-sm',
|
||||
importStatus === 'success'
|
||||
? 'bg-green-50 dark:bg-green-950/40 text-green-600 dark:text-green-400 border border-green-200 dark:border-green-800/50'
|
||||
: 'bg-red-50 dark:bg-red-950/40 text-red-600 dark:text-red-400 border border-red-200 dark:border-red-800/50'
|
||||
]"
|
||||
>
|
||||
<component
|
||||
:is="importStatus === 'success' ? CheckCircle2 : AlertCircle"
|
||||
:size="16"
|
||||
/>
|
||||
<span>{{ importMessage }}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- 说明 -->
|
||||
<div class="flex items-start gap-2 p-3 rounded-lg bg-slate-50 dark:bg-slate-800/60 text-xs text-gray-500 dark:text-slate-400">
|
||||
<FileJson :size="14" class="flex-shrink-0 mt-0.5 text-amber-500" />
|
||||
<p>导出为 JSON 格式,可用于备份或迁移到其他设备。导入时会自动去重。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API 设置卡片 -->
|
||||
<div
|
||||
class="settings-card"
|
||||
@@ -455,78 +530,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索历史管理卡片 -->
|
||||
<!-- 关于项目卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center shadow-lg shadow-amber-500/30">
|
||||
<History :size="20" class="text-white" />
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-500 to-purple-500 flex items-center justify-center shadow-lg shadow-violet-500/30">
|
||||
<Info :size="20" class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">搜索历史</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">
|
||||
共 {{ historyStore.historyCount }} 条记录
|
||||
</p>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">关于项目</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">开源仓库与贡献</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- 导出导入按钮 -->
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-amber-600 dark:text-amber-400 font-medium bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/50 hover:bg-amber-100 dark:hover:bg-amber-950/60 active:scale-[0.98] transition-all text-sm"
|
||||
@click="exportHistory"
|
||||
>
|
||||
<Download :size="18" />
|
||||
<span>导出记录</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-amber-600 dark:text-amber-400 font-medium bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800/50 hover:bg-amber-100 dark:hover:bg-amber-950/60 active:scale-[0.98] transition-all text-sm"
|
||||
@click="triggerImport"
|
||||
>
|
||||
<Upload :size="18" />
|
||||
<span>导入记录</span>
|
||||
</button>
|
||||
<!-- 隐藏的文件输入框 -->
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
type="file"
|
||||
accept=".json"
|
||||
class="hidden"
|
||||
@change="handleImportFile"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 状态提示 -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 translate-y-1"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-150 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-1"
|
||||
<!-- 仓库卡片列表 -->
|
||||
<a
|
||||
v-for="repoUrl in repoData.repositories"
|
||||
:key="repoUrl"
|
||||
:href="repoUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block rounded-xl overflow-hidden border border-gray-200 dark:border-slate-700 hover:border-violet-400 dark:hover:border-violet-500 transition-all hover:scale-[1.01] active:scale-[0.99]"
|
||||
@click="playTap"
|
||||
>
|
||||
<div
|
||||
v-if="importStatus !== 'idle'"
|
||||
:class="[
|
||||
'flex items-center gap-2 px-3 py-2 rounded-lg text-sm',
|
||||
importStatus === 'success'
|
||||
? 'bg-green-50 dark:bg-green-950/40 text-green-600 dark:text-green-400 border border-green-200 dark:border-green-800/50'
|
||||
: 'bg-red-50 dark:bg-red-950/40 text-red-600 dark:text-red-400 border border-red-200 dark:border-red-800/50'
|
||||
]"
|
||||
>
|
||||
<component
|
||||
:is="importStatus === 'success' ? CheckCircle2 : AlertCircle"
|
||||
:size="16"
|
||||
/>
|
||||
<span>{{ importMessage }}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- 说明 -->
|
||||
<div class="flex items-start gap-2 p-3 rounded-lg bg-slate-50 dark:bg-slate-800/60 text-xs text-gray-500 dark:text-slate-400">
|
||||
<FileJson :size="14" class="flex-shrink-0 mt-0.5 text-amber-500" />
|
||||
<p>导出为 JSON 格式,可用于备份或迁移到其他设备。导入时会自动去重。</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="githubHashTimestamp"
|
||||
:src="`https://opengraph.githubassets.com/${githubHashTimestamp}/${getRepoPath(repoUrl)}`"
|
||||
:alt="`${getRepoName(repoUrl)} repository`"
|
||||
class="w-full h-auto object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div v-else class="w-full aspect-[2/1] bg-slate-100 dark:bg-slate-800 animate-pulse" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -613,10 +648,53 @@ import { useSettingsStore, DEFAULT_API_CONFIG } from '@/stores/settings'
|
||||
import { useHistoryStore } from '@/stores/history'
|
||||
import type { SearchHistory } from '@/utils/persistence'
|
||||
import apiData from '@/data/api.json'
|
||||
import repoData from '@/data/repository-opengraph.json'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const historyStore = useHistoryStore()
|
||||
|
||||
// GitHub 关于项目相关
|
||||
const githubHashTimestamp = ref('')
|
||||
|
||||
// 生成 SHA-256 hash
|
||||
async function sha256(message: string): Promise<string> {
|
||||
const msgBuffer = new TextEncoder().encode(message)
|
||||
const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgBuffer)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
|
||||
// 获取当前日期的 YYMMDD 格式
|
||||
function getDateString(): string {
|
||||
const now = new Date()
|
||||
const yy = String(now.getFullYear()).slice(-2)
|
||||
const mm = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(now.getDate()).padStart(2, '0')
|
||||
return `${yy}${mm}${dd}`
|
||||
}
|
||||
|
||||
// 初始化 GitHub hash
|
||||
async function initGitHubHash() {
|
||||
const dateStr = getDateString()
|
||||
githubHashTimestamp.value = await sha256(dateStr)
|
||||
}
|
||||
|
||||
// 从 GitHub URL 提取 owner/repo 路径
|
||||
function getRepoPath(url: string): string {
|
||||
// https://github.com/Moe-Sakura/frontend -> Moe-Sakura/frontend
|
||||
return url.replace('https://github.com/', '')
|
||||
}
|
||||
|
||||
// 从 GitHub URL 提取仓库名
|
||||
function getRepoName(url: string): string {
|
||||
// https://github.com/Moe-Sakura/frontend -> frontend
|
||||
const parts = url.split('/')
|
||||
return parts[parts.length - 1]
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
void initGitHubHash()
|
||||
|
||||
// 导入导出状态
|
||||
const importStatus = ref<'idle' | 'success' | 'error'>('idle')
|
||||
const importMessage = ref('')
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
/**
|
||||
* 背景图片管理 composable
|
||||
* 负责从 API 获取图片、IndexedDB 缓存管理、洗牌队列、Ken Burns 动画等
|
||||
* 简单的定时获取随机图片,失败时保持上一张
|
||||
*
|
||||
* 自动参数:
|
||||
* - name: 跟随 VNDB 获取到的作品名
|
||||
* - maxbrightness: 暗色模式自动设为 0.5
|
||||
* - view: 移动端自动使用 vertical 模式
|
||||
*/
|
||||
|
||||
import { ref, computed, shallowRef } from 'vue'
|
||||
import { imageDB } from '@/utils/imageDB'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useSettingsStore, DEFAULT_API_CONFIG } from '@/stores/settings'
|
||||
import { useSearchStore } from '@/stores/search'
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
|
||||
// Ken Burns 动画变体类型
|
||||
export type KenBurnsType =
|
||||
@@ -18,14 +24,8 @@ export type KenBurnsType =
|
||||
|
||||
// 配置常量
|
||||
const CONFIG = {
|
||||
MAX_CACHE_SIZE: 10000, // 最大缓存 10000 张图片
|
||||
CLEANUP_BATCH_SIZE: 2000, // 每次清理 2000 张
|
||||
FETCH_INTERVAL: 5000, // 5秒获取一次(未达到缓存阈值时)
|
||||
FETCH_INTERVAL_SLOW: 30000, // 30秒获取一次(图片缓存充足时)
|
||||
CACHE_THRESHOLD: 30, // 缓存阈值,达到后切换到慢速获取
|
||||
DISPLAY_INTERVAL: 10000, // 10秒切换一次
|
||||
MAX_BLOB_URLS: 20, // 最大同时保持的 Blob URL 数量
|
||||
PRELOAD_COUNT: 10, // 预加载图片数量
|
||||
DISPLAY_INTERVAL: 30000, // 30秒切换一次
|
||||
MOBILE_BREAKPOINT: 768, // 移动端断点
|
||||
} as const
|
||||
|
||||
// Ken Burns 效果列表
|
||||
@@ -38,32 +38,27 @@ const KEN_BURNS_EFFECTS: KenBurnsType[] = [
|
||||
'kb-pan-down',
|
||||
]
|
||||
|
||||
// 检测是否为移动端
|
||||
function isMobile(): boolean {
|
||||
return window.innerWidth < CONFIG.MOBILE_BREAKPOINT
|
||||
}
|
||||
|
||||
export function useBackgroundImage() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const searchStore = useSearchStore()
|
||||
const uiStore = useUIStore()
|
||||
|
||||
// 状态
|
||||
const currentImageUrl = ref('')
|
||||
const imageCache = shallowRef<string[]>([])
|
||||
const imageCacheSet = shallowRef<Set<string>>(new Set())
|
||||
const imageBlobUrls = shallowRef<Map<string, string>>(new Map())
|
||||
const shuffledQueue = shallowRef<string[]>([])
|
||||
const currentBgKey = ref(0)
|
||||
const currentKenBurns = ref<KenBurnsType>('kb-zoom-in')
|
||||
|
||||
// 定时器
|
||||
let fetchInterval: number | null = null
|
||||
let displayInterval: number | null = null
|
||||
|
||||
// 计算属性
|
||||
const hasBackgroundImage = computed(() => !!currentImageUrl.value)
|
||||
|
||||
const backgroundImageUrl = computed(() => {
|
||||
if (currentImageUrl.value) {
|
||||
return imageBlobUrls.value.get(currentImageUrl.value) || currentImageUrl.value
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const backgroundImageUrl = computed(() => currentImageUrl.value)
|
||||
const kenBurnsClass = computed(() => currentKenBurns.value)
|
||||
|
||||
// 随机选择 Ken Burns 效果
|
||||
@@ -71,302 +66,123 @@ export function useBackgroundImage() {
|
||||
currentKenBurns.value = KEN_BURNS_EFFECTS[Math.floor(Math.random() * KEN_BURNS_EFFECTS.length)]
|
||||
}
|
||||
|
||||
// Fisher-Yates 洗牌算法
|
||||
function shuffleArray<T>(array: T[]): T[] {
|
||||
const shuffled = [...array]
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
|
||||
}
|
||||
return shuffled
|
||||
// 参数级别配置
|
||||
interface UrlOptions {
|
||||
includeName?: boolean
|
||||
includeBrightness?: boolean
|
||||
includeView?: boolean
|
||||
}
|
||||
|
||||
// 重新洗牌队列
|
||||
function reshuffleQueue() {
|
||||
if (imageCache.value.length > 0) {
|
||||
shuffledQueue.value = shuffleArray([...imageCache.value])
|
||||
// 构建 API URL(自动参数)
|
||||
function buildApiUrl(options: UrlOptions = {}): string {
|
||||
const { includeName = true, includeBrightness = true, includeView = true } = options
|
||||
const baseUrl = settingsStore.settings.backgroundImageApiUrl || DEFAULT_API_CONFIG.backgroundImageApiUrl
|
||||
const params = new URLSearchParams()
|
||||
|
||||
// 添加时间戳防止缓存
|
||||
params.set('t', Date.now().toString())
|
||||
|
||||
// 来源作品 - 跟随用户输入框内容
|
||||
if (includeName) {
|
||||
const searchQuery = searchStore.searchQuery?.trim()
|
||||
if (searchQuery) {
|
||||
params.set('name', searchQuery)
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色模式 - 自动添加最大亮度 0.5
|
||||
if (includeBrightness && uiStore.isDarkMode) {
|
||||
params.set('maxbrightness', '0.5')
|
||||
}
|
||||
|
||||
// 移动端 - 使用 vertical 视图模式
|
||||
if (includeView && isMobile()) {
|
||||
params.set('view', 'vertical')
|
||||
}
|
||||
|
||||
return `${baseUrl}?${params.toString()}`
|
||||
}
|
||||
|
||||
// 从 IndexedDB 加载缓存
|
||||
async function loadCacheFromDB(): Promise<boolean> {
|
||||
try {
|
||||
await imageDB.init()
|
||||
const urls = await imageDB.getAllUrls()
|
||||
|
||||
if (urls.length > 0) {
|
||||
const uniqueUrls = [...new Set(urls)]
|
||||
imageCache.value = uniqueUrls
|
||||
imageCacheSet.value = new Set(uniqueUrls)
|
||||
// 检查是否有来源作品参数
|
||||
function hasNameParam(): boolean {
|
||||
return !!searchStore.searchQuery?.trim()
|
||||
}
|
||||
|
||||
// 加载图片
|
||||
function loadImage(apiUrl: string, onSuccess: () => void, onError: () => void) {
|
||||
const img = new Image()
|
||||
img.onload = onSuccess
|
||||
img.onerror = onError
|
||||
img.src = apiUrl
|
||||
}
|
||||
|
||||
// 更新背景图显示
|
||||
function updateBackground(apiUrl: string) {
|
||||
selectRandomKenBurns()
|
||||
currentImageUrl.value = apiUrl
|
||||
currentBgKey.value++
|
||||
}
|
||||
|
||||
// 获取并显示下一张图片(三级重试)
|
||||
function fetchNextImage() {
|
||||
// 第一级:所有参数
|
||||
const url1 = buildApiUrl({ includeName: true, includeBrightness: true, includeView: true })
|
||||
|
||||
loadImage(url1, () => updateBackground(url1), () => {
|
||||
// 第二级:只保留 name,去掉亮度和视图模式
|
||||
if (hasNameParam()) {
|
||||
const url2 = buildApiUrl({ includeName: true, includeBrightness: false, includeView: false })
|
||||
|
||||
// 预加载部分图片的 Blob URL
|
||||
const preloadCount = Math.min(CONFIG.PRELOAD_COUNT, uniqueUrls.length)
|
||||
const newBlobUrls = new Map(imageBlobUrls.value)
|
||||
loadImage(url2, () => updateBackground(url2), () => {
|
||||
// 第三级:去掉 name(只保留时间戳)
|
||||
const url3 = buildApiUrl({ includeName: false, includeBrightness: false, includeView: false })
|
||||
|
||||
loadImage(url3, () => updateBackground(url3), () => {
|
||||
// 全部失败,保持当前图片不变
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 没有 name 参数,直接尝试基础 URL
|
||||
const url3 = buildApiUrl({ includeName: false, includeBrightness: false, includeView: false })
|
||||
|
||||
for (let i = 0; i < preloadCount; i++) {
|
||||
const url = uniqueUrls[i]
|
||||
const blob = await imageDB.getImageByUrl(url)
|
||||
if (blob) {
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
newBlobUrls.set(url, blobUrl)
|
||||
}
|
||||
}
|
||||
imageBlobUrls.value = newBlobUrls
|
||||
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// 静默处理错误
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 从 API 获取图片并添加到缓存
|
||||
async function fetchAndCacheImage() {
|
||||
try {
|
||||
const timestamp = Date.now()
|
||||
const baseUrl = settingsStore.settings.backgroundImageApiUrl || DEFAULT_API_CONFIG.backgroundImageApiUrl
|
||||
const apiUrl = `${baseUrl}?t=${timestamp}`
|
||||
|
||||
const response = await fetch(apiUrl)
|
||||
if (!response.ok) {return}
|
||||
|
||||
const finalUrl = response.url
|
||||
|
||||
// 检查是否已存在
|
||||
if (imageCacheSet.value.has(finalUrl)) {return}
|
||||
|
||||
const existsInDB = await imageDB.hasUrl(finalUrl)
|
||||
if (existsInDB) {return}
|
||||
|
||||
const blob = await response.blob()
|
||||
|
||||
// 验证是否为有效图片
|
||||
const img = new Image()
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
img.onload = async () => {
|
||||
if (!imageCacheSet.value.has(finalUrl)) {
|
||||
try {
|
||||
await imageDB.addImage(finalUrl, blob)
|
||||
|
||||
// 更新缓存
|
||||
const newCache = [...imageCache.value, finalUrl]
|
||||
const newCacheSet = new Set(imageCacheSet.value)
|
||||
newCacheSet.add(finalUrl)
|
||||
const newBlobUrls = new Map(imageBlobUrls.value)
|
||||
newBlobUrls.set(finalUrl, blobUrl)
|
||||
|
||||
imageCache.value = newCache
|
||||
imageCacheSet.value = newCacheSet
|
||||
imageBlobUrls.value = newBlobUrls
|
||||
|
||||
// 限制缓存大小
|
||||
await cleanupCacheIfNeeded()
|
||||
|
||||
// 如果刚好达到阈值,重启获取定时器以切换到慢速模式
|
||||
if (newCache.length === CONFIG.CACHE_THRESHOLD) {
|
||||
startFetchInterval()
|
||||
}
|
||||
|
||||
// 如果队列为空,重新洗牌
|
||||
if (shuffledQueue.value.length === 0) {
|
||||
reshuffleQueue()
|
||||
}
|
||||
} catch {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
}
|
||||
} else {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
}
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
}
|
||||
|
||||
img.src = blobUrl
|
||||
} catch {
|
||||
// 静默处理错误
|
||||
}
|
||||
}
|
||||
|
||||
// 清理缓存(如果超过最大大小)
|
||||
async function cleanupCacheIfNeeded() {
|
||||
const count = await imageDB.getCount()
|
||||
if (count <= CONFIG.MAX_CACHE_SIZE) {return}
|
||||
|
||||
const deletedCount = await imageDB.deleteOldestBatch(CONFIG.CLEANUP_BATCH_SIZE)
|
||||
|
||||
// 同步更新内存缓存
|
||||
const cleanedCache = imageCache.value.slice(deletedCount)
|
||||
const cleanedSet = new Set(cleanedCache)
|
||||
const cleanedBlobUrls = new Map<string, string>()
|
||||
|
||||
// 清理被删除图片的 Blob URL
|
||||
for (let i = 0; i < deletedCount; i++) {
|
||||
const removed = imageCache.value[i]
|
||||
if (removed) {
|
||||
const oldBlobUrl = imageBlobUrls.value.get(removed)
|
||||
if (oldBlobUrl) {
|
||||
URL.revokeObjectURL(oldBlobUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保留剩余的 Blob URL
|
||||
imageBlobUrls.value.forEach((url, key) => {
|
||||
if (cleanedSet.has(key)) {
|
||||
cleanedBlobUrls.set(key, url)
|
||||
loadImage(url3, () => updateBackground(url3), () => {
|
||||
// 失败,保持当前图片不变
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
imageCache.value = cleanedCache
|
||||
imageCacheSet.value = cleanedSet
|
||||
imageBlobUrls.value = cleanedBlobUrls
|
||||
}
|
||||
|
||||
// 清理过多的 Blob URL 以释放内存
|
||||
function cleanupBlobUrls(currentUrl: string) {
|
||||
const blobUrls = imageBlobUrls.value
|
||||
if (blobUrls.size <= CONFIG.MAX_BLOB_URLS) {return}
|
||||
|
||||
const newBlobUrls = new Map<string, string>()
|
||||
const currentBlobUrl = blobUrls.get(currentUrl)
|
||||
if (currentBlobUrl) {
|
||||
newBlobUrls.set(currentUrl, currentBlobUrl)
|
||||
}
|
||||
|
||||
const entries = Array.from(blobUrls.entries())
|
||||
const toKeep = entries.slice(-CONFIG.MAX_BLOB_URLS + 1)
|
||||
|
||||
for (const [url, blobUrl] of entries) {
|
||||
if (url !== currentUrl && !toKeep.some(([u]) => u === url)) {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
} else if (url !== currentUrl) {
|
||||
newBlobUrls.set(url, blobUrl)
|
||||
}
|
||||
}
|
||||
|
||||
imageBlobUrls.value = newBlobUrls
|
||||
}
|
||||
|
||||
// 显示下一张图片
|
||||
async function displayNextImage() {
|
||||
// 如果队列为空,重新洗牌
|
||||
if (shuffledQueue.value.length === 0) {
|
||||
if (imageCache.value.length === 0) {return}
|
||||
reshuffleQueue()
|
||||
}
|
||||
|
||||
const queue = [...shuffledQueue.value]
|
||||
const nextImageUrl = queue.shift()
|
||||
shuffledQueue.value = queue
|
||||
|
||||
if (!nextImageUrl) {return}
|
||||
|
||||
try {
|
||||
// 检查是否已有 Blob URL
|
||||
let blobUrl = imageBlobUrls.value.get(nextImageUrl)
|
||||
|
||||
// 如果没有,从 IndexedDB 加载
|
||||
if (!blobUrl) {
|
||||
const blob = await imageDB.getImageByUrl(nextImageUrl)
|
||||
if (blob) {
|
||||
blobUrl = URL.createObjectURL(blob)
|
||||
const newBlobUrls = new Map(imageBlobUrls.value)
|
||||
newBlobUrls.set(nextImageUrl, blobUrl)
|
||||
imageBlobUrls.value = newBlobUrls
|
||||
}
|
||||
}
|
||||
|
||||
// 预加载图片
|
||||
const preloadImg = new Image()
|
||||
preloadImg.onload = () => {
|
||||
selectRandomKenBurns()
|
||||
currentImageUrl.value = nextImageUrl
|
||||
currentBgKey.value++
|
||||
cleanupBlobUrls(nextImageUrl)
|
||||
}
|
||||
preloadImg.onerror = () => {
|
||||
void displayNextImage()
|
||||
}
|
||||
preloadImg.src = blobUrl || nextImageUrl
|
||||
} catch {
|
||||
void displayNextImage()
|
||||
}
|
||||
}
|
||||
|
||||
// 启动图片获取定时器
|
||||
function startFetchInterval() {
|
||||
if (fetchInterval) {
|
||||
clearInterval(fetchInterval)
|
||||
}
|
||||
|
||||
void fetchAndCacheImage()
|
||||
|
||||
// 根据缓存数量决定获取间隔
|
||||
const interval = imageCache.value.length >= CONFIG.CACHE_THRESHOLD
|
||||
? CONFIG.FETCH_INTERVAL_SLOW
|
||||
: CONFIG.FETCH_INTERVAL
|
||||
|
||||
fetchInterval = window.setInterval(() => {
|
||||
void fetchAndCacheImage()
|
||||
}, interval)
|
||||
}
|
||||
|
||||
// 启动图片显示定时器
|
||||
function startDisplayInterval() {
|
||||
// 启动定时器
|
||||
function startInterval() {
|
||||
if (displayInterval) {
|
||||
clearInterval(displayInterval)
|
||||
}
|
||||
|
||||
void displayNextImage()
|
||||
// 立即获取第一张
|
||||
fetchNextImage()
|
||||
|
||||
// 定时获取下一张
|
||||
displayInterval = window.setInterval(() => {
|
||||
void displayNextImage()
|
||||
fetchNextImage()
|
||||
}, CONFIG.DISPLAY_INTERVAL)
|
||||
}
|
||||
|
||||
// 停止所有定时器
|
||||
function stopAllIntervals() {
|
||||
if (fetchInterval) {
|
||||
clearInterval(fetchInterval)
|
||||
fetchInterval = null
|
||||
}
|
||||
// 停止定时器
|
||||
function stopInterval() {
|
||||
if (displayInterval) {
|
||||
clearInterval(displayInterval)
|
||||
displayInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
// 清理所有 Blob URL
|
||||
function cleanupAllBlobUrls() {
|
||||
imageBlobUrls.value.forEach(blobUrl => {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
})
|
||||
imageBlobUrls.value = new Map()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
async function init() {
|
||||
await imageDB.init()
|
||||
|
||||
const hasCachedImages = await loadCacheFromDB()
|
||||
|
||||
if (hasCachedImages) {
|
||||
reshuffleQueue()
|
||||
}
|
||||
|
||||
startFetchInterval()
|
||||
startDisplayInterval()
|
||||
startInterval()
|
||||
}
|
||||
|
||||
// 销毁
|
||||
function destroy() {
|
||||
stopAllIntervals()
|
||||
cleanupAllBlobUrls()
|
||||
imageDB.close()
|
||||
stopInterval()
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -384,4 +200,3 @@ export function useBackgroundImage() {
|
||||
destroy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
},
|
||||
{
|
||||
"key": "us-westapi",
|
||||
"label": "洛杉矶 ClawCloud",
|
||||
"label": "圣克拉拉 ClawCloud",
|
||||
"url": "https://us-west.api.searchgal.top"
|
||||
},
|
||||
{
|
||||
"key": "us-eastapi",
|
||||
"label": "弗吉尼亚州 ClawCloud",
|
||||
"label": "阿什本 ClawCloud",
|
||||
"url": "https://us-east.api.searchgal.top"
|
||||
},
|
||||
{
|
||||
|
||||
6
src/data/repository-opengraph.json
Normal file
6
src/data/repository-opengraph.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"repositories": [
|
||||
"https://github.com/Moe-Sakura/frontend",
|
||||
"https://github.com/Moe-Sakura/SearchGal"
|
||||
]
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
/**
|
||||
* IndexedDB 图片缓存管理
|
||||
* 用于存储随机背景图片
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// IndexedDB 事务回调中 result 在 onsuccess 时必定存在
|
||||
|
||||
const DB_NAME = 'RandomImageCache'
|
||||
const DB_VERSION = 1
|
||||
const STORE_NAME = 'images'
|
||||
|
||||
export interface ImageRecord {
|
||||
id: number;
|
||||
url: string;
|
||||
blob: Blob;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
class ImageDB {
|
||||
private db: IDBDatabase | null = null
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('无法打开 IndexedDB'))
|
||||
}
|
||||
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result
|
||||
resolve()
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result
|
||||
|
||||
// 创建对象存储
|
||||
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||
const objectStore = db.createObjectStore(STORE_NAME, {
|
||||
keyPath: 'id',
|
||||
autoIncrement: true,
|
||||
})
|
||||
|
||||
// 创建索引
|
||||
objectStore.createIndex('url', 'url', { unique: true })
|
||||
objectStore.createIndex('timestamp', 'timestamp', { unique: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加图片到数据库
|
||||
*/
|
||||
async addImage(url: string, blob: Blob): Promise<number> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readwrite')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
|
||||
const record: Omit<ImageRecord, 'id'> = {
|
||||
url,
|
||||
blob,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
|
||||
const request = objectStore.add(record)
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result as number)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('添加图片失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 URL 是否已存在
|
||||
*/
|
||||
async hasUrl(url: string): Promise<boolean> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readonly')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
const index = objectStore.index('url')
|
||||
|
||||
const request = index.getKey(url)
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result !== undefined)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('检查 URL 失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有图片 URL
|
||||
*/
|
||||
async getAllUrls(): Promise<string[]> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readonly')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
|
||||
const request = objectStore.getAll()
|
||||
|
||||
request.onsuccess = () => {
|
||||
const records = request.result as ImageRecord[]
|
||||
const urls = records.map(record => record.url)
|
||||
resolve(urls)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('获取 URL 列表失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有图片记录
|
||||
*/
|
||||
async getAllRecords(): Promise<ImageRecord[]> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readonly')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
|
||||
const request = objectStore.getAll()
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result as ImageRecord[])
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('获取记录失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 URL 获取图片 Blob
|
||||
*/
|
||||
async getImageByUrl(url: string): Promise<Blob | null> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readonly')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
const index = objectStore.index('url')
|
||||
|
||||
const request = index.get(url)
|
||||
|
||||
request.onsuccess = () => {
|
||||
const record = request.result as ImageRecord | undefined
|
||||
resolve(record ? record.blob : null)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('获取图片失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库中的图片数量
|
||||
*/
|
||||
async getCount(): Promise<number> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readonly')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
|
||||
const request = objectStore.count()
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result)
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('获取数量失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除最旧的图片
|
||||
*/
|
||||
async deleteOldest(): Promise<void> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readwrite')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
const index = objectStore.index('timestamp')
|
||||
|
||||
// 获取最旧的记录
|
||||
const request = index.openCursor()
|
||||
|
||||
request.onsuccess = () => {
|
||||
const cursor = request.result
|
||||
if (cursor) {
|
||||
cursor.delete()
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('删除失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除最旧的 N 张图片
|
||||
* @param count 要删除的图片数量
|
||||
*/
|
||||
async deleteOldestBatch(count: number): Promise<number> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readwrite')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
const index = objectStore.index('timestamp')
|
||||
|
||||
let deletedCount = 0
|
||||
const request = index.openCursor()
|
||||
|
||||
request.onsuccess = () => {
|
||||
const cursor = request.result
|
||||
if (cursor && deletedCount < count) {
|
||||
cursor.delete()
|
||||
deletedCount++
|
||||
cursor.continue()
|
||||
} else {
|
||||
resolve(deletedCount)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('批量删除失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有图片
|
||||
*/
|
||||
async clear(): Promise<void> {
|
||||
if (!this.db) {await this.init()}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readwrite')
|
||||
const objectStore = transaction.objectStore(STORE_NAME)
|
||||
|
||||
const request = objectStore.clear()
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve()
|
||||
}
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('清空失败'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭数据库连接
|
||||
*/
|
||||
close(): void {
|
||||
if (this.db) {
|
||||
this.db.close()
|
||||
this.db = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const imageDB = new ImageDB()
|
||||
|
||||
Reference in New Issue
Block a user