mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-17 04:13:17 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c561ff98f | ||
|
|
817ae298b1 | ||
|
|
92d2e847bd | ||
|
|
d0f12446a8 | ||
|
|
a07d673776 | ||
|
|
083381f2ec | ||
|
|
040f49a33e | ||
|
|
3cfa00a2f6 | ||
|
|
f6068e5bac | ||
|
|
557d68956e | ||
|
|
2f14405cab | ||
|
|
2803d06418 | ||
|
|
298096858f | ||
|
|
7e11b467d1 | ||
|
|
7cd66ffb2d | ||
|
|
5de9c0a76f | ||
|
|
9a3b2ff9b9 | ||
|
|
afa0ba190b | ||
|
|
6316cc42b2 | ||
|
|
abb0a6e751 | ||
|
|
c89dfae2f7 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -43,11 +43,11 @@ jobs:
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.16.0
|
||||
node-version: 22.0.0
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 9.0.5
|
||||
version: 9.1.0
|
||||
- name: remove lockfile
|
||||
run: rm pnpm-lock.yaml
|
||||
- name: Install frontend dependencies
|
||||
|
||||
4
.github/workflows/qodana_code_quality.yml
vendored
4
.github/workflows/qodana_code_quality.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 21.4.0
|
||||
node-version: 22.0.0
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 9.0.5
|
||||
version: 9.1.0
|
||||
- name: remove lockfile
|
||||
run: rm -f pnpm-lock.yaml
|
||||
- name: Install dependencies
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -2,12 +2,25 @@
|
||||
Author: 目棃
|
||||
Description: CHANGELOG
|
||||
Date: 2024-01-15
|
||||
Update: 2024-04-24
|
||||
Update: 2024-05-10
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2024-01-15 17:29:15`
|
||||
>
|
||||
> 更新于 `2024-04-24 15:36:42`
|
||||
> 更新于 `2024-05-10 23:55:15`
|
||||
|
||||
## [0.4.7](https://github.com/BTMuli/TeyvatGuide/releases/v0.4.7) (2024-05-10)
|
||||
|
||||
- 🐛 修复 mention 类型渲染异常
|
||||
- ⚡️ 对签到链接跳转进行处理,优化部分网页活动打开
|
||||
- ♻️ 重构用户祈愿数据库相关处理
|
||||
- 🔥 隐藏网页登录模块 [`#108`](https://github.com/BTMuli/TeyvatGuide/issues/108)
|
||||
- ✨ 采用 ajv 验证 UIGF [`#109`](https://github.com/BTMuli/TeyvatGuide/issues/109)
|
||||
- 💄 完善公告`table`&`p`部分的渲染
|
||||
- 💄 调整帖子文本部分的样式
|
||||
- ♻️ UIAF重构,支持祈愿备份/恢复
|
||||
- ⚡️ 完善公告正则
|
||||
- ♻️ 名片组件抽离,wiki添加名片信息
|
||||
|
||||
## [0.4.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.4.6) (2024-04-24)
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ const pkgJsonConfig = {
|
||||
"version",
|
||||
"description",
|
||||
"type",
|
||||
"packageManager",
|
||||
"scripts",
|
||||
"lint-staged",
|
||||
"keywords",
|
||||
|
||||
34
package.json
34
package.json
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "TeyvatGuide",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"description": "Game Tool for Genshin Impact player",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
@@ -68,6 +67,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"@tauri-apps/api": "^1.5.4",
|
||||
"ajv": "^8.13.0",
|
||||
"artplayer": "^5.1.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"color-convert": "^2.0.1",
|
||||
@@ -80,42 +80,42 @@
|
||||
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1",
|
||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.4.24",
|
||||
"vue-echarts": "^6.7.0",
|
||||
"vue": "^3.4.26",
|
||||
"vue-echarts": "^6.7.1",
|
||||
"vue-json-viewer": "^3.0.4",
|
||||
"vue-router": "^4.3.2",
|
||||
"vuetify": "^3.5.16",
|
||||
"vuetify": "^3.6.3",
|
||||
"wcag-color": "^1.1.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.0.2",
|
||||
"@eslint/js": "^9.1.1",
|
||||
"@tauri-apps/cli": "^1.5.11",
|
||||
"@eslint/js": "^9.2.0",
|
||||
"@tauri-apps/cli": "^1.5.12",
|
||||
"@types/color-convert": "^2.0.3",
|
||||
"@types/js-md5": "^0.7.2",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/node": "^20.12.8",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@typescript-eslint/parser": "^7.7.1",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^9.1.1",
|
||||
"eslint": "^9.2.0",
|
||||
"eslint-config-love": "^47.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsonc": "^2.15.1",
|
||||
"eslint-plugin-n": "^17.2.1",
|
||||
"eslint-plugin-n": "^17.4.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"eslint-plugin-yml": "^1.14.0",
|
||||
"globals": "^15.0.0",
|
||||
"globals": "^15.1.0",
|
||||
"husky": "^9.0.11",
|
||||
"jsonc-eslint-parser": "^2.4.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"oxlint": "^0.3.1",
|
||||
"oxlint": "^0.3.2",
|
||||
"prettier": "3.2.5",
|
||||
"stylelint": "^16.3.1",
|
||||
"stylelint": "^16.5.0",
|
||||
"stylelint-config-idiomatic-order": "^10.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^2.8.0",
|
||||
@@ -123,9 +123,9 @@
|
||||
"stylelint-order": "^6.0.4",
|
||||
"stylelint-prettier": "^5.0.0",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^7.7.1",
|
||||
"vite": "^5.2.10",
|
||||
"vite-plugin-vue-devtools": "^7.0.27",
|
||||
"typescript-eslint": "^7.8.0",
|
||||
"vite": "^5.2.11",
|
||||
"vite-plugin-vue-devtools": "^7.1.3",
|
||||
"vite-plugin-vuetify": "^2.0.3",
|
||||
"vue-eslint-parser": "^9.4.2",
|
||||
"yaml-eslint-parser": "^1.2.2"
|
||||
|
||||
922
pnpm-lock.yaml
generated
922
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 20 KiB |
122
src-tauri/Cargo.lock
generated
122
src-tauri/Cargo.lock
generated
@@ -4,7 +4,7 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "TeyvatGuide"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
@@ -202,6 +202,12 @@ version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
@@ -270,7 +276,7 @@ dependencies = [
|
||||
"proc-macro-crate 3.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
"syn_derive",
|
||||
]
|
||||
|
||||
@@ -401,9 +407,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.94"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
|
||||
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
@@ -698,17 +704,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -732,7 +738,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -743,7 +749,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -775,7 +781,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1082,7 +1088,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1174,7 +1180,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2371,9 +2377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
|
||||
checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60"
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
@@ -2457,7 +2463,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2700,7 +2706,7 @@ dependencies = [
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2865,9 +2871,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.80"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -3224,9 +3230,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.32"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
@@ -3346,22 +3352,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3384,7 +3390,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3410,11 +3416,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.7.0"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
|
||||
checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.0",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
@@ -3428,14 +3434,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.7.0"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
|
||||
checksum = "c8b3a576c4eb2924262d5951a3b737ccaf16c931e39a2810c36f9a7e25575557"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3903,9 +3909,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.59"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3921,7 +3927,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3992,9 +3998,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.16.8"
|
||||
version = "0.16.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26a794e476ce829420b58059f4ac23c2b991dab2ee552be740f931aea95ae9c8"
|
||||
checksum = "575c856fc21e551074869dcfaad8f706412bd5b803dfa0fbf6881c4ff4bfafab"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cairo-rs",
|
||||
@@ -4073,9 +4079,9 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.6.1"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f078117725e36d55d29fafcbb4b1e909073807ca328ae8deb8c0b3843aac0fed"
|
||||
checksum = "047aefcc7721bfb8024a9bc39d4719112262610502de7a224fa62c4570cd78d4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -4089,7 +4095,7 @@ dependencies = [
|
||||
"glib",
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"ignore",
|
||||
"indexmap 1.9.3",
|
||||
@@ -4346,22 +4352,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.58"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4520,7 +4526,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.9",
|
||||
"toml_edit 0.22.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4558,9 +4564,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.9"
|
||||
version = "0.22.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
||||
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
@@ -4595,7 +4601,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4858,7 +4864,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -4892,7 +4898,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -5118,11 +5124,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5557,9 +5563,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.24.7"
|
||||
version = "0.24.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4"
|
||||
checksum = "a04e72739ee84a218e3dbf8625888eadc874285637003ed21ab96a1bbbb538ec"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"block",
|
||||
@@ -5668,7 +5674,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.59",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "TeyvatGuide"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
description = "Game Tool for Genshin Impact player"
|
||||
authors = ["BTMuli <bt-muli@outlook.com>"]
|
||||
license = "MIT"
|
||||
@@ -15,9 +15,9 @@ tauri-build = { version = "1.4", features = [] }
|
||||
[dependencies]
|
||||
chrono = "0.4.38"
|
||||
log = "0.4.21"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
tauri = { version = "1.6.1", features = [ "shell-execute", "shell-open", "window-set-always-on-top", "window-set-fullscreen", "dialog-message", "process-exit", "fs-read-dir", "window-hide", "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request"] }
|
||||
tauri = { version = "1.6.2", features = [ "shell-execute", "shell-open", "window-set-always-on-top", "window-set-fullscreen", "dialog-message", "process-exit", "fs-read-dir", "window-hide", "os-all", "clipboard-all", "dialog-open", "dialog-save", "fs-create-dir", "fs-remove-dir", "fs-write-file", "fs-remove-file", "fs-read-file", "path-all", "fs-exists", "window-close", "window-set-title", "window-unminimize", "window-show", "window-set-focus", "http-request"] }
|
||||
tauri-utils = "1.5.3"
|
||||
url = "2.5.0"
|
||||
walkdir = "2.5.0"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "TeyvatGuide",
|
||||
"version": "0.4.6"
|
||||
"version": "0.4.7"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
25
src/App.vue
25
src/App.vue
@@ -189,13 +189,26 @@ async function getDeepLink(): Promise<UnlistenFn> {
|
||||
e.payload.startsWith("teyvatguide://import_uiaf")
|
||||
) {
|
||||
await toUIAF(e.payload);
|
||||
} else {
|
||||
showSnackbar({
|
||||
text: "无效的 deep link!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.payload.startsWith("router?path=")) {
|
||||
const routerPath = e.payload.replace("router?path=", "");
|
||||
if (router.currentRoute.value.path === routerPath) {
|
||||
showSnackbar({
|
||||
text: "已在当前页面!",
|
||||
color: "warn",
|
||||
timeout: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await router.push(routerPath);
|
||||
return;
|
||||
}
|
||||
showSnackbar({
|
||||
text: "无效的 deep link!",
|
||||
color: "error",
|
||||
timeout: 3000,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="outlined" @click="scan = true" icon="mdi-qrcode-scan" />
|
||||
<v-btn variant="outlined" @click="toWebLogin" icon="mdi-web" />
|
||||
<v-btn v-if="false" variant="outlined" @click="toWebLogin" icon="mdi-web" />
|
||||
<v-btn variant="outlined" @click="confirmRefreshUser" icon="mdi-refresh" :loading="loading" />
|
||||
</template>
|
||||
</v-card>
|
||||
|
||||
@@ -29,6 +29,7 @@ const data = reactive<TGApp.Component.Snackbar.Params>({
|
||||
text: "",
|
||||
});
|
||||
const show = ref<boolean>(false);
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -66,9 +66,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha.js";
|
||||
|
||||
interface GachaDataViewProps {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
|
||||
dataVal: TGApp.Sqlite.GachaRecords.SingleTable[];
|
||||
@@ -179,8 +180,9 @@ function getStar5Avg(): string {
|
||||
}
|
||||
|
||||
// 获取物品图标
|
||||
function getIcon(itemId: string, type: string): string {
|
||||
if (type === "角色") {
|
||||
function getIcon(itemId: string): string {
|
||||
const type = TSUserGacha.getGachaItemType(itemId);
|
||||
if (type[0] === "角色") {
|
||||
return "/WIKI/character/" + itemId + ".webp";
|
||||
} else {
|
||||
return "/WIKI/weapon/" + itemId + ".webp";
|
||||
|
||||
@@ -183,9 +183,9 @@ function getBox(id: number): TItemBoxData {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.gro-tabs {
|
||||
@@ -194,8 +194,8 @@ function getBox(id: number): TItemBoxData {
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.v-tabs.v-slide-group--vertical {
|
||||
height: 100%;
|
||||
.gro-container :deep(.v-tabs.v-slide-group--vertical) {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.gro-window {
|
||||
@@ -210,6 +210,11 @@ function getBox(id: number): TItemBoxData {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.gro-window :deep(.v-window__container) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gro-pools {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -69,22 +69,21 @@ onMounted(async () => {
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.hta-tt-box {
|
||||
width: calc(100% - 10px);
|
||||
display: flex;
|
||||
height: 100%;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.hta-tt-tab {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
color: var(--box-text-4);
|
||||
}
|
||||
|
||||
.hta-tt-window {
|
||||
overflow: auto;
|
||||
width: calc(100% - 100px);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 130px);
|
||||
margin-left: 100px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,19 +43,20 @@ onMounted(async () => {
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.hta-tu-box {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-top: 10px;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.hta-tu-tab {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
color: var(--box-text-4);
|
||||
}
|
||||
|
||||
.hta-tu-window {
|
||||
width: calc(100% - 100px);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.hta-tu-grid {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="hta-tu-box">
|
||||
<v-tabs v-model="tab" direction="vertical" class="hta-tu-tab">
|
||||
<div class="hta-tus-box">
|
||||
<v-tabs v-model="tab" direction="vertical" class="hta-tus-tab">
|
||||
<v-tab value="9">第09层</v-tab>
|
||||
<v-tab value="10">第10层</v-tab>
|
||||
<v-tab value="11">第11层</v-tab>
|
||||
<v-tab value="12">第12层</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="hta-tu-window">
|
||||
<v-window v-model="tab" class="hta-tus-window">
|
||||
<v-window-item
|
||||
v-for="selectItem in select"
|
||||
:key="selectItem.Floor"
|
||||
:value="selectItem.Floor.toString()"
|
||||
>
|
||||
<div class="hta-tu-grid">
|
||||
<div class="hta-tus-grid">
|
||||
<TibWikiAbyss v-for="item in selectItem.Ranks" :key="item.Item" :model-value="item" />
|
||||
</div>
|
||||
</v-window-item>
|
||||
@@ -42,23 +42,25 @@ onMounted(async () => {
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.hta-tu-box {
|
||||
.hta-tus-box {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-top: 10px;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.hta-tu-tab {
|
||||
position: absolute;
|
||||
.hta-tus-tab {
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
color: var(--box-text-4);
|
||||
}
|
||||
|
||||
.hta-tu-window {
|
||||
width: calc(100% - 100px);
|
||||
.hta-tus-window {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.hta-tu-grid {
|
||||
.hta-tus-grid {
|
||||
display: grid;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
|
||||
@@ -50,6 +50,7 @@ const visible = computed({
|
||||
emits("update:modelValue", value);
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-undef
|
||||
let cycleTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
const qrCode = ref<string>("");
|
||||
|
||||
45
src/components/overlay/top-namecard.vue
Normal file
45
src/components/overlay/top-namecard.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<v-list
|
||||
:style="{ backgroundImage: props.data.name === '原神·印象' ? 'none' : `url(${props.data.bg})` }"
|
||||
class="top-nc-box"
|
||||
@click="toNameCard(props.data)"
|
||||
>
|
||||
<v-list-item :title="props.data.name">
|
||||
<template #subtitle>
|
||||
<span :title="props.data.desc">{{ props.data.desc }}</span>
|
||||
</template>
|
||||
<template #prepend>
|
||||
<v-img width="80px" style="margin-right: 10px" :src="props.data.icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface TopNamecardProps {
|
||||
data: TGApp.App.NameCard.Item;
|
||||
}
|
||||
|
||||
interface TopNamecardEmits {
|
||||
(e: "selected", data: TGApp.App.NameCard.Item): void;
|
||||
}
|
||||
|
||||
const props = defineProps<TopNamecardProps>();
|
||||
const emit = defineEmits<TopNamecardEmits>();
|
||||
|
||||
function toNameCard(item: TGApp.App.NameCard.Item) {
|
||||
emit("selected", item);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.top-nc-box {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px 50px 50px 10px;
|
||||
background-color: var(--box-bg-1);
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@ import TGClient from "../../utils/TGClient";
|
||||
import showConfirm from "../func/confirm";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
|
||||
interface TpMention {
|
||||
export interface TpMention {
|
||||
insert: {
|
||||
mention: {
|
||||
uid: string;
|
||||
|
||||
@@ -31,6 +31,11 @@ function getParsedData(data: TGApp.Plugins.Mys.SctPost.Base[]): TGApp.Plugins.My
|
||||
let cur: TGApp.Plugins.Mys.SctPost.Base | undefined;
|
||||
for (const tp of data) {
|
||||
const tpName = getTpName(tp);
|
||||
// 单独处理 TpMention
|
||||
if (tpName === TpMention) {
|
||||
child.push(tp);
|
||||
continue;
|
||||
}
|
||||
if (tpName !== TpText) {
|
||||
cur = tp;
|
||||
child = [];
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:title="props.data.attributes?.link"
|
||||
:style="getTextStyle()"
|
||||
>
|
||||
<v-icon size="small">mdi-link-variant</v-icon>
|
||||
<v-icon size="small" v-if="!props.data.insert.startsWith('>>')">mdi-link-variant</v-icon>
|
||||
<span>{{ props.data.insert }}</span>
|
||||
</div>
|
||||
<span v-else-if="mode == 'emoji'" class="tp-text-emoji">
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<template>
|
||||
<div :style="getLineStyle()" class="tp-texts">
|
||||
<TpText v-for="(text, index) in props.data.children" :data="text" :key="index" />
|
||||
<div :style="getLineStyle()" :class="getClass()" :title="getTitle()">
|
||||
<component
|
||||
:is="getComp(text)"
|
||||
v-for="(text, index) in props.data.children"
|
||||
:data="text"
|
||||
:key="index"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { StyleValue } from "vue";
|
||||
|
||||
import TpMention, { type TpMention as TpMentionType } from "./tp-mention.vue";
|
||||
import TpText, { type TpText as TpTextType } from "./tp-text.vue";
|
||||
|
||||
interface TpTexts extends TpTextType {
|
||||
children: TpTextType[];
|
||||
children: (TpTextType | TpMentionType)[];
|
||||
}
|
||||
|
||||
interface TpTextsProps {
|
||||
@@ -18,22 +24,39 @@ interface TpTextsProps {
|
||||
|
||||
const props = defineProps<TpTextsProps>();
|
||||
|
||||
function getComp(text: TpTextType | TpMentionType): string {
|
||||
if (typeof text.insert === "string") {
|
||||
return TpText;
|
||||
}
|
||||
return TpMention;
|
||||
}
|
||||
|
||||
function getClass(): string {
|
||||
if (props.data.attributes && props.data.attributes.header) {
|
||||
return `tp-texts tp-texts-header${props.data.attributes.header}`;
|
||||
}
|
||||
return "tp-texts";
|
||||
}
|
||||
|
||||
function getTitle(): string {
|
||||
if (props.data.attributes && props.data.attributes.link) {
|
||||
return props.data.attributes.link;
|
||||
}
|
||||
if (props.data.attributes && props.data.attributes.header) {
|
||||
return `H${props.data.attributes.header}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getLineStyle(): StyleValue {
|
||||
const style = <Array<StyleValue>>[];
|
||||
if (props.data.attributes === undefined) {
|
||||
return style;
|
||||
}
|
||||
const ruleBold: StyleValue = "fontFamily: var(--font-title)";
|
||||
const headerFontSizes = ["2rem", "1.75rem", "1.5rem", "1.25rem", "1rem", "0.75rem"];
|
||||
if (props.data.attributes.align) {
|
||||
const ruleAlign: StyleValue = `textAlign: ${props.data.attributes.align}`;
|
||||
style.push(ruleAlign);
|
||||
}
|
||||
if (props.data.attributes.header) {
|
||||
const ruleHeader: StyleValue = `fontSize: ${headerFontSizes[props.data.attributes.header - 1]}`;
|
||||
style.push(ruleHeader);
|
||||
style.push(ruleBold);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
</script>
|
||||
@@ -41,4 +64,37 @@ function getLineStyle(): StyleValue {
|
||||
.tp-texts {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.tp-texts-header1,
|
||||
.tp-texts-header2,
|
||||
.tp-texts-header3,
|
||||
.tp-texts-header4,
|
||||
.tp-texts-header5,
|
||||
.tp-texts-header6 {
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.tp-texts-header1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.tp-texts-header2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tp-texts-header3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tp-texts-header4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tp-texts-header5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tp-texts-header6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,6 +66,7 @@ const jsonData = ref<TGApp.Plugins.Mys.Lottery.FullData>();
|
||||
const timeStatus = ref<string>("未知");
|
||||
const upWay = ref<string>("未知");
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
let timer: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
const visible = computed({
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<!-- todo 角色名片 -->
|
||||
<template>
|
||||
<div class="twc-box" v-if="data !== undefined">
|
||||
<div class="twc-brief">
|
||||
@@ -50,6 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TopNamecard :data="nameCard" @selected="toNameCard" />
|
||||
<TwcMaterials :data="data.materials" />
|
||||
<TwcSkills :data="data.skills" />
|
||||
<TwcConstellations :data="data.constellation" />
|
||||
@@ -94,17 +94,20 @@
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</div>
|
||||
<ToNamecard v-if="hasNc" v-model="showNc" :data="nameCard" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { WikiCharacterData } from "../../data";
|
||||
import { WikiCharacterData, AppNameCardsData, AppCharacterData } from "../../data";
|
||||
import Mys from "../../plugins/Mys";
|
||||
import { createTGWindow } from "../../utils/TGWindow";
|
||||
import { parseHtmlText } from "../../utils/toolFunc";
|
||||
import showSnackbar from "../func/snackbar";
|
||||
import TItembox, { TItemBoxData } from "../main/t-itembox.vue";
|
||||
import ToNamecard from "../overlay/to-namecard.vue";
|
||||
import TopNamecard from "../overlay/top-namecard.vue";
|
||||
|
||||
import TwcConstellations from "./twc-constellations.vue";
|
||||
import TwcMaterials from "./twc-materials.vue";
|
||||
@@ -115,6 +118,7 @@ interface TwcCharacterProps {
|
||||
}
|
||||
|
||||
const props = defineProps<TwcCharacterProps>();
|
||||
const router = useRouter();
|
||||
|
||||
const data = ref<TGApp.App.Character.WikiItem>();
|
||||
const box = computed(() => {
|
||||
@@ -132,7 +136,9 @@ const box = computed(() => {
|
||||
clickable: false,
|
||||
};
|
||||
});
|
||||
const router = useRouter();
|
||||
const hasNc = ref(false);
|
||||
const showNc = ref(false);
|
||||
const nameCard = ref<TGApp.App.NameCard.Item>();
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const res = WikiCharacterData.find((item) => item.id === props.item.id);
|
||||
@@ -144,6 +150,13 @@ async function loadData(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
data.value = res;
|
||||
const appC = AppCharacterData.find((i) => i.name === data.value?.name);
|
||||
if (appC !== undefined) {
|
||||
hasNc.value = true;
|
||||
nameCard.value = AppNameCardsData.find((i) => i.name === appC.nameCard);
|
||||
} else {
|
||||
hasNc.value = false;
|
||||
}
|
||||
showSnackbar({
|
||||
text: `成功获取角色 ${props.item.name} 的 Wiki 数据`,
|
||||
color: "success",
|
||||
@@ -182,6 +195,11 @@ async function toBirth(date: string): Promise<void> {
|
||||
const birth = date.replace("月", "/").replace("日", "");
|
||||
await router.push({ name: "留影叙佳期", params: { date: birth } });
|
||||
}
|
||||
|
||||
function toNameCard(): void {
|
||||
if (showNc.value === true) showNc.value = false;
|
||||
showNc.value = true;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.twc-box {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* @file src/data/index.ts
|
||||
* @description 数据文件入口
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
// 应用数据
|
||||
import type { SchemaType } from "ajv/lib/types/index.js";
|
||||
|
||||
import achievements from "./app/achievements.json";
|
||||
import achievementSeries from "./app/achievementSeries.json";
|
||||
import calendar from "./app/calendar.json";
|
||||
@@ -13,15 +14,16 @@ import gacha from "./app/gacha.json";
|
||||
import GCG from "./app/GCG.json";
|
||||
import nameCards from "./app/namecard.json";
|
||||
import weapon from "./app/weapon.json";
|
||||
// 存档数据
|
||||
import arcBirCalendar from "./archive/birth_calendar.json";
|
||||
import arcBirDraw from "./archive/birth_draw.json";
|
||||
import arcBirRole from "./archive/birth_role.json";
|
||||
// Wiki 数据
|
||||
import schemaUiaf from "./schema/uiaf-schema.json";
|
||||
import schemaUigf from "./schema/uigf-schema.json";
|
||||
import wikiCharacter from "./WIKI/character.json";
|
||||
import wikiMaterial from "./WIKI/material.json";
|
||||
import wikiWeapon from "./WIKI/weapon.json";
|
||||
|
||||
// App
|
||||
export const AppAchievementsData: TGApp.App.Achievement.Item[] = achievements;
|
||||
export const AppAchievementSeriesData: TGApp.App.Achievement.Series[] = achievementSeries;
|
||||
export const AppCalendarData: TGApp.App.Calendar.Item[] = calendar;
|
||||
@@ -30,9 +32,14 @@ export const AppGachaData: TGApp.App.Gacha.PoolItem[] = gacha;
|
||||
export const AppGCGData: TGApp.App.GCG.WikiBriefInfo[] = GCG;
|
||||
export const AppNameCardsData: TGApp.App.NameCard.Item[] = nameCards;
|
||||
export const AppWeaponData: TGApp.App.Weapon.WikiBriefInfo[] = weapon;
|
||||
// Schema
|
||||
export const UiafSchema: SchemaType = schemaUiaf;
|
||||
export const UigfSchema: SchemaType = schemaUigf;
|
||||
// Archive
|
||||
export const ArcBirCalendar: TGApp.Archive.Birth.CalendarData = arcBirCalendar;
|
||||
export const ArcBirDraw: TGApp.Archive.Birth.DrawItem[] = arcBirDraw;
|
||||
export const ArcBirRole: TGApp.Archive.Birth.RoleItem[] = arcBirRole;
|
||||
// Wiki
|
||||
export const WikiCharacterData: TGApp.App.Character.WikiItem[] = wikiCharacter;
|
||||
export const WikiWeaponData: TGApp.App.Weapon.WikiItem[] = wikiWeapon;
|
||||
export const WikiMaterialData: TGApp.App.Material.WikiItem[] = wikiMaterial;
|
||||
|
||||
58
src/data/schema/uiaf-schema.json
Normal file
58
src/data/schema/uiaf-schema.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"description": "Export application name"
|
||||
},
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"description": "Export application version"
|
||||
},
|
||||
"uiaf_version": {
|
||||
"type": "string",
|
||||
"description": "UIAF version applied; Used to prevent application not working when UIGF have breaking update",
|
||||
"pattern": "v\\d+.\\d+"
|
||||
},
|
||||
"export_timestamp": {
|
||||
"type": "number",
|
||||
"description": "Export time in UNIX timestamp"
|
||||
}
|
||||
},
|
||||
"required": ["export_app", "uiaf_version"],
|
||||
"description": "Include basic information defined by export application"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"description": "Achievement ID"
|
||||
},
|
||||
"current": {
|
||||
"type": "number",
|
||||
"description": "Process"
|
||||
},
|
||||
"status": {
|
||||
"type": "number",
|
||||
"description": "Finished status",
|
||||
"enum": [0, 1, 2, 3]
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "number",
|
||||
"description": "Finished time"
|
||||
}
|
||||
},
|
||||
"required": ["id", "current", "status", "timestamp"],
|
||||
"description": "To represent an achievement"
|
||||
},
|
||||
"description": "Include finished or unfinished achievements"
|
||||
}
|
||||
},
|
||||
"required": ["info", "list"]
|
||||
}
|
||||
96
src/data/schema/uigf-schema.json
Normal file
96
src/data/schema/uigf-schema.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"title": "UID of the export record"
|
||||
},
|
||||
"lang": {
|
||||
"type": "string",
|
||||
"title": "language in the format of languagecode2-country/regioncode2"
|
||||
},
|
||||
"export_timestamp": {
|
||||
"type": "number",
|
||||
"title": "Export UNIX timestamp (accurate to the second)"
|
||||
},
|
||||
"export_time": {
|
||||
"type": "string",
|
||||
"title": "Export time",
|
||||
"description": "yyyy-MM-dd HH:mm:ss"
|
||||
},
|
||||
"export_app": {
|
||||
"type": "string",
|
||||
"title": "Name of the export application"
|
||||
},
|
||||
"export_app_version": {
|
||||
"type": "string",
|
||||
"title": "Version of the export application"
|
||||
},
|
||||
"uigf_version": {
|
||||
"type": "string",
|
||||
"title": "UIGF version; follow the regular expression pattern",
|
||||
"pattern": "v\\d+\\.\\d+"
|
||||
},
|
||||
"region_time_zone": {
|
||||
"type": "number",
|
||||
"title": "Region timezone offset"
|
||||
}
|
||||
},
|
||||
"required": ["uid", "uigf_version"],
|
||||
"title": "UIGF Export Information"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uigf_gacha_type": {
|
||||
"type": "string",
|
||||
"title": "UIGF gacha type",
|
||||
"description": "Used to differentiate different gacha types with the same pity calculation for items"
|
||||
},
|
||||
"gacha_type": {
|
||||
"type": "string",
|
||||
"title": "Gacha type"
|
||||
},
|
||||
"item_id": {
|
||||
"type": "string",
|
||||
"title": "Internal ID of the item"
|
||||
},
|
||||
"count": {
|
||||
"type": "string",
|
||||
"title": "Count, usually 1"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"title": "Time when the item was obtained"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"title": "Item name"
|
||||
},
|
||||
"item_type": {
|
||||
"type": "string",
|
||||
"title": "Item type"
|
||||
},
|
||||
"rank_type": {
|
||||
"type": "string",
|
||||
"title": "Item rank"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "Internal ID of the record"
|
||||
}
|
||||
},
|
||||
"required": ["uigf_gacha_type", "gacha_type", "id", "item_id", "time"],
|
||||
"title": "UIGF Item"
|
||||
},
|
||||
"title": "Item List"
|
||||
}
|
||||
},
|
||||
"required": ["info", "list"],
|
||||
"title": "UIGF Root Object"
|
||||
}
|
||||
@@ -301,6 +301,11 @@ async function uploadAbyss(): Promise<void> {
|
||||
color: var(--box-text-4);
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.ua-left-box :deep(.v-tabs.v-slide-group--vertical) {
|
||||
max-height: calc(100% - 150px);
|
||||
}
|
||||
|
||||
.ua-tabs-box {
|
||||
max-height: calc(100% - 150px);
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<v-select v-model="uidCur" class="gacha-top-select" :items="selectItem" variant="outlined" />
|
||||
<div class="gacha-top-btns">
|
||||
<v-btn prepend-icon="mdi-refresh" class="gacha-top-btn" @click="confirmRefresh"
|
||||
>增量刷新</v-btn
|
||||
>
|
||||
>增量刷新
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-refresh" class="gacha-top-btn" @click="confirmRefresh(true)"
|
||||
>全量刷新</v-btn
|
||||
>
|
||||
>全量刷新
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn()">导入</v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="gacha-top-btn" @click="handleExportBtn">导出</v-btn>
|
||||
<v-btn prepend-icon="mdi-cloud-download" class="gacha-top-btn" @click="backupGacha">
|
||||
@@ -52,7 +52,7 @@ import GroHistory from "../../components/gachaRecord/gro-history.vue";
|
||||
import GroOverview from "../../components/gachaRecord/gro-overview.vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import { AppCharacterData, AppWeaponData } from "../../data";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TSUserGacha from "../../plugins/Sqlite/modules/userGacha";
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import { useUserStore } from "../../store/modules/user";
|
||||
import TGLogger from "../../utils/TGLogger";
|
||||
@@ -79,7 +79,7 @@ const tab = ref<string>("");
|
||||
onMounted(async () => {
|
||||
await TGLogger.Info("[UserGacha][onMounted] 进入角色祈愿页面");
|
||||
loadingTitle.value = "正在获取祈愿 UID 列表";
|
||||
selectItem.value = await TGSqlite.getUidList();
|
||||
selectItem.value = await TSUserGacha.getUidList();
|
||||
if (selectItem.value.length === 0) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
@@ -91,7 +91,7 @@ onMounted(async () => {
|
||||
}
|
||||
uidCur.value = selectItem.value[0];
|
||||
loadingTitle.value = `正在获取祈愿数据,默认 UID:${uidCur.value}`;
|
||||
gachaListCur.value = await TGSqlite.getGachaRecords(uidCur.value);
|
||||
gachaListCur.value = await TSUserGacha.getGachaRecords(uidCur.value);
|
||||
await TGLogger.Info(
|
||||
`[UserGacha][onMounted] 获取到 ${uidCur.value} 的 ${gachaListCur.value.length} 条祈愿数据`,
|
||||
);
|
||||
@@ -159,11 +159,11 @@ async function confirmRefresh(force: boolean = false): Promise<void> {
|
||||
];
|
||||
if (force) {
|
||||
loadingTitle.value = "正在获取数据库祈愿最新 ID";
|
||||
checkList[0] = await TGSqlite.getGachaCheck(account.gameUid, "200");
|
||||
checkList[1] = await TGSqlite.getGachaCheck(account.gameUid, "301");
|
||||
checkList[2] = await TGSqlite.getGachaCheck(account.gameUid, "400");
|
||||
checkList[3] = await TGSqlite.getGachaCheck(account.gameUid, "302");
|
||||
checkList[4] = await TGSqlite.getGachaCheck(account.gameUid, "500");
|
||||
checkList[0] = await TSUserGacha.getGachaCheck(account.gameUid, "200");
|
||||
checkList[1] = await TSUserGacha.getGachaCheck(account.gameUid, "301");
|
||||
checkList[2] = await TSUserGacha.getGachaCheck(account.gameUid, "400");
|
||||
checkList[3] = await TSUserGacha.getGachaCheck(account.gameUid, "302");
|
||||
checkList[4] = await TSUserGacha.getGachaCheck(account.gameUid, "500");
|
||||
}
|
||||
console.log(checkList);
|
||||
loadingTitle.value = "正在刷新新手祈愿数据";
|
||||
@@ -190,7 +190,7 @@ async function confirmRefresh(force: boolean = false): Promise<void> {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// 获取祈愿数据并写入数据库
|
||||
// 获取祈愿数据并写入数据库,不用考虑多语言,因为从api获取的数据是中文
|
||||
async function getGachaLogs(
|
||||
pool: string,
|
||||
endId: string = "0",
|
||||
@@ -236,7 +236,7 @@ async function getGachaLogs(
|
||||
}
|
||||
uigfList.push(tempItem);
|
||||
});
|
||||
await TGSqlite.mergeUIGF(account.gameUid, uigfList);
|
||||
await TSUserGacha.mergeUIGF(account.gameUid, uigfList);
|
||||
if (check !== undefined && gachaRes.some((i) => i.id === check)) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
@@ -304,13 +304,7 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
return;
|
||||
}
|
||||
const check = await verifyUigfData(<string>selectedFile);
|
||||
if (!check) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "读取 UIGF 文件失败,请检查文件是否符合规范",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!check) return;
|
||||
const remoteData = await readUigfData(<string>selectedFile);
|
||||
const res = await showConfirm({
|
||||
title: "是否导入祈愿数据?",
|
||||
@@ -334,7 +328,7 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
await TGSqlite.mergeUIGF(remoteData.info.uid, remoteData.list);
|
||||
await TSUserGacha.mergeUIGF(remoteData.info.uid, remoteData.list);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `成功导入 ${remoteData.list.length} 条祈愿数据`,
|
||||
@@ -349,7 +343,7 @@ async function handleImportBtn(savePath?: string): Promise<void> {
|
||||
|
||||
// 导出按钮点击事件
|
||||
async function handleExportBtn(): Promise<void> {
|
||||
const gachaList = await TGSqlite.getGachaRecords(uidCur.value);
|
||||
const gachaList = await TSUserGacha.getGachaRecords(uidCur.value);
|
||||
if (gachaList.length === 0) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
@@ -460,25 +454,25 @@ async function deleteGacha(): Promise<void> {
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][deleteGacha] 已取消祈愿数据删除`);
|
||||
return;
|
||||
}
|
||||
const uidList = await TGSqlite.getUidList();
|
||||
const uidList = await TSUserGacha.getUidList();
|
||||
let secondConfirm: string | boolean | undefined;
|
||||
if (uidList.length <= 1) {
|
||||
secondConfirm = await showConfirm({
|
||||
title: "删除后数据库将为空,确定删除?",
|
||||
text: `UID:${uidCur.value},共 ${gachaListCur.value.length} 条数据`,
|
||||
});
|
||||
}
|
||||
if (!secondConfirm) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消祈愿数据删除",
|
||||
});
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][deleteGacha] 已取消祈愿数据删除`);
|
||||
return;
|
||||
if (!secondConfirm) {
|
||||
showSnackbar({
|
||||
color: "cancel",
|
||||
text: "已取消祈愿数据删除",
|
||||
});
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][deleteGacha] 已取消祈愿数据删除`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
loadingTitle.value = `正在删除${uidCur.value}的祈愿数据`;
|
||||
loading.value = true;
|
||||
await TGSqlite.deleteGachaRecords(uidCur.value);
|
||||
await TSUserGacha.deleteGachaRecords(uidCur.value);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
text: `已成功删除 ${uidCur.value} 的祈愿数据`,
|
||||
@@ -493,7 +487,7 @@ async function deleteGacha(): Promise<void> {
|
||||
|
||||
// 监听 UID 变化
|
||||
watch(uidCur, async (newUid) => {
|
||||
gachaListCur.value = await TGSqlite.getGachaRecords(newUid);
|
||||
gachaListCur.value = await TSUserGacha.getGachaRecords(newUid);
|
||||
showSnackbar({
|
||||
text: `成功获取 ${gachaListCur.value.length} 条祈愿数据`,
|
||||
});
|
||||
|
||||
@@ -10,19 +10,10 @@
|
||||
@keyup.enter="searchNamecard"
|
||||
/>
|
||||
<div class="tw-nc-list">
|
||||
<v-virtual-scroll :items="sortNameCardsData" :item-height="80" class="cards-list">
|
||||
<v-virtual-scroll :items="sortNameCardsData" :item-height="80">
|
||||
<template #default="{ item }">
|
||||
<v-list
|
||||
:style="{ backgroundImage: item.name === '原神·印象' ? 'none' : `url(${item.bg})` }"
|
||||
class="card-box"
|
||||
@click="toNameCard(item)"
|
||||
>
|
||||
<v-list-item :title="item.name" :subtitle="item.desc">
|
||||
<template #prepend>
|
||||
<v-img width="80px" style="margin-right: 10px" :src="item.icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<TopNamecard :data="item" @selected="toNameCard" />
|
||||
<div style="height: 10px" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</div>
|
||||
@@ -45,6 +36,7 @@ import { onMounted, ref } from "vue";
|
||||
|
||||
import showSnackbar from "../../components/func/snackbar";
|
||||
import ToNamecard from "../../components/overlay/to-namecard.vue";
|
||||
import TopNamecard from "../../components/overlay/top-namecard.vue";
|
||||
import { AppNameCardsData } from "../../data";
|
||||
|
||||
const curNameCard = ref<TGApp.App.NameCard.Item>();
|
||||
@@ -135,19 +127,6 @@ function searchNamecard() {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.card-box {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px 50px 50px 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: var(--box-bg-1);
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -38,18 +38,7 @@
|
||||
<!-- 右侧内容-->
|
||||
<div class="right-wrap">
|
||||
<div v-if="curCardName !== '' && selectedSeries !== -1 && !loading">
|
||||
<v-list
|
||||
v-if="curCard"
|
||||
class="achi-series"
|
||||
:style="{ backgroundImage: `url(${curCard.bg})` }"
|
||||
@click="openImg()"
|
||||
>
|
||||
<v-list-item :title="curCard.name" :subtitle="curCard.desc">
|
||||
<template #prepend>
|
||||
<v-img width="80px" style="margin-right: 10px" :src="curCard.icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<TopNamecard :data="curCard" @selected="openImg()" />
|
||||
</div>
|
||||
<div
|
||||
v-for="(achievement, index) in renderSelect"
|
||||
@@ -125,12 +114,18 @@ import showSnackbar from "../../components/func/snackbar";
|
||||
import ToAchiInfo from "../../components/overlay/to-achiInfo.vue";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import ToNamecard from "../../components/overlay/to-namecard.vue";
|
||||
import TopNamecard from "../../components/overlay/top-namecard.vue";
|
||||
import { AppAchievementSeriesData, AppNameCardsData } from "../../data";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import { useAchievementsStore } from "../../store/modules/achievements";
|
||||
import TGLogger from "../../utils/TGLogger";
|
||||
import { getNowStr } from "../../utils/toolFunc";
|
||||
import { getUiafHeader, readUiafData, verifyUiafData } from "../../utils/UIAF";
|
||||
import {
|
||||
getUiafHeader,
|
||||
readUiafData,
|
||||
verifyUiafData,
|
||||
verifyUiafDataClipboard,
|
||||
} from "../../utils/UIAF";
|
||||
|
||||
// Store
|
||||
const achievementsStore = useAchievementsStore();
|
||||
@@ -185,7 +180,7 @@ async function switchHideFin() {
|
||||
|
||||
// 刷新概况
|
||||
async function flushOverview(): Promise<void> {
|
||||
const { total, fin } = await getAchiOverview();
|
||||
const { total, fin } = await TSUserAchi.getOverview();
|
||||
achievementsStore.flushData(total, fin);
|
||||
title.value = achievementsStore.title;
|
||||
}
|
||||
@@ -197,8 +192,8 @@ onMounted(async () => {
|
||||
loadingTitle.value = "正在获取成就系列数据";
|
||||
await flushOverview();
|
||||
await TGLogger.Info(`[Achievements][onMounted] ${title.value}`);
|
||||
allSeriesData.value = await getSeriesData();
|
||||
achievementsStore.lastVersion = await TGSqlite.getLatestAchievementVersion();
|
||||
allSeriesData.value = await TSUserAchi.getSeries();
|
||||
achievementsStore.lastVersion = await TSUserAchi.getLatestAchiVersion();
|
||||
loadingTitle.value = "正在获取成就数据";
|
||||
selectedAchievement.value = await getAchiData("all");
|
||||
loading.value = false;
|
||||
@@ -229,7 +224,7 @@ async function selectSeries(index: number): Promise<void> {
|
||||
selectedSeries.value = index;
|
||||
selectedAchievement.value = await getAchiData("series", index.toString());
|
||||
loadingTitle.value = "正在查找对应的成就名片";
|
||||
curCardName.value = await getNameCardName(index);
|
||||
curCardName.value = await TSUserAchi.getSeriesNameCard(index);
|
||||
if (curCardName.value !== "") {
|
||||
curCard.value = AppNameCardsData.find((item) => item.name === curCardName.value);
|
||||
}
|
||||
@@ -343,14 +338,8 @@ async function importJson(): Promise<void> {
|
||||
await TGLogger.Info("[Achievements][importJson] 已取消文件选择");
|
||||
return;
|
||||
}
|
||||
if (!(await verifyUiafData(<string>selectedFile))) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "读取 UIAF 数据失败,请检查文件是否符合规范",
|
||||
});
|
||||
await TGLogger.Error("[Achievements][importJson] 读取 UIAF 数据失败,请检查文件是否符合规范");
|
||||
return;
|
||||
}
|
||||
const check = await verifyUiafData(<string>selectedFile);
|
||||
if (!check) return;
|
||||
const remoteRaw = await readUiafData(<string>selectedFile);
|
||||
await TGLogger.Info("[Achievements][importJson] 读取 UIAF 数据成功");
|
||||
await TGLogger.Info(`[Achievements][importJson] 导入来源:${remoteRaw.info.export_app}`);
|
||||
@@ -360,7 +349,7 @@ async function importJson(): Promise<void> {
|
||||
loadingTitle.value = "正在解析数据";
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在合并成就数据";
|
||||
await TGSqlite.mergeUIAF(remoteRaw.list);
|
||||
await TSUserAchi.mergeUIAF(remoteRaw.list);
|
||||
loadingTitle.value = "即将刷新页面";
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
@@ -382,7 +371,7 @@ async function exportJson(): Promise<void> {
|
||||
// 获取本地数据
|
||||
const UiafData = {
|
||||
info: await getUiafHeader(),
|
||||
list: await TGSqlite.getUIAF(),
|
||||
list: await TSUserAchi.getUIAF(),
|
||||
};
|
||||
const fileName = `UIAF_${UiafData.info.export_app}_${UiafData.info.export_app_version}_${UiafData.info.export_timestamp}`;
|
||||
const isSave = await dialog.save({
|
||||
@@ -430,32 +419,21 @@ async function handleImportOuter(app: string): Promise<void> {
|
||||
}
|
||||
// 读取 剪贴板
|
||||
const clipboard = await window.navigator.clipboard.readText();
|
||||
let data: TGApp.Plugins.UIAF.Achievement[];
|
||||
// 里面是完整的 uiaf 数据
|
||||
try {
|
||||
data = JSON.parse(clipboard).list;
|
||||
loadingTitle.value = "正在导入数据";
|
||||
loading.value = true;
|
||||
await TGSqlite.mergeUIAF(data);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
color: "success",
|
||||
text: "导入成功,即将刷新页面",
|
||||
});
|
||||
await TGLogger.Info("[Achievements][handleImportOuter] 导入成功");
|
||||
} catch (e) {
|
||||
if (e instanceof Error)
|
||||
await TGLogger.Error(`[Achievements][handleImportOuter] 导入失败 ${e.name}: ${e.message}`);
|
||||
else console.error(e);
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "读取 UIAF 数据失败,请检查文件是否符合规范",
|
||||
});
|
||||
} finally {
|
||||
setTimeout(async () => {
|
||||
await router.push("/achievements");
|
||||
}, 1500);
|
||||
}
|
||||
const check = await verifyUiafDataClipboard();
|
||||
if (!check) return;
|
||||
const data: TGApp.Plugins.UIAF.Data = JSON.parse(clipboard);
|
||||
loadingTitle.value = "正在导入数据";
|
||||
loading.value = true;
|
||||
await TSUserAchi.mergeUIAF(data);
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
color: "success",
|
||||
text: "导入成功,即将刷新页面",
|
||||
});
|
||||
await TGLogger.Info("[Achievements][handleImportOuter] 导入成功");
|
||||
setTimeout(async () => {
|
||||
await router.push("/achievements");
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// 改变成就状态
|
||||
@@ -474,10 +452,12 @@ async function setAchi(
|
||||
}
|
||||
renderSelect.value[renderSelect.value.findIndex((item) => item.id === achievement.id)] =
|
||||
newAchievement;
|
||||
await setAchiDB(newAchievement);
|
||||
await TSUserAchi.updateAchievement(newAchievement);
|
||||
await flushOverview();
|
||||
allSeriesData.value[allSeriesData.value.findIndex((item) => item.id === newAchievement.series)] =
|
||||
(await getSeriesData(newAchievement.series))[0];
|
||||
const seriesIndex = allSeriesData.value.findIndex((item) => item.id === newAchievement.series);
|
||||
if (seriesIndex === -1) return;
|
||||
const seriesGet = await TSUserAchi.getSeries(newAchievement.series);
|
||||
allSeriesData.value[seriesIndex] = seriesGet[0];
|
||||
showSnackbar({
|
||||
text: `已将成就 ${newAchievement.name}[${newAchievement.id}] 标记为 ${
|
||||
target ? "已完成" : "未完成"
|
||||
@@ -490,88 +470,22 @@ async function setAchi(
|
||||
);
|
||||
}
|
||||
|
||||
/* 以下为数据库操作 */
|
||||
// 获取成就概况
|
||||
async function getAchiOverview(): Promise<{
|
||||
total: number;
|
||||
fin: number;
|
||||
}> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
|
||||
const res: Array<{ total: number; fin: number }> = await db.select(sql);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
// 获取成就系列
|
||||
async function getSeriesData(series?: number): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let sql = "SELECT * FROM AchievementSeries ORDER BY `order`;";
|
||||
if (series) {
|
||||
sql = `SELECT *
|
||||
FROM AchievementSeries
|
||||
WHERE id = ${series}
|
||||
ORDER BY \`order\`;`;
|
||||
}
|
||||
return await db.select(sql);
|
||||
}
|
||||
|
||||
// 获取成就(某个系列)
|
||||
async function getAchiData(
|
||||
type: "all" | "series" | "search",
|
||||
value?: string,
|
||||
): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let sql = "";
|
||||
if (type === "all" || (type == "series" && value === undefined)) {
|
||||
sql = "SELECT * FROM Achievements ORDER BY isCompleted, `order`;";
|
||||
} else if (type === "series") {
|
||||
sql = `SELECT *
|
||||
FROM Achievements
|
||||
WHERE series = ${value}
|
||||
ORDER BY isCompleted, \`order\`;`;
|
||||
} else if (type === "search") {
|
||||
if (value === undefined) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "搜索内容不能为空",
|
||||
});
|
||||
return [];
|
||||
}
|
||||
if (value.startsWith("v")) {
|
||||
const version = value.replace("v", "");
|
||||
sql = `SELECT *
|
||||
FROM Achievements
|
||||
WHERE version LIKE '%${version}%'
|
||||
ORDER BY isCompleted, \`order\`;`;
|
||||
} else {
|
||||
sql = `SELECT *
|
||||
FROM Achievements
|
||||
WHERE name LIKE '%${value}%'
|
||||
OR description LIKE '%${value}%'
|
||||
ORDER BY isCompleted, \`order\`;`;
|
||||
}
|
||||
if (type !== "search") {
|
||||
return TSUserAchi.getAchievements(value);
|
||||
}
|
||||
return await db.select(sql);
|
||||
}
|
||||
|
||||
// 获取成就名片
|
||||
async function getNameCardName(series: number): Promise<string> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const sql = `SELECT nameCard
|
||||
FROM AchievementSeries
|
||||
WHERE id = ${series};`;
|
||||
const res: Array<{ nameCard: string }> = await db.select(sql);
|
||||
return res[0].nameCard;
|
||||
}
|
||||
|
||||
// 更新成就数据
|
||||
async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const sql = `UPDATE Achievements
|
||||
SET isCompleted = ${achievement.isCompleted},
|
||||
completedTime = '${achievement.completedTime}'
|
||||
WHERE id = ${achievement.id};`;
|
||||
await db.execute(sql);
|
||||
if (value === undefined) {
|
||||
showSnackbar({
|
||||
color: "error",
|
||||
text: "搜索内容不能为空",
|
||||
});
|
||||
return [];
|
||||
}
|
||||
return TSUserAchi.searchAchievements(value);
|
||||
}
|
||||
</script>
|
||||
<!-- 顶部栏跟 wrap 大概布局 -->
|
||||
@@ -703,19 +617,6 @@ async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Pro
|
||||
<!-- 右侧成就 -->
|
||||
<style lang="css" scoped>
|
||||
/* 成就卡片 */
|
||||
.achi-series {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 10px 50px 50px 10px;
|
||||
background-color: var(--box-bg-1);
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.card-achi {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -184,7 +184,7 @@ async function loadData(): Promise<void> {
|
||||
function getAnnoTime(content: string): string | false {
|
||||
const regexes = [
|
||||
/〓活动时间〓.*?\d\.\d版本期间持续开放/,
|
||||
/(?:〓活动时间〓|〓任务开放时间〓).*?\d\.\d版本更新(?:完成|)后永久开放/,
|
||||
/(?:〓活动时间〓|〓任务开放时间〓).*?(?:(\d\.\d版本更新(?:完成|))|(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}).*?)后永久开放/,
|
||||
/(?:〓(?:活动|折扣)时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?<t class="t_(?:gl|lc)".*?>(.*?)<\/t>/,
|
||||
/(?:〓(?:活动|折扣)时间〓|祈愿时间|【上架时间】).*?<t class="t_(?:gl|lc)".*?>(.*?)<\/t>.*?~.*?<t class="t_(?:gl|lc)".*?>(.*?)<\/t>/,
|
||||
/〓活动时间〓.*?(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}).*?(\d\.\d版本结束)/,
|
||||
@@ -198,8 +198,11 @@ function getAnnoTime(content: string): string | false {
|
||||
const res = content.match(regexes[1]);
|
||||
if (res === null) return false;
|
||||
const regex2 = /\d\.\d版本更新(?:完成|)后永久开放/;
|
||||
const regex3 = /\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}/;
|
||||
const res2 = res[0].match(regex2);
|
||||
return res2 === null ? false : res2[0];
|
||||
if (res2 !== null) return res2[0];
|
||||
const res3 = res[0].match(regex3);
|
||||
return res3 === null ? false : `${res3[0]} 后永久开放`;
|
||||
}
|
||||
if (content.match(regexes[2])) {
|
||||
const res = content.match(regexes[2]);
|
||||
@@ -211,7 +214,7 @@ function getAnnoTime(content: string): string | false {
|
||||
}
|
||||
if (content.match(regexes[4])) {
|
||||
const res = content.match(regexes[4]);
|
||||
if (res != null) {
|
||||
if (res !== null) {
|
||||
const cnt = res[0].match(/〓/g);
|
||||
if (cnt && cnt.length > 2) return false;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ import showConfirm from "../../components/func/confirm";
|
||||
import showSnackbar from "../../components/func/snackbar";
|
||||
import ToLoading from "../../components/overlay/to-loading.vue";
|
||||
import TGSqlite from "../../plugins/Sqlite";
|
||||
import TSUserAchi from "../../plugins/Sqlite/modules/userAchi.js";
|
||||
import { useAchievementsStore } from "../../store/modules/achievements";
|
||||
import { useAppStore } from "../../store/modules/app";
|
||||
import { useHomeStore } from "../../store/modules/home";
|
||||
@@ -166,7 +167,6 @@ async function confirmBackup(): Promise<void> {
|
||||
}
|
||||
loadingTitle.value = "正在备份数据...";
|
||||
loading.value = true;
|
||||
loadingSub.value = "祈愿数据需单独备份";
|
||||
await backUpUserData(saveDir);
|
||||
loading.value = false;
|
||||
showSnackbar({ text: "数据已备份!" });
|
||||
@@ -237,7 +237,7 @@ async function confirmUpdate(title?: string): Promise<void> {
|
||||
loadingTitle.value = "正在更新数据库...";
|
||||
loading.value = true;
|
||||
await TGSqlite.update();
|
||||
achievementsStore.lastVersion = await TGSqlite.getLatestAchievementVersion();
|
||||
achievementsStore.lastVersion = await TSUserAchi.getLatestAchiVersion();
|
||||
appStore.buildTime = getBuildTime();
|
||||
loading.value = false;
|
||||
showSnackbar({
|
||||
|
||||
@@ -223,6 +223,10 @@ async function toNav(item: TGApp.BBS.Navigator.Navigator): Promise<void> {
|
||||
window.open(item.app_path);
|
||||
return;
|
||||
}
|
||||
if (item.name === "签到福利") {
|
||||
await TGClient.open("web_act_thin", item.app_path);
|
||||
return;
|
||||
}
|
||||
const modeConfirm = await showConfirm({
|
||||
title: "是否采用宽屏模式打开?",
|
||||
text: "取消则采用竖屏模式打开",
|
||||
|
||||
2
src/plugins/Mys/types/Collection.d.ts
vendored
2
src/plugins/Mys/types/Collection.d.ts
vendored
@@ -47,7 +47,7 @@ declare namespace TGApp.Plugins.Mys.Collection {
|
||||
* @property {boolean} is_following 是否关注
|
||||
* @property {number} post_num 帖子数量
|
||||
* @property {number} post_updated_at 帖子更新时间(秒级时间戳)
|
||||
* @property {number} status 状态 // todo: 未知
|
||||
* @property {number} status 状态
|
||||
* @property {string} title 标题
|
||||
* @property {number} uid 用户 ID
|
||||
* @property {number} view_num 浏览量
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
/**
|
||||
* @file plugins/Sqlite/index.ts
|
||||
* @description Sqlite 数据库操作类
|
||||
* @since Beta v0.4.5
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app } from "@tauri-apps/api";
|
||||
import Database from "tauri-plugin-sql-api";
|
||||
|
||||
import { getUiafStatus } from "../../utils/UIAF";
|
||||
|
||||
import initDataSql from "./sql/initData";
|
||||
import {
|
||||
importAbyssData,
|
||||
@@ -18,7 +16,6 @@ import {
|
||||
insertRecordData,
|
||||
insertRoleData,
|
||||
} from "./sql/insertData";
|
||||
import { importUIAFData, importUIGFData } from "./sql/updateData";
|
||||
|
||||
class Sqlite {
|
||||
/**
|
||||
@@ -115,15 +112,13 @@ class Sqlite {
|
||||
|
||||
/**
|
||||
* @description 插入 Account 数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.User.Account.Game[]} accounts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async saveAccount(accounts: TGApp.User.Account.Game[]): Promise<void> {
|
||||
const db = await this.getDB();
|
||||
// 为了防止多账号的情况,先清空数据表
|
||||
const clear = "DELETE FROM GameAccount WHERE 1=1;";
|
||||
await db.execute(clear);
|
||||
await db.execute("DELETE FROM GameAccount WHERE true;");
|
||||
for (const a of accounts) {
|
||||
const sql = insertGameAccountData(a);
|
||||
await db.execute(sql);
|
||||
@@ -203,57 +198,6 @@ class Sqlite {
|
||||
await this.initDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取最新成就版本
|
||||
* @since Beta v0.3.3
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
public async getLatestAchievementVersion(): Promise<string> {
|
||||
const db = await this.getDB();
|
||||
const sql = "SELECT version FROM Achievements ORDER BY version DESC LIMIT 1;";
|
||||
const res: Array<{ version: string }> = await db.select(sql);
|
||||
return res[0].version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并 UIAF 数据
|
||||
* @since Beta v0.3.3
|
||||
* @param {TGApp.Plugins.UIAF.Achievement[]} achievements UIAF 数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async mergeUIAF(achievements: TGApp.Plugins.UIAF.Achievement[]): Promise<void> {
|
||||
const db = await this.getDB();
|
||||
const sql = importUIAFData(achievements);
|
||||
for (const item of sql) {
|
||||
await db.execute(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取 UIAF 数据
|
||||
* @since Beta v0.3.3
|
||||
* @returns {Promise<TGApp.Plugins.UIAF.Achievement[]>}
|
||||
*/
|
||||
public async getUIAF(): Promise<TGApp.Plugins.UIAF.Achievement[]> {
|
||||
const db = await this.getDB();
|
||||
const sql = "SELECT * FROM Achievements WHERE isCompleted = 1 OR progress > 0";
|
||||
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
|
||||
|
||||
const achievements: TGApp.Plugins.UIAF.Achievement[] = [];
|
||||
for (const item of res) {
|
||||
const completed = item.isCompleted === 1;
|
||||
const status = getUiafStatus(completed, item.progress);
|
||||
achievements.push({
|
||||
id: item.id,
|
||||
status,
|
||||
timestamp:
|
||||
completed && item.completedTime ? new Date(item.completedTime).getTime() / 1000 : 0,
|
||||
current: item.progress,
|
||||
});
|
||||
}
|
||||
return achievements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 保存深渊数据
|
||||
* @since Beta v0.3.3
|
||||
@@ -399,98 +343,6 @@ class Sqlite {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取已有 uid 列表
|
||||
* @since Beta v0.3.3
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
public async getUidList(): Promise<string[]> {
|
||||
const db = await this.getDB();
|
||||
const sql = "SELECT DISTINCT uid FROM GachaRecords";
|
||||
const res: Array<{ uid: string }> = await db.select(sql);
|
||||
return res.map((item) => item.uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取指定 uid 的用户角色数据
|
||||
* @since Beta v0.3.3
|
||||
* @param {string} uid 用户 uid
|
||||
* @returns {Promise<TGApp.Sqlite.GachaRecords.SingleTable[]>}
|
||||
*/
|
||||
public async getGachaRecords(uid: string): Promise<TGApp.Sqlite.GachaRecords.SingleTable[]> {
|
||||
const db = await this.getDB();
|
||||
const sql = `SELECT *
|
||||
FROM GachaRecords
|
||||
WHERE uid = '${uid}'`;
|
||||
return await db.select(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除指定 uid 的祈愿数据
|
||||
* @since Beta v0.3.3
|
||||
* @param {string} uid 用户 uid
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async deleteGachaRecords(uid: string): Promise<void> {
|
||||
const db = await this.getDB();
|
||||
const sql = `DELETE
|
||||
FROM GachaRecords
|
||||
WHERE uid = '${uid}'`;
|
||||
await db.execute(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并祈愿数据
|
||||
* @since Beta v0.3.3
|
||||
* @param {string} uid UID
|
||||
* @param {TGApp.Plugins.UIGF.GachaItem[]} data UIGF 数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async mergeUIGF(uid: string, data: TGApp.Plugins.UIGF.GachaItem[]): Promise<void> {
|
||||
const db = await this.getDB();
|
||||
const sql = importUIGFData(uid, data);
|
||||
for (const item of sql) {
|
||||
await db.execute(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断今天是否是某个角色的生日
|
||||
* @since Beta v0.3.6
|
||||
* @returns {Promise<false|string>}
|
||||
*/
|
||||
public async isBirthday(): Promise<false | string> {
|
||||
const db = await this.getDB();
|
||||
const dateNow = new Date();
|
||||
const date = `${dateNow.getMonth() + 1},${dateNow.getDate()}`;
|
||||
const sql = `SELECT name
|
||||
FROM AppCharacters
|
||||
WHERE birthday = '${date}';`;
|
||||
const res: Array<{ name: string }> = await db.select(sql);
|
||||
if (res.length === 0) return false;
|
||||
return res.map((item) => item.name).join("、");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 用于检测祈愿增量更新的 gacha id
|
||||
* @since Beta v0.4.4
|
||||
* @param {string} uid 用户 uid
|
||||
* @param {string} type 卡池类型
|
||||
* @returns {Promise<string|undefined>}
|
||||
*/
|
||||
async getGachaCheck(uid: string, type: string): Promise<string | undefined> {
|
||||
const db = await this.getDB();
|
||||
const sql = `SELECT id
|
||||
FROM GachaRecords
|
||||
WHERE uid = '${uid}'
|
||||
AND gachaType = '${type}'
|
||||
ORDER BY id DESC
|
||||
LIMIT 1;`;
|
||||
const res: Array<{ id: string }> = await db.select(sql);
|
||||
if (res.length === 0) return undefined;
|
||||
return res[0].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测特定表是否存在
|
||||
* @since Beta v0.4.5
|
||||
|
||||
196
src/plugins/Sqlite/modules/userAchi.ts
Normal file
196
src/plugins/Sqlite/modules/userAchi.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* @file plugins/Sqlite/modules/userAchi.ts
|
||||
* @description 用户成就模块
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { getUiafStatus } from "../../../utils/UIAF.js";
|
||||
import TGSqlite from "../index";
|
||||
import { importUIAFData } from "../sql/updateData";
|
||||
|
||||
/**
|
||||
* @description 获取成就概况
|
||||
* @since Beta v0.4.7
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.Overview}> 成就概况
|
||||
*/
|
||||
async function getOverview(): Promise<TGApp.Sqlite.Achievement.Overview> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const res = await db.select<TGApp.Sqlite.Achievement.Overview>(
|
||||
"SELECT SUM(totalCount) as total,SUM(finCount) AS fin From AchievementSeries",
|
||||
);
|
||||
return res[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取最新成就版本
|
||||
* @since Beta v0.4.7
|
||||
* @returns {Promise<string>} 最新成就版本
|
||||
*/
|
||||
async function getLatestAchiVersion(): Promise<string> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = { version: string };
|
||||
const res = await db.select<resType>(
|
||||
"SELECT version FROM Achievements ORDER BY version DESC LIMIT 1;",
|
||||
);
|
||||
return res[0].version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就系列数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {number|undefined} id 成就系列ID
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.SeriesTable[]>} 成就系列数据
|
||||
*/
|
||||
async function getSeries(id?: number): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let res: TGApp.Sqlite.Achievement.SeriesTable[];
|
||||
if (id === undefined) {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SeriesTable>(
|
||||
"SELECT * FROM AchievementSeries ORDER BY `order`;",
|
||||
);
|
||||
} else {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SeriesTable>(
|
||||
"SELECT * FROM AchievementSeries WHERE id = ?;",
|
||||
[id],
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {number|undefined} id 成就系列ID
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>} 成就数据
|
||||
*/
|
||||
async function getAchievements(id?: string): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
let res: TGApp.Sqlite.Achievement.SingleTable[];
|
||||
if (id === undefined) {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements ORDER BY isCompleted,`order`;",
|
||||
);
|
||||
} else {
|
||||
res = await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements WHERE series = ? ORDER BY `order`;",
|
||||
[id],
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取成就名片
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} id 成就系列ID
|
||||
* @returns {Promise<string>} 成就名片
|
||||
*/
|
||||
async function getSeriesNameCard(id: string): Promise<string> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = { nameCard: string };
|
||||
const res = await db.select<resType>("SELECT nameCard FROM AchievementSeries WHERE id = ?;", [
|
||||
id,
|
||||
]);
|
||||
return res[0].nameCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查找成就数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} keyword 关键词
|
||||
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>} 成就数据
|
||||
*/
|
||||
async function searchAchievements(
|
||||
keyword: string,
|
||||
): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
|
||||
if (keyword === "") return await getAchievements();
|
||||
const db = await TGSqlite.getDB();
|
||||
const versionReg = /^v\d+(\.\d+)?$/;
|
||||
if (versionReg.test(keyword)) {
|
||||
return await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements WHERE version LIKE ? ORDER BY isCompleted,`order`;",
|
||||
[keyword],
|
||||
);
|
||||
}
|
||||
return await db.select<TGApp.Sqlite.Achievement.SingleTable>(
|
||||
"SELECT * FROM Achievements WHERE name LIKE ? OR description LIKE ? ORDER BY isCompleted,`order`;",
|
||||
[`%${keyword}%`, `%${keyword}%`],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新成就数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Sqlite.Achievement.SingleTable} data UIAF数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateAchievement(data: TGApp.Sqlite.Achievement.SingleTable): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
await db.execute("UPDATE Achievements SET isCompleted = ?, completedTime = ? WHERE id = ?;", [
|
||||
data.isCompleted,
|
||||
data.completedTime.toString(),
|
||||
data.id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将数据库数据转换为UIAF数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Sqlite.Achievement.SingleTable} data 数据库数据
|
||||
* @returns {TGApp.Plugins.UIAF.Achievement} UIAF数据
|
||||
*/
|
||||
function transDb2Uiaf(data: TGApp.Sqlite.Achievement.SingleTable): TGApp.Plugins.UIAF.Achievement {
|
||||
const isCompleted = data.isCompleted === 1;
|
||||
let timestamp = 0;
|
||||
if (isCompleted) timestamp = new Date(data.completedTime).getTime();
|
||||
const status = getUiafStatus(isCompleted, data.progress);
|
||||
return {
|
||||
id: data.id,
|
||||
timestamp: timestamp,
|
||||
current: data.progress,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取UIAF数据
|
||||
* @since Beta v0.4.7
|
||||
* @returns {Promise<TGApp.Plugins.UIAF.Achievement[]>}
|
||||
*/
|
||||
async function getUIAF(): Promise<TGApp.Plugins.UIAF.Achievement[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const data = await db.select<TGApp.Sqlite.Achievement.SingleTable>("SELECT * FROM Achievements;");
|
||||
const res: TGApp.Plugins.UIAF.Achievement[] = [];
|
||||
for (const item: TGApp.Sqlite.Achievement.SingleTable of data) {
|
||||
res.push(transDb2Uiaf(item));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并UIAF数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Plugins.UIAF.Achievement[]} data UIAF数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function mergeUIAF(data: TGApp.Plugins.UIAF.Achievement[]): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
for (const item of data) {
|
||||
const sql = importUIAFData(item);
|
||||
await db.execute(sql);
|
||||
}
|
||||
}
|
||||
|
||||
const TSUserAchi = {
|
||||
getOverview,
|
||||
getLatestAchiVersion,
|
||||
getSeries,
|
||||
getSeriesNameCard,
|
||||
getAchievements,
|
||||
searchAchievements,
|
||||
updateAchievement,
|
||||
getUIAF,
|
||||
mergeUIAF,
|
||||
};
|
||||
|
||||
export default TSUserAchi;
|
||||
147
src/plugins/Sqlite/modules/userGacha.ts
Normal file
147
src/plugins/Sqlite/modules/userGacha.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @file plugins/Sqlite/modules/userGacha.ts
|
||||
* @description 用户祈愿模块
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { AppCharacterData, AppWeaponData } from "../../../data/index";
|
||||
import TGSqlite from "../index";
|
||||
import { importUIGFData } from "../sql/updateData";
|
||||
|
||||
type gachaItemTypeRes =
|
||||
| ["角色", TGApp.App.Character.WikiBriefInfo]
|
||||
| ["武器", TGApp.App.Weapon.WikiBriefInfo]
|
||||
| ["未知", "未知"];
|
||||
|
||||
/**
|
||||
* @description 根据 item_id 获取角色/武器类型
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} item_id - item_id
|
||||
* @return {gachaItemTypeRes}
|
||||
*/
|
||||
function getGachaItemType(item_id: string): gachaItemTypeRes {
|
||||
const findAvatar = AppCharacterData.find((i) => i.id.toString() === item_id);
|
||||
if (findAvatar !== undefined) return ["角色", findAvatar];
|
||||
const findWeapon = AppWeaponData.find((i) => i.id.toString() === item_id);
|
||||
if (findWeapon !== undefined) return ["武器", findWeapon];
|
||||
return ["未知", "未知"];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 转换祈愿数据,防止多语言
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Plugins.UIGF.GachaItem} gacha - UIGF数据
|
||||
* @return {TGApp.Plugins.UIGF.GachaItem} 转换后的数据
|
||||
*/
|
||||
function transGacha(gacha: TGApp.Plugins.UIGF.GachaItem): TGApp.Plugins.UIGF.GachaItem {
|
||||
const type = getGachaItemType(gacha.item_id);
|
||||
let res = gacha;
|
||||
res.item_type = type[0];
|
||||
if (type[0] === "角色") {
|
||||
const data: TGApp.App.Character.WikiBriefInfo = type[1];
|
||||
res = {
|
||||
gacha_type: gacha.gacha_type,
|
||||
item_id: gacha.item_id,
|
||||
count: gacha.count ?? "1",
|
||||
time: gacha.time,
|
||||
name: data.name,
|
||||
item_type: "角色",
|
||||
rank_type: data.star.toString(),
|
||||
id: gacha.id,
|
||||
uigf_gacha_type: gacha.uigf_gacha_type,
|
||||
};
|
||||
} else if (type[0] === "武器") {
|
||||
const data: TGApp.App.Weapon.WikiBriefInfo = type[1];
|
||||
res = {
|
||||
gacha_type: gacha.gacha_type,
|
||||
item_id: gacha.item_id,
|
||||
count: gacha.count ?? "1",
|
||||
time: gacha.time,
|
||||
name: data.name,
|
||||
item_type: "武器",
|
||||
rank_type: data.star.toString(),
|
||||
id: gacha.id,
|
||||
uigf_gacha_type: gacha.uigf_gacha_type,
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取数据库的uid列表
|
||||
* @since Beta v0.4.7
|
||||
* @return {Promise<string[]>}
|
||||
*/
|
||||
async function getUidList(): Promise<string[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = Array<{ uid: string }>;
|
||||
const res = await db.select<resType>("SELECT DISTINCT uid FROM GachaRecords;");
|
||||
return res.map((i) => i.uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取检测增量更新的记录 ID
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} uid - UID
|
||||
* @param {string} type - 类型
|
||||
* @returns {Promise<string|undefined>}
|
||||
*/
|
||||
async function getGachaCheck(uid: string, type: string): Promise<string | undefined> {
|
||||
const db = await TGSqlite.getDB();
|
||||
type resType = Array<{ id: string }>;
|
||||
const res = await db.select<resType>(
|
||||
"SELECT id FROM GachaRecords WHERE uid = ? AND gachaType = ? ORDER BY id DESC LIMIT 1;",
|
||||
[uid, type],
|
||||
);
|
||||
if (res.length === 0) return undefined;
|
||||
return res[0].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取用户祈愿记录
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} uid - UID
|
||||
* @return {Promise<TGApp.Sqlite.GachaRecords.SingleTable[]>}
|
||||
*/
|
||||
async function getGachaRecords(uid: string): Promise<TGApp.Sqlite.GachaRecords.SingleTable[]> {
|
||||
const db = await TGSqlite.getDB();
|
||||
return await db.select("SELECT * FROM GachaRecords WHERE uid = ?;", [uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除指定UID的祈愿记录
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} uid - UID
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function deleteGachaRecords(uid: string): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
await db.execute("DELETE FROM GachaRecords WHERE uid = ?;", [uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 合并祈愿数据
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} uid - UID
|
||||
* @param {TGApp.Plugins.UIGF.GachaItem[]} data - UIGF数据
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function mergeUIGF(uid: string, data: TGApp.Plugins.UIGF.GachaItem[]): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
for (const gacha of data) {
|
||||
const trans = transGacha(gacha);
|
||||
const sql = importUIGFData(uid, trans);
|
||||
await db.execute(sql);
|
||||
}
|
||||
}
|
||||
|
||||
const TSUserGacha = {
|
||||
getUidList,
|
||||
getGachaCheck,
|
||||
getGachaRecords,
|
||||
getGachaItemType,
|
||||
deleteGachaRecords,
|
||||
mergeUIGF,
|
||||
};
|
||||
|
||||
export default TSUserGacha;
|
||||
@@ -1,79 +1,72 @@
|
||||
/**
|
||||
* @file plugins Sqlite sql updateData.ts
|
||||
* @file plugins/Sqlite/sql/updateData.ts
|
||||
* @description 更新数据
|
||||
* @author BTMuli <bt-muli@outlook.com>
|
||||
* @since Alpha v0.2.0
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
// utils
|
||||
import minifySql from "../../../utils/minifySql";
|
||||
|
||||
/**
|
||||
* @description 导入UIAF数据
|
||||
* @since Alpha v0.2.3
|
||||
* @param {TGApp.Plugins.UIAF.Achievement[]} data
|
||||
* @returns {string[]} sql
|
||||
* @description 导入UIAF数据-单项
|
||||
* @since Beta v0.4.7
|
||||
* @param {TGApp.Plugins.UIAF.Achievement} data
|
||||
* @returns {string} sql
|
||||
*/
|
||||
export function importUIAFData(data: TGApp.Plugins.UIAF.Achievement[]): string[] {
|
||||
const sqlRes: string[] = [];
|
||||
data.map((achievement) => {
|
||||
let sql;
|
||||
// 获取完成状态
|
||||
const isCompleted = achievement.status === 2 || achievement.status === 3;
|
||||
if (isCompleted) {
|
||||
const completedTime = new Date(achievement.timestamp * 1000)
|
||||
.toISOString()
|
||||
.replace("T", " ")
|
||||
.slice(0, 19);
|
||||
sql = `
|
||||
export function importUIAFData(data: TGApp.Plugins.UIAF.Achievement): string[] {
|
||||
let sql;
|
||||
const isCompleted = data.status === 2 || data.status === 3;
|
||||
if (isCompleted) {
|
||||
const completedTime = new Date(data.timestamp * 1000)
|
||||
.toISOString()
|
||||
.replace("T", " ")
|
||||
.slice(0, 19);
|
||||
sql = `
|
||||
UPDATE Achievements
|
||||
SET isCompleted = 1, completedTime = '${completedTime}', progress = ${achievement.current}, updated = datetime('now', 'localtime')
|
||||
WHERE id = ${achievement.id} AND (isCompleted = 0 OR completedTime != '${completedTime}' OR progress != ${achievement.current});
|
||||
SET isCompleted = 1,
|
||||
completedTime = '${completedTime}',
|
||||
progress = ${data.current},
|
||||
updated = datetime('now', 'localtime')
|
||||
WHERE id = ${data.id}
|
||||
AND (isCompleted = 0 OR completedTime != '${completedTime}'
|
||||
OR progress != ${data.current});
|
||||
`;
|
||||
} else {
|
||||
sql = `
|
||||
UPDATE Achievements
|
||||
SET progress = ${achievement.current},
|
||||
updated = datetime('now', 'localtime')
|
||||
WHERE id = ${achievement.id}
|
||||
AND progress != ${achievement.current};
|
||||
`;
|
||||
} else {
|
||||
sql = `
|
||||
UPDATE Achievements
|
||||
SET progress = ${achievement.current}, updated = datetime('now', 'localtime')
|
||||
WHERE id = ${achievement.id} AND progress != ${achievement.current};
|
||||
`;
|
||||
}
|
||||
return sqlRes.push(minifySql(sql));
|
||||
});
|
||||
return sqlRes;
|
||||
}
|
||||
return minifySql(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 导入UIGF数据
|
||||
* @since Alpha v0.2.3
|
||||
* @description 导入UIGF数据-单项
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} uid - UID
|
||||
* @param {TGApp.Plugins.UIGF.GachaItem[]} data - UIGF数据
|
||||
* @returns {string[]} sql
|
||||
* @param {TGApp.Plugins.UIGF.GachaItem} gacha - UIGF数据
|
||||
* @returns {string} sql
|
||||
*/
|
||||
export function importUIGFData(uid: string, data: TGApp.Plugins.UIGF.GachaItem[]): string[] {
|
||||
const sqlRes: string[] = [];
|
||||
data.forEach((gacha) => {
|
||||
const sql = `
|
||||
export function importUIGFData(uid: string, gacha: TGApp.Plugins.UIGF.GachaItem): string {
|
||||
const sql = `
|
||||
INSERT INTO GachaRecords (uid, gachaType, itemId, count, time, name, type, rank, id, uigfType, updated)
|
||||
VALUES ('${uid}', '${gacha.gacha_type}', '${gacha.item_id ?? null}', '${
|
||||
gacha.count ?? null
|
||||
}', '${gacha.time}',
|
||||
'${gacha.name}', '${gacha.item_type ?? null}', '${gacha.rank_type ?? null}', '${
|
||||
gacha.id
|
||||
}',
|
||||
VALUES ('${uid}', '${gacha.gacha_type}', '${gacha.item_id ?? null}', '${gacha.count ?? null}', '${gacha.time}',
|
||||
'${gacha.name}', '${gacha.item_type ?? null}', '${gacha.rank_type ?? null}', '${gacha.id}',
|
||||
'${gacha.uigf_gacha_type}', datetime('now', 'localtime'))
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
uid = '${uid}',
|
||||
gachaType = '${gacha.gacha_type}',
|
||||
uigfType = '${gacha.uigf_gacha_type}',
|
||||
time = '${gacha.time}',
|
||||
itemId = '${gacha.item_id ?? null}',
|
||||
count = '${gacha.count ?? null}',
|
||||
name = '${gacha.name}',
|
||||
type = '${gacha.item_type ?? null}',
|
||||
rank = '${gacha.rank_type ?? null}',
|
||||
updated = datetime('now', 'localtime');
|
||||
`;
|
||||
sqlRes.push(minifySql(sql));
|
||||
});
|
||||
return sqlRes;
|
||||
ON CONFLICT (id)
|
||||
DO UPDATE
|
||||
SET uid = '${uid}',
|
||||
gachaType = '${gacha.gacha_type}',
|
||||
uigfType = '${gacha.uigf_gacha_type}',
|
||||
time = '${gacha.time}',
|
||||
itemId = '${gacha.item_id ?? null}',
|
||||
count = '${gacha.count ?? null}',
|
||||
name = '${gacha.name}',
|
||||
type = '${gacha.item_type ?? null}',
|
||||
rank = '${gacha.rank_type ?? null}',
|
||||
updated = datetime('now', 'localtime');
|
||||
`;
|
||||
return minifySql(sql);
|
||||
}
|
||||
|
||||
6
src/types/Plugins/UIAF.d.ts
vendored
6
src/types/Plugins/UIAF.d.ts
vendored
@@ -14,7 +14,7 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
* @property {Achievement[]} list UIAF 成就列表
|
||||
* @return Data
|
||||
*/
|
||||
export interface Data {
|
||||
interface Data {
|
||||
info: Export;
|
||||
list: Achievement[];
|
||||
}
|
||||
@@ -29,7 +29,7 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
* @property {string} uiaf_version UIAF 版本
|
||||
* @return Export
|
||||
*/
|
||||
export interface Export {
|
||||
interface Export {
|
||||
export_app: string;
|
||||
export_timestamp: number;
|
||||
export_app_version: string;
|
||||
@@ -46,7 +46,7 @@ declare namespace TGApp.Plugins.UIAF {
|
||||
* @property {number} status 成就状态,0 为未完成,1 为已完成
|
||||
* @return Achievement
|
||||
*/
|
||||
export interface Achievement {
|
||||
interface Achievement {
|
||||
id: number;
|
||||
timestamp: number;
|
||||
current: number;
|
||||
|
||||
15
src/types/Sqlite/Achievement.d.ts
vendored
15
src/types/Sqlite/Achievement.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file types/Sqlite/Achievement.d.ts
|
||||
* @description 数据库成就相关类型定义文件
|
||||
* @since Alpha v0.2.0
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
declare namespace TGApp.Sqlite.Achievement {
|
||||
@@ -60,4 +60,17 @@ declare namespace TGApp.Sqlite.Achievement {
|
||||
nameCard: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 成就概况
|
||||
* @since Beta v0.4.7
|
||||
* @interface Overview
|
||||
* @property {number} total - 总成就数
|
||||
* @property {number} fin - 已完成成就数
|
||||
* @returns Overview
|
||||
*/
|
||||
interface Overview {
|
||||
total: number;
|
||||
fin: number;
|
||||
}
|
||||
}
|
||||
|
||||
2
src/types/Sqlite/GachaRecords.d.ts
vendored
2
src/types/Sqlite/GachaRecords.d.ts
vendored
@@ -23,7 +23,7 @@ declare namespace TGApp.Sqlite.GachaRecords {
|
||||
* @property {string} updated - 数据库更新时间
|
||||
* @return SingleTable
|
||||
*/
|
||||
export interface SingleTable {
|
||||
interface SingleTable {
|
||||
id: string;
|
||||
uid: string;
|
||||
gachaType: string;
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
/**
|
||||
* @file utils/UIAF.ts
|
||||
* @description UIAF工具类
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app, fs } from "@tauri-apps/api";
|
||||
import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { UiafSchema } from "../data/index.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
|
||||
/**
|
||||
* @description 根据 completed 跟 progress 获取 status
|
||||
@@ -40,16 +47,64 @@ export async function getUiafHeader(): Promise<TGApp.Plugins.UIAF.Export> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测是否存在 UIAF 数据
|
||||
* @description 粗略检测,不保证数据完整性
|
||||
* @since Alpha v0.2.3
|
||||
* @description 检测是否存在 UIAF 数据,采用 ajv 验证 schema
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} path - UIAF 数据路径
|
||||
* @returns {Promise<boolean>} 是否存在 UIAF 数据
|
||||
*/
|
||||
export async function verifyUiafData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const UiafData: TGApp.Plugins.UIAF.Export = JSON.parse(fileData)?.info;
|
||||
return UiafData?.uiaf_version !== undefined;
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UiafSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIAF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors?.toString()}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIAF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIAF 数据格式错误,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 验证UIAF数据-剪贴板
|
||||
* @since Beta v0.4.7
|
||||
* @returns {boolean} 是否验证通过
|
||||
*/
|
||||
export async function verifyUiafDataClipboard(): Promise<boolean> {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UiafSchema);
|
||||
const data = await window.navigator.clipboard.readText();
|
||||
try {
|
||||
const fileJson = JSON.parse(data);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIAF 数据验证失败,剪贴板数据:${data}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIAF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIAF 数据格式错误,剪贴板数据:${data}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
/**
|
||||
* @file utils/UIGF.ts
|
||||
* @description UIGF工具类
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { app, fs, path } from "@tauri-apps/api";
|
||||
import Ajv from "ajv";
|
||||
import { ErrorObject } from "ajv/lib/types/index.js";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar.js";
|
||||
import { UigfSchema } from "../data/index.js";
|
||||
|
||||
import TGLogger from "./TGLogger.js";
|
||||
import { timestampToDate } from "./toolFunc";
|
||||
|
||||
/**
|
||||
@@ -65,16 +71,34 @@ export function convertDataToUigf(
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 检测是否存在 UIGF 数据
|
||||
* @description 粗略检测,不保证数据完整性
|
||||
* @since Alpha v0.2.3
|
||||
* @description 检测是否存在 UIGF 数据,采用 ajv 验证 schema
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} path - UIGF 数据路径
|
||||
* @returns {Promise<boolean>} 是否存在 UIGF 数据
|
||||
*/
|
||||
export async function verifyUigfData(path: string): Promise<boolean> {
|
||||
const fileData: string = await fs.readTextFile(path);
|
||||
const UigfData: TGApp.Plugins.UIGF.Export = JSON.parse(fileData)?.info;
|
||||
return UigfData?.uigf_version !== undefined;
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(UigfSchema);
|
||||
try {
|
||||
const fileJson = JSON.parse(fileData);
|
||||
if (!validate(fileJson)) {
|
||||
const error: ErrorObject = validate.errors[0];
|
||||
showSnackbar({
|
||||
text: `${error.instancePath || error.schemaPath} ${error.message}`,
|
||||
color: "error",
|
||||
});
|
||||
await TGLogger.Error(`UIGF 数据验证失败,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${validate.errors}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
showSnackbar({ text: `UIGF 数据格式错误 ${e}`, color: "error" });
|
||||
await TGLogger.Error(`UIGF 数据格式错误,文件路径:${path}`);
|
||||
await TGLogger.Error(`错误信息 ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
/**
|
||||
* @file utils/dataBS.ts
|
||||
* @description 用户数据的备份、恢复、迁移
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { fs, path } from "@tauri-apps/api";
|
||||
|
||||
import showSnackbar from "../components/func/snackbar";
|
||||
import TGSqlite from "../plugins/Sqlite";
|
||||
import TSUserAchi from "../plugins/Sqlite/modules/userAchi.js";
|
||||
import TSUserGacha from "../plugins/Sqlite/modules/userGacha.js";
|
||||
|
||||
import { exportUigfData, readUigfData, verifyUigfData } from "./UIGF.js";
|
||||
|
||||
/**
|
||||
* @description 备份用户数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} dir 备份目录路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -20,8 +24,7 @@ export async function backUpUserData(dir: string): Promise<void> {
|
||||
console.log("备份目录不存在,创建备份目录");
|
||||
await fs.createDir(dir, { recursive: true });
|
||||
}
|
||||
// 备份成就数据
|
||||
const dataAchi = await TGSqlite.getUIAF();
|
||||
const dataAchi = await TSUserAchi.getUIAF();
|
||||
await fs.writeTextFile(`${dir}${path.sep}UIAF.json`, JSON.stringify(dataAchi));
|
||||
// 备份 ck
|
||||
const dataCK = await TGSqlite.getCookie();
|
||||
@@ -29,12 +32,18 @@ export async function backUpUserData(dir: string): Promise<void> {
|
||||
// 备份深渊数据
|
||||
const dataAbyss = await TGSqlite.getAbyss();
|
||||
await fs.writeTextFile(`${dir}${path.sep}abyss.json`, JSON.stringify(dataAbyss));
|
||||
// todo 添加祈愿数据备份支持
|
||||
// 备份祈愿数据
|
||||
const uidList = await TSUserGacha.getUidList();
|
||||
for (const uid of uidList) {
|
||||
const dataGacha = await TSUserGacha.getGachaRecords(uid);
|
||||
const savePath = `${dir}${path.sep}UIGF_${uid}.json`;
|
||||
await exportUigfData(uid, dataGacha, savePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 恢复用户数据
|
||||
* @since Beta v0.4.1
|
||||
* @since Beta v0.4.7
|
||||
* @param {string} dir 备份目录路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -47,17 +56,18 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const files = (await fs.readDir(dir)).filter((item) => item.type === "File");
|
||||
// 恢复成就数据
|
||||
const dataAchiPath = `${dir}${path.sep}UIAF.json`;
|
||||
if (await fs.exists(dataAchiPath)) {
|
||||
const achiFind = files.find((item) => item.name === "UIAF.json");
|
||||
if (achiFind) {
|
||||
try {
|
||||
const dataAchi: TGApp.Plugins.UIAF.Achievement[] = JSON.parse(
|
||||
await fs.readTextFile(dataAchiPath),
|
||||
await fs.readTextFile(achiFind.path),
|
||||
);
|
||||
await TGSqlite.mergeUIAF(dataAchi);
|
||||
await TSUserAchi.mergeUIAF(dataAchi);
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: "成就数据恢复失败",
|
||||
text: `成就数据恢复失败 ${e}`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
@@ -69,14 +79,14 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
// 恢复 ck
|
||||
const dataCKPath = `${dir}${path.sep}cookie.json`;
|
||||
if (await fs.exists(dataCKPath)) {
|
||||
const ckFind = files.find((item) => item.name === "cookie.json");
|
||||
if (ckFind) {
|
||||
try {
|
||||
const dataCK = await fs.readTextFile(dataCKPath);
|
||||
const dataCK = await fs.readTextFile(ckFind.path);
|
||||
await TGSqlite.saveAppData("cookie", JSON.stringify(JSON.parse(dataCK)));
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: "Cookie 数据恢复失败",
|
||||
text: `Cookie 数据恢复失败 ${e}`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
@@ -88,11 +98,11 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
});
|
||||
}
|
||||
// 恢复深渊数据
|
||||
const dataAbyssPath = `${dir}${path.sep}abyss.json`;
|
||||
if (await fs.exists(dataAbyssPath)) {
|
||||
const abyssFind = files.find((item) => item.name === "abyss.json");
|
||||
if (abyssFind) {
|
||||
try {
|
||||
const dataAbyss: TGApp.Sqlite.Abyss.SingleTable[] = JSON.parse(
|
||||
await fs.readTextFile(dataAbyssPath),
|
||||
await fs.readTextFile(abyssFind.path),
|
||||
);
|
||||
await TGSqlite.restoreAbyss(dataAbyss);
|
||||
} catch (e) {
|
||||
@@ -108,6 +118,29 @@ export async function restoreUserData(dir: string): Promise<void> {
|
||||
color: "warn",
|
||||
});
|
||||
}
|
||||
// 恢复祈愿数据
|
||||
const reg = /UIGF_(\d+).json/;
|
||||
const dataGachaList = files.filter((item) => reg.test(item.name));
|
||||
for (const item of dataGachaList) {
|
||||
const check = await verifyUigfData(item.path);
|
||||
if (!check) {
|
||||
errNum++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const data = await readUigfData(item.path);
|
||||
const uid = data.info.uid;
|
||||
for (const item of data.list) {
|
||||
await TSUserGacha.mergeUIGF(uid, item);
|
||||
}
|
||||
} catch (e) {
|
||||
showSnackbar({
|
||||
text: `UID: ${uid} 祈愿数据恢复失败`,
|
||||
color: "error",
|
||||
});
|
||||
errNum++;
|
||||
}
|
||||
}
|
||||
if (errNum === 0) {
|
||||
showSnackbar({
|
||||
text: "数据恢复成功",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* @file src/utils/linkParser.ts
|
||||
* @description 处理链接
|
||||
* @since Beta v0.3.9
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
|
||||
import showConfirm from "../components/func/confirm";
|
||||
import showSnackbar from "../components/func/snackbar";
|
||||
|
||||
@@ -50,7 +52,7 @@ export async function parsePost(link: string): Promise<false | string> {
|
||||
|
||||
/**
|
||||
* @function parseLink
|
||||
* @since Beta v0.3.9
|
||||
* @since Beta v0.4.7
|
||||
* @description 处理链接
|
||||
* @param {string} link - 链接
|
||||
* @param {boolean} useInner - 是否采用内置 JSBridge 打开
|
||||
@@ -78,6 +80,16 @@ export async function parseLink(
|
||||
const urlTransform = decodeURIComponent(url.search.replace("?url=", ""));
|
||||
return await parseLink(urlTransform, useInner);
|
||||
}
|
||||
console.log(url.pathname, url.search);
|
||||
// 处理特定路径
|
||||
if (url.pathname.startsWith("//discussion")) {
|
||||
await emit("active_deep_link", "router?path=/posts");
|
||||
return true;
|
||||
}
|
||||
if (link === "mihoyobbs://homeForum?game_id=2&tab_type=2") {
|
||||
await emit("active_deep_link", "router?path=/news/2/news");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file web/utils/parseAnno.ts
|
||||
* @description 解析游戏内公告数据
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
*/
|
||||
|
||||
import { saveImgLocal } from "../../utils/TGShare";
|
||||
@@ -33,7 +33,7 @@ function parseAnnoA(a: HTMLAnchorElement): HTMLAnchorElement {
|
||||
|
||||
/**
|
||||
* @description 解析 p
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
* @param {HTMLParagraphElement} p p 元素
|
||||
* @returns {HTMLParagraphElement} 解析后的 p 元素
|
||||
*/
|
||||
@@ -42,9 +42,7 @@ function parseAnnoP(p: HTMLParagraphElement): HTMLParagraphElement {
|
||||
p.innerHTML = decodeRegExp(p.innerHTML);
|
||||
} else {
|
||||
p.querySelectorAll("*").forEach((child) => {
|
||||
if (child.children.length === 0) {
|
||||
child.innerHTML = decodeRegExp(child.innerHTML);
|
||||
}
|
||||
child.innerHTML = decodeRegExp(child.innerHTML);
|
||||
});
|
||||
}
|
||||
return p;
|
||||
@@ -72,6 +70,9 @@ function parseAnnoSpan(span: HTMLSpanElement): HTMLSpanElement {
|
||||
if (child.children.length === 0) {
|
||||
child.innerHTML = decodeRegExp(child.innerHTML);
|
||||
}
|
||||
if (child.tagName === "T") {
|
||||
child.outerHTML = child.innerHTML;
|
||||
}
|
||||
});
|
||||
}
|
||||
return span;
|
||||
@@ -79,14 +80,16 @@ function parseAnnoSpan(span: HTMLSpanElement): HTMLSpanElement {
|
||||
|
||||
/**
|
||||
* @description 解析 table
|
||||
* @since Beta v0.4.4
|
||||
* @since Beta v0.4.7
|
||||
* @param {HTMLTableElement} table table 元素
|
||||
* @returns {HTMLTableElement} 解析后的 table 元素
|
||||
*/
|
||||
function parseAnnoTable(table: HTMLTableElement): HTMLTableElement {
|
||||
table.style.borderColor = "var(--common-shadow-2)";
|
||||
table.querySelectorAll("colgroup").forEach((colgroup) => colgroup.remove());
|
||||
table.querySelectorAll("td").forEach((td) => {
|
||||
if (td.style.backgroundColor) td.style.backgroundColor = "var(--box-bg-1)";
|
||||
td.style.textAlign = "center";
|
||||
});
|
||||
return table;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user