Compare commits

...

46 Commits

Author SHA1 Message Date
目棃
1f814bc8b5 🚀 v0.5.2 2024-08-11 15:46:05 +08:00
目棃
885fa3b19e JSBridge分辨率适配 2024-08-11 15:40:17 +08:00
目棃
82ba88a2c9 🍱 更新4.8下半数据 2024-08-09 09:30:23 +08:00
目棃
1a4d175e3d 👽️ 调整更新日志链接 2024-08-09 09:23:16 +08:00
目棃
6a39aa127b 🐛 修复窗口全屏失效 2024-08-03 21:37:55 +08:00
目棃
5c0d5f50fe ⬆️ migrate tauri-rc 2024-08-02 23:09:42 +08:00
目棃
27501acfd2 🔥 移除扫码登录模块 2024-08-02 13:16:35 +08:00
目棃
69157ea008 🐛 修复公告时间获取异常&内容渲染异常 2024-08-02 13:14:25 +08:00
目棃
db00765f7b ♻️ 重构窗体/缩放调整 2024-08-01 22:09:54 +08:00
目棃
49854367b1 🐛 修复日志目录异常 2024-07-31 19:13:16 +08:00
目棃
cab497f573 📝 更新文档 2024-07-31 18:21:56 +08:00
目棃
2c5c205566 🚀 v0.5.1 2024-07-30 10:50:44 +08:00
目棃
447ee3b329 💄 根据屏幕缩放调整应用缩放 2024-07-30 10:38:58 +08:00
目棃
e049508ab6 🐛 调整抽数计算规则 2024-07-29 14:59:15 +08:00
目棃
9bfb7c4108 🐛 完善活动正则 2024-07-28 23:24:37 +08:00
目棃
ea3a88ecb4 🐛 修复未确认便弹出选择窗口 2024-07-28 17:01:47 +08:00
目棃
b502cf4025 ♻️ 帖子显示版块信息 2024-07-27 21:08:15 +08:00
目棃
942e63654e 验证码登录成功
close #118
2024-07-27 20:41:39 +08:00
目棃
baf8c3f794 💄 显示设备信息 2024-07-27 20:31:54 +08:00
目棃
2abfcdc050 验证码登录测试成功 #118 2024-07-27 20:01:32 +08:00
目棃
d22203e7b4 🐛 修复分享功能异常 2024-07-27 14:14:04 +08:00
目棃
8f116c954f 🌱 继续优化验证码登录,尝试引入geetest 2024-07-27 12:51:28 +08:00
目棃
a8b1ff4588 💄 优化label可见度 2024-07-27 11:55:22 +08:00
目棃
5d036ddeee 🐛 修复图片加载失败 2024-07-27 00:17:10 +08:00
目棃
b2843dbf13 💄 分享色背景设为透明 2024-07-26 22:55:44 +08:00
目棃
672fa2e536 ♻️ 重构链接解析处理 2024-07-26 22:36:40 +08:00
目棃
a7a0a8b0e0 🐛 修复过时用法 2024-07-26 22:03:44 +08:00
目棃
e9252bbfcd 💄 调整不同状态下的背景色、边缘、文本色 2024-07-26 21:27:54 +08:00
目棃
e3e5b757e4 ♻️ 重构 http req,捕获aigis 2024-07-26 18:02:58 +08:00
目棃
6b18d63fb0 🐛 修复遗漏token 2024-07-26 17:15:44 +08:00
目棃
948db203bd 支持复制ck 2024-07-26 14:28:21 +08:00
目棃
f0b3a311d7 🐛 修复用户数据目录打开失败 2024-07-26 14:19:48 +08:00
目棃
289620922d 🧪 -3001,参数错误☹ 2024-07-25 19:40:07 +08:00
目棃
20ad79f08b 🌱 验证码登录请求草创 #118 2024-07-25 16:14:20 +08:00
目棃
db3cb2fa29 💄 角色/武器图鉴材料支持左右切换 2024-07-25 14:18:38 +08:00
目棃
f822c0b32d 💄 仅在原神页面提供公告页跳转 2024-07-25 14:03:04 +08:00
目棃
632576de94 💄 帖子一次刷新12条 2024-07-25 13:07:46 +08:00
目棃
0b8dc2ef60 ️ 优化查找逻辑,不会自动弹出 2024-07-25 13:02:08 +08:00
目棃
047f9eaf3a ️ 按钮显式表示相关操作 2024-07-24 13:01:22 +08:00
目棃
792d337e45 ️ 调整分类删除逻辑,优化卡片样式 2024-07-24 12:49:33 +08:00
目棃
de89aa5159 ️ 调整按钮顺序,添加刷新按钮 2024-07-24 12:02:14 +08:00
目棃
5c8f2d5e57 ️ 咨询页刷新时记忆 tab 状态 2024-07-23 20:03:28 +08:00
目棃
7391752247 🐛 修复nav返回数据为空或者缺失 2024-07-23 13:07:43 +08:00
目棃
27a2e93efc ✏️ UIGF4导出添加lang 2024-07-18 18:13:12 +08:00
目棃
5bf2521938 ️ 不允许低于 UIGF v2.3 版本的数据导入 2024-07-18 18:04:12 +08:00
目棃
41ee27c74e 🔨 调整 pre-commit 脚本 2024-07-18 18:03:31 +08:00
79 changed files with 7663 additions and 5358 deletions

View File

@@ -43,11 +43,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 22.0.0
node-version: 22.3.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.5.0
version: 9.6.0
- name: remove lockfile
run: rm pnpm-lock.yaml
- name: Install frontend dependencies

View File

@@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
pnpm lint-staged

View File

@@ -2,14 +2,45 @@
Author: 目棃
Description: CHANGELOG
Date: 2024-07-09
Update: 2024-07-16
Update: 2024-08-11
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2024-07-09 14:16:16`
>
> 更新于 `2024-07-16 22:03:11`
> 更新于 `2024-08-11 15:43:34`
## [0.5.0](https://github.com/BTMuli/TeyvatGuide/releases/v0.5.0) (2024-07-09)
## [0.5.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.5.2) (2024-08-11)
- 🐛 修复日志目录异常
- ♻️ 重构窗体/缩放调整逻辑
- 🐛 修复公告时间获取异常&内容渲染异常
- 🔥 移除扫码登录模块
👽️ 调整更新日志链接
- 🍱 更新4.8下半数据
## [0.5.1](https://github.com/BTMuli/TeyvatGuide/releases/v0.5.1) (2024-07-30)
- ⚡️ 不允许低于 UIGF v2.3 版本的数据导入
- 🐛 修复网页小工具数据获取异常
- ⚡️ 咨讯页刷新时记忆 tab 状态,顶部按钮进行调整
- ⚡️ 调整收藏页面分类删除逻辑,优化卡片样式
- ⚡️ 设置页数据目录添加按钮显式表示相关操作
- ⚡️ 优化帖子搜索逻辑,不会自动弹出浮窗
- 💄 帖子页面调整刷新数量20→12
- 💄 角色/武器图鉴材料支持左右切换
- ✨ 支持短信验证码登录 [`#118`](https://github.com/BTMuli/TeyvatGuide/issues/118)
- 💄 调整首页日历组件不同状态下的背景色、边缘、文本色
- ♻️ 重构米游社相关链接解析处理
- 💄 分享色背景设为透明
- 🐛 修复特定情况下的米游社子窗口分享功能异常
- 💄 设置页显示设备信息支持复制cookie
- 💄 帖子顶部添加分区图标
- 🐛 修复祈愿页面导出按钮逻辑异常
- 🐛 修复公告页部分公告时间解析异常
- 🐛 调整祈愿页面抽数计算规则
- 💄 根据屏幕缩放调整应用缩放
## [0.5.0](https://github.com/BTMuli/TeyvatGuide/releases/v0.5.0) (2024-07-17)
- ⬆️ 底层架构更新,升级至 `Tauri v2` [`#92`](https://github.com/BTMuli/TeyvatGuide/issues/92)
- 🐛 修复数据恢复异常

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 说明文档
Date: 2023-03-05
Update: 2024-07-14
Update: 2024-07-31
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
>
> 更新于 `2024-07-14 08:50:43`
> 更新于 `2024-07-31 18:21:43`
![](https://img.shields.io/github/last-commit/BTMuli/TeyvatGuide?style=for-the-badge) ![](https://img.shields.io/github/commits-since/BTMuli/TeyvatGuide/latest?include_prereleases&style=for-the-badge)
@@ -33,7 +33,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
> macOS 用户可以通过 Github Release 下载
[![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/BTMuli/TeyvatGuide?include_prereleases&style=for-the-badge)](https://github.com/BTMuli/TeyvatGuide/releases/latest)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/BTMuli/TeyvatGuide?style=for-the-badge)](https://github.com/BTMuli/TeyvatGuide/releases/latest)
## 仓库概况 / Repo Stats
@@ -48,7 +48,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 米游社官方帖获取(支持通过 ID 获取)
- [x] 米游社各分区帖子获取(支持通过 ID 获取)
- [x] 成就管理UIAF v1.1),支持 [`YaeAchievement`](https://github.com/HolographicHat/YaeAchievement) 导入
- [x] 祈愿管理UIGF v3.00.5.0 起支持 UIGF v4.0
- [x] 祈愿管理UIGF v3.0UIGF v4.0
- [x] 留影叙佳期画片查看
- [x] 帖子收藏
@@ -66,6 +66,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 武器图鉴
- [x] 名片图鉴
- [x] 卡牌图鉴
- [x] 材料图鉴
- 应用功能:
- [x] 浅色/深色主题切换

View File

@@ -5,6 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TeyvatGuide</title>
<script src="https://static.geetest.com/v4/gt4.js"></script>
</head>
<body>

View File

@@ -1,6 +1,6 @@
{
"name": "TeyvatGuide",
"version": "0.5.0",
"version": "0.5.2",
"description": "Game Tool for Genshin Impact player",
"private": true,
"type": "module",
@@ -66,73 +66,75 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@tauri-apps/api": "2.0.0-beta.14",
"@tauri-apps/plugin-deep-link": "2.0.0-beta.8",
"@tauri-apps/plugin-dialog": "2.0.0-beta.6",
"@tauri-apps/plugin-fs": "2.0.0-beta.6",
"@tauri-apps/plugin-http": "2.0.0-beta.7",
"@tauri-apps/plugin-log": "2.0.0-beta.7",
"@tauri-apps/plugin-os": "2.0.0-beta.6",
"@tauri-apps/plugin-process": "2.0.0-beta.6",
"@tauri-apps/plugin-shell": "2.0.0-beta.7",
"@tauri-apps/plugin-sql": "2.0.0-beta.6",
"ajv": "^8.16.0",
"@tauri-apps/api": "2.0.0-rc.0",
"@tauri-apps/plugin-deep-link": "2.0.0-beta.10",
"@tauri-apps/plugin-dialog": "2.0.0-beta.8",
"@tauri-apps/plugin-fs": "2.0.0-beta.8",
"@tauri-apps/plugin-http": "2.0.0-beta.9",
"@tauri-apps/plugin-log": "2.0.0-beta.9",
"@tauri-apps/plugin-os": "2.0.0-beta.8",
"@tauri-apps/plugin-process": "2.0.0-beta.8",
"@tauri-apps/plugin-shell": "2.0.0-beta.9",
"@tauri-apps/plugin-sql": "2.0.0-beta.7",
"ajv": "^8.17.1",
"artplayer": "^5.1.6",
"clipboard": "^2.0.11",
"color-convert": "^2.0.1",
"echarts": "^5.5.1",
"html2canvas": "^1.4.1",
"js-md5": "^0.8.3",
"pinia": "^2.1.7",
"jsencrypt": "^3.3.2",
"pinia": "^2.2.0",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode.vue": "^3.4.1",
"uuid": "^10.0.0",
"vue": "^3.4.31",
"vue": "^3.4.35",
"vue-echarts": "^6.7.3",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.4.0",
"vuetify": "^3.6.11",
"vue-router": "^4.4.2",
"vuetify": "^3.6.14",
"wcag-color": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.6.0",
"@eslint/js": "^9.8.0",
"@tauri-apps/cli": "2.0.0-beta.21",
"@types/color-convert": "^2.0.3",
"@types/js-md5": "^0.7.2",
"@types/node": "^20.14.10",
"@types/node": "^22.1.0",
"@types/uuid": "^10.0.0",
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-vue": "^5.0.5",
"@typescript-eslint/parser": "^8.0.0",
"@vitejs/plugin-vue": "^5.1.2",
"concurrently": "^8.2.2",
"eslint": "^9.6.0",
"eslint-config-love": "^53.0.0",
"eslint": "^9.8.0",
"eslint-config-love": "^62.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-n": "^17.9.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.4.0",
"eslint-plugin-n": "^17.10.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.0.0",
"eslint-plugin-vue": "^9.27.0",
"eslint-plugin-yml": "^1.14.0",
"globals": "^15.8.0",
"husky": "^9.0.11",
"globals": "^15.9.0",
"husky": "^9.1.4",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.2.7",
"oxlint": "^0.5.2",
"prettier": "3.3.2",
"stylelint": "^16.6.1",
"oxlint": "^0.6.1",
"prettier": "3.3.3",
"stylelint": "^16.8.1",
"stylelint-config-idiomatic-order": "^10.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.8.0",
"stylelint-high-performance-animation": "^1.10.0",
"stylelint-order": "^6.0.4",
"stylelint-prettier": "^5.0.0",
"typescript": "^5.5.3",
"typescript-eslint": "^7.15.0",
"vite": "^5.3.3",
"vite-plugin-vue-devtools": "^7.3.5",
"stylelint-prettier": "^5.0.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.0",
"vite": "^5.3.5",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-vue-devtools": "^7.3.7",
"vite-plugin-vuetify": "^2.0.3",
"vue-eslint-parser": "^9.4.3",
"yaml-eslint-parser": "^1.2.3"

2079
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

513
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "TeyvatGuide"
version = "0.5.0"
version = "0.5.2"
description = "Game Tool for Genshin Impact player"
authors = ["BTMuli <bt-muli@outlook.com>"]
license = "MIT"
@@ -10,15 +10,15 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.0-beta.18", features = [] }
tauri-build = { version = "2.0.0-beta.19", features = [] }
[dependencies]
chrono = "0.4.38"
log = "0.4.22"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
tauri = { version = "2.0.0-beta.23", features = [] }
tauri-utils = "2.0.0-beta.18"
serde_json = "1.0.122"
tauri = { version = "2.0.0-rc.0", features = [] }
tauri-utils = "2.0.0-rc.0"
url = "2.5.2"
walkdir = "2.5.0"

View File

@@ -4,23 +4,28 @@
"description": "Capability for the dev json window",
"windows": ["Dev_JSON"],
"permissions": [
"app:allow-version",
"app:default",
"event:allow-listen",
"event:default",
"core:app:allow-version",
"core:app:default",
"core:event:allow-listen",
"core:event:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"path:allow-resolve-directory",
"path:default",
"core:path:allow-resolve-directory",
"core:path:default",
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"window:allow-close",
"window:allow-destroy",
"window:allow-set-title",
"window:allow-set-fullscreen",
"window:allow-show",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-set-fullscreen",
"core:window:allow-show",
"core:window:default",
{
"identifier": "http:default",
"allow": [

View File

@@ -6,36 +6,80 @@
"permissions": [
"dialog:allow-message",
"dialog:default",
"event:allow-emit",
"path:allow-resolve-directory",
"path:default",
"core:event:allow-emit",
"http:allow-fetch",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:default",
"core:path:allow-resolve-directory",
"core:path:default",
{
"identifier": "fs:allow-exists",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-mkdir",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-read-dir",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-remove",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-write-file",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "fs:allow-write-text-file",
"allow": [{ "path": "**" }]
"allow": [
{
"path": "**"
}
]
},
{
"identifier": "http:default",
"allow": [
{
"url": "https://*.miyoushe.com/*"
},
{
"url": "https://*.mihoyo.com/*"
}
]
}
],
"remote": {

View File

@@ -4,28 +4,33 @@
"description": "Capability for the sub window",
"windows": ["Sub_window"],
"permissions": [
"app:allow-version",
"app:default",
"core:app:allow-version",
"core:app:default",
"dialog:allow-save",
"dialog:default",
"event:allow-listen",
"event:default",
"core:event:allow-listen",
"core:event:default",
"fs:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"path:allow-resolve-directory",
"path:default",
"core:path:allow-resolve-directory",
"core:path:default",
"shell:allow-open",
"shell:default",
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"window:allow-close",
"window:allow-destroy",
"window:allow-set-title",
"window:allow-set-fullscreen",
"window:allow-show",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-set-fullscreen",
"core:window:allow-show",
"core:window:default",
{
"identifier": "fs:allow-exists",
"allow": [{ "path": "**" }]

View File

@@ -4,18 +4,18 @@
"description": "Capability for the main window",
"windows": ["TeyvatGuide"],
"permissions": [
"app:allow-version",
"app:default",
"core:app:allow-version",
"core:app:default",
"dialog:allow-save",
"dialog:default",
"event:allow-listen",
"event:default",
"core:event:allow-listen",
"core:event:default",
"fs:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"path:allow-resolve-directory",
"path:default",
"core:path:allow-resolve-directory",
"core:path:default",
"process:allow-exit",
"process:default",
"shell:allow-execute",
@@ -24,16 +24,19 @@
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"webview:allow-create-webview-window",
"webview:default",
"window:allow-close",
"window:allow-destroy",
"window:default",
"window:allow-is-minimized",
"window:allow-set-title",
"window:allow-set-focus",
"window:allow-show",
"window:allow-unminimize",
"core:webview:allow-create-webview-window",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:default",
"core:window:allow-is-minimized",
"core:window:allow-set-title",
"core:window:allow-set-focus",
"core:window:allow-show",
"core:window:allow-unminimize",
{
"identifier": "fs:allow-exists",
"allow": [{ "path": "**" }]

File diff suppressed because it is too large Load Diff

View File

@@ -5,23 +5,28 @@
"local": true,
"windows": ["Dev_JSON"],
"permissions": [
"app:allow-version",
"app:default",
"event:allow-listen",
"event:default",
"core:app:allow-version",
"core:app:default",
"core:event:allow-listen",
"core:event:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"path:allow-resolve-directory",
"path:default",
"core:path:allow-resolve-directory",
"core:path:default",
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"window:allow-close",
"window:allow-destroy",
"window:allow-set-title",
"window:allow-set-fullscreen",
"window:allow-show",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-set-fullscreen",
"core:window:allow-show",
"core:window:default",
{
"identifier": "http:default",
"allow": [{ "url": "https://*.miyoushe.com/*" }, { "url": "https://*.mihoyo.com/*" }]
@@ -38,16 +43,25 @@
"permissions": [
"dialog:allow-message",
"dialog:default",
"event:allow-emit",
"path:allow-resolve-directory",
"path:default",
"core:event:allow-emit",
"http:allow-fetch",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:default",
"core:path:allow-resolve-directory",
"core:path:default",
{ "identifier": "fs:allow-exists", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-mkdir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-dir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-text-file", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-remove", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-write-file", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-write-text-file", "allow": [{ "path": "**" }] }
{ "identifier": "fs:allow-write-text-file", "allow": [{ "path": "**" }] },
{
"identifier": "http:default",
"allow": [{ "url": "https://*.miyoushe.com/*" }, { "url": "https://*.mihoyo.com/*" }]
}
],
"platforms": ["windows", "macOS"]
},
@@ -57,28 +71,33 @@
"local": true,
"windows": ["Sub_window"],
"permissions": [
"app:allow-version",
"app:default",
"core:app:allow-version",
"core:app:default",
"dialog:allow-save",
"dialog:default",
"event:allow-listen",
"event:default",
"core:event:allow-listen",
"core:event:default",
"fs:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"path:allow-resolve-directory",
"path:default",
"core:path:allow-resolve-directory",
"core:path:default",
"shell:allow-open",
"shell:default",
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"window:allow-close",
"window:allow-destroy",
"window:allow-set-title",
"window:allow-set-fullscreen",
"window:allow-show",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-set-fullscreen",
"core:window:allow-show",
"core:window:default",
{ "identifier": "fs:allow-exists", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-mkdir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-dir", "allow": [{ "path": "**" }] },
@@ -99,18 +118,18 @@
"local": true,
"windows": ["TeyvatGuide"],
"permissions": [
"app:allow-version",
"app:default",
"core:app:allow-version",
"core:app:default",
"dialog:allow-save",
"dialog:default",
"event:allow-listen",
"event:default",
"core:event:allow-listen",
"core:event:default",
"fs:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"path:allow-resolve-directory",
"path:default",
"core:path:allow-resolve-directory",
"core:path:default",
"process:allow-exit",
"process:default",
"shell:allow-execute",
@@ -119,16 +138,19 @@
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"webview:allow-create-webview-window",
"webview:default",
"window:allow-close",
"window:allow-destroy",
"window:default",
"window:allow-is-minimized",
"window:allow-set-title",
"window:allow-set-focus",
"window:allow-show",
"window:allow-unminimize",
"core:webview:allow-create-webview-window",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:default",
"core:window:allow-is-minimized",
"core:window:allow-set-title",
"core:window:allow-set-focus",
"core:window:allow-show",
"core:window:allow-unminimize",
{ "identifier": "fs:allow-exists", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-mkdir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-dir", "allow": [{ "path": "**" }] },

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
//! @file src/client/menu.rs
//! @desc 客户端菜单模块,负责操作米游社客户端菜单
//! @since Beta v0.5.0
//! @since Beta v0.5.2
use crate::client::utils;
use tauri::menu::{Menu, MenuBuilder, MenuEvent, MenuItemBuilder, Submenu, SubmenuBuilder};
use tauri::{AppHandle, LogicalSize, Manager, Size, Window, Wry};
use tauri_utils::config::WebviewUrl;
@@ -21,7 +22,7 @@ pub fn get_mhy_client_url(func: String) -> WebviewUrl {
.parse()
.unwrap();
}
return WebviewUrl::External(url_res);
WebviewUrl::External(url_res)
}
// 创建子菜单-工具
@@ -39,7 +40,7 @@ fn create_utils_menu(app: AppHandle) -> Submenu<Wry> {
.item(&rotate_window_submenu)
.build()
.expect("failed to create utils_menu");
return utils_menu;
utils_menu
}
// 创建米游社客户端菜单
@@ -48,13 +49,13 @@ pub fn create_mhy_menu(app: AppHandle) -> Menu<Wry> {
let cancel_top_menu = MenuItemBuilder::with_id("cancel_top", "取消置顶").build(&app).unwrap();
let open_post_menu = MenuItemBuilder::with_id("open_post", "打开帖子").build(&app).unwrap();
let utils_menu = create_utils_menu(app.clone());
return MenuBuilder::new(&app)
MenuBuilder::new(&app)
.item(&top_menu)
.item(&cancel_top_menu)
.item(&open_post_menu)
.item(&utils_menu)
.build()
.expect("failed to create mhy_menu");
.expect("failed to create mhy_menu")
}
// 菜单栏事件处理
@@ -181,10 +182,17 @@ fn handle_menu_rotate_window(app_handle: &Window) {
let window = window_get.unwrap();
// 获取窗口宽高比
let cur_size = window.inner_size().ok().unwrap();
let monitor = window.primary_monitor().ok().unwrap().unwrap();
if cur_size.width > cur_size.height {
window.set_size(Size::Logical(LogicalSize { width: 400.0, height: 800.0 })).unwrap();
let trans_size = utils::get_window_size2(monitor, 400.0, 800.0);
window
.set_size(Size::Logical(LogicalSize { width: trans_size.0, height: trans_size.1 }))
.unwrap();
} else {
window.set_size(Size::Logical(LogicalSize { width: 1280.0, height: 720.0 })).unwrap();
let trans_size = utils::get_window_size2(monitor, 1280.0, 720.0);
window
.set_size(Size::Logical(LogicalSize { width: trans_size.0, height: trans_size.1 }))
.unwrap();
}
window.center().unwrap();
window.set_focus().unwrap();

View File

@@ -1,8 +1,10 @@
//! @file src/client/mod.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.5.0
//! @since Beta v0.5.2
mod menu;
mod utils;
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
use tauri_utils::config::WebviewUrl;
@@ -29,8 +31,9 @@ pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
window_find.unwrap().destroy().unwrap();
return;
}
let trans_size = utils::get_window_size(handle.clone(), win_width, win_height);
WebviewWindowBuilder::new(&handle, "mhy_client", url_parse)
.inner_size(win_width, win_height)
.inner_size(trans_size.0, trans_size.1)
.title("米游社")
.center()
.user_agent(win_ua)

View File

@@ -0,0 +1,22 @@
//! @file src/client/utils.rs
//! @desc 结合屏幕分辨率获取窗口大小
//! @since Beta v0.5.2
use tauri::{AppHandle, Manager, Monitor};
pub fn get_window_size(app: AppHandle, width: f64, height: f64) -> (f64, f64) {
let mhy_window = app.get_webview_window("TeyvatGuide").unwrap();
let monitor = mhy_window.primary_monitor().unwrap().expect("failed to get primary monitor");
get_window_size2(monitor, width, height)
}
pub fn get_window_size2(monitor: Monitor, width: f64, height: f64) -> (f64, f64) {
let monitor_size = monitor.size();
let monitor_width = monitor_size.width as f64;
let monitor_height = monitor_size.height as f64;
let width_scale = monitor_width / 1920.0;
let height_scale = monitor_height / 1080.0;
let get_width = (width * width_scale).round();
let get_height = (height * height_scale).round();
(get_width, get_height)
}

View File

@@ -1,8 +1,8 @@
//! @file src/commands.rs
//! @desc 命令模块,负责处理命令
//! @since Beta v0.5.0
//! @since Beta v0.5.1
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
use tauri::{AppHandle, Emitter, Manager, WebviewWindowBuilder};
use tauri_utils::config::{WebviewUrl, WindowConfig};
// 放一个常数,用来判断应用是否初始化

View File

@@ -1,11 +1,11 @@
//! @file src/main.rs
//! @desc 主模块,用于启动应用
//! @since Beta v0.5.0
//! @since Beta v0.5.2
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::Manager;
use tauri::{Emitter, Manager};
mod client;
mod commands;
mod plugins;
@@ -45,7 +45,7 @@ fn main() {
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.plugin(plugins::build_sql_plugin())
.plugin(tauri_plugin_sql::Builder::default().build())
.plugin(plugins::build_log_plugin())
.setup(|_app| {
let _window = _app.get_webview_window("TeyvatGuide");

View File

@@ -1,18 +1,12 @@
//! @file src/plugins.rs
//! @desc 插件模块,用于注册插件
//! @since Beta v0.5.0
//! @since Beta v0.5.2
use super::utils;
use log::LevelFilter;
use tauri::plugin::TauriPlugin;
use tauri::Runtime;
use tauri_plugin_log::{Target, TargetKind, TimezoneStrategy};
use tauri_plugin_sql::PluginConfig;
// sqlite 插件
pub fn build_sql_plugin<R: Runtime>() -> TauriPlugin<R, Option<PluginConfig>> {
tauri_plugin_sql::Builder::default().build()
}
// 日志插件
pub fn build_log_plugin<R: Runtime>() -> TauriPlugin<R> {

View File

@@ -1,7 +1,7 @@
{
"productName": "TeyvatGuide",
"identifier": "TeyvatGuide",
"version": "0.5.0",
"version": "0.5.2",
"build": {
"beforeDevCommand": "pnpm vite:dev",
"beforeBuildCommand": "pnpm vite:build",

View File

@@ -11,7 +11,8 @@
</template>
<script lang="ts" setup>
import { app, event, core, webviewWindow } from "@tauri-apps/api";
import { app, event, core, webviewWindow, window as TauriWindow } from "@tauri-apps/api";
import { PhysicalSize } from "@tauri-apps/api/dpi";
import { UnlistenFn, Event } from "@tauri-apps/api/event";
import { mkdir } from "@tauri-apps/plugin-fs";
import { storeToRefs } from "pinia";
@@ -42,7 +43,7 @@ let themeListener: UnlistenFn;
let urlListener: UnlistenFn;
onBeforeMount(async () => {
const win = webviewWindow.getCurrent();
const win = webviewWindow.getCurrentWebviewWindow();
isMain.value = win.label === "TeyvatGuide";
if (isMain.value) {
const title = "Teyvat Guide v" + (await app.getVersion()) + " Beta";
@@ -51,9 +52,42 @@ onBeforeMount(async () => {
await core.invoke("init_app");
urlListener = await getDeepLink();
}
await checkResize();
await win.show();
});
async function checkResize(): Promise<void> {
const screen = await TauriWindow.currentMonitor();
if (screen === null) {
showSnackbar({
text: "获取屏幕信息失败!",
color: "error",
timeout: 3000,
});
return;
}
const windowCur = await webviewWindow.getCurrentWebviewWindow();
if (await windowCur.isMaximized()) return;
const designSize = getSize(windowCur.label);
const widthScale = screen.size.width / 1920;
const heightScale = screen.size.height / 1080;
await windowCur.setSize(
new PhysicalSize(
Math.round(designSize.width * widthScale),
Math.round(designSize.height * heightScale),
),
);
await windowCur.setZoom((1 / screen.scaleFactor) * Math.min(widthScale, heightScale));
await windowCur.center();
return;
}
function getSize(label: string): PhysicalSize {
if (label === "TeyvatGuide") return new PhysicalSize(1600, 900);
if (label === "Sub_Window" || label === "Dev_JSON") return new PhysicalSize(960, 720);
return new PhysicalSize(1280, 720);
}
onMounted(() => {
document.documentElement.className = theme.value;
themeListener = event.listen("readTheme", async (e: Event<string>) => {

View File

@@ -301,7 +301,7 @@ onMounted(async () => {
const theme = e.payload;
themeGet.value = theme === "default" ? "default" : "dark";
});
if (webviewWindow.getCurrent().label === "TeyvatGuide") {
if (webviewWindow.getCurrentWebviewWindow().label === "TeyvatGuide") {
await mhyClient.run();
}
if (userStore.briefInfo.value && userStore.briefInfo.value.nickname) {

View File

@@ -52,7 +52,7 @@ function toStore() {
}
function toSite() {
window.open("https://app.btmuli.ink/docs/Changelogs.html");
window.open("https://app.btmuli.ink/docs/TeyvatGuide/changelogs.html");
}
</script>
<style lang="css" scoped>

View File

@@ -2,41 +2,60 @@
<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>
<v-list-item title="用户数据目录" :subtitle="appStore.userDir">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-folder-key</v-icon>
</div>
</template>
<v-list-item-title style="cursor: pointer" @click="confirmCUD"
>用户数据目录
</v-list-item-title>
<v-list-item-subtitle @click="openPath('user')">{{ appStore.userDir }}</v-list-item-subtitle>
<template #append>
<v-icon @click="copyPath('user')">mdi-content-copy</v-icon>
<v-icon @click="confirmCUD()" style="cursor: pointer" title="修改用户数据目录"
>mdi-pencil
</v-icon>
&emsp;
<v-icon @click="openPath('user')" style="cursor: pointer" title="打开用户数据目录"
>mdi-folder-open
</v-icon>
&emsp;
<v-icon @click="copyPath('user')" style="cursor: pointer" title="复制用户数据目录路径"
>mdi-content-copy
</v-icon>
</template>
</v-list-item>
<v-list-item title="应用数据库路径">
<v-list-item title="应用数据库路径" :subtitle="appStore.dbPath">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-folder-account</v-icon>
</div>
</template>
<v-list-item-subtitle @click="openPath('db')">{{ appStore.dbPath }}</v-list-item-subtitle>
<template #append>
<v-icon @click="copyPath('db')">mdi-content-copy</v-icon>
<v-icon @click="openPath('db')" style="cursor: pointer" title="打开数据库目录"
>mdi-folder-open
</v-icon>
&emsp;
<v-icon @click="copyPath('db')" style="cursor: pointer" title="复制数据库目录路径"
>mdi-content-copy
</v-icon>
</template>
</v-list-item>
<v-list-item>
<v-list-item title="日志目录" :subtitle="appStore.logDir">
<template #prepend>
<div class="config-icon">
<v-icon>mdi-folder-multiple</v-icon>
</div>
</template>
<v-list-item-title style="cursor: pointer" @click="confirmCLD">日志目录</v-list-item-title>
<v-list-item-subtitle @click="openPath('log')">{{ appStore.logDir }}</v-list-item-subtitle>
<template #append>
<v-icon @click="copyPath('log')">mdi-content-copy</v-icon>
<v-icon @click="confirmCLD()" style="cursor: pointer" title="清理日志文件"
>mdi-delete
</v-icon>
&emsp;
<v-icon @click="openPath('log')" style="cursor: pointer" title="打开日志目录"
>mdi-folder-open
</v-icon>
&emsp;
<v-icon @click="copyPath('log')" style="cursor: pointer" title="复制日志目录路径"
>mdi-content-copy
</v-icon>
</template>
</v-list-item>
</v-list>
@@ -45,6 +64,7 @@
import { path } from "@tauri-apps/api";
import { open } from "@tauri-apps/plugin-dialog";
import { readDir, remove } from "@tauri-apps/plugin-fs";
import { onMounted } from "vue";
import TGSqlite from "../../plugins/Sqlite/index.js";
import { useAppStore } from "../../store/modules/app.js";
@@ -55,12 +75,30 @@ import showSnackbar from "../func/snackbar.js";
const appStore = useAppStore();
onMounted(async () => {
const logDir = await path.appLogDir();
const dbPath = `${await path.appConfigDir()}${path.sep()}TeyvatGuide.db`;
let message = "";
if (appStore.dbPath !== dbPath) {
appStore.dbPath = dbPath;
await TGSqlite.saveAppData("dbPath", dbPath);
message += "数据库路径 ";
}
if (appStore.logDir !== logDir) {
appStore.logDir = logDir;
message += "日志路径 ";
}
if (message !== "") {
showSnackbar({
text: `${message}已更新!`,
color: "success",
});
}
});
async function confirmCUD(): Promise<void> {
const oriDir = appStore.userDir;
const check = await showConfirm({
title: "确认修改用户数据路径吗?",
text: "祈愿数据需修改后重新手动备份!",
});
const check = await showConfirm({ title: "确认修改用户数据路径吗?" });
if (!check) {
showSnackbar({
color: "cancel",
@@ -90,11 +128,21 @@ async function confirmCUD(): Promise<void> {
appStore.userDir = dir;
await TGSqlite.saveAppData("userDir", dir);
await backUpUserData(dir);
await remove(oriDir, { recursive: true });
showSnackbar({
text: "已重新备份数据!即将刷新页面!",
timeout: 3000,
text: "已重新备份数据!",
color: "success",
});
const confirm = await showConfirm({
title: "是否删除原用户数据目录?",
text: "删除后不可恢复!",
});
if (confirm) {
await remove(oriDir, { recursive: true });
showSnackbar({
text: "已删除原用户数据目录!",
color: "success",
});
}
setTimeout(() => {
window.location.reload();
}, 4000);

View File

@@ -1,5 +1,4 @@
<template>
<ToGameLogin v-model="scan" @success="refreshUser" />
<v-card class="tcu-box">
<template #prepend>
<v-avatar :image="userInfo.avatar" />
@@ -9,16 +8,29 @@
<template #text>{{ userInfo.desc }}</template>
<template #actions>
<v-spacer />
<v-btn variant="outlined" @click="scan = true" icon="mdi-qrcode-scan" />
<v-btn variant="outlined" @click="confirmRefreshUser" icon="mdi-refresh" :loading="loading" />
<v-btn
variant="outlined"
@click="tryCaptchaLogin()"
icon="mdi-cellphone"
title="验证码登录"
/>
<v-btn
variant="outlined"
@click="confirmRefreshUser"
icon="mdi-refresh"
:loading="loading"
title="刷新用户信息"
/>
<v-btn variant="outlined" @click="confirmCopyCookie" icon="mdi-cookie" title="复制Cookie" />
</template>
</v-card>
</template>
<script lang="ts" setup>
import type { UnlistenFn } from "@tauri-apps/api/event";
import { UnlistenFn } from "@tauri-apps/api/event";
import { storeToRefs } from "pinia";
import { onMounted, onUnmounted, ref, watch } from "vue";
import Mys from "../../plugins/Mys/index.js";
import TGSqlite from "../../plugins/Sqlite/index.js";
import { useAppStore } from "../../store/modules/app.js";
import { useUserStore } from "../../store/modules/user.js";
@@ -26,8 +38,8 @@ import TGLogger from "../../utils/TGLogger.js";
import { getDeviceFp } from "../../web/request/getDeviceFp.js";
import TGRequest from "../../web/request/TGRequest.js";
import showConfirm from "../func/confirm.js";
import showGeetest from "../func/geetest.js";
import showSnackbar from "../func/snackbar.js";
import ToGameLogin from "../overlay/to-gameLogin.vue";
interface TcUserBadgeEmits {
(e: "loadOuter", v: TGApp.Component.Loading.EmitParams): void;
@@ -39,7 +51,6 @@ const appStore = useAppStore();
const userStore = storeToRefs(useUserStore());
const loading = ref<boolean>(false);
const scan = ref<boolean>(false);
const userInfo = ref<TGApp.App.Account.BriefInfo>({
nickname: "未登录",
uid: "-1",
@@ -61,13 +72,72 @@ watch(userStore.briefInfo, (v) => {
}
});
async function tryCaptchaLogin(): Promise<void> {
const phone = await showConfirm({
mode: "input",
title: "请输入手机号",
text: "+86",
});
if (!phone) {
showSnackbar({
color: "cancel",
text: "已取消验证码登录",
});
return;
}
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(phone)) {
showSnackbar({
color: "error",
text: "请输入正确的手机号",
});
return;
}
const actionType = await tryGetCaptcha(phone);
if (!actionType) return;
showSnackbar({
text: `已发送验证码到 ${phone}`,
});
const captcha = await showConfirm({
mode: "input",
title: "请输入验证码",
text: "验证码:",
otcancel: false,
});
if (!captcha) {
showSnackbar({
color: "error",
text: "输入验证码为空",
});
return;
}
const loginResp = await tryLoginByCaptcha(phone, captcha, actionType);
if (!loginResp) return;
userStore.cookie.value = {
account_id: loginResp.user_info.aid,
ltuid: loginResp.user_info.aid,
stuid: loginResp.user_info.aid,
mid: loginResp.user_info.mid,
cookie_token: "",
stoken: loginResp.token.token,
ltoken: "",
};
showSnackbar({
text: "登录成功,即将刷新用户信息",
color: "success",
});
setTimeout(() => {
refreshUser();
}, 1000);
}
async function refreshUser() {
const ck = userStore.cookie.value;
if (ck === undefined || JSON.stringify(ck) === "{}") {
await TGLogger.Error("[tc-userBadge][refreshUser] cookie 不存在");
showSnackbar({
color: "error",
text: "扫码登录后才能刷新用户信息!",
text: "登录后才能刷新用户信息!",
});
appStore.isLogin = false;
return;
@@ -147,8 +217,8 @@ async function refreshUser() {
emits("loadOuter", { show: false });
}
async function refreshUserInfo(cnt: number = 0): Promise<number> {
let failCount = cnt;
async function refreshUserInfo(): Promise<number> {
let failCount = 0;
const ck = userStore.cookie.value;
if (ck === undefined) {
showSnackbar({
@@ -218,6 +288,74 @@ async function confirmRefreshUser(): Promise<void> {
await refreshUser();
}
async function confirmCopyCookie(): Promise<void> {
const res = await showConfirm({
title: "确认复制 Cookie 吗?",
text: "将会复制当前登录的 Cookie",
});
if (!res) {
showSnackbar({
color: "cancel",
text: "已取消复制",
});
return;
}
if (!userStore.cookie) {
showSnackbar({
color: "error",
text: "请先登录",
});
return;
}
const ckText = useUserStore().getAllCookie();
await navigator.clipboard.writeText(ckText);
showSnackbar({
text: "已复制 Cookie!",
color: "success",
});
}
async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | false> {
const captchaResp = await Mys.User.getCaptcha(phone, aigis);
if ("retcode" in captchaResp) {
if (!captchaResp.data || captchaResp.data === "") {
showSnackbar({
text: `[${captchaResp.retcode}] ${captchaResp.message}`,
color: "error",
});
return false;
}
const aigisResp: TGApp.Plugins.Mys.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
const resp = await showGeetest(JSON.parse(aigisResp.data));
const aigisStr = `${aigisResp.session_id};${btoa(JSON.stringify(resp))}`;
return await tryGetCaptcha(phone, aigisStr);
}
return captchaResp.action_type;
}
async function tryLoginByCaptcha(
phone: string,
captcha: string,
actionType: string,
aigis?: string,
): Promise<TGApp.Plugins.Mys.CaptchaLogin.LoginData | false> {
const loginResp = await Mys.User.login(phone, captcha, actionType, aigis);
if ("retcode" in loginResp) {
if (!loginResp.data || loginResp.data === "") {
showSnackbar({
text: `[${loginResp.retcode}] ${loginResp.message}`,
color: "error",
});
return false;
}
const aigisResp: TGApp.Plugins.Mys.CaptchaLogin.CaptchaAigis = JSON.parse(loginResp.data);
const resp = await showGeetest(JSON.parse(aigisResp.data));
const aigisStr = `${aigisResp.session_id};${btoa(JSON.stringify(resp))}`;
return await tryLoginByCaptcha(phone, captcha, actionType, aigisStr);
}
return loginResp;
}
onUnmounted(() => {
if (signListener) signListener();
});

View File

@@ -0,0 +1,59 @@
/**
* @file component/func/geetest.ts
* @description 封装自定义 geetest 组件,通过函数调用的方式,简化 geetest 的使用
* @since Beta v0.5.1
*/
import { h, render } from "vue";
import type { ComponentInternalInstance, VNode } from "vue";
import geetest from "./geetest.vue";
const geetestId = "tg-func-geetest";
/**
* @description 自定义 geetest 组件
* @since Beta v0.5.1
* @extends ComponentInternalInstance
* @property {Function} exposeProxy.displayBox 弹出 geetest 验证
* @return GeetestInstance
*/
interface GeetestInstance extends ComponentInternalInstance {
exposeProxy: {
displayBox: (
props: TGApp.Plugins.Mys.Geetest.reqResp,
) => Promise<TGApp.Plugins.Mys.Geetest.validateResp | false>;
};
}
const renderBox = (props: TGApp.Plugins.Mys.Geetest.reqResp): VNode => {
const container = document.createElement("div");
container.id = geetestId;
const boxVNode: VNode = h(geetest, props);
render(boxVNode, container);
document.body.appendChild(container);
return boxVNode;
};
let geetestInstance: VNode;
/**
* @function showGeetest
* @since Beta v0.5.1
* @description 弹出 geetest 验证
* @param {TGApp.Plugins.Mys.Geetest.reqResp} props geetest 验证的参数
* @return {Promise<TGApp.Plugins.Mys.Geetest.validateResp>} 验证成功返回验证数据
*/
async function showGeetest(
props: TGApp.Plugins.Mys.Geetest.reqResp,
): Promise<TGApp.Plugins.Mys.Geetest.validateResp | false> {
if (geetestInstance !== undefined) {
const boxVue = <GeetestInstance>geetestInstance.component;
return boxVue.exposeProxy.displayBox(props);
} else {
geetestInstance = renderBox(props);
return await showGeetest(props);
}
}
export default showGeetest;

View File

@@ -0,0 +1,165 @@
<template>
<transition name="func-geetest-outer">
<div v-show="show || showOuter" class="geetest-overlay" @click.self.prevent>
<transition name="func-geetest-inner">
<div v-show="showInner" class="geetest-box">
<div class="geetest-top">
<div class="geetest-title">请完成如下极验测试</div>
</div>
<div id="verify" class="geetest-mid">
<div id="geetest" ref="geetestRef"></div>
</div>
</div>
</transition>
</div>
</transition>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
const show = ref<boolean>(false);
const showOuter = ref<boolean>(false);
const showInner = ref<boolean>(false);
const geetestRef = ref<HTMLElement>(<HTMLElement>document.getElementById("geetest"));
watch(show, () => {
if (show.value) {
showOuter.value = true;
setTimeout(() => {
showInner.value = true;
}, 100);
} else {
setTimeout(() => {
showInner.value = false;
}, 100);
setTimeout(() => {
showOuter.value = false;
}, 300);
}
});
async function displayBox(
props: TGApp.Plugins.Mys.Geetest.reqResp,
): Promise<TGApp.Plugins.Mys.Geetest.validateResp | false> {
return await new Promise<TGApp.Plugins.Mys.Geetest.validateResp>((resolve) => {
// eslint-disable-next-line no-undef
initGeetest(
{
gt: props.gt,
challenge: props.challenge,
offline: false,
new_captcha: true,
product: "custom",
area: "#verify",
width: "250px",
},
(captchaObj: TGApp.Plugins.Mys.Geetest.GeetestCaptcha) => {
geetestRef.value.innerHTML = "";
captchaObj.appendTo("#geetest");
captchaObj.onReady(() => {
show.value = true;
});
captchaObj.onSuccess(async () => {
const validate = captchaObj.getValidate();
resolve(validate);
});
captchaObj.onClose(() => {
show.value = false;
});
},
);
});
}
defineExpose({
displayBox,
});
</script>
<style lang="css" scoped>
.func-geetest-outer-enter-active,
.func-geetest-outer-leave-active,
.func-geetest-inner-enter-active {
transition: all 0.3s;
}
.func-geetest-inner-leave-active {
transition: all 0.5s ease-in-out;
}
.func-geetest-inner-enter-from {
opacity: 0;
transform: scale(1.5);
}
.func-geetest-inner-enter-to,
.func-geetest-inner-leave-from {
opacity: 1;
transform: scale(1);
}
.func-geetest-outer-enter-to,
.func-geetest-outer-leave-from {
opacity: 1;
}
.func-geetest-outer-enter-from,
.func-geetest-outer-leave-to {
opacity: 0;
}
.func-geetest-inner-leave-to {
opacity: 0;
transform: scale(0);
}
.geetest-overlay {
position: fixed;
z-index: 100;
top: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
backdrop-filter: blur(20px);
background: rgb(0 0 0 / 50%);
}
.geetest-box {
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 5px;
background-color: var(--box-bg-1);
color: var(--app-page-content);
gap: 10px;
}
.geetest-top {
border-bottom: 1px solid var(--common-shadow-4);
font-family: var(--font-title);
text-align: center;
}
.geetest-title {
color: var(--common-text-title);
font-size: 20px;
}
.geetest-mid {
display: flex;
width: 100%;
align-items: flex-start;
justify-content: center;
padding: 10px;
border-radius: 5px;
background: var(--box-bg-2);
}
#verify {
width: 256px;
height: 320px;
}
</style>

View File

@@ -125,7 +125,7 @@ function loadData(): void {
gachaCount: reset4count.value,
icon: getIcon(item.itemId),
});
reset4count.value = 0;
reset4count.value = 1;
} else if (item.rank === "5") {
reset4count.value++;
star5List.value.push({
@@ -133,7 +133,7 @@ function loadData(): void {
gachaCount: reset5count.value,
icon: getIcon(item.itemId),
});
reset5count.value = 0;
reset5count.value = 1;
}
});
star5avg.value = getStar5Avg();

View File

@@ -16,9 +16,19 @@
:key="text.week"
rounded
:style="{
border: text.week === weekNow ? '1px solid var(--common-shadow-4)' : 'none',
backgroundColor: text.week === btnNow ? 'var(--tgc-yellow-1)' : 'var(--tgc-btn-1)',
color: text.week === btnNow ? 'var(--box-text-4)' : 'var(--btn-text)',
border: text.week === weekNow ? '1px solid var(--tgc-yellow-1)' : 'none',
backgroundColor:
text.week === btnNow
? 'var(--tgc-yellow-1)'
: text.week === weekNow
? 'transparent'
: 'var(--tgc-btn-1)',
color:
text.week === btnNow
? 'var(--box-text-4)'
: text.week === weekNow
? 'var(--tgc-yellow-1)'
: 'var(--btn-text)',
}"
@click="getContents(text.week)"
>{{ text.text }}

View File

@@ -7,6 +7,7 @@
</div>
</template>
<script lang="ts" setup>
import { emit } from "@tauri-apps/api/event";
import { onMounted, ref, watch } from "vue";
import Mys from "../../plugins/Mys/index.js";
@@ -95,8 +96,15 @@ async function toBBS(link: URL): Promise<void> {
}
if (link.pathname.startsWith("//forum")) {
const forumId = link.pathname.split("/").pop();
const url = `https://www.miyoushe.com/ys/home/${forumId}`;
window.open(url);
const localPath = getLocalPath(forumId);
if (localPath === "") {
showSnackbar({
text: `不支持的链接:${link.href}`,
color: "warn",
});
return;
}
await emit("active_deep_link", `router?path=${localPath}`);
return;
}
}
@@ -105,12 +113,40 @@ async function toBBS(link: URL): Promise<void> {
color: "warn",
});
}
function getLocalPath(forum?: string): string {
if (!forum) return "";
const forumLocalMap: Record<string, string> = {
"31": "/news/3", // 崩坏2官方
"6": "/news/1", // 崩坏3官方
"28": "/news/2", // 原神官方
"33": "/news/4", // 未定官方
"58": "/news/8", // 绝区零官方
"36": "/news/5", // 大别野公告
};
if (forumLocalMap[forum]) return forumLocalMap[forum];
const ysForums = ["26", "43", "29", "49", "50"];
const srForums = ["52", "61", "56", "62"];
const bh3Forums = ["1", "14", "4", "41"];
const bh2Forums = ["30", "51", "40"];
const wdForums = ["37", "60", "42", "38"];
const zzzForums = ["57", "59", "64", "65"];
const dbyForums = ["54", "35", "34", "39", "47", "48", "55", "36"];
if (ysForums.includes(forum)) return `/posts/2/${forum}`;
if (srForums.includes(forum)) return `/posts/6/${forum}`;
if (bh3Forums.includes(forum)) return `/posts/1/${forum}`;
if (bh2Forums.includes(forum)) return `/posts/3/${forum}`;
if (wdForums.includes(forum)) return `/posts/4/${forum}`;
if (zzzForums.includes(forum)) return `/posts/8/${forum}`;
if (dbyForums.includes(forum)) return `/posts/5/${forum}`;
return "";
}
</script>
<style lang="css" scoped>
.tgn-container {
display: flex;
padding: 5px;
gap: 10px 10px;
gap: 10px;
}
.tgn-nav {

View File

@@ -38,13 +38,20 @@
</div>
</div>
</div>
<div class="tpc-forum" v-if="card.forum" :title="`频道: ${card.forum.name}`">
<div
class="tpc-forum"
v-if="card.forum && card.forum.name !== ''"
:title="`频道: ${card.forum.name}`"
>
<img :src="card.forum.icon" :alt="card.forum.name" />
<span>{{ card.forum.name }}</span>
</div>
<div v-if="props.selectMode" class="tpc-select">
<v-checkbox-btn v-model="selectedList" :value="props.modelValue.post.post_id" />
</div>
<v-checkbox-btn
v-if="props.selectMode"
class="tpc-select"
v-model="selectedList"
:value="props.modelValue.post.post_id"
/>
</v-card>
</template>
<script lang="ts" setup>
@@ -254,15 +261,23 @@ function getPostCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.N
border-bottom-left-radius: 5px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
text-shadow: 0 0 5px var(--tgc-dark-1);
}
.tpc-select {
position: absolute;
bottom: 0;
top: 0;
left: 0;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: var(--tgc-yellow-2);
border-bottom-right-radius: 4px;
box-shadow: 0 0 10px var(--tgc-dark-1);
}
.tpc-forum img {

View File

@@ -30,6 +30,8 @@ import { computed } from "vue";
import { useRouter } from "vue-router";
import { useAppStore } from "../../store/modules/app.js";
import { ToChannelItem } from "../../web/constant/bbs.js";
import TGConstant from "../../web/constant/TGConstant.js";
import showSnackbar from "../func/snackbar.js";
import TOverlay from "../main/t-overlay.vue";
@@ -41,12 +43,6 @@ interface ToChannelProps {
type ToChannelEmits = (e: "update:modelValue", value: boolean) => void;
interface ToChannelItem {
title: string;
icon: string;
gid: string;
}
const props = withDefaults(defineProps<ToChannelProps>(), {
modelValue: false,
});
@@ -60,44 +56,7 @@ const visible = computed({
});
const router = useRouter();
const appStore = useAppStore();
const channelList: ToChannelItem[] = [
{
title: "原神",
icon: "/platforms/mhy/ys.webp",
gid: "2",
},
{
title: "崩坏:星穹铁道",
icon: "/platforms/mhy/sr.webp",
gid: "6",
},
{
title: "绝区零",
icon: "/platforms/mhy/zzz.webp",
gid: "8",
},
{
title: "崩坏3",
icon: "/platforms/mhy/bh3.webp",
gid: "1",
},
{
title: "崩坏2",
icon: "/platforms/mhy/bh2.webp",
gid: "3",
},
{
title: "未定事件簿",
icon: "/platforms/mhy/wd.webp",
gid: "4",
},
{
title: "大别野",
icon: "/platforms/mhy/dby.webp",
gid: "5",
},
];
const channelList = TGConstant.BBS.CHANNELS;
function onCancel(): void {
visible.value = false;

View File

@@ -2,7 +2,7 @@
<TOverlay v-model="visible" hide blur-val="20px" :to-click="onCancel">
<div class="tog-box">
<div class="tog-top">
<div class="tog-title">请使用米游社或原神进行操作</div>
<div class="tog-title">请使用米游社APP进行操作</div>
<div class="tog-subtitle">所需米游社版本 >= 2.57.1</div>
</div>
<div class="tog-mid">
@@ -135,7 +135,7 @@ async function cycleGetData() {
if (res.payload.proto !== "OpenToken" && res.payload.proto !== "Account") {
await TGLogger.Warn(`[to-gameLogin] 检测到意外协议:${res.payload.proto}`);
showSnackbar({
text: "请使用米游社或原神进行扫码操作",
text: "请使用米游社APP进行扫码操作",
color: "error",
});
visible.value = false;

View File

@@ -89,13 +89,11 @@ watch(
async (value) => {
if (search.value === "" && value !== "") {
search.value = value;
await searchPosts();
} else if (search.value !== value && value !== "") {
search.value = value;
results.value = [];
lastId.value = "";
isLast.value = false;
await searchPosts();
}
},
);

View File

@@ -63,6 +63,7 @@ onMounted(async () => {
const width = highestResolution.width;
const height = highestResolution.height;
if (width && height) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
width > height
? (vodAspectRatio.value = width / height)
: (vodAspectRatio.value = height / width);
@@ -118,7 +119,7 @@ onMounted(async () => {
};
container.value = new Artplayer(option);
container.value?.on("fullscreen", async (state) => {
await TauriWindow.getCurrent().setFullscreen(state);
await TauriWindow.getCurrentWindow().setFullscreen(state);
});
});

View File

@@ -4,7 +4,7 @@
class="twc-material-box"
v-for="(item, index) in props.data"
:key="index"
@click="checkData(item)"
@click="checkData(item, index)"
>
<div class="twc-material-left">
<div class="twc-material-bg">
@@ -19,7 +19,18 @@
</div>
</div>
</div>
<TwoMaterial :data="curData" v-model="showOverlay" />
<TwoMaterial :data="curData" v-model="showOverlay">
<template #left>
<div class="card-arrow left" @click="switchMaterial(false)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
<template #right>
<div class="card-arrow" @click="switchMaterial(true)">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</template>
</TwoMaterial>
</template>
<script lang="ts" setup>
import { ref } from "vue";
@@ -35,6 +46,7 @@ interface TwcMaterialsProp {
const props = defineProps<TwcMaterialsProp>();
const showOverlay = ref(false);
const curIndex = ref(0);
const curData = ref<TGApp.App.Material.WikiItem>({
id: 0,
name: "",
@@ -45,11 +57,12 @@ const curData = ref<TGApp.App.Material.WikiItem>({
convert: [],
});
function checkData(item: TGApp.App.Calendar.Material) {
function checkData(item: TGApp.App.Calendar.Material, index: number) {
if (showOverlay.value) showOverlay.value = false;
const material = WikiMaterialData.find((m) => m.id === item.id);
if (material) {
curData.value = material;
curIndex.value = index;
showOverlay.value = true;
} else {
showSnackbar({
@@ -58,6 +71,39 @@ function checkData(item: TGApp.App.Calendar.Material) {
});
}
}
function switchMaterial(isNext: boolean) {
if (isNext) {
if (curIndex.value === props.data.length - 1) {
showSnackbar({
text: "已经是最后一个材料了",
color: "warn",
});
return;
}
curIndex.value++;
} else {
if (curIndex.value === 0) {
showSnackbar({
text: "已经是第一个材料了",
color: "warn",
});
return;
}
curIndex.value--;
}
const curItem = props.data[curIndex.value];
const material = WikiMaterialData.find((m) => m.id === curItem.id);
if (material) {
curData.value = material;
return;
}
showSnackbar({
text: `材料 ${curItem.name} 暂无详细信息`,
color: "warn",
});
isNext ? curIndex.value-- : curIndex.value++;
}
</script>
<style lang="css" scoped>
.twc-materials-grid {
@@ -111,4 +157,27 @@ function checkData(item: TGApp.App.Calendar.Material) {
color: var(--box-text-2);
font-size: 14px;
}
.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

@@ -416,7 +416,7 @@
},
{
"id": 1313,
"contentId": 0,
"contentId": 501569,
"name": "夏沃蕾",
"type": "角色牌",
"icon": "/WIKI/GCG/normal/夏沃蕾.webp",
@@ -754,7 +754,7 @@
},
{
"id": 1608,
"contentId": 0,
"contentId": 501547,
"name": "娜维娅",
"type": "角色牌",
"icon": "/WIKI/GCG/normal/娜维娅.webp",
@@ -899,7 +899,7 @@
},
{
"id": 2104,
"contentId": 0,
"contentId": 501558,
"name": "愚人众·霜役人",
"type": "角色牌",
"icon": "/WIKI/GCG/normal/愚人众·霜役人.webp",
@@ -1991,7 +1991,7 @@
},
{
"id": 213131,
"contentId": 0,
"contentId": 501564,
"name": "尖兵协同战法",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/尖兵协同战法.webp",
@@ -2297,7 +2297,7 @@
},
{
"id": 216081,
"contentId": 0,
"contentId": 501548,
"name": "不明流通渠道",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/不明流通渠道.webp",
@@ -2427,7 +2427,7 @@
},
{
"id": 221041,
"contentId": 0,
"contentId": 501559,
"name": "冰雅刺剑",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/冰雅刺剑.webp",
@@ -2904,7 +2904,7 @@
},
{
"id": 311308,
"contentId": 0,
"contentId": 501561,
"name": "「究极霸王超级魔剑」",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/「究极霸王超级魔剑」.webp",
@@ -3003,7 +3003,7 @@
},
{
"id": 311409,
"contentId": 0,
"contentId": 501562,
"name": "勘探钻机",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/勘探钻机.webp",
@@ -3382,7 +3382,7 @@
},
{
"id": 312029,
"contentId": 0,
"contentId": 501566,
"name": "角斗士的凯旋",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/角斗士的凯旋.webp",
@@ -4002,7 +4002,7 @@
},
{
"id": 322027,
"contentId": 0,
"contentId": 501567,
"name": "瑟琳",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/瑟琳.webp",
@@ -4618,7 +4618,7 @@
},
{
"id": 332037,
"contentId": 0,
"contentId": 501563,
"name": "噔噔!",
"type": "行动牌",
"icon": "/WIKI/GCG/normal/噔噔!.webp",

View File

@@ -1409,7 +1409,7 @@
},
{
"id": 10000099,
"contentId": 0,
"contentId": 501441,
"dropDays": [3, 6, 7],
"name": "艾梅莉埃",
"itemType": "character",
@@ -6094,7 +6094,7 @@
},
{
"id": 13513,
"contentId": 0,
"contentId": 501725,
"dropDays": [3, 6, 7],
"name": "柔灯挽歌",
"itemType": "weapon",

View File

@@ -2141,5 +2141,41 @@
"postId": "55147896",
"up5List": [12512, 11511],
"up4List": [11418, 12402, 13401, 14409, 15405]
},
{
"name": "凝露轻芳",
"version": "4.8",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2024/07/19/a613355dfdca28e391d39905d3d0879b_5070315245511088722.jpg",
"from": "2024-08-06T18:00:00+08:00",
"to": "2024-08-27T14:59:00+08:00",
"type": 301,
"postId": "56072207",
"up5List": [10000099],
"up4List": [10000048, 10000023, 10000020]
},
{
"name": "素霓伣天",
"version": "4.8",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2024/07/19/4dff39a5df5b0978653f46b92fa512f8_7130552963756568493.jpg",
"from": "2024-08-06T18:00:00+08:00",
"to": "2024-08-27T14:59:00+08:00",
"type": 400,
"postId": "56072208",
"up5List": [10000060],
"up4List": [10000048, 10000023, 10000020]
},
{
"name": "神铸赋形",
"version": "4.8",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2024/07/19/3cecd09457f7158fad2d9c140dc09fdb_8057167364819822333.jpg",
"from": "2024-08-06T18:00:00+08:00",
"to": "2024-08-27T14:59:00+08:00",
"type": 302,
"postId": "56072210",
"up5List": [13513, 15508],
"up4List": [12415, 14416, 11402, 13407, 15401]
}
]

View File

@@ -181,7 +181,7 @@
},
{
"id": 13513,
"contentId": 0,
"contentId": 501725,
"name": "柔灯挽歌",
"star": 5,
"bg": "/icon/bg/5-Star.webp",

View File

@@ -10,6 +10,7 @@ import { createVuetify } from "vuetify";
import App from "./App.vue";
import router from "./router/index.js";
import store from "./store/index.js";
import "https://static.geetest.com/static/js/gt.0.4.9.js";
import "@mdi/font/css/materialdesignicons.css";
import "vuetify/styles";

View File

@@ -399,7 +399,7 @@ async function exportUigf(): Promise<void> {
});
return;
}
const res = showConfirm({
const res = await showConfirm({
title: "是否导出祈愿数据?",
text: `UID${uidCur.value},共 ${gachaList.length} 条数据`,
});

View File

@@ -184,11 +184,10 @@ async function loadData(): Promise<void> {
function getAnnoTime(content: string): string | false {
const regexes = [
/〓活动时间〓.*?\d\.\d版本期间持续开放/,
/(?:〓活动时间〓|〓任务开放时间〓).*?(?:(\d\.\d版本更新(?:完成|))|(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}).*?)后永久开放/,
/(?:〓(?:活动|折扣)时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/,
/(?:〓(?:活动|折扣)时间〓|祈愿时间|【上架时间】).*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/,
/(?:|).*?(?:(\d\.\d)(?:|)|&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt; *?)/s,
/(?:|||).*?(\d\.\d).*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/s,
/(?:(?:|)||).*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;.*?~.*?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&lt;\/t&gt;/s,
/〓活动时间〓.*?(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}).*?(\d\.\d版本结束)/,
/〓更新时间〓.+?&lt;t class="t_(?:gl|lc)".*?&gt;(.*?)&;lt;\/t&gt;/,
];
if (content.match(regexes[0])) {
const res = content.match(regexes[0]);
@@ -206,10 +205,22 @@ function getAnnoTime(content: string): string | false {
}
if (content.match(regexes[2])) {
const res = content.match(regexes[2]);
if (res?.[1]?.match(/\d\.\d/)) {
return `${res?.[1]}版本更新后 ~ ${res?.[2]}`;
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[3])) {
const res = content.match(regexes[3]);
try {
const span1 = document.createElement("span");
span1.innerHTML = res?.[1] ?? "";
const span2 = document.createElement("span");
span2.innerHTML = res?.[2] ?? "";
return `${span1.innerText} ~ ${span2.innerText}`;
} catch (e) {
console.error(e);
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[4])) {
@@ -220,11 +231,6 @@ function getAnnoTime(content: string): string | false {
}
return `${res?.[1]} ~ ${res?.[2]}`;
}
if (content.match(regexes[5])) {
console.log("verUpdateTime");
// todo 待处理
return false;
}
return false;
}
@@ -346,6 +352,7 @@ async function createAnno(item: TGApp.App.Announcement.ListCard): Promise<void>
border-bottom-left-radius: 5px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
text-shadow: 0 0 5px var(--tgc-dark-1);
}
.anno-label img {

View File

@@ -52,6 +52,10 @@
</div>
</template>
<v-list-item-title @click="confirmUpdateDevice()">刷新设备信息</v-list-item-title>
<v-list-item-subtitle>
{{ appStore.deviceInfo.device_name }}({{ appStore.deviceInfo.product }}) -
{{ appStore.deviceInfo.device_fp }}
</v-list-item-subtitle>
<template #append>
<v-icon @click="confirmUpdateDevice(true)">mdi-bug</v-icon>
</template>

View File

@@ -11,19 +11,21 @@
label="请输入帖子 ID 或搜索词"
:single-line="true"
hide-details
@click:append="searchPost"
@keyup.enter="searchPost"
@keyup.enter="searchPost()"
/>
<v-spacer />
<v-btn class="news-switch-btn" @click="switchAnno">
<v-btn class="news-top-btn" @click="firstLoad(tab, true)">
<v-icon>mdi-refresh</v-icon>
</v-btn>
<v-btn class="news-top-btn" @click="showList = true">
<v-icon>mdi-view-list</v-icon>
</v-btn>
<v-btn class="news-top-btn" @click="switchAnno" v-if="gid === '2'">
<template #prepend>
<v-icon>mdi-bullhorn</v-icon>
</template>
切换游戏内公告
</v-btn>
<v-btn class="news-switch-btn" @click="showList = true">
<v-icon>mdi-view-list</v-icon>
</v-btn>
</v-tabs>
<v-window v-model="tab">
<v-window-item v-for="(value, index) in tabValues" :key="index" :value="value">
@@ -33,7 +35,7 @@
</div>
</div>
<div class="load-news">
<v-btn class="news-switch-btn" rounded :loading="loadingSub" @click="loadMore(value)">
<v-btn class="news-top-btn" rounded :loading="loadingSub" @click="loadMore(value)">
已加载{{ rawData[value].lastId }}加载更多
</v-btn>
</div>
@@ -44,7 +46,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted, ref, watch } from "vue";
import { computed, nextTick, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import showSnackbar from "../../components/func/snackbar.js";
@@ -79,7 +81,6 @@ type RawData = {
// 路由
const router = useRouter();
const gid = <string>useRoute().params.gid;
let type = <string | undefined>useRoute().params.type;
// loading
const loading = ref<boolean>(true);
const loadingTitle = ref<string>("正在加载");
@@ -87,10 +88,21 @@ const loadingSub = ref<boolean>(false);
// UI 数据
const appStore = useAppStore();
const tab = ref<NewsKey>("notice");
const showList = ref<boolean>(false);
const showSearch = ref<boolean>(false);
const tabValues = ref<Array<NewsKey>>(["notice", "activity", "news"]);
const tabList = ["notice", "activity", "news"];
const tab = computed({
get: () => {
if (appStore.recentNewsType === "" || !tabList.includes(appStore.recentNewsType)) {
return "notice";
}
return <NewsKey>appStore.recentNewsType;
},
set: (val) => {
appStore.recentNewsType = val;
},
});
// 渲染数据
const search = ref<string>("");
@@ -117,29 +129,13 @@ const rawData = ref<RawData>({
},
});
onMounted(async () => {
await TGLogger.Info(`[News][${gid}][onMounted] 打开咨讯页面`);
const typeList = ["notice", "activity", "news"];
if (type != undefined) {
appStore.recentNewsType = type;
}
const curType = appStore.recentNewsType;
if (typeList.includes(curType)) {
tab.value = <NewsKey>curType;
} else {
tab.value = "notice";
appStore.recentNewsType = "notice";
}
await firstLoad(tab.value);
});
onMounted(async () => await firstLoad(tab.value));
watch(tab, (val) => {
appStore.recentNewsType = val;
});
async function firstLoad(key: NewsKey): Promise<void> {
async function firstLoad(key: NewsKey, refresh: boolean = false): Promise<void> {
if (rawData.value[key].lastId !== 0) {
return;
if (!refresh) return;
postData.value[key] = [];
rawData.value[key].lastId = 0;
}
loadingTitle.value = `正在获取${rawData.value[key].name}数据...`;
loading.value = true;
@@ -191,7 +187,7 @@ async function loadMore(key: NewsKey): Promise<void> {
});
}
function searchPost(): void {
async function searchPost(): Promise<void> {
if (search.value === "") {
showSnackbar({
text: "请输入搜索内容",
@@ -203,7 +199,7 @@ function searchPost(): void {
if (isNaN(numCheck)) {
showSearch.value = true;
} else {
createPost(search.value);
await createPost(search.value);
}
}
</script>
@@ -215,7 +211,7 @@ function searchPost(): void {
font-family: var(--font-title);
}
.news-switch-btn {
.news-top-btn {
height: 40px;
margin-left: 15px;
background: var(--btn-bg-1);
@@ -223,7 +219,7 @@ function searchPost(): void {
font-family: var(--font-title);
}
.dark .news-switch-btn {
.dark .news-top-btn {
border: 1px solid var(--common-shadow-2);
}

View File

@@ -317,11 +317,11 @@ async function toEdit(): Promise<void> {
await load();
}
async function deleteOper(forever: boolean): Promise<void> {
async function deleteOper(force: boolean): Promise<void> {
if (selectedMode.value) {
await deletePost(forever);
await deletePost(force);
} else {
await deleteCollect(forever);
await deleteCollect(force);
}
}
@@ -377,7 +377,10 @@ async function deleteCollect(force: boolean): Promise<void> {
return;
}
const title = force ? "删除分类" : "清空分类";
const res = await showConfirm({ title: `确定${title}?` });
const res = await showConfirm({
title: `确定${title}?`,
text: `该分类下${selected.value.length}条帖子将被${force ? "删除" : "移除分类(未分类将被删除)"}`,
});
if (!res) {
showSnackbar({
text: "取消删除",

View File

@@ -49,7 +49,8 @@
<ToPostSearch :gid="curGid.toString()" v-model="showSearch" :keyword="search" />
</template>
<script setup lang="ts">
import { nextTick, onMounted, ref, watch } from "vue";
import { nextTick, onBeforeMount, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import showSnackbar from "../../components/func/snackbar.js";
import TGameNav from "../../components/main/t-gamenav.vue";
@@ -134,6 +135,10 @@ const gameList = {
大别野: 5,
};
// 路由
const gid = useRoute().params.gid;
const forum = useRoute().params.forum;
// 渲染参数
const curForumLabel = ref<string>("酒馆");
const forumItem = ref<string[]>(["酒馆", "攻略", "同人图", "COS", "硬核"]);
@@ -162,12 +167,44 @@ const posts = ref<TGApp.Plugins.Mys.Post.FullData[]>([]);
const search = ref<string>("");
const showSearch = ref<boolean>(false);
onBeforeMount(async () => {
if (gid && typeof gid === "string") {
const gameKeys = Object.values(gameList);
if (!gameKeys.includes(Number(gid))) {
showSnackbar({
text: `不存在GID为${gid}的游戏`,
color: "error",
});
return;
}
curGameLabel.value = <keyof typeof gameList>(
Object.keys(gameList)[gameKeys.indexOf(Number(gid))]
);
curGid.value = Number(gid);
}
if (forum && typeof forum === "string") {
const forumKeys = Object.keys(forumList[curGameLabel.value]);
if (!forumKeys.includes(forum)) {
showSnackbar({
text: `${curGameLabel.value}不存在ID为${forum}的版块`,
color: "error",
});
return;
}
curForumLabel.value = forum;
}
});
onMounted(async () => {
await TGLogger.Info(
`[Posts][${curGameLabel.value}][onMounted][${curForumLabel.value}] 打开帖子列表`,
);
loading.value = true;
await freshPostData();
if (gid && forum) {
freshCurForum(curForumLabel.value);
} else {
await freshPostData();
}
loading.value = false;
});
@@ -202,7 +239,7 @@ async function freshPostData(): Promise<void> {
);
loading.value = true;
loadingTitle.value = `正在加载 ${curGameLabel.value}-${curForumLabel.value}-${curSortLabel.value} 的数据`;
const postsGet = await Mys.Posts.get(curForum.value, curSortType.value);
const postsGet = await Mys.Posts.get(curForum.value, curSortType.value, 12);
posts.value = postsGet.list;
await nextTick();
loading.value = false;

View File

@@ -36,6 +36,11 @@
}
.test-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
border-radius: 5px;
background: var(--tgc-btn-1);
color: var(--btn-text);
}

View File

@@ -1,10 +1,11 @@
/**
* @file plugins/Mys/index.ts
* @description Mys plugin index
* @since Beta v0.4.5
* @since Beta v0.5.1
*/
import MysApi from "./api/index.js";
import { getCaptcha, doCaptchaLogin } from "./request/doCaptchaLogin.js";
import { getLoginQr, getLoginStatus } from "./request/doGameLogin.js";
import { getCollectionData, getCollectionPosts } from "./request/getCollectionData.js";
import getForumList from "./request/getForumList.js";
@@ -50,6 +51,8 @@ const Mys = {
User: {
getQr: getLoginQr,
getData: getLoginStatus,
getCaptcha,
login: doCaptchaLogin,
},
Vote: {
get: getVoteInfo,

View File

@@ -0,0 +1,140 @@
/**
* @file plugins/Mys/request/doCaptchaLogin.ts
* @description 通过短信验证码登录账号获取 stoken
* @since Beta v0.5.1
*/
import { JSEncrypt } from "jsencrypt";
import showSnackbar from "../../../components/func/snackbar.js";
import TGHttp from "../../../utils/TGHttp.js";
import { getDeviceInfo } from "../../../utils/toolFunc.js";
import TGConstant from "../../../web/constant/TGConstant.js";
const PUB_KEY_STR = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7cI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs
9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q
CgGs52bFoYMtyi+xEQIDAQAB
-----END PUBLIC KEY-----`;
const encrypt = new JSEncrypt();
encrypt.setPublicKey(PUB_KEY_STR);
/**
* @description rsa 加密
* @since Beta v0.5.1
* @param {string} data - 待加密数据
* @returns {string} 加密后数据
*/
function rsaEncrypt(data: string): string {
const res = encrypt.encrypt(data.toString());
if (res === false) {
showSnackbar({
text: "RSA 加密失败",
color: "error",
});
return "";
}
return res;
}
/**
* @description 获取短信验证码
* @since Beta v0.5.1
* @param {string} phone - 手机号
* @param {string} [aigis] - 验证数据
* @returns {Promise<TGApp.Plugins.Mys.CaptchaLogin.CaptchaData | TGApp.BBS.Response.Base>}
*/
export async function getCaptcha(
phone: string,
aigis?: string,
): Promise<TGApp.Plugins.Mys.CaptchaLogin.CaptchaData | TGApp.BBS.Response.BaseWithData> {
const url = "https://passport-api.mihoyo.com/account/ma-cn-verifier/verifier/createLoginCaptcha";
const device_fp = getDeviceInfo("device_fp");
const device_name = getDeviceInfo("device_name");
const device_id = getDeviceInfo("device_id");
const device_model = getDeviceInfo("product");
const body = { area_code: rsaEncrypt("+86"), mobile: rsaEncrypt(phone) };
const header: Record<string, string> = {
"x-rpc-aigis": aigis || "",
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "2",
"x-rpc-app_id": TGConstant.BBS.APP_ID,
"x-rpc-device_fp": device_fp,
"x-rpc-device_name": device_name,
"x-rpc-device_id": device_id,
"x-rpc-device_model": device_model,
"user-agent": TGConstant.BBS.UA_MOBILE,
"content-type": "application/json",
referer: "https://user.miyoushe.com/",
"x-rpc-game_biz": TGConstant.Utils.GAME_BIZ,
};
const resp = await TGHttp<
TGApp.Plugins.Mys.CaptchaLogin.CaptchaResponse | TGApp.BBS.Response.Base
>(
url,
{
method: "POST",
headers: header,
body: JSON.stringify(body),
},
true,
);
const data = await resp.data;
if (data.retcode !== 0) {
return <TGApp.BBS.Response.BaseWithData>{
retcode: data.retcode,
message: data.message,
data: resp.resp.headers.get("x-rpc-aigis"),
};
}
return <TGApp.Plugins.Mys.CaptchaLogin.CaptchaData>data.data;
}
/**
* @description 通过短信验证码登录
* @since Beta v0.5.1
* @param {string} phone - 手机号
* @param {string} captcha - 验证码
* @param {string} action_type - 操作类型
* @param {string} [aigis] - 验证数据
* @returns {Promise<TGApp.Plugins.Mys.CaptchaLogin.LoginData | TGApp.BBS.Response.Base>}
*/
export async function doCaptchaLogin(
phone: string,
captcha: string,
action_type: string,
aigis?: string,
): Promise<TGApp.Plugins.Mys.CaptchaLogin.LoginData | TGApp.BBS.Response.Base> {
const url = "https://passport-api.mihoyo.com/account/ma-cn-passport/app/loginByMobileCaptcha";
const device_fp = getDeviceInfo("device_fp");
const device_name = getDeviceInfo("device_name");
const device_id = getDeviceInfo("device_id");
const device_model = getDeviceInfo("product");
const body = {
area_code: rsaEncrypt("+86"),
mobile: rsaEncrypt(phone),
action_type,
captcha,
};
const header = {
"x-rpc-aigis": aigis || "",
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "2",
"x-rpc-app_id": TGConstant.BBS.APP_ID,
"x-rpc-device_fp": device_fp,
"x-rpc-device_name": device_name,
"x-rpc-device_id": device_id,
"x-rpc-device_model": device_model,
"user-agent": TGConstant.BBS.UA_MOBILE,
};
const resp = await TGHttp<TGApp.Plugins.Mys.CaptchaLogin.LoginResponse | TGApp.BBS.Response.Base>(
url,
{
method: "POST",
headers: header,
body: JSON.stringify(body),
},
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}

View File

@@ -1,24 +1,30 @@
/**
* @file plugins/Mys/request/getForumList.ts
* @description Mys 插件特定论坛请求
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 获取特定论坛列表
* @since Beta v0.5.0
* @since Beta v0.5.1
* @param {number} forumId 特定论坛 ID
* @param {number} type 排序方式: 0-按热度排序1-最新回复2-按时间排序
* @param {number} page_size 每页数量
* @return {Promise<TGApp.Plugins.Mys.Forum.FullData>}
*/
async function getForumList(
forumId: number,
type: number = 0,
page_size: number = 20,
): Promise<TGApp.Plugins.Mys.Forum.FullData> {
const url = "https://bbs-api.miyoushe.com/post/wapi/getForumPostList";
const params = { forum_id: forumId.toString(), sort_type: type.toString() };
const params = {
forum_id: forumId.toString(),
sort_type: type.toString(),
page_size: page_size.toString(),
};
const resp = await TGHttp<TGApp.Plugins.Mys.Forum.Response>(url, {
method: "GET",
query: params,

View File

@@ -1,23 +1,24 @@
/**
* @file plugins/Mys/request/getHomeNavigator.ts
* @description Mys 插件首页导航请求
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 获取首页导航列表
* @since Beta v0.5.0
* @since Beta v0.5.1
* @param {number} gid GID
* @return {Promise<TGApp.BBS.Navigator.Navigator[]>}
*/
async function getHomeNavigator(gid: number = 2): Promise<TGApp.BBS.Navigator.Navigator[]> {
const url = "https://bbs-api.miyoushe.com/apihub/api/home/new";
const params = { gids: gid.toString() };
const header = { "x-rpc-client_type": "2" };
const resp = await TGHttp<TGApp.BBS.Navigator.HomeResponse>(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
headers: header,
query: params,
});
return resp.data.navigator;

179
src/plugins/Mys/types/CaptchaLogin.d.ts vendored Normal file
View File

@@ -0,0 +1,179 @@
/**
* @file plugins/Mys/types/CaptchaLogin.d.ts
* @description Mys 插件 Captcha 登录类型定义文件
* @since Beta v0.5.1
*/
/**
* @description Mys 插件 Captcha 登录类型
* @since Beta v0.5.1
* @namespace TGApp.Plugins.Mys.CaptchaLogin
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.CaptchaLogin {
/**
* @description 获取短信验证码返回数据
* @since Beta v0.5.1
* @interface CaptchaResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {CaptchaData} data 数据
* @return GetCaptchaResponse
*/
interface CaptchaResponse extends TGApp.BBS.Response.BaseWithData {
data: CaptchaData;
}
/**
* @description 获取短信验证码返回数据
* @since Beta v0.5.1
* @interface CaptchaData
* @property {string} sent_new 是否发送新验证码
* @property {string} countdown 倒计时
* @property {string} action_type 操作类型
* @return CaptchaData
*/
interface CaptchaData {
sent_new: string;
countdown: string;
action_type: string;
}
/**
* @description 触发验证的序列化数据
* @since Beta v0.5.1
* @interface CaptchaAigis
* @property {string} session_id 会话 id
* @property {number} mmt_type mmt 类型
* @see TGApp.Plugins.Mys.Geetest.validateResp
* @property {string} data 数据,为上面的序列化数据
* @return CaptchaBody
*/
interface CaptchaAigis {
session_id: string;
mmt_type: number;
data: string;
}
/**
* @description 短信验证码登录返回数据
* @since Beta v0.5.1
* @interface LoginResponse
* @extends TGApp.BBS.Response.BaseWithData
* @property {LoginData} data 数据
* @return LoginResponse
*/
interface LoginResponse extends TGApp.BBS.Response.BaseWithData {
data: LoginData;
}
/**
* @description 短信验证码登录返回数据
* @since Beta v0.5.1
* @interface LoginData
* @property {Token} token token数据
* @property {UserInfo} user_info 用户信息
* @property {ReactivateInfo} reactivate_info 重新激活信息
* @property {string} login_ticket 登录 ticket
* @property {boolean} new_user 是否新用户
* @property {RealnameInfo} realname_info 实名信息
* @property {boolean} need_realperson 是否需要实名认证
* @property {string} oauth_hw_open_id 华为 open id
* @return LoginData
*/
interface LoginData {
token: Token;
user_info: UserInfo;
reactivate_info: ReactivateInfo;
login_ticket: string;
new_user: boolean;
realname_info: RealnameInfo;
need_realperson: boolean;
oauth_hw_open_id: string;
}
/**
* @description token 数据
* @since Beta v0.5.1
* @interface Token
* @property {number} token_type token 类型
* @property {string} token token
* @return Token
*/
interface Token {
token_type: number;
token: string;
}
/**
* @description 用户信息
* @since Beta v0.5.1
* @interface UserInfo
* @property {string} aid 账号 id
* @property {string} mid mid
* @property {string} account_name 账号名称
* @property {string} email 邮箱
* @property {number} is_email_verify 是否验证邮箱
* @property {string} area_code 手机区号
* @property {string} mobile 手机号
* @property {string} safe_area_code 安全手机区号
* @property {string} safe_mobile 安全手机号
* @property {string} realname 真实姓名
* @property {string} identity_code 身份证号
* @property {string} rebind_area_code 重新绑定手机区号
* @property {string} rebind_mobile 重新绑定手机号
* @property {string} rebind_mobile_time 重新绑定手机时间
* @property {string[]} links 账号绑定信息
* @property {string} country 国家
* @property {string} unmasked_email 邮箱
* @property {number} unmasked_email_type 邮箱类型
* @return UserInfo
*/
interface UserInfo {
aid: string;
mid: string;
account_name: string;
email: string;
is_email_verify: number;
area_code: string;
mobile: string;
safe_area_code: string;
safe_mobile: string;
realname: string;
identity_code: string;
rebind_area_code: string;
rebind_mobile: string;
rebind_mobile_time: string;
links: string[];
country: string;
unmasked_email: string;
unmasked_email_type: number;
}
/**
* @description 重新激活信息
* @since Beta v0.5.1
* @interface ReactivateInfo
* @property {boolean} required 是否需要重新激活
* @property {string} ticket 重新激活 ticket
* @return ReactivateInfo
*/
interface ReactivateInfo {
required: boolean;
ticket: string;
}
/**
* @description 实名信息
* @since Beta v0.5.1
* @interface RealnameInfo
* @property {boolean} required 是否需要实名认证
* @property {string} action_type 操作类型
* @property {string} action_ticket 操作 ticket
* @return RealnameInfo
*/
interface RealnameInfo {
required: boolean;
action_type: string;
action_ticket: string;
}
}

100
src/plugins/Mys/types/Geetest.d.ts vendored Normal file
View File

@@ -0,0 +1,100 @@
/**
* @file plugins/Mys/types/Geetest.d.ts
* @description Mys 插件 Geetest 类型定义文件
* @since Beta v0.5.2
*/
/**
* @description Mys 插件 Geetest 类型
* @since Beta v0.5.2
* @namespace TGApp.Plugins.Mys.Geetest
* @memberof TGApp.Plugins.Mys
*/
declare namespace TGApp.Plugins.Mys.Geetest {
/**
* @description 极验验证的响应数据
* @since Beta v0.5.1
* @interface reqResp
* @property {string} gt - 极验验证 gt
* @property {string} challenge - 极验验证 challenge
* @property {number} new_captcha - 极验验证 new_captcha
* @property {number} success - 极验验证 success
* @return reqResp
*/
interface reqResp {
gt: string;
challenge: string;
new_captcha: number;
success: number;
}
/**
* @description 极验验证的请求数据
* @since Beta v0.5.1
* @interface postData
* @property {string} challenge - 极验验证 challenge
* @property {string} validate - 极验验证 validate
* @return postData
*/
interface postData {
challenge: string;
validate: string;
}
/**
* @description 极验验证的请求方法-请求参数
* @since Beta v0.5.1
* @interface InitGeetestParams
* @property {string} gt - 极验验证 gt
* @property {string} challenge - 极验验证 challenge
* @property {boolean} offline - 极验验证 offline
* @property {boolean} new_captcha - 极验验证 new_captcha
* @property {string} product - 极验验证 product
* @property {string} width - 极验验证 width
* @property {string} area - 极验验证 area
* @return InitGeetestParams
*/
interface InitGeetestParams {
gt: string;
challenge: string;
offline: boolean;
new_captcha: boolean;
product: string;
width: string;
area: string;
}
/**
* @description Geetest 插件 captchaObj
* @since Beta v0.5.2
* @interface GeetestCaptcha
* @property {Function} appendTo
* @property {Function} getValidate
* @property {Function} onSuccess
* @property {Function} onClose
* @property {Function} onReady
* @return GeetestCaptcha
*/
interface GeetestCaptcha {
appendTo: (selector: string) => void;
getValidate: () => validateResp;
onSuccess: (callback: () => void) => void;
onClose: (callback: () => void) => void;
onReady: (callback: () => void) => void;
}
/**
* @description Geetest 插件 validate
* @since Beta v0.5.1
* @interface validateResp
* @property {string} geetest_challenge
* @property {string} geetest_validate
* @property {string} geetest_seccode
* @return validateResp
*/
interface validateResp {
geetest_challenge: string;
geetest_validate: string;
geetest_seccode: string;
}
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/modules/userCollect.ts
* @description 用户收藏模块
* @since Beta v0.4.5
* @since Beta v0.5.1
*/
import TGSqlite from "../index.js";
@@ -93,7 +93,7 @@ async function createCollect(title: string, desc: string): Promise<boolean> {
/**
* @description 删除收藏合集
* @since Beta v0.4.5
* @since Beta v0.5.1
* @param {string} title 收藏合集标题
* @param {boolean} force 是否强制删除
* @return {Promise<boolean>} 返回是否删除成功
@@ -108,6 +108,9 @@ async function deleteCollect(title: string, force: boolean): Promise<boolean> {
if (force) {
const deleteSql = "DELETE FROM UFCollection WHERE title = ?";
await db.execute(deleteSql, [title]);
const deletePostSql =
"DELETE FROM UFPost WHERE id IN (SELECT postId FROM UFMap WHERE collectionId = ?)";
await db.execute(deletePostSql, [res[0].id]);
}
const deleteRefSql = "DELETE FROM UFMap WHERE collectionId = ?";
await db.execute(deleteRefSql, [res[0].id]);

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/modules/userGacha.ts
* @description 用户祈愿模块
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import { AppCharacterData, AppWeaponData } from "../../../data/index.js";
@@ -29,12 +29,12 @@ function getGachaItemType(item_id: string): gachaItemTypeRes {
/**
* @description 转换祈愿数据,防止多语言
* @since Beta v0.4.7
* @since Beta v0.5.1
* @param {TGApp.Plugins.UIGF.GachaItem} gacha - UIGF数据
* @return {TGApp.Plugins.UIGF.GachaItem} 转换后的数据
*/
function transGacha(gacha: TGApp.Plugins.UIGF.GachaItem): TGApp.Plugins.UIGF.GachaItem {
const type = getGachaItemType(gacha.item_id!);
const type = getGachaItemType(gacha.item_id);
let res = gacha;
res.item_type = type[0];
if (type[0] === "角色") {

View File

@@ -1,7 +1,7 @@
/**
* @file router/modules/main.ts
* @description 主路由模块
* @since Beta v0.4.5
* @since Beta v0.5.1
*/
const mainRoutes = [
@@ -21,7 +21,7 @@ const mainRoutes = [
component: async () => await import("../../pages/common/News.vue"),
},
{
path: "/posts",
path: "/posts/:gid?/:forum?",
name: "酒馆",
component: async () => await import("../../pages/common/Posts.vue"),
},

View File

@@ -1,7 +1,7 @@
/**
* @file store/modules/app.ts
* @description App store module
* @since Beta v0.5.0
* @since Beta v0.5.2
*/
import { path } from "@tauri-apps/api";
@@ -16,7 +16,7 @@ const userDataDir = `${await path.appLocalDataDir()}${path.sep()}userData`;
// 用于存放数据库的路径
const dbDataPath = `${await path.appConfigDir()}${path.sep()}TeyvatGuide.db`;
// 用于存放日志的路径
const logDataDir = `${await path.appConfigDir()}${path.sep()}logs`;
const logDataDir = await path.appLogDir();
export const useAppStore = defineStore(
"app",

View File

@@ -1,7 +1,7 @@
/**
* @file store/modules/user.ts
* @description 用户信息模块
* @since Beta v0.3.9
* @since Beta v0.5.1
*/
import { defineStore } from "pinia";
@@ -28,10 +28,38 @@ export const useUserStore = defineStore(
});
const cookie = ref<TGApp.User.Account.Cookie>();
function getAllCookie(): string {
let res = "";
if (!cookie.value) return res;
if (cookie.value.ltuid && cookie.value.ltuid !== "") {
res += `ltuid=${cookie.value.ltuid};`;
}
if (cookie.value.ltoken && cookie.value.ltoken !== "") {
res += `ltoken=${cookie.value.ltoken};`;
}
if (cookie.value.mid && cookie.value.mid !== "") {
res += `mid=${cookie.value.mid};`;
}
if (cookie.value.cookie_token && cookie.value.cookie_token !== "") {
res += `cookie_token=${cookie.value.cookie_token};`;
}
if (cookie.value.stoken && cookie.value.stoken !== "") {
res += `stoken=${cookie.value.stoken};`;
}
if (cookie.value.stuid && cookie.value.stuid !== "") {
res += `stuid=${cookie.value.stuid};`;
}
if (cookie.value.account_id && cookie.value.account_id !== "") {
res += `account_id=${cookie.value.account_id};`;
}
return res;
}
return {
cookie,
briefInfo,
account,
getAllCookie,
};
},
{

View File

@@ -1,7 +1,7 @@
/**
* @file types/Plugins/UIGF.d.ts
* @description UIGF 插件类型定义文件
* @since Beta v0.5.0
* @since Beta v0.5.1
* @version UIGF v3.0 | UIGF v4.0
*/
@@ -60,13 +60,14 @@ declare namespace TGApp.Plugins.UIGF {
/**
* @description UIGF 头部信息, v4.0
* @since Beta v0.5.0
* @since Beta v0.5.1
* @interface Info4
* @see docs\UIGF4.md
* @property {string} export_timestamp - 导出时间戳(秒)
* @property {string} export_app - 导出应用
* @property {string} export_app_version - 导出应用版本
* @property {string} version - UIGF 版本
* @property {string} lang - 语言
* @return Info4
*/
interface Info4 {
@@ -74,6 +75,7 @@ declare namespace TGApp.Plugins.UIGF {
export_app: string;
export_app_version: string;
version: string;
lang?: string;
}
/**
@@ -114,7 +116,8 @@ declare namespace TGApp.Plugins.UIGF {
/**
* @description UIGF 祈愿列表
* @since Alpha v0.2.3
* @Beta v0.5.1
* @version UIGF v3.0
* @interface GachaItem
* @property {EnumGachaType} gacha_type - 祈愿类型
* @property {string} item_id - 物品ID
@@ -130,7 +133,7 @@ declare namespace TGApp.Plugins.UIGF {
interface GachaItem {
uigf_gacha_type: string;
gacha_type: string;
item_id?: string;
item_id: string;
count?: string;
time: string;
name: string;

View File

@@ -1,7 +1,7 @@
/**
* @file utils/TGClient.ts
* @desc 负责米游社客户端的 callback 处理
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import { event, core, webviewWindow } from "@tauri-apps/api";
@@ -99,7 +99,7 @@ class TGClient {
/**
* @func getSaveImgJS
* @since Beta v0.3.7
* @since Beta v0.5.1
* @desc 获取保存图片的 JS
* @param {string} url - 图片链接
* @param {string} format - 图片格式
@@ -107,23 +107,19 @@ class TGClient {
*/
getSaveImgJS(url: string, format: string): string {
return `javascript:(async function() {
const _t = window.__TAURI__;
const defaultPath = await _t.path.downloadDir() + Date.now() + '.${format}';
const savePath = await _t.dialog.save({
var _path = window.__TAURI__.path;
var saveDefault = await _path.downloadDir() + _path.sep() + Date.now() + '.${format}';
var savePath = await window.__TAURI_PLUGIN_DIALOG__.save({
title: '保存图片',
filters: [{ name: '图片', extensions: ['png'] }],
defaultPath: defaultPath,
filters: [{ name: '图片', extensions: ['${format}'] }],
defaultPath: saveDefault,
});
if (savePath) {
const resBlob = await _t.http.fetch('${url}',{
method: 'GET',
responseType: _t.http.ResponseType.Binary
});
const buffer = new Uint8Array(resBlob.data);
await _t.fs.writeBinaryFile({
contents: buffer,
path: savePath,
});
if(savePath !== null) {
var resp = await window.__TAURI_PLUGIN_HTTP__.fetch('${url}',{
method: 'GET'
}).then(res => res.arrayBuffer());
var buffer = new Uint8Array(resp);
await window.__TAURI_PLUGIN_FS__.writeFile(savePath, buffer);
alert('保存成功');
}
})();`;
@@ -814,14 +810,13 @@ class TGClient {
/**
* @func share
* @since Beta v0.5.0
* @since Beta v0.5.1
* @desc 分享
* @param {TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.SharePayload>} arg - 方法参数
* @returns {Promise<void>} - 无返回值
*/
async share(arg: TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.SharePayload>): Promise<void> {
// 如果有数据
if (arg.payload.type === "image" && arg.payload.content?.image_url !== undefined) {
if (arg.payload.type === "default") {
const image = arg.payload.content.image_url;
const format = image.split(".").pop();
const executeJS = this.getSaveImgJS(image, format ?? "png");
@@ -859,7 +854,7 @@ class TGClient {
var scale = 1.5;
var option = {
debug: true,
backgroundColor: "white",
backgroundColor: "transparent",
height: shareDom.scrollHeight * scale,
width: shareDom.scrollWidth * scale,
style: {
@@ -903,6 +898,14 @@ class TGClient {
return;
}
if (arg.payload.type === "image") {
if (arg.payload.content?.image_url !== undefined) {
const image = arg.payload.content.image_url;
const format = image.split(".").pop();
const executeJS = this.getSaveImgJS(image, format ?? "png");
await core.invoke("execute_js", { label: "mhy_client", js: executeJS });
await this.callback(arg.callback, {});
return;
}
if (arg.payload.content?.image_base64 !== undefined) {
let image = arg.payload.content.image_base64;
image = `data:image/png;base64,${image}`;

View File

@@ -1,14 +1,14 @@
/**
* @file utils/TGHttp.ts
* @description 封装HTTP请求
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import { fetch } from "@tauri-apps/plugin-http";
/**
* @description 请求参数
* @since Beta v0.5.0
* @since Beta v0.5.1
* @property {"GET"|"POST"} method 请求方法
* @property {Record<string,string>} headers 请求头
* @property {Record<string,string>} query 请求参数
@@ -26,13 +26,23 @@ type TGHttpParams = {
/**
* @description 发送请求
* @since Beta v0.5.0
* @since Beta v0.5.1
* @template T
* @param {string} url 请求地址
* @param {TGHttpParams} options 请求参数
* @returns {Promise<T>}
* @returns {Promise<T>} 请求结果
*/
async function TGHttp<T>(url: string, options: TGHttpParams): Promise<T> {
async function TGHttp<T>(url: string, options: TGHttpParams): Promise<T>;
async function TGHttp<T>(
url: string,
options: TGHttpParams,
fullResponse: true,
): Promise<{ data: Promise<T>; resp: Response }>;
async function TGHttp<T>(
url: string,
options: TGHttpParams,
fullResponse?: true,
): Promise<T | { data: Promise<T>; resp: Response }> {
const httpHeaders = new Headers();
if (options.headers) {
for (const key in options.headers) {
@@ -55,8 +65,11 @@ async function TGHttp<T>(url: string, options: TGHttpParams): Promise<T> {
return await fetch(url, fetchOptions)
.then((res) => {
if (res.ok) {
if (options.isBlob) return res.arrayBuffer();
return res.json();
const data = options.isBlob ? res.arrayBuffer() : res.json();
if (fullResponse) {
return { data, resp: res };
}
return data;
}
throw new Error(`HTTP error! status: ${res.status}`);
})

View File

@@ -1,7 +1,7 @@
/**
* @file utils/UIGF.ts
* @description UIGF工具类
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import { app, path } from "@tauri-apps/api";
@@ -50,7 +50,7 @@ async function getUigfHeader(uid: string): Promise<TGApp.Plugins.UIGF.Info> {
/**
* @description 获取 UIGF v4.0 头部信息
* @since Beta v0.5.0
* @since Beta v0.5.1
* @returns {TGApp.Plugins.UIGF.Info4} UIGF v4.0 头部信息
*/
async function getUigf4Header(): Promise<TGApp.Plugins.UIGF.Info4> {
@@ -60,6 +60,7 @@ async function getUigf4Header(): Promise<TGApp.Plugins.UIGF.Info4> {
export_app: "TeyvatGuide",
export_app_version: await app.getVersion(),
version: "v4.0",
lang: "zh-cn",
};
}
@@ -110,7 +111,7 @@ export async function verifyUigfData(path: string, isVersion4: boolean = false):
/**
* @description 验证 UIGF 数据
* @since Beta v0.5.0
* @since Beta v0.5.1
* @param {object} data - UIGF 数据
* @returns {boolean} 是否验证通过
*/
@@ -126,6 +127,14 @@ function validateUigfData(data: object): boolean {
});
return false;
}
const parsedData: TGApp.Plugins.UIGF.Schema = <TGApp.Plugins.UIGF.Schema>data;
if (parsedData.info.uigf_version < "v2.3") {
showSnackbar({
text: "UIGF 版本过低,请使用 v2.3 或以上版本",
color: "error",
});
return false;
}
return true;
}

View File

@@ -1,7 +1,7 @@
/**
* @file src/utils/linkParser.ts
* @description 处理链接
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import { emit } from "@tauri-apps/api/event";
@@ -52,7 +52,7 @@ export async function parsePost(link: string): Promise<false | string> {
/**
* @function parseLink
* @since Beta v0.5.0
* @since Beta v0.5.1
* @description 处理链接
* @param {string} link - 链接
* @param {boolean} useInner - 是否采用内置 JSBridge 打开
@@ -83,16 +83,24 @@ export async function parseLink(
console.log(url.pathname, url.search);
// 处理特定路径
if (url.pathname.startsWith("//discussion")) {
await emit("active_deep_link", "router?path=/posts");
const gid = url.pathname.split("/").pop();
const forum = url.searchParams.get("forum_id");
await emit("active_deep_link", `router?path=/posts/${gid}/${forum}`);
return true;
}
if (link === "mihoyobbs://homeForum?game_id=2&tab_type=2") {
await emit("active_deep_link", "router?path=/news/2/news");
return true;
}
if (link === "mihoyobbs://homeForum?game_id=8&tab_type=2") {
await emit("active_deep_link", "router?path=/news/8/news");
return true;
if (url.pathname.startsWith("//homeForum")) {
const game_id = url.searchParams.get("game_id");
const tab_type = url.searchParams.get("tab_type");
if (game_id && tab_type) {
const tabList = ["", "notice", "activity", "news"];
if (["1", "2", "3"].includes(tab_type)) {
await emit(
"active_deep_link",
`router?path=/news/${game_id}/${tabList[parseInt(tab_type)]}`,
);
return true;
}
}
}
}
return false;

View File

@@ -81,7 +81,7 @@ export function getInitDeviceInfo(): TGApp.App.Device.DeviceInfo {
* @param {string} key - 设备信息 key
* @returns {string} 设备信息
*/
export function getDeviceInfo(key: "device_id" | "device_fp"): string {
export function getDeviceInfo(key: keyof TGApp.App.Device.DeviceInfo): string {
const localDevice = localStorage.getItem("deviceInfo");
let deviceInfo: TGApp.App.Device.DeviceInfo;
if (localDevice === null) {

View File

@@ -75,7 +75,9 @@ onMounted(async () => {
annoHtml.value = await TGUtils.Anno.parseContent(annoData.value.content);
if (annoData.value.banner !== "") annoBanner.value = await saveImgLocal(annoData.value.banner);
annoTitle.value = `Anno_${annoId}`;
await webviewWindow.getCurrent().setTitle(`Anno_${annoId} ${annoData.value.title}`);
await webviewWindow
.getCurrentWebviewWindow()
.setTitle(`Anno_${annoId} ${annoData.value.title}`);
annoRef.value = <HTMLElement>document.querySelector(".anno-body");
} catch (error) {
if (error instanceof Error)
@@ -83,7 +85,7 @@ onMounted(async () => {
else console.error(error);
loadingEmpty.value = true;
loadingTitle.value = "公告不存在或解析失败";
await webviewWindow.getCurrent().setTitle(`Anno_${annoId} Parsing Error`);
await webviewWindow.getCurrentWebviewWindow().setTitle(`Anno_${annoId} Parsing Error`);
return;
}
// 打开 json

View File

@@ -15,31 +15,32 @@
</div>
<div class="tp-post-meta">
<div class="mpm-forum" v-if="postData.forum">
<img :src="getGameIcon(postData.forum.game_id)" alt="gameIcon" />
<img :src="postData.forum.icon" alt="forumIcon" />
<span>{{ postData.forum.name }}</span>
</div>
<div class="mpm-item" :title="`浏览数:${postData.stat.view_num}`">
<div class="mpm-item" :title="`浏览数:${postData?.stat?.view_num}`">
<v-icon>mdi-eye</v-icon>
<span>{{ postData.stat.view_num }}</span>
<span>{{ postData?.stat?.view_num }}</span>
</div>
<div class="mpm-item" :title="`收藏数:${postData.stat.bookmark_num}`">
<div class="mpm-item" :title="`收藏数:${postData?.stat?.bookmark_num}`">
<v-icon>mdi-star</v-icon>
<span>{{ postData.stat.bookmark_num }}</span>
<span>{{ postData?.stat?.bookmark_num }}</span>
</div>
<div class="mpm-item" :title="`回复数:${postData.stat.reply_num}`">
<div class="mpm-item" :title="`回复数:${postData?.stat?.reply_num}`">
<v-icon>mdi-comment</v-icon>
<span>{{ postData.stat.reply_num }}</span>
<span>{{ postData?.stat?.reply_num }}</span>
</div>
<div class="mpm-item" :title="`点赞数:${postData.stat.like_num}`">
<div class="mpm-item" :title="`点赞数:${postData?.stat?.like_num}`">
<v-icon>mdi-thumb-up</v-icon>
<span>{{ postData.stat.like_num }}</span>
<span>{{ postData?.stat?.like_num }}</span>
</div>
<div class="mpm-item" :title="`转发数:${postData.stat.forward_num}`">
<div class="mpm-item" :title="`转发数:${postData?.stat?.forward_num}`">
<v-icon>mdi-share-variant</v-icon>
<span>{{ postData.stat.forward_num }}</span>
<span>{{ postData?.stat?.forward_num }}</span>
</div>
</div>
<TpAvatar :data="postData.user" position="right" />
<TpAvatar :data="postData.user" position="right" v-if="postData.user" />
</div>
<div class="tp-post-title" @click="toPost()" title="点击查看评论">
<span class="mpt-official" v-if="postData.post.post_status.is_official"></span>
@@ -98,6 +99,7 @@ import { useAppStore } from "../store/modules/app.js";
import TGClient from "../utils/TGClient.js";
import TGLogger from "../utils/TGLogger.js";
import { createTGWindow } from "../utils/TGWindow.js";
import TGConstant from "../web/constant/TGConstant.js";
// loading
const loading = ref<boolean>(true);
@@ -122,13 +124,19 @@ const shareTimeTimer = ref<any>();
// 合集
const showCollection = ref<boolean>(false);
function getGameIcon(gameId: number): string {
const find = TGConstant.BBS.CHANNELS.find((item) => item.gid === gameId.toString());
if (find) return find.icon;
return "/platforms/mhy/mys.webp";
}
onMounted(async () => {
appVersion.value = await app.getVersion();
// 检查数据
if (!postId) {
loadingEmpty.value = true;
loadingTitle.value = "未找到数据";
await webviewWindow.getCurrent().setTitle("未找到数据");
await webviewWindow.getCurrentWebviewWindow().setTitle("未找到数据");
await TGLogger.Error("[t-post][onMounted] PostID 不存在");
return;
}
@@ -139,7 +147,9 @@ onMounted(async () => {
loadingTitle.value = "正在渲染数据...";
renderPost.value = getRenderPost(postData.value);
shareTitle.value = `Post_${postId}`;
await webviewWindow.getCurrent().setTitle(`Post_${postId} ${postData.value.post.subject}`);
await webviewWindow
.getCurrentWebviewWindow()
.setTitle(`Post_${postId} ${postData.value.post.subject}`);
} catch (error) {
if (error instanceof Error) {
await TGLogger.Error(`[t-post][${postId}] ${error.name}: ${error.message}`);
@@ -151,7 +161,7 @@ onMounted(async () => {
loadingSub.value = "请检查帖子是否存在或者是否为合法的帖子";
}
loadingEmpty.value = true;
await webviewWindow.getCurrent().setTitle(`Post_${postId} Parsing Error`);
await webviewWindow.getCurrentWebviewWindow().setTitle(`Post_${postId} Parsing Error`);
return;
}
await TGLogger.Info(`[t-post][${postId}][onMounted] ${postData.value.post.subject}`);
@@ -357,6 +367,11 @@ onUnmounted(() => {
object-fit: cover;
}
.mpm-forum img:first-child {
border-radius: 5px;
margin-right: 5px;
}
.mpm-forum span {
overflow: hidden;
text-overflow: ellipsis;

19
src/vite-env.d.ts vendored
View File

@@ -1,10 +1,12 @@
/**
* @file vite-env.d.ts
* @description vite-env.d.ts
* @author BTMuli<bt-muli@outlook.com>
* @since Beta v0.3.3
* @description 全局类型定义文件
* @since Beta v0.5.1
*/
/**
* @description vue 文件类型声明
*/
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<object, object, any>;
@@ -48,3 +50,14 @@ interface ImportMetaEnv {
declare interface ImportMeta {
readonly env: ImportMetaEnv;
}
/**
* @description 极验验证的请求方法-请求参数
* @param {TGApp.BBS.Geetest.InitGeetestParams} params
* @param {(captchaObj: TGApp.BBS.Geetest.GeetestCaptcha) => void} callback
* @return void
*/
declare function initGeetest(
params: TGApp.Plugins.Mys.Geetest.InitGeetestParams,
callback: (captchaObj: TGApp.Plugins.Mys.Geetest.GeetestCaptcha) => void,
): void;

View File

@@ -1,10 +1,17 @@
/**
* @file web/constant/TGConstant.ts
* @description 常量
* @since Beta v0.3.6
* @since Beta v0.5.1
*/
import { BBS_APP_ID, BBS_SALT, BBS_UA_MOBILE, BBS_UA_PC, BBS_VERSION } from "./bbs.js";
import {
BBS_APP_ID,
BBS_SALT,
BBS_UA_MOBILE,
BBS_UA_PC,
BBS_VERSION,
CHANNEL_LIST,
} from "./bbs.js";
import SERVER from "./server.js";
import { GAME_BIZ } from "./utils.js";
@@ -14,6 +21,7 @@ const TGConstant = {
UA_PC: BBS_UA_PC,
UA_MOBILE: BBS_UA_MOBILE,
APP_ID: BBS_APP_ID,
CHANNELS: CHANNEL_LIST,
},
Salt: BBS_SALT,
Server: SERVER,

View File

@@ -1,7 +1,7 @@
/**
* @file web/constant/bbs.ts
* @description 常量-应用数据
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
export const BBS_VERSION = "2.72.2";
@@ -21,3 +21,63 @@ export const BBS_SALT = {
X6: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
PROD: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
};
/**
* @description 频道列表
* @version 2.72.2
* @since Beta v0.5.1
* @interface ToChannelItem
* @property {string} title - 频道名称
* @property {string} icon - 频道图标
* @property {string} gid - 频道 gid
* @return ToChannelItem
*/
export interface ToChannelItem {
title: string;
icon: string;
gid: string;
}
/**
* @description 渠道列表
* @version 2.72.2
* @since Beta v0.5.1
* @type {Array<ToChannelItem>}
*/
export const CHANNEL_LIST: ToChannelItem[] = [
{
title: "原神",
icon: "/platforms/mhy/ys.webp",
gid: "2",
},
{
title: "崩坏:星穹铁道",
icon: "/platforms/mhy/sr.webp",
gid: "6",
},
{
title: "绝区零",
icon: "/platforms/mhy/zzz.webp",
gid: "8",
},
{
title: "崩坏3",
icon: "/platforms/mhy/bh3.webp",
gid: "1",
},
{
title: "崩坏2",
icon: "/platforms/mhy/bh2.webp",
gid: "3",
},
{
title: "未定事件簿",
icon: "/platforms/mhy/wd.webp",
gid: "4",
},
{
title: "大别野",
icon: "/platforms/mhy/dby.webp",
gid: "5",
},
];

View File

@@ -12,7 +12,7 @@ import TGConstant from "../constant/TGConstant.js";
* @description 获取设备指纹
* @since Beta v0.5.0
* @param {TGApp.App.Device.DeviceInfo} Info - 设备信息
* @returns {Promise<string>} 设备指纹
* @returns {Promise<TGApp.App.Device.DeviceInfo>} 设备指纹
*/
export async function getDeviceFp(
Info?: TGApp.App.Device.DeviceInfo,

View File

@@ -1,7 +1,7 @@
/**
* @file web/utils/parseAnno.ts
* @description 解析游戏内公告数据
* @since Beta v0.5.0
* @since Beta v0.5.2
*/
import { saveImgLocal } from "../../utils/TGShare.js";
@@ -30,7 +30,7 @@ function parseAnnoA(a: HTMLAnchorElement): HTMLAnchorElement {
/**
* @description 解析 p
* @since Beta v0.4.7
* @since Beta v0.5.2
* @param {HTMLParagraphElement} p p 元素
* @returns {HTMLParagraphElement} 解析后的 p 元素
*/
@@ -40,6 +40,7 @@ function parseAnnoP(p: HTMLParagraphElement): HTMLParagraphElement {
} else {
p.querySelectorAll("*").forEach((child) => {
child.innerHTML = decodeRegExp(child.innerHTML);
child.querySelectorAll("span").forEach((span) => parseAnnoSpan(span));
});
}
return p;

View File

@@ -4,7 +4,7 @@
* @since Beta v0.4.3
*/
import TGConstant from "../constant/TGConstant";
import TGConstant from "../constant/TGConstant.js";
/**
* @description 转义正则表达式

View File

@@ -11,7 +11,13 @@
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["DOM", "ESNext"],
"types": ["vite/client"],
"types": [
"vite/client",
"src/types",
"src/plugins/Bili/types",
"src/plugins/Hutao/types",
"src/plugins/Mys/types"
],
"allowSyntheticDefaultImports": true,
"composite": true
},

View File

@@ -1,11 +1,12 @@
/**
* @file vite.config.ts
* @description vite 配置文件
* @since Beta v0.5.0
* @since Beta v0.5.1
*/
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
import { nodePolyfills } from "vite-plugin-node-polyfills";
import VueDevtools from "vite-plugin-vue-devtools";
import vuetify from "vite-plugin-vuetify";
@@ -13,7 +14,7 @@ import buildTimePlugin from "./src/utils/TGBuild.js";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vuetify(), buildTimePlugin(), VueDevtools()],
plugins: [vue(), vuetify(), buildTimePlugin(), VueDevtools(), nodePolyfills()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors