Compare commits

..

70 Commits

Author SHA1 Message Date
BTMuli
2a30d89a63 🚀 v0.3.9 2024-01-02 01:56:19 +08:00
BTMuli
866b908cbb 添加部分链接 2024-01-02 01:53:04 +08:00
BTMuli
e5c3d10991 添加移除遮罩子菜单 2024-01-02 00:58:58 +08:00
BTMuli
7674407b71 🐛 增加数据库为空处理,优化为空的提醒样式
fix #75
2024-01-02 00:39:12 +08:00
BTMuli
39135525cb ️ 优化深渊页面
*侧边栏添加滚动条
*深渊 star icon清晰度调整
2024-01-01 23:18:59 +08:00
BTMuli
1cbc840b05 📝 更新首页 icon 说明 2024-01-01 00:19:51 +08:00
BTMuli
2b29a94cad 🔥 移除帖子的 loadmore,刷新已经够用 2024-01-01 00:13:05 +08:00
BTMuli
508d119a81 🚨 修复 qodana 报错 2023-12-31 23:42:15 +08:00
BTMuli
4a48d490a8 ️ 更新时间移至hint,添加分享时间 2023-12-31 17:34:00 +08:00
BTMuli
3fa035f182 ️ 调整范围,优化 hint 2023-12-31 16:49:59 +08:00
BTMuli
47fce2fbae 命座天赋 icon 本地化 2023-12-30 22:13:30 +08:00
BTMuli
1b9f1cd51f ️ 点击标题打开子窗口 2023-12-30 20:42:35 +08:00
BTMuli
bb04550e24 添加模拟触摸,将鼠标事件转为触摸事件 2023-12-30 17:51:55 +08:00
BTMuli
c051da80bc 🔧 尝试 qodana 插件 2023-12-29 23:12:13 +08:00
BTMuli
d956b0e05d 🚨 修复 qodana 报错 2023-12-29 23:05:26 +08:00
BTMuli
147719ad36 ♻️ 对于部分点击外部事件进行特殊处理 2023-12-29 23:02:01 +08:00
BTMuli
65e3fd2019 ♻️ 对 confirm 组件事件划分更为精细 2023-12-29 22:57:23 +08:00
BTMuli
a51af0328a 🐛 修复播放数量 undefined 2023-12-29 22:18:07 +08:00
BTMuli
7a9976f4ec 🎨 微改 2023-12-29 17:06:15 +08:00
BTMuli
7cd87895af 🐛 修复 JSBridge 加载错误
fix #73
2023-12-29 17:04:45 +08:00
BTMuli
d50cb960f8 ♻️ 完善扫码登录
*修复暗色模式无法识别二维码的问题
*扫码成功后自动刷新数据
*清除无用代码
2023-12-29 15:48:14 +08:00
BTMuli
17ca9fbde8 💄 调整样式 2023-12-29 15:25:29 +08:00
BTMuli
7188791ed7 名片支持 share 2023-12-29 15:03:01 +08:00
BTMuli
d0fff017cf 💡 整理 todo 2023-12-29 14:56:08 +08:00
BTMuli
b1496f1232 ♻️ 仅从数据库中获取名片名称,弃用 NameCard Table 2023-12-29 01:12:41 +08:00
BTMuli
5d9cce1079 ️ 更换游戏时若存在相同分区不重置分区索引 2023-12-29 00:53:00 +08:00
BTMuli
b3133a1041 🔧 eslint 忽略数据 json 2023-12-29 00:46:52 +08:00
BTMuli
900dd577e5 💄 修改名片图鉴 icon 2023-12-28 17:05:06 +08:00
BTMuli
bc10f5f2d7 支持查询 2023-12-28 16:48:14 +08:00
BTMuli
5cb7325b81 ️ 调整类型说明 2023-12-28 16:36:01 +08:00
BTMuli
416c914dc9 🍱 更新重新排序后的名片数据 2023-12-28 16:23:43 +08:00
BTMuli
9978071784 ✏️ 添加 index 用于排序 2023-12-28 16:16:18 +08:00
BTMuli
2dcf1c8d16 ♻️ 主题判断上移 2023-12-28 16:10:27 +08:00
BTMuli
e6a5a07119 💄 调整样式 2023-12-28 15:46:51 +08:00
BTMuli
8b01c9aa7a 🐛 修复分享图渲染错误
*PostID:47046009
2023-12-28 13:02:41 +08:00
BTMuli
2be72692bb 添加 topic 2023-12-28 12:50:52 +08:00
BTMuli
ca9c5ca931 添加 TpVote
* PostID:44118649
2023-12-28 00:15:08 +08:00
BTMuli
6b4cd2a666 💄 完善 unknown 样式&处理 2023-12-27 23:17:55 +08:00
BTMuli
9973cb1577 🐛 修复 postRef 为 null 问题 2023-12-27 22:53:12 +08:00
BTMuli
afddc9f955 添加用户页跳转 2023-12-27 22:32:55 +08:00
BTMuli
959a3e373a 添加合集 overlay 2023-12-27 22:26:47 +08:00
BTMuli
6c6ff7fef0 🌱 添加帖子合集相关类型&请求 2023-12-27 21:27:47 +08:00
BTMuli
f708b455fa ✏️ 函数修正 2023-12-27 21:24:41 +08:00
BTMuli
22175442e5 🍱 替换首页&默认用户 icon 2023-12-27 15:43:34 +08:00
BTMuli
6e5b3016f1 ️ 添加名片类型,描述智能换行 2023-12-26 23:58:53 +08:00
BTMuli
8fc8ace1eb 🍱 去掉描述的空格 2023-12-26 22:41:43 +08:00
BTMuli
4f9eb9e3df ️ 侧边栏添加 hover 提示 2023-12-26 21:17:54 +08:00
BTMuli
4d7f3ad5f0 💄 调整样式 2023-12-26 20:43:17 +08:00
BTMuli
a66bc28415 💄 调整样式 2023-12-26 15:53:59 +08:00
BTMuli
ba6787504e 💄 设置页样式变更 2023-12-26 00:31:32 +08:00
BTMuli
65f4309bf5 🐛 修复数据缺失 2023-12-24 14:14:31 +08:00
BTMuli
2f903ff458 💄 添加 switch,调整样式 2023-12-24 13:57:12 +08:00
BTMuli
aa079242ba 💄 icon 变更 2023-12-23 22:28:09 +08:00
BTMuli
1b442ffd71 💄 添加获取途径 2023-12-23 22:25:16 +08:00
BTMuli
41cbe0af06 名片图鉴完工 2023-12-23 22:17:22 +08:00
BTMuli
b5bbdad588 🌱 添加名片、材料图鉴入口 2023-12-23 21:54:25 +08:00
BTMuli
f4d0755154 ️ 调整 wiki 样式 2023-12-23 21:50:56 +08:00
BTMuli
0666454f5d 🌱 新增名片图鉴与材料图鉴 2023-12-23 21:23:34 +08:00
BTMuli
5435e75ca2 ️ 扫码逻辑调整,自动获取结果/刷新 2023-12-23 21:20:21 +08:00
BTMuli
18fd7fe59f 💄 调整样式,更加紧凑 2023-12-22 22:43:18 +08:00
BTMuli
dc655d89da 🌱 武器图鉴基本完成 2023-12-22 22:33:24 +08:00
BTMuli
d94f4cff9d ✏️ 类型修正 2023-12-22 22:06:44 +08:00
BTMuli
bb296ce9d1 🐛 修复颜色解析错误
* PostID: 46557602
2023-12-21 12:34:18 +08:00
BTMuli
88d2e6126a 🚨 修复 qodana 报错 2023-12-21 00:30:42 +08:00
BTMuli
100cb96dac 新增几个 callback 的处理 2023-12-21 00:22:32 +08:00
BTMuli
8f655896f5 ♻️ 重构 JSBridge,调整回调顺序,完善类型 2023-12-20 21:23:18 +08:00
BTMuli
62484a9559 🔧 @typescript-eslint/restrict-template-expressions 置为 warn 2023-12-20 21:21:00 +08:00
BTMuli
051b084ac0 ✏️ 类型修正 2023-12-20 20:52:38 +08:00
BTMuli
0af8fe9b9a ♻️ 重构 userStore 用法 2023-12-20 20:49:46 +08:00
BTMuli
66333f5155 ⬆️ 更新依赖 2023-12-20 20:45:38 +08:00
87 changed files with 5149 additions and 3502 deletions

View File

@@ -3,6 +3,8 @@ dist
src-tauri/target
# Package files
pnpm-lock.yaml
# data
src/data/**/*.json
# lint files
!.prettierrc.yml
!.stylelintrc.yml

View File

@@ -68,6 +68,7 @@ overrides:
trailingUnderscore: allow
"@typescript-eslint/no-non-null-assertion": warn
"@typescript-eslint/no-misused-promises": off
"@typescript-eslint/restrict-template-expressions": warn
- files: ["*.vue"]
parser: vue-eslint-parser
parserOptions:

View File

@@ -59,6 +59,7 @@ jobs:
releaseBody: |
> [!TIP]
> Windows 平台用户建议通过微软应用商店下载macOS 平台仅在此发布Linux 平台暂不支持。
> 如有使用问题可加入 [反馈QQ群](https://h5.qun.qq.com/s/3cgX0hJ4GA)
<a href="https://apps.microsoft.com/store/detail/9NLBNNNBNSJN?launch=true&cid=BTMuli&mode=mini">
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" alt="download"/>

4
.gitignore vendored
View File

@@ -6,7 +6,9 @@ node_modules
dist
# Secrets
.env.sh
# wiki-json(dev)
# wiki(Dev)
src/data/WIKI/Character/
src/data/WIKI/Weapon/
src/data/WIKI/GCG/
public/icon/talents/
public/icon/constellations/

View File

@@ -13,3 +13,5 @@ qodana.yaml
*.webp
*.png
*.svg
# data
!src/data/**/*.json

View File

@@ -1,5 +1,5 @@
extends:
- stylelint-high-performance-animation
# - stylelint-high-performance-animation
- stylelint-order
- stylelint-declaration-block-no-ignored-properties
- stylelint-config-standard-vue

View File

@@ -2,12 +2,44 @@
Author: 目棃
Description: CHANGELOG
Date: 2023-09-08
Update: 2023-12-11
Update: 2024-01-02
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-09-08 09:45:17 `
>
> 更新于 `2023-12-11 19:01:09`
> 更新于 `2024-01-02 01:50:28`
## [0.3.9](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.9) (2024-01-02)
### Feat
- 应用创建用户反馈QQ群 [`657618889`](https://h5.qun.qq.com/s/3cgX0hJ4GA)
- 应用:扫码逻辑调整,自动获取结果&刷新
- 名片:添加索引数据,显示获取途径,支持分享
- 应用:侧边栏添加 `hover` 时的提示
- 帖子:添加帖子合集数据渲染&处理
- 帖子:添加用户、帖子的 JSBridge 跳转
- 帖子:添加 `TpVote` 类型解析&渲染
- 帖子:添加 `topic` 数据渲染
- JSBridge添加 `工具` 子菜单,包括 `重试桥接` `模拟触摸` `移除遮罩` 等选项 [`#73`](https://github.com/BTMuli/TeyvatGuide/issues/73)
### Fix
- 帖子:修复部分帖子颜色解析错误
- 帖子:完善 Unknown 样式&处理
- 帖子:修复部分帖子分享图渲染错误
- 帖子:修复部分帖子视频播放量为 `undefined`
- 应用:修复用户深渊页面渲染错误 [`#75`](https://github.com/BTMuli/TeyvatGuide/issues/75)
### Change
- 应用:重构 userStore 用法
- JSBridge: 重构 JSBridge完善类型提示
- 应用:侧边栏首页及默认用户 Icon 变更
- 应用:设置页样式变更
- 应用:主题判断上移
- 帖子:隐藏更新时间,添加分享时间
- 应用:移除帖子页面的 `loadmore` 功能
## [0.3.8](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.8) (2023-12-20)

View File

@@ -1,13 +1,13 @@
---
Author: 目棃
Date: 2023-03-10
Description: 项目资源说明
Update: 2023-09-08
Date: 2023-03-10
Update: 2024-01-01
---
> 本文档 [`Front-matter`](https://github.com/BTMuli/Mucli#FrontMatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-10 22:05:44`
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-10 22:05:44`
>
> 更新于 `2023-09-08 09:44:56`
> 更新于 `2024-01-01 00:19:35`
## 说明
@@ -38,7 +38,7 @@ Update: 2023-09-08
## 侧边栏图标
- 顶部收缩按钮:`mdi:chevron-right` `mdi:chevron-left`
- 首页:[Fandom](https://genshin-impact.fandom.com/wiki/Genshin_Impact_Wiki)
- 首页:米游社网页活动图标
- 公告:个人绘制 SVG
- 咨讯:[米游社](https://www.miyoushe.com)
- 成就:个人绘制 SVG

View File

@@ -1,9 +1,9 @@
{
"name": "TeyvatGuide",
"version": "0.3.8",
"version": "0.3.9",
"description": "Game Tool for Genshin Impact player",
"private": true,
"packageManager": "pnpm@8.11.0",
"packageManager": "pnpm@8.12.1",
"scripts": {
"build": "tauri build",
"debug": "tauri build --debug",
@@ -75,52 +75,52 @@
"html2canvas": "^1.4.1",
"js-md5": "^0.8.3",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode.vue": "^3.4.1",
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
"uuid": "^9.0.1",
"vue": "^3.3.11",
"vue-echarts": "^6.6.3",
"vue": "^3.3.13",
"vue-echarts": "^6.6.5",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.2.5",
"vuetify": "^3.4.6",
"vuetify": "^3.4.8",
"wcag-color": "^1.1.1"
},
"devDependencies": {
"@tauri-apps/cli": "^1.5.7",
"@tauri-apps/cli": "^1.5.8",
"@types/color-convert": "^2.0.3",
"@types/js-md5": "^0.7.2",
"@types/node": "^20.10.4",
"@types/node": "^20.10.5",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"@vitejs/plugin-vue": "^4.5.2",
"@vue/devtools": "^6.5.1",
"concurrently": "^8.2.2",
"eslint": "^8.55.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsonc": "^2.10.0",
"eslint-plugin-n": "^16.4.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-config-standard-with-typescript": "^43.0.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsonc": "^2.11.2",
"eslint-plugin-n": "^16.5.0",
"eslint-plugin-prettier": "^5.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.19.2",
"eslint-plugin-yml": "^1.10.0",
"eslint-plugin-yml": "^1.11.0",
"husky": "^8.0.3",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.2.0",
"prettier": "3.1.0",
"stylelint": "^15.11.0",
"prettier": "3.1.1",
"stylelint": "^16.0.2",
"stylelint-config-idiomatic-order": "^10.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.7.0",
"stylelint-high-performance-animation": "^1.9.0",
"stylelint-order": "^6.0.4",
"stylelint-prettier": "^4.1.0",
"stylelint-prettier": "^5.0.0",
"typescript": "^5.3.3",
"vite": "^5.0.7",
"vite-plugin-vuetify": "^1.0.2",
"vite": "^5.0.10",
"vite-plugin-vuetify": "^2.0.1",
"yaml-eslint-parser": "^1.2.2"
}
}

956
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -3,30 +3,25 @@
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
# -------------------------------------------------------------------------------#
version: "1.0"
# Specify inspection profile for code analysis
profile:
name: qodana.starter
# Enable inspections
# include:
# - name: <SomeEnabledInspectionId>
# Disable inspections
# exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
# The following options are only applied in CI/CD environment
# These options are ignored during local run
# Execute shell command before Qodana execution
# bootstrap: sh ./prepare-qodana.sh
# Install IDE plugins before Qodana execution
# plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Specify Qodana linter for analysis
linter: jetbrains/qodana-js:latest
include:
- name: CheckDependencyLicenses

181
src-tauri/Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 3
[[package]]
name = "TeyvatGuide"
version = "0.3.8"
version = "0.3.9"
dependencies = [
"serde",
"serde_json",
@@ -412,9 +412,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.9.5"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "convert_case"
@@ -497,9 +497,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -507,9 +507,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751"
dependencies = [
"cfg-if",
"crossbeam-epoch",
@@ -518,22 +518,21 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
version = "0.9.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
checksum = "b9bcf5bdbfdd6030fb4a1c497b5d5fc5921aa2f60d359a17e249c0e6df3de153"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -541,9 +540,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
dependencies = [
"cfg-if",
]
@@ -582,7 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -592,7 +591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
dependencies = [
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -616,7 +615,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -627,7 +626,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -977,7 +976,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -1391,9 +1390,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.3"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
@@ -1409,25 +1408,11 @@ dependencies = [
[[package]]
name = "home"
version = "0.5.5"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "html5ever"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
dependencies = [
"log",
"mac",
"markup5ever 0.10.1",
"proc-macro2",
"quote",
"syn 1.0.109",
"windows-sys 0.52.0",
]
[[package]]
@@ -1438,7 +1423,7 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [
"log",
"mac",
"markup5ever 0.11.0",
"markup5ever",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -1486,9 +1471,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.27"
version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
@@ -1501,7 +1486,7 @@ dependencies = [
"httpdate",
"itoa 1.0.10",
"pin-project-lite",
"socket2 0.4.10",
"socket2",
"tokio",
"tower-service",
"tracing",
@@ -1743,18 +1728,6 @@ dependencies = [
"treediff",
]
[[package]]
name = "kuchiki"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
dependencies = [
"cssparser",
"html5ever 0.25.2",
"matches",
"selectors",
]
[[package]]
name = "kuchikiki"
version = "0.8.2"
@@ -1762,7 +1735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8"
dependencies = [
"cssparser",
"html5ever 0.26.0",
"html5ever",
"indexmap 1.9.3",
"matches",
"selectors",
@@ -1872,20 +1845,6 @@ dependencies = [
"libc",
]
[[package]]
name = "markup5ever"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
"log",
"phf 0.8.0",
"phf_codegen 0.8.0",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]]
name = "markup5ever"
version = "0.11.0"
@@ -2251,7 +2210,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -2468,7 +2427,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -2802,9 +2761,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.11.22"
version = "0.11.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
dependencies = [
"base64 0.21.5",
"bytes",
@@ -3029,7 +2988,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -3051,14 +3010,14 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
name = "serde_spanned"
version = "0.6.4"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
@@ -3101,7 +3060,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -3204,16 +3163,6 @@ version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "socket2"
version = "0.5.5"
@@ -3566,9 +3515,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.40"
version = "2.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
dependencies = [
"proc-macro2",
"quote",
@@ -3837,7 +3786,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-sql"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#4f8df2f86b32821198cd14a687fb743132265c71"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#7de603ebff4c8900d1cc24644c5c5a2dd06ab48b"
dependencies = [
"futures-core",
"log",
@@ -3902,7 +3851,7 @@ dependencies = [
"dunce",
"glob",
"heck 0.4.1",
"html5ever 0.26.0",
"html5ever",
"infer",
"json-patch",
"kuchikiki",
@@ -3963,22 +3912,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -3993,9 +3942,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
"deranged",
"itoa 1.0.10",
@@ -4013,9 +3962,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
dependencies = [
"time-core",
]
@@ -4043,9 +3992,9 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
[[package]]
name = "tokio"
version = "1.35.0"
version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
dependencies = [
"backtrace",
"bytes",
@@ -4053,7 +4002,7 @@ dependencies = [
"mio",
"num_cpus",
"pin-project-lite",
"socket2 0.5.5",
"socket2",
"windows-sys 0.48.0",
]
@@ -4186,7 +4135,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]
@@ -4417,7 +4366,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
"wasm-bindgen-shared",
]
@@ -4451,7 +4400,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4953,9 +4902,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.28"
version = "0.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5"
dependencies = [
"memchr",
]
@@ -4982,9 +4931,9 @@ dependencies = [
[[package]]
name = "wry"
version = "0.24.6"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744"
checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4"
dependencies = [
"base64 0.13.1",
"block",
@@ -4996,9 +4945,9 @@ dependencies = [
"gio",
"glib",
"gtk",
"html5ever 0.25.2",
"html5ever",
"http",
"kuchiki",
"kuchikiki",
"libc",
"log",
"objc",
@@ -5041,9 +4990,9 @@ dependencies = [
[[package]]
name = "xattr"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d367426ae76bdfce3d8eaea6e94422afd6def7d46f9c89e2980309115b3c2c41"
checksum = "a7dae5072fe1f8db8f8d29059189ac175196e410e40ba42d5d4684ae2f750995"
dependencies = [
"libc",
"linux-raw-sys",
@@ -5067,7 +5016,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.40",
"syn 2.0.41",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "TeyvatGuide"
version = "0.3.8"
version = "0.3.9"
description = "Game Tool for Genshin Impact player"
authors = ["BTMuli <bt-muli@outlook.com>"]
license = "MIT"

View File

@@ -1,16 +1,29 @@
//! @file src/client.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.3.8
//! @since Beta v0.3.9
use tauri::{AppHandle, CustomMenuItem, Manager, Menu, WindowBuilder, WindowUrl};
use tauri::{AppHandle, CustomMenuItem, Manager, Menu, Submenu, WindowBuilder, WindowUrl};
use url::Url;
// 创建子菜单-工具
fn create_utils_menu() -> Menu {
let retry_bridge = CustomMenuItem::new("retry".to_string(), "重试桥接");
let mock_touch = CustomMenuItem::new("mock_touch".to_string(), "模拟触摸");
let remove_overlay = CustomMenuItem::new("remove_overlay".to_string(), "移除遮罩");
return Menu::new().add_item(retry_bridge).add_item(mock_touch).add_item(remove_overlay);
}
// 创建米游社客户端菜单
fn create_mhy_menu() -> Menu {
let top = CustomMenuItem::new("top".to_string(), "置顶");
let cancel_top = CustomMenuItem::new("cancel_top".to_string(), "取消置顶");
let open_post = CustomMenuItem::new("open_post".to_string(), "打开帖子");
return Menu::new().add_item(top).add_item(cancel_top).add_item(open_post);
let utils_menu = Submenu::new("工具".to_string(), create_utils_menu());
return Menu::new()
.add_item(top)
.add_item(cancel_top)
.add_item(open_post)
.add_submenu(utils_menu);
}
// 获取米游社客户端入口地址
@@ -101,6 +114,36 @@ pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
})()"#;
window.eval(&execute_js).ok().unwrap();
}
"retry" => {
let window = handle.get_window("mhy_client").unwrap();
let execute_js = r#"javascript:(async function(){
const arg = {
method: 'teyvat_retry',
}
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
window.eval(&execute_js).ok().unwrap();
}
"mock_touch" => {
let window = handle.get_window("mhy_client").unwrap();
let execute_js = r#"javascript:(async function(){
const arg = {
method: 'teyvat_touch',
}
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
window.eval(&execute_js).ok().unwrap();
}
"remove_overlay" => {
let window = handle.get_window("mhy_client").unwrap();
let execute_js = r#"javascript:(async function(){
const arg = {
method: 'teyvat_remove',
}
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
window.eval(&execute_js).ok().unwrap();
}
_ => {}
});
}

View File

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

View File

@@ -1,5 +1,5 @@
<template>
<v-app>
<v-app :theme="vuetifyTheme">
<TSidebar v-if="isMain" />
<v-main>
<v-container :fluid="true" class="app-container">
@@ -12,7 +12,8 @@
<script lang="ts" setup>
import { app, event, fs, tauri, window as TauriWindow } from "@tauri-apps/api";
import { onBeforeMount, onMounted, ref } from "vue";
import { storeToRefs } from "pinia";
import { computed, onBeforeMount, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import TBackTop from "./components/app/t-backTop.vue";
@@ -26,9 +27,13 @@ import { getBuildTime } from "./utils/TGBuild";
import TGRequest from "./web/request/TGRequest";
const appStore = useAppStore();
const userStore = storeToRefs(useUserStore());
const isMain = ref<boolean>(false);
const theme = ref<string>(appStore.theme);
const router = useRouter();
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
});
onBeforeMount(async () => {
// 获取当前窗口
@@ -137,26 +142,26 @@ async function checkUserLoad(): Promise<void> {
if (JSON.stringify(ckDB) !== "{}" && !appStore.isLogin) {
appStore.isLogin = true;
}
const userStore = useUserStore();
const ckLocal = userStore.cookie;
if (JSON.stringify(ckLocal) !== JSON.stringify(ckDB)) {
userStore.cookie = ckDB;
userStore.cookie.value = ckDB;
console.info("cookie 数据已更新!");
} else {
console.info("cookie 数据已加载!");
}
const infoLocal = userStore.getBriefInfo();
const infoLocal = userStore.briefInfo.value;
const appData = await TGSqlite.getAppData();
const infoDB = appData.find((item) => item.key === "userInfo")?.value;
if (infoDB === undefined && JSON.stringify(infoLocal) !== "{}") {
await userStore.saveBriefInfo();
await TGSqlite.saveAppData("userInfo", JSON.stringify(infoLocal));
} else if (infoDB !== undefined && infoLocal !== JSON.parse(infoDB)) {
userStore.setBriefInfo(JSON.parse(infoDB));
userStore.briefInfo.value = JSON.parse(infoDB);
console.info("briefInfo 数据已更新!");
} else {
console.info("briefInfo 数据已加载!");
}
const accountLocal = userStore.getCurAccount();
const accountLocal = userStore.account.value;
const accountDB = await TGSqlite.getCurAccount();
if (accountDB === false) {
showSnackbar({
@@ -167,7 +172,7 @@ async function checkUserLoad(): Promise<void> {
return;
}
if (accountDB !== accountLocal) {
userStore.setCurAccount(accountDB);
userStore.account.value = accountDB;
console.info("curAccount 数据已更新!");
} else {
console.info("curAccount 数据已加载!");
@@ -231,16 +236,16 @@ async function checkUpdate(): Promise<void> {
title: "检测到版本更新",
text: "请到设置页手动更新版本,即将弹出更新说明子页面",
});
if (confirm) {
appStore.buildTime = getBuildTime();
window.open("https://app.btmuli.ink/docs/Changelogs.html");
} else {
if (!confirm) {
showSnackbar({
text: "请到设置页手动更新版本!",
color: "error",
timeout: 3000,
});
return;
}
appStore.buildTime = getBuildTime();
window.open("https://app.btmuli.ink/docs/Changelogs.html");
}
}
</script>

View File

@@ -1,8 +1,7 @@
/**
* @file assets themes dark.css
* @file assets/themes/dark.css
* @description 主题样式文件-深色主题
* @author BTMuli <bt-muli@outlook.com>
* @since Beta v0.3.0
* @since Beta v0.3.9
*/
/* dark mode */
@@ -27,6 +26,7 @@ html.dark {
--box-text-2: var(--tgc-white-2);
--box-text-3: var(--tgc-blue-1);
--box-text-4: var(--tgc-white-4);
--box-text-5: var(--tgc-red-1);
--box-text-7: var(--tgc-white-5);
/* button */

View File

@@ -1,7 +1,7 @@
/**
* @file assets themes default.css
* @file assets/themes/default.css
* @description 主题样式文件-默认(浅色)主题
* @since Beta v0.3.3
* @since Beta v0.3.9
*/
/* default(light) theme */
@@ -26,6 +26,7 @@ html.default {
--box-text-2: var(--tgc-dark-2);
--box-text-3: var(--tgc-blue-2);
--box-text-4: var(--tgc-blue-3); /* subtitle */
--box-text-5: var(--tgc-pink-1); /* tag */
--box-text-7: var(--tgc-dark-7); /* quote */
/* button */

View File

@@ -0,0 +1,101 @@
<template>
<div class="tab-box">
<img class="tab-icon" src="/icon.webp" alt="App" />
<div class="tab-info click" title="点击前往 Github Release" @click="toRelease()">
TeyvatGuide Beta
</div>
<div class="tab-info">v{{ versionApp }}.{{ buildTime === "" ? "Dev" : buildTime }}</div>
<div class="tab-links">
<div class="tab-link" @click="toGroup()" title="点击加入反馈群">
<img src="/platforms/other/qq.webp" alt="qq" />
</div>
<div class="tab-link" @click="toGithub()" title="点击查看仓库">
<img src="/platforms/other/github.webp" alt="github" />
</div>
<div class="tab-link" @click="toStore()" title="点击查看商店页面">
<img src="/platforms/other/microsoft-store.webp" alt="store" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { computed, onMounted, ref } from "vue";
import { useAppStore } from "../../store/modules/app";
const appStore = useAppStore();
const versionApp = ref<string>();
const buildTime = computed(() => appStore.buildTime);
onMounted(async () => {
versionApp.value = await app.getVersion();
});
function toRelease() {
window.open("https://github.com/BTMuli/TeyvatGuide/releases/latest");
}
function toGroup() {
window.open("https://h5.qun.qq.com/s/3cgX0hJ4GA");
}
function toGithub() {
window.open("https://github.com/BTMuli/TeyvatGuide");
}
function toStore() {
window.open("https://www.microsoft.com/store/productId/9NLBNNNBNSJN");
}
</script>
<style lang="css" scoped>
.tab-box {
position: fixed;
top: 16px;
right: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px;
border-radius: 10px;
background-image: linear-gradient(to bottom, rgb(19 84 122 / 80%), rgb(128 208 199 / 80%));
box-shadow: 0 0 10px var(--common-shadow-2);
}
.tab-icon {
width: 200px;
aspect-ratio: 1 / 1;
}
.tab-info {
color: var(--tgc-white-1);
font-family: var(--font-title);
font-size: 14px;
text-align: center;
text-shadow: 0 0 2px rgb(19 84 122 / 80%);
}
.tab-info.click {
color: var(--tgc-yellow-1);
cursor: pointer;
}
.tab-links {
display: flex;
backdrop-filter: blur(20px);
column-gap: 10px;
}
.tab-link {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.tab-link img {
width: 32px;
height: 32px;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<v-navigation-drawer :permanent="true" :rail="rail" class="tsb-box">
<v-list v-model:opened="open" class="side-list" density="compact" :nav="true">
<v-list class="side-list" density="compact" :nav="true">
<!-- 负责收缩侧边栏 -->
<v-list-item @click="collapse()">
<template v-if="rail" #prepend>
@@ -15,138 +15,208 @@
</template>
</v-list-item>
<!-- 菜单项 -->
<v-list-item value="home" title="首页" :link="true" href="/">
<v-list-item value="home" :link="true" href="/" :title.attr="'首页'">
<template #title>首页</template>
<template #prepend>
<img src="/source/UI/paimon.webp" alt="homeIcon" class="side-icon" />
<img src="/source/UI/paimon.webp" alt="homeIcon" class="side-icon paimon" />
</template>
</v-list-item>
<v-list-item title="公告" value="announcements" :link="true" href="/announcements">
<v-list-item :title.attr="'公告'" value="announcements" :link="true" href="/announcements">
<template #title>公告</template>
<template #prepend>
<img src="../../assets/icons/board.svg" alt="annoIcon" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="咨讯" value="news" :link="true" href="/news/2">
<v-list-item :title.attr="'咨讯'" value="news" :link="true" href="/news/2">
<template #title>咨讯</template>
<template #prepend>
<img src="/platforms/mhy/mys.webp" alt="mihoyo" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="帖子" value="posts" :link="true" href="/posts">
<v-list-item :title.attr="'帖子'" value="posts" :link="true" href="/posts">
<template #title>帖子</template>
<template #prepend>
<img src="/source/UI/posts.png" alt="posts" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="成就" value="achievements" :link="true" href="/achievements">
<v-list-item :title.attr="'成就'" value="achievements" :link="true" href="/achievements">
<template #title>成就</template>
<template #prepend>
<img src="../../assets/icons/achievements.svg" alt="achievementsIcon" class="side-icon" />
</template>
</v-list-item>
<v-divider />
<v-list-item title="原神战绩" value="record" :link="true" href="/user/record">
<v-list-item :title.attr="'原神战绩'" value="record" :link="true" href="/user/record">
<template #title>原神战绩</template>
<template #prepend>
<img src="/source/UI/userRecord.webp" alt="record" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="我的角色" value="character" :link="true" href="/user/characters">
<v-list-item :title.attr="'我的角色'" value="character" :link="true" href="/user/characters">
<template #title>我的角色</template>
<template #prepend>
<img src="/source/UI/userAvatar.webp" alt="characters" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="深渊记录" value="abyss" :link="true" href="/user/abyss">
<v-list-item :title.attr="'深渊记录'" value="abyss" :link="true" href="/user/abyss">
<template #title>深渊记录</template>
<template #prepend>
<img src="/source/UI/userAbyss.webp" alt="abyss" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="祈愿记录" value="gacha" :link="true" href="/user/gacha">
<v-list-item :title.attr="'祈愿记录'" value="gacha" :link="true" href="/user/gacha">
<template #title>祈愿记录</template>
<template #prepend>
<img src="/source/UI/userGacha.webp" alt="gacha" class="side-icon" />
</template>
</v-list-item>
<v-divider />
<v-list-item v-show="isDevEnv" title="测试" value="test" :link="true" href="/test">
<v-list-item
v-show="isDevEnv"
:title.attr="'测试页面'"
value="test"
:link="true"
href="/test"
>
<template #title>测试页面</template>
<template #prepend>
<v-icon>mdi-test-tube</v-icon>
</template>
</v-list-item>
<v-divider v-show="isDevEnv" />
<v-list-group value="wiki" :fluid="true">
<v-menu :open-on-click="true" location="end">
<template #activator="{ props }">
<v-list-item title="图鉴" v-bind="props">
<v-list-item :title.attr="'图鉴'" v-bind="props">
<template #title>图鉴</template>
<template #prepend>
<img src="/source/UI/wikiIcon.webp" alt="wikiIcon" class="side-icon" />
</template>
</v-list-item>
</template>
<v-list-item title="深渊数据库" value="wiki-abyss" :link="true" href="/wiki/abyss">
<template #prepend>
<img src="/source/UI/wikiAbyss.webp" alt="abyssIcon" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="角色图鉴" value="wiki-character" :link="true" href="/wiki/character">
<template #prepend>
<img src="/source/UI/wikiAvatar.webp" alt="characterIcon" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="武器图鉴" value="wiki-weapon" :link="true" href="/wiki/weapon">
<template #prepend>
<img src="/source/UI/wikiWeapon.webp" alt="weaponIcon" class="side-icon" />
</template>
</v-list-item>
<v-list-item title="GCG" value="wiki-GCG" :link="true" href="/wiki/GCG">
<template #prepend>
<img src="/source/UI/wikiGCG.webp" alt="gcgIcon" class="side-icon" />
</template>
</v-list-item>
</v-list-group>
<v-list class="side-list-menu wiki" density="compact" :nav="true">
<v-list-item
class="side-item-menu"
title="深渊数据库"
value="wiki-abyss"
: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="角色图鉴"
value="wiki-character"
:link="true"
href="/wiki/character"
>
<template #prepend>
<img src="/source/UI/wikiAvatar.webp" alt="characterIcon" class="side-icon-menu" />
</template>
</v-list-item>
<v-list-item
class="side-item-menu"
title="武器图鉴"
value="wiki-weapon"
:link="true"
href="/wiki/weapon"
>
<template #prepend>
<img src="/source/UI/wikiWeapon.webp" alt="weaponIcon" class="side-icon-menu" />
</template>
</v-list-item>
<v-list-item
class="side-item-menu"
title="GCG"
value="wiki-GCG"
:link="true"
href="/wiki/GCG"
>
<template #prepend>
<img src="/source/UI/wikiGCG.webp" alt="gcgIcon" class="side-icon-menu" />
</template>
</v-list-item>
<v-list-item
class="side-item-menu"
value="wiki-namecard"
:link="true"
href="/wiki/namecard"
v-if="isDevEnv"
>
<template #default>
<v-icon size="20" color="var(--tgc-yellow-2)">mdi-credit-card-outline</v-icon>
<span style="margin-left: 10px; font-size: 0.8125rem">名片图鉴</span>
</template>
</v-list-item>
<v-list-item
class="side-item-menu"
title="材料图鉴"
value="wiki-material"
:link="true"
href="/wiki/material"
v-if="isDevEnv"
>
<template #prepend>
<img src="/source/UI/wikiGCG.webp" alt="gcgIcon" class="side-icon-menu" />
</template>
</v-list-item>
</v-list>
</v-menu>
<div class="bottom-menu">
<v-menu :open-on-click="true" location="end">
<template #activator="{ props }">
<v-list-item :title="userInfo.nickname" v-bind="props">
<v-list-item :title.attr="userInfo.nickname" v-bind="props">
<template #title>{{ userInfo.nickname }}</template>
<template #prepend>
<img :src="userInfo.avatar" alt="userIcon" class="side-icon" />
<img :src="userInfo.avatar" alt="userIcon" class="side-icon paimon" />
</template>
</v-list-item>
</template>
<v-list class="side-list-user" density="compact" :nav="true">
<v-list-item class="side-item-user" title="签到" @click="openClient('sign_in')">
<v-list class="side-list-menu" density="compact" :nav="true">
<v-list-item class="side-item-menu" title="签到" @click="openClient('sign_in')">
<template #prepend>
<img src="/source/UI/userGacha.webp" class="side-icon-user" alt="sing_in" />
<img src="/source/UI/userGacha.webp" class="side-icon-menu" alt="sing_in" />
</template>
</v-list-item>
<v-list-item class="side-item-user" title="战绩" @click="openClient('game_record')">
<v-list-item class="side-item-menu" title="战绩" @click="openClient('game_record')">
<template #prepend>
<img src="/source/UI/userRecord.webp" class="side-icon-user" alt="game_record" />
<img src="/source/UI/userRecord.webp" class="side-icon-menu" alt="game_record" />
</template>
</v-list-item>
<v-list-item class="side-item-user" title="便笺" @click="openClient('daily_note')">
<v-list-item class="side-item-menu" title="便笺" @click="openClient('daily_note')">
<template #prepend>
<img src="/icon/material/210.webp" class="side-icon-user" alt="daily_note" />
<img src="/icon/material/210.webp" class="side-icon-menu" alt="daily_note" />
</template>
</v-list-item>
<v-list-item class="side-item-user" title="酒馆" @click="openClient('tavern')">
<v-list-item class="side-item-menu" title="酒馆" @click="openClient('tavern')">
<template #prepend>
<img src="/platforms/mhy/mys.webp" alt="酒馆" class="side-icon-user" />
<img src="/platforms/mhy/mys.webp" alt="酒馆" class="side-icon-menu" />
</template>
</v-list-item>
<v-list-item
class="side-item-user"
class="side-item-menu"
title="登录"
@click="login"
v-show="userStore.cookie?.game_token === ''"
v-show="userStore.cookie.value?.game_token === ''"
>
<template #prepend>
<img src="/source/UI/defaultUser.webp" class="side-icon-user" alt="login" />
<img src="/source/UI/lumine.webp" class="side-icon-menu" alt="login" />
</template>
</v-list-item>
</v-list>
</v-menu>
<v-list-item :title="themeTitle" @click="switchTheme()">
<v-list-item :title.attr="themeTitle" @click="switchTheme()">
<template #title>{{ themeTitle }}</template>
<template #prepend>
<v-icon>
{{ themeGet === "default" ? "mdi-weather-night" : "mdi-weather-sunny" }}
</v-icon>
</template>
</v-list-item>
<v-list-item title="设置" value="config" :link="true" href="/config">
<v-list-item :title.attr="'设置'" value="config" :link="true" href="/config">
<template #title>设置</template>
<template #prepend>
<img src="../../assets/icons/setting.svg" alt="setting" class="side-icon" />
</template>
@@ -158,6 +228,7 @@
<script lang="ts" setup>
import { event, window as TauriWindow } from "@tauri-apps/api";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref } from "vue";
import { useAppStore } from "../../store/modules/app";
@@ -166,16 +237,16 @@ import mhyClient from "../../utils/TGClient";
import showSnackbar from "../func/snackbar";
const appStore = useAppStore();
const userStore = useUserStore();
const userStore = storeToRefs(useUserStore());
const isDevEnv = ref<boolean>(import.meta.env.MODE === "development");
const userInfo = computed(() => {
const info = userStore.getBriefInfo();
const info = userStore.briefInfo.value;
if (info && info.nickname) return info;
return {
nickname: "未登录",
avatar: "/source/UI/defaultUser.webp",
avatar: "/source/UI/lumine.webp",
};
});
const rail = ref(appStore.sidebar.collapse);
@@ -192,15 +263,6 @@ const themeTitle = computed(() => {
return themeGet.value === "default" ? "夜间模式" : "日间模式";
});
const open = computed({
get() {
return appStore.getSubmenu();
},
set(value: string[]) {
appStore.sidebar.submenu.wiki = value.includes("wiki");
},
});
function collapse(): void {
rail.value = !rail.value;
appStore.sidebar.collapse = rail.value;
@@ -263,18 +325,29 @@ function login(): void {
margin-right: 32px;
}
.side-list-user {
.side-icon.paimon {
width: 32px;
height: 32px;
margin-right: 24px;
transform: translateX(-4px);
}
.side-list-menu {
background: var(--app-side-bg) !important;
color: var(--app-side-content) !important;
font-family: var(--font-title);
}
.side-item-user {
.side-list-menu.wiki {
margin-left: 10px;
}
.side-item-menu {
border: 1px solid var(--common-shadow-2);
background: var(--box-bg-1);
}
.side-icon-user {
.side-icon-menu {
width: 20px;
height: 20px;
border-radius: 5px;

View File

@@ -1,7 +1,7 @@
/**
* @file component func confirm.ts
* @file component/func/confirm.ts
* @description 封装自定义 confirm 组件,通过函数调用的方式,简化 confirm 的使用
* @since Beta v0.3.4
* @since Beta v0.3.9
*/
import { h, render } from "vue";
@@ -35,10 +35,24 @@ const renderBox = (props: TGApp.Component.Confirm.Params): VNode => {
let confirmInstance: VNode;
async function showConfirm(props: TGApp.Component.Confirm.ParamsConfirm): Promise<boolean>;
async function showConfirm(props: TGApp.Component.Confirm.ParamsInput): Promise<string | false>;
async function showConfirm(props: TGApp.Component.Confirm.Params): Promise<string | boolean>;
async function showConfirm(props: TGApp.Component.Confirm.Params): Promise<string | boolean> {
/**
* @function showConfirm
* @description 弹出 confirm
* @param {TGApp.Component.Confirm.Params} props confirm 的参数
* @return {Promise<string | boolean | undefined>} 点击确认返回 true点击取消返回 false点击外部返回 undefined
*/
async function showConfirm(
props: TGApp.Component.Confirm.ParamsConfirm,
): Promise<boolean | undefined>;
async function showConfirm(
props: TGApp.Component.Confirm.ParamsInput,
): Promise<string | false | undefined>;
async function showConfirm(
props: TGApp.Component.Confirm.Params,
): Promise<string | boolean | undefined>;
async function showConfirm(
props: TGApp.Component.Confirm.Params,
): Promise<string | boolean | undefined> {
if (confirmInstance !== undefined) {
const boxVue = <ConfirmInstance>confirmInstance.component;
return await boxVue.exposeProxy.displayBox(props);

View File

@@ -17,12 +17,12 @@
v-model="inputVal"
class="confirm-input-box"
ref="inputRef"
@keydown.enter="handleClick(true)"
@keydown.enter="handleConfirm"
/>
</div>
<div class="confirm-btn-box">
<button class="confirm-btn no-btn" @click="handleClick(false)">取消</button>
<button class="confirm-btn ok-btn" @click="handleClick(true)">确定</button>
<button class="confirm-btn no-btn" @click="handleCancel">取消</button>
<button class="confirm-btn ok-btn" @click="handleConfirm">确定</button>
</div>
</div>
</transition>
@@ -54,7 +54,7 @@ const data = reactive<TGApp.Component.Confirm.Params>({
const show = ref<boolean>(false);
const showOuter = ref<boolean>(false);
const showInner = ref<boolean>(false);
const confirmVal = ref<boolean | string>(false);
const confirmVal = ref<boolean | string | undefined>();
const inputVal = ref<string>("");
const inputRef = ref<HTMLInputElement>();
@@ -78,14 +78,16 @@ onMounted(async () => {
await displayBox(props);
});
async function displayBox(params: TGApp.Component.Confirm.Params): Promise<string | boolean> {
async function displayBox(
params: TGApp.Component.Confirm.Params,
): Promise<string | boolean | undefined> {
data.title = params.title;
data.text = params.text ?? "";
data.mode = params.mode ?? "confirm";
data.otcancel = params.otcancel ?? true;
show.value = true;
// 等待确认框关闭返回关闭后的confirmVal
return await new Promise<string | boolean>((resolve) => {
return await new Promise<string | boolean | undefined>((resolve) => {
nextTick(() => {
if (data.mode === "input") {
// 等待确认框打开,聚焦输入框
@@ -103,27 +105,28 @@ async function displayBox(params: TGApp.Component.Confirm.Params): Promise<strin
});
}
// 点击确认事件
function handleClick(status: boolean): void {
let res;
if (!status) {
res = false;
// 确认
function handleConfirm(): void {
if (data.mode === "input") {
confirmVal.value = inputVal.value;
inputVal.value = "";
} else {
if (data.mode === "input") {
res = inputVal.value;
inputVal.value = "";
} else {
res = true;
}
confirmVal.value = true;
}
show.value = false;
confirmVal.value = res;
}
// 取消
function handleCancel(): void {
confirmVal.value = false;
show.value = false;
}
// 点击外部事件
function handleOuter(): void {
if (data.otcancel) {
handleClick(false);
confirmVal.value = undefined;
show.value = false;
}
}

View File

@@ -5,7 +5,6 @@
<span>近期活动</span>
</div>
<div v-if="!loading" class="position-grid">
<!-- todo hover 效果优化 -->
<v-card
v-for="card in positionCards"
:key="card.postId"

View File

@@ -1,24 +1,26 @@
<template>
<TOverlay v-model="visible" hide blur-val="20px">
<TOverlay v-model="visible" hide blur-val="20px" :to-click="onCancel">
<div class="tog-box">
<div class="tog-top">
<div class="tog-title">请使用米游社APP进行扫码操作</div>
<div class="tog-subtitle">所需米游社版本 >= 2.57.1</div>
</div>
<div class="tog-mid">
<qrcode-vue class="tog-qr" :value="qrCode" render-as="svg" />
</div>
<div class="tog-bottom">
<v-btn class="tog-btn" @click="onCancel">取消</v-btn>
<v-btn class="tog-btn" @click="freshQr">刷新</v-btn>
<v-btn class="tog-btn" :loading="loading" @click="getData">已扫码</v-btn>
<qrcode-vue
class="tog-qr"
:value="qrCode"
render-as="svg"
:background="'var(--box-bg-1)'"
foreground="var(--box-text-1)"
/>
</div>
</div>
</TOverlay>
</template>
<script setup lang="ts">
import { storeToRefs } from "pinia";
import QrcodeVue from "qrcode.vue";
import { computed, reactive, ref, watch } from "vue";
import { computed, onUnmounted, reactive, ref, watch } from "vue";
import Mys from "../../plugins/Mys";
import { useUserStore } from "../../store/modules/user";
@@ -30,7 +32,10 @@ interface ToWebLoginProps {
modelValue: boolean;
}
type ToWebLoginEmits = (e: "update:modelValue", value: boolean) => void;
type ToWebLoginEmits = {
(e: "update:modelValue", value: boolean): void;
(e: "success"): void;
};
const props = withDefaults(defineProps<ToWebLoginProps>(), {
modelValue: false,
@@ -44,7 +49,8 @@ const visible = computed({
emits("update:modelValue", value);
},
});
const loading = ref<boolean>(false);
let cycleTimer: NodeJS.Timeout | null = null;
const qrCode = ref<string>("");
const ticket = ref<string>("");
const cookie = reactive<TGApp.User.Account.Cookie>({
@@ -58,14 +64,23 @@ const cookie = reactive<TGApp.User.Account.Cookie>({
ltoken: "",
});
const userStore = useUserStore();
const userStore = storeToRefs(useUserStore());
watch(visible, async (value) => {
if (value) {
await freshQr();
cycleTimer = setInterval(cycleGetData, 1000);
}
});
function onCancel(): void {
visible.value = false;
showSnackbar({
text: "已取消登录",
color: "cancel",
});
}
async function freshQr(): Promise<void> {
const res = await Mys.User.getQr();
if ("retcode" in res) {
@@ -88,25 +103,31 @@ async function freshQr(): Promise<void> {
}
}
async function getData(): Promise<void> {
loading.value = true;
async function cycleGetData() {
if (cycleTimer === null) return;
const res = await Mys.User.getData(ticket.value);
console.log(res);
if ("retcode" in res) {
showSnackbar({
text: `[${res.retcode}] ${res.message}`,
color: "error",
});
} else if (res.stat === "Init") {
showSnackbar({
text: "请先扫码",
color: "error",
});
} else if (res.stat === "Scanned") {
showSnackbar({
text: "请在米游社APP上确认登录",
color: "error",
});
} else {
if (res.retcode === -106) {
// 二维码过期
await freshQr();
} else {
// 取消轮询
if (cycleTimer) clearInterval(cycleTimer);
visible.value = false;
}
return;
}
if (res.stat === "Init" || res.stat === "Scanned") {
return;
}
if (res.stat === "Confirmed") {
// 取消轮询
if (cycleTimer) clearInterval(cycleTimer);
const data: TGApp.Plugins.Mys.GameLogin.StatusPayloadRaw = JSON.parse(res.payload.raw);
cookie.account_id = data.uid;
cookie.ltuid = data.uid;
@@ -118,13 +139,12 @@ async function getData(): Promise<void> {
color: "success",
});
visible.value = false;
setTimeout(() => {
emits("success");
}, 1000);
}
}
function onCancel(): void {
visible.value = false;
}
async function getTokens(): Promise<void> {
const stokenRes = await TGRequest.User.bgGameToken.getStoken(
cookie.account_id,
@@ -141,8 +161,12 @@ async function getTokens(): Promise<void> {
if (typeof cookieTokenRes === "string") cookie.cookie_token = cookieTokenRes;
const ltokenRes = await TGRequest.User.bySToken.getLToken(cookie.mid, cookie.stoken);
if (typeof ltokenRes === "string") cookie.ltoken = ltokenRes;
await userStore.saveCookie(cookie);
userStore.cookie.value = cookie;
}
onUnmounted(() => {
if (cycleTimer) clearInterval(cycleTimer);
});
</script>
<style lang="css" scoped>
.tog-box {
@@ -187,15 +211,4 @@ async function getTokens(): Promise<void> {
width: 256px;
height: 256px;
}
.tog-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.tog-btn {
background: var(--tgc-btn-1);
color: var(--btn-text);
}
</style>

View File

@@ -1,37 +1,87 @@
<template>
<!-- todo 支持 share -->
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
<div class="ton-box" v-if="props.data">
<img alt="bg" class="ton-bg" v-if="props.data" :src="props.data.profile" />
<div class="ton-content">
<span>{{ props.data.name }}</span>
<span>{{ props.data.desc }}</span>
<div v-if="props.data" class="ton-container">
<slot name="left"></slot>
<div class="ton-box">
<img alt="bg" class="ton-bg" v-if="props.data" :src="props.data.profile" />
<div class="ton-content">
<span>{{ props.data.name }}</span>
<span>{{ parseNamecard(props.data.desc) }}</span>
<span>获取途径{{ props.data.source }}</span>
</div>
<div class="ton-type">{{ getType }}</div>
<v-btn
class="ton-share"
@click="shareNamecard"
variant="outlined"
:loading="loading"
data-html2canvas-ignore
>
<v-icon>mdi-share-variant</v-icon>
<span>分享</span>
</v-btn>
</div>
<slot name="right"></slot>
</div>
</TOverlay>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { computed, ref } from "vue";
import { generateShareImg } from "../../utils/TGShare";
import TOverlay from "../main/t-overlay.vue";
interface ToNamecardProps {
modelValue: boolean;
data?: {
profile: string;
name: string;
bg: string;
icon: string;
desc: string;
};
data?: TGApp.App.NameCard.Item;
}
enum ToNamecardTypeEnum {
other = 0,
achievement = 1,
role = 2,
record = 3,
activity = 4,
unknown = 5,
}
type ToNamecardTypeMap = {
[key in ToNamecardTypeEnum]: string;
};
const typeMap: ToNamecardTypeMap = {
[ToNamecardTypeEnum.other]: "其他",
[ToNamecardTypeEnum.achievement]: "成就",
[ToNamecardTypeEnum.role]: "角色",
[ToNamecardTypeEnum.record]: "纪行",
[ToNamecardTypeEnum.activity]: "活动",
[ToNamecardTypeEnum.unknown]: "未知",
};
type ToNamecardEmits = (e: "update:modelValue", value: boolean) => void;
const props = defineProps<ToNamecardProps>();
const emits = defineEmits<ToNamecardEmits>();
const loading = ref<boolean>(false);
const getType = computed(() => {
if (!props.data) return typeMap[ToNamecardTypeEnum.unknown];
switch (props.data.type) {
case ToNamecardTypeEnum.achievement:
return typeMap[ToNamecardTypeEnum.achievement];
case ToNamecardTypeEnum.role:
return typeMap[ToNamecardTypeEnum.role];
case ToNamecardTypeEnum.record:
return typeMap[ToNamecardTypeEnum.record];
case ToNamecardTypeEnum.activity:
return typeMap[ToNamecardTypeEnum.activity];
default:
return typeMap[ToNamecardTypeEnum.other];
}
});
const visible = computed({
get: () => props.modelValue,
set: (value) => {
@@ -42,8 +92,80 @@ const visible = computed({
function onCancel() {
visible.value = false;
}
function parseNamecard(desc: string): string {
let array = [];
if (desc.startsWith("名片纹饰。「")) {
array.push("名片纹饰。");
const reg = /「.+?」/g;
const match = desc.match(reg);
if (match !== null) {
for (const item of match) {
if (item.length <= 34) {
array.push(item);
} else {
array.push("「");
array.push(...parseDesc(item.slice(1, -1), true));
const maxLength = Math.max(...array.map((item) => item.length));
array.push(" ".repeat(maxLength - 4) + "」");
}
}
}
} else {
array.push("名片纹饰。");
const content = desc.slice(5);
if (content.length <= 32) {
array.push(content);
} else {
array.push(...parseDesc(content));
}
}
const res = array.join("\n");
if (!res.endsWith("\n")) return res + "\n";
return res;
}
function parseDesc(desc: string, inQuote: boolean = false): string[] {
let res = desc.replace(/。/g, "。\n");
res = res.replace(//g, "\n");
res = res.replace(//g, "\n");
res = res.replace(//g, "\n");
if (!desc.includes("!」")) {
res = res.replace(//g, "\n");
}
res = res.replace(/…/g, "…\n");
const match = res.split("\n");
let array: string[] = [];
for (const item of match) {
if (item.length > 0 && item.length <= 32) {
array.push(item);
} else {
const match2 = item.replace(//g, "\n").split("\n");
for (const item2 of match2) {
if (item2.length > 0) array.push(item2);
}
}
}
if (inQuote) array = array.map((item) => ` ${item}`);
return array;
}
async function shareNamecard(): Promise<void> {
const namecardBox = <HTMLElement>document.querySelector(".ton-box");
const fileName = `${getType.value}名片】-${props.data?.name}`;
loading.value = true;
await generateShareImg(fileName, namecardBox);
loading.value = false;
}
</script>
<style lang="css" scoped>
.ton-container {
display: flex;
align-items: center;
justify-content: center;
column-gap: 10px;
}
.ton-box {
position: relative;
overflow: hidden;
@@ -58,6 +180,17 @@ function onCancel() {
height: 100%;
}
.ton-type {
position: absolute;
top: 10px;
left: 10px;
padding: 0 5px;
border: 1px solid var(--tgc-white-1);
border-radius: 5px;
backdrop-filter: blur(20px);
color: var(--tgc-white-1);
}
.ton-content {
position: absolute;
right: 0;
@@ -69,18 +202,34 @@ function onCancel() {
align-items: flex-start;
justify-content: flex-end;
padding: 10px;
background: rgb(0 0 0 / 20%);
box-shadow: 0 0 10px rgb(255 255 255 /20%);
background: var(--common-shadow-t-1);
color: var(--tgc-white-1);
}
.ton-content :first-child {
font-family: var(--font-title);
font-size: 20px;
text-shadow: 0 0 5px rgb(0 0 0/80%);
}
.ton-content :nth-child(1) {
font-family: var(--font-title);
font-size: 20px;
.ton-content :nth-child(2) {
border-bottom: 1px dotted var(--tgc-white-1);
text-shadow: 0 0 2px rgb(0 0 0/80%);
white-space: pre-wrap;
}
.ton-content :nth-child(2) {
.ton-content :last-child {
opacity: 0.8;
text-shadow: 0 0 2px black;
}
.ton-share {
position: absolute;
right: 10px;
bottom: 10px;
border: 1px solid var(--tgc-white-1);
border-radius: 5px;
backdrop-filter: blur(5px);
color: var(--tgc-white-1);
}
</style>

View File

@@ -9,6 +9,7 @@ import { toRaw } from "vue";
import TGClient from "../../utils/TGClient";
import showConfirm from "../func/confirm";
import showSnackbar from "../func/snackbar";
interface TpMention {
insert: {
@@ -28,18 +29,26 @@ const props = defineProps<TpMentionProps>();
console.log("tpMention", props.data.insert.mention.uid, toRaw(props.data).insert.mention);
async function toLink(): Promise<void> {
let prefix = "";
const uid = props.data.insert.mention.uid;
const confirm = await showConfirm({
title: "跳转提示",
text: "是否采用内置 JSBridge 跳转?",
});
if (confirm) {
const prefix = "https://m.miyoushe.com/ys/#/accountCenter/0?id=";
await TGClient.open("mention", `${prefix}${uid}`);
} else {
const prefix = "https://www.miyoushe.com/ys/accountCenter/postList?id=";
window.open(`${prefix}${uid}`);
if (confirm === undefined) {
showSnackbar({
text: "已取消跳转",
color: "info",
});
return;
}
if (confirm === true) {
prefix = "https://m.miyoushe.com/ys/#/accountCenter/0?id=";
await TGClient.open("mention", `${prefix}${uid}`);
return;
}
prefix = "https://www.miyoushe.com/ys/accountCenter/postList?id=";
window.open(`${prefix}${uid}`);
}
</script>
<style lang="css" scoped>

View File

@@ -11,6 +11,7 @@ import TpText from "./tp-text.vue";
import TpUnknown from "./tp-unknown.vue";
import TpVillaCard from "./tp-villaCard.vue";
import TpVod from "./tp-vod.vue";
import TpVote from "./tp-vote.vue";
interface TpParserProps {
data: TGApp.Plugins.Mys.SctPost.Base[];
@@ -37,6 +38,8 @@ function getTpName(tp: TGApp.Plugins.Mys.SctPost.Base) {
return TpMention;
} else if ("villa_card" in tp.insert) {
return TpVillaCard;
} else if ("vote" in tp.insert) {
return TpVote;
}
return TpUnknown;
}

View File

@@ -1,12 +1,32 @@
<template>
<div class="mys-post-unknown">
<code class="mys-post-unknown-code">{{ JSON.stringify(props.data, null, 2) }}</code>
<div class="tp-unknown-box">
<code class="tp-unknown-code">{{ JSON.stringify(props.data, null, 2) }}</code>
</div>
</template>
<script lang="ts" setup>
import { toRaw } from "vue";
interface TpUnknownProps {
data: TGApp.Plugins.Mys.SctPost.Empty;
}
const props = defineProps<TpUnknownProps>();
console.warn("tpUnknown", toRaw(props.data.insert));
</script>
<style lang="css" scoped>
.tp-unknown-box {
width: 800px;
padding: 10px;
border: 1px solid var(--common-shadow-1);
border-radius: 10px;
background: var(--box-bg-1);
}
.tp-unknown-code {
font-family: var(--font-text);
opacity: 0.6;
white-space: pre-wrap;
word-break: break-all;
}
</style>

View File

@@ -1,4 +1,5 @@
<template>
<!-- todo 样式优化 47041934-->
<div
class="tp-villa-card-box"
:style="{

View File

@@ -34,7 +34,7 @@ interface TpVod {
format: "MP4"; // 待补充
label: "480P" | "720P" | "1080P" | "2K"; // 待补充
}>;
view_num: number;
view_num?: number;
transcode_status: number;
review_status: number;
};
@@ -93,8 +93,10 @@ onMounted(async () => {
name: "subtitle",
index: 100,
position: "left",
html: `<i class="mdi mdi-eye"></i><span style="padding-left: 5px">${props.data.insert.vod.view_num}</span>`,
tooltip: `播放数:${props.data.insert.vod.view_num}`,
html: `<i class="mdi mdi-eye"></i><span style="padding-left: 5px">${
props.data.insert.vod?.view_num ?? 0
}</span>`,
tooltip: `播放数:${props.data.insert.vod?.view_num ?? 0}`,
},
],
};
@@ -141,14 +143,16 @@ function getVodTime(): string {
left: 0;
display: flex;
overflow: hidden;
width: 100%;
align-items: center;
justify-content: center;
border-radius: 10px;
aspect-ratio: v-bind(vodAspectRatio);
}
.tp-vod-cover :nth-child(1) {
max-width: 100%;
.tp-vod-cover :first-child {
width: 100%;
object-fit: cover;
}
.tp-vod-cover :nth-child(2) {

View File

@@ -0,0 +1,143 @@
<template>
<div class="tp-vote-box">
<div class="tp-vote-info">
<span>{{ votes?.title }}</span>
<span>{{ votes?.count }}人已参与|{{ votes?.is_over ? "已截止" : "投票中" }}</span>
</div>
<div class="tp-vote-list">
<div v-for="(item, index) in votes?.data" :key="index" class="tp-vote-item">
<div class="tp-vote-item-title">
<span>{{ item.title }}</span>
<span>
<span>{{ item.count }}</span>
<span>{{ item.percent.toFixed(2) }}%</span>
</span>
</div>
<div class="tp-vote-progress">
<div class="tp-vote-val" :style="{ width: item.percent + '%' }" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import Mys from "../../plugins/Mys";
interface TpVote {
insert: {
vote: {
id: string;
uid: string;
};
};
}
interface TpVoteProps {
data: TpVote;
}
interface TpVoteInfo {
title: string;
count: number;
is_over: boolean;
data: Array<{
title: string;
count: number;
percent: number;
}>;
}
const props = defineProps<TpVoteProps>();
const votes = ref<TpVoteInfo>();
onMounted(async () => {
const vote = props.data.insert.vote;
const voteInfo = await Mys.Vote.get(vote.id, vote.uid);
const voteResult = await Mys.Vote.result(vote.id, vote.uid);
votes.value = {
title: voteInfo.title,
count: voteResult.user_cnt,
is_over: voteResult.is_over,
data: voteInfo.vote_option_indexes.map((item, index) => ({
title: item,
count: voteResult.option_stats[index],
percent: (voteResult.option_stats[index] / voteResult.user_cnt) * 100,
})),
};
});
</script>
<style lang="css" scoped>
.tp-vote-box {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
row-gap: 10px;
}
.tp-vote-info {
display: flex;
align-items: center;
justify-content: space-between;
}
.tp-vote-info :first-child {
font-size: 20px;
font-weight: bold;
}
.tp-vote-list {
display: grid;
gap: 10px 20px;
grid-template-columns: repeat(2, 1fr);
}
.tp-vote-item {
display: flex;
flex-direction: column;
gap: 5px;
}
.tp-vote-item-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.tp-vote-item-title :first-child {
font-size: 16px;
font-weight: bold;
}
.tp-vote-item-title :last-child {
display: flex;
flex-direction: column;
gap: 5px;
}
.tp-vote-item-title :last-child :first-child {
font-size: 12px;
}
.tp-vote-item-title :last-child :last-child {
font-size: 10px;
}
.tp-vote-progress {
overflow: hidden;
width: 100%;
height: 10px;
border-radius: 5px;
background: var(--common-shadow-1);
}
.tp-vote-val {
height: 100%;
border-radius: 5px;
background: linear-gradient(to right, #fb7299, #00aeec);
}
</style>

View File

@@ -0,0 +1,202 @@
<template>
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="5px">
<div class="tpoc-box">
<div class="tpoc-top">
<span>{{ props.collection.collection_title }}</span>
<span>合集ID{{ props.collection.collection_id }}</span>
</div>
<div class="tpoc-list">
<!-- todo 加上封面 -->
<div
class="tpoc-item"
v-for="(item, index) in posts"
:key="index"
@click="toPost(item.postId)"
>
<div class="tpoc-item-title" :title="item.title">{{ item.title }}</div>
<div class="tpoc-item-info">
<div class="tpoc-iii">
<span title="创建时间">
<v-icon size="12">mdi-clock-time-four-outline</v-icon>
<span>{{ getDate(item.created) }}</span>
</span>
<span title="最后更新时间">
<v-icon size="12">mdi-clock-time-four-outline</v-icon>
<span>{{ getDate(item.updated) }}</span>
</span>
</div>
<div class="tpoc-iii">
<span title="评论数">
<v-icon size="12">mdi-comment</v-icon>
<span>{{ item.comments }}</span>
</span>
<span title="点赞数">
<v-icon size="12">mdi-thumb-up</v-icon>
<span>{{ item.likes }}</span>
</span>
</div>
</div>
</div>
</div>
</div>
</TOverlay>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import Mys from "../../plugins/Mys";
import showSnackbar from "../func/snackbar";
import TOverlay from "../main/t-overlay.vue";
interface TpoCollectionProps {
collection: TGApp.Plugins.Mys.Post.Collection;
modelValue: boolean;
}
type TpoCollectionEmits = (e: "update:modelValue", value: boolean) => void;
interface TpoCollectionItem {
postId: string;
title: string;
created: number;
updated: number;
comments: number;
likes: number;
}
const props = defineProps<TpoCollectionProps>();
const emits = defineEmits<TpoCollectionEmits>();
const visible = computed({
get: () => props.modelValue,
set: (value) => {
emits("update:modelValue", value);
},
});
const posts = ref<TpoCollectionItem[]>([]);
const router = useRouter();
onMounted(async () => {
const collectionPosts = await Mys.Collection.data(props.collection.collection_id);
const tempArr: TpoCollectionItem[] = [];
for (const postItem of collectionPosts) {
const post: TpoCollectionItem = {
postId: postItem.post.post_id,
title: postItem.post.subject,
created: postItem.post.created_at,
updated: postItem.post.updated_at,
comments: postItem.stat.reply_num,
likes: postItem.stat.like_num,
};
tempArr.push(post);
}
posts.value = tempArr;
});
function onCancel() {
visible.value = false;
}
function getDate(date: number): string {
return new Date(date * 1000).toLocaleString().replace(/\//g, "-").split(" ")[0];
}
function toPost(postId: string) {
if (router.currentRoute.value.params.post_id === postId) {
showSnackbar({
text: "已经在当前帖子",
color: "warn",
});
return;
}
router.push({
name: "帖子详情",
params: {
post_id: postId,
},
});
}
</script>
<style lang="css" scoped>
.tpoc-box {
padding: 10px;
border-radius: 5px;
background-color: var(--box-bg-1);
}
.tpoc-top {
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--common-shadow-2);
margin-bottom: 10px;
}
.tpoc-top :nth-child(1) {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.tpoc-top :nth-child(2) {
font-size: 14px;
opacity: 0.8;
}
.tpoc-list {
display: flex;
width: 400px;
max-height: 400px;
flex-direction: column;
padding-right: 10px;
overflow-y: auto;
row-gap: 5px;
}
.tpoc-item {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
background: var(--box-bg-2);
color: var(--box-text-2);
cursor: pointer;
}
.tpoc-item-title {
overflow: hidden;
width: 100%;
font-family: var(--font-title);
font-size: 16px;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
.tpoc-item-info {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
font-size: 12px;
opacity: 0.8;
}
.tpoc-iii {
display: flex;
align-items: center;
justify-content: center;
column-gap: 5px;
}
.tpoc-iii span {
display: flex;
align-items: center;
justify-content: center;
column-gap: 2px;
}
</style>

View File

@@ -5,8 +5,16 @@
:name="`第${props.modelValue.id}间`"
mode="level"
/>
<TuaDetailBattle title="上半" :model-value="props.modelValue.upBattle" />
<TuaDetailBattle title="下半" :model-value="props.modelValue.downBattle" />
<TuaDetailBattle
v-if="props.modelValue.upBattle"
title="上半"
:model-value="props.modelValue.upBattle"
/>
<TuaDetailBattle
v-if="props.modelValue.downBattle"
title="下半"
:model-value="props.modelValue.downBattle"
/>
</div>
</template>
<script lang="ts" setup>

View File

@@ -62,8 +62,11 @@ const props = defineProps<TuaDetailTitleProps>();
width: 20px;
height: 20px;
padding: 1px;
border-radius: 50%;
background: var(--tgc-btn-1);
filter: invert(22%) sepia(7%) saturate(1241%) hue-rotate(182deg) brightness(95%) contrast(99%);
object-fit: cover;
}
.dark .tud-t-val img {
filter: none;
}
</style>

View File

@@ -9,8 +9,7 @@
class="twc-constellation-tab"
density="compact"
>
<!-- todo 换成本地资源 -->
<img :src="`https://api.ambr.top/assets/UI/${item.Icon}.png`" alt="icon" />
<img :src="`/icon/constellations/${item.Icon}.webp`" alt="icon" />
<span v-if="tab === item.Name">{{ item.Name }}</span>
</v-tab>
</v-tabs>

View File

@@ -9,8 +9,7 @@
class="twc-skill-tab"
density="compact"
>
<!-- todo 换成本地资源 -->
<img :src="`https://api.ambr.top/assets/UI/${item.icon}.png`" alt="icon" />
<img :src="`/icon/talents/${item.icon}.webp`" alt="icon" />
<span v-if="tab === item.name">{{ item.name }}</span>
</v-tab>
</v-tabs>

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,11 @@
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSub" />
<div class="ua-box">
<v-tabs v-model="userTab" direction="vertical" align-tabs="start" class="ua-tab">
<v-tab v-for="item in localAbyss" :key="item.id" :value="item.id" @click="toAbyss(item.id)">
{{ item.id }}
</v-tab>
<div class="ua-tabs">
<v-tab v-for="item in localAbyss" :key="item.id" :value="item.id" @click="toAbyss(item.id)">
{{ item.id }}
</v-tab>
</div>
<div class="ua-tab-bottom">
<v-btn class="ua-btn" @click="shareAbyss">
<v-icon>mdi-share</v-icon>
@@ -66,6 +68,7 @@
</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import showSnackbar from "../../components/func/snackbar";
@@ -80,7 +83,7 @@ import { generateShareImg } from "../../utils/TGShare";
import TGRequest from "../../web/request/TGRequest";
// store
const userStore = useUserStore();
const userStore = storeToRefs(useUserStore());
// loading
const loading = ref<boolean>(true);
const loadingTitle = ref<string>();
@@ -88,14 +91,13 @@ const loadingSub = ref<string>();
// data
const userTab = ref<number>(0);
const user = ref<TGApp.Sqlite.Account.Game>(userStore.getCurAccount());
const user = ref<TGApp.Sqlite.Account.Game>(userStore.account.value);
const localAbyss = ref<TGApp.Sqlite.Abyss.SingleTable[]>([]);
const localAbyssID = ref<number[]>([]);
const curAbyss = ref<TGApp.Sqlite.Abyss.SingleTable>(<TGApp.Sqlite.Abyss.SingleTable>{});
const abyssRef = ref<HTMLElement>(<HTMLElement>{});
// todo 优化数据加载
onMounted(async () => {
loadingTitle.value = "正在加载深渊数据";
await initAbyssData();
@@ -103,7 +105,9 @@ onMounted(async () => {
});
async function initAbyssData(): Promise<void> {
localAbyss.value = await TGSqlite.getAbyss(user.value.gameUid);
const abyssGet = await TGSqlite.getAbyss(user.value.gameUid);
if (abyssGet.length === 0) return;
localAbyss.value = abyssGet;
localAbyss.value.forEach((item) => {
localAbyssID.value.push(item.id);
});
@@ -114,7 +118,7 @@ async function initAbyssData(): Promise<void> {
async function getAbyssData(): Promise<void> {
loadingTitle.value = "正在获取深渊数据";
loading.value = true;
if (!userStore.cookie) {
if (!userStore.cookie.value) {
showSnackbar({
text: "未登录",
color: "error",
@@ -123,10 +127,10 @@ async function getAbyssData(): Promise<void> {
return;
}
const cookie = {
account_id: userStore.cookie.account_id,
cookie_token: userStore.cookie.cookie_token,
ltoken: userStore.cookie.ltoken,
ltuid: userStore.cookie.ltuid,
account_id: userStore.cookie.value.account_id,
cookie_token: userStore.cookie.value.cookie_token,
ltoken: userStore.cookie.value.ltoken,
ltuid: userStore.cookie.value.ltuid,
};
loadingTitle.value = "正在获取上期深渊数据";
const resP = await TGRequest.User.byCookie.getAbyss(cookie, "2", user.value);
@@ -227,6 +231,12 @@ async function uploadAbyss(): Promise<void> {
font-family: var(--font-text);
}
.ua-tabs {
max-height: calc(100% - 150px);
margin-top: 5px;
overflow-y: auto;
}
/* stylelint-disable selector-class-pattern */
.ua-tab.v-tabs.v-slide-group--vertical {
height: 100%;
@@ -306,7 +316,8 @@ async function uploadAbyss(): Promise<void> {
flex-direction: column;
align-items: center;
border-radius: 5px;
background: var(--common-shadow-2);
background: var(--common-shadow-t-2);
box-shadow: 0 0 5px var(--common-shadow-2);
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 1.5rem;

View File

@@ -54,6 +54,7 @@
/>
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import DucDetailOverlay from "../../components/devCharacter/duc-detail-overlay.vue";
@@ -67,8 +68,8 @@ import { generateShareImg } from "../../utils/TGShare";
import TGRequest from "../../web/request/TGRequest";
// store
const userStore = useUserStore();
const user = userStore.getCurAccount();
const userStore = storeToRefs(useUserStore());
const user = userStore.account.value;
// loading
const loading = ref<boolean>(false);
@@ -133,7 +134,7 @@ async function loadRole(): Promise<void> {
async function refreshRoles(): Promise<void> {
loadingTitle.value = "正在获取角色数据";
loading.value = true;
if (!userStore.cookie) {
if (!userStore.cookie.value) {
showSnackbar({
text: "请先登录",
color: "error",
@@ -142,10 +143,10 @@ async function refreshRoles(): Promise<void> {
return;
}
const cookie = {
account_id: userStore.cookie.account_id,
cookie_token: userStore.cookie.cookie_token,
ltoken: userStore.cookie.ltoken,
ltuid: userStore.cookie.ltuid,
account_id: userStore.cookie.value.account_id,
cookie_token: userStore.cookie.value.cookie_token,
ltoken: userStore.cookie.value.ltoken,
ltuid: userStore.cookie.value.ltuid,
};
const res = await TGRequest.User.byLToken.getRoleList(cookie, user);
if (Array.isArray(res)) {
@@ -166,7 +167,7 @@ async function refreshRoles(): Promise<void> {
async function refreshTalent(): Promise<void> {
loadingTitle.value = "正在获取天赋数据";
loading.value = true;
if (!userStore.cookie) {
if (!userStore.cookie.value) {
showSnackbar({
text: "请先登录",
color: "error",
@@ -178,8 +179,8 @@ async function refreshTalent(): Promise<void> {
loadingTitle.value = `正在获取${role.name}的天赋数据`;
loadingSub.value = `CID${role.cid}`;
const res = await TGRequest.User.calculate.getSyncAvatarDetail(
userStore.cookie.account_id,
userStore.cookie.cookie_token,
userStore.cookie.value.account_id,
userStore.cookie.value.cookie_token,
user.gameUid,
role.cid,
);

View File

@@ -2,13 +2,7 @@
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSub" />
<div class="gacha-top-bar">
<div class="gacha-top-title">祈愿记录</div>
<v-select
v-model="uidCur"
class="gacha-top-select"
:items="selectItem"
variant="outlined"
:theme="vuetifyTheme"
/>
<v-select v-model="uidCur" class="gacha-top-select" :items="selectItem" variant="outlined" />
<div class="gacha-top-btns">
<v-btn prepend-icon="mdi-refresh" class="gacha-top-btn" @click="confirmRefresh">刷新</v-btn>
<v-btn prepend-icon="mdi-import" class="gacha-top-btn" @click="handleImportBtn()">
@@ -48,7 +42,8 @@
</template>
<script lang="ts" setup>
import { dialog, fs, path } from "@tauri-apps/api";
import { computed, onMounted, ref, watch } from "vue";
import { storeToRefs } from "pinia";
import { onMounted, ref, watch } from "vue";
import showConfirm from "../../components/func/confirm";
import showSnackbar from "../../components/func/snackbar";
@@ -57,15 +52,13 @@ import GroOverview from "../../components/gachaRecord/gro-overview.vue";
import ToLoading from "../../components/overlay/to-loading.vue";
import { AppCharacterData, AppWeaponData } from "../../data";
import TGSqlite from "../../plugins/Sqlite";
import { useAppStore } from "../../store/modules/app";
import { useUserStore } from "../../store/modules/user";
import { backupUigfData, exportUigfData, readUigfData, verifyUigfData } from "../../utils/UIGF";
import TGRequest from "../../web/request/TGRequest";
// store
const userStore = useUserStore();
const appStore = useAppStore();
const account = userStore.getCurAccount();
const userStore = storeToRefs(useUserStore());
const account = userStore.account.value;
const authkey = ref<string>("");
// loading
@@ -78,9 +71,6 @@ const selectItem = ref<string[]>([]);
const uidCur = ref<string>("");
const gachaListCur = ref<TGApp.Sqlite.GachaRecords.SingleTable[]>([]);
const tab = ref<string>("");
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
});
onMounted(async () => {
loadingTitle.value = "正在获取祈愿 UID 列表";
@@ -119,7 +109,7 @@ async function confirmRefresh(): Promise<void> {
}
loadingTitle.value = "正在获取 authkey";
loading.value = true;
if (!userStore.cookie) {
if (!userStore.cookie.value) {
showSnackbar({
color: "error",
text: "请先登录",
@@ -128,10 +118,10 @@ async function confirmRefresh(): Promise<void> {
return;
}
const cookie = {
stoken: userStore.cookie.stoken,
mid: userStore.cookie.mid,
stoken: userStore.cookie.value.stoken,
mid: userStore.cookie.value.mid,
};
const gameUid = userStore.getCurAccount().gameUid;
const gameUid = account.gameUid;
const authkeyRes = await TGRequest.User.getAuthkey(cookie, gameUid);
if (typeof authkeyRes === "string") {
authkey.value = authkeyRes;
@@ -391,7 +381,7 @@ async function deleteGacha(): Promise<void> {
return;
}
const uidList = await TGSqlite.getUidList();
let secondConfirm: string | boolean = "";
let secondConfirm: string | boolean | undefined;
if (uidList.length <= 1) {
secondConfirm = await showConfirm({
title: "删除后数据库将为空,确定删除?",

View File

@@ -32,6 +32,7 @@
</div>
</template>
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import showSnackbar from "../../components/func/snackbar";
@@ -47,8 +48,8 @@ import { generateShareImg } from "../../utils/TGShare";
import TGRequest from "../../web/request/TGRequest";
// store
const userStore = useUserStore();
const user = userStore.getCurAccount();
const userStore = storeToRefs(useUserStore());
const user = userStore.account.value;
// loading
const loading = ref<boolean>(false);
@@ -82,7 +83,7 @@ async function initUserRecordData(): Promise<void> {
async function refresh(): Promise<void> {
loadingTitle.value = "正在获取战绩数据";
loading.value = true;
if (!userStore.cookie) {
if (!userStore.cookie.value) {
showSnackbar({
text: "请先登录",
color: "error",
@@ -91,8 +92,8 @@ async function refresh(): Promise<void> {
return;
}
const cookie = {
account_id: userStore.cookie.account_id,
cookie_token: userStore.cookie.cookie_token,
account_id: userStore.cookie.value.account_id,
cookie_token: userStore.cookie.value.cookie_token,
};
const res = await TGRequest.User.getRecord(cookie, user);
if (!("retcode" in res)) {

View File

@@ -0,0 +1,3 @@
<template>
<h1>WIKI-Material</h1>
</template>

169
src/pages/WIKI/Namecard.vue Normal file
View File

@@ -0,0 +1,169 @@
<template>
<div class="tw-nc-box">
<v-text-field
v-model="search"
prepend-inner-icon="mdi-magnify"
label="搜索"
hide-details
variant="outlined"
@click:prepend-inner="searchNamecard"
@keyup.enter="searchNamecard"
/>
<div class="tw-nc-list">
<v-virtual-scroll :items="sortNameCardsData" :item-height="80" class="cards-list">
<template #default="{ item }">
<v-list
:style="{ backgroundImage: item.name === '原神·印象' ? 'none' : `url(${item.bg})` }"
class="card-box"
@click="toNameCard(item)"
>
<v-list-item :title="item.name" :subtitle="item.desc">
<template #prepend>
<v-img width="80px" style="margin-right: 10px" :src="item.icon" />
</template>
</v-list-item>
</v-list>
</template>
</v-virtual-scroll>
</div>
</div>
<ToNamecard v-model="visible" :data="curNameCard">
<template #left>
<div class="card-arrow left" @click="switchCard(false)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
<template #right>
<div class="card-arrow" @click="switchCard(true)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
</ToNamecard>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import showSnackbar from "../../components/func/snackbar";
import ToNamecard from "../../components/overlay/to-namecard.vue";
import { AppNameCardsData } from "../../data";
const curNameCard = ref<TGApp.App.NameCard.Item>();
const sortNameCardsData = ref<TGApp.App.NameCard.Item[]>([]);
const curIndex = ref(0);
const total = ref(0);
const visible = ref(false);
const search = ref("");
onMounted(() => {
sortData(AppNameCardsData);
});
function sortData(data: TGApp.App.NameCard.Item[]) {
sortNameCardsData.value = data.sort((a, b) => a.type - b.type || a.index - b.index);
curIndex.value = 0;
total.value = sortNameCardsData.value.length;
curNameCard.value = sortNameCardsData.value[curIndex.value];
showSnackbar({
text: `共搜索到 ${sortNameCardsData.value.length} 个结果`,
color: "success",
});
}
function toNameCard(item: TGApp.App.NameCard.Item) {
curNameCard.value = item;
curIndex.value = sortNameCardsData.value.findIndex((i) => i.name === item.name);
visible.value = true;
}
function switchCard(isNext: boolean) {
if (isNext) {
if (curIndex.value === total.value - 1) {
showSnackbar({
text: "已经是最后一个了",
color: "warn",
});
return;
}
curIndex.value++;
} else {
if (curIndex.value === 0) {
showSnackbar({
text: "已经是第一个了",
color: "warn",
});
return;
}
curIndex.value--;
}
curNameCard.value = sortNameCardsData.value[curIndex.value];
}
function searchNamecard() {
if (!search.value) {
sortData(AppNameCardsData);
} else if (search.value === "") {
if (sortNameCardsData.value.length === AppNameCardsData.length) {
showSnackbar({
text: "请先输入搜索内容",
color: "warn",
});
} else {
sortData(AppNameCardsData);
}
} else {
const searchResult = AppNameCardsData.filter((item) => {
return item.name.includes(search.value) || item.desc.includes(search.value);
});
sortData(searchResult);
}
}
</script>
<style lang="css" scoped>
.tw-nc-box {
display: flex;
flex-direction: column;
row-gap: 10px;
}
.tw-nc-list {
overflow: auto;
height: calc(100vh - 100px);
padding-right: 10px;
}
.card-box {
width: 100%;
height: 80px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px 50px 50px 10px;
margin-bottom: 10px;
background-color: var(--box-bg-1);
background-position: right;
background-repeat: no-repeat;
cursor: pointer;
font-family: var(--font-title);
}
.card-arrow {
position: relative;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dark .card-arrow {
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
}
.card-arrow img {
width: 100%;
height: 100%;
}
.card-arrow.left img {
transform: rotate(180deg);
}
</style>

View File

@@ -37,10 +37,9 @@
</div>
<!-- 右侧内容-->
<div class="right-wrap">
<div
v-if="selectedSeries !== 0 && selectedSeries !== 17 && selectedSeries !== -1 && !loading"
>
<div v-if="curCardName !== '' && selectedSeries !== -1 && !loading">
<v-list
v-if="curCard"
class="achi-series"
:style="{ backgroundImage: `url(${curCard.bg})` }"
@click="openImg()"
@@ -105,14 +104,14 @@
<script lang="ts" setup>
import { dialog, fs, path } from "@tauri-apps/api";
import { computed, nextTick, onBeforeMount, onMounted, reactive, ref } from "vue";
import { computed, nextTick, onBeforeMount, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import showConfirm from "../../components/func/confirm";
import showSnackbar from "../../components/func/snackbar";
import ToLoading from "../../components/overlay/to-loading.vue";
import ToNamecard from "../../components/overlay/to-namecard.vue";
import { AppAchievementSeriesData } from "../../data";
import { AppAchievementSeriesData, AppNameCardsData } from "../../data";
import TGSqlite from "../../plugins/Sqlite";
import { useAchievementsStore } from "../../store/modules/achievements";
import { getNowStr } from "../../utils/toolFunc";
@@ -129,7 +128,8 @@ const hideFin = ref<boolean>(false);
const showNameCard = ref<boolean>(false);
// data
const title = ref(achievementsStore.title);
let curCard = reactive({ profile: "", bg: "", icon: "", name: "", desc: "" });
const curCardName = ref<string>("");
let curCard = ref<TGApp.App.NameCard.Item>();
// series
const allSeriesData = ref<TGApp.Sqlite.Achievement.SeriesTable[]>([]);
const selectedSeries = ref<number>(-1);
@@ -156,7 +156,7 @@ async function switchHideFin() {
title: "是否切换显示已完成?",
text,
});
if (res === false) {
if (!res) {
showSnackbar({
color: "warn",
text: "已取消切换",
@@ -211,15 +211,9 @@ async function selectSeries(index: number): Promise<void> {
selectedSeries.value = index;
selectedAchievement.value = await getAchiData("series", index.toString());
loadingTitle.value = "正在查找对应的成就名片";
if (selectedSeries.value !== 0 && selectedSeries.value !== 17) {
const cardGet = await TGSqlite.getNameCard(index);
curCard = {
profile: `/source/nameCard/profile/${cardGet.name}.webp`,
bg: `/source/nameCard/bg/${cardGet.name}.webp`,
icon: `/source/nameCard/icon/${cardGet.name}.webp`,
name: cardGet.name,
desc: cardGet.desc,
};
curCardName.value = await getNameCardName(index);
if (curCardName.value !== "") {
curCard.value = AppNameCardsData.find((item) => item.name === curCardName.value);
}
// 右侧滚动到顶部
const rightWrap = document.querySelector(".right-wrap");
@@ -358,37 +352,37 @@ async function handleImportOuter(app: string): Promise<void> {
title: "是否导入祈愿数据?",
text: `来源APP${app}`,
});
if (confirm) {
// 读取 剪贴板
const clipboard = await window.navigator.clipboard.readText();
let data: TGApp.Plugins.UIAF.Achievement[];
// 里面是完整的 uiaf 数据
try {
data = JSON.parse(clipboard).list;
loadingTitle.value = "正在导入数据";
loading.value = true;
await TGSqlite.mergeUIAF(data);
loading.value = false;
showSnackbar({
color: "success",
text: "导入成功,即将刷新页面",
});
} catch (e) {
console.error(e);
showSnackbar({
color: "error",
text: "读取 UIAF 数据失败,请检查文件是否符合规范",
});
} finally {
setTimeout(async () => {
await router.push("/achievements");
}, 1500);
}
} else {
if (!confirm) {
showSnackbar({
color: "warn",
text: "已取消导入",
});
return;
}
// 读取 剪贴板
const clipboard = await window.navigator.clipboard.readText();
let data: TGApp.Plugins.UIAF.Achievement[];
// 里面是完整的 uiaf 数据
try {
data = JSON.parse(clipboard).list;
loadingTitle.value = "正在导入数据";
loading.value = true;
await TGSqlite.mergeUIAF(data);
loading.value = false;
showSnackbar({
color: "success",
text: "导入成功,即将刷新页面",
});
} catch (e) {
console.error(e);
showSnackbar({
color: "error",
text: "读取 UIAF 数据失败,请检查文件是否符合规范",
});
} finally {
setTimeout(async () => {
await router.push("/achievements");
}, 1500);
}
}
@@ -483,6 +477,16 @@ async function getAchiData(
return await db.select(sql);
}
// 获取成就名片
async function getNameCardName(series: number): Promise<string> {
const db = await TGSqlite.getDB();
const sql = `SELECT nameCard
FROM AchievementSeries
WHERE id = ${series};`;
const res: Array<{ nameCard: string }> = await db.select(sql);
return res[0].nameCard;
}
// 更新成就数据
async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Promise<void> {
const db = await TGSqlite.getDB();
@@ -592,10 +596,11 @@ async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Pro
height: 40px;
padding: 5px;
border-radius: 5px;
background:
linear-gradient(to bottom, rgb(255 255 255 / 15%) 0%, rgb(0 0 0 / 15%) 100%),
radial-gradient(at top center, rgb(255 255 255 / 40%) 0%, rgb(0 0 0 / 40%) 120%) #989898;
background-blend-mode: multiply, multiply;
filter: invert(60%) brightness(100%);
}
.dark .series-icon {
filter: none;
}
.series-content {
@@ -627,6 +632,7 @@ async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Pro
height: 80px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px 50px 50px 10px;
background-color: var(--box-bg-1);
background-position: right;
background-repeat: no-repeat;
cursor: pointer;

View File

@@ -1,156 +1,142 @@
<template>
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSub" />
<ToGameLogin v-model="scan" />
<v-list class="config-list">
<v-list-subheader :inset="true" class="config-header" title="应用信息" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
<template #prepend>
<img class="config-icon" src="/platforms/tauri.webp" alt="Tauri" />
</template>
<template #append>
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item>
<template #prepend>
<img class="config-icon" src="/icon.webp" alt="App" />
</template>
<v-list-item-title>
应用版本
<v-btn
size="small"
variant="outlined"
@click="toOuter('https://github.com/BTMuli/TeyvatGuide/releases/latest')"
>
BETA
</v-btn>
</v-list-item-title>
<template #append>
<v-list-item-subtitle>{{ versionApp }}.{{ buildTime }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="成就版本">
<template #prepend>
<img class="config-icon" src="../../assets/icons/achievements.svg" alt="Achievements" />
</template>
<template #append>
<v-list-item-subtitle>{{ achievementsStore.lastVersion }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="登录信息">
<v-list-item-subtitle v-show="userInfo?.nickname !== '未登录'">
{{ userInfo?.nickname }} uid:{{ userInfo?.uid }}
</v-list-item-subtitle>
<v-list-item-subtitle v-show="userInfo?.nickname === '未登录'">
未登录请扫码登录
</v-list-item-subtitle>
<template #prepend>
<img class="config-icon" :src="userInfo?.avatar" alt="Login" />
</template>
<template #append>
<v-btn class="config-btn" @click="confirmScanLogin">扫码登录</v-btn>
<v-btn class="config-btn" @click="confirmRefreshUser"> 刷新数据</v-btn>
</template>
</v-list-item>
<v-list-subheader :inset="true" class="config-header" title="系统信息" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item title="系统平台" prepend-icon="mdi-microsoft-windows">
<template #append>
<v-list-item-subtitle>{{ osPlatform }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="系统版本" prepend-icon="mdi-monitor-dashboard">
<template #append>
<v-list-item-subtitle>{{ osVersion }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="数据库更新时间" prepend-icon="mdi-database-sync">
<template #append>
<v-list-item-subtitle
>{{ dbInfo.find((item) => item.key === "dataUpdated")?.value }}
<ToGameLogin v-model="scan" @success="refreshUser" />
<div class="config-box">
<v-list class="config-list">
<v-list-subheader :inset="true" class="config-header" title="应用信息" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
<template #prepend>
<img class="config-icon" src="/platforms/tauri.webp" alt="Tauri" />
</template>
<template #append>
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="成就版本">
<template #prepend>
<img class="config-icon" src="../../assets/icons/achievements.svg" alt="Achievements" />
</template>
<template #append>
<v-list-item-subtitle>{{ achievementsStore.lastVersion }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="登录信息">
<v-list-item-subtitle v-show="userInfo?.nickname !== '未登录'">
{{ userInfo?.nickname }} uid:{{ userInfo?.uid }}
</v-list-item-subtitle>
</template>
<v-list-item-subtitle
>更新于
{{ dbInfo.find((item) => item.key === "dataUpdated")?.updated }}
</v-list-item-subtitle>
</v-list-item>
<v-list-item title="数据库版本" prepend-icon="mdi-database-search">
<template #append>
<v-list-item-subtitle
>{{ dbInfo.find((item) => item.key === "appVersion")?.value }}
<v-list-item-subtitle v-show="userInfo?.nickname === '未登录'">
未登录请扫码登录
</v-list-item-subtitle>
</template>
<v-list-item-subtitle
>更新于
{{ dbInfo.find((item) => item.key === "appVersion")?.updated }}
</v-list-item-subtitle>
</v-list-item>
<v-list-subheader :inset="true" class="config-header" title="设置" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item prepend-icon="mdi-camera-iris">
<v-select
v-model="showHome"
:items="homeStore.getShowItems()"
label="首页显示组件"
:multiple="true"
:chips="true"
:theme="vuetifyTheme"
/>
<template #append>
<v-btn class="config-btn" @click="submitHome"> 确定</v-btn>
</template>
</v-list-item>
<v-list-item prepend-icon="mdi-database-export" title="数据备份" @click="confirmBackup" />
<v-list-item prepend-icon="mdi-database-import" title="数据恢复" @click="confirmRestore" />
<v-list-item prepend-icon="mdi-database-arrow-up" title="数据更新" @click="confirmUpdate()" />
<v-list-subheader :inset="true" class="config-header" title="调试" @click="tryShowReset" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item
v-if="isDevEnv"
title="调试模式"
subtitle="开启后将显示调试信息"
prepend-icon="mdi-bug-play"
>
<template #append>
<v-switch
v-model="appStore.devMode"
:label="appStore.devMode ? '开启' : '关闭'"
:inset="true"
color="#FAC51E"
@click="submitDevMode"
<template #prepend>
<img class="config-icon user" :src="userInfo?.avatar" alt="Login" />
</template>
<template #append>
<v-btn class="config-btn" @click="confirmScanLogin">扫码登录</v-btn>
<v-btn class="config-btn" @click="confirmRefreshUser"> 刷新数据</v-btn>
</template>
</v-list-item>
<v-list-subheader :inset="true" class="config-header" title="系统信息" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item title="系统平台" prepend-icon="mdi-microsoft-windows">
<template #append>
<v-list-item-subtitle>{{ osPlatform }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="系统版本" prepend-icon="mdi-monitor-dashboard">
<template #append>
<v-list-item-subtitle>{{ osVersion }}</v-list-item-subtitle>
</template>
</v-list-item>
<v-list-item title="数据库更新时间" prepend-icon="mdi-database-sync">
<template #append>
<v-list-item-subtitle
>{{ dbInfo.find((item) => item.key === "dataUpdated")?.value }}
</v-list-item-subtitle>
</template>
<v-list-item-subtitle
>更新于
{{ dbInfo.find((item) => item.key === "dataUpdated")?.updated }}
</v-list-item-subtitle>
</v-list-item>
<v-list-item title="数据库版本" prepend-icon="mdi-database-search">
<template #append>
<v-list-item-subtitle
>{{ dbInfo.find((item) => item.key === "appVersion")?.value }}
</v-list-item-subtitle>
</template>
<v-list-item-subtitle
>更新于
{{ dbInfo.find((item) => item.key === "appVersion")?.updated }}
</v-list-item-subtitle>
</v-list-item>
<v-list-subheader :inset="true" class="config-header" title="设置" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item prepend-icon="mdi-camera-iris">
<v-select
v-model="showHome"
:items="homeStore.getShowItems()"
label="首页显示组件"
:multiple="true"
:chips="true"
/>
</template>
</v-list-item>
<v-list-item prepend-icon="mdi-refresh" title="刷新设备信息" @click="confirmUpdateDevice" />
<v-list-item prepend-icon="mdi-database-remove" title="清除缓存" @click="confirmDelCache" />
<v-list-item
v-show="showReset"
title="重置数据库"
prepend-icon="mdi-database-settings"
@click="confirmResetDB()"
/>
<v-list-item prepend-icon="mdi-cog-sync" title="恢复默认设置" @click="confirmResetApp" />
<v-list-subheader :inset="true" class="config-header" title="路径" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item
prepend-icon="mdi-folder-key"
title="本地数据库路径"
:subtitle="appStore.dataPath.dbDataPath"
/>
<v-list-item
prepend-icon="mdi-folder-account"
title="本地用户数据路径"
:subtitle="appStore.dataPath.userDataDir"
/>
</v-list>
<template #append>
<v-btn class="config-btn" @click="submitHome"> 确定</v-btn>
</template>
</v-list-item>
<v-list-item prepend-icon="mdi-database-export" title="数据备份" @click="confirmBackup" />
<v-list-item prepend-icon="mdi-database-import" title="数据恢复" @click="confirmRestore" />
<v-list-item prepend-icon="mdi-database-arrow-up" title="数据更新" @click="confirmUpdate()" />
<v-list-subheader :inset="true" class="config-header" title="调试" @click="tryShowReset" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item
v-if="isDevEnv"
title="调试模式"
subtitle="开启后将显示调试信息"
prepend-icon="mdi-bug-play"
>
<template #append>
<v-switch
v-model="appStore.devMode"
:label="appStore.devMode ? '开启' : '关闭'"
:inset="true"
color="#FAC51E"
@click="submitDevMode"
/>
</template>
</v-list-item>
<v-list-item prepend-icon="mdi-refresh" title="刷新设备信息" @click="confirmUpdateDevice" />
<v-list-item prepend-icon="mdi-database-remove" title="清除缓存" @click="confirmDelCache" />
<v-list-item
v-show="showReset"
title="重置数据库"
prepend-icon="mdi-database-settings"
@click="confirmResetDB()"
/>
<v-list-item prepend-icon="mdi-cog-sync" title="恢复默认设置" @click="confirmResetApp" />
<v-list-subheader :inset="true" class="config-header" title="路径" />
<v-divider :inset="true" class="border-opacity-75" />
<v-list-item
prepend-icon="mdi-folder-key"
title="本地数据库路径"
:subtitle="appStore.dataPath.dbDataPath"
/>
<v-list-item
prepend-icon="mdi-folder-account"
title="本地用户数据路径"
:subtitle="appStore.dataPath.userDataDir"
/>
</v-list>
<TAppBadge />
</div>
</template>
<script lang="ts" setup>
import { app, fs, invoke, os, process as TauriProcess } from "@tauri-apps/api";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref } from "vue";
import TAppBadge from "../../components/app/t-appBadge.vue";
import showConfirm from "../../components/func/confirm";
import showSnackbar from "../../components/func/snackbar";
import ToGameLogin from "../../components/overlay/to-gameLogin.vue";
@@ -170,7 +156,7 @@ import { restoreAbyssData, restoreCookieData } from "../../web/utils/restoreData
// Store
const appStore = useAppStore();
const userStore = useUserStore();
const userStore = storeToRefs(useUserStore());
const homeStore = useHomeStore();
const achievementsStore = useAchievementsStore();
@@ -179,18 +165,11 @@ const isDevEnv = ref<boolean>(import.meta.env.MODE === "development");
// About App
const versionApp = ref<string>("");
const versionTauri = ref<string>("");
const buildTime = computed(() => appStore.buildTime);
// About OS
const osPlatform = ref<string>("");
const osVersion = ref<string>("");
const dbInfo = ref<
Array<{
key: string;
value: string;
updated: string;
}>
>([]);
const dbInfo = ref<Array<TGApp.Sqlite.AppData.Item>>([]);
// loading
const loading = ref<boolean>(true);
@@ -202,25 +181,22 @@ const showReset = ref<boolean>(false);
// data
const showHome = ref<string[]>(homeStore.getShowValue());
const userInfo = computed(() => {
const info = userStore.getBriefInfo();
if (info && info.nickname) {
const info = userStore.briefInfo;
if (info.value && info.value.nickname) {
return {
nickname: info.nickname,
uid: info.uid,
desc: info.desc,
avatar: info.avatar,
nickname: info.value.nickname,
uid: info.value.uid,
desc: info.value.desc,
avatar: info.value.avatar,
};
}
return {
nickname: "未登录",
uid: "-1",
desc: "请扫码登录",
avatar: "/source/UI/defaultUser.webp",
avatar: "/source/UI/lumine.webp",
};
});
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
});
// load version
onMounted(async () => {
@@ -252,18 +228,7 @@ function toOuter(url: string): void {
// 扫码登录
async function confirmScanLogin(): Promise<void> {
const confirmRes = await showConfirm({
title: "请使用米游社 APP 执行操作",
text: "请在成功后刷新数据",
});
if (confirmRes) {
scan.value = true;
} else {
showSnackbar({
color: "grey",
text: "已取消扫码登录",
});
}
scan.value = true;
}
// 刷新用户信息
@@ -286,8 +251,13 @@ async function confirmRefreshUser(): Promise<void> {
});
return;
}
const ck = userStore.cookie;
if (JSON.stringify(ck) === "{}") {
await refreshUser();
}
// 刷新用户信息
async function refreshUser(): Promise<void> {
const ck = userStore.cookie.value;
if (ck === undefined || JSON.stringify(ck) === "{}") {
showSnackbar({
color: "error",
text: "扫码登录后才能刷新用户信息!",
@@ -328,7 +298,8 @@ async function confirmRefreshUser(): Promise<void> {
loadingTitle.value = "刷新失败!正在获取用户头像、昵称信息";
failCount++;
}
await userStore.saveCookie(ck);
userStore.cookie.value = ck;
await TGSqlite.saveAppData("cookie", JSON.stringify(ck));
const infoRes = await TGRequest.User.byCookie.getUserInfo(ck.cookie_token, ck.account_id);
if ("retcode" in infoRes) {
console.error(infoRes);
@@ -341,7 +312,8 @@ async function confirmRefreshUser(): Promise<void> {
avatar: infoRes.avatar_url,
desc: infoRes.introduce,
};
await userStore.saveBriefInfo(briefInfo);
userStore.briefInfo.value = briefInfo;
await TGSqlite.saveAppData("userInfo", JSON.stringify(briefInfo));
loadingTitle.value = "获取成功!正在获取用户游戏账号信息";
}
const accountRes = await TGRequest.User.byCookie.getAccounts(ck.cookie_token, ck.account_id);
@@ -349,7 +321,7 @@ async function confirmRefreshUser(): Promise<void> {
loadingTitle.value = "获取成功!正在保存到数据库!";
await TGSqlite.saveAccount(accountRes);
const curAccount = await TGSqlite.getCurAccount();
if (curAccount) userStore.setCurAccount(curAccount);
if (curAccount) userStore.account.value = curAccount;
} else {
console.error(accountRes);
loadingTitle.value = "获取失败!";
@@ -434,7 +406,7 @@ async function confirmRestore(): Promise<void> {
}
loadingSub.value = "正在恢复祈愿数据";
res = await restoreCookieData();
userStore.cookie = await TGSqlite.getCookie();
userStore.cookie.value = await TGSqlite.getCookie();
if (!res) {
fail.push("Cookie");
}
@@ -483,7 +455,7 @@ async function confirmUpdateDevice(): Promise<void> {
title: "确认更新设备信息吗?",
text: `DeviceFp:${localFp}`,
});
if (res === false) {
if (!res) {
showSnackbar({
text: "已取消更新设备信息",
color: "cancel",
@@ -524,7 +496,7 @@ async function confirmDelCache(): Promise<void> {
title: "确认清除缓存吗?",
text: `当前缓存大小为 ${cacheSize},耗时 ${timeEnd - timeStart} 毫秒`,
});
if (res === false) {
if (!res) {
showSnackbar({
color: "grey",
text: "已取消清除缓存",
@@ -572,7 +544,7 @@ async function tryShowReset(): Promise<void> {
text: "请联系开发者获取",
mode: "input",
});
if (res === false) {
if (!res) {
showSnackbar({
color: "grey",
text: "已取消",
@@ -644,9 +616,14 @@ function submitHome(): void {
</script>
<style lang="css" scoped>
.config-box {
position: relative;
display: compact;
}
.config-list {
border-radius: 10px;
margin: 10px;
margin-right: 220px;
background: var(--box-bg-1);
color: var(--box-text-4);
font-family: var(--font-text);
@@ -664,12 +641,16 @@ function submitHome(): void {
height: 40px;
padding: 5px;
border: 1px solid var(--common-shadow-1);
border-radius: 10px;
border-radius: 5px;
margin-right: 15px;
background:
linear-gradient(to bottom, rgb(255 255 255 / 15%) 0%, rgb(0 0 0 / 15%) 100%),
radial-gradient(at top center, rgb(255 255 255 / 40%) 0%, rgb(0 0 0 / 40%) 120%) #989898;
background-blend-mode: multiply, multiply;
backdrop-filter: blur(20px);
background: var(--app-side-bg);
box-shadow: 0 0 5px var(--common-shadow-1);
}
.config-icon.user {
padding: 2px;
border-radius: 50%;
}
.config-btn {

View File

@@ -6,7 +6,6 @@
v-model="curGameLabel"
class="post-switch-item"
:items="gameItem"
:theme="vuetifyTheme"
variant="outlined"
label="游戏"
/>
@@ -14,7 +13,6 @@
v-model="curForumLabel"
class="post-switch-item"
:items="forumItem"
:theme="vuetifyTheme"
variant="outlined"
label="频道"
/>
@@ -22,7 +20,6 @@
v-model="curSortLabel"
class="post-switch-item"
:items="sortItem"
:theme="vuetifyTheme"
variant="outlined"
label="排序"
/>
@@ -37,12 +34,11 @@
@click:append="searchPost"
@keyup.enter="searchPost"
/>
<v-btn class="post-fresh-btn" @click="freshPostData(false)">
<v-btn class="post-fresh-btn" @click="freshPostData()">
<v-icon>mdi-refresh</v-icon>
<span>刷新</span>
</v-btn>
</div>
<!-- todo: hover效果本来是只有 iconhover之后显示 title -->
<div class="posts-nav">
<div
v-for="navItem in nav"
@@ -54,7 +50,6 @@
<span>{{ navItem.name }}</span>
</div>
</div>
<!-- todo 无限加载 -->
<div class="posts-grid">
<v-card v-for="post in posts" :key="post.postId" class="post-card">
<div class="post-cover" @click="createPost(post)">
@@ -105,27 +100,20 @@
</div>
</v-card>
</div>
<div class="load-more">
<v-btn :loading="loading" @click="freshPostData(true)">
{{ rawData.page }}已加载{{ posts.length }}加载更多
</v-btn>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, watch } from "vue";
import { nextTick, onMounted, ref, watch } from "vue";
import showConfirm from "../../components/func/confirm";
import showSnackbar from "../../components/func/snackbar";
import ToLoading from "../../components/overlay/to-loading.vue";
import Mys from "../../plugins/Mys";
import { useAppStore } from "../../store/modules/app";
import TGClient from "../../utils/TGClient";
import { createPost } from "../../utils/TGWindow";
const loading = ref<boolean>(true);
const loadingTitle = ref<string>("正在加载数据");
const appStore = useAppStore();
// 常量
const sortList = {
@@ -196,16 +184,10 @@ const gameList = {
大别野: 5,
};
// 主题
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
});
// 渲染参数
const curForumLabel = ref<string>("酒馆");
const forumItem = ref<string[]>(["酒馆", "攻略", "同人图", "COS", "硬核"]);
const curForum = ref<number>(26);
const rawData = ref({ page: 1, is_last: false });
// 游戏相关
const curGameLabel = ref<keyof typeof gameList>("原神");
@@ -233,7 +215,7 @@ const search = ref<string>();
onMounted(async () => {
loading.value = true;
await freshNavData();
await freshPostData(false);
await freshPostData();
loading.value = false;
});
@@ -241,21 +223,26 @@ onMounted(async () => {
watch(curGameLabel, async (newVal) => {
curGid.value = gameList[newVal];
forumItem.value = Object.keys(forumList[newVal]);
curForumLabel.value = forumItem.value[0];
freshCurForum(forumItem.value[0]);
if (!forumItem.value.includes(curForumLabel.value)) {
curForumLabel.value = forumItem.value[0];
freshCurForum(forumItem.value[0]);
} else {
freshCurForum(curForumLabel.value);
await freshPostData();
}
await freshNavData();
});
// 监听论坛变化
watch(curForumLabel, async (newVal) => {
freshCurForum(newVal);
await freshPostData(false);
await freshPostData();
});
// 监听排序变化
watch(curSortLabel, async (newVal) => {
curSortType.value = sortList[newVal];
await freshPostData(false);
await freshPostData();
});
async function toNav(path: string): Promise<void> {
@@ -275,11 +262,17 @@ async function toNav(path: string): Promise<void> {
window.open(path);
return;
}
// todo 记忆宽屏竖屏
const modeConfirm = await showConfirm({
title: "是否采用宽屏模式打开?",
text: "取消则采用竖屏模式打开",
});
if (modeConfirm === undefined) {
showSnackbar({
text: "已取消打开",
color: "cancel",
});
return;
}
if (modeConfirm) await TGClient.open("web_act", path);
else await TGClient.open("web_act_thin", path);
}
@@ -309,33 +302,11 @@ async function freshNavData(): Promise<void> {
nav.value = await Mys.Posts.nav(curGid.value);
}
async function freshPostData(more: boolean = false): Promise<void> {
async function freshPostData(): Promise<void> {
loading.value = true;
loadingTitle.value = `正在加载 ${curGameLabel.value}-${curForumLabel.value}-${curSortLabel.value} 的数据`;
if (more) {
const postsGet = await Mys.Posts.get(
curForum.value,
curGid.value,
curSortType.value,
rawData.value.page,
);
if (rawData.value.is_last) {
showSnackbar({
text: "已经是最后一页了",
color: "warn",
});
loading.value = false;
return;
}
posts.value = posts.value.concat(Mys.Posts.card(postsGet));
rawData.value.is_last = postsGet.is_last;
rawData.value.page = postsGet.page;
} else {
const postsGet = await Mys.Posts.get(curForum.value, curGid.value, curSortType.value);
posts.value = Mys.Posts.card(postsGet);
rawData.value.is_last = false;
rawData.value.page = 1;
}
const postsGet = await Mys.Posts.get(curForum.value, curSortType.value);
posts.value = Mys.Posts.card(postsGet);
await nextTick();
loading.value = false;
}
@@ -604,19 +575,4 @@ function searchPost(): void {
gap: 5px;
opacity: 0.6;
}
.load-more {
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
font-family: var(--font-title);
transition: all 0.3s linear;
}
.load-more button {
border-radius: 5px;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
</style>

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/types/Weapon.d.ts
* @description 武器组件类型定义
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
/**
@@ -51,7 +51,7 @@ declare namespace TGApp.Plugins.Hutao.Weapon {
/**
* @description 精炼描述
* @since Beta v0.3.8
* @since Beta v0.3.9
* @memberof TGApp.Plugins.Hutao.Weapon
* @interface RhiAffix
* @property {string} Name 精炼名称
@@ -60,7 +60,7 @@ declare namespace TGApp.Plugins.Hutao.Weapon {
*/
interface RhiAffix {
Name: string;
Description: Array<{
Descriptions: Array<{
Level: number;
Description: string;
}>;

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Mys/api/index.ts
* @description Mys API
* @since Beta v0.3.7
* @since Beta v0.3.9
*/
const MysApi = {
@@ -10,7 +10,7 @@ const MysApi = {
Lottery: "https://bbs-api.miyoushe.com/painter/wapi/lottery/user/show?id={lotteryId}",
News: "https://bbs-api.mihoyo.com/post/wapi/getNewsList?gids={gid}&page_size={pageSize}&type={newsType}&last_id={lastId}",
Forum:
"https://bbs-api.miyoushe.com/post/wapi/getForumPostList?forum_id={forum}&gids={gid}&sort_type={type}&page={page}&page_size=20",
"https://bbs-api.miyoushe.com/post/wapi/getForumPostList?forum_id={forum}&sort_type={type}",
Feed: "https://bbs-api.miyoushe.com/post/api/feeds/posts?gids={gid}",
Navigator: "https://bbs-api.miyoushe.com/apihub/api/home/new?gids={gid}",
Position: "https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/home/position?app_sn=ys_obc",

View File

@@ -1,11 +1,12 @@
/**
* @file plugins/Mys/index.ts
* @description Mys plugin index
* @since Beta v0.3.7
* @since Beta v0.3.9
*/
import MysApi from "./api";
import { getLoginQr, getLoginStatus } from "./request/doGameLogin";
import { getCollectionData, getCollectionPosts } from "./request/getCollectionData";
import getForumList from "./request/getForumList";
import getGachaData from "./request/getGachaData";
import getHomeNavigator from "./request/getHomeNavigator";
@@ -13,6 +14,7 @@ import getLotteryData from "./request/getLotteryData";
import getNewsList from "./request/getNewsList";
import getPositionData from "./request/getPositionData";
import getPostData from "./request/getPostData";
import { getVoteInfo, getVoteResult } from "./request/getVoteData";
import getGachaCard from "./utils/getGachaCard";
import getLotteryCard from "./utils/getLotteryCard";
import { getActivityCard, getNewsCard, getNoticeCard } from "./utils/getNewsCard";
@@ -24,6 +26,10 @@ const Mys = {
Post: {
get: getPostData,
},
Collection: {
info: getCollectionData,
data: getCollectionPosts,
},
Posts: {
get: getForumList,
card: getPostsCard,
@@ -53,6 +59,10 @@ const Mys = {
getQr: getLoginQr,
getData: getLoginStatus,
},
Vote: {
get: getVoteInfo,
result: getVoteResult,
},
};
export default Mys;

View File

@@ -1,6 +1,5 @@
/**
* @file plugins/Mys/utils/doGameLogin
* @todo 完善
* @description 获取 gameToken曲线获取 stoken
* @since Beta v0.3.0
*/
@@ -29,7 +28,7 @@ export async function getLoginQr(): Promise<
})
.then((res) => {
if (res.data.retcode === 0) return res.data.data;
return res.data;
return <TGApp.BBS.Response.Base>res.data;
});
}
@@ -44,7 +43,6 @@ export async function getLoginStatus(
): Promise<TGApp.Plugins.Mys.GameLogin.GetLoginStatusData | TGApp.BBS.Response.Base> {
const url = "https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query";
const data = { app_id: "4", device, ticket };
console.log(data);
return await http
.fetch<TGApp.Plugins.Mys.GameLogin.GetLoginStatusResponse | TGApp.BBS.Response.Base>(url, {
method: "POST",
@@ -52,6 +50,6 @@ export async function getLoginStatus(
})
.then((res) => {
if (res.data.retcode === 0) return res.data.data;
return res.data;
return <TGApp.BBS.Response.Base>res.data;
});
}

View File

@@ -0,0 +1,58 @@
/**
* @file plugins/Mys/request/getCollectionPosts.ts
* @description Mys 获取合集帖子
* @since Beta v0.3.9
*/
import { http } from "@tauri-apps/api";
import MysApi from "../api";
/**
* @description 获取合集信息
* @since Beta v0.3.9
* @todo invalid request
* @param {number} collectionId 合集 ID
* @returns {Promise<TGApp.Plugins.Mys.Collection.ResponseData>} 合集信息
*/
export async function getCollectionData(
collectionId: number,
): Promise<TGApp.Plugins.Mys.Collection.ResponseData> {
const url = `https://bbs-api.miyoushe.com/collection/wapi/collection/detail?id=${collectionId}`;
console.log(url);
return await http
.fetch<TGApp.Plugins.Mys.Collection.Response>(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Referer: MysApi.Post.Referer,
},
})
.then((res) => {
console.log(res.data);
return res.data.data;
});
}
/**
* @description 获取合集帖子
* @since Beta v0.3.9
* @param {string} collectionId 合集 ID
* @returns {Promise<TGApp.Plugins.Mys.Post.FullData[]>}
*/
export async function getCollectionPosts(
collectionId: string,
): Promise<TGApp.Plugins.Mys.Collection.Data[]> {
const url = `https://bbs-api.miyoushe.com/post/wapi/getPostFullInCollection?collection_id=${collectionId}`;
return await http
.fetch<TGApp.Plugins.Mys.Collection.ResponsePosts>(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Referer: MysApi.Post.Referer,
},
})
.then((res) => {
return res.data.data.posts;
});
}

View File

@@ -10,23 +10,19 @@ import MysApi from "../api";
/**
* @description 获取特定论坛列表
* @since Beta v0.3.7
* @since Beta v0.3.9
* @param {number} forumId 特定论坛 ID
* @param {number} gid GID
* @param {number} type 排序方式: 0-按热度排序1-最新回复2-按时间排序
* @param {number} page 页码
* @return {Promise<TGApp.Plugins.Mys.Forum.FullData>}
*/
async function getForumList(
forumId: number,
gid: number = 2,
type: number = 0,
page: number = 1,
): Promise<TGApp.Plugins.Mys.Forum.FullData> {
const url = MysApi.Forum.replace("{forum}", forumId.toString())
.replace("{gid}", gid.toString())
.replace("{type}", type.toString())
.replace("{page}", page.toString());
const url = MysApi.Forum.replace("{forum}", forumId.toString()).replace(
"{type}",
type.toString(),
);
return await http.fetch<TGApp.Plugins.Mys.Forum.Response>(url).then((res) => res.data.data);
}

View File

@@ -0,0 +1,56 @@
/**
* @file plugins/Mys/request/getVoteData.ts
* @description Mys 插件投票请求
* @since Beta v0.3.9
*/
import { http } from "@tauri-apps/api";
import MysApi from "../api";
/**
* @description 获取投票信息
* @since Beta v0.3.9
* @param {string} id 投票 ID
* @param {string} uid 用户 ID
* @return {Promise<TGApp.Plugins.Mys.Vote.Info>}
*/
export async function getVoteInfo(id: string, uid: string): Promise<TGApp.Plugins.Mys.Vote.Info> {
const url = `https://bbs-api.miyoushe.com/apihub/api/getVotes?owner_uid=${uid}&vote_ids=${id}`;
return await http
.fetch<TGApp.Plugins.Mys.Vote.InfoResponse>(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Referer: MysApi.Post.Referer,
},
})
.then((res) => {
return res.data.data.data[0];
});
}
/**
* @description 获取投票结果
* @since Beta v0.3.9
* @param {string} id 投票 ID
* @param {string} uid 用户 ID
* @return {Promise<TGApp.Plugins.Mys.Vote.Result>}
*/
export async function getVoteResult(
id: string,
uid: string,
): Promise<TGApp.Plugins.Mys.Vote.Result> {
const url = `https://bbs-api.miyoushe.com/apihub/api/getVotesResult?owner_uid=${uid}&vote_ids=${id}`;
return await http
.fetch<TGApp.Plugins.Mys.Vote.ResultResponse>(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Referer: MysApi.Post.Referer,
},
})
.then((res) => {
return res.data.data.data[0];
});
}

92
src/plugins/Mys/types/Collection.d.ts vendored Normal file
View File

@@ -0,0 +1,92 @@
/**
* @file plugins/Mys/types/Collection.d.ts
* @description Mys 插件合集类型声明
* @since Beta v0.3.9
*/
/**
* @description Mys 合集类型
* @since Beta v0.3.9
* @namespace TGApp.Plugins.Mys.Collection
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.Collection {
/**
* @description 合集信息返回
* @since Beta v0.3.9
* @interface Response
* @extends TGApp.BBS.Response.BaseWithData
* @property {ResponseData} data 返回数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.BaseWithData {
data: ResponseData;
}
/**
* @description 合集信息返回数据
* @since Beta v0.3.9
* @interface ResponseData
* @property {Info} collection_info 合集信息
* @property {TGApp.Plugins.Mys.User.Collection} author_info 用户信息
* @return ResponseData
*/
interface ResponseData {
collection_info: Info;
author_info: TGApp.Plugins.Mys.User.Collection;
}
/**
* @description 合集信息
* @since Beta v0.3.9
* @interface Info
* @property {string} cover 封面
* @property {string} desc 描述
* @property {number} id 合集 ID
* @property {boolean} is_delete 是否删除
* @property {boolean} is_following 是否关注
* @property {number} post_num 帖子数量
* @property {number} post_updated_at 帖子更新时间(秒级时间戳)
* @property {number} status 状态 // todo: 未知
* @property {string} title 标题
* @property {number} uid 用户 ID
* @property {number} view_num 浏览量
* @return Info
*/
interface Info {
cover: string;
desc: string;
id: number;
is_delete: boolean;
is_following: boolean;
post_num: number;
post_updated_at: number;
status: number;
title: string;
uid: number;
view_num: number;
}
/**
* @description 获取合集帖子返回
* @since Beta v0.3.9
* @interface ResponsePosts
* @extends TGApp.BBS.Response.BaseWithData
* @property {Data[]} data.list 合集帖子列表
* @return ResponsePosts
*/
interface ResponsePosts extends TGApp.BBS.Response.BaseWithData {
data: {
posts: Data[];
};
}
/**
* @description 合集帖子
* @since Beta v0.3.9
* @interface Data
* @see TGApp.Plugins.Mys.Post.FullData
* @return Data
*/
type Data = TGApp.Plugins.Mys.Post.FullData;
}

View File

@@ -1,12 +1,12 @@
/**
* @file plugins/Mys/types/post.d.ts
* @description Mys 插件帖子类型定义文件
* @since Beta v0.3.7
* @since Beta v0.3.9
*/
/**
* @description Mys 插件帖子类型
* @since Beta v0.3.4
* @since Beta v0.3.9
* @namespace TGApp.Plugins.Mys.Post
* @memberof TGApp.Plugins.Mys
*/
@@ -27,7 +27,7 @@ declare namespace TGApp.Plugins.Mys.Post {
/**
* @description 帖子数据
* @since Alpha v0.2.1
* @since Beta v0.3.9
* @interface FullData
* @property {Post} post 帖子信息
* @property {Forum} forum 所属版块
@@ -44,7 +44,7 @@ declare namespace TGApp.Plugins.Mys.Post {
* @property {number} vot_count 投票数
* @property {number} last_modify_time 最后修改时间
* @property {string} recommend_type 推荐类型
* @property {unknown} collection 合集,可能为 null
* @property {Collection} collection 合集,可能为 null
* @property {unknown[]} vod_list 视频列表,可能为空
* @property {boolean} is_block_on 是否被屏蔽
* @property {unknown} forum_rank_info 版块排行信息,可能为 null
@@ -68,7 +68,7 @@ declare namespace TGApp.Plugins.Mys.Post {
vot_count: number;
last_modify_time: number;
recommend_type: string;
collection: unknown | null;
collection: Collection | null;
vod_list: Vod[];
is_block_on: boolean;
forum_rank_info: unknown | null;
@@ -304,6 +304,35 @@ declare namespace TGApp.Plugins.Mys.Post {
answer_num: number;
}
/**
* @description 合集信息
* @since Beta v0.3.9
* @interface Collection
* @property {string} prev_post_id 上一篇帖子 ID为 0 说明没有上一篇
* @property {string} next_post_id 下一篇帖子 ID为 0 说明没有下一篇
* @property {string} collection_id 合集 ID
* @property {number} cur 第几篇
* @property {number} total 总篇数
* @property {string} collection_title 合集标题
* @property {number} prev_post_game_id 上一篇帖子游戏 ID
* @property {number} next_post_game_id 下一篇帖子游戏 ID
* @property {number} prev_post_view_type 上一篇帖子浏览类型
* @property {number} next_post_view_type 下一篇帖子浏览类型
* @return Collection
*/
interface Collection {
prev_post_id: string;
next_post_id: string;
collection_id: string;
cur: number;
total: number;
collection_title: string;
prev_post_game_id: number;
next_post_game_id: number;
prev_post_view_type: number;
next_post_view_type: number;
}
/**
* @description 视频信息
* @since Beta v0.3.7

View File

@@ -1,12 +1,12 @@
/**
* @file plugins/Mys/types/user.ts
* @description Mys 插件用户类型定义文件
* @since Beta v0.3.7
* @since Beta v0.3.9
*/
/**
* @description Mys 插件用户类型
* @since Beta v0.3.7
* @since Beta v0.3.9
* @namespace TGApp.Plugins.Mys.User
* @memberof TGApp.Plugins.Mys
*/
@@ -310,4 +310,33 @@ declare namespace TGApp.Plugins.Mys.User {
nickname: string;
avatar_url: string;
}
/**
* @description collection中的用户信息
* @since Beta v0.3.9
* @interface Collection
* @property {string} avatar 用户头像
* @property {string} avatar_url 用户头像链接
* @property {Certification} certification 用户认证信息
* @property {number} gender 用户性别
* @property {string} introduce 用户简介
* @property {boolean} is_followed 是否被关注
* @property {boolean} is_following 是否关注
* @property {string} nickname 用户昵称
* @property {string} pendant 用户挂件 URL可能为 ""
* @property {number} uid 用户 ID
* @return Collection
*/
interface Collection {
avatar: string;
avatar_url: string;
certification: Certification;
gender: number;
introduce: string;
is_followed: boolean;
is_following: boolean;
nickname: string;
pendant: string;
uid: string;
}
}

83
src/plugins/Mys/types/Vote.d.ts vendored Normal file
View File

@@ -0,0 +1,83 @@
/**
* @file plugins/Mys/types/Vote.d.ts
* @description Mys 插件投票类型定义文件
* @since Beta v0.3.9
*/
/**
* @description Mys 插件投票类型
* @since Beta v0.3.9
* @namespace TGApp.Plugins.Mys.Vote
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.Vote {
/**
* @description 投票信息返回
* @since Beta v0.3.9
* @interface InfoResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {Info[]} data.data 投票信息
* @return InfoResponse
*/
interface InfoResponse extends TGApp.BBS.Response.BaseWithData {
data: {
data: Info[];
};
}
/**
* @description 投票结果返回
* @since Beta v0.3.9
* @interface ResultResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {Result[]} data.data 投票结果
* @return ResultResponse
*/
interface ResultResponse extends TGApp.BBS.Response.BaseWithData {
data: {
data: Result[];
};
}
/**
* @description 投票信息
* @since Beta v0.3.9
* @interface Info
* @property {string} vote_id 投票 ID
* @property {string} uid 用户 ID
* @property {number} vote_limit 投票限制
* @property {number} end_time 投票结束时间(秒级时间戳)
* @property {string} title 投票标题
* @property {string[]} vote_option_indexes 投票选项索引
* @property {string} created_at 投票创建时间(秒级时间戳)
* @return Info
*/
interface Info {
vote_id: string;
uid: string;
vote_limit: number;
end_time: number;
title: string;
vote_option_indexes: string[];
created_at: string;
}
/**
* @description 投票结果
* @since Beta v0.3.9
* @interface Result
* @property {string} vote_id 投票 ID
* @property {boolean} is_over 是否已结束
* @property {Record<string, number>} option_stats 投票选项统计
* @property {number} user_cnt 投票人数
* @property {unknown[]} vote_option_indexes 投票选项索引
* @return Result
*/
interface Result {
vote_id: string;
is_over: boolean;
option_stats: Record<string, number>;
user_cnt: number;
vote_option_indexes: unknown[];
}
}

View File

@@ -1,14 +1,14 @@
/**
* @file plugins Mys utils getGachaCard.ts
* @file plugins/Mys/utils/getGachaCard.ts
* @description Mys 插件抽卡工具
* @since Beta v0.3.3
* @since Beta v0.3.9
*/
import getPostData from "../request/getPostData";
/**
* @description 根据卡池信息转为渲染用的卡池信息
* @since Beta v0.3.3
* @since Beta v0.3.9
* @param {TGApp.Plugins.Mys.Gacha.Data[]} gachaData 卡池信息
* @param {Record<number, string>} poolCover 卡池封面
* @returns {Promise<TGApp.Plugins.Mys.Gacha.RenderCard[]>}
@@ -47,7 +47,7 @@ async function getGachaCard(
url: character.url,
})),
voice: {
icon: data.voice_icon || "/source/UI/defaultUser.webp",
icon: data.voice_icon || "/source/UI/lumine.webp",
url: data.voice_url,
},
time: {

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/index.ts
* @description Sqlite 数据库操作类
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
import { app } from "@tauri-apps/api";
@@ -198,21 +198,6 @@ class Sqlite {
await this.initDB();
}
/**
* @description 获取成就系列对应的名片
* @since Beta v0.3.3
* @param {number} seriesId 系列 ID
* @returns {Promise<TGApp.Sqlite.NameCard.Item>}
*/
public async getNameCard(seriesId: number): Promise<TGApp.Sqlite.NameCard.SingleTable> {
const db = await this.getDB();
const sql = `SELECT *
FROM NameCard
WHERE name = (SELECT nameCard FROM AchievementSeries WHERE id = ${seriesId});`;
const res: TGApp.Sqlite.NameCard.SingleTable[] = await db.select(sql);
return res[0];
}
/**
* @description 获取最新成就版本
* @since Beta v0.3.3
@@ -287,7 +272,10 @@ class Sqlite {
const db = await this.getDB();
let sql;
if (uid) {
sql = `SELECT * FROM SpiralAbyss WHERE uid = '${uid}' order by id desc`;
sql = `SELECT *
FROM SpiralAbyss
WHERE uid = '${uid}'
order by id desc`;
} else {
sql = "SELECT * FROM SpiralAbyss order by uid, id desc";
}
@@ -329,7 +317,9 @@ class Sqlite {
*/
public async getUserRecord(uid: string): Promise<TGApp.Sqlite.Record.SingleTable | false> {
const db = await this.getDB();
const sql = `SELECT * FROM UserRecord WHERE uid = '${uid}'`;
const sql = `SELECT *
FROM UserRecord
WHERE uid = '${uid}'`;
const res: TGApp.Sqlite.Record.SingleTable[] = await db.select(sql);
if (res.length === 0) return false;
return res[0];
@@ -343,7 +333,9 @@ class Sqlite {
*/
public async getAppCharacter(id: number): Promise<TGApp.Sqlite.Character.AppData> {
const db = await this.getDB();
const sql = `SELECT * FROM AppCharacters WHERE id = ${id}`;
const sql = `SELECT *
FROM AppCharacters
WHERE id = ${id}`;
const res: TGApp.Sqlite.Character.AppData[] = await db.select(sql);
return res[0];
}
@@ -378,9 +370,11 @@ class Sqlite {
data: TGApp.Sqlite.Character.RoleTalent[],
): Promise<void> {
const db = await this.getDB();
const sql = `UPDATE UserCharacters
SET talent = '${JSON.stringify(data)}', updated = datetime('now', 'localtime')
WHERE uid = '${uid}' AND cid = ${cid}`;
const sql = `UPDATE UserCharacters
SET talent = '${JSON.stringify(data)}',
updated = datetime('now', 'localtime')
WHERE uid = '${uid}'
AND cid = ${cid}`;
await db.execute(sql);
}
@@ -392,7 +386,9 @@ class Sqlite {
*/
public async getUserCharacter(uid: string): Promise<TGApp.Sqlite.Character.UserRole[] | false> {
const db = await this.getDB();
const sql = `SELECT * FROM UserCharacters WHERE uid = '${uid}'`;
const sql = `SELECT *
FROM UserCharacters
WHERE uid = '${uid}'`;
const res: TGApp.Sqlite.Character.UserRole[] = await db.select(sql);
if (res.length === 0) return false;
return res;
@@ -418,7 +414,9 @@ class Sqlite {
*/
public async getGachaRecords(uid: string): Promise<TGApp.Sqlite.GachaRecords.SingleTable[]> {
const db = await this.getDB();
const sql = `SELECT * FROM GachaRecords WHERE uid = '${uid}'`;
const sql = `SELECT *
FROM GachaRecords
WHERE uid = '${uid}'`;
return await db.select(sql);
}
@@ -430,7 +428,9 @@ class Sqlite {
*/
public async deleteGachaRecords(uid: string): Promise<void> {
const db = await this.getDB();
const sql = `DELETE FROM GachaRecords WHERE uid = '${uid}'`;
const sql = `DELETE
FROM GachaRecords
WHERE uid = '${uid}'`;
await db.execute(sql);
}
@@ -458,7 +458,9 @@ class Sqlite {
const db = await this.getDB();
const dateNow = new Date();
const date = `${dateNow.getMonth() + 1},${dateNow.getDate()}`;
const sql = `SELECT name FROM AppCharacters WHERE birthday = '${date}';`;
const sql = `SELECT name
FROM AppCharacters
WHERE birthday = '${date}';`;
const res: Array<{ name: string }> = await db.select(sql);
if (res.length === 0) return false;
return res.map((item) => item.name).join("、");

View File

@@ -1,8 +1,7 @@
/**
* @file plugins Sqlite utils transCharacter.ts
* @file plugins/Sqlite/utils/transCharacter.ts
* @description Sqlite 数据转换
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
* @since Beta v0.3.9
*/
import { timeToSecond } from "./transTime";
@@ -26,7 +25,7 @@ export function transCharacterData(data: TGApp.Game.Abyss.CharacterData[]): stri
/**
* @description 将通过 api 获取到的深渊数据转换为数据库中的数据
* @since Alpha v0.2.0
* @since Beta v0.3.9
* @param {TGApp.Game.Abyss.Floor} data 深渊数据
* @returns {string} 转换后的深渊数据
*/
@@ -37,24 +36,34 @@ export function transFloorData(data: TGApp.Game.Abyss.Floor[]): string {
winStar: item.star,
maxStar: item.max_star,
isUnlock: item.is_unlock ? 1 : 0,
levels: item.levels.map((level) => {
return {
id: level.index,
winStar: level.star,
maxStar: level.max_star,
upBattle: transBattleData(
<TGApp.Game.Abyss.Battle>level.battles.find((l) => l.index === 1),
),
downBattle: transBattleData(
<TGApp.Game.Abyss.Battle>level.battles.find((l) => l.index === 2),
),
};
}),
levels: item.levels.map((level) => transLevelData(level)),
};
});
return JSON.stringify(floor);
}
/**
* @description 将通过 api 获取到的深渊数据转换为数据库中的数据
* @since Beta v0.3.9
* @param {TGApp.Game.Abyss.Level} data 深渊数据
* @returns {TGApp.Sqlite.Abyss.Level} 转换后的深渊数据
*/
function transLevelData(data: TGApp.Game.Abyss.Level): TGApp.Sqlite.Abyss.Level {
const res: TGApp.Sqlite.Abyss.Level = {
id: data.index,
winStar: data.star,
maxStar: data.max_star,
};
for (const battle of data.battles) {
if (battle.index === 1) {
res.upBattle = transBattleData(battle);
} else {
res.downBattle = transBattleData(battle);
}
}
return res;
}
/**
* @description 将通过 api 获取到的深渊数据转换为数据库中的数据
* @since Alpha v0.2.0

View File

@@ -1,7 +1,7 @@
/**
* @file router/modules/wiki.ts
* @description wiki 路由模块
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
const wikiRoutes = [
@@ -20,6 +20,16 @@ const wikiRoutes = [
name: "卡牌图鉴",
component: async () => await import("../../pages/WIKI/GCG.vue"),
},
{
path: "/wiki/namecard",
name: "名片图鉴",
component: async () => await import("../../pages/WIKI/Namecard.vue"),
},
{
path: "/wiki/material",
name: "材料图鉴",
component: async () => await import("../../pages/WIKI/Material.vue"),
},
{
path: "/wiki/weapon",
name: "武器图鉴",

View File

@@ -1,7 +1,7 @@
/**
* @file store/modules/app.ts
* @description App store module
* @since Beta v0.3.6
* @since Beta v0.3.9
*/
import { path } from "@tauri-apps/api";
@@ -26,11 +26,6 @@ export const useAppStore = defineStore(
const sidebar = reactive({
// 是否折叠
collapse: true,
// 是否显示
submenu: {
// 数据库
wiki: false,
},
});
// 开发者模式
const devMode = ref(false);
@@ -54,20 +49,11 @@ export const useAppStore = defineStore(
function init(): void {
loading.value = false;
devMode.value = false;
sidebar.submenu = {
wiki: false,
};
theme.value = "default";
isLogin.value = false;
initDevice();
}
function getSubmenu(): string[] {
const open = [];
if (sidebar.submenu.wiki) open.push("wiki");
return open;
}
function changeTheme(): void {
if (theme.value === "default") theme.value = "dark";
else theme.value = "default";
@@ -88,7 +74,6 @@ export const useAppStore = defineStore(
deviceInfo,
isLogin,
init,
getSubmenu,
changeTheme,
};
},

View File

@@ -1,153 +1,37 @@
/**
* @file store/modules/user.ts
* @description 用户信息模块
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
import { defineStore } from "pinia";
import { ref } from "vue";
import TGSqlite from "../../plugins/Sqlite";
export const useUserStore = defineStore(
"user",
() => {
const briefInfo = ref<TGApp.App.Account.BriefInfo>(loadBriefInfo());
const account = ref<TGApp.Sqlite.Account.Game>(loadAccount());
const briefInfo = ref<TGApp.App.Account.BriefInfo>({
nickname: "",
avatar: "",
uid: "",
desc: "",
});
const account = ref<TGApp.Sqlite.Account.Game>({
gameBiz: "",
gameUid: "",
isChosen: 0,
isOfficial: 0,
level: "",
nickname: "",
region: "",
regionName: "",
});
const cookie = ref<TGApp.User.Account.Cookie>();
/**
* @description 从本地加载用户信息
* @since Beta v0.3.8
* @function loadBriefInfo
* @memberof useUserStore
* @returns {TGApp.App.Account.BriefInfo}
*/
function loadBriefInfo(): TGApp.App.Account.BriefInfo {
const info = window.localStorage.getItem("briefInfo");
if (info !== null && info !== undefined) {
console.log(JSON.parse(info).briefInfo);
return JSON.parse(info).briefInfo;
}
return {
nickname: "",
avatar: "",
uid: "",
desc: "",
};
}
/**
* @description 从本地加载当前用户信息
* @since Beta v0.3.8
* @function loadAccount
* @memberof useUserStore
* @returns {TGApp.Sqlite.Account.Game}
*/
function loadAccount(): TGApp.Sqlite.Account.Game {
const info = window.localStorage.getItem("account");
if (info !== null && info !== undefined) {
console.log(JSON.parse(info).account);
return JSON.parse(info).account;
}
return {
gameBiz: "",
gameUid: "",
isChosen: 0,
isOfficial: 0,
level: "",
nickname: "",
region: "",
regionName: "",
};
}
/**
* @description 获取用户信息
* @since Beta v0.3.8
* @function getBriefInfo
* @memberof useUserStore
* @returns {TGApp.App.Account.BriefInfo}
*/
function getBriefInfo(): TGApp.App.Account.BriefInfo {
return briefInfo.value;
}
/**
* @description 获取当前用户信息
* @since Beta v0.3.8
* @function getCurAccount
* @memberof useUserStore
* @returns {TGApp.Sqlite.Account.Game}
*/
function getCurAccount(): TGApp.Sqlite.Account.Game {
return account.value;
}
/**
* @description 设置用户信息
* @param info
* @since Beta v0.3.8
* @function setBriefInfo
* @memberof useUserStore
* @param {TGApp.App.Account.BriefInfo} info - 用户信息
* @returns {void}
*/
function setBriefInfo(info: TGApp.App.Account.BriefInfo): void {
briefInfo.value = info;
}
/**
* @description 设置当前用户信息
* @since Beta v0.3.8
* @function setCurAccount
* @memberof useUserStore
* @param {TGApp.Sqlite.Account.Game} user - 用户信息
* @returns {void}
*/
function setCurAccount(user: TGApp.Sqlite.Account.Game): void {
account.value = user;
}
/**
* @description 保存cookie到本地
* @since Beta v0.3.8
* @function saveCookie
* @memberof useUserStore
* @param {TGApp.User.Account.Cookie} ck - cookie对象
* @returns {Promise<void>}
*/
async function saveCookie(ck?: TGApp.User.Account.Cookie): Promise<void> {
if (ck) {
cookie.value = ck;
}
await TGSqlite.saveAppData("cookie", JSON.stringify(cookie.value));
}
/**
* @description 保存用户信息到本地
* @since Beta v0.3.8
* @function saveBriefInfo
* @memberof useUserStore
* @param {TGApp.App.Account.BriefInfo} info - 用户信息
* @returns {Promise<void>}
*/
async function saveBriefInfo(info?: TGApp.App.Account.BriefInfo): Promise<void> {
if (info) {
setBriefInfo(info);
localStorage.setItem("briefInfo", JSON.stringify({ briefInfo: info }));
}
await TGSqlite.saveAppData("userInfo", JSON.stringify(briefInfo.value));
}
return {
cookie,
getBriefInfo,
setBriefInfo,
setCurAccount,
getCurAccount,
saveCookie,
saveBriefInfo,
briefInfo,
account,
};
},
{

View File

@@ -1,7 +1,6 @@
/**
* @file types/App/Achievement.d.ts
* @description 应用成就相关类型定义文件
* @todo https://github.com/BTMuli/TeyvatGuide/issues/19
* @since Alpha v0.1.5
*/

View File

@@ -1,16 +1,22 @@
/**
* @file types App NameCard.d.ts
* @file types/App/NameCard.d.ts
* @description 本应用的名片类型定义
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.5
* @since Beta v0.3.9
*/
/**
* @description 名片数据
* @since Beta v0.3.9
* @namespace TGApp.App.NameCard
* @memberof TGApp.App
*/
declare namespace TGApp.App.NameCard {
/**
* @description 名片数据
* @since Alpha v0.1.5
* @since Beta v0.3.9
* @interface Item
* @property {string} name - 名片名称
* @property {number} index - 名片索引
* @property {string} desc - 名片描述
* @property {string} icon - 名片图标
* @property {string} bg - 名片背景图
@@ -20,8 +26,9 @@ declare namespace TGApp.App.NameCard {
* @property {string} source - 名片来源
* @return Item
*/
export interface Item {
interface Item {
name: string;
index: number;
desc: string;
icon: string;
bg: string;

View File

@@ -1,7 +1,7 @@
/**
* @file types/App/Weapon.d.ts
* @description 本应用的武器类型定义文件
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
declare namespace TGApp.App.Weapon {
@@ -30,7 +30,7 @@ declare namespace TGApp.App.Weapon {
/**
* @description 转换后的武器数据
* @since Beta v0.3.8
* @since Beta v0.3.9
* @interface WikiItem
* @memberof TGApp.App.Weapon
* @property {number} id 武器 id
@@ -40,7 +40,7 @@ declare namespace TGApp.App.Weapon {
* @property {string} weapon 武器类型
* @property {TGApp.App.Calendar.Material[]} materials 武器培养材料
* @property {TGApp.Plugins.Hutao.Weapon.RhiAffix} affix 精炼描述
* @property {string|string[]} story 武器故事
* @property {string[]} story 武器故事
* @return WikiItem
*/
interface WikiItem {
@@ -51,6 +51,6 @@ declare namespace TGApp.App.Weapon {
weapon: string;
materials: TGApp.App.Calendar.Material[];
affix: TGApp.Plugins.Hutao.Weapon.RhiAffix;
story: string | string[];
story: string[];
}
}

View File

@@ -1,7 +1,7 @@
/**
* @file types/BBS/Response.d.ts
* @description BBS 返回数据类型定义文件
* @since Beta v0.3.6
* @since Beta v0.3.9
*/
/**
@@ -14,14 +14,14 @@ declare namespace TGApp.BBS.Response {
/**
* @description 基础返回类型,设计米游社接口请求都是这个类型
* @interface Base
* @since Beta v0.3.5
* @since Beta v0.3.9
* @property {never} retcode - 响应代码
* @property {string} message - 响应消息
* @property {never} data - 响应数据
* @return Base
*/
interface Base {
retcode: never;
retcode: number;
message: string;
data: never;
}

236
src/types/Plugins/JSBridge.d.ts vendored Normal file
View File

@@ -0,0 +1,236 @@
/**
* @file types/Plugins/JSBridge.d.ts
* @description JSBridge 插件相关类型定义文件
* @since Beta v0.3.9
*/
/**
* @description JSBridge 插件相关类型命名
* @since Beta v0.3.9
* @namespace TGApp.Plugins.JSBridge
* @memberof TGApp.Plugins
*/
declare namespace TGApp.Plugins.JSBridge {
/**
* @description 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> {
method: string;
payload: T;
callback: string;
}
/**
* @description 通用 arg 参数-无参数
* @since Beta v0.3.9
* @interface NullArg
* @return NullArg
*/
type NullArg = Arg<null>;
/**
* @description configShare 方法参数
* @since Beta v0.3.9
* @interface ConfigSharePayload
* @property {boolean} enable - 是否启用分享
* @return ConfigSharePayload
*/
interface ConfigSharePayload {
enable: boolean;
}
/**
* @description 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 {
pageInfo: {
page_path: string;
page_name: string;
sub_page_path: string;
sub_page_name: string;
source_path: string;
source_name: string;
page_id: string;
page_type: string;
source_id: string;
extra_info: unknown;
};
eventInfo: {
time: string;
action_id: number;
btn_name: string;
module_id: string;
module_name: string;
extra_info: unknown;
};
commonInfo: {
extra_info: {
game_id: string;
view_type: string;
};
};
}
/**
* @description getActionTicket 方法参数
* @since Beta v0.3.9
* @interface GetActionTicketPayload
* @property {string} action_type
* @return GetActionTicketPayload
*/
interface GetActionTicketPayload {
action_type: string;
}
/**
* @description genAuthkey 方法参数
* @since Beta v0.3.9
* @interface GenAuthkeyPayload
* @return GenAuthkeyPayload
*/
type GenAuthkeyPayload = Record<string, string>;
/**
* @description getCookieToken 方法参数
* @since Beta v0.3.9
* @interface GetCookieTokenPayload
* @property {boolean} forceRefresh - 是否强制刷新
* @return GetCookieTokenPayload
*/
interface GetCookieTokenPayload {
forceRefresh: boolean;
}
/**
* @description 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 {
query: Record<string, string | number> | string;
body: Record<string, string | number> | string;
}
/**
* @description 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 {
image_list: Array<{
url: string;
format: string;
}>;
}
/**
* @description openApplication 方法参数
* @since Beta v0.3.9
* @interface OpenApplicationPayload
* @property {number} gameCenterId - 游戏中心对应 id
* @return OpenApplicationPayload
*/
interface OpenApplicationPayload {
gameCenterId: number;
}
/**
* @description pushPage 方法参数
* @since Beta v0.3.9
* @interface PushPagePayload
* @property {string} page - 页面地址
* @return PushPagePayload
*/
interface PushPagePayload {
page: string;
}
/**
* @description setPresentationStyle 方法参数
* @since Beta v0.3.9
* @interface SetPresentationStylePayload
* @property {string} style - 窗口样式
* @property {unknown} navigationBar - 导航栏
* @property {string} statusBar.style - 状态栏模式
* @return SetPresentationStylePayload
*/
interface SetPresentationStylePayload {
style: string;
navigationBar: unknown;
statusBar: {
style: string;
};
}
/**
* @description 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;
};
};
}

View File

@@ -1,10 +1,15 @@
/**
* @file types Sqlite Abyss.d.ts
* @file types/Sqlite/Abyss.d.ts
* @description 数据库深境螺旋相关类型定义文件
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
* @since Beta v0.3.9
*/
/**
* @description 数据库深渊类型命名
* @since Beta v0.3.9
* @namespace TGApp.Sqlite.Abyss
* @memberof TGApp.Sqlite
*/
declare namespace TGApp.Sqlite.Abyss {
/**
* @description 数据库-深境螺旋表
@@ -30,7 +35,7 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {string} updated - 更新时间
* @return SingleTable
*/
export interface SingleTable {
interface SingleTable {
uid: string;
id: number;
startTime: string;
@@ -59,7 +64,7 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {number} star - 星级
* @return Character
*/
export interface Character {
interface Character {
id: number;
value: number;
star: number;
@@ -76,7 +81,7 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {Level[]} levels - 关卡数据
* @return Floor
*/
export interface Floor {
interface Floor {
id: number;
winStar: number;
maxStar: number;
@@ -86,7 +91,7 @@ declare namespace TGApp.Sqlite.Abyss {
/**
* @description 数据库-深境螺旋表-关卡数据
* @since Alpha v0.2.0
* @since Beta v0.3.9
* @interface Level
* @property {number} id - 关卡 ID
* @property {number} winStar - 获得星数
@@ -95,12 +100,12 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {Battle} downBattle - 下半场数据
* @return Level
*/
export interface Level {
interface Level {
id: number;
winStar: number;
maxStar: number;
upBattle: Battle;
downBattle: Battle;
upBattle?: Battle;
downBattle?: Battle;
}
/**
@@ -111,7 +116,7 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {CharacterInfo[]} characters - 角色数据
* @return Battle
*/
export interface Battle {
interface Battle {
time: string;
characters: CharacterInfo[];
}
@@ -125,7 +130,7 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {number} level - 等级
* @return CharacterInfo
*/
export interface CharacterInfo {
interface CharacterInfo {
id: number;
level: number;
star: number;

View File

@@ -1,7 +1,6 @@
/**
* @file types/Sqlite/Achievement.d.ts
* @description 数据库成就相关类型定义文件
* @todo https://github.com/BTMuli/TeyvatGuide/issues/19
* @since Alpha v0.2.0
*/

View File

@@ -33,7 +33,7 @@ declare namespace TGApp.Sqlite.AppData {
* @property {string} updated - 数据库更新时间
* @return Item
*/
export interface Item {
interface Item {
key: DBKey;
value: string;
updated: string;

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/**
* @file utils/TGShare.ts
* @description 生成分享截图并保存到本地
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
import { dialog, fs, http, path } from "@tauri-apps/api";
@@ -72,7 +72,7 @@ function getShareImgBgColor(): string {
/**
* @description 生成分享截图
* @since Beta v0.3.8
* @since Beta v0.3.9
* @param {string} fileName - 文件名
* @param {HTMLElement} element - 元素
* @param {number} scale - 缩放比例
@@ -122,15 +122,14 @@ export async function generateShareImg(
title: "图像过大",
text: `图像大小为 ${sizeStr},是否保存到文件?`,
});
if (!saveFile) {
showSnackbar({
color: "warn",
text: "将尝试保存到剪贴板",
});
} else {
if (saveFile === true) {
await saveCanvasImg(buffer, fileName);
return;
}
showSnackbar({
color: "warn",
text: "将尝试保存到剪贴板",
});
}
try {
await copyToClipboard(buffer);

View File

@@ -1,12 +1,13 @@
/**
* @file src/utils/linkParser.ts
* @description 处理链接
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
import TGClient from "./TGClient";
import { createPost } from "./TGWindow";
import showConfirm from "../components/func/confirm";
import showSnackbar from "../components/func/snackbar";
/**
* @function parsePost
@@ -48,7 +49,7 @@ export async function parsePost(link: string): Promise<false | string> {
/**
* @function parseLink
* @since Beta v0.3.8
* @since Beta v0.3.9
* @description 处理链接
* @param {string} link - 链接
* @param {boolean} useInner - 是否采用内置 JSBridge 打开
@@ -110,24 +111,26 @@ export async function parseLink(
"bbs.mihoyo.com",
"qaa.miyoushe.com",
];
if (prefix.includes(url.hostname)) {
if (!useInner) {
const openCheck = await showConfirm({
title: "采用内置 JSBridge",
text: "取消则使用外部浏览器打开",
if (prefix.includes(url.hostname) && !useInner) {
const openCheck = await showConfirm({
title: "采用内置 JSBridge",
text: "取消则使用外部浏览器打开",
});
if (openCheck === undefined) {
showSnackbar({
text: "已取消打开",
color: "warn",
});
if (!openCheck) return url.href;
const typeCheck = await showConfirm({
title: "采用宽屏模式?",
text: "取消则使用默认竖屏",
});
if (typeCheck) {
await TGClient.open("web_act", link);
} else {
await TGClient.open("web_act_thin", link);
}
return true;
}
if (!openCheck) return url.href;
const typeCheck = await showConfirm({
title: "采用宽屏模式?",
text: "取消则使用默认竖屏",
});
if (!typeCheck) await TGClient.open("web_act_thin", link);
else await TGClient.open("web_act", link);
return true;
}
return url.href.toString();
}

View File

@@ -1,7 +1,7 @@
/**
* @file utils/toolFunc.ts
* @description 一些工具函数
* @since Beta v0.3.8
* @since Beta v0.3.9
*/
import { os, path } from "@tauri-apps/api";
@@ -163,18 +163,40 @@ export function getRandomString(length: number, type: string = "all"): string {
return res;
}
/**
* @description 将颜色转为 hex
* @since Beta v0.3.9
* @param {string} color - 颜色
* @returns {string} hex
*/
function color2Hex(color: string): string {
if (color.startsWith("#")) return color;
if (color.startsWith("rgb")) {
// 正则获取 rgb(0, 0, 0) 或 rgba(0, 0, 0, 0) 或 rgb(0 0 0/0)
const reg = /rgba?\((.*?)\)/;
const match = reg.exec(color);
if (match === null) return "#000000";
const rgb = match[1];
const rgbArr = rgb.split(/[ ,/]/);
if (rgbArr.length < 3) return "#000000";
const r = parseInt(rgbArr[0]);
const g = parseInt(rgbArr[1]);
const b = parseInt(rgbArr[2]);
return colorConvert.rgb.hex([r, g, b]);
}
return colorConvert.keyword.hex(<KEYWORD>color);
}
/**
* @description 判断颜色是否相似
* @since Beta v0.3.7
* @since Beta v0.3.9
* @param {string} colorBg - 背景颜色
* @param {string} colorText - 文本颜色
* @returns {boolean} 是否相似
*/
export function isColorSimilar(colorBg: string, colorText: string): boolean {
const hexBg = colorBg.startsWith("#") ? colorBg : colorConvert.keyword.hex(<KEYWORD>colorBg);
const hexText = colorText.startsWith("#")
? colorText
: colorConvert.keyword.hex(<KEYWORD>colorText);
const hexBg = color2Hex(colorBg);
const hexText = color2Hex(colorText);
return score(hexText, hexBg) === "Fail";
}

View File

@@ -7,87 +7,103 @@
:title="shareTitle"
/>
<ToLoading v-model="loading" :empty="loadingEmpty" :title="loadingTitle" :subtitle="loadingSub" />
<div class="tp-post-body">
<div class="tp-post-body" v-if="postData">
<div class="tp-post-info">
<div class="tp-post-version">
PostID{{ postId }} | Render by TeyvatGuide v{{ appVersion }}
</div>
<div class="tp-post-meta">
<div class="mpm-forum" v-if="postRender.forum !== null">
<img :src="postRender.forum.icon" alt="forumIcon" />
<span>{{ postRender.forum?.name }}</span>
<div class="mpm-forum" v-if="postData.forum">
<img :src="postData.forum.icon" alt="forumIcon" />
<span>{{ postData.forum.name }}</span>
</div>
<div class="mpm-item" :title="`浏览数:${postRender.metadata.view_num}`">
<div class="mpm-item" :title="`浏览数:${postData.stat.view_num}`">
<v-icon>mdi-eye</v-icon>
<span>{{ postRender.metadata.view_num }}</span>
<span>{{ postData.stat.view_num }}</span>
</div>
<div class="mpm-item" :title="`收藏数:${postRender.metadata.bookmark_num}`">
<div class="mpm-item" :title="`收藏数:${postData.stat.bookmark_num}`">
<v-icon>mdi-star</v-icon>
<span>{{ postRender.metadata.bookmark_num }}</span>
<span>{{ postData.stat.bookmark_num }}</span>
</div>
<div class="mpm-item" :title="`回复数:${postRender.metadata.reply_num}`">
<div class="mpm-item" :title="`回复数:${postData.stat.reply_num}`">
<v-icon>mdi-comment</v-icon>
<span>{{ postRender.metadata.reply_num }}</span>
<span>{{ postData.stat.reply_num }}</span>
</div>
<div class="mpm-item" :title="`点赞数:${postRender.metadata.like_num}`">
<div class="mpm-item" :title="`点赞数:${postData.stat.like_num}`">
<v-icon>mdi-thumb-up</v-icon>
<span>{{ postRender.metadata.like_num }}</span>
<span>{{ postData.stat.like_num }}</span>
</div>
<div class="mpm-item" :title="`转发数:${postRender.metadata.forward_num}`">
<div class="mpm-item" :title="`转发数:${postData.stat.forward_num}`">
<v-icon>mdi-share-variant</v-icon>
<span>{{ postRender.metadata.forward_num }}</span>
<span>{{ postData.stat.forward_num }}</span>
</div>
</div>
<div class="tp-post-author">
<div class="mpa-left">
<span>{{ postRender.author.nickname }}</span>
<span>{{ postData.user.nickname }}</span>
<span :title="getMpaLeftDesc()">{{ getMpaLeftDesc() }}</span>
</div>
<div class="mpa-right">
<div class="mpa-right" @click="toAuthor()" title="点击前往用户主页">
<div class="mpa-icon">
<img :src="postRender.author.avatar_url" alt="userIcon" />
<img :src="postData.user.avatar_url" alt="userIcon" />
</div>
<div v-if="postRender.author.pendant !== ''" class="mpa-pendant">
<img :src="postRender.author.pendant" alt="userPendant" />
<div v-if="postData.user.pendant !== ''" class="mpa-pendant">
<img :src="postData.user.pendant" alt="userPendant" />
</div>
</div>
</div>
</div>
<div class="tp-post-title">
<span class="mpt-official" v-if="postRender.isOfficial"></span>
<span>{{ postRender.title }}</span>
<div class="tp-post-title" @click="toPost()" title="点击查看评论">
<span class="mpt-official" v-if="postData.post.post_status.is_official"></span>
<span>{{ postData.post.subject }}</span>
</div>
<!-- 一些附加信息比如 topiccollection -->
<div class="tp-post-extra">
<div
class="tp-post-collection"
:title="`合集ID${postData.collection.collection_id}`"
v-if="postData.collection"
@click="showOverlayC()"
>
<v-icon size="12">mdi-widgets</v-icon>
<span>{{ postData.collection.collection_title }}</span>
<span>{{ postData.collection.cur }}/{{ postData.collection.total }}</span>
</div>
<div v-for="topic in postData.topics" :key="topic.id" class="tp-post-topic">
<v-icon size="12">mdi-tag</v-icon>
<span>{{ topic.name }}</span>
</div>
</div>
<div class="tp-post-subtitle">
<span>创建时间{{ postRender.created }}&emsp;</span>
<span>更新时间{{ postRender.updated }}</span>
<span :title="`更新时间: ${getDate(postData.post.updated_at)}`">
创建时间{{ getDate(postData.post.created_at) }}
</span>
<span>分享时间{{ getDate(shareTime) }}</span>
</div>
<TpParser v-model:data="renderPost" />
</div>
<TpoCollection
v-model="showCollection"
:collection="postData.collection"
v-if="postData?.collection"
/>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { appWindow } from "@tauri-apps/api/window";
import { nextTick, onMounted, ref, watch } from "vue";
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import TSwitchTheme from "../components/app/t-switchTheme.vue";
import TShareBtn from "../components/main/t-shareBtn.vue";
import ToLoading from "../components/overlay/to-loading.vue";
import TpParser from "../components/post/tp-parser.vue";
import TpoCollection from "../components/post/tpo-collection.vue";
import Mys from "../plugins/Mys";
import { useAppStore } from "../store/modules/app";
import TGClient from "../utils/TGClient";
import { createTGWindow } from "../utils/TGWindow";
interface PostRender {
title: string;
isOfficial: boolean;
created: string;
updated: string;
author: Partial<TGApp.Plugins.Mys.User.Post>;
forum: TGApp.Plugins.Mys.Post.Forum | null;
metadata: TGApp.Plugins.Mys.Post.Stat;
}
// loading
const loading = ref<boolean>(true);
const loadShare = ref<boolean>(false);
@@ -103,29 +119,13 @@ const shareTitle = ref<string>("");
const appVersion = ref<string>();
const postId = Number(useRoute().params.post_id);
const renderPost = ref<TGApp.Plugins.Mys.SctPost.Base[]>([]);
const postRender = ref<PostRender>({
title: "",
isOfficial: false,
created: "",
updated: "",
author: {
nickname: "",
certification: {
type: 0,
label: "",
},
},
forum: null,
metadata: {
view_num: 0,
bookmark_num: 0,
reply_num: 0,
like_num: 0,
forward_num: 0,
original_like_num: 0,
post_upvote_stat: [],
},
});
const postData = ref<TGApp.Plugins.Mys.Post.FullData>();
// 当前时间,秒级时间戳
const shareTime = ref<number>(Math.floor(Date.now() / 1000));
// 时间更新定时器
const shareTimeTimer = ref<any>();
// 合集
const showCollection = ref<boolean>(false);
onMounted(async () => {
await appWindow.show();
@@ -140,21 +140,11 @@ onMounted(async () => {
// 获取数据
loadingTitle.value = "正在获取数据...";
try {
const postData = await Mys.Post.get(postId);
postData.value = await Mys.Post.get(postId);
loadingTitle.value = "正在渲染数据...";
renderPost.value = getRenderPost(postData);
postRender.value = {
title: postData.post.subject,
isOfficial: postData.post.post_status.is_official,
created: new Date(postData.post.created_at * 1000).toLocaleString().replace(/\//g, "-"),
updated: new Date(postData.post.updated_at * 1000).toLocaleString().replace(/\//g, "-"),
author: postData.user,
forum: postData.forum,
metadata: postData.stat,
};
renderPost.value = getRenderPost(postData.value);
shareTitle.value = `Post_${postId}`;
postRef.value = <HTMLElement>document.querySelector(".tp-post-body");
await appWindow.setTitle(`Post_${postId} ${postData.post.subject}`);
await appWindow.setTitle(`Post_${postId} ${postData.value.post.subject}`);
} catch (error) {
console.error(error);
loadingEmpty.value = true;
@@ -169,12 +159,17 @@ onMounted(async () => {
createPostJson(postId);
}
await nextTick(() => {
shareTimeTimer.value = setInterval(() => {
shareTime.value = Math.floor(Date.now() / 1000);
}, 1000);
loading.value = false;
postRef.value = <HTMLElement>document.querySelector(".tp-post-body");
});
});
watch(loadShare, (value) => {
if (value) {
shareTime.value = Math.floor(Date.now() / 1000);
loadingTitle.value = "正在生成分享图片";
loadingSub.value = `${shareTitle.value}.png`;
loading.value = true;
@@ -184,10 +179,18 @@ watch(loadShare, (value) => {
}
});
function showOverlayC() {
showCollection.value = true;
}
function getMpaLeftDesc(): string {
return postRender.value.author.certification?.label === ""
? postRender.value.author.introduce ?? ""
: postRender.value.author.certification?.label ?? "";
return postData.value?.user.certification?.label === ""
? postData.value?.user.introduce ?? ""
: postData.value?.user.certification?.label ?? "";
}
function getDate(date: number): string {
return new Date(date * 1000).toLocaleString().replace(/\//g, "-");
}
function getRenderPost(data: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.SctPost.Base[] {
@@ -241,6 +244,20 @@ function createPostJson(postId: number): void {
const jsonTitle = `Post_${postId}_JSON`;
createTGWindow(jsonPath, "Dev_JSON", jsonTitle, 960, 720, false, false);
}
async function toAuthor(): Promise<void> {
const url = `https://m.miyoushe.com/ys/#/accountCenter/0?id=${postData.value?.user.uid}`;
await TGClient.open("web_thin", url);
}
async function toPost(): Promise<void> {
const url = `https://m.miyoushe.com/ys/#/article/${postId}`;
await TGClient.open("web_thin", url);
}
onUnmounted(() => {
clearInterval(shareTimeTimer.value);
});
</script>
<style lang="css" scoped>
.tp-post-body {
@@ -251,26 +268,35 @@ function createPostJson(postId: number): void {
/* title */
.tp-post-title {
margin: 10px auto;
display: flex;
width: fit-content;
align-items: center;
justify-content: start;
color: var(--common-text-title);
cursor: pointer;
font-family: var(--font-title);
font-size: 20px;
}
.mpt-official {
display: inline-block;
width: 30px;
width: 24px;
align-items: center;
justify-content: center;
border-radius: 5px;
margin-right: 2px;
background: var(--common-shadow-1);
color: var(--box-text-3);
color: var(--box-text-5);
font-size: 16px;
text-align: center;
}
/* subtitle */
.tp-post-subtitle {
display: flex;
align-items: center;
justify-content: flex-start;
column-gap: 10px;
font-size: 16px;
opacity: 0.6;
}
@@ -283,6 +309,7 @@ function createPostJson(postId: number): void {
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px dashed var(--common-shadow-2);
margin-bottom: 10px;
}
.tp-post-version {
@@ -328,6 +355,7 @@ function createPostJson(postId: number): void {
position: relative;
width: 50px;
height: 50px;
cursor: pointer;
}
.mpa-icon {
@@ -397,4 +425,37 @@ function createPostJson(postId: number): void {
column-gap: 2px;
opacity: 0.8;
}
/* extra */
.tp-post-extra {
display: flex;
align-items: center;
justify-content: start;
column-gap: 10px;
}
/* collection */
.tp-post-collection {
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
color: var(--box-text-3);
column-gap: 5px;
cursor: pointer;
font-family: var(--font-title);
font-size: 12px;
}
/* topic */
.tp-post-topic {
display: flex;
align-items: center;
justify-content: center;
color: var(--box-text-5);
font-family: var(--font-title);
font-size: 12px;
}
</style>

View File

@@ -9,34 +9,34 @@
<span>{{ data.name }} {{ data.title }}</span>
<span>{{ data.description }}</span>
</div>
<div class="twc-bi-grid">
<div class="twc-bim-item">
<div class="twc-bi-grid1">
<div class="twc-big-item">
<span>所属</span>
<span>{{ data.brief.camp }}</span>
</div>
<div class="twc-bim-item">
<div class="twc-big-item">
<span>命之座</span>
<span>{{ data.brief.constellation }}</span>
</div>
<div class="twc-bim-item">
<div class="twc-big-item">
<span>生日</span>
<span>{{ data.brief.birth }}</span>
</div>
</div>
<div class="twc-bi-grid">
<div class="twc-bib-item">
<div class="twc-bi-grid2">
<div class="twc-big-item">
<span>汉语CV</span>
<span>{{ data.brief.cv.cn }}</span>
</div>
<div class="twc-bib-item">
<div class="twc-big-item">
<span>日语CV</span>
<span>{{ data.brief.cv.jp }}</span>
</div>
<div class="twc-bib-item">
<div class="twc-big-item">
<span>英语CV</span>
<span>{{ data.brief.cv.en }}</span>
</div>
<div class="twc-bib-item">
<div class="twc-big-item">
<span>韩语CV</span>
<span>{{ data.brief.cv.kr }}</span>
</div>
@@ -46,7 +46,7 @@
<TwcMaterials :data="data.materials" />
<TwcSkills :data="data.skills" />
<TwcConstellations :data="data.constellation" />
<v-expansion-panels :theme="vuetifyTheme" class="twc-text-item">
<v-expansion-panels class="twc-text-item">
<v-expansion-panel>
<template #title><span class="twc-text-title">资料</span></template>
<template #text>
@@ -101,7 +101,6 @@ import TwcConstellations from "../components/wiki/twc-constellations.vue";
import TwcMaterials from "../components/wiki/twc-materials.vue";
import TwcSkills from "../components/wiki/twc-skills.vue";
import { getWikiData } from "../data";
import { useAppStore } from "../store/modules/app";
// 路由数据
const id = <string>useRoute().params.id;
@@ -111,12 +110,6 @@ const loadingEmpty = ref<boolean>(false);
const loadingTitle = ref<string>("正在加载");
const loadingSub = ref<string>();
// 主题
const appStore = useAppStore();
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
});
// 数据
const data = ref<TGApp.App.Character.WikiItem>();
const box = computed(() => {
@@ -142,7 +135,6 @@ onMounted(async () => {
if (res === undefined) return;
data.value = res.default;
await appWindow.setTitle(`Wiki_Character - ${data.value.name}`);
await appWindow.setAlwaysOnTop(true);
loading.value = false;
} catch (error) {
loadingEmpty.value = true;
@@ -174,7 +166,7 @@ onMounted(async () => {
.twc-brief {
display: flex;
align-items: flex-end;
align-items: flex-start;
column-gap: 10px;
}
@@ -202,36 +194,27 @@ onMounted(async () => {
opacity: 0.8;
}
.twc-bi-grid {
.twc-bi-grid1 {
display: grid;
column-gap: 10px;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr);
}
.twc-bim-item {
.twc-bi-grid2 {
display: grid;
column-gap: 10px;
grid-template-columns: repeat(2, 1fr);
}
.twc-big-item {
display: flex;
column-gap: 5px;
}
.twc-bim-item :nth-child(1) {
.twc-big-item :nth-child(1) {
font-weight: bold;
}
.twc-bib-item {
display: flex;
flex-direction: column;
}
.twc-bib-item :nth-child(1) {
font-weight: bold;
}
.twc-text {
display: flex;
flex-direction: column;
row-gap: 10px;
}
.twc-text-title {
color: var(--common-text-title);
font-family: var(--font-title);

View File

@@ -1,17 +1,65 @@
<template>
<TSwitchTheme />
<ToLoading v-model="loading" :empty="loadingEmpty" :title="loadingTitle" :subtitle="loadingSub" />
<div class="ww-box">
{{ data }}
<div class="tww-box" v-if="data !== undefined">
<div class="tww-brief">
<TItembox :model-value="box" />
<div class="tww-brief-info">
<div class="tww-brief-title">{{ data.name }}</div>
<v-rating
class="tww-brief-rating"
v-model="select"
:length="selectItems.length"
:size="24"
dense
/>
<div class="tww-brief-desc">{{ data.description }}</div>
</div>
</div>
<TwcMaterials :data="data.materials" />
<v-expansion-panels class="tww-affix">
<v-expansion-panel expand-icon="mdi-menu-down">
<template #title>
<span class="tww-text-title">{{ data.affix.Name }}-精炼 {{ select }}</span>
</template>
<template #text>
<span
class="tww-text-content"
v-html="parseHtmlText(data.affix.Descriptions[select - 1].Description)"
/>
</template>
</v-expansion-panel>
</v-expansion-panels>
<v-expansion-panels class="tww-story">
<v-expansion-panel
expand-icon="mdi-menu-down"
v-for="(story, index) in data.story"
:key="index"
>
<template #title>
<span class="tww-text-title">
{{ data.story.length > 1 ? `故事 ${index + 1}` : "故事" }}
</span>
</template>
<template #text>
<span class="tww-text-content">{{ parseHtmlText(story) }}</span>
</template>
</v-expansion-panel>
</v-expansion-panels>
</div>
</template>
<script lang="ts" setup>
import { appWindow } from "@tauri-apps/api/window";
import { onMounted, ref } from "vue";
import { computed, onMounted, ref, toRaw } from "vue";
import { useRoute } from "vue-router";
import TSwitchTheme from "../components/app/t-switchTheme.vue";
import showSnackbar from "../components/func/snackbar";
import TItembox, { TItemBoxData } from "../components/main/t-itembox.vue";
import ToLoading from "../components/overlay/to-loading.vue";
import TwcMaterials from "../components/wiki/twc-materials.vue";
import { getWikiData } from "../data";
import { parseHtmlText } from "../utils/toolFunc";
// 路由数据
const id = <string>useRoute().params.id;
@@ -23,6 +71,21 @@ const loadingSub = ref<string>();
// 数据
const data = ref<TGApp.App.Weapon.WikiItem>();
const box = computed(() => {
return <TItemBoxData>{
bg: `/icon/bg/${data.value?.star}-Star.webp`,
icon: `/WIKI/weapon/${data.value?.id}.webp`,
size: "128px",
height: "128px",
display: "inner",
lt: `/icon/weapon/${data.value?.weapon}.webp`,
ltSize: "40px",
innerHeight: 0,
clickable: false,
};
});
const select = ref<number>(1);
const selectItems = ref<number[]>([]);
onMounted(async () => {
await appWindow.show();
@@ -30,7 +93,9 @@ onMounted(async () => {
const res = await getWikiData("Weapon", id);
if (res !== undefined) {
data.value = res.default;
console.log(toRaw(data.value));
}
selectItems.value = data.value?.affix.Descriptions.map((item) => item.Level) ?? [];
loading.value = false;
} catch (error) {
loadingEmpty.value = true;
@@ -45,9 +110,62 @@ onMounted(async () => {
text: "Wiki 数据获取失败,即将关闭窗口",
color: "error",
});
// setTimeout(() => {
// appWindow.close();
// }, 3000);
setTimeout(() => {
appWindow.close();
}, 3000);
}
});
</script>
<style lang="css" scoped>
.tww-box {
display: flex;
width: 800px;
flex-direction: column;
margin: 0 auto;
row-gap: 10px;
}
.tww-brief {
display: flex;
align-items: flex-start;
column-gap: 10px;
}
.tww-brief-info {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.tww-brief-title {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.tww-brief-rating {
color: var(--common-text-title);
}
.tww-brief-desc {
display: flex;
align-items: flex-end;
opacity: 0.8;
}
.tww-story {
display: flex;
flex-direction: column;
row-gap: 5px;
}
.tww-text-title {
font-weight: bold;
}
.tww-text-content {
font-size: 14px;
white-space: pre-wrap;
word-break: break-all;
}
</style>

View File

@@ -1,20 +1,18 @@
/**
* @file web utils backupData.ts
* @file web/utils/backupData.ts
* @description 数据备份
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.5
*/
// tauri
import { fs, path } from "@tauri-apps/api";
/**
* @description 备份 Cookie 数据
* @since Alpha v0.2.0
* @param {Record<string, string>} cookie cookie
* @param {TGApp.User.Account.Cookie} cookie cookie
* @returns {Promise<void>}
*/
export async function backupCookieData(cookie: Record<string, string>): Promise<void> {
export async function backupCookieData(cookie: TGApp.User.Account.Cookie): Promise<void> {
const savePath = `${await path.appLocalDataDir()}\\userData\\cookie.json`;
await fs.writeTextFile(savePath, JSON.stringify(cookie, null, 2));
}

View File

@@ -1,7 +1,7 @@
/**
* @file web/utils/getRequestHeader.ts
* @description 获取请求头
* @since Beta v0.3.6
* @since Beta v0.3.9
*/
import Md5 from "js-md5";
@@ -102,19 +102,14 @@ export function getRequestHeader(
/**
* @description 获取 DS
* @since Beta v0.3.4
* @since Beta v0.3.9
* @param {string} saltType salt 类型
* @param {number} dsType ds 类型
* @param {Record<string, string|number>|string} body
* @param {Record<string, string|number>|string} query
* @returns {string} DS
*/
export function getDS4JS(
saltType: string,
dsType: 1 | 2,
body: undefined,
query: undefined,
): string;
export function getDS4JS(saltType: string, dsType: 1, body: undefined, query: undefined): string;
export function getDS4JS(
saltType: string,
dsType: 2,

View File

@@ -1,7 +1,7 @@
/**
* @file web utils restoreData.ts
* @file web/utils/restoreData.ts
* @description 数据恢复
* @since Alpha v0.2.0
* @since Beta v0.3.9
*/
import { fs, path } from "@tauri-apps/api";
@@ -23,13 +23,15 @@ export async function restoreCookieData(): Promise<boolean> {
/**
* @description 恢复深渊数据
* @since Alpha v0.2.0
* @since Beta v0.3.9
* @returns {Promise<boolean>}
*/
export async function restoreAbyssData(): Promise<boolean> {
const abyssPath = `${await path.appLocalDataDir()}\\userData\\abyss.json`;
if (!(await fs.exists(abyssPath))) return false;
const abyssData = await fs.readTextFile(abyssPath);
await TGSqlite.restoreAbyss(JSON.parse(abyssData));
const parseData = JSON.parse(abyssData);
if (!parseData || !Array.isArray(parseData)) return false;
await TGSqlite.restoreAbyss(<TGApp.Sqlite.Abyss.SingleTable[]>parseData);
return true;
}