Compare commits

...

43 Commits

Author SHA1 Message Date
BTMuli
2ab31d8f5c 🚀 v0.8.6 2025-11-19 14:09:48 +08:00
BTMuli
1af990512d 📝 更新README 2025-11-19 13:55:53 +08:00
BTMuli
d96d451156 👽️ 调整读取格式 2025-11-19 13:50:47 +08:00
BTMuli
f029306ebb 🚸 添加跳转视频链接 2025-11-19 13:40:25 +08:00
BTMuli
d3c5baa0c2 📝 更新资源说明文档 2025-11-19 13:22:01 +08:00
BTMuli
ba0802752c 🔊 完善log 2025-11-19 00:51:49 +08:00
BTMuli
ff94e12ff5 🚸 调整默认文本 2025-11-19 00:07:13 +08:00
BTMuli
0fbf1f7c2a 🚸 添加AIGC相关注释 2025-11-18 23:01:51 +08:00
BTMuli
68809a93c6 支撑导入剧诗数据 2025-11-18 22:51:32 +08:00
BTMuli
0edcadef63 👽️ 移除剧诗概览,支撑导入剧诗数据 2025-11-18 22:42:46 +08:00
BTMuli
9f9c30914f 🔥 移除胡桃深渊统计页面 2025-11-18 22:29:24 +08:00
BTMuli
04cf372798 🎨 路由重定向 2025-11-18 22:27:27 +08:00
BTMuli
6617a26c90 👽️ 移除深渊上传,支撑导入胡桃深渊数据 2025-11-18 22:20:58 +08:00
BTMuli
d244423800 🚸 调整导入浮窗ui,显示导入进度 2025-11-18 22:02:07 +08:00
BTMuli
3366efaadd 🐛 处理拓展解析异常 2025-11-15 20:36:50 +08:00
BTMuli
d74e7a7a31 🥅 处理异常,清除缓存后重启 2025-11-15 14:54:06 +08:00
BTMuli
2d0b409813 🐛 修复图片渲染异常 2025-11-15 14:37:05 +08:00
BTMuli
942068faea 🚀 v0.8.5 2025-11-10 16:34:14 +08:00
BTMuli
0f0f7684d2 🍱 更新下半数据 2025-11-10 16:31:01 +08:00
BTMuli
531cb32f72 🚀 v0.8.4 2025-10-27 19:48:35 +08:00
BTMuli
a368223805 千星奇域页面 2025-10-27 19:42:02 +08:00
BTMuli
6eab6c81f1 🍱 增加千星奇域元数据 2025-10-27 17:26:33 +08:00
BTMuli
68594a2a76 🐛 剔除多余换行 2025-10-27 12:33:09 +08:00
BTMuli
5d5f22d76e 🚸 添加prefix 2025-10-25 23:05:13 +08:00
BTMuli
65e948c34c 添加getRegionRoleInfo事件处理 2025-10-25 21:04:04 +08:00
BTMuli
68dead3d84 🔥 考虑合并祈愿,不单独分页 2025-10-25 19:46:03 +08:00
BTMuli
babc6a9a75 嵌入祈愿详情 2025-10-25 19:45:17 +08:00
BTMuli
6db4ff5ac9 👽️ 移除非必需参数 2025-10-25 18:59:26 +08:00
BTMuli
ce1b6f365e 🏷️ 调整类型注释 2025-10-25 14:07:46 +08:00
BTMuli
b6ed9668ac 🚸 完善类型,添加交互 2025-10-25 13:00:10 +08:00
BTMuli
2a2a190f5f 🐛 修复部分帖子渲染异常 2025-10-25 12:31:03 +08:00
BTMuli
5d03a32362 💄 调整名片样式 2025-10-24 23:35:12 +08:00
BTMuli
33d9ba5c4d 重构帖子解析逻辑,增加新类型解析
*PostID:69886846,69915487
2025-10-24 22:21:03 +08:00
BTMuli
9020214d23 🐛 修复部分帖子解析异常 2025-10-24 20:13:51 +08:00
BTMuli
78c3f79bfd 🧑‍💻 JSON内容复制 2025-10-24 19:58:31 +08:00
BTMuli
ee0fc6dbae 完善投稿活动类型声明,渲染投稿活动&交互
*PostID:69823686
2025-10-24 19:27:49 +08:00
BTMuli
8c51b79558 🐛 修复浮窗显示异常 2025-10-24 18:05:50 +08:00
BTMuli
8c1899637f 🌱 暂时将千星奇域移到祈愿子tab 2025-10-24 16:21:30 +08:00
BTMuli
56df920a7d 嵌入官方公告页面(已登录) 2025-10-24 16:15:05 +08:00
BTMuli
64c6f4ab8f 🚸 兑换码浮窗显示游戏名称 2025-10-24 12:20:15 +08:00
BTMuli
d3902d6e31 🌱 千星奇域抽卡记录页面 2025-10-23 23:56:23 +08:00
BTMuli
01e355b0d6 千星奇域抽卡记录获取 2025-10-23 23:09:58 +08:00
BTMuli
c40b3c6ff0 👽️ 公告添加千星奇域分类 2025-10-23 22:40:30 +08:00
83 changed files with 8108 additions and 2464 deletions

View File

@@ -2,12 +2,44 @@
Author: 目棃
Description: CHANGELOG
Date: 2025-09-09
Update: 2025-10-22
Update: 2025-11-19
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2025-09-09 14:30:56`
>
> 更新于 `2025-10-22 13:59:03`
> 更新于 `2025-11-19 14:08:20`
## [0.8.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.6) (2025-11-19)
> 关于胡桃数据库导入功能的说明请参考 [导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
- 👽️ 移除剧诗概览,支持导入胡桃剧诗数据
- 👽️ 移除深渊上传,支持导入胡桃深渊数据
- 🔥 移除胡桃深渊统计页面
- 🚸 调整导入祈愿记录浮窗ui显示导入进度
- 🐛 修复图片渲染异常
- 🥅 处理清除缓存异常,清除缓存后重启
- 🚸 帖子详情添加AIGC相关注释
- 🚸 添加跳转视频链接
- 📝 更新相关文档
## [0.8.5](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.5) (2025-11-10)
- 🍱 更新下半数据
## [0.8.4](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.4) (2025-10-27)
- 👽️ 公告添加千星奇域分类
- 🚸 兑换码浮窗显示游戏名称
- ✨ 嵌入官方公告页面(已登录)
- ✨ 嵌入官方祈愿详情(已登录)
- ✨ 完善投稿活动类型声明,渲染投稿活动&交互
- 🐛 修复部分帖子解析异常
- ✨ 重构帖子解析逻辑,增加新类型解析
- 💄 调整名片样式
- ✨ 添加getRegionRoleInfo事件处理
- 🐛 公告解析剔除多余换行
- ✨ 千星奇域祈愿页面草创
## [0.8.3](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.3) (2025-10-22)

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 说明文档
Date: 2023-03-05
Update: 2025-10-08
Update: 2025-11-19
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
>
> 更新于 `2025-10-08 10:08:49`
> 更新于 `2025-11-19 13:21:38`
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/BTMuli/TeyvatGuide) ![](https://img.shields.io/github/last-commit/BTMuli/TeyvatGuide) ![](https://img.shields.io/github/commits-since/BTMuli/TeyvatGuide/latest?include_prereleases)
@@ -65,13 +65,13 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 真境剧诗
- [x] 幽境危战
- [x] 祈愿数据获取(近一年)
- [x] 千星奇域祈愿数据获取(近一年)
- [x] 用户收藏帖子获取
- [x] 用户关注帖子获取
- [x] 一键完成米游币每日任务
- [x] 一键完成游戏签到
- Wiki 功能:
- [x] 深渊数据库Hutao API
- [x] 角色图鉴
- [x] 武器图鉴
- [x] 名片图鉴
@@ -90,7 +90,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
## UI 参考 / UI Reference
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
- [Starward](https://github.com/Scighost/Starward)
- [米游社](https://www.miyoushe.com/ys/)
- [原神](https://yuanshen.com/)
@@ -102,6 +102,8 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- UIAF[UIAF v1.1](docs/standards/UIAF.md)
- UIGF[UIGF v3.0](docs/standards/UIGF3.md)[UIGF v4.0](docs/standards/UIGF.md)
- [macOS 平台门禁属性导致应用无法打开应用的修复指引](docs/macos-gatekeeper/README.md)
- [隐私政策](https://app.btmuli.ink/docs/TeyvatGuide/privacy.html)
- [如何导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
## 特定项目 / Special Project
@@ -136,7 +138,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
本项目在开发过程中参考了诸多相关开源项目,特此鸣谢。
- [UIGF Organization](https://github.com/UIGF-org)
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
- [StarWard](https://github.com/Scighost/Starward)
- [xunkong](https://github.com/xunkong/xunkong)
- [gs-helper](https://github.com/vikiboss/gs-helper)

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 项目资源说明
Date: 2023-03-10
Update: 2025-02-28
Update: 2025-11-19
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-10 22:05:44`
>
> 更新于 `2025-02-28 09:40:33`
> 更新于 `2025-11-19 12:31:22`
## 说明
@@ -40,8 +40,8 @@ Update: 2025-02-28
相关仓库:
- [TGAssistant](https://github.com/BTMuli/TGAssistant):项目下游仓库,用于处理项目数据。
- [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata):胡桃元数据仓库,项目大部分数据来源于此。
- [Snap.Static](https://github.com/DGP-Studio/Snap.Static):胡桃静态资源仓库,项目部分图像资源来源于此。
- ~~[Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)~~:胡桃元数据仓库,项目大部分数据来源于此。
- ~~[Snap.Static](https://github.com/DGP-Studio/Snap.Static)~~:胡桃静态资源仓库,项目部分图像资源来源于此。
- [amos-data](https://github.com/yuehaiteam/amos-data):成就数据仓库,成就数据的详细信息来源于此。
## 字体

View File

@@ -1,9 +1,9 @@
{
"name": "teyvatguide",
"version": "0.8.3",
"version": "0.8.6",
"description": "Game Tool for GenshinImpact player",
"private": true,
"packageManager": "pnpm@10.19.0",
"packageManager": "pnpm@10.22.0",
"type": "module",
"scripts": {
"build": "tauri build",
@@ -72,63 +72,63 @@
"dependencies": {
"@mdi/font": "7.4.47",
"@tauri-apps/api": "^2.9.0",
"@tauri-apps/plugin-deep-link": "^2.4.3",
"@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-fs": "^2.4.2",
"@tauri-apps/plugin-http": "^2.5.2",
"@tauri-apps/plugin-log": "^2.7.0",
"@tauri-apps/plugin-opener": "^2.5.0",
"@tauri-apps/plugin-os": "^2.3.1",
"@tauri-apps/plugin-process": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.1",
"@tauri-apps/plugin-sql": "^2.3.0",
"@tauri-apps/plugin-deep-link": "^2.4.5",
"@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.4.4",
"@tauri-apps/plugin-http": "^2.5.4",
"@tauri-apps/plugin-log": "^2.7.1",
"@tauri-apps/plugin-opener": "^2.5.2",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.3",
"@tauri-apps/plugin-sql": "^2.3.1",
"ajv": "^8.17.1",
"artplayer": "^5.3.0",
"color-convert": "^3.1.2",
"color-convert": "^3.1.3",
"echarts": "^6.0.0",
"html2canvas": "^1.4.1",
"js-md5": "^0.8.3",
"jsencrypt": "^3.5.4",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"qrcode.vue": "^3.6.0",
"sass-embedded": "^1.93.2",
"sass-embedded": "^1.93.3",
"swiper": "^12.0.3",
"uuid": "^13.0.0",
"vue": "^3.5.22",
"vue": "^3.5.24",
"vue-echarts": "^8.0.1",
"vue-json-pretty": "^2.5.0",
"vue-json-pretty": "^2.6.0",
"vue-router": "^4.6.3",
"vuetify": "^3.10.6",
"vuetify": "^3.10.11",
"wcag-color": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@btmuli/stylelint-plugin-color": "^0.1.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.38.0",
"@tauri-apps/cli": "2.9.0",
"@eslint/js": "^9.39.1",
"@tauri-apps/cli": "2.9.4",
"@types/color-convert": "^2.0.4",
"@types/fs-extra": "^11.0.4",
"@types/js-md5": "^0.8.0",
"@types/node": "^24.9.1",
"@typescript-eslint/parser": "^8.46.2",
"@typescript/native-preview": "7.0.0-dev.20251021.1",
"@vitejs/plugin-vue": "^6.0.1",
"@types/node": "^24.10.1",
"@typescript-eslint/parser": "^8.47.0",
"@typescript/native-preview": "7.0.0-dev.20251118.1",
"@vitejs/plugin-vue": "^6.0.2",
"app-root-path": "^3.1.0",
"concurrently": "^9.2.1",
"eslint": "^9.38.0",
"eslint": "^9.39.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsonc": "^2.21.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.5.1",
"eslint-plugin-yml": "^1.19.0",
"fs-extra": "^11.3.2",
"globals": "^16.4.0",
"globals": "^16.5.0",
"husky": "^9.1.7",
"jsonc-eslint-parser": "^2.4.1",
"lint-staged": "^16.2.5",
"oxlint": "^1.23.0",
"lint-staged": "^16.2.6",
"oxlint": "^1.29.0",
"prettier": "3.6.2",
"stylelint": "^16.25.0",
"stylelint-config-idiomatic-order": "^10.0.0",
@@ -141,12 +141,12 @@
"stylelint-scss": "^6.12.1",
"tsx": "^4.20.6",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.2",
"vite": "npm:rolldown-vite@^7.1.19",
"vite-plugin-vue-devtools": "^8.0.3",
"typescript-eslint": "^8.47.0",
"vite": "npm:rolldown-vite@^7.2.6",
"vite-plugin-vue-devtools": "^8.0.5",
"vite-plugin-vuetify": "^2.1.2",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.1.1",
"vue-tsc": "^3.1.4",
"yaml-eslint-parser": "^1.3.0"
}
}

2165
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

27
src-tauri/Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "TeyvatGuide"
version = "0.8.3"
version = "0.8.6"
dependencies = [
"chrono",
"log",
@@ -4869,9 +4869,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.9.0"
version = "2.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f07c6590706b2fc0ab287b041cf5ce9c435b3850bdae5571e19d9d27584e89d"
checksum = "9e492485dd390b35f7497401f67694f46161a2a00ffd800938d5dd3c898fb9d8"
dependencies = [
"anyhow",
"bytes",
@@ -4912,7 +4912,6 @@ dependencies = [
"tokio",
"tray-icon",
"url",
"urlpattern",
"webkit2gtk",
"webview2-com",
"window-vibrancy",
@@ -4921,9 +4920,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.5.0"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71be1f494b683ac439e6d61c16ab5c472c6f9c6ee78995b29556d9067c021a1"
checksum = "87d6f8cafe6a75514ce5333f115b7b1866e8e68d9672bf4ca89fc0f35697ea9d"
dependencies = [
"anyhow",
"cargo_toml",
@@ -4943,9 +4942,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.5.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190"
checksum = "b7ef707148f0755110ca54377560ab891d722de4d53297595380a748026f139f"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -4970,9 +4969,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.5.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f"
checksum = "71664fd715ee6e382c05345ad258d6d1d50f90cf1b58c0aa726638b33c2a075d"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -5202,9 +5201,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3367f0b47df90e9195cd9f04a56b0055a2cba45aa11923c6c253d748778176fc"
checksum = "9368f09358496f2229313fccb37682ad116b7f46fa76981efe116994a0628926"
dependencies = [
"cookie",
"dpi",
@@ -5227,9 +5226,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d91d29ca680c545364cf75ba2f2e3c7ea2ab6376bfa3be26b56fa2463a5b5e"
checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93"
dependencies = [
"gtk",
"http",

View File

@@ -1,6 +1,6 @@
[package]
name = "TeyvatGuide"
version = "0.8.3"
version = "0.8.6"
description = "Game Tool for Genshin Impact player"
authors = ["BTMuli <bt-muli@outlook.com>"]
license = "MIT"
@@ -17,14 +17,14 @@ name = "teyvat_guide_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.0", features = [] }
tauri-build = { version = "2.5.2", features = [] }
[dependencies]
chrono = "0.4.42"
log = "0.4.28"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
tauri = { version = "2.9.0", features = [] }
tauri = { version = "2.9.3", features = [] }
tauri-utils = "2.8.0"
url = "2.5.7"
walkdir = "2.5.0"

View File

@@ -8,7 +8,7 @@ mod utils;
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
use tauri_utils::config::WebviewUrl;
static BBS_VERSION: &'static str = "2.93.0";
static BBS_VERSION: &'static str = "2.95.1";
#[tauri::command]
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2",
"productName": "TeyvatGuide",
"identifier": "TeyvatGuide",
"version": "0.8.3",
"version": "0.8.6",
"build": {
"beforeDevCommand": "pnpm vite:dev",
"beforeBuildCommand": "pnpm vite:build",

View File

@@ -7,7 +7,7 @@
<div v-if="hasNav" class="tgn-nav">
<v-icon size="25" @click="tryGetCode" title="查看兑换码">mdi-code-tags-check</v-icon>
</div>
<ToLivecode v-model="showOverlay" :data="codeData" v-model:actId="actId" />
<ToLivecode v-model="showOverlay" :gid="model" :data="codeData" :actId="actId" />
</div>
</template>
<script lang="ts" setup>

View File

@@ -112,7 +112,10 @@
}"
v-else
>
{{ props.modelValue.post.post_id }}
<span>{{ props.modelValue.post.post_id }}</span>
<template v-if="isDevEnv">
<span data-html2canvas-ignore>[{{ props.modelValue.post.view_type }}]</span>
</template>
</div>
</div>
</template>
@@ -154,7 +157,8 @@ export type RenderCard = {
topics: Array<TGApp.BBS.Post.Topic>;
reasons: Array<TGApp.BBS.Post.RecommendTags>;
};
// @ts-expect-error The import.meta meta-property is not allowed in files which will build into CommonJS output.
const isDevEnv = import.meta.env.MODE === "development";
const stats: Readonly<Array<RenderStatus>> = [
{ stat: 0, label: "未知", color: "var(--tgc-od-red)" },
{ stat: 1, label: "进行中", color: "var(--tgc-od-green)" },

View File

@@ -110,11 +110,6 @@
</v-list-item>
</template>
<v-list class="side-list-menu sub" density="compact" :nav="true">
<v-list-item class="side-item-menu" title="深渊数据库" :link="true" href="/wiki/abyss">
<template #prepend>
<img src="/source/UI/wikiAbyss.webp" alt="abyssIcon" class="side-icon-menu" />
</template>
</v-list-item>
<v-list-item class="side-item-menu" title="角色图鉴" :link="true" href="/wiki/character">
<template #prepend>
<img src="/source/UI/wikiAvatar.webp" alt="characterIcon" class="side-icon-menu" />

View File

@@ -2,7 +2,7 @@
<TOverlay v-model="visible" class="tolc-overlay">
<div class="tolc-box">
<div class="tolc-title">
<span>兑换码</span>
<span>{{ gameInfo?.name ?? "" }}兑换码</span>
<v-icon
size="18px"
title="share"
@@ -39,16 +39,28 @@
</template>
<script setup lang="ts">
import showSnackbar from "@comp/func/snackbar.js";
import useBBSStore from "@store/bbs.js";
import { generateShareImg } from "@utils/TGShare.js";
import { timestampToDate } from "@utils/toolFunc.js";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import TMiImg from "./t-mi-img.vue";
import TOverlay from "./t-overlay.vue";
type ToLiveCodeProps = { data: Array<TGApp.BBS.Navigator.CodeData>; actId: string | undefined };
type ToLiveCodeProps = {
data: Array<TGApp.BBS.Navigator.CodeData>;
actId: string | undefined;
gid: number;
};
const { gameList } = storeToRefs(useBBSStore());
const props = defineProps<ToLiveCodeProps>();
const visible = defineModel<boolean>({ default: false });
const gameInfo = computed<TGApp.BBS.Game.Item | undefined>(() => {
return gameList.value.find((i) => i.id === props.gid);
});
function copy(code: string): void {
navigator.clipboard.writeText(code);

View File

@@ -1,40 +1,35 @@
<!-- 名片栏组件 -->
<template>
<div
class="top-nc-box"
@click="emit('selected', props.data)"
:class="props.finish ? '' : 'grey'"
:title.attr="props.data.name"
:style="{ backgroundImage: `url('/WIKI/nameCard/bg/${props.data.name}.webp')` }"
>
<v-list-item>
<template #title>
<div class="title">
<TwnTypeTag :type="props.data.type" />
<span>{{ props.data.name }}</span>
</div>
</template>
<template #subtitle>
<div class="top-nc-bgc" />
<div class="top-nc-prepend">
<img :src="`/WIKI/nameCard/icon/${props.data.name}.webp`" alt="icon" />
</div>
<div class="top-nc-info">
<div class="top-nc-title">
<TwnTypeTag :type="props.data.type" />
<span>{{ props.data.name }}</span>
</div>
<div class="top-nc-desc">
<span class="desc" :title="props.data.desc">{{ props.data.desc }}</span>
</template>
<template #prepend>
<img :src="`/WIKI/nameCard/icon/${props.data.name}.webp`" alt="icon" class="icon" />
</template>
</v-list-item>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import TwnTypeTag from "@comp/pageWiki/twn-type-tag.vue";
import { computed } from "vue";
type TopNameCardProps = { data: TGApp.App.NameCard.Item; finish?: boolean };
type TopNameCardEmits = (e: "selected", v: TGApp.App.NameCard.Item) => void;
const props = withDefaults(defineProps<TopNameCardProps>(), { finish: true });
const emit = defineEmits<TopNameCardEmits>();
const bgImage = computed<string>(() => {
if (props.data.name === "原神·印象") return "none;";
return `url("/WIKI/nameCard/bg/${props.data.name}.webp")`;
});
</script>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;
@@ -42,17 +37,19 @@ const bgImage = computed<string>(() => {
.top-nc-box {
@include github-styles.github-card-shadow;
position: relative;
display: flex;
width: 100%;
height: 80px;
box-sizing: border-box;
align-items: center;
justify-content: flex-start;
border: 1px solid var(--common-shadow-1);
border-radius: 4px 50px 50px 4px;
background-color: var(--box-bg-1);
background-image: v-bind(bgImage); /* stylelint-disable-line value-keyword-case */
background-position: right;
background-repeat: no-repeat;
column-gap: 8px;
cursor: pointer;
font-family: var(--font-title);
transition: filter 0.5s ease-in-out;
@@ -70,19 +67,60 @@ const bgImage = computed<string>(() => {
@include github-styles.github-card-shadow("dark");
}
.icon {
height: 60px;
margin-right: 12px;
aspect-ratio: 23 / 15;
.top-nc-bgc {
position: absolute;
z-index: 0;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to right, var(--box-bg-1) 40%, transparent 75%);
}
.title {
.top-nc-prepend {
position: relative;
z-index: 1;
display: flex;
height: 60px;
flex-shrink: 0;
align-items: center;
justify-content: center;
margin-left: 8px;
img {
height: 60px;
aspect-ratio: 23/15;
}
}
.top-nc-info {
position: relative;
z-index: 1;
display: flex;
overflow: hidden;
height: 100%;
flex-direction: column;
flex-grow: 1;
align-items: flex-start;
justify-content: center;
margin-right: 8px;
}
.top-nc-title {
display: flex;
align-items: center;
column-gap: 4px;
font-size: 16px;
}
.desc {
text-shadow: 0 0 2px var(--common-shadow-t-8);
.top-nc-desc {
position: relative;
overflow: hidden;
max-width: calc(100% - 16px);
font-size: 14px;
opacity: 0.75;
text-overflow: ellipsis;
text-shadow: 0 0 2px var(--box-bg-1);
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,105 @@
<!-- 游戏内公告浮窗 -->
<template>
<TOverlay v-model="visible">
<div class="tao-iframe-box">
<!-- TODO:加载完成后修改样式 -->
<iframe :src="link" class="tao-iframe" />
</div>
</TOverlay>
</template>
<script lang="ts" setup>
import TOverlay from "@comp/app/t-overlay.vue";
import showSnackbar from "@comp/func/snackbar.js";
import takumiReq from "@req/takumiReq.js";
import useAppStore from "@store/app.js";
import useUserStore from "@store/user.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, watch } from "vue";
const { lang } = storeToRefs(useAppStore());
const { cookie, account } = storeToRefs(useUserStore());
const visible = defineModel<boolean>();
const authkey = ref<string>("");
const link = ref<string>("");
onMounted(async () => await refreshUrl());
watch(
() => lang.value,
async () => {
if (!visible.value) return;
await refreshUrl();
},
);
async function refreshUrl(): Promise<void> {
const res = await getUrl();
if (res === "") return;
link.value = res;
}
async function refreshAuthkey(): Promise<void> {
if (!cookie.value || !account.value) {
visible.value = false;
showSnackbar.warn("请先登录账号");
return;
}
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
if (typeof authkeyRes === "string") {
authkey.value = authkeyRes;
} else {
showSnackbar.error("获取authkey失败");
visible.value = false;
return;
}
}
async function getUrl(): Promise<string> {
const path = "https://sdk.mihoyo.com/hk4e/announcement/index.html";
if (authkey.value === "") await refreshAuthkey();
if (authkey.value === "") return "";
const param: Record<string, string> = {
auth_appid: "announcement",
authkey_ver: "1",
bundle_id: "hk4e_cn",
channel_id: "14",
game: "hk4e",
game_biz: account.value.gameBiz,
lang: lang.value,
level: account.value.level,
platform: "pc",
region: account.value.region,
sdk_presentation_style: "fullscreen",
sdk_screen_transparent: "true",
sign_type: "2",
uid: account.value.gameUid,
timestamp: Math.floor(Date.now() / 1000).toString(),
authkey: authkey.value,
};
const targetLink = new URL(path);
for (const key in param) {
targetLink.searchParams.append(key, param[key]);
}
return targetLink.toString();
}
</script>
<style lang="scss" scoped>
.tao-iframe-box {
position: relative;
display: flex;
overflow: hidden;
width: 50vw;
align-items: center;
justify-content: center;
border-radius: 8px;
aspect-ratio: 16/9;
}
.tao-iframe {
width: 100%;
height: 100%;
border: none;
background-color: transparent;
}
</style>

View File

@@ -404,6 +404,9 @@ async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | fa
if ("retcode" in captchaResp) {
if (!captchaResp.data || captchaResp.data === "") {
showSnackbar.error(`[${captchaResp.retcode}] ${captchaResp.message}`);
await TGLogger.Error(
`[tc-userBadge][tryGetCaptcha] ${captchaResp.retcode} ${captchaResp.message}`,
);
return false;
}
const aigisResp: TGApp.BBS.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
@@ -424,6 +427,9 @@ async function tryLoginByCaptcha(
if ("retcode" in loginResp) {
if (!loginResp.data || loginResp.data === "") {
showSnackbar.error(`[${loginResp.retcode}] ${loginResp.message}`);
await TGLogger.Error(
`[tc-userBadge][tryLoginByCaptcha] ${loginResp.retcode} ${loginResp.message}`,
);
return false;
}
const aigisResp: TGApp.BBS.CaptchaLogin.CaptchaAigis = JSON.parse(loginResp.data);

View File

@@ -3,7 +3,9 @@
<div class="tucfi-label">
<slot name="label">{{ props.label }}</slot>
</div>
<div v-if="props.data === null"><span class="tucfi-data">暂无数据</span></div>
<div v-if="!props.data">
<span class="tucfi-data">暂无数据</span>
</div>
<div v-else-if="!Array.isArray(props.data)" class="tucfi-data">
<TItemBox :model-value="getBox(props.data)" />
</div>

View File

@@ -0,0 +1,144 @@
<!-- 千星奇域概览单项组件 -->
<template>
<div class="gbr-dl-box">
<div class="gbr-dl-progress" />
<div class="gbr-dl-icon">
<img :alt="props.data.name" :src="getIcon()" />
</div>
<div class="gbr-dl-base">
<div class="gbr-dl-name">{{ props.data.name }}</div>
<div class="gbr-dl-time">{{ props.data.time }}</div>
</div>
<div class="gbr-dl-info">
<div class="gbr-dl-cnt">{{ props.count }}</div>
<div class="gbr-dl-hint" v-if="hint !== ''">{{ hint }}</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
export type GbrDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGachaB; count: number };
const props = defineProps<GbrDataLineProps>();
const hint = getEndHint();
function getIcon(): string {
console.log(props.data);
// const find = AppGachaBData.find((i) => i.id.toString() === props.data.itemId);
// if (!find) return `/source/UI/paimon.webp`;
// return `https://api.hakush.in/gi/UI/${find.icon}.webp`;
// TODO: 缺失元数据
return `/source/UI/paimon.webp`;
}
function getEndHint(): string {
if (props.data.gachaType === "1000") return "";
if (!props.data.isUp) return "歪";
return "";
}
const progressColor = computed<string>(() => {
if (hint === "UP" && props.data.rank === "5") return "#d19a66";
if (hint === "UP" && props.data.rank === "4") return "#c678dd";
if (hint === "歪") return "#e06c75";
return "#61afef";
});
const progressWidth = computed<string>(() => {
let final = 10;
if (props.data.rank === "5") {
if (props.data.gachaType === "302") final = 80;
else final = 90;
} else if (props.data.rank === "4") final = 10;
else return "0%";
return ((props.count / final) * 100).toFixed(2) + "%";
});
</script>
<style lang="scss" scoped>
.gbr-dl-box {
position: relative;
display: flex;
width: 100%;
height: 48px;
box-sizing: border-box;
align-items: center;
justify-content: flex-start;
padding: 8px;
border: 1px solid var(--common-shadow-1);
border-radius: 4px;
background: var(--box-bg-2);
column-gap: 4px;
}
.gbr-dl-progress {
position: absolute;
bottom: 0;
left: 0;
width: v-bind(progressWidth); /* stylelint-disable-line value-keyword-case */
max-width: 100%;
height: 4px;
border-radius: 4px;
background: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
}
.gbr-dl-icon {
display: flex;
width: 32px;
height: 32px;
flex-shrink: 0;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
}
}
.gbr-dl-base {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.gbr-dl-name {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 14px;
line-height: 18px;
}
.gbr-dl-time {
color: var(--box-text-7);
font-size: 12px;
line-height: 14px;
}
.gbr-dl-info {
display: flex;
align-items: center;
justify-content: center;
margin-left: auto;
column-gap: 4px;
}
.gbr-dl-cnt {
color: var(--common-text-title);
font-family: var(--font-title);
}
.gbr-dl-hint {
display: flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
padding: 4px;
border-radius: 50%;
background: var(--box-bg-3);
color: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
font-family: var(--font-title);
transform: rotate(25deg);
}
</style>

View File

@@ -0,0 +1,263 @@
<!-- 千星奇域概览数据视图组件 -->
<template>
<div class="gbr-dv-container">
<div class="gbr-dvt-title">
<span>{{ title }}</span>
<span>{{ props.dataVal.length }}</span>
</div>
<div class="gbr-dvt-subtitle">
<span v-show="props.dataVal.length === 0">暂无数据</span>
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
</div>
<div class="gbr-mid-list">
<div class="gbr-ml-item">
<span>4已垫</span>
<span>{{ reset4count - 1 }}</span>
</div>
<div class="gbr-ml-item">
<span>5已垫</span>
<span>{{ reset5count - 1 }}</span>
</div>
<div class="gbr-ml-item">
<span>5平均</span>
<span>{{ star5avg }}</span>
</div>
</div>
<div class="gbr-mid-list">
<div class="gbr-ml-item">
<span>5统计</span>
<span>{{ getTitle("5") }}</span>
</div>
<div class="gbr-ml-item">
<span>4统计</span>
<span>{{ getTitle("4") }}</span>
</div>
</div>
<!-- 这边放具体物品的列表 -->
<div class="gbr-bottom">
<v-tabs v-model="tab" density="compact">
<v-tab value="5">5</v-tab>
<v-tab value="4">4</v-tab>
<v-tab value="3">3</v-tab>
</v-tabs>
<v-window v-model="tab" class="gbr-bottom-window">
<v-window-item value="5" class="gbr-b-window-item">
<v-virtual-scroll :items="star5List" :item-height="48">
<template #default="{ item }">
<GbrDataLine :data="item.data" :count="item.count" />
</template>
</v-virtual-scroll>
</v-window-item>
<v-window-item value="4" class="gbr-b-window-item">
<v-virtual-scroll :items="star4List" :item-height="48">
<template #default="{ item }">
<GbrDataLine :data="item.data" :count="item.count" />
</template>
</v-virtual-scroll>
</v-window-item>
<v-window-item value="3" class="gbr-b-window-item">
<v-virtual-scroll :items="star3List" :item-height="48">
<template #default="{ item }">
<GbrDataLine :data="item.data" :count="item.count" />
</template>
</v-virtual-scroll>
</v-window-item>
</v-window>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, shallowRef, watch } from "vue";
import GbrDataLine, { type GbrDataLineProps } from "./gbr-data-line.vue";
type GachaDataViewProps = {
dataType: "normal" | "boy" | "girl";
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGachaB>;
};
const props = defineProps<GachaDataViewProps>();
// data
const loading = ref<boolean>(true); // 是否加载完
const title = ref<string>(""); // 卡片标题
const startDate = ref<string>(""); // 最早的时间
const endDate = ref<string>(""); // 最晚的时间
const star5List = shallowRef<Array<GbrDataLineProps>>([]); // 5星物品数据
const star4List = shallowRef<Array<GbrDataLineProps>>([]); // 4星物品数据
const star3List = shallowRef<Array<GbrDataLineProps>>([]);
const reset5count = ref<number>(1); // 5星垫抽数量
const reset4count = ref<number>(1); // 4星垫抽数量
const reset3count = ref<number>(1); // 3星垫抽数量
const star3count = ref<number>(0); // 3星物品数量
const star5avg = ref<string>(""); // 5星平均抽数
const tab = ref<string>("5"); // tab
onMounted(() => {
loadData();
loading.value = false;
});
function loadData(): void {
title.value = getTitle("top");
const tempData = props.dataVal;
const temp5Data: Array<GbrDataLineProps> = [];
const temp4Data: Array<GbrDataLineProps> = [];
const temp3Data: Array<GbrDataLineProps> = [];
// 按照 id 升序
tempData
.sort((a, b) => a.id.localeCompare(b.id))
.forEach((item) => {
// 处理时间
if (startDate.value === "" || item.time < startDate.value) startDate.value = item.time;
if (endDate.value === "" || item.time > endDate.value) endDate.value = item.time;
if (item.rank === "2") {
reset3count.value++;
reset4count.value++;
reset5count.value++;
} else if (item.rank === "3") {
reset4count.value++;
reset5count.value++;
temp3Data.push({ data: item, count: reset3count.value });
reset3count.value = 1;
} else if (item.rank === "4") {
reset5count.value++;
temp4Data.push({ data: item, count: reset4count.value });
reset4count.value = 1;
} else if (item.rank === "5") {
reset4count.value++;
temp5Data.push({ data: item, count: reset5count.value });
reset5count.value = 1;
}
});
star5List.value = temp5Data.reverse();
star4List.value = temp4Data.reverse();
star3List.value = temp3Data.reverse();
star5avg.value = getStar5Avg();
}
// 获取标题
function getTitle(type: "top" | "5" | "4" | "3"): string {
if (type === "top") {
if (props.dataType === "normal") return "常驻颂愿";
if (props.dataType === "boy") return "活动颂愿(男)";
if (props.dataType === "girl") return "活动颂愿(女)";
return "";
}
if (props.dataVal.length === 0) return "暂无数据";
if (type === "5") {
// 5星物品统计 00.00%
return `${star5List.value.length} [${((star5List.value.length * 100) / props.dataVal.length)
.toFixed(2)
.padStart(5, "0")}%]`;
}
if (type === "4") {
// 4星物品统计
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
.toFixed(2)
.padStart(5, "0")}%]`;
}
// 3星物品统计
return `${star3count.value} [${((star3count.value * 100) / props.dataVal.length)
.toFixed(2)
.padStart(5, "0")}%]`;
}
// 获取5星平均抽数
function getStar5Avg(): string {
const resetList = star5List.value.map((item) => item.count);
if (resetList.length === 0) return "0";
const total = resetList.reduce((a, b) => a + b);
return (total / star5List.value.length).toFixed(2);
}
// 监听数据变化
watch(
() => props.dataVal,
() => {
star5List.value = [];
star4List.value = [];
reset5count.value = 1;
reset4count.value = 1;
star3count.value = 1;
startDate.value = "";
endDate.value = "";
star5avg.value = "";
tab.value = "5";
loadData();
},
);
</script>
<style lang="css" scoped>
.gbr-dv-container {
height: 100%;
box-sizing: border-box;
padding: 8px;
border-radius: 4px;
background: var(--box-bg-1);
}
.gbr-dvt-title {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 18px;
}
.gbr-dvt-subtitle {
width: 100%;
font-family: var(--font-text);
font-size: 12px;
opacity: 0.6;
}
.gbr-mid-list {
padding-top: 4px;
padding-bottom: 4px;
border-top: 1px solid var(--common-shadow-4);
color: var(--box-text-7);
}
.gbr-ml-item {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
font-family: var(--font-title);
font-size: 14px;
}
.gbr-bottom {
position: relative;
display: flex;
width: 100%;
height: calc(100% - 150px);
box-sizing: border-box;
flex-direction: column;
gap: 8px;
}
.gbr-bottom-window {
position: relative;
height: calc(100vh - 428px);
overflow-y: auto;
}
.gbr-b-window-item {
position: relative;
width: 100%;
box-sizing: border-box;
padding-right: 4px;
}
/* stylelint-disable selector-class-pattern */
:deep(.v-virtual-scroll__item + .v-virtual-scroll__item) {
margin-top: 8px;
}
/* stylelint-enable selector-class-pattern */
</style>

View File

@@ -0,0 +1,34 @@
<!-- 千星奇域祈愿概览组件 -->
<template>
<div class="gro-o-container">
<GbrDataView :data-val="normalData" data-type="normal" />
<GbrDataView :data-val="boyData" data-type="boy" />
<GbrDataView :data-val="girlData" data-type="girl" />
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import GbrDataView from "./gbr-data-view.vue";
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
const props = defineProps<GachaOverviewProps>();
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
props.modelValue.filter((item) => item.opGachaType === "1000"),
);
const girlData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
props.modelValue.filter((item) => item.opGachaType === "20011" || item.opGachaType === "20012"),
);
const boyData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
props.modelValue.filter((item) => item.opGachaType === "20021" || item.opGachaType === "20022"),
);
</script>
<style lang="css" scoped>
.gro-o-container {
display: grid;
height: 100%;
column-gap: 8px;
grid-template-columns: repeat(3, 1fr);
}
</style>

View File

@@ -0,0 +1,62 @@
<!-- 千星奇域数据表格 -->
<template>
<v-data-table
:headers="headers"
:items="props.modelValue"
fixed-header
fixed-footer
class="gbr-t-box"
>
<template v-slot:item="{ item }">
<tr class="gbr-t-tr">
<td>{{ item.time }}</td>
<td>{{ getPool(item.opGachaType) }}</td>
<td>{{ item.type }}</td>
<td>{{ item.name }}</td>
<td>{{ item.rank }}</td>
</tr>
</template>
</v-data-table>
</template>
<script lang="ts" setup>
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
const props = defineProps<GroTableProps>();
const headers = <const>[
{ title: "时间", align: "center", key: "time" },
{ title: "卡池", align: "center", key: "opGachaType" },
{ title: "类型", align: "center", key: "type" },
{ title: "名称", align: "center", key: "name" },
{ title: "星级", align: "center", key: "rank" },
];
function getPool(type: string) {
switch (type) {
case "1000":
return "常驻颂愿";
case "2000":
return "活动颂愿";
case "20011":
case "20012":
return "活动颂愿-男";
case "20021":
case "20022":
return "活动颂愿-女";
default:
return "未知";
}
}
</script>
<style lang="css" scoped>
.gbr-t-box {
height: calc(100vh - 200px);
padding-right: 5px;
border-radius: 5px;
overflow-y: auto;
}
.gbr-t-tr {
text-align: center;
}
</style>

View File

@@ -20,7 +20,7 @@ import { computed } from "vue";
import { AppGachaData } from "@/data/index.js";
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.SingleTable; count: number };
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGacha; count: number };
const props = defineProps<GroDataLineProps>();
const hint = getEndHint();

View File

@@ -64,7 +64,7 @@ import GroDataLine, { type GroDataLineProps } from "./gro-data-line.vue";
type GachaDataViewProps = {
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
dataVal: Array<TGApp.Sqlite.GachaRecords.SingleTable>;
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGacha>;
};
const props = defineProps<GachaDataViewProps>();

View File

@@ -0,0 +1,134 @@
<!-- 嵌入游戏内容的iframe组件 -->
<template>
<div class="gro-iframe-container">
<v-tabs class="gro-ic-tabs" v-model="poolTab" align-tabs="start" direction="vertical">
<v-tab v-for="(item, index) in tabList" :key="index" :value="item.value">
<template v-if="item.beyond">
<img src="/icon/nation/千星奇域.webp" title="千星奇域" alt="beyond" />
</template>
{{ item.label }}
</v-tab>
</v-tabs>
<iframe class="gro-iframe" :src="link" style="width: 100%; height: 100%; border: none" />
</div>
</template>
<script lang="ts" setup>
import showSnackbar from "@comp/func/snackbar.js";
import takumiReq from "@req/takumiReq.js";
import useUserStore from "@store/user.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
/**
* 卡池类型-ID映射
* @remarks
* 目前缺失集录&新手池
* TODO: 动态获取当前卡池类型&ID映射
*/
const GachaIdMap: Record<string, string> = {
"200": "34ff1a235049182fd199d285110e3e7d292c50cd", // 常驻
"301": "182e725d99b742b14839117650d3e79628cc6221", //角色活动
"302": "8ff7a7d42bea79b0d54e92fdb58a20f971490372", // 武器活动
"400": "bb0486115a7e7c4bd2994135f7d212014b17173b", // 角色活动-2
"1000": "f3f5090a8ec0b28f15805c9969aa6c4ec357", // 千星奇域常驻
"20011": "a8d0a985efb4ed61eb2e73a86a57237bd116", // 千星奇域角色活动-男
"20021": "57016dec6b768231ba1342c01935417a799b", // 千星奇域角色活动-女
};
const tabNormal: ReadonlyArray<GroTab> = [
{ label: "常驻祈愿", value: "200" },
{ label: "角色活动祈愿", value: "301" },
{ label: "武器活动祈愿", value: "302" },
{ label: "角色活动祈愿-2", value: "400" },
];
const tabBeyond: ReadonlyArray<GroTab> = [
{ label: "常驻颂愿", value: "1000", beyond: true },
{ label: "活动颂愿-男", value: "20011", beyond: true },
{ label: "活动颂愿-女", value: "20021", beyond: true },
];
type GroTabKey = keyof typeof GachaIdMap;
type GroTab = { label: string; value: string; beyond?: boolean };
type GroIframeProps = { mode: "normal" | "beyond" };
const props = defineProps<GroIframeProps>();
const { cookie, account } = storeToRefs(useUserStore());
const authkey = ref<string>("");
const link = ref<string>("");
const poolTab = ref<GroTabKey>("200");
const tabList = shallowRef<ReadonlyArray<GroTab>>(props.mode === "beyond" ? tabBeyond : tabNormal);
onMounted(async () => {
link.value = await getUrl();
});
watch(
() => poolTab.value,
async () => {
link.value = await getUrl();
},
);
async function getUrl(): Promise<string> {
const path = "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v3/index.html";
const pathB = "https://webstatic.mihoyo.com/hk4e/event/e20250716gacha/index.html";
const pathF = poolTab.value.length < 4 ? path : pathB;
if (authkey.value === "") await refreshAuthkey();
const param: Record<string, string> = {
win_mode: "fullscreen",
no_joypad_close: "1",
authkey_ver: "1",
sign_type: "2",
auth_appid: "webview_gacha",
gacha_id: GachaIdMap[poolTab.value],
timestamp: Math.floor(Date.now() / 1000).toString(),
lang: "zh-cn",
device_type: "pc",
region: account.value.region,
authkey: authkey.value,
game_biz: account.value.gameBiz,
};
const targetLink = new URL(pathF);
for (const key in param) {
targetLink.searchParams.append(key, param[key]);
}
return targetLink.toString();
}
async function refreshAuthkey(): Promise<void> {
if (!cookie.value || !account.value) {
return;
}
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
if (typeof authkeyRes === "string") {
authkey.value = authkeyRes;
} else {
showSnackbar.error("获取authkey失败");
return;
}
}
</script>
<style lang="scss" scoped>
.gro-iframe-container {
display: flex;
width: 100%;
height: 100%;
align-items: flex-start;
justify-content: space-between;
}
.gro-ic-tabs {
height: 100%;
img {
width: 24px;
height: 24px;
margin-right: 4px;
}
}
.gro-ic-window {
height: 100%;
flex: 1;
}
</style>

View File

@@ -12,22 +12,22 @@ import { computed, ref, watch } from "vue";
import GroDataView from "./gro-data-view.vue";
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.SingleTable> };
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGacha> };
const props = defineProps<GachaOverviewProps>();
const newData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
const newData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
props.modelValue.filter((item) => item.uigfType === "100"),
);
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
props.modelValue.filter((item) => item.uigfType === "200"),
);
const avatarData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
const avatarData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
props.modelValue.filter((item) => item.uigfType === "301"),
);
const weaponData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
const weaponData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
props.modelValue.filter((item) => item.uigfType === "302"),
);
const mixData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
const mixData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
props.modelValue.filter((item) => item.uigfType === "500"),
);
@@ -56,7 +56,7 @@ watch(
.gro-o-container {
display: grid;
height: 100%;
grid-column-gap: 8px;
column-gap: 8px;
grid-template-columns: v-bind(cnCols); /* stylelint-disable-line value-keyword-case */
}
</style>

View File

@@ -19,7 +19,7 @@
</v-data-table>
</template>
<script lang="ts" setup>
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.SingleTable> };
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGacha> };
const props = defineProps<GroTableProps>();

View File

@@ -62,9 +62,11 @@ import { computed, onMounted, ref, shallowRef, watch } from "vue";
type UgoUidProps = { mode: "import" | "export" };
type UgoUidItem = { uid: string; length: number; time: string };
const fpEmptyText = "点击选择文件路径";
const props = defineProps<UgoUidProps>();
const visible = defineModel<boolean>();
const fp = ref<string>("未选择");
const fp = ref<string>(fpEmptyText);
const dataRaw = shallowRef<TGApp.Plugins.UIGF.Schema4>();
const data = shallowRef<Array<UgoUidItem>>([]);
const selectedData = shallowRef<Array<UgoUidItem>>([]);
@@ -91,7 +93,7 @@ async function refreshData(): Promise<void> {
data.value = [];
dataRaw.value = undefined;
if (props.mode === "import") {
fp.value = "未选择";
fp.value = fpEmptyText;
await handleImportData();
} else {
fp.value = await getDefaultSavePath();
@@ -118,7 +120,7 @@ async function selectFile(): Promise<void> {
}
async function handleImportData(): Promise<void> {
if (fp.value === "未选择") return;
if (fp.value === fpEmptyText) return;
try {
await showLoading.start("正在导入数据...", "正在验证数据...");
const check = await verifyUigfData(fp.value, true);
@@ -161,7 +163,7 @@ async function handleExportData(): Promise<void> {
data.value = tmpData;
}
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.SingleTable[]): UgoUidItem {
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.TableGacha[]): UgoUidItem {
const timeList = data.map((item) => new Date(item.time).getTime());
return {
uid: data[0].uid,
@@ -178,7 +180,7 @@ async function handleSelected(): Promise<void> {
async function handleImport(): Promise<void> {
if (!dataRaw.value) {
showSnackbar.error("未获取到数据!");
fp.value = "未选择";
fp.value = fpEmptyText;
return;
}
if (selectedData.value.length === 0) {
@@ -234,6 +236,7 @@ async function handleExport(): Promise<void> {
position: relative;
display: flex;
width: 100%;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
column-gap: 10px;
@@ -249,6 +252,7 @@ async function handleExport(): Promise<void> {
color: var(--tgc-od-white);
cursor: pointer;
font-size: 12px;
word-break: break-all;
}
.ugo-header {

View File

@@ -55,10 +55,7 @@ const showOverlay = ref<boolean>(false);
const localUrl = ref<string>();
const bgColor = ref<string>("transparent");
const oriUrl = computed<string>(() => {
if (typeof props.data.insert.image === "string") return props.data.insert.image;
return props.data.insert.image.url;
});
const oriUrl = ref<string>("");
const imgExt = computed<string>(() => getImageExt());
const showOri = ref<boolean>(imgExt.value === "gif" || imageQualityPercent.value === 100);
@@ -71,6 +68,7 @@ const imgWidth = computed<string>(() => {
console.log("tp-image", props.data.insert.image, props.data.attributes);
onMounted(async () => {
oriUrl.value = miniImgUrl();
const link = appStore.getImageUrl(oriUrl.value, imgExt.value);
localUrl.value = await saveImgLocal(link);
});
@@ -93,6 +91,17 @@ onUnmounted(() => {
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
});
function miniImgUrl(): string {
let url: string;
if (typeof props.data.insert.image === "string") {
url = props.data.insert.image;
} else {
url = props.data.insert.image.url;
}
const link = new URL(url);
return `${link.origin}${link.pathname}`;
}
function getImageTitle(): string {
const res: string[] = [];
if (props.data.attributes) {
@@ -120,11 +129,11 @@ function getImageTitle(): string {
function getImageExt(): string {
if (props.data.attributes && props.data.attributes.ext) return props.data.attributes.ext;
if (typeof props.data.insert.image === "string") {
const arr = props.data.insert.image.split(".");
return arr[arr.length - 1];
if (typeof props.data.insert.image !== "string") {
return props.data.insert.image.format;
}
return props.data.insert.image.format;
const arr = oriUrl.value.split(".");
return arr[arr.length - 1];
}
</script>
<style lang="scss" scoped>

View File

@@ -7,6 +7,8 @@
/>
</template>
<script lang="ts" setup>
import TpUgcLevel from "@comp/viewPost/tp-ugc-level.vue";
import TpUgcTag from "@comp/viewPost/tp-ugc-tag.vue";
import type { Component } from "vue";
import TpBackupText from "./tp-backupText.vue";
@@ -93,9 +95,11 @@ function getParsedText(data: TpTextType): Array<TpTextType> {
function getTpName(tp: TGApp.BBS.SctPost.Base): Component {
if (tp.children) return TpTexts;
if (typeof tp.insert === "undefined") return TpUnknown;
if (typeof tp.insert === "string") return TpText;
// game_user_info属于backup_text的一种必须放在backup_text判断的前面
// 判断特殊backup_text
if ("game_user_info" in tp.insert) return TpUid;
if ("ugc_master_tag" in tp.insert) return TpUgcTag;
if ("backup_text" in tp.insert) {
if (tp.insert.backup_text === "[游戏卡片]" && "reception_card" in tp.insert) return TpGameCard;
if (tp.insert.backup_text === "[自定义表情]" && "custom_emoticon" in tp.insert) {
@@ -105,6 +109,7 @@ function getTpName(tp: TGApp.BBS.SctPost.Base): Component {
}
if ("divider" in tp.insert) return TpDivider;
if ("image" in tp.insert) return TpImage;
if ("level" in tp.insert) return TpUgcLevel;
if ("link_card" in tp.insert) return TpLinkCard;
if ("mention" in tp.insert) return TpMention;
if ("video" in tp.insert) return TpVideo;

View File

@@ -0,0 +1,151 @@
<!-- UGC关卡组件 TODO:UI调整-->
<template>
<div class="tul-card-box" @click="console.log(props.data)">
<TMiImg
@click="toLevel()"
class="tul-cover"
:src="props.data.insert.level.cover.url"
alt="cover"
/>
<div class="tul-content">
<div class="tul-top">
<div class="tul-title">{{ props.data.insert.level.level_name }}</div>
<div class="tul-sub">
<div class="tul-info-item" title="游戏类型">
<v-icon size="16" color="yellow">mdi-gamepad-variant</v-icon>
<span>{{ props.data.insert.level.play_type }}</span>
</div>
<div class="tul-info-item" title="游玩人数">
<v-icon size="16" color="blue">mdi-account-multiple</v-icon>
<span>{{ props.data.insert.level.show_limit_play_num_str }}</span>
</div>
</div>
<div class="tul-attach" v-if="props.data.insert.level.level_attachment">
<span></span>
<span>{{ props.data.insert.level.level_attachment.content }}</span>
</div>
</div>
<div class="tul-bottom">
<div class="tul-info">
<div class="tul-info-item" title="热度">
<v-icon size="16" color="orange">mdi-fire</v-icon>
<span>{{ props.data.insert.level.hot_score }}</span>
</div>
<div class="tul-info-item" title="点赞率">
<v-icon size="16" color="pink">mdi-thumb-up</v-icon>
<span>{{ props.data.insert.level.good_rate }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import TMiImg from "@comp/app/t-mi-img.vue";
import TGClient from "@utils/TGClient.js";
type TpUgcLevel = { insert: { level: TGApp.BBS.UGC.Level } };
type TpUgcLevelProps = { data: TpUgcLevel };
const props = defineProps<TpUgcLevelProps>();
async function toLevel(): Promise<void> {
let url = `https://act.miyoushe.com/ys/ugc_community/mx/#/pages/level-detail/index?`;
url = `${url}id=${props.data.insert.level.level_id}&region=${props.data.insert.level.region}`;
await TGClient.open("web_act_thin", url.toString());
}
</script>
<style lang="scss" scoped>
.tul-card-box {
display: flex;
width: 100%;
box-sizing: border-box;
padding: 8px;
border: 1px solid var(--common-shadow-1);
border-radius: 4px;
margin-bottom: 8px;
background: var(--box-bg-1);
column-gap: 8px;
}
.tul-cover {
max-width: 50%;
max-height: 180px;
border-radius: 4px;
cursor: pointer;
transition: all 0.5s;
&:hover {
scale: 0.9;
}
}
.tul-content {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: var(--font-title);
}
.tul-top {
display: flex;
width: 100%;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.tul-title {
color: var(--tgc-od-red);
font-family: var(--font-title);
font-size: 20px;
}
.tul-sub {
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;
color: var(--tgc-od-white);
column-gap: 16px;
font-size: 16px;
}
.tul-attach {
margin-top: 8px;
color: var(--common-text-secondary);
font-size: 16px;
span {
color: var(--tgc-od-blue);
&:first-child {
font-family: Genshin, sans-serif;
font-size: 20px;
line-height: 14px;
vertical-align: top;
}
}
}
.tul-bottom {
display: flex;
width: 100%;
align-items: center;
justify-content: flex-end;
}
.tul-info {
display: flex;
gap: 16px;
}
.tul-info-item {
display: flex;
align-items: center;
font-size: 14px;
gap: 4px;
}
</style>

View File

@@ -0,0 +1,52 @@
<!-- ugc_master_tag backup_text -->
<template>
<span class="tut-box">
{{ tagName }}
</span>
</template>
<script lang="ts" setup>
import useAppStore from "@store/app.js";
import { str2Color } from "@utils/toolFunc.js";
import { storeToRefs } from "pinia";
import { computed } from "vue";
type TpUgcTag = {
insert: {
backup_text: string;
ugc_master_tag: {
id: number;
name: string;
is_available: boolean;
};
};
};
type TpUgcTagProps = { data: TpUgcTag };
const props = defineProps<TpUgcTagProps>();
const { theme } = storeToRefs(useAppStore());
const tagName = computed<string>(() => props.data.insert.ugc_master_tag.name);
const isDarkMode = computed<boolean>(() => theme.value === "dark");
const tagColor = computed<string>(() => tag2Color(tagName.value, isDarkMode.value));
const bgColor = computed<string>(() => `rgba(${tagColor.value.slice(4, -1)}, 0.18)`);
function tag2Color(str: string, isDarkMode: boolean = false): string {
const adjust = isDarkMode ? 80 : -40;
return str2Color(str, adjust);
}
</script>
<style lang="scss" scoped>
.tut-box {
display: inline-flex;
width: fit-content;
align-items: center;
justify-content: center;
padding: 0 6px;
border-radius: 12px;
margin-right: 4px;
background: v-bind(bgColor); /* stylelint-disable-line value-keyword-case */
color: v-bind(tagColor); /* stylelint-disable-line value-keyword-case */
font-family: var(--font-title);
font-size: 12px;
gap: 2px;
}
</style>

View File

@@ -23,6 +23,7 @@
import showLoading from "@comp/func/loading.js";
import useAppStore from "@store/app.js";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { openUrl } from "@tauri-apps/plugin-opener";
import { getImageBuffer, saveCanvasImg, saveImgLocal } from "@utils/TGShare.js";
import { getVideoDuration } from "@utils/toolFunc.js";
import Artplayer, { type Option } from "artplayer";
@@ -106,7 +107,7 @@ onMounted(async () => {
name: "download-cover",
index: 0,
position: "right",
html: `<i class="mdi mdi-download"></i>`,
html: `<span class="mdi mdi-image-check"></span>`,
tooltip: "下载封面",
click: async () => {
await showLoading.start("正在下载封面", props.data.insert.vod.cover);
@@ -117,6 +118,17 @@ onMounted(async () => {
await showLoading.end();
},
},
{
name: "download-video",
index: 0,
position: "right",
html: `<span class="mdi mdi-video-check"></span>`,
tooltip: "下载视频",
click: async () => {
if (!container.value) return;
await openUrl(container.value.url);
},
},
],
};
container.value = new Artplayer(option);

View File

@@ -73,10 +73,7 @@ const format = defineModel<string>("format", { default: "png" });
const bgMode = ref<number>(0); // 0: transparent, 1: black, 2: white
const isOriSize = ref<boolean>(false);
const buffer = shallowRef<Uint8Array | null>(null);
const oriLink = computed<string>(() => {
const image = props.image.insert.image;
return typeof image === "string" ? image : image.url;
});
const oriLink = computed<string>(() => miniImgUrl());
const showCopy = computed<boolean>(() => {
// 只能显示 png/jpg/jpeg/webp 格式的复制按钮
return ["png", "jpg", "jpeg", "webp"].includes(format.value.toLowerCase());
@@ -124,6 +121,17 @@ async function onDownload(): Promise<void> {
await saveCanvasImg(buffer.value, fileName, format.value);
await showLoading.end();
}
function miniImgUrl(): string {
let url: string;
if (typeof props.image.insert.image === "string") {
url = props.image.insert.image;
} else {
url = props.image.insert.image.url;
}
const link = new URL(url);
return `${link.origin}${link.pathname}`;
}
</script>
<style lang="css" scoped>
.tpoi-box {

View File

@@ -23,7 +23,8 @@
"Id": 1223,
"Name": "诳言掩虚实之迹",
"Description": "元素战技<color=#FFD780FF>{LINK#S11222}弈术·千夜一舞{/LINK}</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Nefer_01"
"Icon": "UI_Talent_U_Nefer_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 1224,
@@ -35,7 +36,8 @@
"Id": 1225,
"Name": "见机在忽微之间",
"Description": "元素爆发<color=#FFD780FF>{LINK#S11225}圣约·真眸幻戏{/LINK}</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Nefer_02"
"Icon": "UI_Talent_U_Nefer_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 1226,

View File

@@ -657,7 +657,7 @@
},
{
"id": 10000122,
"contentId": 0,
"contentId": 506676,
"dropDays": [2, 5, 7],
"name": "奈芙尔",
"itemType": "character",
@@ -2369,7 +2369,7 @@
},
{
"id": 14521,
"contentId": 0,
"contentId": 506913,
"dropDays": [2, 5, 7],
"name": "真语秘匣",
"itemType": "weapon",
@@ -4337,7 +4337,7 @@
},
{
"id": 14434,
"contentId": 0,
"contentId": 506911,
"dropDays": [1, 4, 7],
"name": "霜辰",
"itemType": "weapon",
@@ -4705,7 +4705,7 @@
},
{
"id": 13434,
"contentId": 0,
"contentId": 506912,
"dropDays": [2, 5, 7],
"name": "圣祭者的辉杖",
"itemType": "weapon",

View File

@@ -2990,5 +2990,41 @@
"postId": "69786055",
"up5List": [14521, 11513],
"up4List": [11434, 13434, 14434, 12402, 15401]
},
{
"name": "炉边烬影",
"version": "6.1",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/11/04/f1799709dd6e2c1d5807382d0bb46246_8044642901403314243_transformed.jpg",
"from": "2025-11-11T18:00:00+08:00",
"to": "2025-12-02T14:59:00+08:00",
"type": 301,
"postId": "70352225",
"up5List": [10000096],
"up4List": [10000108, 10000045, 10000064]
},
{
"name": "陵薮市朝",
"version": "6.1",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/11/04/146ff4ba52605431d0086cb5d18c1440_4528556376432618835_transformed.jpg",
"from": "2025-11-11T18:00:00+08:00",
"to": "2025-12-02T14:59:00+08:00",
"type": 400,
"postId": "70352227",
"up5List": [10000030],
"up4List": [10000108, 10000045, 10000064]
},
{
"name": "神铸赋形",
"version": "6.1",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/10/27/54cde6bd8912354aa5b358d42a69dd79_6997260099594855541_transformed.jpg",
"from": "2025-11-11T18:00:00+08:00",
"to": "2025-12-02T14:59:00+08:00",
"type": 302,
"postId": "70352228",
"up5List": [13512, 13504],
"up4List": [11427, 12427, 15427, 13401, 14403]
}
]

3327
src/data/app/gachaB.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
{ "id": 15503, "contentId": 1682, "name": "终末嗟叹之诗", "star": 5, "weapon": "弓" },
{ "id": 15502, "contentId": 219, "name": "阿莫斯之弓", "star": 5, "weapon": "弓" },
{ "id": 15501, "contentId": 323, "name": "天空之翼", "star": 5, "weapon": "弓" },
{ "id": 14521, "contentId": 0, "name": "真语秘匣", "star": 5, "weapon": "法器" },
{ "id": 14521, "contentId": 506913, "name": "真语秘匣", "star": 5, "weapon": "法器" },
{ "id": 14520, "contentId": 506390, "name": "纺夜天镜", "star": 5, "weapon": "法器" },
{ "id": 14519, "contentId": 504739, "name": "溢彩心念", "star": 5, "weapon": "法器" },
{ "id": 14518, "contentId": 504503, "name": "寝正月初晴", "star": 5, "weapon": "法器" },
@@ -88,7 +88,7 @@
{ "id": 15403, "contentId": 177, "name": "祭礼弓", "star": 4, "weapon": "弓" },
{ "id": 15402, "contentId": 176, "name": "绝弦", "star": 4, "weapon": "弓" },
{ "id": 15401, "contentId": 195, "name": "西风猎弓", "star": 4, "weapon": "弓" },
{ "id": 14434, "contentId": 0, "name": "霜辰", "star": 4, "weapon": "法器" },
{ "id": 14434, "contentId": 506911, "name": "霜辰", "star": 4, "weapon": "法器" },
{ "id": 14433, "contentId": 506375, "name": "乌髓孑灯", "star": 4, "weapon": "法器" },
{ "id": 14432, "contentId": 506378, "name": "天光的纺琴", "star": 4, "weapon": "法器" },
{ "id": 14431, "contentId": 501965, "name": "木棉之环", "star": 4, "weapon": "法器" },
@@ -113,7 +113,7 @@
{ "id": 14403, "contentId": 197, "name": "祭礼残章", "star": 4, "weapon": "法器" },
{ "id": 14402, "contentId": 192, "name": "流浪乐章", "star": 4, "weapon": "法器" },
{ "id": 14401, "contentId": 185, "name": "西风秘典", "star": 4, "weapon": "法器" },
{ "id": 13434, "contentId": 0, "name": "圣祭者的辉杖", "star": 4, "weapon": "长柄武器" },
{ "id": 13434, "contentId": 506912, "name": "圣祭者的辉杖", "star": 4, "weapon": "长柄武器" },
{ "id": 13433, "contentId": 506374, "name": "掘金之锹", "star": 4, "weapon": "长柄武器" },
{ "id": 13432, "contentId": 504502, "name": "且住亭御咄", "star": 4, "weapon": "长柄武器" },
{ "id": 13431, "contentId": 501966, "name": "虹的行迹", "star": 4, "weapon": "长柄武器" },

View File

@@ -1,7 +1,6 @@
/**
* @file src/data/index.ts
* @description 数据文件入口
* @since Beta v0.8.0
* 数据文件入口
* @since Beta v0.8.4
*/
import type { Schema } from "ajv";
@@ -11,6 +10,7 @@ import achievementSeries from "./app/achievementSeries.json" with { type: "json"
import calendar from "./app/calendar.json" with { type: "json" };
import character from "./app/character.json" with { type: "json" };
import gacha from "./app/gacha.json" with { type: "json" };
import gachaB from "./app/gachaB.json" with { type: "json" };
import nameCards from "./app/namecard.json" with { type: "json" };
import weapon from "./app/weapon.json" with { type: "json" };
import arcBirCalendar from "./archive/birth_calendar.json" with { type: "json" };
@@ -28,6 +28,7 @@ export const AppAchievementSeriesData: Array<TGApp.App.Achievement.Series> = ach
export const AppCalendarData: Array<TGApp.App.Calendar.Item> = calendar;
export const AppCharacterData: Array<TGApp.App.Character.WikiBriefInfo> = character;
export const AppGachaData: Array<TGApp.App.Gacha.PoolItem> = gacha;
export const AppGachaBData: Array<TGApp.App.Gacha.GachaBMeta> = gachaB;
export const AppNameCardsData: Array<TGApp.App.NameCard.Item> = nameCards;
export const AppWeaponData: Array<TGApp.App.Weapon.WikiBriefInfo> = weapon;
// Schema

View File

@@ -1,7 +1,7 @@
/**
* @file src/enum/anno.ts
* @description 游戏内公告相关枚举
* @since Beta v0.8.0
* @since Beta v0.8.4
*/
/**
@@ -37,17 +37,18 @@ export function getAnnoLangDesc(lang: TGApp.BBS.Announcement.AnnoLangEnum): stri
/**
* @description 公告类型
* @since Beta v0.7.7
* @since Beta v0.8.4
* @const AnnoTypeEnum
*/
export const AnnoTypeEnum: typeof TGApp.BBS.Announcement.AnnoType = {
ACTIVITY: "activity",
GAME: "game",
UGC: "ugc",
};
/**
* @description 获取公告类型描述
* @since Beta v0.7.7
* @since Beta v0.8.4
* @param {TGApp.BBS.Announcement.AnnoTypeEnum} type 公告类型
* @return {string} 公告类型描述
*/
@@ -57,5 +58,7 @@ export function getAnnoTypeDesc(type: TGApp.BBS.Announcement.AnnoTypeEnum): stri
return "活动公告";
case AnnoTypeEnum.GAME:
return "游戏公告";
case AnnoTypeEnum.UGC:
return "千星奇域";
}
}

View File

@@ -25,3 +25,15 @@ export const AvatarExtResTypeEnum: typeof TGApp.BBS.User.AvatarExtResType = {
GIF: 3,
PNG: 4,
};
/**
* @description 帖子ViewType
* @since Beta v0.8.4
* @enum PostViewTypeEnum
*/
export const PostViewTypeEnum: typeof TGApp.BBS.Post.PostViewType = {
NORMAL: 1,
PIC: 2,
VOD: 5,
UGC: 7,
};

View File

@@ -1,25 +1,29 @@
/**
* @file enum/game.ts
* @description 游戏相关枚举
* 游戏相关枚举
* @since Beta v0.8.0
*/
/**
* @description 服务器类型
* 服务器类型
* @since Beta v0.8.0
* @const GameServerEnum
*/
export const GameServerEnum: typeof TGApp.Game.Base.ServerType = {
/** 国服-官方服 */
CN_GF01: "cn_gf01",
/** 国服-渠道服 */
CN_QD01: "cn_qd01",
/** 国际服-美服 */
OS_USA: "os_usa",
/** 国际服-欧服 */
OS_EURO: "os_euro",
/** 国际服-亚服 */
OS_ASIA: "os_asia",
/** 国际服-港澳台服 */
OS_CHT: "os_cht",
};
/**
* @description 获取公告服务器描述
* 获取公告服务器描述
* @since Beta v0.8.0
* @param {TGApp.Game.Base.ServerTypeEnum} server 公告服务器
* @return {string} 公告服务器描述
@@ -42,15 +46,20 @@ export function getGameServerDesc(server: TGApp.Game.Base.ServerTypeEnum): strin
}
/**
* @description 近期活动活动类型枚举
* 近期活动活动类型枚举
* @since Beta v0.8.0
* @enum ActCalendarTypeEnum
*/
export const ActCalendarTypeEnum: typeof TGApp.Game.ActCalendar.ActType = {
/** 幽境危战 */
HardChallenge: "ActTypeHardChallenge",
/** 真境剧诗 */
RoleCombat: "ActTypeRoleCombat",
/** 深渊螺旋 */
Tower: "ActTypeTower",
/** 双倍活动 */
Double: "ActTypeDouble",
/** 探索活动 */
Explore: "ActTypeExplore",
/** 其他活动 */
Other: "ActTypeOther",
};

View File

@@ -1,3 +1,4 @@
<!-- 深境螺旋 -->
<template>
<v-app-bar>
<template #prepend>
@@ -20,19 +21,9 @@
<img src="/source/UI/userChallenge.webp" alt="challenge" />
<span>幽境危战</span>
</v-btn>
<v-btn :rounded="true" class="ua-btn" @click="toWiki()">
<img src="/source/UI/wikiAbyss.webp" alt="wiki" />
<span>深渊数据库</span>
</v-btn>
</div>
</template>
<template #append>
<div class="uat-hutao">
<span>胡桃云账号</span>
<span @click="editHutaoEmail()">{{ hutaoEmail ?? "未设置" }}</span>
</div>
</template>
<template #extension>
<div class="uat-acts">
<v-btn
class="ua-btn"
@@ -43,7 +34,7 @@
分享
</v-btn>
<v-btn class="ua-btn" @click="refreshAbyss()" prepend-icon="mdi-refresh">刷新</v-btn>
<v-btn class="ua-btn" @click="uploadAbyss()" prepend-icon="mdi-cloud-upload">上传</v-btn>
<v-btn class="ua-btn" @click="tryReadAbyss()" prepend-icon="mdi-download">导入</v-btn>
<v-btn class="ua-btn" @click="deleteAbyss()" prepend-icon="mdi-delete">删除</v-btn>
</div>
</template>
@@ -107,26 +98,25 @@ import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TuaDetail from "@comp/userAbyss/tua-detail.vue";
import TuaOverview from "@comp/userAbyss/tua-overview.vue";
import Hutao from "@Hutao/index.js";
import recordReq from "@req/recordReq.js";
import TSUserAbyss from "@Sqlm/userAbyss.js";
import TSUserAvatar from "@Sqlm/userAvatar.js";
import useUserStore from "@store/user.js";
import { getVersion } from "@tauri-apps/api/app";
import { open } from "@tauri-apps/plugin-dialog";
import { readTextFile } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
import { generateShareImg } from "@utils/TGShare.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const { account, cookie, hutaoEmail } = storeToRefs(useUserStore());
const { account, cookie } = storeToRefs(useUserStore());
const userTab = ref<number>(0);
const version = ref<string>();
const uidCur = ref<string>();
const uidList = shallowRef<Array<string>>();
const localAbyss = shallowRef<TGApp.Sqlite.Abyss.TableData[]>([]);
const abyssIdList = computed<Array<number>>(() => localAbyss.value.map((abyss) => abyss.id));
onMounted(async () => {
await showLoading.start("正在加载深渊数据");
@@ -148,35 +138,10 @@ watch(() => uidCur.value, loadAbyss);
async function toCombat(): Promise<void> {
await router.push({ name: "真境剧诗" });
}
async function toChallenge(): Promise<void> {
await router.push({ name: "幽境危战" });
}
async function toWiki(): Promise<void> {
await router.push({ name: "深渊数据库" });
}
async function editHutaoEmail(): Promise<void> {
if (hutaoEmail.value) {
const chgCheck = await showDialog.check("是否更改胡桃云账号", `当前账号:${hutaoEmail.value}`);
if (!chgCheck) {
showSnackbar.cancel("已取消更改胡桃云账号");
return;
}
}
const newEmail = await showDialog.input("请输入胡桃云账号", "胡桃云账号", hutaoEmail.value);
if (!newEmail) {
showSnackbar.cancel("已取消设置胡桃云账号");
return;
}
// 简单验证邮箱格式
const mailReg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
if (!mailReg.test(newEmail)) {
showSnackbar.error("邮箱格式错误");
return;
}
hutaoEmail.value = newEmail;
showSnackbar.success("已设置胡桃云账号");
}
async function loadAbyss(): Promise<void> {
localAbyss.value = [];
@@ -262,62 +227,6 @@ async function shareAbyss(): Promise<void> {
await TGLogger.Info(`[UserAbyss][shareAbyss][${userTab.value}] 生成深渊数据分享图片成功`);
}
async function uploadAbyss(): Promise<void> {
await TGLogger.Info("[UserAbyss][uploadAbyss] 上传深渊数据");
const abyssData = localAbyss.value.find((item) => item.id === Math.max(...abyssIdList.value));
if (!abyssData) {
showSnackbar.warn("未找到深渊数据");
await TGLogger.Warn("[UserAbyss][uploadAbyss] 未找到深渊数据");
return;
}
const maxFloor = Number(abyssData.maxFloor.split("-")[0]);
if (isNaN(maxFloor) || maxFloor <= 9) {
showSnackbar.warn("尚未完成深渊,请完成深渊后重试!");
await TGLogger.Warn(`[UserAbyss][uploadAbyss] 尚未完成深渊 ${abyssData.maxFloor}`);
return;
}
const startTime = new Date(abyssData.startTime).getTime();
const endTime = new Date(abyssData.endTime).getTime();
const nowTime = new Date().getTime();
if (nowTime < startTime || nowTime > endTime) {
showSnackbar.warn("非最新深渊数据,请刷新深渊数据后重试!");
await TGLogger.Warn("[UserAbyss][uploadAbyss] 非最新深渊数据");
return;
}
try {
await showLoading.start(`正在上传${account.value.gameUid}的深渊数据`, `期数:${abyssData.id}`);
const transAbyss = Hutao.Abyss.utils.transData(abyssData);
if (hutaoEmail.value) transAbyss.ReservedUserName = hutaoEmail.value;
await showLoading.update("正在获取角色数据");
const roles = await TSUserAvatar.getAvatars(Number(account.value.gameUid));
if (!roles) {
await showLoading.end();
showSnackbar.warn("未找到角色数据");
return;
}
await showLoading.update("正在转换角色数据");
transAbyss.Avatars = Hutao.Abyss.utils.transAvatars(roles);
await showLoading.update("正在上传深渊数据");
const res = await Hutao.Abyss.upload(transAbyss);
if (res.retcode !== 0) {
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error("[UserAbyss][uploadAbyss] 上传深渊数据失败");
await TGLogger.Error(`[UserAbyss][uploadAbyss] ${res.retcode} ${res.message}`);
return;
}
showSnackbar.success(res.message ?? "上传深渊数据成功");
await TGLogger.Info("[UserAbyss][uploadAbyss] 上传深渊数据成功");
await TGLogger.Info(`[${res.retcode}] ${res.message}`);
} catch (e) {
if (e instanceof Error) {
showSnackbar.error(e.message);
await TGLogger.Error("[UserAbyss][uploadAbyss] 上传深渊数据失败");
await TGLogger.Error(`[UserAbyss][uploadAbyss] ${e.message}`);
}
}
await showLoading.end();
}
async function deleteAbyss(): Promise<void> {
if (uidCur.value === undefined || uidCur.value === "") {
showSnackbar.warn("未选择游戏UID");
@@ -337,6 +246,47 @@ async function deleteAbyss(): Promise<void> {
else uidCur.value = undefined;
await loadAbyss();
}
/**
* 尝试读取胡桃工具箱导出的深渊数据
* @since Beta v0.8.6
* @return {Promise<void>}
*/
async function tryReadAbyss(): Promise<void> {
const file = await open({
multiple: false,
title: "选择胡桃工具箱导出的深渊数据文件",
filters: [{ name: "JSON 文件", extensions: ["json"] }],
directory: false,
});
if (file === null) {
showSnackbar.cancel("已取消文件选择");
return;
}
try {
await showLoading.start("正在导入深渊数据文件", file);
const fileData = JSON.parse(await readTextFile(file));
if (!Array.isArray(fileData)) {
await showLoading.end();
showSnackbar.warn("文件数据格式错误");
return;
}
// TODO:数据结构
for (const item of fileData) {
await showLoading.update(`Uid: ${item["uid"]},ScheduleId: ${item["schedule_id"]}`);
await TSUserAbyss.saveAbyss(item["uid"], item["data"]);
}
await showLoading.end();
showSnackbar.success(`成功导入 ${fileData.length} 条深渊数据,即将刷新页面`);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
window.location.reload();
} catch (e) {
console.error(e);
await TGLogger.Error(`[UserAbyss][tryReadAbyss] 导入深渊数据失败: ${e}`);
await showLoading.end();
showSnackbar.error("导入深渊数据失败,请检查文件格式是否正确");
}
}
</script>
<style lang="css" scoped>
.uat-left {
@@ -361,21 +311,6 @@ async function deleteAbyss(): Promise<void> {
}
}
.uat-hutao {
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
font-family: var(--font-text);
font-size: 16px;
:last-child {
color: var(--tgc-pink-1);
cursor: pointer;
font-weight: bold;
}
}
.uat-acts {
display: flex;
align-items: center;
@@ -404,7 +339,7 @@ async function deleteAbyss(): Promise<void> {
.ua-box {
display: flex;
height: calc(100vh - 144px);
height: calc(100vh - 96px);
align-items: flex-start;
justify-content: center;
border: 1px solid var(--common-shadow-2);
@@ -470,7 +405,7 @@ async function deleteAbyss(): Promise<void> {
.uaw-o-box {
display: grid;
width: 100%;
grid-gap: 8px;
gap: 8px;
grid-template-columns: repeat(3, 1fr);
}

View File

@@ -50,6 +50,9 @@
分享
</v-btn>
<v-btn class="ucp-btn" @click="refreshChallenge()" prepend-icon="mdi-refresh">刷新</v-btn>
<v-btn class="ucp-btn" @click="tryReadChallenge()" prepend-icon="mdi-download">
导入
</v-btn>
<v-btn class="ucp-btn" @click="deleteChallenge()" prepend-icon="mdi-delete">删除</v-btn>
</div>
<div class="pop-list">
@@ -123,6 +126,8 @@ import recordReq from "@req/recordReq.js";
import TSUserChallenge from "@Sqlm/userChallenge.js";
import useUserStore from "@store/user.js";
import { getVersion } from "@tauri-apps/api/app";
import { open } from "@tauri-apps/plugin-dialog";
import { readTextFile } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
import { generateShareImg } from "@utils/TGShare.js";
import { storeToRefs } from "pinia";
@@ -337,6 +342,47 @@ async function refreshPopList(hint: boolean = true): Promise<void> {
`已刷新 ${getGameServerDesc(server.value)}${popList.value.length} 位赋光之人`,
);
}
/**
* 尝试读取胡桃工具箱导出的危战数据
* @since Beta v0.8.6
* @return {Promise<void>}
*/
async function tryReadChallenge(): Promise<void> {
const file = await open({
multiple: false,
title: "选择胡桃工具箱导出的危战数据文件",
filters: [{ name: "JSON 文件", extensions: ["json"] }],
directory: false,
});
if (file === null) {
showSnackbar.cancel("已取消文件选择");
return;
}
try {
await showLoading.start("正在导入危战数据文件", file);
const fileData = JSON.parse(await readTextFile(file));
if (!Array.isArray(fileData)) {
await showLoading.end();
showSnackbar.warn("文件数据格式错误");
return;
}
// TODO:数据结构
for (const item of fileData) {
await showLoading.update(`Uid: ${item["uid"]},ScheduleId: ${item["schedule_id"]}`);
await TSUserChallenge.saveChallenge(item["uid"], item["data"]);
}
await showLoading.end();
showSnackbar.success(`成功导入 ${fileData.length} 条危战数据,即将刷新页面`);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
window.location.reload();
} catch (e) {
console.error(e);
await TGLogger.Error(`[UserChallenge][tryReadChallenge] 导入危战数据失败: ${e}`);
await showLoading.end();
showSnackbar.error("导入危战数据失败,请检查文件格式是否正确");
}
}
</script>
<style lang="scss" scoped>
.ucp-top-prepend {

View File

@@ -21,10 +21,6 @@
<img src="/source/UI/userChallenge.webp" alt="challenge" />
<span>幽境危战</span>
</v-btn>
<v-btn :rounded="true" class="uc-btn" @click="loadWiki()">
<img src="/source/UI/wikiAbyss.webp" alt="abyss" />
<span>统计数据</span>
</v-btn>
</div>
</template>
<template #append>
@@ -41,13 +37,8 @@
<v-btn class="uc-btn" @click="refreshCombat()" :rounded="true" prepend-icon="mdi-refresh">
刷新
</v-btn>
<v-btn
class="uc-btn"
@click="uploadCombat()"
:rounded="true"
prepend-icon="mdi-cloud-upload"
>
上传
<v-btn class="uc-btn" @click="tryReadCombat()" :rounded="true" prepend-icon="mdi-download">
导入
</v-btn>
<v-btn class="uc-btn" @click="deleteCombat()" :rounded="true" prepend-icon="mdi-delete">
删除
@@ -93,7 +84,6 @@
<span>暂无数据请尝试刷新</span>
</div>
</div>
<TucOverlay v-model="showData" :data="cloudCombat" />
</template>
<script lang="ts" setup>
import TSubLine from "@comp/app/t-subline.vue";
@@ -101,30 +91,27 @@ import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TucAvatars from "@comp/userCombat/tuc-avatars.vue";
import TucOverlay from "@comp/userCombat/tuc-overlay.vue";
import TucOverview from "@comp/userCombat/tuc-overview.vue";
import TucRound from "@comp/userCombat/tuc-round.vue";
import Hutao from "@Hutao/index.js";
import recordReq from "@req/recordReq.js";
import TSUserCombat from "@Sqlm/userCombat.js";
import useUserStore from "@store/user.js";
import { getVersion } from "@tauri-apps/api/app";
import { open } from "@tauri-apps/plugin-dialog";
import { readTextFile } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
import { generateShareImg } from "@utils/TGShare.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const { account, cookie } = storeToRefs(useUserStore());
const userTab = ref<number>(0);
const showData = ref<boolean>(false);
const version = ref<string>();
const uidCur = ref<string>();
const uidList = shallowRef<Array<string>>();
const localCombat = shallowRef<Array<TGApp.Sqlite.Combat.SingleTable>>([]);
const cloudCombat = shallowRef<TGApp.Plugins.Hutao.Combat.Data>();
const combatIdList = computed<Array<number>>(() => localCombat.value.map((combat) => combat.id));
onMounted(async () => {
await showLoading.start("正在加载剧诗数据");
@@ -160,16 +147,6 @@ async function loadCombat(): Promise<void> {
if (localCombat.value.length > 0) userTab.value = localCombat.value[0].id;
}
async function loadWiki(): Promise<void> {
await showLoading.start("正在加载统计数据");
const res = await Hutao.Combat.data();
if (res === undefined) showSnackbar.error("未获取到剧诗数据");
else cloudCombat.value = <TGApp.Plugins.Hutao.Combat.Data>res;
await showLoading.end();
showSnackbar.success("成功获取统计数据");
showData.value = true;
}
async function refreshCombat(): Promise<void> {
if (!cookie.value) {
showSnackbar.error("未登录");
@@ -238,49 +215,6 @@ async function shareCombat(): Promise<void> {
await TGLogger.Info(`[UserCombat][shareCombat][${userTab.value}] 生成剧诗数据分享图片成功`);
}
async function uploadCombat(): Promise<void> {
await TGLogger.Info("[UserCombat][uploadCombat] 上传剧诗数据");
const combatData = localCombat.value.find((item) => item.id === Math.max(...combatIdList.value));
if (!combatData) {
showSnackbar.error("未找到剧诗数据");
await TGLogger.Warn("[UserCombat][uploadCombat] 未找到深渊数据");
return;
}
if (!combatData.hasDetailData) {
showSnackbar.error("未获取到详情数据");
await TGLogger.Warn(`[UserCombat][uploadCombat] 未获取到详细数据`);
return;
}
const startTime = new Date(combatData.startTime).getTime();
const endTime = new Date(combatData.endTime).getTime();
const nowTime = new Date().getTime();
if (nowTime < startTime || nowTime > endTime) {
showSnackbar.warn("非最新剧诗数据,请刷新剧诗数据后重试!");
await TGLogger.Warn("[UserCombat][uploadCombat] 非最新剧诗数据");
return;
}
try {
await showLoading.start("正在上传剧诗数据");
const transCombat = Hutao.Combat.trans(combatData);
const res = await Hutao.Combat.upload(transCombat);
if (res.retcode === 0) {
showSnackbar.success(res.message ?? "上传剧诗数据成功");
await TGLogger.Info("[UserCombat][uploadCombat] 上传剧诗数据成功");
} else {
showSnackbar.error(`[${res.retcode}]${res.message}`);
await TGLogger.Error("[UserCombat][uploadCombat] 上传剧诗数据失败");
await TGLogger.Error(`[UserCombat][uploadCombat] ${res.retcode} ${res.message}`);
}
} catch (e) {
if (e instanceof Error) {
showSnackbar.error(e.message);
await TGLogger.Error("[UserCombat][uploadCombat] 上传剧诗数据失败");
await TGLogger.Error(`[UserCombat][uploadCombat] ${e.message}`);
}
}
await showLoading.end();
}
async function deleteCombat(): Promise<void> {
if (uidCur.value === undefined || uidCur.value === "") {
showSnackbar.error("未找到符合条件的数据!");
@@ -301,6 +235,47 @@ async function deleteCombat(): Promise<void> {
await loadCombat();
await showLoading.end();
}
/**
* 尝试读取胡桃工具箱导出的剧诗数据
* @since Beta v0.8.6
* @returns {Promise<void>}
*/
async function tryReadCombat(): Promise<void> {
const file = await open({
multiple: false,
title: "选择胡桃工具箱导出的剧诗数据文件",
filters: [{ name: "JSON 文件", extensions: ["json"] }],
directory: false,
});
if (file === null) {
showSnackbar.cancel("已取消文件选择");
return;
}
try {
await showLoading.start("正在导入剧诗数据文件", file);
const fileData = JSON.parse(await readTextFile(file));
if (!Array.isArray(fileData)) {
await showLoading.end();
showSnackbar.warn("文件数据格式错误");
return;
}
// TODO:数据结构
for (const item of fileData) {
await showLoading.update(`Uid: ${item["uid"]},ScheduleId: ${item["schedule_id"]}`);
await TSUserCombat.saveCombat(item["uid"], item["data"]);
}
await showLoading.end();
showSnackbar.success(`成功导入 ${fileData.length} 条剧诗数据,即将刷新页面`);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
window.location.reload();
} catch (e) {
console.error(e);
await TGLogger.Error(`[UserCombat][tryReadCombat] 导入剧诗数据失败: ${e}`);
await showLoading.end();
showSnackbar.error("导入剧诗数据失败,请检查文件格式是否正确");
}
}
</script>
<style lang="scss" scoped>
.uct-left {

View File

@@ -1,3 +1,4 @@
<!-- 祈愿记录页面 -->
<template>
<v-app-bar>
<template #prepend>
@@ -12,6 +13,7 @@
variant="outlined"
label="游戏UID"
/>
<img src="/icon/nation/千星奇域.webp" alt="byd" @click="toBeyond()" title="千星奇域" />
</div>
</template>
<template #extension>
@@ -40,6 +42,7 @@
<v-tab value="echarts">图表概览</v-tab>
<v-tab value="table">数据表格</v-tab>
<v-tab value="history">过往祈愿</v-tab>
<v-tab value="iframe" v-if="isLogin">祈愿详情</v-tab>
</v-tabs>
<v-window v-model="tab" class="gacha-window">
<v-window-item value="overview" class="gacha-window-item">
@@ -54,6 +57,9 @@
<v-window-item value="history" class="gacha-window-item">
<gro-history />
</v-window-item>
<v-window-item value="iframe" class="gacha-window-item">
<gro-iframe mode="normal" />
</v-window-item>
</v-window>
</div>
<UgoUid v-model="ovShow" :mode="ovMode" />
@@ -64,12 +70,14 @@ import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import GroEcharts from "@comp/userGacha/gro-echarts.vue";
import GroHistory from "@comp/userGacha/gro-history.vue";
import GroIframe from "@comp/userGacha/gro-iframe.vue";
import GroOverview from "@comp/userGacha/gro-overview.vue";
import GroTable from "@comp/userGacha/gro-table.vue";
import UgoUid from "@comp/userGacha/ugo-uid.vue";
import hk4eReq from "@req/hk4eReq.js";
import takumiReq from "@req/takumiReq.js";
import TSUserGacha from "@Sqlm/userGacha.js";
import useAppStore from "@store/app.js";
import useUserStore from "@store/user.js";
import { path } from "@tauri-apps/api";
import { open, save } from "@tauri-apps/plugin-dialog";
@@ -77,17 +85,22 @@ import TGLogger from "@utils/TGLogger.js";
import { exportUigfData, readUigfData, verifyUigfData } from "@utils/UIGF.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
import { AppCharacterData, AppWeaponData } from "@/data/index.js";
const router = useRouter();
const { isLogin } = storeToRefs(useAppStore());
const { account, cookie } = storeToRefs(useUserStore());
const authkey = ref<string>("");
const uidCur = ref<string>();
const tab = ref<string>("overview");
const ovShow = ref<boolean>(false);
const ovMode = ref<"export" | "import">("import");
const selectItem = shallowRef<Array<string>>([]);
const gachaListCur = shallowRef<Array<TGApp.Sqlite.GachaRecords.SingleTable>>([]);
const gachaListCur = shallowRef<Array<TGApp.Sqlite.GachaRecords.TableGacha>>([]);
onMounted(async () => {
await showLoading.start("正在加载祈愿数据", "正在获取祈愿 UID 列表");
@@ -122,6 +135,10 @@ watch(
},
);
async function toBeyond(): Promise<void> {
await router.push({ name: "千星奇域祈愿记录" });
}
// 刷新按钮点击事件
async function confirmRefresh(force: boolean): Promise<void> {
await TGLogger.Info(`[UserGacha][${account.value.gameUid}][confirmRefresh] 刷新祈愿数据`);
@@ -376,6 +393,10 @@ async function deleteGacha(): Promise<void> {
img {
width: 32px;
height: 32px;
&:last-child {
cursor: pointer;
}
}
span {
@@ -416,6 +437,12 @@ async function deleteGacha(): Promise<void> {
height: 40px;
color: var(--box-text-4);
font-family: var(--font-title);
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
}
.gacha-window {

341
src/pages/User/GachaB.vue Normal file
View File

@@ -0,0 +1,341 @@
<!-- 千星奇域祈愿记录页面 -->
<template>
<v-app-bar>
<template #prepend>
<div class="gb-top-title">
<img src="/icon/nation/千星奇域.webp" alt="gacha" />
<span>祈愿记录</span>
<v-select
:hide-details="true"
density="compact"
v-model="uidCur"
:items="selectItem"
variant="outlined"
label="游戏UID"
/>
<img src="/source/UI/userGacha.webp" alt="byd" @click="toGacha()" title="祈愿" />
</div>
</template>
<template #extension>
<div class="gb-top-btns">
<v-btn prepend-icon="mdi-refresh" class="gb-top-btn" @click="confirmRefresh(false)">
增量刷新
</v-btn>
<v-btn prepend-icon="mdi-refresh" class="gb-top-btn" @click="confirmRefresh(true)">
全量刷新
</v-btn>
<v-btn prepend-icon="mdi-delete" class="gb-top-btn" @click="deleteGacha()">删除</v-btn>
</div>
</template>
</v-app-bar>
<div class="gb-container">
<v-tabs v-model="tab" align-tabs="start" class="gb-tab" density="compact">
<v-tab value="overview">数据概览</v-tab>
<v-tab value="table">数据表格</v-tab>
<v-tab value="iframe" v-if="isLogin">祈愿详情</v-tab>
</v-tabs>
<v-window v-model="tab" class="gb-window">
<v-window-item value="overview" class="gb-window-item">
<gbr-overview v-model="gachaListCur" />
</v-window-item>
<v-window-item value="table" class="gb-window-item">
<gbr-table v-model="gachaListCur" />
</v-window-item>
<v-window-item value="iframe" class="gb-window-item">
<gro-iframe mode="beyond" />
</v-window-item>
</v-window>
</div>
</template>
<script lang="ts" setup>
import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import GbrOverview from "@comp/userGacha/gbr-overview.vue";
import GbrTable from "@comp/userGacha/gbr-table.vue";
import GroIframe from "@comp/userGacha/gro-iframe.vue";
import hk4eReq from "@req/hk4eReq.js";
import takumiReq from "@req/takumiReq.js";
import TSUserGachaB from "@Sqlm/userGachaB.js";
import useAppStore from "@store/app.js";
import useUserStore from "@store/user.js";
import TGLogger from "@utils/TGLogger.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef, watch } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const { isLogin } = storeToRefs(useAppStore());
const { account, cookie } = storeToRefs(useUserStore());
const authkey = ref<string>("");
const uidCur = ref<string>();
const tab = ref<string>("overview");
const selectItem = shallowRef<Array<string>>([]);
const gachaListCur = shallowRef<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>([]);
onMounted(async () => {
await TGLogger.Info("[UserGachaB][onMounted] 进入千星奇域页面");
await showLoading.start("正在加载千星奇域祈愿数据", "正在获取UID列表...");
selectItem.value = await TSUserGachaB.getUidList();
await TGLogger.Info(`[UserGachaB][onMounted] 总UID数${selectItem.value.length}`);
if (selectItem.value.length === 0) {
await showLoading.end();
await TGLogger.Info("[UserGachaB][onMounted] UID列表为空");
return;
}
uidCur.value = selectItem.value[0];
await TGLogger.Info(`[UserGachaB][onMounted] 当前UID:${uidCur.value}`);
await showLoading.update(`UID: ${uidCur.value}`);
gachaListCur.value = await TSUserGachaB.getGachaRecords(uidCur.value);
await TGLogger.Info(`[UserGachaB][onMounted] 祈愿记录数: ${gachaListCur.value.length}`);
await showLoading.end();
showSnackbar.success(`加载完成,共 ${gachaListCur.value.length} 条祈愿记录`);
});
watch(
() => uidCur.value,
async (newUid) => {
if (!newUid) return;
gachaListCur.value = await TSUserGachaB.getGachaRecords(newUid);
showSnackbar.success(`成功获取 ${gachaListCur.value.length} 条祈愿数据`);
await TGLogger.Info(
`[UserGachaB][${newUid}][watch] 成功获取 ${gachaListCur.value.length} 条祈愿数据`,
);
},
);
/**
* 跳转至祈愿页面
*/
async function toGacha(): Promise<void> {
await router.push({ name: "祈愿记录" });
}
/**
* 刷新祈愿数据
* @param {boolean} force 是否强制刷新
* @return {Promise<void>} void
*/
async function confirmRefresh(force: boolean): Promise<void> {
if (!isLogin.value || !cookie.value) {
showSnackbar.warn("请先登录账号");
return;
}
await TGLogger.Info(`[UserGachaB][${account.value.gameUid}] 开始刷新千星奇域祈愿数据`);
if (uidCur.value && uidCur.value !== account.value.gameUid) {
const switchCheck = await showDialog.check(
"是否切换游戏账户",
`确认则尝试切换至 ${uidCur.value}`,
);
if (switchCheck) {
await useUserStore().switchGameAccount(uidCur.value);
await confirmRefresh(force);
return;
}
const freshCheck = await showDialog.check(
"确定刷新?",
`用户${account.value.gameUid}与当前UID${uidCur.value}不一致`,
);
if (!freshCheck) {
showSnackbar.cancel("已取消祈愿数据刷新");
return;
}
}
await showLoading.start(`正在刷新祈愿数据`, `UID:${account.value.gameUid},正在获取 authkey`);
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
if (typeof authkeyRes === "string") {
authkey.value = authkeyRes;
await TGLogger.Info(`[UserGacha][${account.value.gameUid}] 成功获取 authkey`);
} else {
showSnackbar.error("获取 authkey 失败");
await TGLogger.Error(`[UserGacha][${account.value.gameUid}] 获取 authkey 失败`);
await TGLogger.Error(
`[UserGachaB][${account.value.gameUid}] ${authkeyRes.retcode} ${authkeyRes.message}`,
);
await showLoading.end();
return;
}
await refreshGachaPool("1000", "常驻颂愿", force);
await refreshGachaPool("20011", "活动颂愿·男", force);
await refreshGachaPool("20012", "活动颂愿·男2", force);
await refreshGachaPool("20021", "活动颂愿·女", force);
await refreshGachaPool("20022", "活动颂愿·女2", force);
await showLoading.end();
await TGLogger.Info(`[UserGacha][${account.value.gameUid}] 刷新祈愿数据完成`);
showSnackbar.success("祈愿数据刷新完成,即将刷新页面");
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
window.location.reload();
}
/**
* 刷新指定祈愿池数据
* @param {string} gachaType 祈愿池类型
* @param {string} gachaName 祈愿池名称
* @param {boolean} force 是否强制刷新
* @return {Promise<void>} void
*/
async function refreshGachaPool(
gachaType: string,
gachaName: string,
force: boolean,
): Promise<void> {
let endId = "0";
let reqId = "0";
let page = 0;
await showLoading.start(`正在刷新${gachaName}数据`, "");
if (!force) {
endId = (await TSUserGachaB.getGachaCheck(account.value.gameUid, gachaType)) ?? "0";
}
while (true) {
page++;
const gachaRes = await hk4eReq.gachaB(authkey.value, gachaType, reqId);
if (!Array.isArray(gachaRes)) {
showSnackbar.error(`[${gachaType}][${gachaRes.retcode}] ${gachaRes.message}`);
await TGLogger.Error(
`[UserGachaB][${account.value.gameUid}][refreshGachaPool] 获取祈愿数据失败`,
);
await TGLogger.Error(
`[UserGachaB][${account.value.gameUid}][refreshGachaPool] ${gachaRes.retcode} ${gachaRes.message}`,
);
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
break;
}
if (gachaRes.length === 0) {
// if (force) {
// await showLoading.update(`正在清理${label}数据`);
// if (gachaDataMap) {
// await TSUserGacha.cleanGachaRecords(account.value.gameUid, type, gachaDataMap);
// }
// }
break;
}
if (force) await showLoading.update(`[${gachaName}] 第${page}页,${gachaRes.length}`);
for (const item of gachaRes) {
if (!force) {
await showLoading.update(`[${item.item_type}][${item.time}] ${item.item_name}`);
}
if (force) {
// if (!gachaDataMap) gachaDataMap = {};
// if (!gachaDataMap[item.time]) gachaDataMap[item.time] = [];
// gachaDataMap[item.time].push(item.id.toString());
}
}
await TSUserGachaB.insertGachaList(gachaRes);
if (!force && gachaRes.some((i) => i.id.toString() === endId.toString())) break;
reqId = gachaRes[gachaRes.length - 1].id.toString();
if (force) await new Promise<void>((resolve) => setTimeout(resolve, 1000));
}
}
/**
* 删除当前UID的祈愿数据
* @return {Promise<void>} void
*/
async function deleteGacha(): Promise<void> {
if (gachaListCur.value.length === 0 || !uidCur.value) {
showSnackbar.error("暂无祈愿数据");
return;
}
await TGLogger.Info(`[UserGachaB][${uidCur.value}][deleteGacha] 删除祈愿数据`);
const delCheck = await showDialog.check(
"确定删除祈愿数据?",
`UID${uidCur.value},共 ${gachaListCur.value.length} 条数据`,
);
if (!delCheck) {
showSnackbar.cancel("已取消祈愿数据删除");
await TGLogger.Info(`[UserGachaB][${uidCur.value}][deleteGacha] 已取消祈愿数据删除`);
return;
}
const uidList = await TSUserGachaB.getUidList();
if (uidList.length <= 1) {
const forceCheck = await showDialog.check("删除后数据库将为空,确定删除?");
if (!forceCheck) {
showSnackbar.cancel("已取消祈愿数据删除");
return;
}
}
await showLoading.start("正在删除祈愿数据", `UID:${uidCur.value}`);
await TSUserGachaB.deleteRecords(uidCur.value);
await showLoading.end();
showSnackbar.success(`已成功删除 ${uidCur.value} 的祈愿数据,即将刷新页面`);
await TGLogger.Info(
`[UserGachaB][${uidCur.value}][deleteGacha] 成功删除 ${gachaListCur.value.length} 条祈愿数据`,
);
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
window.location.reload();
}
</script>
<style lang="scss" scoped>
.gb-top-title {
display: flex;
align-items: center;
justify-content: center;
margin-left: 12px;
column-gap: 8px;
img {
width: 32px;
height: 32px;
&:last-child {
cursor: pointer;
}
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
}
.gb-top-btns {
display: flex;
margin-left: 16px;
column-gap: 8px;
}
.gb-top-btn {
border-radius: 4px;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
.dark .gb-top-btn {
border: 1px solid var(--common-shadow-2);
}
.gb-container {
display: flex;
height: calc(100vh - 144px);
flex-direction: column;
align-items: flex-start;
justify-content: center;
border: 1px solid var(--common-shadow-2);
border-radius: 4px;
background: var(--app-page-bg);
}
.gb-tab {
height: 40px;
color: var(--box-text-4);
font-family: var(--font-title);
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
}
.gb-window {
width: 100%;
height: 100%;
padding: 8px;
}
.gb-window-item {
height: 100%;
}
</style>

View File

@@ -32,9 +32,14 @@
</div>
</template>
<template #append>
<v-btn class="anno-switch-btn" @click="switchNews" prepend-icon="mdi-bullhorn">
切换米游社咨讯
</v-btn>
<div class="anno-top-append">
<v-btn class="anno-switch-btn" @click="switchNews" prepend-icon="mdi-bullhorn">
切换米游社咨讯
</v-btn>
<v-btn class="anno-switch-btn" v-if="isLogin" @click="showIframe()">
<v-icon>mdi-web</v-icon>
</v-btn>
</div>
</template>
</v-app-bar>
<v-window v-model="tab">
@@ -44,11 +49,13 @@
</div>
</v-window-item>
</v-window>
<TaoIframe v-if="isLogin" v-model="iframeVisible" />
</template>
<script lang="ts" setup>
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import TaCard from "@comp/pageAnno/ta-card.vue";
import TaoIframe from "@comp/pageAnno/tao-iframe.vue";
import { AnnoLangEnum, AnnoTypeEnum, getAnnoLangDesc, getAnnoTypeDesc } from "@enum/anno.js";
import { getGameServerDesc, GameServerEnum } from "@enum/game.js";
import hk4eReq from "@req/hk4eReq.js";
@@ -89,14 +96,16 @@ const langList: ReadonlyArray<AnnoSelect<TGApp.BBS.Announcement.AnnoLangEnum>> =
const tabList: ReadonlyArray<AnnoSelect<TGApp.BBS.Announcement.AnnoTypeEnum>> = [
AnnoTypeEnum.ACTIVITY,
AnnoTypeEnum.GAME,
AnnoTypeEnum.UGC,
].map((i) => ({ text: getAnnoTypeDesc(i), value: i }));
const { server, lang } = storeToRefs(useAppStore());
const { server, lang, isLogin } = storeToRefs(useAppStore());
const router = useRouter();
const tab = ref<TGApp.BBS.Announcement.AnnoTypeEnum>(AnnoTypeEnum.ACTIVITY);
const annoCards = shallowRef<AnnoList>({ activity: [], game: [] });
const annoCards = shallowRef<AnnoList>({ activity: [], game: [], ugc: [] });
const isReq = ref<boolean>(false);
const iframeVisible = ref<boolean>(false);
watch(
() => server.value,
@@ -123,6 +132,10 @@ onMounted(async () => {
await loadData();
});
function showIframe(): void {
iframeVisible.value = true;
}
async function loadData(): Promise<void> {
if (isReq.value) return;
isReq.value = true;
@@ -134,6 +147,7 @@ async function loadData(): Promise<void> {
const detailResp = await hk4eReq.anno.detail(server.value, AnnoLangEnum.CHS);
const actCards: Array<AnnoCard> = [];
const gameCards: Array<AnnoCard> = [];
const ugcCards: Array<AnnoCard> = [];
for (const list of listResp.list) {
for (const item of list.list) {
const detail = detailResp.find((i) => i.ann_id === item.ann_id);
@@ -143,13 +157,15 @@ async function loadData(): Promise<void> {
actCards.push(card);
} else if (card.typeLabel === "game") {
gameCards.push(card);
} else if (card.typeLabel === "ugc") {
ugcCards.push(card);
}
} else {
await TGLogger.Warn(`[Announcements][loadData] 未找到公告详情:${item.ann_id}`);
}
}
}
annoCards.value = { activity: actCards, game: gameCards };
annoCards.value = { activity: actCards, game: gameCards, ugc: ugcCards };
await showLoading.end();
isReq.value = false;
}
@@ -177,12 +193,17 @@ function getAnnoCard(
const timeStart = anno.start_time.split(" ")[0];
const timeEnd = anno.end_time.split(" ")[0];
const time = `${timeStart} ~ ${timeEnd}`;
const labelMap: Record<string, TGApp.BBS.Announcement.AnnoTypeEnum> = {
1: AnnoTypeEnum.ACTIVITY,
2: AnnoTypeEnum.GAME,
26: AnnoTypeEnum.UGC,
};
return {
id: anno.ann_id,
title: anno.title,
subtitle: anno.subtitle.replace(/<br \/>/g, " "),
banner: anno.banner,
typeLabel: anno.type === 2 ? "game" : "activity",
typeLabel: labelMap[anno.type],
tagIcon: anno.tag_icon,
tagLabel: getAnnoTag(anno.tag_label),
timeStr: time,
@@ -217,9 +238,16 @@ async function switchNews(): Promise<void> {
font-family: var(--font-title);
}
.anno-top-append {
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
column-gap: 8px;
}
.anno-switch-btn {
height: 40px;
margin-right: 16px;
background: var(--tgc-btn-1);
color: var(--btn-text);
font-family: var(--font-title);
@@ -232,8 +260,8 @@ async function switchNews(): Promise<void> {
.anno-grid {
display: grid;
font-family: var(--font-title);
gap: 8px;
grid-auto-rows: auto;
grid-gap: 8px;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
}
</style>

View File

@@ -166,7 +166,7 @@ import { emit } from "@tauri-apps/api/event";
import { open } from "@tauri-apps/plugin-dialog";
import { remove } from "@tauri-apps/plugin-fs";
import { platform } from "@tauri-apps/plugin-os";
import { exit } from "@tauri-apps/plugin-process";
import { relaunch } from "@tauri-apps/plugin-process";
import { backUpUserData, restoreUserData } from "@utils/dataBS.js";
import { getBuildTime } from "@utils/TGBuild.js";
import TGLogger from "@utils/TGLogger.js";
@@ -391,14 +391,18 @@ async function confirmDelCache(): Promise<void> {
}
await showLoading.start("正在清除缓存");
for (const dir of CacheDir) {
await showLoading.update(dir);
await remove(dir, { recursive: true });
try {
await showLoading.update(dir);
await remove(dir, { recursive: true });
} catch (e) {
await TGLogger.Error(`[Config][confirmDelCache] 清除缓存失败 ${dir} ${e}`);
}
}
await showLoading.end();
await TGLogger.Info("[Config][confirmDelCache] 缓存清除完成");
showSnackbar.success("缓存已清除!即将退出应用!");
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
await exit();
await relaunch();
}
// 恢复默认设置

View File

@@ -20,20 +20,41 @@
</template>
<script lang="ts" setup>
import showSnackbar from "@comp/func/snackbar.js";
import recordReq from "@req/recordReq.js";
import hk4eReq from "@req/hk4eReq.js";
import takumiReq from "@req/takumiReq.js";
import useUserStore from "@store/user.js";
import { storeToRefs } from "pinia";
import { ref } from "vue";
const { cookie, account } = storeToRefs(useUserStore());
const authkey = ref<string>("");
async function test(): Promise<void> {
if (!cookie.value) return;
const resp = await recordReq.actCalendar(cookie.value, account.value);
console.log(resp);
if ("retcode" in resp) {
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
if (!cookie.value || !account.value) {
showSnackbar.warn("请先登录账号");
return;
}
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
if (typeof authkeyRes === "string") {
authkey.value = authkeyRes;
} else {
showSnackbar.error("获取authkey失败");
return;
}
const list: Array<TGApp.Game.Gacha.GachaBItem> = [];
let endId = "0";
while (true) {
const res = await hk4eReq.gachaB(authkey.value, "1000", endId);
if (Array.isArray(res)) {
if (res.length === 0) break;
list.push(...res);
endId = res[res.length - 1].id;
} else {
showSnackbar.warn(`[${res.retcode}] 获取祈愿记录失败:${res.message}`);
}
}
console.log(list);
}
</script>
<style lang="css" scoped>

View File

@@ -1,7 +1,6 @@
/**
* @file plugins/Sqlite/index.ts
* @description Sqlite 数据库操作类
* @since Beta v0.8.0
* Sqlite 数据库操作类
* @since Beta v0.8.4
*/
import { app } from "@tauri-apps/api";
@@ -17,10 +16,11 @@ class Sqlite {
"Achievements",
"AppData",
"GachaRecords",
"GachaBRecords",
"GameAccount",
"SpiralAbyss",
"RoleCombat",
"HardChallenge",
"RoleCombat",
"SpiralAbyss",
"UFCollection",
"UFMap",
"UFPost",
@@ -39,7 +39,7 @@ class Sqlite {
private constructor() {}
/**
* @description 获取数据库实例
* 获取数据库实例
* @since Beta v0.3.3
* @returns {Promise<Database>}
*/
@@ -49,7 +49,7 @@ class Sqlite {
}
/**
* @description 检测是否需要创建数据库
* 检测是否需要创建数据库
* @since Beta v0.6.1
* @returns {Promise<boolean>}
*/
@@ -70,7 +70,7 @@ class Sqlite {
}
/**
* @description 初始化数据库
* 初始化数据库
* @since Beta v0.4.5
* @returns {Promise<void>}
*/
@@ -81,7 +81,7 @@ class Sqlite {
}
/**
* @description 获取数据库信息
* 获取数据库信息
* @since Beta v0.3.3
* @returns {Promise<TGApp.Sqlite.AppData.Item[]>}
*/
@@ -92,7 +92,7 @@ class Sqlite {
}
/**
* @description 对比数据判断是否需要更新
* 对比数据判断是否需要更新
* @since Beta v0.3.3
* @returns {Promise<boolean>}
*/
@@ -105,7 +105,7 @@ class Sqlite {
}
/**
* @description 保存 appData
* 保存 appData
* @since Beta v0.3.3
* @param {string} key
* @param {string} value
@@ -118,7 +118,7 @@ class Sqlite {
}
/**
* @description 已有数据表跟触发器不变的情况下,更新数据库数据
* 已有数据表跟触发器不变的情况下,更新数据库数据
* @since Beta v0.3.3
* @returns {Promise<void>}
*/
@@ -131,7 +131,7 @@ class Sqlite {
}
/**
* @description 更新 SpiralAbyss 表
* 更新 SpiralAbyss 表
* @since Beta v0.6.1
* @returns {Promise<void>}
*/
@@ -147,7 +147,7 @@ class Sqlite {
}
/**
* @description 重置数据库
* 重置数据库
* @since Beta v0.4.0
* @returns {Promise<void>}
*/

View File

@@ -1,7 +1,6 @@
/**
* @file plugins/Sqlite/modules/userAbyss.ts
* @description Sqlite-用户深渊模块
* @since Beta v0.6.8
* Sqlite-用户深渊模块
* @since Beta v0.8.6
*/
import { path } from "@tauri-apps/api";
@@ -50,8 +49,8 @@ function getRestoreSql(tableData: TGApp.Sqlite.Abyss.TableData): string {
}
/**
* @description 获取深渊数据的插入更新Sql
* @since Beta v0.6.1
* 获取深渊数据的插入更新Sql
* @since Beta v0.8.6
* @param {string} uid - 用户UID
* @param {TGApp.Game.Abyss.FullData} data -深渊数据
* @returns {string}
@@ -67,7 +66,7 @@ function getInsertSql(uid: string, data: TGApp.Game.Abyss.FullData): string {
const normalSkillRank = transCharacterData(data.normal_skill_rank);
const energySkillRank = transCharacterData(data.energy_skill_rank);
const floors = transFloorData(data.floors);
const skippedFloor = data.skipped_floor;
const skippedFloor = data.skipped_floor ?? "";
const timeNow = timestampToDate(new Date().getTime());
return `
INSERT INTO SpiralAbyss (uid, id, startTime, endTime, totalBattleTimes, totalWinTimes, maxFloor,

View File

@@ -1,9 +1,9 @@
/**
* @file plugins/Sqlite/modules/userGacha.ts
* @description 用户祈愿模块
* @since Beta v0.7.5
* 用户祈愿模块
* @since Beta v0.8.6
*/
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import { path } from "@tauri-apps/api";
import { exists, mkdir, readDir } from "@tauri-apps/plugin-fs";
@@ -115,9 +115,9 @@ async function getGachaCheck(uid: string, type: string): Promise<string | undefi
* @description 获取用户祈愿记录
* @since Beta v0.4.7
* @param {string} uid - UID
* @return {Promise<TGApp.Sqlite.GachaRecords.SingleTable[]>}
* @return {Promise<TGApp.Sqlite.GachaRecords.TableGacha[]>}
*/
async function getGachaRecords(uid: string): Promise<TGApp.Sqlite.GachaRecords.SingleTable[]> {
async function getGachaRecords(uid: string): Promise<TGApp.Sqlite.GachaRecords.TableGacha[]> {
const db = await TGSqlite.getDB();
return await db.select("SELECT * FROM GachaRecords WHERE uid = ?;", [uid]);
}
@@ -132,9 +132,9 @@ async function getGachaRecords(uid: string): Promise<TGApp.Sqlite.GachaRecords.S
async function getGachaRecordsGroupByDate(
uid: string,
type?: string,
): Promise<Record<string, TGApp.Sqlite.GachaRecords.SingleTable[]>> {
): Promise<Record<string, TGApp.Sqlite.GachaRecords.TableGacha[]>> {
const db = await TGSqlite.getDB();
type resType = Array<TGApp.Sqlite.GachaRecords.SingleTable>;
type resType = Array<TGApp.Sqlite.GachaRecords.TableGacha>;
let res: resType;
if (type) {
res = await db.select<resType>(
@@ -146,7 +146,7 @@ async function getGachaRecordsGroupByDate(
uid,
]);
}
const map: Record<string, TGApp.Sqlite.GachaRecords.SingleTable[]> = {};
const map: Record<string, TGApp.Sqlite.GachaRecords.TableGacha[]> = {};
for (const item of res) {
// key 是 yyyy-MM-dd hh:mm:ss按照日期分组
const key = item.time.split(" ")[0];
@@ -198,33 +198,53 @@ async function cleanGachaRecords(
}
/**
* @description 合并祈愿数据
* @since Beta v0.4.7
* 合并祈愿数据
* @since Beta v0.8.6
* @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();
let cnt = 0;
const len = data.length;
let progress = 0;
for (const gacha of data) {
const trans = transGacha(gacha);
if (cnt % 20 === 0) {
progress = Math.round((cnt / len) * 100 * 100) / 100;
await showLoading.update(
`[${progress}%][${trans.time}] ${"⭐".repeat(Number(trans.rank_type))}-${trans.name}`,
);
cnt++;
}
const sql = getInsertSql(uid, trans);
await db.execute(sql);
}
}
/**
* @description 合并祈愿数据v4.0
* @since Beta v0.5.0
* 合并祈愿数据v4.0
* @since Beta v0.8.6
* @param {TGApp.Plugins.UIGF.GachaHk4e} data - UIGF数据
* @return {Promise<void>}
*/
async function mergeUIGF4(data: TGApp.Plugins.UIGF.GachaHk4e): Promise<void> {
const db = await TGSqlite.getDB();
let cnt: number = 0;
const len = data.list.length;
let progress: number = 0;
for (const gacha of data.list) {
const trans = transGacha(gacha, data.timezone);
if (cnt % 20 === 0) {
progress = Math.round((cnt / len) * 100 * 100) / 100;
await showLoading.update(
`[${progress}%][${trans.time}] ${"⭐".repeat(Number(trans.rank_type))}-${trans.name}`,
);
}
const sql = getInsertSql(data.uid.toString(), trans);
await db.execute(sql);
cnt++;
}
}

View File

@@ -0,0 +1,161 @@
/**
* 千星奇域祈愿模块
* @since Beta v0.8.4
*/
import showSnackbar from "@comp/func/snackbar.js";
import TGSqlite from "@Sql/index.js";
import { exists, mkdir } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
/**
* 获取导入 Sql
* @since Beta v0.8.4
* @param {TGApp.Game.Gacha.GachaBItem} gacha - 抽卡记录数据
* @returns {string}
*/
function getInsertSql(gacha: TGApp.Game.Gacha.GachaBItem): string {
return `
INSERT INTO GachaBRecords(id, uid, region, scheduleId, gachaType,
opGachaType, time, itemId, name, type,
rank, isUp, updated)
VALUES ('${gacha.id}', '${gacha.uid}', '${gacha.region}', '${gacha.schedule_id}',
'${gacha.op_gacha_type === "1000" ? "1000" : "2000"}', '${gacha.op_gacha_type}', '${gacha.time}',
'${gacha.item_id}', '${gacha.item_name}', '${gacha.item_type}',
'${gacha.rank_type}', '${gacha.is_up}', datetime('now', 'localtime'))
ON CONFLICT (id)
DO UPDATE
SET uid = '${gacha.uid}',
region = '${gacha.region}',
scheduleId = '${gacha.schedule_id}',
gachaType = '${gacha.op_gacha_type === "1000" ? "1000" : "2000"}',
opGachaType = '${gacha.op_gacha_type}',
time = '${gacha.time}',
itemId = '${gacha.item_id}',
name = '${gacha.item_name}',
type = '${gacha.item_type}',
rank = '${gacha.rank_type}',
isUp = '${gacha.is_up}',
updated = datetime('now', 'localtime');
`;
}
/**
* 插入列表数据
* @since Beta v0.8.4
* @param {Array<TGApp.Game.Gacha.GachaBItem>} list - 抽卡记录列表
* @returns {Promise<void>}
*/
async function insertGachaList(list: Array<TGApp.Game.Gacha.GachaBItem>): Promise<void> {
const db = await TGSqlite.getDB();
for (const gacha of list) {
const sql = getInsertSql(gacha);
await db.execute(sql);
}
}
/**
* 获取数据库UID列表
* @since Beta v0.8.4
* @returns {Promise<Array<string>>}
*/
async function getUidList(): Promise<Array<string>> {
const db = await TGSqlite.getDB();
type resType = Array<{ uid: string }>;
const res = await db.select<resType>("SELECT DISTINCT uid FROM GachaBRecords;");
return res.map((i) => i.uid);
}
/**
* 获取增量更新的记录 ID
* @since Beta v0.8.4
* @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 GachaBRecords WHERE uid = ? AND opGachaType = ? ORDER BY id DESC LIMIT 1;",
[uid, type],
);
if (res.length === 0) return undefined;
return res[0].id;
}
/**
* 获取用户祈愿记录
* @since Beta v0.8.4
* @param {string} uid - UID
* @param {string} [type] - 类型
* @return {Promise<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>}
*/
async function getGachaRecords(
uid: string,
type?: string,
): Promise<Array<TGApp.Sqlite.GachaRecords.TableGachaB>> {
const db = await TGSqlite.getDB();
if (type) {
return await db.select("SELECT * FROM GachaBRecords WHERE uid = ? AND opGachaType = ?;", [
uid,
type,
]);
}
return await db.select("SELECT * FROM GachaBRecords WHERE uid = ?;", [uid]);
}
/**
* 备份祈愿数据
* @since Beta v0.8.4
* @param {string} dir - 备份目录
* @remarks 等UIGF标准最终确定后与TSUserGacha合并
*/
async function backUpUigf(dir: string): Promise<void> {
if (!(await exists(dir))) {
await TGLogger.Warn("不存在指定的祈愿备份目录,即将创建");
await mkdir(dir, { recursive: true });
}
showSnackbar.error(`千星奇域祈愿数据备份功能尚未实现,请耐心等待后续版本更新。`);
}
/**
* 恢复祈愿数据
* @since Beta v0.8.4
* @param {string} dir - 恢复目录
* @remarks 等UIGF标准最终确定后与TSUserGacha合并
*/
async function restoreUigf(dir: string): Promise<boolean> {
if (!(await exists(dir))) {
await TGLogger.Warn("不存在指定的祈愿备份目录");
return false;
}
return true;
}
/**
* 删除用户祈愿数据
* @since Beta v0.8.4
* @param {string} uid - UID
* @returns {Promise<void>}
*/
async function deleteRecords(uid: string): Promise<void> {
const db = await TGSqlite.getDB();
await db.execute("DELETE FROM GachaBRecords WHERE uid = ?;", [uid]);
}
/**
* 千星奇域祈愿模块
* @since Beta v0.8.4
*/
const TSUserGachaB = {
getUidList,
getGachaCheck,
getGachaRecords,
insertGachaList,
backUpUigf,
restoreUigf,
deleteRecords,
};
export default TSUserGachaB;

View File

@@ -1,6 +1,5 @@
-- @file plugins/Sqlite/sql/createTable.sql
-- @brief sqlite数据库创建表语句
-- @since Beta v0.8.0
-- @since Beta v0.8.4
-- @brief 创建成就数据表
create table if not exists Achievements
@@ -148,6 +147,24 @@ create table if not exists GachaRecords
updated text
);
-- @brief 创建千星奇域祈愿数据表
create table if not exists GachaBRecords
(
id text primary key not null,
uid text,
region text,
scheduleId text,
gachaType text,
opGachaType text,
time text,
itemId text,
name text,
type text,
rank text,
isUp text,
updated text
);
-- @brief 创建用户帖子收藏
create table if not exists UFPost
(

View File

@@ -1,7 +1,7 @@
/**
* @file request/hk4eReq.ts
* @description Hk4eApi 请求模块
* @since Beta v0.8.0
* @since Beta v0.8.4
*/
import { AnnoLangEnum } from "@enum/anno.js";
@@ -122,7 +122,7 @@ async function getGachaLog(
size: "20",
end_id: endId,
};
const resp = await TGHttp<TGApp.Game.Gacha.GachaLogResponse | TGApp.BBS.Response.Base>(
const resp = await TGHttp<TGApp.Game.Gacha.GachaLogResp | TGApp.BBS.Response.Base>(
"https://public-operation-hk4e.mihoyo.com/gacha_info/api/getGachaLog",
{ method: "GET", query: params },
);
@@ -130,6 +130,37 @@ async function getGachaLog(
return resp.data.list;
}
/**
* @description 获取千星奇域抽卡记录
* @since Beta v0.8.4
* @param {string} authKey authKey
* @param {string} gachaType 抽卡类型
* @param {string} endId 结束 id默认为 0
* @returns {Promise<Array<TGApp.Game.Gacha.GachaBItem>|TGApp.BBS.Response.Base>} 抽卡记录
*/
async function getBeyondGachaLog(
authKey: string,
gachaType: string,
endId: string = "0",
): Promise<Array<TGApp.Game.Gacha.GachaBItem> | TGApp.BBS.Response.Base> {
const params = {
lang: "zh-cn",
auth_appid: "webview_gacha",
authkey: authKey,
authkey_ver: "1",
sign_type: "2",
gacha_type: gachaType,
size: "5",
end_id: endId,
};
const resp = await TGHttp<TGApp.Game.Gacha.GachaBLogResp | TGApp.BBS.Response.Base>(
"https://public-operation-hk4e.mihoyo.com/gacha_info/api/getBeyondGachaLog",
{ method: "GET", query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.list;
}
/**
* @description 获取登录二维码
* @since Beta v0.7.2
@@ -171,6 +202,7 @@ async function queryPandaQr(
const hk4eReq = {
anno: { list: getAnnoList, detail: getAnnoDetail },
gacha: getGachaLog,
gachaB: getBeyondGachaLog,
loginQr: { create: fetchPandaQr, state: queryPandaQr },
};

View File

@@ -78,7 +78,7 @@ async function genAuthKey(
game_uid: account.gameUid,
region: account.region,
};
const resp = await TGHttp<TGApp.Game.Gacha.AuthkeyResponse | TGApp.BBS.Response.Base>(
const resp = await TGHttp<TGApp.Game.Gacha.AuthkeyResp | TGApp.BBS.Response.Base>(
`${taBu}binding/api/genAuthKey`,
{
method: "POST",

View File

@@ -1,7 +1,7 @@
/**
* @file router modules user.ts
* @file router/modules/user.ts
* @description user 路由模块
* @since Beta v0.8.0
* @since Beta v0.8.4
*/
import type { RouteRecordRaw } from "vue-router";
@@ -31,6 +31,11 @@ const userRoutes = (<const>[
name: "祈愿记录",
component: async () => await import("@/pages/User/Gacha.vue"),
},
{
path: "/user/gachaB",
name: "千星奇域祈愿记录",
component: async () => await import("@/pages/User/GachaB.vue"),
},
{
path: "/user/record",
name: "原神战绩",

View File

@@ -1,16 +1,10 @@
/**
* @file router/modules/wiki.ts
* @description wiki 路由模块
* @since Beta v0.6.7
* wiki 路由模块
* @since Beta v0.8.6
*/
import type { RouteRecordRaw } from "vue-router";
const wikiRoutes = (<const>[
{
path: "/wiki/abyss",
name: "深渊数据库",
component: async () => await import("@/pages/WIKI/Abyss.vue"),
},
{
path: "/wiki/character/:id?",
name: "角色图鉴",

View File

@@ -1,7 +1,6 @@
/**
* @file router routes.ts
* @description 路由配置
* @since Beta v0.4.4
* 路由配置
* @since Beta v0.8.6
*/
import { RouteRecordRaw } from "vue-router";
@@ -18,6 +17,7 @@ const routes = (<const>[
...archiveRoutes,
...wikiRoutes,
...userRoutes,
{ path: "/:pathMatch(.*)*", redirect: "/" },
]) satisfies Array<RouteRecordRaw>;
export default routes;

View File

@@ -1,36 +1,59 @@
/**
* @file types/App/Gacha.d.ts
* @description 本应用的祈愿相关类型定义
* @since Beta v0.4.4
* 本应用的祈愿相关类型定义
* @since Beta v0.8.4
*/
/**
* @description 祈愿记录命名空间
* @namespace Gacha
* @since Beta v0.4.4
* @memberof TGApp.App
*/
declare namespace TGApp.App.Gacha {
/**
* @description 祈愿类型枚举
* 祈愿类型枚举
* @since Beta v0.4.4
* @enum {number}
* @property {number} Newbie 新手祈愿 = 100
* @property {number} Normal 常驻祈愿 = 200
* @property {number} CharacterUp 角色活动祈愿 = 301
* @property {number} CharacterUp2 角色活动祈愿2 = 400
* @property {number} WeaponUp 武器活动祈愿 = 302
* @property {number} MixUp 集录祈愿 = 500
* @return WishType
*/
const enum WishType {
const WishType = <const>{
/** 新手祈愿 */
Newbie = 100,
/** 常驻祈愿 */
Normal = 200,
/** 角色活动祈愿 */
CharacterUp = 301,
/** 角色活动祈愿2 */
CharacterUp2 = 400,
/** 武器活动祈愿 */
WeaponUp = 302,
/** 集录祈愿 */
MixUp = 500,
}
};
/**
* 祈愿类型枚举
* @since Beta v0.8.4
*/
type WishTypeEnum = (typeof WishType)[keyof typeof WishType];
/**
* 千星奇域祈愿类型
* @since Beta v0.8.4
*/
const WishTypeB = <const>{
/** 常驻祈愿 */
Normal: "1000",
/** 活动祈愿 */
Event: "2000",
/** 男性活动祈愿1 */
EventBoy1: "20011",
/** 男性活动祈愿2 */
EventBoy2: "20012",
/** 女性活动祈愿1 */
EventGirl1: "20021",
/** 女性活动祈愿2 */
EventGirl2: "20022",
};
/**
* 千星奇域祈愿类型
* @since Beta v0.8.4
*/
type WishTypeBEnum = (typeof WishTypeB)[keyof typeof WishTypeB];
/**
* @description 祈愿记录项
* @interface PoolItem
@@ -47,16 +70,49 @@ declare namespace TGApp.App.Gacha {
* @property {number[]} up4List up四星
* @return PoolItem
*/
interface PoolItem {
type PoolItem = {
/** 卡池名称 */
name: string;
/** 卡池版本 */
version: string;
/** 卡池排序 */
order: number;
/** 卡池横幅 */
banner: string;
/** 卡池开始时间 yyyy-MM-ddTHH:mm:ss+08:00 */
from: string;
/** 卡池结束时间 yyyy-MM-ddTHH:mm:ss+08:00 */
to: string;
type: WishType;
/** 卡池类型 */
type: number;
/** 卡池帖子ID */
postId: string;
up5List: number[];
up4List: number[];
}
/** up五星 */
up5List: Array<number>;
/** up四星 */
up4List: Array<number>;
};
/**
* 千星奇域套装类型
* @since Beta v0.8.4
*/
type GachaBSetType = "装扮部件" | "装扮套装";
/**
* 千星奇域祈愿元数据
* @since Beta v0.8.4
*/
type GachaBMeta = {
/** ID */
id: string;
/** 名称 */
name: string;
/** 图标 */
icon: string;
/** 稀有度 */
rank: number;
/** 类型 */
type: GachaBSetType;
};
}

View File

@@ -1,7 +1,7 @@
/**
* @file types/BBS/Announcement.d.ts
* @description 从 BBS 获取到的游戏内公告类型定义文件
* @since Beta v0.8.0
* @since Beta v0.8.4
*/
declare namespace TGApp.BBS.Announcement {
@@ -30,14 +30,16 @@ declare namespace TGApp.BBS.Announcement {
/**
* @description 公告类型
* @since Beta v0.7.7
* @since Beta v0.8.4
* @const AnnoType
* @property {string} "activity" - 活动公告
* @property {string} "game" - 游戏公告
* @property {string} "ugc" - 千星奇域公告
*/
const AnnoType = <const>{
ACTIVITY: "activity",
GAME: "game",
UGC: "ugc",
};
/**

View File

@@ -1,7 +1,7 @@
/**
* @file types/BBS/Forum.d.ts
* @description BBS 版块类型定义
* @since Beta v0.7.1
* @since Beta v0.8.4
*/
declare namespace TGApp.BBS.Forum {
@@ -11,7 +11,6 @@ declare namespace TGApp.BBS.Forum {
* @interface GameForumResp
* @extends TGApp.BBS.Response.BaseWithData
* @property {Array<GameForum>} data.list 所有版块信息
* @return GameForumResp
*/
type GameForumResp = TGApp.BBS.Response.BaseWithData & { data: { list: Array<GameForum> } };
@@ -21,7 +20,6 @@ declare namespace TGApp.BBS.Forum {
* @interface PostForumResp
* @extends TGApp.BBS.Response.BaseWithData
* @property {PostForumRes} data 版块帖子列表
* @return PostForumResp
*/
type PostForumResp = TGApp.BBS.Response.BaseWithData<PostForumRes>;
@@ -31,7 +29,6 @@ declare namespace TGApp.BBS.Forum {
* @interface GameForum
* @property {number} game_id 游戏 ID
* @property {Array<GameForumItem>} forums 版块信息
* @return GameForum
*/
type GameForum = { game_id: number; forums: Array<GameForumItem> };
@@ -66,7 +63,6 @@ declare namespace TGApp.BBS.Forum {
* @property {string} read_me 说明
* @property {Array<ForumCate>} forum_cate_list 分类列表
* @property {Array<ForumCate>} video_cat_list 视频分类列表
* @return GameForumItem
*/
type GameForumItem = {
id: number;
@@ -100,16 +96,15 @@ declare namespace TGApp.BBS.Forum {
/**
* @description 视频分类
* @since Beta v0.6.8
* @since Beta v0.8.4
* @interface ForumCate
* @property {number} id 分类 ID
* @property {string} name 分类名称
* @property {number} forum_id 版块 ID
* @property {string} desc 描述
* @property {string} remark 备注
* @return ForumCate
* @property {string} [desc] 描述
* @property {string} [remark] 备注
*/
type ForumCate = { id: number; name: string; forum_id: number; desc: string; remark: string };
type ForumCate = { id: number; name: string; forum_id: number; desc?: string; remark?: string };
/**
* @description 版块帖子列表
@@ -121,7 +116,6 @@ declare namespace TGApp.BBS.Forum {
* @property {number} page 页码
* @property {unknown} databox 数据盒子
* @property {Array<TGApp.BBS.Post.FullData>} list 帖子列表
* @return PostForumRes
*/
type PostForumRes = {
last_id: string;

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,38 @@
/**
* @file types/BBS/Response.d.ts
* @description BBS 返回数据类型定义文件
* BBS 返回数据类型定义文件
* @since Beta v0.7.1
*/
declare namespace TGApp.BBS.Response {
/**
* @description 基础返回类型,设计米游社接口请求都是这个类型
* @interface Base
* 基础返回响应
* @since Beta v0.3.9
* @property {never} retcode - 响应代码
* @property {string} message - 响应消息
* @property {never} data - 响应数据
* @return Base
*/
type Base = { retcode: number; message: string; data: never };
type Base = {
/**
* 响应代码
* @remarks
* 为0表示请求成功非0表示请求失败
* 请求失败时data 恒为 null
*/
retcode: number;
/** 响应消息 */
message: string;
/** 响应数据 */
data: never;
};
/**
* @description 基础返回类型-带有 data 的
* @interface BaseWithData
* 成功返回响应
* @since Beta v0.7.1
* @property {0} retcode - 响应代码
* @property {string} message - 响应消息
* @property {any} data - 响应数据
* @return BaseWithData
* @remarks 仅用于表示请求成功数据,retcode 恒为 0
*/
type BaseWithData<T = unknown> = { retcode: 0; message: string; data: T };
type BaseWithData<T = unknown> = {
/** 响应代码 */
retcode: 0;
/** 响应消息 */
message: string;
/** 响应数据 */
data: T;
};
}

View File

@@ -1,7 +1,7 @@
/**
* @file types/BBS/SctPost.d.ts
* @description 结构化帖子类型声明文件
* @since Beta v0.7.2
* @since Beta v0.8.4
*/
declare namespace TGApp.BBS.SctPost {
@@ -31,14 +31,60 @@ declare namespace TGApp.BBS.SctPost {
type Empty = { insert: never; attributes?: never };
/**
* @description 帖子结构化数据-其他类型
* @since Beta v0.6.7
* @description 帖子结构化数据-viewType为2
* @since Beta v0.8.4
* @property {string} describe - 描述
* @property {Array<string>} imgs - 图片链接
* @property {Array<string>} link_card_ids - 关联卡片ID
* @return Other
* @return Pic
*/
type Other = { describe: string; imgs: Array<string>; link_card_ids?: Array<string> } & {
type Pic = { describe: string; imgs: Array<string>; link_card_ids?: Array<string> } & {
[key: string]: unknown;
};
/**
* @description 帖子结构化数据-viewType为7
* @since Beta v0.8.4
* @description 下面详细结构参见相关组件
* @todo 为简便起见所有字段可能为null,但是目前没遇到text&level为null的情况
* @property {Array<Base>} text - 文字内容
* @property {Array<UgcImage>} images - 图片内容
* @property {Array<UgcVod>} vods - 视频内容
* @property {Array<UgcLevel>} levels - 等级内容
*/
type Ugc = {
text: Array<Base> | null;
images: Array<UgcImage> | null;
vods: Array<UgcVod> | null;
levels: Array<UgcLevel> | null;
};
/**
* @description Ugc结构下的图片内容
* @since Beta v0.8.4
* @interface UgcImage
* @property {number} image_id - 图片ID
* @property {string} image_url - 图片链接
* @property {TGApp.BBS.Post.Image} image - 图片信息
*/
type UgcImage = { image_id: number; image_url: string; image: TGApp.BBS.Post.Image };
/**
* @description Ugc结构下的视频内容
* @since Beta v0.8.4
* @interface UgcVod
* @property {string} vod_id - 视频ID
* @property {TGApp.BBS.Post.Vod} vod - 视频信息
*/
type UgcVod = { vod_id: string; vod: TGApp.BBS.Post.Vod };
/**
* @description Ugc结构下的关卡内容
* @since Beta v0.8.4
* @interface UgcLevel
* @property {string} level_id - 关卡ID
* @property {string} region - 关卡服务器
* @property {TGApp.BBS.UGC.Level} level - 关卡信息
*/
type UgcLevel = { level_id: string; region: string; level: TGApp.BBS.UGC.Level };
}

133
src/types/BBS/UGC.d.ts vendored Normal file
View File

@@ -0,0 +1,133 @@
/**
* @file types/BBS/UGC.d.ts
* @description 千星奇域类型定义文件
* @since Beta v0.8.4
*/
declare namespace TGApp.BBS.UGC {
/**
* @description UGC 游戏角色信息
* @since Beta v0.8.4
* @interface Character
* @property {string} region 游戏区服
* @property {string} game_uid 游戏 UID
* @property {string} nickname 游戏昵称
* @property {string} user_label 用户标签
* @property {string} region_name 区服名称
*/
type Character = {
region: string;
game_uid: string;
nickname: string;
user_label: string;
region_name: string;
};
/**
* @description 关卡信息
* @since Beta v0.8.4
* @interface Level
* @property {string} level_id 关卡 ID
* @property {string} region 区域
* @property {string} level_name 关卡名称
* @property {Cover} cover 关卡封面
* @property {string} desc 关卡描述
* @property {number} limit_play_num_min 最小游玩人数
* @property {number} limit_play_num_max 最大游玩人数
* @property {string} play_type 游玩类型
* @property {string} good_rate 好评率
* @property {string} hot_score 热度分数
* @property {string} creator_uid 创建者 UID
* @property {InteractInfo} interact_info 交互信息
* @property {LevelAttachment} level_attachment 关卡附件
* @property {UserPlayInfo} user_play_info 用户游玩信息
* @property {Extra} extra 额外信息
* @property {boolean} level_info_has_released 关卡信息是否已发布
* @property {string} level_source_type 关卡来源类型
* @property {string} data_box 数据盒
* @property {string} show_limit_play_num_str 显示的游玩人数限制字符串
* @property {string} level_intro 关卡介绍
*/
type Level = {
level_id: string;
region: string;
level_name: string;
cover: Cover;
desc: string;
limit_play_num_min: number;
limit_play_num_max: number;
play_type: string;
good_rate: string;
hot_score: string;
creator_uid: string;
interact_info: InteractInfo;
level_attachment: LevelAttachment | null;
user_play_info: UserPlayInfo;
extra: Extra;
level_info_has_released: boolean;
level_source_type: string;
data_box: string;
show_limit_play_num_str: string;
level_intro: string;
};
/**
* @description 关卡封面
* @since Beta v0.8.4
* @interface Cover
* @property {string} url 封面链接
*/
type Cover = { url: string };
/**
* @description 用户交互信息
* @since Beta v0.8.4
* @interface InteractInfo
* @property {boolean} has_fav 是否已收藏
*/
type InteractInfo = { has_fav: boolean };
/**
* @description 关卡附件
* @since Beta v0.8.4
* @interface LevelAttachment
* @property {string} type 信息类型
* @property {string} content 信息内容
*/
type LevelAttachment = { type: string; content: string };
/**
* @description 游玩信息
* @since Beta v0.8.4
* @interface UserPlayInfo
* @property {boolean} has_played 是否已游玩
* @property {string} played_time 游玩时间
* @property {number} played_count 游玩次数
*/
type UserPlayInfo = { has_played: boolean; played_time: string; played_count: number };
/**
* @description 额外信息
* @since Beta v0.8.4
* @interface Extra
* @property {Array<PlayLink>} play_link 游玩链接
* @property {boolean} friends_played 好友游玩过
* @property {Array<unknown>} friends_played_list 好友游玩列表 // TODO: 类型待确定
* @property {string} first_online_time 首次上线时间
*/
type Extra = {
play_link: Array<PlayLink>;
friends_played: boolean;
friends_played_list: Array<Character>;
first_online_time: string;
};
/**
* @description 游玩链接
* @since Beta v0.8.4
* @interface PlayLink
* @property {string} link_content 链接内容
* @property {string} link_type 链接类型
*/
type PlayLink = { link_content: string; link_type: string };
}

View File

@@ -1,147 +1,136 @@
/**
* @file src/types/Game/ActCalendar.d.ts
* @description 游戏-活动日历相关类型定义文件
* 游戏-活动日历相关类型定义文件
* @since Beta v0.8.3
*/
declare namespace TGApp.Game.ActCalendar {
/**
* @description 获取活动日历返回响应
* 获取活动日历返回响应
* @since Beta v0.8.0
* @interface Response
* @extends TGApp.BBS.Response.BaseWithData
* @property {ActRes} data - 返回数据
*/
type Response = TGApp.BBS.Response.BaseWithData<ActRes>;
/**
* @description 活动日历返回数据
* 活动日历返回数据
* @since Beta v0.8.0
* @interface ActRes
* @property {Array<ActPool>} avatar_card_pool_list 角色卡池列表
* @property {Array<ActPool>} weapon_card_pool_list 武器卡池列表
* @property {Array<ActPool>} mixed_card_pool_list 混合卡池列表
* @property {Array<ActPool>} selected_avatar_card_pool_list 选中角色卡池列表 // TODO:未知用途
* @property {Array<ActPool>} selected_mixed_card_pool_list 选中混合卡池列表 // TODO:未知用途
* @property {Array<ActItem>} act_list 活动列表
* @property {Array<ActItem>} fixed_act_list 固定活动列表
* @property {Array<ActItem>} selected_act_list 选中活动列表 // TODO:未知用途
*/
type ActRes = {
/** 角色卡池列表 */
avatar_card_pool_list: Array<ActPool>;
/** 武器卡池列表 */
weapon_card_pool_list: Array<ActPool>;
/** 混合卡池列表 */
mixed_card_pool_list: Array<ActPool>;
/** 选中角色卡池列表 */
selected_avatar_card_pool_list: Array<ActPool>;
/** 选中混合卡池列表 */
selected_mixed_card_pool_list: Array<ActPool>;
/** 活动列表 */
act_list: Array<ActItem>;
/** 固定活动列表 */
fixed_act_list: Array<ActItem>;
/** 选中活动列表 */
selected_act_list: Array<ActItem>;
};
/**
* @description 活动卡池信息
* 活动卡池信息
* @since Beta v0.8.0
* @interface ActPool
* @property {number} pool_id 卡池id
* @property {string} version_name 游戏版本 - 5.8
* @property {string} pool_name 卡池名称 - 角色活动祈愿
* @property {number} pool_type 卡池类型 - 1:角色活动祈愿, 2:武器活动祈愿, 3:
* @property {Array<ActPoolAvatar>} avatars 角色列表
* @property {Array<ActPoolWeapon>} weapons 武器列表
* @property {string} start_timestamp 开始时间戳(秒)
* @property {TGApp.Game.Base.DateTime} start_time 开始时间
* @property {string} end_timestamp 结束时间戳(秒)
* @property {TGApp.Game.Base.DateTime} end_time 结束时间
* @property {string} jump_url 跳转链接
* @property {number} pool_status 卡池状态 // 1:未开始 2:进行中 3:已结束
* @property {number} countdown_seconds 距离结束倒计时(秒)
*/
type ActPool = {
/** 卡池id */
pool_id: number;
/** 游戏版本 - 5.8 */
version_name: string;
/** 卡池名称 - 角色活动祈愿 */
pool_name: string;
/** 卡池类型 - 1:角色活动祈愿, 2:武器活动祈愿, 3:混合活动祈愿 */
pool_type: number;
/** 角色列表 */
avatars: Array<ActPoolAvatar>;
/** 武器列表 */
weapons: Array<ActPoolWeapon>;
/** 开始时间戳(秒) */
start_timestamp: string;
/** 开始时间 */
start_time: TGApp.Game.Base.DateTime;
/** 结束时间戳(秒) */
end_timestamp: string;
/** 结束时间 */
end_time: TGApp.Game.Base.DateTime;
/** 跳转链接 */
jump_url: string;
/** 卡池状态 // 1:未开始 2:进行中 3:已结束 */
pool_status: number;
/** 距离结束倒计时(秒) */
countdown_seconds: number;
};
/**
* @description 角色卡池数据
* 角色卡池数据
* @since Beta v0.8.0
* @interface ActPoolAvatar
* @property {number} id 角色id
* @property {string} icon 角色图标
* @property {string} name 角色名称
* @property {string} element 角色元素(英文)
* @property {number} rarity 角色星级
* @property {boolean} is_invisible 是否隐藏
*/
type ActPoolAvatar = {
/** 角色id */
id: number;
/** 角色图标 */
icon: string;
/** 角色名称 */
name: string;
/** 角色元素(英文) */
element: string;
/** 角色星级 */
rarity: number;
/** 是否隐藏 */
is_invisible: boolean;
};
/**
* @description 武器卡池数据
* 武器卡池数据
* @since Beta v0.8.0
* @interface ActPoolWeapon
* @property {number} id 武器id
* @property {string} icon 武器图标
* @property {number} rarity 武器星级
* @property {string} name 武器名称
* @property {string} wiki_url 百科链接
*/
type ActPoolWeapon = {
/** 武器id */
id: number;
/** 武器图标 */
icon: string;
/** 武器星级 */
rarity: number;
/** 武器名称 */
name: string;
/** 百科链接 */
wiki_url: string;
};
/**
* @description 活动类型枚举
* 活动类型枚举
* @since Beta v0.8.0
* @const ActType
* @todo 可能不完整,等待补充
* @property {string} "ActTypeHardChallenge" 幽境危战
* @property {string} "ActTypeRoleCombat" 真境剧诗
* @property {string} "ActTypeTower" 深渊螺旋
* @property {string} "ActTypeDouble" 双倍活动
* @property {string} "ActTypeExplore" 探索活动
* @property {string} "ActTypeOther" 其他活动
* @remarks TODO:可能不完整,等待补充
*/
const ActType = <const>{
/** 幽境危战 */
HardChallenge: "ActTypeHardChallenge",
/** 真境剧诗 */
RoleCombat: "ActTypeRoleCombat",
/** 深渊螺旋 */
Tower: "ActTypeTower",
/** 双倍活动 */
Double: "ActTypeDouble",
/** 探索活动 */
Explore: "ActTypeExplore",
/** 其他活动 */
Other: "ActTypeOther",
};
/**
* @description 活动类型枚举
* 活动类型枚举
* @since Beta v0.8.0
* @enum ActTypeEnum
*/
type ActTypeEnum = (typeof ActType)[keyof typeof ActType] | string;
/**
* @description 活动信息
* 活动信息
* @since Beta v0.8.0
* @interface ActItem
*/
type ActItem =
| ActItemHardChallenge
@@ -152,190 +141,200 @@ declare namespace TGApp.Game.ActCalendar {
| ActItemOther;
/**
* @description 活动信息-通用
* 活动信息-通用
* @since Beta v0.8.0
* @interface ActItemBase
* @template T
* @property {number} id 活动id
* @property {string} name 活动名称
* @property {T} type 活动类型
* @property {string} start_timestamp 开始时间戳(秒)
* @property {TGApp.Game.Base.DateTime} start_time 开始时间
* @property {string} end_timestamp 结束时间戳(秒)
* @property {TGApp.Game.Base.DateTime} end_time 结束时间
* @property {string} desc 活动描述
* @property {string} strategy 活动攻略
* @property {number} countdown_seconds 距离结束倒计时(秒)
* @property {number} status 活动状态 // 1:未开始 2:进行中 3:已结束
* @property {Array<ActReward>} reward_list 活动奖励列表
* @property {boolean} is_finished 是否完成
*/
type ActItemBase<T extends ActTypeEnum> = {
/** 活动id */
id: number;
/** 活动名称 */
name: string;
/** 活动类型 */
type: T;
/** 开始时间戳(秒) */
start_timestamp: string;
/** 开始时间 */
start_time: TGApp.Game.Base.DateTime;
/** 结束时间戳(秒) */
end_timestamp: string;
/** 结束时间 */
end_time: TGApp.Game.Base.DateTime;
/** 活动描述 */
desc: string;
/** 活动攻略 */
strategy: string;
/** 距离结束倒计时(秒) */
countdown_seconds: number;
/**
* 活动状态
* @remarks
* 1:未开始
* 2:进行中
* 3:已结束
*/
status: number;
/** 活动奖励列表 */
reward_list: Array<ActReward>;
/** 是否完成 */
is_finished: boolean;
};
/**
* @description 活动奖励信息
* 活动奖励信息
* @since Beta v0.8.0
* @interface ActReward
* @property {number} item_id 物品id
* @property {string} name 物品名称
* @property {string} icon 物品图标
* @property {string} wiki_url 物品百科链接
* @property {number} num 物品数量
* @property {string} rarity 物品稀有度
* @property {boolean} homepage_show 是否首页展示
*/
type ActReward = {
/** 物品id */
item_id: number;
/** 物品名称 */
name: string;
/** 物品图标 */
icon: string;
/** 物品百科链接 */
wiki_url: string;
/** 物品数量 */
num: number;
/** 物品稀有度 */
rarity: string;
/** 是否首页展示 */
homepage_show: boolean;
};
/**
* @description 活动信息-幽境危战
* 活动信息-幽境危战
* @since Beta v0.8.0
* @interface ActItemHardChallenge
* @extends ActItemBase<"ActTypeHardChallenge">
* @property {ActHardChallenge} hard_challenge_detail 幽境危战活动详情
*/
type ActItemHardChallenge = ActItemBase<"ActTypeHardChallenge"> & {
/** 幽境危战活动详情 */
hard_challenge_detail: ActHardChallenge;
};
/**
* @description 幽境危战活动详情
* 幽境危战活动详情
* @since Beta v0.8.0
* @interface ActHardChallenge
* @property {boolean} is_unlock 是否解锁
* @property {number} difficulty 当前难度
* @property {number} second 挑战耗时(秒)
* @property {string} icon 活动图标
* @property {ActHardChallengeSub} sub 子信息
*/
type ActHardChallenge = {
/** 是否解锁 */
is_unlock: boolean;
/** 当前难度 */
difficulty: number;
/** 挑战耗时(秒) */
second: number;
/** 活动图标 */
icon: string;
/** 子信息 */
sub: ActHardChallengeSub;
};
/**
* @description 幽境危战子信息
* 幽境危战子信息
* @since Beta v0.8.0
* @interface ActHardChallengeSub
* @property {number} seconds 挑战耗时(秒)
* @property {number} x 未知参数
* @property {number} y 未知参数
*/
type ActHardChallengeSub = { seconds: number; x: number; y: number };
type ActHardChallengeSub = {
/** 挑战耗时(秒) */
seconds: number;
/** 未知参数 */
x: number;
/** 未知参数 */
y: number;
};
/**
* @description 活动信息-真境剧诗
* @since Beta v0.8.0
* @interface ActItemRoleCombat
* @extends ActItemBase<"ActTypeRoleCombat">
* @property {ActRoleCombat} role_combat_detail 真境剧诗活动详情
* 活动信息-真境剧诗
* @since Beta v0.8.0剧诗活动详情
*/
type ActItemRoleCombat = ActItemBase<"ActTypeRoleCombat"> & { role_combat_detail: ActRoleCombat };
type ActItemRoleCombat = ActItemBase<"ActTypeRoleCombat"> & {
/** 真境剧诗活动详情 */
role_combat_detail: ActRoleCombat;
};
/**
* @description 真境剧诗活动详情
* 真境剧诗活动详情
* @since Beta v0.8.3
* @interface ActRoleCombat
* @property {number} difficulty_id 难度id
* @property {boolean} has_data 是否有数据
* @property {boolean} is_unlock 是否解锁
* @property {number} max_round_id 最大回合数
* @property {number} tarot_finished_cnt 塔罗牌完成数
*/
type ActRoleCombat = {
/** 难度id */
difficulty_id: number;
/** 是否有数据 */
has_data: boolean;
/** 是否解锁 */
is_unlock: boolean;
/** 最大回合数 */
max_round_id: number;
/** 塔罗牌完成数 */
tarot_finished_cnt: number;
};
/**
* @description 活动信息-深渊螺旋
* 活动信息-深渊螺旋
* @since Beta v0.8.0
* @interface ActItemTower
* @extends ActItemBase<"ActTypeTower">
* @property {ActTower} tower_detail 深渊螺旋活动详情
*/
type ActItemTower = ActItemBase<"ActTypeTower"> & { tower_detail: ActTower };
type ActItemTower = ActItemBase<"ActTypeTower"> & {
/** 深渊螺旋活动详情 */
tower_detail: ActTower;
};
/**
* @description 深渊螺旋活动详情
* 深渊螺旋活动详情
* @since Beta v0.8.0
* @interface ActTower
* @property {boolean} is_unlock 是否解锁
* @property {number} max_star 最大星数
* @property {number} total_star 总星数
* @property {boolean} has_data 是否有数据
*/
type ActTower = { is_unlock: boolean; max_star: number; total_star: number; has_data: boolean };
type ActTower = {
/** 是否解锁 */
is_unlock: boolean;
/** 最大星数 */
max_star: number;
/** 总星数 */
total_star: number;
/** 是否有数据 */
has_data: boolean;
};
/**
* @description 活动信息-双倍活动
* 活动信息-双倍活动
* @since Beta v0.8.0
* @interface ActItemDouble
* @extends ActItemBase<"ActTypeDouble">
* @property {ActDouble} double_detail 双倍活动详情
*/
type ActItemDouble = ActItemBase<"ActTypeDouble"> & { double_detail: ActDouble };
type ActItemDouble = ActItemBase<"ActTypeDouble"> & {
/** 双倍活动详情 */
double_detail: ActDouble;
};
/**
* @description 双倍活动详情
* 双倍活动详情
* @since Beta v0.8.0
* @interface ActDouble
* @property {number} total 总次数
* @property {number} left 剩余次数
*/
type ActDouble = { total: number; left: number };
type ActDouble = {
/** 总次数 */
total: number;
/** 剩余次数 */
left: number;
};
/**
* @description 活动信息-探索活动
* 活动信息-探索活动
* @since Beta v0.8.0
* @interface ActItemExplore
* @extends ActItemBase<"ActTypeExplore">
* @property {ActExplore} explore_detail 探索活动详情
*/
type ActItemExplore = ActItemBase<"ActTypeExplore"> & { explore_detail: ActExplore };
type ActItemExplore = ActItemBase<"ActTypeExplore"> & {
/** 探索活动详情 */
explore_detail: ActExplore;
};
/**
* @description 探索活动详情
* 探索活动详情
* @since Beta v0.8.0
* @interface ActExplore
* @property {number} explore_percent 探索进度百分比
* @property {boolean} is_finished 是否完成
*/
type ActExplore = { explore_percent: number; is_finished: boolean };
type ActExplore = {
/** 探索进度百分比 */
explore_percent: number;
/** 是否完成 */
is_finished: boolean;
};
/**
* @description 活动信息-其他活动
* 活动信息-其他活动
* @since Beta v0.8.0
* @interface ActItemOther
* @extends ActItemBase<"ActTypeOther">
* @property {boolean} is_finished 是否完成
*/
type ActItemOther = ActItemBase<"ActTypeOther"> & { is_finished: boolean };
type ActItemOther = ActItemBase<"ActTypeOther"> & {
/** 是否完成 */
is_finished: boolean;
};
}

View File

@@ -1,54 +1,50 @@
/**
* @file types/Game/Base.d.ts
* @description 游戏相关基础类型定义文件
* 游戏相关基础类型定义文件
* @since Beta v0.8.0
*/
declare namespace TGApp.Game.Base {
/**
* @description 服务器类型
* 服务器类型
* @since Beta v0.8.0
* @const ServerType
* @property {string} "cn_gf01" - 国内-国服
* @property {string} "cn_qd01" - 国内-渠道服
* @property {string} "os_usa" - 海外-美国
* @property {string} "os_euro" - 海外-欧洲
* @property {string} "os_asia" - 海外-亚洲
* @property {string} "os_cht" - 海外-繁体中文
*/
const ServerType = <const>{
/** 国服-官方服 */
CN_GF01: "cn_gf01",
/** 国服-渠道服 */
CN_QD01: "cn_qd01",
/** 国际服-美服 */
OS_USA: "os_usa",
/** 国际服-欧服 */
OS_EURO: "os_euro",
/** 国际服-亚服 */
OS_ASIA: "os_asia",
/** 国际服-港澳台服 */
OS_CHT: "os_cht",
};
/**
* @description 公告服务器类型枚举
* 公告服务器类型枚举
* @since Beta v0.8.0
* @enum ServerTypeEnum
*/
type ServerTypeEnum = (typeof ServerType)[keyof typeof ServerType];
/**
* @description 时间类型
* 时间类型
* @since Beta v0.8.0
* @interface DateTime
* @property {number} year - 年份
* @property {number} month - 月份1-12
* @property {number} day - 日1-31
* @property {number} hour - 小时0-23
* @property {number} minute - 分钟0-59
* @property {number} second - 秒0-59
*/
type DateTime = {
/** 年份 */
year: number;
/** 月份1-12 */
month: number;
/** 日1-31 */
day: number;
/** 小时0-23 */
hour: number;
/** 分钟0-59 */
minute: number;
/** 秒0-59 */
second: number;
};
}

View File

@@ -1,53 +1,59 @@
/**
* @file types/Game/Gacha.d.ts
* @description 游戏抽卡相关类型定义文件
* @since Beta v0.3.0
* @since Beta v0.8.4
*/
/**
* @description 游戏抽卡相关类型定义命名空间
* @since Beta v0.3.0
* @namespace TGApp.Game.Gacha
* @memberof TGApp.Game
*/
declare namespace TGApp.Game.Gacha {
/**
* @description 获取 authkey 返回类型
* @interface AuthkeyResponse
* @since Beta v0.3.0
* @interface AuthkeyResp
* @since Beta v0.8.4
* @extends TGApp.BBS.Response.BaseWithData
* @property {number} data.sign_type - 签名类型
* @property {number} data.authkey_ver - authkey 版本
* @property {string} data.authkey - authkey
* @return AuthkeyResponse
*/
interface AuthkeyResponse extends TGApp.BBS.Response.BaseWithData {
data: {
sign_type: number;
authkey_ver: number;
authkey: string;
};
}
type AuthkeyResp = TGApp.BBS.Response.BaseWithData<AuthkeyRes>;
/**
* @description 获取 authkey 数据类型
* @interface AuthkeyRes
* @since Beta v0.8.4
* @property {number} sign_type - 签名类型
* @property {number} authkey_ver - authkey 版本
* @property {string} authkey - authkey
*/
type AuthkeyRes = { sign_type: number; authkey_ver: number; authkey: string };
/**
* @description 获取抽卡记录返回类型
* @interface GachaLogResponse
* @since Beta v0.3.0
* @interface GachaLogResp
* @since Beta v0.8.4
* @extends TGApp.BBS.Response.BaseWithData
* @property {number} data.page - 页码
* @property {number} data.size - 每页大小
* @property {number} data.total - 总数
* @property {GachaItem[]} data.list - 抽卡记录列表
* @return GachaLogResponse
*/
interface GachaLogResponse extends TGApp.BBS.Response.BaseWithData {
data: {
page: number;
size: number;
total: number;
list: GachaItem[];
};
}
type GachaLogResp = TGApp.BBS.Response.BaseWithData<GachaLogRes>;
/**
* @description 获取千星奇域抽卡记录返回类型
* @interface GachaBLogResp
* @since Beta v0.8.4
* @extends TGApp.BBS.Response.BaseWithData
*/
type GachaBLogResp = TGApp.BBS.Response.BaseWithData<GachaBLogRes>;
/**
* @description 抽卡记录返回数据类型
* @interface GachaLogRes
* @since Beta v0.8.4
* @property {number} page - 页码
* @property {number} size - 每页大小
* @property {number} total - 总数
* @property {Array<GachaItem>} list - 抽卡记录列表
*/
type GachaLogRes = {
page: number;
size: number;
total: number;
list: Array<GachaItem>;
};
/**
* @description 抽卡记录类型
@@ -65,7 +71,7 @@ declare namespace TGApp.Game.Gacha {
* @property {string} id - 抽卡记录 id
* @return GachaItem
*/
interface GachaItem {
type GachaItem = {
uid: string;
gacha_type: string;
item_id: string;
@@ -76,5 +82,44 @@ declare namespace TGApp.Game.Gacha {
item_type: string;
rank_type: string;
id: number;
}
};
/**
* @description 千星奇域抽卡记录返回数据类型
* @interface GachaBLogRes
* @since Beta v0.8.4
* @property {Array<GachaBItem>} list - 页码
* @property {number} total - 总数
*/
type GachaBLogRes = { list: Array<GachaBItem>; total: number };
/**
* @description 千星奇域抽卡记录类型
* @interface GachaBItem
* @since Beta v0.8.4
* @property {string} id - 抽卡记录 id
* @property {string} is_up - 是否为UP池,0-否1-是
* @property {string} item_id - 物品 id
* @property {string} item_name - 物品名称
* @property {string} item_type - 物品类型
* @property {string} op_gacha_type - 抽卡类型,用于接口请求
* @property {string} rank_type - 星级
* @property {string} region - 区域
* @property {string} schedule_id - 排期ID
* @property {string} time - 抽卡时间格式yyyy-MM-dd HH:mm:ss
* @property {string} uid - 用户 uid
*/
type GachaBItem = {
id: string;
is_up: string;
item_id: string;
item_name: string;
item_type: string;
op_gacha_type: string;
rank_type: string;
region: string;
schedule_id: string;
time: string;
uid: string;
};
}

View File

@@ -1,247 +1,246 @@
/**
* @file types/Plugins/JSBridge.d.ts
* @description JSBridge 插件相关类型定义文件
* @since Beta v0.6.0
* JSBridge 插件相关类型定义文件
* @since Beta v0.8.4
*/
/**
* @description JSBridge 插件相关类型命名
* @since Beta v0.6.0
* @namespace TGApp.Plugins.JSBridge
* @memberof TGApp.Plugins
*/
declare namespace TGApp.Plugins.JSBridge {
/**
* @description JSBridge 通用 arg 参数
* JSBridge 通用 arg 参数
* @since Beta v0.3.9
* @interface Arg
* @template T
* @property {string} method - 方法名
* @property {T} payload - 参数
* @property {string} callback - 回调函数名
* @return Arg
*/
interface Arg<T> {
type Arg<T> = {
/** 方法名 */
method: string;
/** 参数 */
payload: T;
/** 回调函数名 */
callback: string;
}
};
/**
* @description 通用 arg 参数-无参数
* 通用 arg 参数-无参数
* @since Beta v0.3.9
* @interface NullArg
* @return NullArg
*/
type NullArg = Arg<null>;
/**
* @description configShare 方法参数
* configShare 方法参数
* @since Beta v0.3.9
* @interface ConfigSharePayload
* @property {boolean} enable - 是否启用分享
* @return ConfigSharePayload
*/
interface ConfigSharePayload {
type ConfigSharePayload = {
/** 是否启用分享 */
enable: boolean;
}
};
/**
* @description eventTrack 方法参数
* eventTrack 方法参数
* @since Beta v0.3.9
* @interface EventTrackPayload
* @property {object} pageInfo - 页面信息
* @property {string} pageInfo.page_path - 页面路径
* @property {string} pageInfo.page_name - 页面名称
* @property {string} pageInfo.sub_page_path - 子页面路径
* @property {string} pageInfo.sub_page_name - 子页面名称
* @property {string} pageInfo.source_path - 来源页面路径
* @property {string} pageInfo.source_name - 来源页面名称
* @property {string} pageInfo.page_id - 页面 ID
* @property {string} pageInfo.page_type - 页面类型
* @property {string} pageInfo.source_id - 来源 ID
* @property {unknown} pageInfo.extra_info - 额外信息
* @property {object} eventInfo - 事件信息
* @property {string} eventInfo.time - 事件时间
* @property {number} eventInfo.action_id - 事件 ID
* @property {string} eventInfo.btn_name - 按钮名称
* @property {string} eventInfo.module_id - 模块 ID
* @property {string} eventInfo.module_name - 模块名称
* @property {unknown} eventInfo.extra_info - 额外信息
* @property {object} commonInfo - 公共信息
* @property {object} commonInfo.extra_info - 额外信息
* @property {string} commonInfo.extra_info.game_id - 游戏 ID
* @property {string} commonInfo.extra_info.view_type - 视图类型
* @return EventTrackPayload
*/
interface EventTrackPayload {
type EventTrackPayload = {
/** 页面信息 */
pageInfo: {
/** 页面路径 */
page_path: string;
/** 页面名称 */
page_name: string;
/** 子页面路径 */
sub_page_path: string;
/** 子页面名称 */
sub_page_name: string;
/** 来源页面路径 */
source_path: string;
/** 来源页面名称 */
source_name: string;
/** 页面 ID */
page_id: string;
/** 页面类型 */
page_type: string;
/** 来源 ID */
source_id: string;
/** 额外信息 */
extra_info: unknown;
};
/** 事件信息 */
eventInfo: {
/** 事件发生时间 */
time: string;
/** 事件 ID */
action_id: number;
/** 按钮名称 */
btn_name: string;
/** 模块 ID */
module_id: string;
/** 模块名称 */
module_name: string;
/** 额外信息 */
extra_info: unknown;
};
/** 公共信息 */
commonInfo: {
/** 额外信息 */
extra_info: {
/** 游戏 ID */
game_id: string;
/** 视图类型 */
view_type: string;
};
};
}
};
/**
* @description getActionTicket 方法参数
* getActionTicket 方法参数
* @since Beta v0.3.9
* @interface GetActionTicketPayload
* @property {string} action_type
* @return GetActionTicketPayload
*/
interface GetActionTicketPayload {
type GetActionTicketPayload = {
/** 行为类型 */
action_type: string;
}
};
/**
* @description genAuthkey 方法参数
* genAuthkey 方法参数
* @since Beta v0.3.9
* @interface GenAuthkeyPayload
* @return GenAuthkeyPayload
*/
type GenAuthkeyPayload = Record<string, string>;
/**
* @description getCookieToken 方法参数
* getCookieToken 方法参数
* @since Beta v0.3.9
* @interface GetCookieTokenPayload
* @property {boolean} forceRefresh - 是否强制刷新
* @return GetCookieTokenPayload
*/
interface GetCookieTokenPayload {
type GetCookieTokenPayload = {
/** 是否强制刷新 */
forceRefresh: boolean;
}
};
/**
* @description getDS2 方法参数
* getDS2 方法参数
* @since Beta v0.3.9
* @interface GetDS2Payload
* @property {Record<string,string|number>|string} query - 查询参数
* @property {Record<string,string|number>|string} body - 请求体
* @return GetDS2Payload
*/
interface GetDS2Payload {
type GetDS2Payload = {
/** 查询参数 */
query: Record<string, string | number> | string;
/** 请求体 */
body: Record<string, string | number> | string;
}
};
/**
* @description onClickImg 方法参数
* onClickImg 方法参数
* @since Beta v0.3.9
* @interface OnClickImgPayload
* @property {Array<object>} image_list - 图片列表
* @property {string} image_list[].url - 图片链接
* @property {string} image_list[].format - 图片格式
* @return OnClickImgPayload
*/
interface OnClickImgPayload {
type OnClickImgPayload = {
/** 图片列表 */
image_list: Array<{
/** 图片链接 */
url: string;
/** 图片格式 */
format: string;
}>;
}
};
/**
* @description openApplication 方法参数
* openApplication 方法参数
* @since Beta v0.3.9
* @interface OpenApplicationPayload
* @property {number} gameCenterId - 游戏中心对应 id
* @return OpenApplicationPayload
*/
interface OpenApplicationPayload {
type OpenApplicationPayload = {
/** 游戏中心对应 id */
gameCenterId: number;
}
};
/**
* @description 打开系统浏览器
* openSystemBrowser 方法参数
* @since Beta v0.6.0
* @interface OpenSystemBrowserPayload
* @property {string} open_url - 打开的链接
* @return OpenSystemBrowserPayload
*/
interface OpenSystemBrowserPayload {
type OpenSystemBrowserPayload = {
/** 打开的链接 */
open_url: string;
}
};
/**
* @description pushPage 方法参数
* pushPage 方法参数
* @since Beta v0.3.9
* @interface PushPagePayload
* @property {string} page - 页面地址
* @return PushPagePayload
*/
interface PushPagePayload {
type PushPagePayload = {
/** 页面地址 */
page: string;
}
};
/**
* @description setPresentationStyle 方法参数
* setPresentationStyle 方法参数
* @since Beta v0.3.9
* @interface SetPresentationStylePayload
* @property {string} style - 窗口样式
* @property {unknown} navigationBar - 导航栏
* @property {string} statusBar.style - 状态栏模式
* @return SetPresentationStylePayload
*/
interface SetPresentationStylePayload {
type SetPresentationStylePayload = {
/** 窗口样式 */
style: string;
/** 导航栏 */
navigationBar: unknown;
/** 状态栏模式 */
statusBar: {
/** 状态栏模式 */
style: string;
};
}
};
/**
* @description share 方法参数
* share 方法参数
* @since Beta v0.3.9
* @interface SharePayload
* @property {string} type - 分享类型 // screenshot
* @property {object} content - 分享内容
* @property {boolean} content?.preview - 是否预览
* @return SharePayload
*/
type SharePayload =
| {
type: "default";
content: {
title: string;
description: string;
link: string;
image_url: string;
};
}
| {
type: "screenshot";
content: {
preview: boolean;
};
}
| {
type: "image";
content: {
image_url?: string;
image_base64?: string;
};
};
type SharePayload = SharePayloadDefault | SharePayloadScreenshot | SharePayloadImage;
/**
* share 方法参数-默认分享
* @since Beta v0.8.4
*/
type SharePayloadDefault = {
/** 分享类型,默认值"default" */
type: "default";
/** 分享内容 */
content: {
/** 标题 */
title: string;
/** 描述 */
description: string;
/** 链接 */
link: string;
/** 图片链接 */
image_url: string;
};
};
/**
* share 方法参数-截图分享
* @since Beta v0.8.4
*/
type SharePayloadScreenshot = {
/** 分享类型,值为"screenshot" */
type: "screenshot";
/** 分享内容 */
content: {
/** 是否预览 */
preview: boolean;
};
};
/**
* share 方法参数-图片分享
* @since Beta v0.8.4
*/
type SharePayloadImage = {
/** 分享类型,值为"image" */
type: "image";
/** 分享内容 */
content: {
/** 图片链接(可选) */
image_url?: string;
/** 图片的 Base64 编码(可选) */
image_base64?: string;
};
};
/**
* getRegionRoleInfo 方法参数
* @since Beta v0.8.4
*/
type GetRegionRoleInfoPayload = {
/** 游戏 biz 标识 */
game_biz: string;
};
}

View File

@@ -1,161 +1,165 @@
/**
* @file types/Plugins/UIGF.d.ts
* @description UIGF 插件类型定义文件
* @since Beta v0.5.1
* @version UIGF v3.0 | UIGF v4.0
* UIGF 标准类型定义文件
* @since Beta v0.8.4
* @version UIGF v3.0 | UIGF v4.1
*/
declare namespace TGApp.Plugins.UIGF {
/**
* @description UIGF 数据
* UIGF 数据
* @since Beta v0.5.0
* @interface Schema
* @property {Info} info - UIGF 头部信息
* @property {GachaItem[]} list - UIGF 祈愿列表
* @return Schema
*/
interface Schema {
info: Info;
list: GachaItem[];
}
/**
* @description UIGF 数据, v4.0
* @since Beta v0.5.0
* @interface Schema4
* @property {Info4} info - UIGF 头部信息
* @property {GachaHk4e[]} hk4e - UIGF 祈愿列表,原神数据
* @return Schema4
*/
interface Schema4 {
info: Info4;
hk4e: GachaHk4e[];
}
/**
* @description UIGF 头部信息
* @since Beta v0.5.0
* @interface Info
* @see docs\UIGF.md
* @property {string} uid - UID
* @property {string} lang - 语言
* @property {string} uigf_version - UIGF 版本,应用使用的是 2.3.0
* @property {number} export_timestamp - 导出时间戳(秒)
* @property {string} export_time - 导出时间 yyyy-MM-dd HH:mm:ss
* @property {string} export_app - 导出应用
* @property {string} export_app_version - 导出应用版本
* @property {number} region_time_zone - 时区
* @return Info
*/
interface Info {
uid: string;
lang: string;
uigf_version: string;
export_timestamp?: number;
export_time?: string;
export_app?: string;
export_app_version?: string;
region_time_zone?: number;
}
/**
* @description UIGF 头部信息, v4.0
* @since Beta v0.5.1
* @interface Info4
* @see docs\UIGF4.md
* @property {string} export_timestamp - 导出时间戳(秒)
* @property {string} export_app - 导出应用
* @property {string} export_app_version - 导出应用版本
* @property {string} version - UIGF 版本
* @property {string} lang - 语言
* @return Info4
*/
interface Info4 {
export_timestamp: string;
export_app: string;
export_app_version: string;
version: string;
lang?: string;
}
/**
* @description 祈愿类型
* @since Alpha v0.2.3
* @enum EnumGachaType
* @property {string} CharacterUp - 角色活动祈愿
* @property {string} CharacterUp2 - 角色活动祈愿2
* @property {string} WeaponUp - 武器活动祈愿
* @property {string} Normal - 普通祈愿
* @property {string} Newbie - 新手祈愿
* @return EnumGachaType
*/
enum EnumGachaType {
CharacterUp = "301",
CharacterUp2 = "400",
WeaponUp = "302",
Normal = "200",
Newbie = "100",
}
/**
* @description UIGF 祈愿类型
* @since Alpha v0.2.3
* @enum EnumUigfGachaType
* @property {string} CharacterUp - 角色活动祈愿&角色活动祈愿2
* @property {string} WeaponUp - 武器活动祈愿
* @property {string} Normal - 普通祈愿
* @property {string} Newbie - 新手祈愿
* @return EnumUigfGachaType
*/
enum EnumUigfGachaType {
CharacterUp = "301",
WeaponUp = "302",
Normal = "200",
Newbie = "100",
}
/**
* @description UIGF 祈愿列表
* @Beta v0.5.1
* @version UIGF v3.0
* @interface GachaItem
* @property {EnumGachaType} gacha_type - 祈愿类型
* @property {string} item_id - 物品ID
* @property {string} count - 数量
* @property {string} time - 时间 yyyy-MM-dd HH:mm:ss
* @property {string} name - 名称
* @property {string} item_type - 物品类型
* @property {string} rank_type - 稀有度
* @property {string} id - ID
* @property {EnumUigfGachaType} uigf_gacha_type - UIGF 祈愿类型
* @return GachaItem
*/
interface GachaItem {
uigf_gacha_type: string;
gacha_type: string;
item_id: string;
count?: string;
time: string;
name: string;
item_type?: string;
rank_type?: string;
id: string;
}
type Schema = {
/** 头部信息 */
info: Info;
/** 祈愿列表 */
list: Array<GachaItem>;
};
/**
* @description UIGF 祈愿列表 v4.0,原神数据
* @since Beta v0.5.0
* @interface GachaHk4e
* @property {string|number} uid - UID
* @property {number} timezone - 时区
* @property {string} lang - 语言
* @property {GachaItem[]} list - 祈愿列表
* @return GachaHk4e
* UIGF 数据 v4.0
* @since Beta v0.8.4
*/
interface GachaHk4e {
uid: string | number;
timezone: number;
type Schema4 = {
/** 头部信息 */
info: Info4;
/** 祈愿列表,原神数据 */
hk4e: Array<GachaHk4e>;
/** 祈愿列表,千星奇域数据 */
hk4e_ugc?: Array<GachaUgc>;
};
/**
* UIGF 头部信息
* @since Beta v0.5.0
* @version UIGF v3.0
*/
type Info = {
/** UID */
uid: string;
/** 语言 */
lang: string;
/** UIGF 版本,应用使用的是 3.0 */
uigf_version: string;
/** 导出时间戳(秒) */
export_timestamp?: number;
/** 导出时间 yyyy-MM-dd HH:mm:ss */
export_time?: string;
/** 导出应用 */
export_app?: string;
/** 导出应用版本 */
export_app_version?: string;
/** 时区 */
region_time_zone?: number;
};
/**
* UIGF 头部信息
* @since Beta v0.5.1
* @version v4.0+
*/
type Info4 = {
/** 导出时间戳(秒) */
export_timestamp: string;
/** 导出应用 */
export_app: string;
/** 导出应用版本 */
export_app_version: string;
/** UIGF 版本 */
version: string;
/** 语言 */
lang?: string;
list: GachaItem[];
}
};
/**
* UIGF4 祈愿项,原神
* @since Beta v0.5.0
*/
type GachaHk4e = {
/** UID */
uid: string | number;
/** 时区 */
timezone: number;
/** 语言 */
lang?: string;
list: Array<GachaItem>;
};
/**
* UIGF4 祈愿项,千星奇域
* @since Beta v0.8.4
* @remarks 该标准尚未最终确定
*/
type GachaUgc = {
/** UID */
uid: string | number;
/** 时区 */
timezone: number;
/** 语言 */
lang?: string;
/** 服务器区域 */
region: string;
/** 祈愿列表 */
list: Array<GachaItemB>;
};
/**
* UIGF 祈愿项-原神
* @since Beta v0.5.1
* @version UIGF v3.0
*/
type GachaItem = {
/** UIGF 祈愿类型 */
uigf_gacha_type: string;
/** 祈愿类型 */
gacha_type: string;
/** 物品ID */
item_id: string;
/** 数量 */
count?: string;
/** 时间 yyyy-MM-dd HH:mm:ss */
time: string;
/** 名称 */
name: string;
/** 物品类型 */
item_type?: string;
/** 稀有度 */
rank_type?: string;
/** ID */
id: string;
};
/**
* UIGF 祈愿项-千星奇域
* @since Beta v0.8.4
* @remarks 该标准尚未最终确定
*/
type GachaItemB = {
/** id */
id: string;
/** 排期id */
schedule_id: string;
/** 物品类型 */
item_type: string;
/** 物品id */
item_id: string;
/** 名称 */
item_name: string;
/** 稀有度 */
rank_type: string;
/** 是否限定 */
is_up: string;
/** 时间 yyyy-MM-dd HH:mm:ss */
time: string;
/**
* 祈愿类型
* @remarks
* 1000-常驻池
* 2000-活动池
*/
gacha_type: string;
/** 祈愿类型,用于接口请求 */
op_gacha_type: string;
};
}

View File

@@ -1,39 +1,108 @@
/**
* @file types Sqlite GachaRecords.d.ts
* @description 数据库抽卡记录相关类型定义文件
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.3
* 数据库抽卡记录相关类型定义文件
* @since Beta v0.8.4
*/
declare namespace TGApp.Sqlite.GachaRecords {
/**
* @description 数据库-抽卡记录表
* 原神抽卡记录表类型定义
* @since Alpha v0.2.3
* @interface SingleTable
* @property {string} id - 抽卡记录 ID
* @property {string} uid - UID
* @property {string} gachaType - 抽卡类型
* @property {string} uigfType - UIGF 类型
* @property {string} time - 抽卡时间
* @property {string} itemId - 抽卡物品 ID
* @property {string} name - 抽卡物品名称
* @property {string} type - 抽卡物品类型
* @property {string} rank - 抽卡物品星级
* @property {string} count - 抽卡物品数量
* @property {string} updated - 数据库更新时间
* @return SingleTable
*/
interface SingleTable {
type TableGacha = {
/** 抽卡记录 ID */
id: string;
/** UID */
uid: string;
/** 抽卡类型 */
gachaType: string;
/** UIGF 类型 */
uigfType: string;
/**
* 抽卡时间
* @remarks
* 从接口获取的数据均为 UTC+8 时间
* 从外部导入数据也转换为 UTC+8 时间
*/
time: string;
/** 抽卡物品 ID */
itemId: string;
/**
* 抽卡物品名称
* @remarks
* 从接口获取的数据均为中文名称
* 从外部导入数据从本地字典中获取中文名称
*/
name: string;
/** 抽卡物品类型 */
type: string;
/** 抽卡物品星级 */
rank: string;
/**
* 抽卡物品数量
* @remarks 恒为 "1"
*/
count: string;
/** 数据库更新时间 */
updated: string;
}
};
/**
* 千星奇域抽卡记录表类型定义
* @since Beta v0.8.4
*/
type TableGachaB = {
/** 抽卡记录 ID */
id: string;
/** UID */
uid: string;
/** 服务器区域 */
region: string;
/** 排期 ID */
scheduleId: string;
/**
* 抽卡类型
* @remarks
* 1000-常驻池
* 2000-活动池
* 20011-男活动池
* 20012-男活动池2
* 20021-女活动池
* 20022-女活动池2
*/
gachaType: string;
/**
* 抽卡类型(接口用)
* @remarks
* 100-常驻池
* 200-活动池
*/
opGachaType: string;
/** 抽卡时间 */
time: string;
/** 抽卡物品 ID */
itemId: string;
/**
* 抽卡物品名称
* @remarks
* 从接口获取到的为中文名称
* 从外部导入数据需要转换为中文名称
*/
name: string;
/**
* 抽卡物品类型
* @remarks
* 从接口获取到的为中文名称
* 从外部导入数据需要转换为中文名称
*/
type: string;
/** 抽卡物品星级 */
rank: string;
/**
* 是否是 UP 物品
* @remarks 0-否1-是
*/
isUp: string;
/** 数据库更新时间 */
updated: string;
};
}

View File

@@ -1,27 +1,26 @@
/**
* @file utils/TGBbs.ts
* @description 关于 BBS 的工具函数
* @since Beta v0.8.0
* 关于 BBS 的工具函数
* @since Beta v0.8.4
*/
/**
* @description salt 类型
* @since Beta v0.7.3
* salt 类型
* @since Beta v0.8.4
*/
export type SaltKey = "K2" | "LK2" | "X4" | "X6" | "PROD";
const BBS_VERSION: Readonly<string> = "2.93.0";
const BBS_VERSION: Readonly<string> = "2.95.1";
const BBS_UA_MOBILE: Readonly<string> = `Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/${BBS_VERSION}`;
const BBS_UA_PC: Readonly<string> = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/${BBS_VERSION}`;
/**
* @description salt 值
* @version 2.93.0
* @since Beta v0.8.0
* salt 值
* @version 2.95.1
* @since Beta v0.8.4
*/
const BBS_SALT: Readonly<Record<SaltKey, string>> = {
K2: "idMMaGYmVgPzh3wxmWudUXKUPGidO7GM",
LK2: "G1ktdwFL4IyGkHuuWSmz0wUe9Db9scyK",
K2: "sfYPEgpxkOe1I3XVMLdwp1Lyt9ORgZsq",
LK2: "sidQFEglajEz7FA0Aj7HQPV88zpf17SO",
X4: "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
X6: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
PROD: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",

View File

@@ -1,6 +1,5 @@
/**
* @file utils/TGClient.ts
* @desc 负责米游社客户端的 callback 处理
* 负责米游社客户端的 callback 处理
* @since Beta v0.8.3
*/
@@ -23,9 +22,6 @@ import TGLogger from "./TGLogger.js";
import { createPost } from "./TGWindow.js";
import { getDeviceInfo } from "./toolFunc.js";
// invoke 参数
type InvokeArg = { func: string };
class Client {
private listener: UnlistenFn | undefined;
private route: string[] = [];
@@ -43,10 +39,9 @@ class Client {
}
/**
* @func run
* 运行米游社客户端
* @since Beta v0.3.4
* @desc 运行米游社客户端
* @returns {void} - 无返回值
* @returns Promise<void>
*/
async run(): Promise<void> {
if (this.listener === undefined) {
@@ -58,15 +53,12 @@ class Client {
}
}
/* 内置函数 */
/**
* @func callback
* 回调函数
* @since Beta v0.5.0
* @desc 回调函数
* @param {string} callback - 回调函数名
* @param {object} data - 回调数据
* @returns {void} - 无返回值
* @returns Promise<void>
*/
async callback(callback: string, data: object): Promise<void> {
const response = { retcode: 0, message: "success", data: data ?? {} };
@@ -76,12 +68,11 @@ class Client {
}
/**
* @func getSaveImgJS
* 获取保存图片的 JS
* @since Beta v0.5.1
* @desc 获取保存图片的 JS
* @param {string} url - 图片链接
* @param {string} format - 图片格式
* @returns {string} - JS
* @returns JS代码字符串
*/
getSaveImgJS(url: string, format: string): string {
return `javascript:(async function() {
@@ -104,9 +95,8 @@ class Client {
}
/**
* @func getUrl
* 获取 url
* @since Beta v0.5.0
* @desc 获取 url
* @param {string} func - 方法名
* @returns {string} - url
*/
@@ -130,9 +120,8 @@ class Client {
}
/**
* @func handleCallback
* 处理米游社客户端的 callback
* @since Beta v0.6.1
* @desc 处理米游社客户端的 callback
* @param {Event<string>} arg - 事件参数
* @returns {Promise<void>} - 返回值
*/
@@ -162,14 +151,16 @@ class Client {
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.EventTrackPayload>>argParse,
);
break;
case "getStatusBarHeight":
await this.getStatusBarHeight(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
case "genAuthKey":
await this.genAuthKey(
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GenAuthkeyPayload>>argParse,
);
break;
case "getActionTicket":
await this.getActionTicket(
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetActionTicketPayload>>argParse,
);
break;
case "getCookieInfo":
await this.getCookieInfo(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
@@ -178,14 +169,6 @@ class Client {
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetCookieTokenPayload>>argParse,
);
break;
case "getActionTicket":
await this.getActionTicket(
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetActionTicketPayload>>argParse,
);
break;
case "getHTTPRequestHeaders":
await this.getHTTPRequestHeaders(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
case "getDS":
await this.getDS(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
@@ -194,6 +177,17 @@ class Client {
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetDS2Payload>>argParse,
);
break;
case "getHTTPRequestHeaders":
await this.getHTTPRequestHeaders(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
case "getRegionRoleInfo":
await this.getRegionRoleInfo(
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetRegionRoleInfoPayload>>argParse,
);
break;
case "getStatusBarHeight":
await this.getStatusBarHeight(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
case "getUserInfo":
await this.getUserInfo(<TGApp.Plugins.JSBridge.NullArg>argParse);
break;
@@ -437,7 +431,7 @@ class Client {
await TGLogger.Error(`[TGClient][open] ${e}`);
}
}
await core.invoke<InvokeArg>("create_mhy_client", { func, url });
await core.invoke("create_mhy_client", { func, url });
await this.loadJSBridge();
}
@@ -657,6 +651,26 @@ class Client {
await this.callback(arg.callback, data);
}
/**
* 获取对应区服的角色信息
* @since Beta v0.8.4
* @param {TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetRegionRoleInfoPayload>} arg - 方法参数
* @returns {Promise<void>} - 无返回值
*/
async getRegionRoleInfo(
arg: TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetRegionRoleInfoPayload>,
): Promise<void> {
const user = useUserStore();
const data = {
region: user.account.region,
game_uid: user.account.gameUid,
nickname: user.account.nickname,
user_label: user.account.nickname,
region_name: user.account.regionName,
};
await this.callback(arg.callback, data);
}
/**
* @func getUserInfo
* @since Beta v0.3.9

View File

@@ -82,12 +82,12 @@ export async function getUigf4Header(): Promise<TGApp.Plugins.UIGF.Info4> {
/**
* @description 数据转换-数据库到 UIGF
* @since Beta v0.7.5
* @param {TGApp.Sqlite.GachaRecords.SingleTable[]} data - 数据库数据
* @param {TGApp.Sqlite.GachaRecords.TableGacha[]} data - 数据库数据
* @param {number} timezone - 时区
* @returns {TGApp.Plugins.UIGF.GachaItem[]} UIGF 数据
*/
function convertDataToUigf(
data: TGApp.Sqlite.GachaRecords.SingleTable[],
data: TGApp.Sqlite.GachaRecords.TableGacha[],
timezone: number,
): TGApp.Plugins.UIGF.GachaItem[] {
return data.map((gacha) => {
@@ -191,13 +191,13 @@ export async function readUigf4Data(userPath: string): Promise<TGApp.Plugins.UIG
* @description 导出 UIGF 数据
* @since Beta v0.7.5
* @param {string} uid - UID
* @param {TGApp.Sqlite.GachaRecords.SingleTable[]} gachaList - 祈愿列表
* @param {TGApp.Sqlite.GachaRecords.TableGacha[]} gachaList - 祈愿列表
* @param {string} savePath - 保存路径
* @returns {Promise<void>}
*/
export async function exportUigfData(
uid: string,
gachaList: TGApp.Sqlite.GachaRecords.SingleTable[],
gachaList: TGApp.Sqlite.GachaRecords.TableGacha[],
savePath?: string,
): Promise<void> {
const timezone = getUigfTimeZone(uid);

View File

@@ -1,7 +1,6 @@
/**
* @file src/utils/annoParser.ts
* @description 解析游戏内公告数据
* @since Beta v0.7.6
* 解析游戏内公告数据
* @since Beta v0.8.4
*/
import TpText from "@comp/viewPost/tp-text.vue";
@@ -38,18 +37,22 @@ function handleAnnoContent(data: string): string {
}
/**
* @description 解析公告内容,转换为结构化数据
* @since Beta v0.5.3
* 解析公告内容,转换为结构化数据
* @since Beta v0.8.4
* @param {TGApp.BBS.Announcement.AnnoDetail} anno - 公告内容
* @returns {TGApp.BBS.SctPost.Base[]} 结构化数据
* @returns {Array<TGApp.BBS.SctPost.Base>} 结构化数据
*/
function parseAnnoContent(anno: TGApp.BBS.Announcement.AnnoDetail): Array<TGApp.BBS.SctPost.Base> {
const parser = new DOMParser();
const first = handleAnnoContent(anno.content);
const doc = parser.parseFromString(first, "text/html");
const children: Array<TGApp.BBS.SctPost.Base> = [];
let children: Array<TGApp.BBS.SctPost.Base> = [];
if (anno.banner !== "") children.push({ insert: { image: anno.banner } });
doc.body.childNodes.forEach((child) => children.push(...parseAnnoNode(child)));
// 剔除 attributes 为空且内容为 "\n\n" 的节点
children = children.filter((child) => {
return !(child.attributes === undefined && child.insert === "\n\n");
});
return children;
}

View File

@@ -1,7 +1,6 @@
/**
* @file src/utils/linkParser.ts
* @description 处理链接
* @since Beta v0.7.3
* 处理链接
* @since Beta v0.8.4
*/
import showDialog from "@comp/func/dialog.js";
@@ -12,9 +11,8 @@ import TGClient from "./TGClient.js";
import { createPost } from "./TGWindow.js";
/**
* @function parsePost
* 处理帖子链接
* @since Beta v0.6.9
* @description 处理帖子
* @param {string} link
* @returns {Promise<false|string>} - 处理情况,或者转换后的链接
*/
@@ -54,9 +52,8 @@ export async function parsePost(link: string): Promise<false | string> {
}
/**
* @function parseLink
* 处理链接
* @since Beta v0.7.2
* @description 处理链接
* @param {string} link - 链接
* @param {boolean} useInner - 是否采用内置 JSBridge 打开
* @returns {Promise<boolean|string>} - 处理情况,或者转换后的链接
@@ -144,6 +141,7 @@ export async function parseLink(
const prefix = [
"m.miyoushe.com",
"act.mihoyo.com",
"act.miyoushe.com",
"ys.mihoyo.com",
"mhyurl.cn",
"webstatic.mihoyo.com",

View File

@@ -1,7 +1,5 @@
/**
* @file utils/toolFunc.ts
* @description 一些工具函数
* @since Beta v0.8.2
* 一些工具函数
*/
import { AvatarExtResTypeEnum, AvatarExtTypeEnum } from "@enum/bbs.js";
@@ -122,14 +120,14 @@ export function bytesToSize(bytes: number): string {
}
/**
* @description 获取缓存目录
* @since Beta v0.5.0
* @returns {string|string[]} 缓存目录
* 获取缓存目录
* @returns {Array<string>|false} 缓存目录
*/
export async function getCacheDir(): Promise<string[] | false> {
export async function getCacheDir(): Promise<Array<string> | false> {
const cacheDir = await path.appCacheDir();
const osType = type().toLowerCase();
if (osType === "windows") {
// TODO: 会报错显示占用
const cache = `${cacheDir}${path.sep()}EBWebview${path.sep()}Default${path.sep()}Cache`;
const codeCache = `${cacheDir}${path.sep()}EBWebview${path.sep()}Default${path.sep()}Code Cache`;
return [cache, codeCache];

View File

@@ -1,3 +1,4 @@
<!-- 公告详情页面,TODO:联动消除红点 -->
<template>
<TSwitchTheme />
<TPinWin />

View File

@@ -9,6 +9,9 @@
</template>
<template #text>
<div class="tpj-box">
<span class="tpj-box-copy" @click="copyContent(jsonData)" title="复制全部内容到剪贴板">
<v-icon small>mdi-content-copy</v-icon>
</span>
<vue-json-pretty
:data="JSON.parse(JSON.stringify(jsonData))"
:show-icon="true"
@@ -30,6 +33,9 @@
</template>
<template #text>
<div class="tpj-box">
<span class="tpj-box-copy" @click="copyContent(parseData)" title="复制全部内容到剪贴板">
<v-icon small>mdi-content-copy</v-icon>
</span>
<vue-json-pretty
:data="parseData"
:show-icon="true"
@@ -101,6 +107,16 @@ onMounted(async () => {
}
await showLoading.end();
});
async function copyContent(jsonData: unknown): Promise<void> {
try {
await navigator.clipboard.writeText(JSON.stringify(jsonData, null, 2));
showSnackbar.success("已复制帖子返回内容 JSON 到剪贴板");
} catch (err) {
showSnackbar.error("复制失败,请稍后重试");
await TGLogger.Error(`[${postId}]复制帖子返回内容 JSON 失败:${err}`);
}
}
</script>
<style lang="scss" scoped>
.tpj-page {
@@ -125,4 +141,20 @@ onMounted(async () => {
border-radius: 4px;
background: var(--box-bg-1);
}
.tpj-box-copy {
position: absolute;
z-index: 1;
top: 8px;
right: 8px;
display: flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
border-radius: 4px;
color: var(--common-text-title);
cursor: pointer;
transition: background 0.2s;
}
</style>

View File

@@ -33,6 +33,7 @@
<v-icon size="16">mdi-comment</v-icon>
<span>{{ postData?.stat?.reply_num }}</span>
</div>
<!-- TODO: 展示不同种类点赞图标&数量 -->
<div
class="mpm-item"
:title="`点赞数:${postData?.stat?.like_num}`"
@@ -62,6 +63,15 @@
</div>
<!-- 一些附加信息比如 topiccollection -->
<div class="tp-post-extra">
<div
class="tp-post-contribution"
v-if="postData.contribution_act"
:title="`投稿活动:${postData.contribution_act.title}`"
@click="toAct()"
>
<v-icon size="10">mdi-party-popper</v-icon>
<span>{{ postData.contribution_act.title }}</span>
</div>
<div
class="tp-post-collection"
:title="`合集ID${postData.collection.collection_id}`"
@@ -98,6 +108,10 @@
<v-icon size="16">mdi-copyright</v-icon>
<span>{{ getRepublishAuthorization(postData.post.republish_authorization) }}</span>
</span>
<span v-if="postData.post.post_extra?.minos_aigc_info?.is_aigc" class="tp-post-aigc">
<v-icon size="16">mdi-robot</v-icon>
<span>疑似含AI生成内容请谨慎甄别</span>
</span>
</div>
<TpParser v-model:data="renderPost" />
</div>
@@ -123,6 +137,7 @@ import VpBtnCollect from "@comp/viewPost/vp-btn-collect.vue";
import VpBtnReply from "@comp/viewPost/vp-btn-reply.vue";
import VpOverlayCollection from "@comp/viewPost/vp-overlay-collection.vue";
import VpOverlayUser from "@comp/viewPost/vp-overlay-user.vue";
import { PostViewTypeEnum } from "@enum/bbs.js";
import apiHubReq from "@req/apiHubReq.js";
import postReq from "@req/postReq.js";
import useAppStore from "@store/app.js";
@@ -130,6 +145,8 @@ import useBBSStore from "@store/bbs.js";
import useUserStore from "@store/user.js";
import { app, webviewWindow } from "@tauri-apps/api";
import { emit, type Event, listen, type UnlistenFn } from "@tauri-apps/api/event";
import { openUrl } from "@tauri-apps/plugin-opener";
import { parseLink, parsePost } from "@utils/linkParser.js";
import TGClient from "@utils/TGClient.js";
import TGLogger from "@utils/TGLogger.js";
import { createTGWindow } from "@utils/TGWindow.js";
@@ -260,13 +277,25 @@ function getRepublishAuthorization(type: number): string {
async function getRenderPost(
data: TGApp.BBS.Post.FullData,
): Promise<Array<TGApp.BBS.SctPost.Base>> {
if (
data.post.view_type === PostViewTypeEnum.NORMAL ||
data.post.view_type === PostViewTypeEnum.VOD
) {
return JSON.parse(data.post.structured_content);
}
if (data.post.view_type === PostViewTypeEnum.PIC) {
return await parsePostPic(data);
}
if (data.post.view_type === PostViewTypeEnum.UGC) {
return parsePostUgc(data.post);
}
const postContent = data.post.content;
let jsonParse: string;
if (postContent.startsWith("<")) {
jsonParse = data.post.structured_content;
} else {
try {
jsonParse = await parseContent(data);
jsonParse = JSON.stringify(await parsePostPic(data));
} catch (e) {
if (e instanceof SyntaxError) {
await TGLogger.Warn(`[t-post][${postId}] ${e.name}: ${e.message}`);
@@ -274,12 +303,16 @@ async function getRenderPost(
jsonParse = data.post.structured_content;
}
}
return JSON.parse(jsonParse);
const res = JSON.parse(jsonParse);
if (!Array.isArray(res) && !res.insert) return [res];
return res;
}
async function parseContent(fullData: TGApp.BBS.Post.FullData): Promise<string> {
async function parsePostPic(
fullData: TGApp.BBS.Post.FullData,
): Promise<Array<TGApp.BBS.SctPost.Base>> {
const content = fullData.post.content;
const data: TGApp.BBS.SctPost.Other = JSON.parse(content);
const data: TGApp.BBS.SctPost.Pic = JSON.parse(content);
const result: TGApp.BBS.SctPost.Base[] = [];
for (const key of Object.keys(data)) {
switch (key) {
@@ -311,7 +344,35 @@ async function parseContent(fullData: TGApp.BBS.Post.FullData): Promise<string>
break;
}
}
return JSON.stringify(result);
return result;
}
function parsePostUgc(post: TGApp.BBS.Post.Post): Array<TGApp.BBS.SctPost.Base> {
const data: TGApp.BBS.SctPost.Ugc = JSON.parse(post.structured_content);
const result: Array<TGApp.BBS.SctPost.Base> = [];
if (Array.isArray(data.text)) {
for (const text of data.text) {
result.push(text);
}
// 手动添加换行以对齐解析逻辑
if (data.text.length > 0) result.push({ insert: "\n" });
}
if (Array.isArray(data.images)) {
for (const image of data.images) {
result.push({ insert: { image: image.image } });
}
}
if (Array.isArray(data.vods)) {
for (const vod of data.vods) {
result.push({ insert: { vod: vod.vod } });
}
}
if (Array.isArray(data.levels)) {
for (const level of data.levels) {
result.push({ insert: { level: level.level } });
}
}
return result;
}
async function createPostJson(postId: number): Promise<void> {
@@ -365,6 +426,23 @@ async function toForum(forum: TGApp.BBS.Post.Forum): Promise<void> {
await emit("active_deep_link", `router?path=/posts/forum/${forum.game_id}/${forum.id}`);
}
async function toAct(): Promise<void> {
if (!postData.value || !postData.value.external_link) return;
const link = postData.value.external_link.external_link;
const isPost = await parsePost(link);
if (isPost !== false) {
location.href = `/post_detail/${isPost}`;
return;
}
const res = await parseLink(link);
if (res === true) return;
if (res === false) {
showSnackbar.error(`未知链接:${link}`, 3000);
return;
}
await openUrl(res);
}
function handleUser(user: TGApp.BBS.Post.User): void {
curUid.value = user.uid;
if (showCollection.value) showCollection.value = false;
@@ -416,7 +494,8 @@ function handleUser(user: TGApp.BBS.Post.User): void {
opacity: 0.8;
}
.tp-post-copyright {
.tp-post-copyright,
.tp-post-aigc {
display: flex;
align-items: center;
justify-content: center;
@@ -517,6 +596,26 @@ function handleUser(user: TGApp.BBS.Post.User): void {
gap: 8px 4px;
}
.tp-post-contribution {
@include github-styles.github-tag-dark-gen(#e06c75);
display: flex;
height: 20px;
box-sizing: border-box;
align-items: center;
justify-content: center;
padding: 0 6px;
border-radius: 4px;
column-gap: 2px;
cursor: pointer;
font-family: var(--font-title);
font-size: 12px;
&:hover {
@include github-styles.github-tag-dark-gen(#c678dd);
}
}
.tp-post-collection {
@include github-styles.github-tag-dark-gen(#3572a5);