Compare commits

...

21 Commits

Author SHA1 Message Date
目棃
0c561ff98f 🚀 v0.4.7 2024-05-11 00:37:06 +08:00
目棃
817ae298b1 🐛 修复祈愿备份路径错误 2024-05-11 00:35:40 +08:00
目棃
92d2e847bd 🐛 修复导出字段数据缺失 2024-05-10 23:41:57 +08:00
目棃
d0f12446a8 ♻️ 名片组件抽离,wiki添加名片信息 2024-05-10 23:13:03 +08:00
目棃
a07d673776 ️ 完善公告正则 2024-05-09 17:20:21 +08:00
目棃
083381f2ec 💄 修复 tabs 渲染异常 2024-05-09 16:41:17 +08:00
目棃
040f49a33e 🐛 修复类型获取错误 2024-05-09 16:17:43 +08:00
目棃
3cfa00a2f6 💄 修复左侧列表高度异常 2024-05-09 15:55:58 +08:00
目棃
f6068e5bac 🐛 修复成就验证异常 2024-05-07 20:15:02 +08:00
目棃
557d68956e 🔧 移除packageManager,pnpm锁版本太烦了 2024-05-07 19:57:23 +08:00
目棃
2f14405cab ♻️ UIAF重构,支持祈愿备份/恢复
close #109
2024-05-07 19:55:23 +08:00
目棃
2803d06418 🍱 替换成就icon 2024-05-07 18:28:50 +08:00
目棃
298096858f 💄 调整文本部分的样式 2024-05-05 16:49:13 +08:00
目棃
7e11b467d1 💄 完善表格&p部分的渲染 2024-05-05 16:19:56 +08:00
目棃
7cd66ffb2d 采用 ajv 验证 UIGF #109 2024-05-05 15:49:40 +08:00
目棃
5de9c0a76f 🔥 隐藏网页登录模块
close #108
2024-05-05 02:54:56 +08:00
目棃
9a3b2ff9b9 🚨 忽略 NodeJS 的 undef 2024-05-05 02:50:54 +08:00
目棃
afa0ba190b ♻️ 重构用户祈愿数据库相关处理 2024-05-05 02:46:07 +08:00
目棃
6316cc42b2 ️ 对签到链接跳转进行处理,优化部分网页活动打开 2024-04-29 00:32:36 +08:00
目棃
abb0a6e751 🐛 修复 mention 类型渲染异常 2024-04-26 15:06:40 +08:00
目棃
c89dfae2f7 ⬆️ 更新依赖 2024-04-26 00:58:36 +08:00
50 changed files with 1611 additions and 1057 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -20,7 +20,6 @@ const pkgJsonConfig = {
"version",
"description",
"type",
"packageManager",
"scripts",
"lint-staged",
"keywords",

View File

@@ -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

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
View File

@@ -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]]

View File

@@ -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"

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "TeyvatGuide",
"version": "0.4.6"
"version": "0.4.7"
},
"tauri": {
"allowlist": {

View File

@@ -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,
});
});
}

View File

@@ -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>

View File

@@ -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(() => {

View File

@@ -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";

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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%;

View File

@@ -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>("");

View 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>

View File

@@ -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;

View File

@@ -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 = [];

View File

@@ -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">

View File

@@ -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>

View File

@@ -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({

View File

@@ -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 {

View File

@@ -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;

View 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"]
}

View 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"
}

View File

@@ -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;

View File

@@ -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} 条祈愿数据`,
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -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版本更新后).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/,
/(?:〓(?:活动|折扣)时间〓|祈愿时间|【上架时间】).*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/,
/〓活动时间〓.*?(\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;
}

View File

@@ -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({

View File

@@ -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: "取消则采用竖屏模式打开",

View File

@@ -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 浏览量

View File

@@ -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

View 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;

View 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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}
/**

View File

@@ -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;
}
}
/**

View File

@@ -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: "数据恢复成功",

View File

@@ -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;
}

View File

@@ -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;
}