Compare commits

...

94 Commits

Author SHA1 Message Date
目棃
1efab84da0 🚀 v0.6.3 2024-11-19 17:12:02 +08:00
目棃
116d29823e 🍱 更新卡池数据
close #133
2024-11-19 17:04:57 +08:00
目棃
53c045e419 剧诗统计浮窗 2024-11-19 17:00:20 +08:00
目棃
3fef8467f4 ♻️ 调整目录结构 2024-11-19 14:45:29 +08:00
目棃
e1f85d1d92 💄 微调UI 2024-11-19 11:49:48 +08:00
目棃
878eef66fa 🍱 更新部分5.2资源
#133
2024-11-19 11:32:40 +08:00
目棃
be4e2c1039 🐛 修复成就页点击异常,调整UI 2024-11-19 10:44:39 +08:00
目棃
62171b78d9 🍱 更新部分5.2资源
#133
2024-11-19 10:28:37 +08:00
目棃
7932de8654 💄 调整收藏页UI,优化刷新逻辑 2024-11-19 10:04:20 +08:00
目棃
c6f45f0a35 ♻️ 请求模块重构 2024-11-19 09:34:18 +08:00
目棃
9b1fa22cbe 💄 修复file_size不存在导致的hint异常 2024-11-17 22:45:12 +08:00
目棃
63929bc9fd 🎨 调整加载逻辑 2024-11-17 16:39:27 +08:00
目棃
53f2612d32 💄 调整UI 2024-11-17 12:35:15 +08:00
目棃
c17339bab5 🐛 修复路径跳转异常 2024-11-17 11:29:40 +08:00
目棃
8a89883784 🐛 修复路径问题 2024-11-17 11:08:32 +08:00
目棃
4bc1808478 🎨 优化链接跳转,调整UI 2024-11-17 11:01:17 +08:00
目棃
c7b5bf34ef 🎨 优化错误处理 2024-11-17 08:59:15 +08:00
目棃
6b66467cff 💄 调整溢出 2024-11-17 08:43:44 +08:00
目棃
680a54a0f1 🎨 一些优化
* 优化公告解析逻辑
* 咨讯页loading显示版块名称
* 移除无用文件
2024-11-16 19:01:30 +08:00
目棃
2f60e128a5 🎨 调整刷新逻辑 2024-11-16 12:34:53 +08:00
目棃
863246707f 🔥 移除第9层统计数据 2024-11-16 12:29:40 +08:00
目棃
8f0853c41b 🐛 修复topic切换分区不生效 2024-11-16 12:07:08 +08:00
目棃
3166567486 💄 调整 ui 2024-11-16 11:58:43 +08:00
目棃
82f937a33e 🎨 优化结构 2024-11-16 09:13:50 +08:00
目棃
6974ada1c0 🐛 修复帖子loading异常 2024-11-15 18:02:15 +08:00
目棃
0c24b95fff ♻️ 函数式调用替代to-loading 2024-11-15 17:54:14 +08:00
目棃
c87ec77543 loading函数式调用 2024-11-15 16:51:17 +08:00
目棃
36b0d198a9 ♻️ showConfirm重构 2024-11-15 16:26:37 +08:00
目棃
76f8bc3c16 🐛 修复验证码登录-100,刷新后切换登录逻辑调整 2024-11-15 14:28:46 +08:00
目棃
f655b6b235 💄 topic显示在卡片中 2024-11-15 13:54:34 +08:00
目棃
83e0d35245 🐛 修复分类更新无效 2024-11-15 13:41:12 +08:00
目棃
10a5b88d24 🐛 修复分享图元素选取异常 2024-11-15 12:34:37 +08:00
目棃
afe53f3d30 话题跳转 2024-11-15 12:07:55 +08:00
目棃
b8afab093f 📝 修正文档链接 2024-11-15 09:53:48 +08:00
目棃
ac6dbe0cdf ♻️ showSnackbar重构 2024-11-14 18:01:38 +08:00
目棃
20b9631468 🐛 修复调用异常 2024-11-14 17:13:45 +08:00
目棃
857d99361a 👽️ 增加 genshinnet 域名处理 2024-11-13 13:16:04 +08:00
目棃
eaa07601a5 🎨 一些调整 2024-11-12 15:46:11 +08:00
目棃
d88e1d1429 🧪 剧诗表检测 2024-11-11 17:58:20 +08:00
目棃
da4a095618 真境剧诗适配
close #113
2024-11-11 17:51:48 +08:00
目棃
d35b94f79f ✏️ 修正类型 2024-11-11 13:44:02 +08:00
目棃
272f3bc14a 📝 添加UIGF4文档备份 2024-11-11 11:59:47 +08:00
目棃
a2ce468e2f 🌱 重构胡桃请求,增加幻想真境剧诗请求 2024-11-11 11:54:43 +08:00
目棃
6b8cbe0e57 🎨 调整帖子数量 2024-11-08 21:37:20 +08:00
目棃
dafcc30239 💄 修复帖子页兑换码弹窗高度异常 2024-11-08 21:36:14 +08:00
目棃
044059beb0 🔥 移除无用代码 2024-11-08 17:48:28 +08:00
目棃
eea9287cfa 🌱 幻想真境剧诗数据获取 2024-11-08 17:24:54 +08:00
目棃
8020d623e3 支持自定义表情组件渲染 2024-11-08 14:36:20 +08:00
目棃
74ff33e1fb 💄 调整UID卡片样式 2024-11-08 13:53:49 +08:00
目棃
cae2a0a1e6 🔧 调整格式化配置 2024-11-05 14:08:47 +08:00
目棃
45bc398626 新增 UID 卡片类型 2024-11-03 08:55:12 +08:00
目棃
7b5bf201ce 💄 调整UP四星颜色 2024-11-01 13:51:13 +08:00
目棃
8df9370932 🐛 修复数据undefined 2024-10-31 17:47:43 +08:00
目棃
5358b07157 🚀 v0.6.2 2024-10-31 11:27:08 +08:00
目棃
6a78bf059c 💄 调整卡片UI,维持名片比例 2024-10-31 10:13:24 +08:00
目棃
41e71357ef 💄 队伍出场页面UI调整 2024-10-30 22:49:33 +08:00
目棃
9d08880c10 💄 角色持有适配差距 2024-10-30 22:18:44 +08:00
目棃
a812e0781b 👽️ 修正咨讯Api 2024-10-30 18:07:33 +08:00
目棃
f08668e3da 💄 修复图标渲染异常 2024-10-30 17:40:14 +08:00
目棃
a544e6bb6b 🍱 更新下半卡池 2024-10-29 22:56:14 +08:00
目棃
c006a3f84d ♻️ 角色使用率&出场率 2024-10-29 18:38:11 +08:00
目棃
05cee4d8e9 ♻️ 深渊数据库重构,概览显示差距 2024-10-29 17:05:14 +08:00
目棃
06345bf5db 🐛 修复release打包失败 2024-10-27 13:11:19 +08:00
目棃
1c25942920 💄 修复视频封面位置异常 2024-10-27 12:43:29 +08:00
目棃
c17f9f4f0a ♻️ 调整代码结构 2024-10-27 12:38:45 +08:00
目棃
980b7ffd45 💄 深渊期数上对齐 2024-10-26 20:12:22 +08:00
目棃
35fcee9b3b 💄 修复回顶组件宽度异常[mac] 2024-10-26 20:05:24 +08:00
目棃
6701464002 🐛 修复用户登录状态异常 2024-10-26 18:22:15 +08:00
目棃
5f1d07968c 💄 mac隐藏游戏目录设置&游戏启动badge 2024-10-26 15:39:27 +08:00
目棃
83bfd5d1eb 🎨 调整保存时的hint 2024-10-26 15:08:02 +08:00
目棃
0050ee773e 💄 调整未登录时的部分内容渲染 2024-10-26 11:21:56 +08:00
目棃
7749406c90 ✏️ 修正部分类型 2024-10-25 10:11:56 +08:00
目棃
26041948ef ♻️ 请求重构,合并postWapi跟apiHub相关请求 2024-10-24 23:51:45 +08:00
目棃
41db04b7a2 💄 子回复取消保持,点击其他隐藏 2024-10-24 22:14:35 +08:00
目棃
003344c722 ⬆️ 走ssh更新,统一更新日志路径,更新依赖 2024-10-24 22:10:01 +08:00
目棃
bb9c9d55e6 🚀 v0.6.1 2024-10-22 20:51:36 +08:00
目棃
0d37f4e82b ✏️ 修正类型错误 2024-10-22 20:46:13 +08:00
目棃
e5a917b07b 💄优化select 2024-10-22 20:44:21 +08:00
目棃
4cef204f72 💄调整副标题 2024-10-17 14:16:04 +08:00
目棃
97444190ab 💄深渊分享显示应用信息,圣遗物详情推荐属性高亮 2024-10-17 11:51:41 +08:00
目棃
632b4e88ec 🎨 调整插入账户sql 2024-10-17 11:12:27 +08:00
目棃
c7861cc213 🎨 调整skippedFloor数据获取逻辑 2024-10-17 11:01:12 +08:00
目棃
2a5a408098 💄显示跳过楼层 2024-10-16 19:08:23 +08:00
目棃
1794df2b8a 👽️ 适配深渊新字段 2024-10-16 18:59:05 +08:00
目棃
f8121d504c 🐛 修复数据库初始化异常 2024-10-16 18:41:37 +08:00
目棃
be24bdc7ce 🎨 调整点击逻辑 2024-10-13 12:10:00 +08:00
目棃
18507b6273 🐛 修复封面503 2024-10-12 15:16:06 +08:00
目棃
2e152965df 🐛 修复数据未即时刷新 2024-10-12 10:58:47 +08:00
目棃
d735d0d098 🎨 调整卡片封面加载逻辑 2024-10-11 18:07:03 +08:00
目棃
631fbfc29c 🐛 修复banner为空时的渲染异常 2024-10-11 17:53:20 +08:00
目棃
67aa3b7363 ♻️ 公告卡片组件抽离,支持分享 2024-10-11 17:02:07 +08:00
目棃
454f9e9750 🎨 虚拟列表性能优化 2024-10-11 16:31:53 +08:00
目棃
6b34ae612c 💄 处理特定情况下的内容溢出,调整Sql创建 2024-10-10 09:30:30 +08:00
目棃
3112b2e41a ⬆️ 更新依赖,补充缺失的素材日历数据 2024-10-10 09:22:09 +08:00
353 changed files with 18634 additions and 8423 deletions

View File

@@ -43,11 +43,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 22.8.0
node-version: 23.1.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.10.0
version: 9.13.2
- name: Install frontend dependencies
run: pnpm install

View File

@@ -12,11 +12,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 22.8.0
node-version: 23.1.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.10.0
version: 9.13.2
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: "Qodana Scan"

1
.npmrc
View File

@@ -1 +0,0 @@
ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/

View File

@@ -16,3 +16,4 @@ qodana.yaml
# data
!src/data/**/*.json
src-tauri/gen/*.json
!eslint

View File

@@ -2,12 +2,64 @@
Author: 目棃
Description: CHANGELOG
Date: 2024-10-09
Update: 2024-10-09
Update: 2024-11-19
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2024-10-09 15:51:43`
>
> 更新于 `2024-10-09 15:59:58`
> 更新于 `2024-11-19 17:06:05`
## [0.6.3](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.3) (2024-11-19)
- 🐛 修复用户战绩角色数据`undefined`
- 🐛 修复咨讯页加载更多异常
- 🐛 修复验证码登录提示`-100`数据刷新后若为已登录UID则不会再提示切换
- 🐛 修复部分公告渲染异常
- 🐛 修复成就页面在存在搜索内容时点击左侧成就系列无响应
- ✨ 帖子新增 UID 卡片解析&渲染
- ✨ 帖子新增自定义表情解析&渲染
- ✨ 真境剧诗适配,新增真境剧诗页面,支持获取&分享&上传(胡桃数据库),可通过深渊页面进入
- ✨ 新增话题页面,可通过帖子卡片标签点击或帖子详情顶部标签点击进入
- ✨ 更完善的`loading`显示调整了组件UI
- 🍱 更新5.2版本资源 [`#133`](https://github.com/BTMuli/TeyvatGuide/issues/133)
- 💄 调整祈愿记录UP四星颜色
- 💄 修复帖子页兑换码弹窗高度异常
- 💄 调整帖子卡片UI增加显示帖子话题如存在话题&版块支持点击跳转
- 💄 调整帖子详情页UI顶部话题&版块支持点击跳转
- 💄 调整帖子显示数量,支持加载更多,默认排序改为`最新回复`,移除`默认排序`,增加`热门`排序
- 💄 咨讯、帖子等页面刷新时自动滚动到顶部
- 🔥 深渊数据库显示移除第9层统计数据
- 👽️ 米游社子窗口增加`genshinnet`域名支持
- 🎨 优化帖子详情数据加载的错误处理
- ♻️ `snackbar``confirm``loading`组件重构
- ♻️ 请求模块重构
## [0.6.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.2) (2024-10-31)
- 🐛 修复用户登录状态异常 [`#132`](https://github.com/BTMuli/TeyvatGudie/issues/132)
- 💄 帖子子回复取消保持,点击其他隐藏
- 💄 调整未登录时的部分内容渲染
- 💄 调整保存时图片的hint
- 💄 `mac`:修复回顶组件宽度异常
- 💄 `mac`:修复视频封面位置异常
- 💄 调整角色卡片UI维持名片比例
- ♻️ 深渊数据库重构,概览显示差距
- 🍱 更新下半卡池
- 👽️ 修正咨讯Api
## [0.6.1](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.1) (2024-10-22)
- 🐛 新用户数据库初始化异常 [`#131`](https://github.com/BTMuli/TeyavtGuide/issues/131)
- 🐛 修复角色数据未即时刷新
- 🐛 修复`openSystemBrowser`回调执行异常
- ♻️ 公告卡片组件抽离,支持分享
- 🎨 成就页面&名片图鉴页面采用虚拟列表优化性能
- 🎨 调整卡片封面加载逻辑
- 💄 处理特定情况下的内容溢出
- 💄 适配深渊新字段,显示跳过楼层
- 💄深渊分享显示应用信息,圣遗物详情推荐属性高亮
- 💄调整帖子子窗口副标题样式
- 💄调整留影叙佳期选项样式
## [0.6.0](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.0) (2024-10-09)

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 说明文档
Date: 2023-03-05
Update: 2024-10-07
Update: 2024-11-19
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
>
> 更新于 `2024-10-07 21:51:00`
> 更新于 `2024-11-19 17:06:18`
![](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)
@@ -49,6 +49,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 游戏内公告&活动获取
- [x] 米游社官方帖获取(支持通过 ID 获取)
- [x] 米游社各分区帖子获取(支持通过 ID 获取)
- [x] 米游社话题帖子获取(通过话题点击跳转)
- [x] 成就管理UIAF v1.1),支持 [`YaeAchievement`](https://github.com/HolographicHat/YaeAchievement) 导入
- [x] 祈愿管理UIGF v3.0UIGF v4.0
- [x] 留影叙佳期画片查看
@@ -60,7 +61,9 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 原神战绩数据获取
- [x] 角色详情数据获取
- [x] 螺旋深渊数据获取
- [x] 真境剧诗数据获取
- [x] 祈愿数据获取(近一年)
- [x] 用户收藏帖子获取
- Wiki 功能:
@@ -93,14 +96,14 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- Changelog: [CHANGELOG](CHANGELOG.md)
- 资源来源:[项目资源说明](docs/项目资源说明.md)
- UIAF[UIAF v1.1](docs/UIAF.md)
- UIGF[UIGF v3.0](docs/UIGF.md)
- UIGF[UIGF v3.0](docs/UIGF3.md)[UIGF v4.0](docs/UIGF.md)
- [macOS 平台门禁属性导致应用无法打开应用的修复指引](docs/macos-gatekeeper/README.md)
## 特定项目 / Special Project
- [MuCli](https://github.com/BTMuli/MuCli):基于 NodeJS 的命令行工具,用于生成项目文档。
- [TGAssistant](https://github.com/BTMuli/TGAssistant)Teyvat Guide 的资源获取、解析、处理仓库。
- ~~[WhiteTea](https://github.com/BTMuli/WhiteTea)Github Bot自动化处理 Teyvat Guide 的 Issue 和 Pull Request。~~ 服务已挂T_T
- [WhiteTea](https://github.com/BTMuli/WhiteTea)Github Bot自动化处理 Teyvat Guide 的 Issue 和 Pull Request。
## 技术栈 / Tech Stack
@@ -122,7 +125,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
应用版本号遵循 [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/) 规范。
隐私政策:[Privacy](https://app.btmuli.ink/docs/privacy.html)
隐私政策:[Privacy](https://app.btmuli.ink/docs/TeyvatGuide/privacy.html)
## 鸣谢 / Thanks

View File

@@ -1,190 +1,371 @@
---
Author: 目棃
Description: UIGF v2.4 Backup
Date: 2023-11-15
Update: 2024-03-13
Description: UIGF v4 Backup
Date: 2024-11-11
Update: 2024-11-11
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-11-15 20:58:36`
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2024-11-11 11:57:27`
>
> 更新于 `2024-03-13 15:50:36`
> 更新于 `2024-11-11 11:57:27`
> 本文档为 [UIGF v3.0](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/UIGF.md) 的备份,仅供参考。
> 本文档为 [UIGF v4.0](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/uigf.md) 的备份,仅供参考。
# 统一可交换抽卡记录标准 v3.0
# 统一可交换抽卡记录标准 v4.0
> Uniformed Interchangeable GachaLog Format standard (UIGF) v3.0 <Badge text="Current" type="message" />
>
> ::: warning UIGF 标准使用声明
> 应用必须在同时支持 UIGF 数据格式**导入**和**导出**功能并在相关功能区域或文档中提供跳转至 [UIGF-Org](https://uigf.org) 的超链接后声明支持 UIGF 格式
> Uniformed Interchangeable GachaLog Format standard (UIGF) v4.0 <Badge text="Current" type="message" />
仅包含导入或导出功能降低了用户数据可流通性,且将数据至于用户不可控的风险中,不符合 UIGF-Org 设计的初衷。
::: warning 中断性更新警告
`UIGF v4.0 及更高版本` 对于 `UIGF v3.0 及更低版本``SRGF v1.0` **不具备向下兼容性**。UIGF/SRGF 合作项目如需适配,需重新认证。
:::
## 更新记录
| 版本 | 说明 | 兼容 |
| ----------------------------- | ---------------------------------------------------------- | -------------- |
| `v2.0` | 首个正式版本 | v2.0 |
| `v2.1` | 简化了部分语言表述,与 v2.0在数据格式上完全一致 | v2.1 and lower |
| [`v2.2`](UIGF-legacy-v2.2.md) | 新增 `info.export_timestamp` 填充 UNIX 时间戳 | v2.2 and lower |
| [`v2.3`](UIGF-legacy-v2.3.md) | 扩充至非中文语境,使用 Json Schema 表述。移除了 Excel 格式 | v2.3 and lower |
| [`v2.4`](UIGF-legacy-v2.4.md) | 新增 `info.region_time_zone` 支持时区处理 | v2.4 and lower |
| `v3.0` | 新增 集录祈愿类型支持 | v3.0 and lower |
| 版本 | 说明 | 兼容 |
| ------ | --------------------------------- | --------------- |
| `v3.0` | 低版本的更新日志请查看历史版本 | v3.0 及更低版本 |
| `v4.0` | 合并 SRGF新增绝区零抽卡格式支持 | v4.0 |
### v3.0 更新内容
## 前言
- `gacha_type` 增加新枚举项
-`gacha_type` 枚举新增值为 `500` 的项,用于表示集录祈愿类型
为了统一不同应用、不同游戏、不同账号间的抽卡记录导入导出行为,我们决定将所有支持的游戏抽卡格式合并入 UIGF 中。不同的游戏、不同的账号将能够以单个文件或字符串的形式进行表示,导入与导出操作对用户而言将变得史无前例的简单。
## `info` 字段说明
## 实现与认证
### `region_time_zone`
实现 `UIGF v4.0 及更高版本`格式的导入导出并不意味着需要移除对 `UIGF v3.0 及更低版本``SRGF v1.0` 的导入导出支持。但是,不建议对 `UIGF v4.0 和更高版本``UIGF v3.0 及更低版本``SRGF v1.0` 使用同一套导入导出逻辑。
由于在获取祈愿记录时得到的`time`为服务器时间,为了准确判断时间的时区偏移,引入此字段
导出方可以选择性地填充针对每个游戏的字段或直接忽略;导入方可以选择性地读取针对每个游戏的字段或直接忽略
与 SRGF 不同,由于无法直接从服务器获取`region_time_zone`,在导出方未提供此字段时,需要根据 `uid` 进行推断
针对对某一款游戏的支持,必须同时实现数据的导入和导出功能,否则将无法通过认证
#### 映射关系
::: info UIGF 标准使用声明
请在应用内提供跳转至 [UIGF-Org](https://uigf.org) 的超链接,声明支持 UIGF 数据格式。
| `uid`首个字符 | `region_time_zone` | 游戏服务器 |
| ------------- | ------------------ | --------------------------------- |
| `'6'` | `-5` | os_usa |
| `'7'` | `1` | os_euro |
| 剩余情况 | `8` | os_cht, os_asia, cn_gf01, cn_qd01 |
App 不应假定 `region_time_zone` 的值为上表中给出的值,应具有处理非标准 `region_time_zone` 值的能力。
`region_time_zone` 的值与 `uid` 推断结果不一致,则优先选择 `region_time_zone` 给出的值。
## `list` 字段说明
### `id`
物品内包含了一项较为特殊的字段: `id`,为原神官方 API 中包含的,代表每条抽卡记录唯一性的 `id`。App 导出 UIGF 时
- 需要确保每个物品都有一个有效的唯一 `id`
- 若有记录中不包含`id`,则应从下一个自带有效 `id` 的物品开始,为每条缺失`id`字段的数据补全`id`
赋值数据向前(时间排序)依次递减,每次递减的值应保持为 `1`
### `gacha_type`
由于存在会共享保底与概率的卡池,所以需要一个额外的字段来界定
我们在 `UIGF` 的所有格式中注入了 `uigf_gacha_type` 字段
在导出到 `UIGF` 格式时需要注意添加对应的 `uigf_gacha_type` 字段
#### 映射关系
| `uigf_gacha_type` | `gacha_type` |
| ----------------- | -------------- |
| `100` | `100` |
| `200` | `200` |
| `301` | `301` or `400` |
| `302` | `302` |
| `500` | `500` |
### `item_id`
物品游戏内ID你可以通过 [UIGF API](../API.md) 获取这一数据
仅包含导入或导出功能降低了用户数据的流通性,且将数据置于用户不可控的风险中,不符合 UIGF-Org 设计的初衷。
:::
## Json Schema
> UIGF-Org 提供[Json Schema](/schema/uigf.json) 用于验证
> UIGF-Org 提供下述 Json Schema 以用于验证资料结构的正确性。
::: warning 注意字段类型
开发者务必遵循 Schema 内定义的字段类型。使用错误的类型可能会导致其他由强类型编程语言编写的工具在解析 Json 文件时产生错误,进而导致数据转移失败。
为了避免这类问题,我们建议您针对 UIGF 格式设计专用的数据结构。同时,设计相关的单元测试以确保导入导出的一致性。
我们也提供 [UIGF 格式校验工具](https://schema.uigf.org/?schema=uigf)来帮助你校验 Json 文件。
:::
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"info": {
"type": "object",
"properties": {
"uid": {
"type": "string",
"title": "导出记录的 UID"
},
"lang": {
"type": "string",
"title": "语言 languagecode2-country/regioncode2"
},
"export_timestamp": {
"type": "number",
"title": "导出 UNIX 时间戳(秒)"
},
"export_time": {
"type": "string",
"title": "导出时间",
"description": "yyyy-MM-dd HH:mm:ss"
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "导出档案的时间戳,秒级"
},
"export_app": {
"type": "string",
"title": "导出 App 名称"
"description": "导出档案的 App 名称"
},
"export_app_version": {
"type": "string",
"title": "导出 App 版本"
"description": "导出档案的 App 版本"
},
"uigf_version": {
"version": {
"type": "string",
"title": "UIGF 版本号",
"pattern": "v\\d+\\.\\d+"
},
"region_time_zone": {
"type": "number",
"title": "区域时区偏移"
"pattern": "^v\\d+\\.\\d+$",
"description": "导出档案的 UIGF 版本号,格式为 'v{major}.{minor}',如 v4.0"
}
},
"required": ["uid", "uigf_version"],
"title": "UIGF 导出信息"
"required": ["export_timestamp", "export_app", "export_app_version", "version"]
},
"list": {
"hk4e": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uigf_gacha_type": {
"type": "string",
"title": "UIGF 卡池类型",
"description": "用于区分卡池类型不同,但卡池保底计算相同的物品"
"uid": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "UID"
},
"gacha_type": {
"type": "string",
"title": "卡池类型"
"timezone": {
"type": "integer",
"description": "时区偏移,由米哈游 API 返回,若与服务器时区不同请注意 list 中 time 的转换"
},
"item_id": {
"lang": {
"type": "string",
"title": "物品的内部 ID"
"description": "语言代码",
"enum": [
"de-de",
"en-us",
"es-es",
"fr-fr",
"id-id",
"it-it",
"ja-jp",
"ko-kr",
"pt-pt",
"ru-ru",
"th-th",
"tr-tr",
"vi-vn",
"zh-cn",
"zh-tw"
]
},
"count": {
"type": "string",
"title": "个数一般为1"
},
"time": {
"type": "string",
"title": "获取物品的时间"
},
"name": {
"type": "string",
"title": "物品名称"
},
"item_type": {
"type": "string",
"title": "物品类型"
},
"rank_type": {
"type": "string",
"title": "物品等级"
},
"id": {
"type": "string",
"title": "记录内部 ID"
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uigf_gacha_type": {
"type": "string",
"description": "UIGF 卡池类型,用于区分卡池类型不同,但卡池保底计算相同的物品",
"enum": ["100", "200", "301", "302", "500"]
},
"gacha_type": {
"type": "string",
"description": "卡池类型,米哈游 API 返回",
"enum": ["100", "200", "301", "302", "400", "500"]
},
"item_id": {
"type": "string",
"description": "物品的内部 ID"
},
"count": {
"type": "string",
"description": "物品个数一般为1米哈游 API 返回"
},
"time": {
"type": "string",
"description": "抽取物品时对应时区timezone下的当地时间"
},
"name": {
"type": "string",
"description": "物品名称,米哈游 API 返回"
},
"item_type": {
"type": "string",
"description": "物品类型,米哈游 API 返回"
},
"rank_type": {
"type": "string",
"description": "物品等级,米哈游 API 返回"
},
"id": {
"type": "string",
"description": "记录内部 ID米哈游 API 返回"
}
},
"required": ["uigf_gacha_type", "gacha_type", "item_id", "time", "id"]
}
}
},
"required": ["uigf_gacha_type", "gacha_type", "id", "item_id", "time"],
"title": "UIGF 物品"
},
"title": "物品列表"
"required": ["uid", "timezone", "list"]
}
},
"hkrpg": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uid": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "UID"
},
"timezone": {
"type": "integer",
"description": "时区偏移,由米哈游 API 返回,若与服务器时区不同请注意 list 中 time 的转换"
},
"lang": {
"type": "string",
"description": "语言代码",
"enum": [
"de-de",
"en-us",
"es-es",
"fr-fr",
"id-id",
"it-it",
"ja-jp",
"ko-kr",
"pt-pt",
"ru-ru",
"th-th",
"tr-tr",
"vi-vn",
"zh-cn",
"zh-tw"
]
},
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gacha_id": {
"type": "string",
"description": "卡池 Id"
},
"gacha_type": {
"type": "string",
"description": "卡池类型",
"enum": ["1", "2", "11", "12"]
},
"item_id": {
"type": "string",
"description": "物品的内部 ID"
},
"count": {
"type": "string",
"description": "物品个数一般为1米哈游 API 返回"
},
"time": {
"type": "string",
"description": "抽取物品时对应时区timezone下的当地时间"
},
"name": {
"type": "string",
"description": "物品名称,米哈游 API 返回"
},
"item_type": {
"type": "string",
"description": "物品类型,米哈游 API 返回"
},
"rank_type": {
"type": "string",
"description": "物品等级,米哈游 API 返回"
},
"id": {
"type": "string",
"description": "记录内部 ID米哈游 API 返回"
}
},
"required": ["gacha_type", "gacha_id", "time", "item_id", "id"]
}
}
},
"required": ["uid", "timezone", "list"]
}
},
"nap": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uid": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "UID"
},
"timezone": {
"type": "integer",
"description": "时区偏移,由米哈游 API 返回,若与服务器时区不同请注意 list 中 time 的转换"
},
"lang": {
"type": "string",
"description": "语言代码",
"enum": [
"de-de",
"en-us",
"es-es",
"fr-fr",
"id-id",
"it-it",
"ja-jp",
"ko-kr",
"pt-pt",
"ru-ru",
"th-th",
"tr-tr",
"vi-vn",
"zh-cn",
"zh-tw"
]
},
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gacha_id": {
"type": "string",
"description": "卡池 Id"
},
"gacha_type": {
"type": "string",
"description": "卡池类型",
"enum": ["1", "2", "3", "5"]
},
"item_id": {
"type": "string",
"description": "物品的内部 ID"
},
"count": {
"type": "string",
"description": "物品个数一般为1米哈游 API 返回"
},
"time": {
"type": "string",
"description": "抽取物品时对应时区timezone下的当地时间"
},
"name": {
"type": "string",
"description": "物品名称,米哈游 API 返回"
},
"item_type": {
"type": "string",
"description": "物品类型,米哈游 API 返回"
},
"rank_type": {
"type": "string",
"description": "物品等级,米哈游 API 返回"
},
"id": {
"type": "string",
"description": "记录内部 ID米哈游 API 返回"
}
},
"required": ["gacha_type", "item_id", "time", "id"]
}
}
},
"required": ["uid", "timezone", "list"]
}
}
},
"required": ["info", "list"],
"title": "UIGF 根对象"
"required": ["info"]
}
```

190
docs/UIGF3.md Normal file
View File

@@ -0,0 +1,190 @@
---
Author: 目棃
Description: UIGF v3 Backup
Date: 2023-11-15
Update: 2024-11-11
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-11-15 20:58:36`
>
> 更新于 `2024-11-11 11:56:11`
> 本文档为 [UIGF v3.0](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/uigf-legacy-v3.0.md) 的备份,仅供参考。
# 统一可交换抽卡记录标准 v3.0
> Uniformed Interchangeable GachaLog Format standard (UIGF) v3.0 <Badge text="Current" type="message" />
>
> ::: warning UIGF 标准使用声明
> 应用必须在同时支持 UIGF 数据格式**导入**和**导出**功能并在相关功能区域或文档中提供跳转至 [UIGF-Org](https://uigf.org) 的超链接后声明支持 UIGF 格式
仅包含导入或导出功能降低了用户数据可流通性,且将数据至于用户不可控的风险中,不符合 UIGF-Org 设计的初衷。
:::
## 更新记录
| 版本 | 说明 | 兼容 |
| ----------------------------- | ---------------------------------------------------------- | -------------- |
| `v2.0` | 首个正式版本 | v2.0 |
| `v2.1` | 简化了部分语言表述,与 v2.0在数据格式上完全一致 | v2.1 and lower |
| [`v2.2`](UIGF-legacy-v2.2.md) | 新增 `info.export_timestamp` 填充 UNIX 时间戳 | v2.2 and lower |
| [`v2.3`](UIGF-legacy-v2.3.md) | 扩充至非中文语境,使用 Json Schema 表述。移除了 Excel 格式 | v2.3 and lower |
| [`v2.4`](UIGF-legacy-v2.4.md) | 新增 `info.region_time_zone` 支持时区处理 | v2.4 and lower |
| `v3.0` | 新增 集录祈愿类型支持 | v3.0 and lower |
### v3.0 更新内容
- `gacha_type` 增加新枚举项
-`gacha_type` 枚举新增值为 `500` 的项,用于表示集录祈愿类型
## `info` 字段说明
### `region_time_zone`
由于在获取祈愿记录时得到的`time`为服务器时间,为了准确判断时间的时区偏移,引入此字段。
与 SRGF 不同,由于无法直接从服务器获取`region_time_zone`,在导出方未提供此字段时,需要根据 `uid` 进行推断。
#### 映射关系
| `uid`首个字符 | `region_time_zone` | 游戏服务器 |
| ------------- | ------------------ | --------------------------------- |
| `'6'` | `-5` | os_usa |
| `'7'` | `1` | os_euro |
| 剩余情况 | `8` | os_cht, os_asia, cn_gf01, cn_qd01 |
App 不应假定 `region_time_zone` 的值为上表中给出的值,应具有处理非标准 `region_time_zone` 值的能力。
`region_time_zone` 的值与 `uid` 推断结果不一致,则优先选择 `region_time_zone` 给出的值。
## `list` 字段说明
### `id`
物品内包含了一项较为特殊的字段: `id`,为原神官方 API 中包含的,代表每条抽卡记录唯一性的 `id`。App 导出 UIGF 时
- 需要确保每个物品都有一个有效的唯一 `id`
- 若有记录中不包含`id`,则应从下一个自带有效 `id` 的物品开始,为每条缺失`id`字段的数据补全`id`
赋值数据向前(时间排序)依次递减,每次递减的值应保持为 `1`
### `gacha_type`
由于存在会共享保底与概率的卡池,所以需要一个额外的字段来界定
我们在 `UIGF` 的所有格式中注入了 `uigf_gacha_type` 字段
在导出到 `UIGF` 格式时需要注意添加对应的 `uigf_gacha_type` 字段
#### 映射关系
| `uigf_gacha_type` | `gacha_type` |
| ----------------- | -------------- |
| `100` | `100` |
| `200` | `200` |
| `301` | `301` or `400` |
| `302` | `302` |
| `500` | `500` |
### `item_id`
物品游戏内ID你可以通过 [UIGF API](../API.md) 获取这一数据
## Json Schema
> UIGF-Org 提供[Json Schema](/schema/uigf.json) 用于验证
```json
{
"type": "object",
"properties": {
"info": {
"type": "object",
"properties": {
"uid": {
"type": "string",
"title": "导出记录的 UID"
},
"lang": {
"type": "string",
"title": "语言 languagecode2-country/regioncode2"
},
"export_timestamp": {
"type": "number",
"title": "导出 UNIX 时间戳(秒)"
},
"export_time": {
"type": "string",
"title": "导出时间",
"description": "yyyy-MM-dd HH:mm:ss"
},
"export_app": {
"type": "string",
"title": "导出 App 名称"
},
"export_app_version": {
"type": "string",
"title": "导出 App 版本"
},
"uigf_version": {
"type": "string",
"title": "UIGF 版本号",
"pattern": "v\\d+\\.\\d+"
},
"region_time_zone": {
"type": "number",
"title": "区域时区偏移"
}
},
"required": ["uid", "uigf_version"],
"title": "UIGF 导出信息"
},
"list": {
"type": "array",
"items": {
"type": "object",
"properties": {
"uigf_gacha_type": {
"type": "string",
"title": "UIGF 卡池类型",
"description": "用于区分卡池类型不同,但卡池保底计算相同的物品"
},
"gacha_type": {
"type": "string",
"title": "卡池类型"
},
"item_id": {
"type": "string",
"title": "物品的内部 ID"
},
"count": {
"type": "string",
"title": "个数一般为1"
},
"time": {
"type": "string",
"title": "获取物品的时间"
},
"name": {
"type": "string",
"title": "物品名称"
},
"item_type": {
"type": "string",
"title": "物品类型"
},
"rank_type": {
"type": "string",
"title": "物品等级"
},
"id": {
"type": "string",
"title": "记录内部 ID"
}
},
"required": ["uigf_gacha_type", "gacha_type", "id", "item_id", "time"],
"title": "UIGF 物品"
},
"title": "物品列表"
}
},
"required": ["info", "list"],
"title": "UIGF 根对象"
}
```

View File

@@ -1,11 +1,20 @@
import eslint_jsonc from "eslint-plugin-jsonc";
import eslint_js from "@eslint/js";
import eslint_ts from "typescript-eslint";
import eslint_vue from "eslint-plugin-vue";
import { jsonEslintConfig } from "./eslint/jsonEslint.js";
import { vueEslintConfig } from "./eslint/vueEslint.js";
import ymlEslintConfig from "./eslint/ymlEslint.js";
export default [
eslint_js.configs.recommended,
...eslint_jsonc.configs["flat/recommended-with-jsonc"],
...eslint_ts.configs.recommended,
...eslint_vue.configs["flat/essential"],
...jsonEslintConfig,
ymlEslintConfig,
...vueEslintConfig,
ymlEslintConfig,
{
ignores: [
"dist",
@@ -13,7 +22,7 @@ export default [
"pnpm-lock.yaml",
"src/data/**/*.json",
"src-tauri/tauri.conf.json",
"src-tauri/gen/*.json",
"src-tauri/**/*.json",
"qodana.yaml",
".github",
".vscode",

View File

@@ -1,14 +1,10 @@
import eslint_jsonc from "eslint-plugin-jsonc";
import jsonc_parser from "jsonc-eslint-parser";
import pluginJsonc from "eslint-plugin-jsonc";
import parserJsonc from "jsonc-eslint-parser";
const pkgJsonConfig = {
files: ["package.json"],
plugins: {
jsonc: eslint_jsonc,
},
languageOptions: {
parser: jsonc_parser,
},
plugins: { jsonc: pluginJsonc },
languageOptions: { parser: parserJsonc },
rules: {
"jsonc/comma-dangle": ["error", "never"],
"jsonc/sort-keys": [
@@ -38,12 +34,8 @@ const pkgJsonConfig = {
const tscJsonConfig = {
files: ["tsconfig.json"],
plugins: {
jsonc: eslint_jsonc,
},
languageOptions: {
parser: jsonc_parser,
},
plugins: { jsonc: pluginJsonc },
languageOptions: { parser: parserJsonc },
rules: {
"jsonc/comma-dangle": ["error", "never"],
"jsonc/sort-keys": [
@@ -66,29 +58,12 @@ const tscJsonConfig = {
const jsoncConfig = {
files: ["source/data/out/**/*.json", ".vscode/**/*.json"],
plugins: {
jsonc: eslint_jsonc,
},
languageOptions: {
parser: jsonc_parser,
},
plugins: { jsonc: pluginJsonc },
languageOptions: { parser: parserJsonc },
rules: {
"jsonc/comma-dangle": ["error", "never"],
"jsonc/sort-keys": [
"error",
{
pathPattern: "^$",
order: {
type: "asc",
},
},
],
"jsonc/sort-keys": ["error", { pathPattern: "^$", order: { type: "asc" } }],
},
};
export const jsonEslintConfig = [
...eslint_jsonc.configs["flat/recommended-with-json"],
pkgJsonConfig,
tscJsonConfig,
jsoncConfig,
];
export const jsonEslintConfig = [pkgJsonConfig, tscJsonConfig, jsoncConfig];

View File

@@ -1,66 +1,42 @@
import eslint_js from "@eslint/js";
import eslint_import from "eslint-plugin-import";
import eslint_prettier from "eslint-plugin-prettier";
import eslint_vue from "eslint-plugin-vue";
import pluginImport from "eslint-plugin-import";
import pluginPrettier from "eslint-plugin-prettier";
import pluginVue from "eslint-plugin-vue";
import globals from "globals";
import eslint_ts from "typescript-eslint";
import vue_parser from "vue-eslint-parser";
const tsConfigRules = {
"@typescript-eslint/consistent-type-assertions": [
"error",
{
assertionStyle: "angle-bracket",
},
],
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "angle-bracket" }],
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-expressions": ["error", { allowShortCircuit: false }],
"import/order": [
"error",
{
groups: ["builtin", "external", "internal", "parent", "sibling", "index", "unknown"],
"newlines-between": "always",
alphabetize: {
order: "asc",
caseInsensitive: true,
},
alphabetize: { order: "asc", caseInsensitive: true },
},
],
"prettier/prettier": "error",
};
const tsConfig = {
files: ["*.ts"],
plugins: {
typescript: eslint_ts,
import: eslint_import,
prettier: eslint_prettier,
},
files: ["*.ts", "*.d.ts", "src/**/*.ts", "src/**/*.d.ts"],
plugins: { typescript: eslint_ts, import: pluginImport, prettier: pluginPrettier },
languageOptions: {
parser: eslint_ts.parser,
parserOptions: {
project: "tsconfig.json",
tsconfigRootDir: ".",
},
parserOptions: { project: "tsconfig.json", tsconfigRootDir: "." },
},
rules: tsConfigRules,
};
const vueConfig = {
plugins: {
vue: eslint_vue,
import: eslint_import,
prettier: eslint_prettier,
},
files: ["src/**/*.vue"],
plugins: { vue: pluginVue, import: pluginImport, prettier: pluginPrettier },
languageOptions: {
globals: {
...globals.browser,
...globals.es2021,
TGApp: "readonly",
window: "readonly",
},
globals: { ...globals.browser, ...globals.es2021, TGApp: "readonly", window: "readonly" },
ecmaVersion: "latest",
sourceType: "module",
parser: vue_parser,
@@ -71,17 +47,7 @@ const vueConfig = {
tsconfigRootDir: ".",
},
},
rules: {
...tsConfigRules,
"vue/multi-word-component-names": "off",
},
rules: { ...tsConfigRules, "vue/multi-word-component-names": "off" },
};
export const vueEslintConfig = [
eslint_js.configs.recommended,
...eslint_ts.configs.recommended,
...eslint_vue.configs["flat/essential"],
...pluginVue.configs["flat/essential"],
tsConfig,
vueConfig,
];
export const vueEslintConfig = [tsConfig, vueConfig];

View File

@@ -1,28 +1,17 @@
import eslint_yml from "eslint-plugin-yml";
import yml_parser from "yaml-eslint-parser";
import pluginYml from "eslint-plugin-yml";
import parserYml from "yaml-eslint-parser";
const ymlEslintConfig = {
files: ["**/*.yml", "**/*.yaml"],
plugins: {
yml: eslint_yml,
},
plugins: { yml: pluginYml },
languageOptions: {
parser: yml_parser,
parserOptions: {
defaultYAMLVersion: "1.2",
extraFileExtensions: [".yaml", ".yml"],
},
parser: parserYml,
parserOptions: { defaultYAMLVersion: "1.2", extraFileExtensions: [".yaml", ".yml"] },
},
rules: {
"yml/indent": ["error", 2],
"yml/key-spacing": ["error"],
"yml/quotes": [
"error",
{
prefer: "double",
avoidEscape: true,
},
],
"yml/quotes": ["error", { prefer: "double", avoidEscape: true }],
"yml/sort-keys": ["error", "asc"],
},
};

View File

@@ -1,7 +1,7 @@
{
"name": "TeyvatGuide",
"version": "0.6.0",
"description": "Game Tool for Genshin Impact player",
"name": "teyvatguide",
"version": "0.6.3",
"description": "Game Tool for GenshinImpact player",
"private": true,
"type": "module",
"scripts": {
@@ -66,73 +66,69 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@tauri-apps/api": "^2.0.0-rc.4",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-deep-link": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
"@tauri-apps/plugin-fs": "^2.0.0",
"@tauri-apps/plugin-http": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.1",
"@tauri-apps/plugin-fs": "^2.0.2",
"@tauri-apps/plugin-http": "^2.0.1",
"@tauri-apps/plugin-log": "^2.0.0",
"@tauri-apps/plugin-os": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0",
"@tauri-apps/plugin-sql": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.1",
"@tauri-apps/plugin-sql": "^2.0.1",
"ajv": "^8.17.1",
"artplayer": "^5.1.7",
"artplayer": "^5.2.1",
"clipboard": "^2.0.11",
"color-convert": "^2.0.1",
"echarts": "^5.5.1",
"html2canvas": "^1.4.1",
"js-md5": "^0.8.3",
"jsencrypt": "^3.3.2",
"pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.0.2",
"uuid": "^10.0.0",
"vue": "^3.5.10",
"pinia": "^2.2.6",
"pinia-plugin-persistedstate": "^4.1.3",
"uuid": "^11.0.3",
"vue": "^3.5.13",
"vue-echarts": "^7.0.3",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.4.5",
"vuetify": "^3.7.2",
"vuetify": "^3.7.4",
"wcag-color": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.11.1",
"@tauri-apps/cli": "2.0.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.15.0",
"@tauri-apps/cli": "2.1.0",
"@types/color-convert": "^2.0.4",
"@types/js-md5": "^0.7.2",
"@types/node": "^22.7.4",
"@types/node": "^22.9.0",
"@types/uuid": "^10.0.0",
"@typescript-eslint/parser": "^8.8.0",
"@vitejs/plugin-vue": "^5.1.4",
"concurrently": "^9.0.1",
"eslint": "^9.11.1",
"eslint-config-love": "^83.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-n": "^17.10.3",
"@typescript-eslint/parser": "^8.14.0",
"@vitejs/plugin-vue": "^5.2.0",
"concurrently": "^9.1.0",
"eslint": "^9.15.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsonc": "^2.18.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-vue": "^9.28.0",
"eslint-plugin-yml": "^1.14.0",
"globals": "^15.10.0",
"eslint-plugin-vue": "^9.31.0",
"eslint-plugin-yml": "^1.15.0",
"globals": "^15.12.0",
"husky": "^9.1.6",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.2.10",
"oxlint": "^0.9.9",
"oxlint": "^0.11.1",
"prettier": "3.3.3",
"stylelint": "^16.9.0",
"stylelint": "^16.10.0",
"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.2",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
"vite": "^5.4.8",
"vite-plugin-vue-devtools": "^7.4.6",
"typescript": "^5.6.3",
"typescript-eslint": "^8.14.0",
"vite": "^5.4.11",
"vite-plugin-vue-devtools": "^7.6.4",
"vite-plugin-vuetify": "^2.0.4",
"vue-eslint-parser": "^9.4.3",
"yaml-eslint-parser": "^1.2.3"

1471
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

1301
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.6.0"
version = "0.6.3"
description = "Game Tool for Genshin Impact player"
authors = ["BTMuli <bt-muli@outlook.com>"]
license = "MIT"
@@ -10,67 +10,77 @@ 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", features = [] }
tauri-build = { version = "2.0.2", features = [] }
[dependencies]
chrono = "0.4.38"
log = "0.4.22"
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
tauri = { version = "2.0.0", features = [] }
tauri-utils = "2.0.0"
url = "2.5.2"
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
tauri = { version = "2.1.1", features = [] }
tauri-utils = "2.1.0"
url = "2.5.3"
walkdir = "2.5.0"
# deep link 插件
[dependencies.tauri-plugin-deep-link]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# dialog 插件
[dependencies.tauri-plugin-dialog]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# fs 插件
[dependencies.tauri-plugin-fs]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# http 插件
[dependencies.tauri-plugin-http]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
features = ["unsafe-headers"]
# log 插件
[dependencies.tauri-plugin-log]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# os 插件
[dependencies.tauri-plugin-os]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# process 插件
[dependencies.tauri-plugin-process]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# shell 插件
[dependencies.tauri-plugin-shell]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# single-instance 插件
[dependencies.tauri-plugin-single-instance]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# sqlite 插件
[dependencies.tauri-plugin-sql]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
features = ["sqlite"]

View File

@@ -78,12 +78,15 @@
},
{
"url": "https://*.mihoyo.com/*"
},
{
"url": "https://*.genshinnet.com/*"
}
]
}
],
"remote": {
"urls": ["https://*.mihoyo.com/*", "https://*.miyoushe.com/*"]
"urls": ["https://*.mihoyo.com/*", "https://*.miyoushe.com/*", "https://*.genshinnet.com/*"]
},
"platforms": ["windows", "macOS"]
}

View File

@@ -78,6 +78,9 @@
},
{
"url": "https://*.hoyoverse.com/*"
},
{
"url": "https://*.genshinnet.com/*"
}
]
}

View File

@@ -1744,7 +1744,7 @@
"fs": {
"default_permission": {
"identifier": "default",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"permissions": [
"create-app-specific-dirs",
"read-app-specific-dirs-recursive",

File diff suppressed because one or more lines are too long

View File

@@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {
@@ -79,7 +79,7 @@
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"
@@ -130,7 +130,7 @@
"identifier": {
"anyOf": [
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},
@@ -3537,7 +3537,7 @@
"const": "dialog:deny-save"
},
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {
@@ -79,7 +79,7 @@
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"
@@ -130,7 +130,7 @@
"identifier": {
"anyOf": [
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},
@@ -3537,7 +3537,7 @@
"const": "dialog:deny-save"
},
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},

View File

@@ -1,6 +1,6 @@
//! @file src/client/mod.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.5.2
//! @since Beta v0.6.2
mod menu;
mod utils;
@@ -12,7 +12,7 @@ use tauri_utils::config::WebviewUrl;
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
let mut win_width = 400.0;
let mut win_height = 800.0;
let win_ua = "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.75.1";
let win_ua = "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.77.2";
let url_parse;
if url != "" {
url_parse = WebviewUrl::External(url.parse().unwrap());
@@ -37,6 +37,7 @@ pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
.title("米游社")
.center()
.user_agent(win_ua)
// todo mac环境下没看到menu
.menu(menu::create_mhy_menu(handle.clone()))
.on_menu_event(move |app, event| menu::handle_menu_event(app, event))
.build()

View File

@@ -1,20 +1,24 @@
//! @file src/main.rs
//! @desc 主模块,用于启动应用
//! @since Beta v0.5.2
//! @since Beta v0.6.2
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::{Emitter, Manager};
mod client;
mod commands;
mod plugins;
mod utils;
use crate::client::create_mhy_client;
use crate::commands::{create_window, execute_js, get_dir_size, init_app};
use crate::plugins::{build_log_plugin, build_si_plugin};
use tauri::{generate_context, generate_handler, Builder, Manager, Window, WindowEvent};
// 窗口事件处理
fn window_event_handler(app: &tauri::Window, event: &tauri::WindowEvent) {
fn window_event_handler(app: &Window, event: &WindowEvent) {
match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
if app.label() == "TeyvatGuide" {
// 子窗口 label 的数组
@@ -33,11 +37,9 @@ fn window_event_handler(app: &tauri::Window, event: &tauri::WindowEvent) {
}
fn main() {
tauri::Builder::default()
Builder::default()
.on_window_event(move |app, event| window_event_handler(app, event))
.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| {
app.emit("active_deep_link", argv).unwrap();
}))
.plugin(build_si_plugin())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
@@ -46,7 +48,7 @@ fn main() {
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_sql::Builder::default().build())
.plugin(plugins::build_log_plugin())
.plugin(build_log_plugin())
.setup(|_app| {
let _window = _app.get_webview_window("TeyvatGuide");
#[cfg(debug_assertions)]
@@ -55,13 +57,13 @@ fn main() {
}
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::init_app,
commands::create_window,
commands::execute_js,
commands::get_dir_size,
client::create_mhy_client,
.invoke_handler(generate_handler![
init_app,
create_window,
execute_js,
get_dir_size,
create_mhy_client
])
.run(tauri::generate_context!())
.run(generate_context!())
.expect("error while running tauri application");
}

View File

@@ -1,28 +1,33 @@
//! @file src/plugins.rs
//! @desc 插件模块,用于注册插件
//! @since Beta v0.5.3
//! @since Beta v0.6.2
use super::utils;
use crate::utils::get_current_date;
use log::LevelFilter;
use tauri::plugin::TauriPlugin;
use tauri::Runtime;
use tauri_plugin_log::{Target, TargetKind, TimezoneStrategy};
use tauri::{Emitter, Runtime};
use tauri_plugin_log::{Builder, Target, TargetKind, TimezoneStrategy};
use tauri_plugin_single_instance::init;
// 单例插件
pub fn build_si_plugin<R: Runtime>() -> TauriPlugin<R> {
init(move |app, argv, _cwd| app.emit("active_deep_link", argv).unwrap())
}
// 日志插件
pub fn build_log_plugin<R: Runtime>() -> TauriPlugin<R> {
if cfg!(debug_assertions) {
return tauri_plugin_log::Builder::default()
.targets([Target::new(TargetKind::Webview)])
.timezone_strategy(TimezoneStrategy::UseLocal)
.level(LevelFilter::Debug)
.build();
}
tauri_plugin_log::Builder::default()
#[cfg(debug_assertions)]
Builder::default()
.targets([Target::new(TargetKind::Webview)])
.timezone_strategy(TimezoneStrategy::UseLocal)
.level(LevelFilter::Debug)
.build::<R>();
Builder::default()
.targets([
Target::new(TargetKind::Webview),
Target::new(TargetKind::LogDir { file_name: utils::get_current_date().into() }),
Target::new(TargetKind::LogDir { file_name: get_current_date().into() }),
])
.timezone_strategy(TimezoneStrategy::UseLocal)
.level(LevelFilter::Info)
.build()
.build::<R>()
}

View File

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

View File

@@ -21,7 +21,7 @@ import { useRouter } from "vue-router";
import TBackTop from "./components/app/t-backTop.vue";
import TSidebar from "./components/app/t-sidebar.vue";
import showConfirm from "./components/func/confirm.js";
import showDialog from "./components/func/dialog.js";
import showSnackbar from "./components/func/snackbar.js";
import TGSqlite from "./plugins/Sqlite/index.js";
import TSUserAccount from "./plugins/Sqlite/modules/userAccount.js";
@@ -29,7 +29,7 @@ import { useAppStore } from "./store/modules/app.js";
import { useUserStore } from "./store/modules/user.js";
import { getBuildTime } from "./utils/TGBuild.js";
import TGLogger from "./utils/TGLogger.js";
import TGRequest from "./web/request/TGRequest.js";
import OtherApi from "./web/request/otherReq.js";
const appStore = useAppStore();
const userStore = storeToRefs(useUserStore());
@@ -60,7 +60,7 @@ onBeforeMount(async () => {
async function checkResize(): Promise<void> {
const screen = await TauriWindow.currentMonitor();
if (screen === null) {
showSnackbar({ text: "获取屏幕信息失败!", color: "error", timeout: 3000 });
showSnackbar.error("获取屏幕信息失败!", 3000);
return;
}
const windowCur = await webviewWindow.getCurrentWebviewWindow();
@@ -100,6 +100,7 @@ onMounted(async () => {
function listenOnInit(): void {
console.info("[App][listenOnInit] 监听初始化事件!");
event.listen("initApp", async () => {
await checkAppLoad();
await checkDeviceFp();
try {
await checkUserLoad();
@@ -112,6 +113,19 @@ function listenOnInit(): void {
});
}
async function checkAppLoad(): Promise<void> {
let checkDB = false;
try {
checkDB = await TGSqlite.check();
} catch (error) {
if (error instanceof Error) {
await TGLogger.Error(`[App][checkAppLoad] ${error.name}: ${error.message}`);
} else console.error(error);
}
if (!checkDB) await TGSqlite.update();
else await TGLogger.Info("[App][checkAppLoad] 数据库已成功加载!");
}
// 检测 deviceFp
async function checkDeviceFp(): Promise<void> {
const appData = await TGSqlite.getAppData();
@@ -119,7 +133,7 @@ async function checkDeviceFp(): Promise<void> {
const deviceFind = appData.find((item) => item.key === "deviceInfo");
if (typeof deviceFind === "undefined") {
if (deviceLocal.device_fp === "0000000000000") {
appStore.deviceInfo = await TGRequest.Device.getFp(appStore.deviceInfo);
appStore.deviceInfo = await OtherApi.fp(appStore.deviceInfo);
}
await TGSqlite.saveAppData("deviceInfo", JSON.stringify(deviceLocal));
return;
@@ -138,17 +152,19 @@ async function checkUserLoad(): Promise<void> {
await mkdir(appStore.userDir, { recursive: true });
// 检测用户数据
const uidDB = await TSUserAccount.account.getAllUid();
if (uidDB.length === 0) {
showSnackbar({ text: "未检测到可用UID请重新登录!", color: "warn" });
if (uidDB.length === 0 && appStore.isLogin) {
showSnackbar.warn("未检测到可用UID请重新登录");
appStore.isLogin = false;
return;
}
if (!appStore.isLogin) appStore.isLogin = true;
// 然后获取最近的UID
if (userStore.uid.value === undefined || !uidDB.includes(userStore.uid.value)) {
userStore.uid.value = uidDB[0];
}
const curAccount = await TSUserAccount.account.getAccount(userStore.uid.value);
if (curAccount === false) {
showSnackbar({ text: `未获取到${userStore.uid.value}账号数据`, color: "error" });
showSnackbar.error(`未获取到${userStore.uid.value}账号数据`);
await TGLogger.Error(`[App][listenOnInit] 获取${userStore.uid.value}账号数据失败`);
await new Promise((resolve) => setTimeout(resolve, 1000));
} else {
@@ -157,7 +173,7 @@ async function checkUserLoad(): Promise<void> {
}
const curGameAccount = await TSUserAccount.game.getCurAccount(userStore.uid.value);
if (curGameAccount === false) {
showSnackbar({ text: `未获取到${userStore.uid.value}游戏数据`, color: "error" });
showSnackbar.error(`未获取到${userStore.uid.value}游戏数据`);
await TGLogger.Error(`[App][listenOnInit] 获取${userStore.uid.value}游戏数据失败`);
await new Promise((resolve) => setTimeout(resolve, 1000));
} else {
@@ -168,13 +184,11 @@ async function checkUserLoad(): Promise<void> {
async function getDeepLink(): Promise<UnlistenFn> {
return await event.listen("active_deep_link", async (e: Event<string>) => {
const windowGet = new webviewWindow.WebviewWindow("TeyvatGuide");
if (await windowGet.isMinimized()) {
await windowGet.unminimize();
}
if (await windowGet.isMinimized()) await windowGet.unminimize();
await windowGet.setFocus();
const payload = parseDeepLink(e.payload);
if (payload === false) {
showSnackbar({ text: "无效的 deep link", color: "error", timeout: 3000 });
showSnackbar.error("无效的 deep link", 3000);
await TGLogger.Error(`[App][getDeepLink] 无效的 deep link ${JSON.stringify(e.payload)}`);
return;
}
@@ -208,7 +222,7 @@ async function handleDeepLink(payload: string): Promise<void> {
if (payload.startsWith("router?path=")) {
const routerPath = payload.replace("router?path=", "");
if (router.currentRoute.value.path === routerPath) {
showSnackbar({ text: "已在当前页面!", color: "warn", timeout: 3000 });
showSnackbar.warn("已在当前页面!", 3000);
return;
}
await router.push(routerPath);
@@ -228,26 +242,20 @@ async function toUIAF(link: string) {
// 检测更新
async function checkUpdate(): Promise<void> {
// @ts-expect-error-next-line
const isProdEnv = import.meta.env.MODE === "production";
const needUpdate = await TGSqlite.checkUpdate();
if (needUpdate && isProdEnv) {
await TGLogger.Info("[App][checkUpdate] 检测到版本更新!");
const confirm = await showConfirm({
title: "检测到版本更新",
text: "是否更新数据库数据?(请确保成就数据已导出)",
});
if (!confirm) {
showSnackbar({
text: "请到设置页手动更新数据库!",
color: "error",
timeout: 3000,
});
const updateCheck = await showDialog.check("检测到版本更新", "是否更新数据库数据?");
if (!updateCheck) {
showSnackbar.error("请到设置页手动更新数据库!", 3000);
return;
}
appStore.buildTime = getBuildTime();
await TGSqlite.update();
showSnackbar({ text: "数据库已更新!", color: "success", timeout: 3000 });
window.open("https://app.btmuli.ink/docs/Changelogs.html");
showSnackbar.success("数据库已更新!", 3000);
window.open("https://app.btmuli.ink/docs/TeyvatGuide/changelogs.html");
}
}

View File

@@ -56,6 +56,7 @@ onUnmounted(() => {
position: fixed;
right: 10px;
bottom: 10px;
width: 40px;
height: 40px;
border-radius: 50%;
transition: all 0.3s ease-in-out;

View File

@@ -19,10 +19,11 @@ import { useAppStore } from "../../store/modules/app.js";
import TGClient from "../../utils/TGClient.js";
import TGLogger from "../../utils/TGLogger.js";
import { createPost } from "../../utils/TGWindow.js";
import TGRequest from "../../web/request/TGRequest.js";
import showConfirm from "../func/confirm.js";
import OtherApi from "../../web/request/otherReq.js";
import showDialog from "../func/dialog.js";
import showSnackbar from "../func/snackbar.js";
import ToLivecode from "../overlay/to-livecode.vue";
import ToLivecode from "./to-livecode.vue";
interface TGameNavProps {
modelValue: number;
@@ -51,7 +52,7 @@ watch(
);
async function loadNav(): Promise<void> {
nav.value = await Mys.Posts.nav(props.modelValue);
nav.value = await Mys.ApiHub.homeNew(props.modelValue);
}
async function tryGetCode(): Promise<void> {
@@ -60,27 +61,24 @@ async function tryGetCode(): Promise<void> {
if (!navFind) return;
const actIdFind = new URL(navFind.app_path).searchParams.get("act_id");
if (!actIdFind) {
showSnackbar({ text: "未找到活动ID", color: "warn" });
showSnackbar.warn("未找到活动ID");
return;
}
actId.value = actIdFind;
const res = await TGRequest.Nav.getCode(actIdFind);
const res = await OtherApi.code(actIdFind);
if (!Array.isArray(res)) {
showSnackbar({ text: `[${res.retcode}] ${res.message}`, color: "warn" });
showSnackbar.warn(`[${res.retcode}] ${res.message}`);
return;
}
codeData.value = res;
showSnackbar({ text: "获取兑换码成功", color: "success" });
showSnackbar.success("获取兑换码成功");
await TGLogger.Info(JSON.stringify(res));
showOverlay.value = true;
}
async function toNav(item: TGApp.BBS.Navigator.Navigator): Promise<void> {
if (!appStore.isLogin) {
showSnackbar({
text: "请先登录",
color: "warn",
});
showSnackbar.warn("请先登录");
return;
}
await TGLogger.Info(`[TGameNav][toNav] 打开网页活动 ${item.name}`);
@@ -106,19 +104,13 @@ async function toNav(item: TGApp.BBS.Navigator.Navigator): Promise<void> {
await TGClient.open("web_act_thin", item.app_path);
return;
}
const modeConfirm = await showConfirm({
title: "是否采用宽屏模式打开?",
text: "取消则采用竖屏模式打开",
});
if (modeConfirm === undefined) {
showSnackbar({
text: "已取消打开",
color: "cancel",
});
const modeCheck = await showDialog.check("是否采用宽屏模式打开?", "取消则采用竖屏模式打开");
if (modeCheck === undefined) {
showSnackbar.cancel("取消打开");
return;
}
if (modeConfirm) await TGClient.open("web_act", item.app_path);
else await TGClient.open("web_act_thin", item.app_path);
if (!modeCheck) await TGClient.open("web_act_thin", item.app_path);
else await TGClient.open("web_act", item.app_path);
}
// protocol
@@ -133,20 +125,14 @@ async function toBBS(link: URL): Promise<void> {
const forumId = link.pathname.split("/").pop();
const localPath = getLocalPath(forumId);
if (localPath === "") {
showSnackbar({
text: `不支持的链接:${link.href}`,
color: "warn",
});
showSnackbar.warn(`不支持的链接:${link.href}`);
return;
}
await emit("active_deep_link", `router?path=${localPath}`);
return;
}
}
showSnackbar({
text: `不支持的链接:${link.href}`,
color: "warn",
});
showSnackbar.warn(`不支持的链接:${link.href}`);
}
function getLocalPath(forum?: string): string {
@@ -167,13 +153,13 @@ function getLocalPath(forum?: string): string {
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}`;
if (ysForums.includes(forum)) return `/posts/forum/2/${forum}`;
if (srForums.includes(forum)) return `/posts/forum/6/${forum}`;
if (bh3Forums.includes(forum)) return `/posts/forum/1/${forum}`;
if (bh2Forums.includes(forum)) return `/posts/forum/3/${forum}`;
if (wdForums.includes(forum)) return `/posts/forum/4/${forum}`;
if (zzzForums.includes(forum)) return `/posts/forum/8/${forum}`;
if (dbyForums.includes(forum)) return `/posts/forum/5/${forum}`;
return "";
}
</script>

View File

@@ -1,93 +1,38 @@
<template>
<div
class="tib-box"
:style="{
width: modelValue.size,
height: modelValue.height,
cursor: modelValue.clickable ? 'pointer' : 'default',
}"
>
<div
class="tib-bg"
:style="{
width: modelValue.size,
height: modelValue.size,
}"
>
<div class="tib-box">
<div class="tib-bg">
<slot name="bg">
<img :src="modelValue.bg" alt="bg" />
<img :src="props.modelValue.bg" alt="bg" />
</slot>
</div>
<div
class="tib-icon"
:style="{
width: modelValue.size,
height: modelValue.size,
}"
>
<div class="tib-icon">
<slot name="icon">
<img :src="modelValue.icon" alt="icon" />
<img :src="props.modelValue.icon" alt="icon" />
</slot>
</div>
<div
class="tib-cover"
:style="{
width: modelValue.size,
height: modelValue.size,
}"
>
<div
class="tib-lt"
:style="{
width: modelValue.ltSize,
height: modelValue.ltSize,
}"
>
<img :src="modelValue.lt" alt="lt" />
<div class="tib-cover">
<div class="tib-lt">
<img :src="props.modelValue.lt" alt="lt" />
</div>
<div
v-show="modelValue.rt"
class="tib-rt"
:style="{
width: modelValue.rtSize,
height: modelValue.rtSize,
}"
>
{{ modelValue.rt }}
<div v-show="props.modelValue.rt" class="tib-rt">
{{ props.modelValue.rt }}
</div>
<div
class="tib-inner"
:style="{
height: `${props.modelValue.innerHeight ?? 0}px`,
fontSize: `${props.modelValue.innerHeight ? props.modelValue.innerHeight / 2 : 0}px`,
}"
>
<div class="tib-inner">
<slot name="inner-icon">
<img
v-show="modelValue.innerIcon"
:src="modelValue.innerIcon"
v-show="props.modelValue.innerIcon"
:src="props.modelValue.innerIcon"
alt="inner-icon"
:style="{
width: `${props.modelValue.innerHeight ?? 0}px`,
height: `${props.modelValue.innerHeight ?? 0}px`,
}"
/>
</slot>
<slot name="inner-text">
<span :title="modelValue.innerText">{{ modelValue.innerText }}</span>
<span :title="props.modelValue.innerText">{{ props.modelValue.innerText }}</span>
</slot>
</div>
</div>
<div
v-if="modelValue.display === 'outer'"
class="tib-outer"
:style="{
height: `${props.modelValue.outerHeight ?? 0}px`,
fontSize: `${props.modelValue.outerHeight ? props.modelValue.outerHeight / 2 : 0}px`,
}"
>
<div v-if="props.modelValue.display === 'outer'" class="tib-outer">
<slot name="outer-text">
<span>{{ modelValue.outerText }}</span>
<span>{{ props.modelValue.outerText }}</span>
</slot>
</div>
</div>
@@ -109,6 +54,7 @@ export interface TItemBoxData {
innerText: string;
outerHeight?: number;
outerText?: string;
innerBlur?: string;
}
interface TItemBoxProps {
@@ -116,10 +62,23 @@ interface TItemBoxProps {
}
const props = defineProps<TItemBoxProps>();
const size = props.modelValue.size;
const height = props.modelValue.height;
const cursor = props.modelValue.clickable ? "pointer" : "default";
const sizeLt = props.modelValue.ltSize;
const sizeRt = props.modelValue.rtSize;
const sizeInner = `${props.modelValue.innerHeight ?? 0}px`;
const fontSizeInner = props.modelValue.innerHeight ? `${props.modelValue.innerHeight / 2}px` : "0";
const sizeOuter = `${props.modelValue.outerHeight ?? 0}px`;
const fontSizeOuter = props.modelValue.outerHeight ? `${props.modelValue.outerHeight / 2}px` : "0";
const innerBlur = props.modelValue.innerBlur ?? "0";
</script>
<style lang="css" scoped>
.tib-box {
position: relative;
width: v-bind(size);
height: v-bind(height);
cursor: v-bind(cursor);
}
.tib-bg {
@@ -127,6 +86,8 @@ const props = defineProps<TItemBoxProps>();
top: 0;
left: 0;
overflow: hidden;
width: v-bind(size);
height: v-bind(size);
border-radius: 5px;
}
@@ -139,6 +100,8 @@ const props = defineProps<TItemBoxProps>();
.tib-icon {
position: relative;
overflow: hidden;
width: v-bind(size);
height: v-bind(size);
border-radius: 5px;
}
@@ -153,6 +116,8 @@ const props = defineProps<TItemBoxProps>();
top: 0;
left: 0;
display: flex;
width: v-bind(size);
height: v-bind(size);
flex-direction: column;
align-items: center;
justify-content: center;
@@ -164,6 +129,8 @@ const props = defineProps<TItemBoxProps>();
top: 3%;
left: 3%;
display: flex;
width: v-bind(sizeLt);
height: v-bind(sizeLt);
align-items: center;
justify-content: center;
}
@@ -179,6 +146,8 @@ const props = defineProps<TItemBoxProps>();
top: 0;
right: 0;
display: flex;
width: v-bind(sizeRt);
height: v-bind(sizeRt);
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 40%);
@@ -194,16 +163,22 @@ const props = defineProps<TItemBoxProps>();
left: 0;
display: flex;
width: 100%;
height: v-bind(sizeInner);
align-items: center;
justify-content: center;
-webkit-backdrop-filter: blur(v-bind(innerBlur));
backdrop-filter: blur(v-bind(innerBlur));
background: rgb(20 20 20 / 40%);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
color: var(--tgc-white-1);
font-family: var(--font-title);
font-size: v-bind(fontSizeInner);
}
.tib-inner img {
width: v-bind(sizeInner);
height: v-bind(sizeInner);
margin-right: 5px;
}
@@ -219,9 +194,11 @@ const props = defineProps<TItemBoxProps>();
bottom: 0;
display: flex;
width: 100%;
height: v-bind(sizeOuter);
align-items: center;
justify-content: center;
color: var(--common-text-title);
font-size: v-bind(fontSizeOuter);
text-align: center;
}
</style>

View File

@@ -24,8 +24,7 @@ onMounted(async () => {
async function switchPin(): Promise<void> {
isPined.value = !isPined.value;
await getCurrentWindow().setAlwaysOnTop(isPined.value);
const text = isPined.value ? "已将窗口置顶!" : "已经取消窗口置顶!";
showSnackbar({ text: text, color: "success" });
showSnackbar.success(isPined.value ? "已将窗口置顶!" : "已经取消窗口置顶!");
}
</script>
<style lang="css" scoped>

View File

@@ -1,21 +1,31 @@
<template>
<div v-if="card" :id="`post-card-${card.postId}`" class="tpc-card">
<div class="tpc-cover">
<img :src="card.cover" alt="cover" @click="createPost(card)" />
<div v-if="isAct" class="tpc-act">
<div class="tpc-status" :style="{ background: card.status?.colorCss }">
{{ card.status?.status }}
</div>
<div class="tpc-time">
<v-icon>mdi-clock-time-four-outline</v-icon>
<span>{{ card.subtitle }}</span>
<div class="tpc-top">
<div class="tpc-cover" @click="createPost(card)">
<img :src="localCover" alt="cover" v-if="localCover" />
<v-progress-circular color="primary" :indeterminate="true" v-else-if="card.cover !== ''" />
<img src="/source/UI/defaultCover.webp" alt="cover" v-else />
<div v-if="isAct" class="tpc-act">
<div class="tpc-status">{{ card.status?.status }}</div>
<div class="tpc-time">
<v-icon>mdi-clock-time-four-outline</v-icon>
<span>{{ card.subtitle }}</span>
</div>
</div>
</div>
</div>
<div class="tpc-content">
<div class="tpc-title" :title="card.title" @click="shareCard">{{ card.title }}</div>
<TpAvatar v-if="card.user" :data="card.user" position="left" />
<div class="tpc-data" v-if="card.data">
</div>
<div class="tpc-mid" v-if="card.user">
<TpAvatar :data="card.user" position="left" />
</div>
<div class="tpc-bottom" v-if="card.data">
<div class="tpc-tags">
<div v-for="topic in card.topics" :key="topic.id" class="tpc-tag" @click="toTopic(topic)">
<v-icon size="10">mdi-tag</v-icon>
<span>{{ topic.name }}</span>
</div>
</div>
<div class="tpc-data">
<div class="tpc-info-item" :title="`浏览数:${card.data.view}`">
<v-icon>mdi-eye</v-icon>
<span>{{ card.data.view }}</span>
@@ -42,6 +52,7 @@
class="tpc-forum"
v-if="card.forum && card.forum.name !== ''"
:title="`频道: ${card.forum.name}`"
@click="toForum(card.forum)"
>
<img :src="card.forum.icon" :alt="card.forum.name" />
<span>{{ card.forum.name }}</span>
@@ -49,28 +60,27 @@
<v-checkbox-btn
v-if="props.selectMode"
class="tpc-select"
v-model="selectedList"
:value="props.modelValue.post.post_id"
@click="emits('onSelected', props.modelValue.post.post_id)"
data-html2canvas-ignore
/>
<div class="tpc-info-id" v-else>{{ props.modelValue.post.post_id }}</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onBeforeMount, ref } from "vue";
import { emit } from "@tauri-apps/api/event";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { generateShareImg } from "../../utils/TGShare.js";
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
import { createPost } from "../../utils/TGWindow.js";
import TpAvatar from "../post/tp-avatar.vue";
import TpAvatar from "../viewPost/tp-avatar.vue";
interface TPostCardProps {
modelValue: TGApp.Plugins.Mys.Post.FullData;
selectMode?: boolean;
selected?: string[];
}
interface TPostCardEmits {
(e: "update:selected", value: string[]): void;
(e: "onSelected", value: string): void;
}
const props = withDefaults(defineProps<TPostCardProps>(), {
@@ -79,16 +89,33 @@ const props = withDefaults(defineProps<TPostCardProps>(), {
const emits = defineEmits<TPostCardEmits>();
const isAct = ref<boolean>(false);
const card = ref<TGApp.Plugins.Mys.News.RenderCard>();
const selectedList = computed({
get: () => props.selected,
set: (v) => {
if (v === undefined) return;
emits("update:selected", v);
},
const localCover = ref<string>();
const cardBg = computed<string>(() => {
if (card.value && card.value.status) return card.value.status.colorCss;
return "none";
});
onBeforeMount(() => {
card.value = getPostCard(props.modelValue);
onMounted(async () => await reload(props.modelValue));
watch(() => props.modelValue, reload);
async function reload(data: TGApp.Plugins.Mys.Post.FullData): Promise<void> {
if (localCover.value) {
URL.revokeObjectURL(localCover.value);
localCover.value = undefined;
}
card.value = getPostCard(data);
if (card.value && card.value.cover !== "") {
localCover.value = await saveImgLocal(card.value.cover);
}
}
onUnmounted(() => {
if (localCover.value) {
URL.revokeObjectURL(localCover.value);
localCover.value = undefined;
}
});
/**
@@ -138,15 +165,7 @@ function getActivityStatus(status: number): TGApp.Plugins.Mys.News.RenderStatus
}
}
/**
* @description 获取封面图
* @since Beta v0.4.5
* @param {TGApp.Plugins.Mys.Post.FullData} item 咨讯列表项
* @returns {string} 封面图链接
*/
function getPostCover(item: TGApp.Plugins.Mys.Post.FullData): string {
//
const defaultCover = "/source/UI/defaultCover.webp";
let cover;
if (item.cover) {
cover = item.cover.url;
@@ -155,24 +174,25 @@ function getPostCover(item: TGApp.Plugins.Mys.Post.FullData): string {
} else if (item.post.images.length > 0) {
cover = item.post.images[0];
}
if (cover === undefined) return defaultCover;
if (cover === undefined) return "";
if (cover.endsWith(".gif")) return cover;
return `${cover}?x-oss-process=image/format,png`;
}
/**
* @description 获取公共属性
* @since Beta v0.4.5
* @since Beta v0.6.1
* @param {TGApp.Plugins.Mys.Post.FullData} item 咨讯列表项
* @returns {TGApp.Plugins.Mys.News.RenderCard} 渲染用咨讯列表项
*/
function getCommonCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.News.RenderCard {
let forum = null;
let data = null;
let forum: TGApp.Plugins.Mys.News.RenderForum | null = null;
let data: TGApp.Plugins.Mys.News.RenderData | null = null;
if (item.forum !== null) {
forum = {
name: item.forum.name,
icon: item.forum.icon,
id: item.forum.id,
};
}
if (item.stat !== null) {
@@ -192,6 +212,7 @@ function getCommonCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys
user: item.user,
forum: forum,
data: data,
topics: item.topics,
};
}
@@ -218,15 +239,41 @@ async function shareCard(): Promise<void> {
const fileName = `PostCard_${card.value.postId}`;
await generateShareImg(fileName, dom, 2.5);
}
async function toTopic(topic: TGApp.Plugins.Mys.Topic.Info): Promise<void> {
const gid = props.modelValue.post.game_id;
await emit("active_deep_link", `router?path=/posts/topic/${gid}/${topic.id}`);
}
async function toForum(forum: TGApp.Plugins.Mys.News.RenderForum): Promise<void> {
const gid = props.modelValue.post.game_id;
await emit("active_deep_link", `router?path=/posts/forum/${gid}/${forum.id}`);
}
</script>
<style lang="css" scoped>
.tpc-card {
position: relative;
display: flex;
overflow: hidden;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: space-between;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
background: var(--box-bg-1);
box-shadow: 2px 2px 5px var(--common-shadow-2);
row-gap: 10px;
}
.tpc-top {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
row-gap: 5px;
}
.tpc-cover {
@@ -238,6 +285,7 @@ async function shareCard(): Promise<void> {
justify-content: center;
aspect-ratio: 36 / 13;
background: var(--common-shadow-2);
cursor: pointer;
}
.tpc-cover img {
@@ -247,18 +295,25 @@ async function shareCard(): Promise<void> {
transition: all 0.3s linear;
}
.tpc-content {
.tpc-mid {
position: relative;
width: 100%;
padding: 0 10px;
}
.tpc-bottom {
position: relative;
display: flex;
width: 100%;
flex-direction: column;
padding: 10px;
gap: 10px;
padding: 5px 10px;
row-gap: 5px;
}
.tpc-title {
overflow: hidden;
width: 100%;
padding: 5px 10px;
cursor: pointer;
font-family: var(--font-title);
font-size: 18px;
@@ -266,6 +321,32 @@ async function shareCard(): Promise<void> {
white-space: nowrap;
}
.tpc-tags {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
color: var(--box-text-5);
cursor: pointer;
font-size: 12px;
gap: 5px;
:hover {
color: var(--box-text-3);
}
}
.tpc-tag {
display: flex;
align-items: center;
justify-content: center;
padding: 0 3px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
background: var(--box-bg-2);
gap: 3px;
}
.tpc-forum {
position: absolute;
top: 0;
@@ -280,6 +361,7 @@ async function shareCard(): Promise<void> {
border-bottom-left-radius: 5px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
cursor: pointer;
text-shadow: 0 0 5px var(--tgc-dark-1);
}
@@ -294,9 +376,10 @@ async function shareCard(): Promise<void> {
justify-content: center;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: var(--tgc-yellow-2);
background: var(--box-bg-2);
border-bottom-right-radius: 4px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--box-text-5);
}
.tpc-forum img {
@@ -306,7 +389,6 @@ async function shareCard(): Promise<void> {
}
.tpc-cover img:hover {
cursor: pointer;
transform: scale(1.1);
transition: all 0.3s linear;
}
@@ -351,6 +433,7 @@ async function shareCard(): Promise<void> {
align-items: center;
justify-content: flex-start;
padding: 5px 30px 5px 5px;
background-color: v-bind(cardBg);
clip-path: polygon(0 0, calc(100% - 15px) 0, 100% 50%, calc(100% - 15px) 100%, 0 100%);
color: var(--tgc-white-1);

View File

@@ -1,29 +1,25 @@
<template>
<div class="share-box" title="分享">
<div class="share-btn" @click="shareContent()">
<v-icon> mdi-share-variant</v-icon>
<v-icon>mdi-share-variant</v-icon>
</div>
</div>
</template>
<script lang="ts" setup>
// utils
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import showLoading from "../func/loading.js";
interface TShareBtnProps {
modelValue: HTMLElement;
title: string;
loading: boolean;
}
type TShareBtnEmits = (e: "update:loading", value: boolean) => void;
const props = defineProps<TShareBtnProps>();
const emit = defineEmits<TShareBtnEmits>();
async function shareContent(): Promise<void> {
showLoading.start("正在生成分享图片", props.title);
await TGLogger.Info("[TShareBtn][shareContent] 开始生成分享图片");
emit("update:loading", true);
props.modelValue.querySelectorAll("details").forEach((item) => {
if (item.open) {
item.setAttribute("details-open", "");
@@ -39,7 +35,7 @@ async function shareContent(): Promise<void> {
item.open = false;
}
});
emit("update:loading", false);
showLoading.end();
await TGLogger.Info("[TShareBtn][shareContent] 生成分享图片完成");
}
</script>

View File

@@ -38,7 +38,7 @@
<img src="/platforms/mhy/mys.webp" alt="mihoyo" class="side-icon" />
</template>
</v-list-item>
<v-list-item :title.attr="'帖子'" value="posts" :link="true" href="/posts">
<v-list-item :title.attr="'帖子'" value="posts" :link="true" href="/posts/forum">
<template #title>帖子</template>
<template #prepend>
<img src="/source/UI/posts.png" alt="posts" class="side-icon" />

View File

@@ -1,5 +1,11 @@
<template>
<TOverlay v-model="visible" :hide="true" :to-click="onCancel" blur-val="20px">
<TOverlay
v-model="visible"
:hide="true"
:to-click="onCancel"
blur-val="20px"
class="tolc-overlay"
>
<div class="tolc-box">
<div class="tolc-title">
<span>兑换码</span>
@@ -49,7 +55,8 @@ import { computed } from "vue";
import { generateShareImg } from "../../utils/TGShare.js";
import { timestampToDate } from "../../utils/toolFunc.js";
import showSnackbar from "../func/snackbar.js";
import TOverlay from "../main/t-overlay.vue";
import TOverlay from "./t-overlay.vue";
interface ToLiveCodeProps {
data: TGApp.BBS.Navigator.CodeData[];
@@ -75,7 +82,7 @@ function onCancel(): void {
function copy(code: string): void {
navigator.clipboard.writeText(code);
showSnackbar({ text: "已复制到剪贴板", color: "success" });
showSnackbar.success("已复制到剪贴板");
}
async function shareImg(): Promise<void> {
@@ -85,6 +92,10 @@ async function shareImg(): Promise<void> {
}
</script>
<style lang="css" scoped>
.tolc-overlay {
height: 100vh;
}
.tolc-box {
position: relative;
width: 340px;

View File

@@ -29,7 +29,8 @@
import { computed, ref } from "vue";
import { generateShareImg } from "../../utils/TGShare.js";
import TOverlay from "../main/t-overlay.vue";
import TOverlay from "./t-overlay.vue";
interface ToNamecardProps {
modelValue: boolean;

View File

@@ -1,67 +0,0 @@
/**
* @file component/func/confirm.ts
* @description 封装自定义 confirm 组件,通过函数调用的方式,简化 confirm 的使用
* @since Beta v0.3.9
*/
import { h, render } from "vue";
import type { ComponentInternalInstance, VNode } from "vue";
import confirm from "./confirm.vue";
const confirmId = "tg-func-confirm";
/**
* @description 自定义 confirm 组件
* @since Beta v0.3.4
* @extends ComponentInternalInstance
* @property {Function} exposeProxy.displayBox 显示 confirm
* @return ConfirmInstance
*/
interface ConfirmInstance extends ComponentInternalInstance {
exposeProxy: {
displayBox: (props: TGApp.Component.Confirm.Params) => Promise<string | boolean>;
};
}
const renderBox = (props: TGApp.Component.Confirm.Params): VNode => {
const container = document.createElement("div");
container.id = confirmId;
const boxVNode: VNode = h(confirm, props);
render(boxVNode, container);
document.body.appendChild(container);
return boxVNode;
};
let confirmInstance: VNode;
/**
* @function showConfirm
* @since Beta v0.3.9
* @todo 重载重构
* @description 弹出 confirm
* @param {TGApp.Component.Confirm.Params} props confirm 的参数
* @return {Promise<string | boolean | undefined>} 点击确认返回 true点击取消返回 false点击外部返回 undefined
*/
async function showConfirm(
props: TGApp.Component.Confirm.ParamsConfirm,
): Promise<boolean | undefined>;
async function showConfirm(
props: TGApp.Component.Confirm.ParamsInput,
): Promise<string | false | undefined>;
async function showConfirm(
props: TGApp.Component.Confirm.Params,
): Promise<string | boolean | undefined>;
async function showConfirm(
props: TGApp.Component.Confirm.Params,
): Promise<string | boolean | undefined> {
if (confirmInstance !== undefined) {
const boxVue = <ConfirmInstance>confirmInstance.component;
return await boxVue.exposeProxy.displayBox(props);
} else {
confirmInstance = renderBox(props);
return await showConfirm(props);
}
}
export default showConfirm;

Some files were not shown because too many files have changed in this diff Show More