Compare commits

..

41 Commits

Author SHA1 Message Date
BTMuli
80e27a20a7 🚀 v0.3.6 2023-11-28 15:19:05 +08:00
BTMuli
26d1883d98 添加 isLogin,用以判断是否登录 2023-11-28 15:15:36 +08:00
BTMuli
91fd375263 保留旧版,默认新版,旧版增加左右切换
close #20
2023-11-25 20:40:59 +08:00
BTMuli
3b0ed774df 🐛 修复分享图生成错误 2023-11-25 20:27:30 +08:00
BTMuli
5f3f6640a4 💩 分享图生成错误 2023-11-25 17:15:52 +08:00
BTMuli
b1424fb582 🌱 完成左下命座显示 2023-11-25 16:34:12 +08:00
BTMuli
f64b48c356 🌱 完成右侧天赋展示 4/5
#20
2023-11-25 16:21:34 +08:00
BTMuli
90242829a9 🌱 角色详情页面初步改造 3/5
#20
2023-11-25 00:05:45 +08:00
BTMuli
d6ae3765b6 💄 微调样式 2023-11-21 16:23:14 +08:00
BTMuli
92a1775d19 🐛 修复顶部名片渲染错误 2023-11-21 15:49:00 +08:00
BTMuli
fd3822fe70 ♻️ 列表渲染改成原生,降低性能消耗 2023-11-21 15:23:51 +08:00
BTMuli
618d3a1632 🚸 应用加载时关闭隐藏的子窗口 2023-11-21 13:53:18 +08:00
BTMuli
1c3b73bde9 ️ 优化 hint 2023-11-18 22:13:02 +08:00
BTMuli
c541d67abc 隐藏已完成成就
close #19
2023-11-18 22:03:36 +08:00
BTMuli
1d3b1ae78e 完成单个成就状态修改
close #60
2023-11-18 21:51:04 +08:00
BTMuli
312436b4e2 💄 增加 icon 清晰度 2023-11-18 21:01:47 +08:00
BTMuli
d98663dccb 🐛 完善数据库检测机制
fix #62
2023-11-18 20:46:14 +08:00
BTMuli
653073e684 🚨 修复 qodana 报错 2023-11-18 12:29:35 +08:00
BTMuli
1d09f4817b ️ 调整部分 ui 2023-11-18 00:28:12 +08:00
BTMuli
6ca5de28ac 🌱 初步迁移非重复数据库操作 #60 2023-11-17 23:47:31 +08:00
BTMuli
ff649c2426 🐛 初始化数据库仅保留手动方式 #62 2023-11-17 23:24:17 +08:00
BTMuli
1c5bebf75e ️ 输入框自动聚焦,回车自动确认 2023-11-17 23:22:32 +08:00
BTMuli
b60718aa62 ️ 优化 share 样式 2023-11-17 22:26:29 +08:00
BTMuli
c018638e4a 💄 微调样式 2023-11-17 21:55:31 +08:00
BTMuli
1906e911c7 💄 调整 finish icon color 2023-11-17 21:12:53 +08:00
BTMuli
fbb66b3964 🎨 内存不是问题x 2023-11-17 18:05:29 +08:00
BTMuli
561f34cf8b ⬆️ 更新依赖 2023-11-17 13:26:59 +08:00
BTMuli
5241c08c33 👽️ 大别野也有咨讯区了 2023-11-17 00:19:17 +08:00
BTMuli
630a64323d 🐛 我真傻,真的 2023-11-16 23:58:17 +08:00
BTMuli
54d2e27054 🐛 修复一些 bug
* 米社子窗口关闭后无法再次创建
* 保存图片回调默认路径错误
* 调整 closePage 逻辑
2023-11-16 22:38:42 +08:00
BTMuli
1c69cf07a5 ⬆️ 米社 salt 更新,2.59.1 → 2.63.1 2023-11-16 22:01:10 +08:00
BTMuli
715c206945 素材日历添加“留影叙佳期”入口
close #61
2023-11-16 22:00:35 +08:00
BTMuli
219286f6a1 🎨 完善 fp 更新逻辑 2023-11-16 20:59:32 +08:00
BTMuli
fc3d417961 🎨 微调逻辑 2023-11-16 14:44:23 +08:00
BTMuli
712a09131e 实装 getDeviceFp
close #58
2023-11-16 14:23:30 +08:00
BTMuli
13e9440c6f ✏️ 完善类型 #51 2023-11-16 00:21:36 +08:00
BTMuli
bed0e528b0 ✏️ extends BaseWithData #51 2023-11-16 00:16:04 +08:00
BTMuli
43c282efd2 📝 更新 UIGF 文档 2023-11-15 21:02:13 +08:00
BTMuli
b74a3b0bbf 🎨 格式化 2023-11-15 14:04:13 +08:00
BTMuli
7c0239391e 🔧 调整部分配置 2023-11-15 14:00:52 +08:00
BTMuli
b1060f76c5 ⬆️ 更新依赖,开写 v0.3.6 2023-11-15 14:00:08 +08:00
67 changed files with 2404 additions and 1311 deletions

View File

@@ -5,3 +5,6 @@ src-tauri/target
TGAssistant
# Package files
pnpm-lock.yaml
# lint files
!.prettierrc.yml
!.stylelintrc.yml

View File

@@ -32,7 +32,7 @@ jobs:
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.9.2
version: 8.10.5
- name: Install frontend dependencies
run: pnpm install

View File

@@ -5,4 +5,4 @@ useTabs: false
tabWidth: 2
bracketSpacing: true
endOfLine: auto
trailingComma: "all"
trailingComma: all

View File

@@ -8,4 +8,4 @@ plugins:
- stylelint-prettier
- stylelint-order
rules:
"prettier/prettier": true
prettier/prettier: true

View File

@@ -2,12 +2,42 @@
Author: 目棃
Description: CHANGELOG
Date: 2023-09-08
Update: 2023-11-11
Update: 2023-11-28
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-09-08 09:45:17 `
>
> 更新于 `2023-11-11 12:12:58`
> 更新于 `2023-11-28 15:18:27`
## [0.3.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.6) (2023-11-25)
### Feat
- 应用:实装 `device_fp`,有效降低 `1034` 错误 [`#58`](https://github.com/BTMuli/TeyvatGuide/issues/58)
- 首页:今日素材组件添加留影叙佳期入口,角色生日时颜色变更 [`#61`](https://github.com/BTMuli/TeyvatGuide/issues/61)
- 组件:优化 showConfirm 组件 input 模式下的体验
- 成就:支持单个成就完成状态修改 [`#60`](https://github.com/BTMuli/TeyvatGuide/issues/60)
- 成就:支持隐藏已完成成就 [`#19`](https://github.com/BTMuli/TeyvatGuide/issues/19)
- 角色:角色详情页 UI 迭代,支持角色卡片分享 [`#20`](https://github.com/BTMuli/TeyvatGuide/issues/20)
### Fix
- JSBridge修复窗口关闭后无法再次创建的问题
- JSBridge修复保存图片默认路径错误
- JSBridge调整 closePage 逻辑
- 应用:在生成分享图时忽略某些元素
- 应用:完善数据库检测机制 [`#62`](https://github.com/BTMuli/TeyvatGuide/issues/62)
- JSBridge应用启动时关闭隐藏的子窗口
- 应用:完善登录态检测机制
### Change
- 应用:米游社 salt 版本更新到 2.63.1
- 咨讯:大别野版块不再忽略咨讯区
- 分享:提高生成分享图的清晰度
- 成就:调整完成 icon 的颜色
- 组件:增加素材日历组件 overlay 国家 icon 清晰度
- 成就:重构成就页面代码,优化性能
## [0.3.5](https://github.com/BTMuli/TeyvatGuide/releases/v0.3.5) (2023-11-11)

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 说明文档
Date: 2023-03-05
Update: 2023-11-11
Update: 2023-11-15
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
>
> 更新于 `2023-11-11 12:14:39`
> 更新于 `2023-11-15 21:01:51`
![](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)
@@ -86,7 +86,7 @@ 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 v2.3](docs/UIGF.md)~~ [UIGF v2.4](https://github.com/BTMuli/TeyvatGuide/issues/59)
- UIGF[UIGF v2.4](docs/UIGF.md)
## 特定项目 / Special Project

View File

@@ -1,50 +1,77 @@
---
Author: 目棃
Date: 2023-04-07
Description: UIGF v2.3 Backup
Update: 2023-04-07
Description: UIGF v2.4 Backup
Date: 2023-11-15
Update: 2023-11-15
---
> 本文档 [`Front-matter`](https://github.com/BTMuli/Mucli#FrontMatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于`2023-04-07 19:51:40`
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-11-15 20:58:36`
>
> 更新于 `2023-04-07 19:51:40`
> 更新于 `2023-11-15 20:58:36`
>
> 本文档为 [UIGF v2.4](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/UIGF.md) 的备份,仅供参考。
> 本文档为 [`UIGF`](https://github.com/UIGF-org/UIGF-org.github.io/blob/main/docs/zh/standards/UIGF-pre-release.md) 的备份。
# 统一可交换抽卡记录标准 v2.4
# 统一可交换祈愿记录标准 v2.3
> Uniformed Interchangeable GachaLog Format standard (UIGF) v2.4 <Badge text="Current" type="message" />
>
> ::: warning UIGF 标准使用声明
> 应用必须在同时支持 UIGF 数据格式**导入**和**导出**功能并在相关功能区域或文档中提供跳转至 [UIGF-Org](https://uigf.org) 的超链接后声明支持 UIGF 格式
> Uniformed Interchangeable GachaLog Format standard (UIGF) v2.3
仅包含导入功能降低了用户数据可流通性,且将数据至于用户不可控的风险中,不符合 UIGF-Org 设计的初衷。
:::
## 更新记录
| 版本 | 说明 | 兼容 |
| ------ | ------------------------------------------------ | -------------- |
| `v2.0` | 首个正式版本 | v2.0 |
| `v2.1` | 简化了部分语言表述,与 v2.0 在数据格式上完全一致 | v2.1 and lower |
| `v2.2` | 新增 `info.export_timestamp` 填充 UNIX 时间戳 | v2.2 and lower |
| `v2.3` | 扩充至非中文语境,使用 Json Schema 表述 | v2.3 and lower |
| 版本 | 说明 | 兼容 |
| ----------------------------- | ---------------------------------------------------------- | -------------- |
| `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` | 新增 `info.region_time_zone` 支持时区处理 | v2.4 and lower |
## Id
### v2.4 更新内容
原神的祈愿记录物品内包含了一项较为特殊的字段: `id` ,该值在 1.3 版本后加入
所以**先前查询出的物品**若无特殊兼容性修改则不会包含相应的 `id`
App 导出 UIGF 时
- 国际化兼容性增强
-`info` 对象中新增了 `region_time_zone` 字段
- 需要确保每个物品的 `id` 的有效性。
- 从最后一个自带有效 `id` 的物品开始,向前(相对于时间)依次递减 `id` 的值,每次递减的值应保持为 `1`
## `info` 字段说明
导入 UIGF 到 App 时
### `region_time_zone`
- App 不应假设所有的 `gacha_item` 都有有效的 `id`
- App 应具有处理 `id` 字段为 `null`或 `` 空字符串的能力
由于在获取祈愿记录时得到的`time`为服务器时间,为了准确判断时间的时区偏移,引入此字段。
## GachaType
与 SRGF 不同,由于无法直接从服务器获取`region_time_zone`,在导出方未提供此字段时,需要根据 `uid` 进行推断。
祈愿包含了会共享保底与概率的卡池,所以需要一个额外的字段来界定
我们在`UIGF`的所有格式中注入了`uigf_gacha_type`字段
在导出到`UIGF`格式时需要注意添加对应的`uigf_gacha_type`字段
#### 映射关系
### 映射关系
| `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` |
| ----------------- | -------------- |
@@ -53,225 +80,112 @@ App 导出 UIGF 时
| `301` | `301` or `400` |
| `302` | `302` |
## Json 格式
### `item_id`
> Uniformed Interchangeable GachaLog Format standard of Json (UIGF.J)
> Json 格式 由于 与从官方接口获取到的格式一致
> 更便于各 App 的导入与导出,我们也在此做出规范
> 该格式应仅用于各 App 间的数据互通
物品游戏内ID你可以通过 [UIGF API](../API.md) 获取这一数据
### 导出的格式
## Json Schema
> UIGF-Org 提供[Json Schema](/schema/uigf.json) 用于验证
```json
{
"type": "object",
"title": "UIGF object",
"properties": {
"info": {
"type": "object",
"properties": {
"uid": {
"type": "string",
"title": "Uid",
"description": "Uid"
},
"lang": {
"type": "string",
"title": "Language",
"description": "语言 ISO 3166"
},
"uigf_version": {
"type": "string",
"title": "UIGF Version",
"description": "UIGF 版本号"
},
"export_timestamp": {
"type": "number",
"title": "Export Timestamp",
"description": "导出时间戳(秒)"
},
"export_time": {
"type": "string",
"description": "导出时间",
"format": "date-time",
"pattern": "yyyy-MM-dd HH:mm:ss",
"title": "Export Time"
},
"export_app": {
"type": "string",
"title": "Export App",
"description": "导出应用"
},
"export_app_version": {
"type": "string",
"title": "Export App Version",
"description": "导出应用版本"
}
},
"title": "Infomation",
"required": ["uid", "lang", "uigf_version"],
"description": "包含导出方定义的基本信息"
},
"list": {
"type": "array",
"items": {
"root": {
"type": "object",
"properties": {
"info": {
"type": "object",
"properties": {
"gacha_type": {
"uid": {
"type": "string",
"description": "祈愿类型"
"title": "导出记录的 UID"
},
"item_id": {
"lang": {
"type": "string",
"title": "Item Id",
"description": "空字符串"
"title": "语言 languagecode2-country/regioncode2"
},
"count": {
"type": "string",
"title": "Count",
"description": "数量"
"export_timestamp": {
"type": "number",
"title": "导出 UNIX 时间戳(秒)"
},
"time": {
"export_time": {
"type": "string",
"title": "Time",
"description": "物品获取时间",
"pattern": "yyyy-MM-dd HH:mm:ss",
"format": "date-time"
"title": "导出时间",
"description": "yyyy-MM-dd HH:mm:ss"
},
"name": {
"export_app": {
"type": "string",
"title": "Name",
"description": "名称"
"title": "导出 App 名称"
},
"item_type": {
"export_app_version": {
"type": "string",
"title": "Item Type",
"description": "物品类型"
"title": "导出 App 版本"
},
"rank_type": {
"uigf_version": {
"type": "string",
"title": "Item Quality",
"description": "物品星级"
"title": "UIGF 版本号",
"pattern": "v\\d+\\.\\d+"
},
"id": {
"type": "string",
"title": "Id",
"description": "内部数据库Id"
},
"uigf_gacha_type": {
"type": "string",
"title": "Query Type",
"description": "向接口查询时需要的 gacha_type"
"region_time_zone": {
"type": "number",
"title": "区域时区偏移"
}
},
"required": ["gacha_type", "name", "id", "uigf_gacha_type", "time"],
"title": "Gacha Item",
"description": "祈愿物品"
"required": ["uid", "uigf_version"],
"title": "UIGF 导出信息"
},
"title": "List",
"description": "物品列表"
}
},
"required": ["info", "list"],
"description": "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": "个数",
"description": "一般为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 根对象"
}
}
```
## Excel 工作簿 (Workbook Format)
> Uniformed Interchangeable GachaLog Format standard of Workbook (UIGF.W)
### 单元格的格式
- 在填充单元格内的数据时,应统一转换到 `String` 字符串类型后填入
### 表名及内容
| 表名 | 内容 | 类型 | 是否必要 |
| ------------ | -------------------------------------- | ------ | ------------------------------ |
| 统计分析 | 统计分析内容等 | 任意 | 否 |
| 角色活动祈愿 | `gacha_type` : `301 or 400` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
| 武器活动祈愿 | `gacha_type` : `302` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
| 常驻祈愿 | `gacha_type` : `200` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
| 新手祈愿 | `gacha_type` : `100` 的祈愿数据 | 祈愿表 | 否,但是应该导出 |
| 原始数据 | 全部祈愿数据 | 数据表 | **详见下方原始数据表结构说明** |
- 表的顺序可以是任意的
- 可以隐藏部分表,防止用户随意篡改数据
- Sheet 的名称应与游戏内祈愿记录页面显示的名称保持一致
> App 间应依据 `原始数据表` 的内容,来进行数据互通
### 祈愿表结构
本节内容是为了规范兼容分析类 App
- 表头对应的内容填充**顺序需要严格按照下方说明**排布
- **共享保底的卡池**按祈愿类型 (`gacha_type`) 区分
- 此类 `Sheet` 存在的目的,是为了便于用户观看与祈愿分析工具的分析
| 表头 | 内容 | 是否必要 |
| -------- | ---------------------------------------- | ---------------------------- |
| 时间 | `yyyy-MM-dd HH:mm:ss` 格式的 `time` 时间 | 是 |
| 名称 | `name`物品名称 | 是 |
| 物品类型 | `item_type` | 是 |
| 星级 | `rank_type` | 是 |
| 祈愿类型 | `gacha_type` 的转义名称 | 是,尽管部分工具不会分析此项 |
| ... | ... | 否 |
> 如果你认为有必要的话,可以额外增加其他表头,但请确保表头的前几列为上表规范的内容
> 表内的数据通常按祈愿 Id 升序或降序排列,分析 App 不应假设表内的顺序为特定的升序与降序
#### `gacha_type` 转义名称
| gacha_type | 名称 |
| ---------- | -------------- |
| 100 | 新手祈愿 |
| 200 | 常驻祈愿 |
| 301 | 角色活动祈愿 |
| 400 | 角色活动祈愿-2 |
| 302 | 武器活动祈愿 |
#### 示例
| 时间 | 名称 | 类别 | 星级 | 祈愿类型 | ... |
| ------------------- | -------- | ---- | ---- | -------------- | --- |
| 2021-02-17 18:45:09 | 以理服人 | 武器 | 3 | 角色活动祈愿-2 | ... |
| ... | ... | ... | ... | ... | ... |
### 原始数据表结构
导出时
- App 在导出时应尽可能询问用户是否应包含原始数据表
- 一旦在工作簿内包含了名为 `原始数据` 的表,即表示支持本格式
- 该表内的内容应严格按照本格式所述填充
- **表头的顺序需严格按照下表设置**。
- 现有的字段采用**字典顺序**递增排序,后续新增的字段依添加的顺序排在后侧。
- 若无特殊需求,我们建议导出所有 json 数据内包含的字段
导入时
- 强烈建议您编写不依赖于列的顺序位置便可实现导入的程序,以达到最大化的兼容。
- 如果省略了其中某些非必要字段的值,请保持表头存在,对应的列则空置。
| 表头 | 是否必要 |
| ----------------- | ---------------------------------------------------- |
| `count` | 否,但是建议保留,不排除后续会有`count`不为 1 的情况 |
| `gacha_type` | 是 |
| `id` | 是,且大部分 App 按此字段排序数据 |
| `item_id` | 否,目前官方已经弃用了此字段 |
| `item_type` | 是 |
| `lang` | 否,但建议保留,以便国际化 |
| `name` | 是 |
| `rank_type` | 否,但建议保留,以便分析 |
| `time` | 否,但建议保留,以便分析 |
| `uid` | 否,但建议将选择权交予用户,保留以便分析 |
| `uigf_gacha_type` | 是 |
#### 示例
| count | gacha_type | id | item_id | item_type | lang | name | rank_type | time | uid | uigf_gacha_type |
| ----- | ---------- | ------------------- | ------- | --------- | ----- | -------- | --------- | ------------------- | --------- | --------------- |
| 1 | 301 | 1613556360008291100 | | 武器 | zh-cn | 以理服人 | 3 | 2021-02-17 18:45:09 | 123456789 | 301 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |

View File

@@ -1,9 +1,9 @@
{
"name": "TeyvatGuide",
"version": "0.3.5",
"version": "0.3.6",
"description": "Game Tool for Genshin Impact player",
"private": true,
"packageManager": "pnpm@8.10.0",
"packageManager": "pnpm@8.10.5",
"scripts": {
"build": "tauri build",
"debug": "tauri build --debug",
@@ -78,38 +78,38 @@
"qrcode.vue": "^3.4.1",
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
"uuid": "^9.0.1",
"vue": "^3.3.7",
"vue": "^3.3.8",
"vue-echarts": "^6.6.1",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.2.5",
"vuetify": "^3.3.23",
"vuetify": "^3.4.2",
"wcag-color": "^1.1.1"
},
"devDependencies": {
"@tauri-apps/cli": "^1.5.6",
"@types/color-convert": "^2.0.2",
"@types/js-md5": "^0.7.1",
"@types/node": "^20.8.10",
"@types/uuid": "^9.0.6",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@vitejs/plugin-vue": "^4.4.0",
"@types/color-convert": "^2.0.3",
"@types/js-md5": "^0.7.2",
"@types/node": "^20.9.1",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitejs/plugin-vue": "^4.5.0",
"@vue/devtools": "^6.5.1",
"concurrently": "^8.2.2",
"eslint": "^8.52.0",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard-with-typescript": "^39.1.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsonc": "^2.10.0",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-n": "^16.3.1",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.18.1",
"eslint-plugin-yml": "^1.10.0",
"husky": "^8.0.3",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.0.2",
"prettier": "3.0.3",
"lint-staged": "^15.1.0",
"prettier": "3.1.0",
"stylelint": "^15.11.0",
"stylelint-config-idiomatic-order": "^9.0.0",
"stylelint-config-standard-vue": "^1.0.0",
@@ -118,7 +118,7 @@
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^4.0.2",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vite": "^5.0.0",
"vite-plugin-vuetify": "^1.0.2",
"yaml-eslint-parser": "^1.2.2"
}

775
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

156
src-tauri/Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 3
[[package]]
name = "TeyvatGuide"
version = "0.3.5"
version = "0.3.6"
dependencies = [
"serde",
"serde_json",
@@ -39,7 +39,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
dependencies = [
"cfg-if",
"getrandom 0.2.10",
"getrandom 0.2.11",
"once_cell",
"version_check",
"zerocopy",
@@ -221,9 +221,9 @@ dependencies = [
[[package]]
name = "bstr"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
dependencies = [
"memchr",
"serde",
@@ -472,9 +472,9 @@ dependencies = [
[[package]]
name = "crc-catalog"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4939f9ed1444bd8c896d37f3090012fa6e7834fe84ef8c9daa166109515732f9"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
@@ -548,7 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -582,7 +582,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -593,7 +593,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -734,7 +734,7 @@ checksum = "f54cc3e827ee1c3812239a9a41dede7b4d7d5d5464faa32d71bd7cba28ce2cb2"
dependencies = [
"cc",
"rustc_version",
"toml 0.8.6",
"toml 0.8.8",
"vswhom",
"winreg 0.51.0",
]
@@ -762,9 +762,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
dependencies = [
"libc",
"windows-sys 0.48.0",
@@ -943,7 +943,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -1106,9 +1106,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
@@ -1412,9 +1412,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150"
dependencies = [
"bytes",
"fnv",
@@ -1746,9 +1746,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.149"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "libm"
@@ -1756,6 +1756,17 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libredox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
"bitflags 2.4.1",
"libc",
"redox_syscall 0.4.1",
]
[[package]]
name = "libsqlite3-sys"
version = "0.26.0"
@@ -1778,9 +1789,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "lock_api"
@@ -2185,9 +2196,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.58"
version = "0.10.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9dfc0783362704e97ef3bd24261995a699468440099ef95d869b4d9732f829a"
checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
@@ -2206,7 +2217,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -2217,9 +2228,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.94"
version = "0.9.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f55da20b29f956fb01f0add8683eb26ee13ebe3ebd935e49898717c6b4b2830"
checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
dependencies = [
"cc",
"libc",
@@ -2474,12 +2485,12 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "plist"
version = "1.5.1"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa"
checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef"
dependencies = [
"base64 0.21.5",
"indexmap 1.9.3",
"indexmap 2.1.0",
"line-wrap",
"quick-xml",
"serde",
@@ -2568,9 +2579,9 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.30.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
]
@@ -2644,7 +2655,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.10",
"getrandom 0.2.11",
]
[[package]]
@@ -2671,15 +2682,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
@@ -2700,12 +2702,12 @@ dependencies = [
[[package]]
name = "redox_users"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom 0.2.10",
"redox_syscall 0.2.16",
"getrandom 0.2.11",
"libredox",
"thiserror",
]
@@ -2967,22 +2969,22 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.190"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.190"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3004,7 +3006,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3054,7 +3056,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3153,9 +3155,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.11.1"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "socket2"
@@ -3517,9 +3519,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.38"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
@@ -3582,7 +3584,7 @@ dependencies = [
"cfg-expr 0.15.5",
"heck 0.4.1",
"pkg-config",
"toml 0.8.6",
"toml 0.8.8",
"version-compare 0.1.1",
]
@@ -3791,7 +3793,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-sql"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#8d6045421a553330e9da8b9e1e4405d419c5ea88"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#84a2a11c4dc6a6b5569ef2e38bb6c2a4e04a7219"
dependencies = [
"futures-core",
"log",
@@ -3932,7 +3934,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -3997,9 +3999,9 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
[[package]]
name = "tokio"
version = "1.33.0"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
dependencies = [
"backtrace",
"bytes",
@@ -4069,14 +4071,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.6"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.7",
"toml_edit 0.21.0",
]
[[package]]
@@ -4103,9 +4105,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.20.7"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
dependencies = [
"indexmap 2.1.0",
"serde",
@@ -4140,7 +4142,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -4260,7 +4262,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
dependencies = [
"getrandom 0.2.10",
"getrandom 0.2.11",
]
[[package]]
@@ -4365,7 +4367,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"wasm-bindgen-shared",
]
@@ -4399,7 +4401,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4841,9 +4843,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winnow"
version = "0.5.18"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32"
checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
dependencies = [
"memchr",
]
@@ -4938,22 +4940,22 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.21"
version = "0.7.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686b7e407015242119c33dab17b8f61ba6843534de936d94368856528eae4dcc"
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.21"
version = "0.7.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020f3dfe25dfc38dfea49ce62d5d45ecdd7f0d8a724fa63eb36b6eba4ec76806"
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]

View File

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

View File

@@ -1,6 +1,6 @@
//! @file src/client.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.3.4
//! @since Beta v0.3.6
use tauri::{AppHandle, Manager, WindowBuilder, WindowUrl};
use url::Url;
@@ -15,6 +15,10 @@ fn get_mhy_client_url(func: String) -> WindowUrl {
} else if func == "game_record" {
url_res =
"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen".parse().unwrap();
} else if func == "birthday" {
url_res = "https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html?activity_id=20220301153521"
.parse()
.unwrap();
}
return WindowUrl::External(url_res);
}
@@ -29,10 +33,11 @@ pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
} else {
mhy_window_config.url = get_mhy_client_url(func.clone());
}
if func == "birthday" {
if func == "birthday"
|| url.starts_with("https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html")
{
mhy_window_config.width = 1280.0;
mhy_window_config.height = 720.0;
mhy_window_config.resizable = false;
}
let has_mhy_client = handle.get_window("mhy_client").is_some();
if has_mhy_client {

View File

@@ -118,6 +118,10 @@ fn main() {
])
.setup(|_app| {
let _window = _app.get_window("TeyvatGuide").unwrap();
let _mhy = _app.get_window("mhy_client");
if _mhy.is_some() {
_mhy.unwrap().close().unwrap();
}
#[cfg(debug_assertions)] // only include this code on debug builds
_window.open_devtools(); // open the devtools on startup
Ok(())

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "TeyvatGuide",
"version": "0.3.5"
"version": "0.3.6"
},
"tauri": {
"allowlist": {
@@ -137,17 +137,17 @@
},
{
"fullscreen": false,
"resizable": true,
"resizable": false,
"title": "米游社",
"label": "mhy_client",
"url": "https://api-static.mihoyo.com/",
"userAgent": "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.60.1",
"userAgent": "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.63.1",
"visible": false,
"width": 400,
"height": 800,
"center": true,
"decorations": true,
"closable": false
"closable": true
}
]
}

View File

@@ -91,14 +91,27 @@ async function checkAppLoad(): Promise<void> {
console.info("数据已加载!");
return;
}
await createDataDir();
await initData();
appStore.loading = true;
console.info("数据加载完成!");
const checkDB = await TGSqlite.check();
if (!checkDB) {
await TGSqlite.reset();
showSnackbar({
text: "检测到数据库不完整!已重置数据库!",
color: "error",
timeout: 3000,
});
await createDataDir();
} else {
appStore.loading = true;
console.info("数据库已加载!");
}
}
// 检测 ck,info 数据
async function checkUserLoad(): Promise<void> {
if (!appStore.isLogin) {
console.info("未登录!");
return;
}
const userStore = useUserStore();
const ckLocal = userStore.cookie;
const ckDB = await TGSqlite.getCookie();
@@ -156,19 +169,6 @@ async function createDataDir(): Promise<void> {
console.info("数据文件夹创建完成!");
}
// 初始化数据库
async function initData(): Promise<void> {
if (import.meta.env.MODE === "development") {
console.info("开发环境,跳过数据库初始化!");
return;
}
await TGSqlite.reset();
showSnackbar({
text: "已成功初始化数据库!",
});
console.info("已成功初始化数据库!");
}
async function getDeepLink(): Promise<void> {
await event.listen("active_deep_link", async (e) => {
const windowGet = new TauriWindow.WebviewWindow("TeyvatGuide");
@@ -210,6 +210,7 @@ async function getDeepLink(): Promise<void> {
// 检测更新
async function checkUpdate(): Promise<void> {
if (!appStore.loading) return;
const isProdEnv = import.meta.env.MODE === "production";
const needUpdate = await TGSqlite.checkUpdate();
if (needUpdate && isProdEnv) {

View File

@@ -166,11 +166,18 @@ const userStore = useUserStore();
const isDevEnv = ref<boolean>(import.meta.env.MODE === "development");
const userInfo = computed(() => {
const info = userStore.getBriefInfo();
return {
nickname: info?.nickname ?? "未登录",
avatar: info?.avatar ?? "/source/UI/defaultUser.webp",
};
if (appStore.isLogin) {
const info = userStore.getBriefInfo();
return {
nickname: info.nickname,
avatar: info.avatar,
};
} else {
return {
nickname: "未登录",
avatar: "/source/UI/defaultUser.webp",
};
}
});
const rail = ref(appStore.sidebar.collapse);
// theme
@@ -217,8 +224,11 @@ async function switchTheme(): Promise<void> {
}
async function openClient(func: string): Promise<void> {
if (userStore.cookie.game_token === "") return login();
await mhyClient.open(func);
if (appStore.isLogin) {
await mhyClient.open(func);
} else {
login();
}
}
function login(): void {

View File

@@ -0,0 +1,84 @@
<template>
<div class="duc-dolb-box">
<div
v-for="constellation in constellations"
:key="constellation.pos"
:title="constellation.name"
class="duc-dolb-item"
>
<div v-if="!constellation.active" class="duc-dolb-lock">
<v-icon color="white">mdi-lock</v-icon>
</div>
<img class="duc-dolb-icon" :src="constellation.icon" alt="constellation" />
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUpdated, ref } from "vue";
import { saveImgLocal } from "../../utils/TGShare";
interface DucDetailOlbProps {
modelValue: TGApp.Sqlite.Character.RoleConstellation[];
}
const props = defineProps<DucDetailOlbProps>();
const constellations = ref<TGApp.Sqlite.Character.RoleConstellation[]>([]);
async function loadData() {
const tempConstellations = props.modelValue;
for (const constellation of tempConstellations) {
if (constellation.icon.startsWith("blob:")) return;
constellation.icon = await saveImgLocal(constellation.icon);
}
constellations.value = tempConstellations;
}
onMounted(async () => {
await loadData();
});
onUpdated(async () => {
await loadData();
});
</script>
<style>
.duc-dolb-box {
display: flex;
align-items: center;
justify-content: center;
column-gap: 10px;
}
.duc-dolb-item {
position: relative;
display: flex;
width: 60px;
height: 60px;
align-items: center;
justify-content: center;
border-radius: 50%;
backdrop-filter: blur(5px);
background: rgb(0 0 0/40%);
}
.duc-dolb-lock {
position: absolute;
display: flex;
width: 54px;
height: 54px;
align-items: center;
justify-content: center;
padding: 3px;
border-radius: 50%;
backdrop-filter: blur(5px);
background-color: rgb(0 0 0 / 40%);
}
.duc-dolb-icon {
width: 50px;
height: 50px;
padding: 5px;
border-radius: 50%;
}
</style>

View File

@@ -0,0 +1,109 @@
<template>
<div class="ddo-lt-box">
<div class="ddo-ltb-icon" :title="getTitle">
<TItemBox :model-value="boxData" />
</div>
<div class="ddo-ltb-info">
<span>{{ props.data.name }}</span>
<span>Lv.{{ props.data.level }}</span>
<span>{{ info }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import TItemBox, { TItemBoxData } from "../main/t-itembox.vue";
type DucDetailOltProps =
| {
data: TGApp.Sqlite.Character.UserRole;
mode: "avatar";
}
| {
data: TGApp.Sqlite.Character.RoleWeapon;
mode: "weapon";
};
const props = defineProps<DucDetailOltProps>();
const getTitle = computed(() => {
if (props.mode === "avatar") {
return `${props.data.name}`;
} else {
const descriptionList = props.data.description.split("");
return descriptionList.reduce((prev: string, cur: string, index: number) => {
if (index % 10 === 0) {
return `${prev}\n${cur}`;
} else {
return `${prev}${cur}`;
}
}, "");
}
});
const boxData = computed<TItemBoxData>(() => {
if (props.mode === "avatar") {
return {
bg: `/icon/bg/${props.data.star}-Star.webp`,
icon: `/WIKI/character/icon/${props.data.cid}.webp`,
size: "100px",
height: "100px",
display: "inner",
innerHeight: 0,
innerText: "",
clickable: false,
lt: `/icon/element/${props.data.element}.webp`,
ltSize: "30px",
};
} else {
return {
bg: `/icon/bg/${props.data.star}-Star.webp`,
icon: `/WIKI/weapon/icon/${props.data.id}.webp`,
size: "100px",
height: "100px",
display: "inner",
innerHeight: 0,
innerText: "",
clickable: false,
lt: `/icon/weapon/${props.data.type}.webp`,
ltSize: "30px",
};
}
});
const info = computed(() => {
if (props.mode === "avatar") {
return `好感 ${props.data.fetter}`;
} else {
return `精炼 ${props.data.affix}`;
}
});
</script>
<style lang="css" scoped>
.ddo-lt-box {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
column-gap: 10px;
}
.ddo-ltb-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
color: var(--tgc-white-1);
text-align: left;
}
.ddo-ltb-info :nth-child(1) {
margin-bottom: 10px;
font-family: var(--font-title);
font-size: 20px;
}
.ddo-ltb-info :not(:nth-child(1)) {
font-family: var(--font-text);
font-size: 16px;
opacity: 0.8;
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<div class="duc-dort-box">
<div :title="talent.name" v-for="talent in talents" :key="talent.pos" class="duc-dort-item">
<span>{{ talent.name }}</span>
<img :src="talent.icon" alt="talent" />
<span>Lv.{{ talent.level }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUpdated, ref } from "vue";
import { saveImgLocal } from "../../utils/TGShare";
interface DucDetailOrtProps {
modelValue: TGApp.Sqlite.Character.RoleTalent[];
}
const props = defineProps<DucDetailOrtProps>();
const talents = ref<TGApp.Sqlite.Character.RoleTalent[]>([]);
async function loadData(): Promise<void> {
const tempTalent = props.modelValue;
for (const talent of tempTalent) {
if (talent.icon.startsWith("blob:")) return;
talent.icon = await saveImgLocal(talent.icon);
}
talents.value = tempTalent;
}
onMounted(async () => {
await loadData();
});
onUpdated(async () => {
await loadData();
});
</script>
<style lang="css" scoped>
.duc-dort-box {
display: flex;
flex-direction: column;
row-gap: 10px;
}
.duc-dort-item {
display: flex;
justify-content: flex-end;
column-gap: 10px;
}
.duc-dort-item img {
width: 40px;
height: 40px;
padding: 5px;
border-radius: 50%;
backdrop-filter: blur(5px);
background: rgba(0 0 0 /40%);
}
.duc-dort-item span {
display: flex;
align-items: center;
justify-content: center;
color: var(--tgc-white-1);
font-family: var(--font-title);
font-size: 16px;
text-shadow: 0 0 5px rgba(0 0 0/40%);
}
.duc-dort-item :nth-last-child(1) {
width: 48px;
justify-content: flex-start;
}
</style>

View File

@@ -0,0 +1,199 @@
<template>
<TOverlay v-model="visible" hide :to-click="onOverlayCancel" blur-val="20px">
<div class="duc-do-box">
<!-- 左侧箭头 -->
<div class="duc-arrow-left" @click="handleClick('left')">
<img src="../../assets/icons/arrow-right.svg" alt="left" />
</div>
<!-- 中间内容 -->
<div class="duc-do-container">
<img :src="nameCard" class="duc-doc-bg" v-if="nameCard !== false" alt="bg" />
<div class="duc-doc-bgc" />
<!-- 左上角色跟武器 -->
<div class="duc-doc-lt">
<DucDetailOlt :data="props.dataVal" mode="avatar" />
<DucDetailOlt :data="JSON.parse(props.dataVal.weapon)" mode="weapon" />
<v-btn
class="duc-doc-btn"
@click="share"
variant="outlined"
:loading="loading"
data-html2canvas-ignore
>
<v-icon>mdi-share-variant</v-icon>
<span>分享</span>
</v-btn>
</div>
<!-- 右侧天赋 -->
<div class="duc-doc-rt">
<DucDetailOrt :model-value="JSON.parse(props.dataVal.talent)" />
</div>
<!-- 左下命座 -->
<div class="duc-doc-lb">
<DucDetailOlb :model-value="JSON.parse(props.dataVal.constellation)" />
</div>
</div>
<!-- 右侧箭头 -->
<div class="duc-arrow-right" @click="handleClick('right')">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</div>
</TOverlay>
</template>
<script lang="ts" setup>
import { computed, onMounted, onUpdated, ref } from "vue";
import DucDetailOlb from "./duc-detail-olb.vue";
import DucDetailOlt from "./duc-detail-olt.vue";
import DucDetailOrt from "./duc-detail-ort.vue";
import TGSqlite from "../../plugins/Sqlite";
import { generateShareImg } from "../../utils/TGShare";
import TOverlay from "../main/t-overlay.vue";
interface DucDetailOverlayProps {
modelValue: boolean;
dataVal: TGApp.Sqlite.Character.UserRole;
}
type DucDetailOverlayEmits = {
(e: "update:modelValue", value: boolean): void;
(e: "clickL"): void;
(e: "clickR"): void;
};
const props = defineProps<DucDetailOverlayProps>();
const emits = defineEmits<DucDetailOverlayEmits>();
const visible = computed({
get: () => props.modelValue,
set: (value) => {
emits("update:modelValue", value);
},
});
// share
const loading = ref<boolean>(false);
// 渲染数据
const nameCard = ref<string | false>(false);
function onOverlayCancel() {
visible.value = false;
emits("update:modelValue", false);
}
function handleClick(pos: "left" | "right") {
pos === "left" ? emits("clickL") : emits("clickR");
}
onMounted(async () => {
await loadData();
});
onUpdated(async () => {
await loadData();
});
async function loadData(): Promise<void> {
if (!props.modelValue) return;
if (props.dataVal.cid !== 10000005 && props.dataVal.cid !== 10000007) {
const role = await TGSqlite.getAppCharacter(props.dataVal.cid);
nameCard.value = `/source/nameCard/profile/${role.nameCard}.webp`;
}
}
async function share(): Promise<void> {
const detailBox = <HTMLElement>document.querySelector(".duc-do-container");
const fileName = `【角色详情】-${props.dataVal.name}`;
loading.value = true;
await generateShareImg(fileName, detailBox);
loading.value = false;
}
</script>
<style lang="css" scoped>
.duc-do-box {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 10px;
}
.duc-arrow-left,
.duc-arrow-right {
position: relative;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dark .duc-arrow-left,
.dark .duc-arrow-right {
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
}
.duc-arrow-left img {
width: 100%;
height: 100%;
transform: rotate(180deg);
}
.duc-arrow-right img {
width: 100%;
height: 100%;
}
.duc-do-container {
position: relative;
overflow: hidden;
width: 800px;
border-radius: 5px;
aspect-ratio: 21 / 10;
background: var(--box-bg-1);
}
.duc-doc-bg {
position: absolute;
right: 0;
left: 0;
width: 100%;
height: 100%;
}
.duc-doc-bgc {
position: absolute;
right: 0;
left: 0;
width: 100%;
height: 100%;
background: rgb(0 0 0 / 20%);
}
.duc-doc-lt {
position: absolute;
top: 10px;
left: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5px;
row-gap: 10px;
}
.duc-doc-btn {
color: var(--tgc-white-1);
}
.duc-doc-rt {
position: absolute;
top: 10px;
right: 10px;
padding: 5px;
}
.duc-doc-lb {
position: absolute;
bottom: 10px;
left: 10px;
padding: 5px;
}
</style>

View File

@@ -13,7 +13,12 @@
</div>
<div v-show="data?.text !== '' && data.mode === 'input'" class="confirm-input">
<div class="confirm-input-label">{{ data.text }}</div>
<input v-model="inputVal" class="confirm-input-box" />
<input
v-model="inputVal"
class="confirm-input-box"
ref="inputRef"
@keydown.enter="handleClick(true)"
/>
</div>
<div class="confirm-btn-box">
<button class="confirm-btn no-btn" @click="handleClick(false)">取消</button>
@@ -26,7 +31,7 @@
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, watch } from "vue";
import { nextTick, onMounted, reactive, ref, watch } from "vue";
interface ConfirmProps {
title: string;
@@ -51,6 +56,7 @@ const showOuter = ref<boolean>(false);
const showInner = ref<boolean>(false);
const confirmVal = ref<boolean | string>(false);
const inputVal = ref<string>("");
const inputRef = ref<HTMLInputElement>();
watch(show, () => {
if (show.value) {
@@ -80,6 +86,14 @@ async function displayBox(params: TGApp.Component.Confirm.Params): Promise<strin
show.value = true;
// 等待确认框关闭返回关闭后的confirmVal
return await new Promise<string | boolean>((resolve) => {
nextTick(() => {
if (data.mode === "input") {
// 等待确认框打开,聚焦输入框
setTimeout(() => {
inputRef.value?.focus();
}, 100);
}
});
watch(show, () => {
// 等 0.5s 动画
setTimeout(() => {

View File

@@ -2,9 +2,21 @@
<div class="calendar-box">
<div class="calendar-title">
<div class="calendar-title-left">
<v-icon size="small"> mdi-calendar-clock</v-icon>
<v-icon size="small" style="opacity: 0.8">mdi-calendar-clock</v-icon>
<span>今日素材</span>
<span>{{ dateNow }}</span>
<!-- 如果是某人生日礼物图标颜色为红色 -->
<span
v-if="birthInfo.isLogin"
@click="toBirthday"
class="calendar-title-gift"
:style="{
color: birthInfo.active ? 'var(--tgc-red-1)' : 'inherit',
}"
:title="birthInfo.text"
>
<v-icon size="small">mdi-gift</v-icon>
</span>
</div>
<div class="calendar-title-mid">
<v-btn
@@ -30,9 +42,9 @@
:label="switchType === 'avatar' ? '角色' : '武器'"
@change="switchType = switchType === 'avatar' ? 'weapon' : 'avatar'"
/>
<v-btn class="calendar-title-btn" @click="share">
<v-btn class="calendar-title-btn" @click="share" data-html2canvas-ignore>
<template #prepend>
<v-icon> mdi-share-variant</v-icon>
<v-icon>mdi-share-variant</v-icon>
</template>
<span>分享</span>
</v-btn>
@@ -64,6 +76,9 @@
import { computed, onMounted, ref } from "vue";
import { AppCalendarData } from "../../data";
import TGSqlite from "../../plugins/Sqlite";
import { useAppStore } from "../../store/modules/app";
import TGClient from "../../utils/TGClient";
import { generateShareImg } from "../../utils/TGShare";
import TibCalendarItem from "../itembox/tib-calendar-item.vue";
import ToCalendar from "../overlay/to-calendar.vue";
@@ -88,6 +103,13 @@ const switchType = ref<string>("avatar");
const selectedItem = ref<TGApp.App.Calendar.Item>(<TGApp.App.Calendar.Item>{});
const selectedType = ref<"avatar" | "weapon">("avatar");
// birthday
const birthInfo = ref({
isLogin: true,
active: false,
text: "点击前往留影叙佳期",
});
const btnText = [
{
week: 7,
@@ -125,7 +147,17 @@ defineExpose({
loading,
});
onMounted(() => {
onMounted(async () => {
const appStore = useAppStore();
if (appStore.isLogin) {
const birthRes = await TGSqlite.isBirthday();
if (birthRes !== false) {
birthInfo.value.active = true;
birthInfo.value.text = `今天是 ${birthRes} 的生日!`;
}
} else {
birthInfo.value.isLogin = false;
}
const dayNow = new Date().getDay() === 0 ? 7 : new Date().getDay();
const week = <{ week: number; text: string }>btnText.find((item) => item.week === dayNow);
dateNow.value =
@@ -174,6 +206,11 @@ async function share(): Promise<void> {
const title = `【今日素材】${showType}${btnNow.value}`;
await generateShareImg(title, div);
}
// 前往留影叙佳期
async function toBirthday(): Promise<void> {
await TGClient.open("birthday");
}
</script>
<style lang="css" scoped>
.calendar-box {
@@ -201,6 +238,13 @@ async function share(): Promise<void> {
column-gap: 10px;
}
.calendar-title-gift {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.calendar-title-mid {
display: flex;
align-items: center;

View File

@@ -250,8 +250,7 @@ onUnmounted(() => {
.pool-title-left img {
width: 25px;
height: 25px;
border-radius: 50%;
background: var(--common-shadow-4);
filter: brightness(0.8);
}
.pool-title-right {

View File

@@ -34,7 +34,6 @@
</div>
<div class="position-card-text">
<v-icon>mdi-clock-outline</v-icon>
<span>剩余时间</span>
<span v-if="positionTimeGet[card.postId] !== '已结束'">{{
positionTimeGet[card.postId]
}}</span>
@@ -130,6 +129,7 @@ onUnmounted(() => {
width: 20px;
height: 20px;
margin: 0 10px;
filter: brightness(0.9);
}
.position-grid {
@@ -182,5 +182,11 @@ onUnmounted(() => {
display: inline-block;
min-width: 200px;
align-items: flex-start;
margin-right: 5px;
}
.position-card-text :nth-child(1) {
color: var(--btn-text);
filter: brightness(0.8);
}
</style>

View File

@@ -2,7 +2,7 @@
<TItemBox :model-value="box" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { onMounted, ref } from "vue";
import TItemBox from "../main/t-itembox.vue";
import type { TItemBoxData } from "../main/t-itembox.vue";
@@ -14,33 +14,48 @@ interface TibCalendarItemProps {
}
const props = defineProps<TibCalendarItemProps>();
const box = computed<TItemBoxData>(() => {
const box = ref<TItemBoxData>({
bg: "",
icon: "",
size: "100px",
height: "100px",
display: "inner",
clickable: false,
lt: "",
ltSize: "30px",
innerHeight: 25,
innerIcon: "",
innerText: "",
});
onMounted(() => {
if (props.model === "avatar") {
return {
box.value = {
bg: props.data.bg,
icon: props.data.icon,
size: "100px",
height: "100px",
display: "inner",
clickable: props.clickable,
lt: props.data.elementIcon,
lt: props.data.elementIcon ?? "",
ltSize: "30px",
innerHeight: 25,
innerIcon: props.data.weaponIcon,
innerText: props.data.name,
};
} else {
box.value = {
bg: props.data.bg,
icon: props.data.icon,
size: "100px",
height: "100px",
display: "inner",
clickable: props.clickable,
lt: props.data.weaponIcon,
ltSize: "30px",
innerHeight: 25,
innerText: props.data.name,
};
}
return {
bg: props.data.bg,
icon: props.data.icon,
size: "100px",
height: "100px",
display: "inner",
clickable: props.clickable,
lt: props.data.weaponIcon,
ltSize: "30px",
innerHeight: 25,
innerText: props.data.name,
};
});
</script>

View File

@@ -18,8 +18,8 @@ const getName = (): string => {
return props.modelValue.id === 10000005
? "旅行者-空"
: props.modelValue.id === 10000007
? "旅行者-荧"
: props.modelValue.name;
? "旅行者-荧"
: props.modelValue.name;
};
onMounted(async () => {

View File

@@ -143,10 +143,13 @@ function toDetail(item: TGApp.App.Calendar.Item): void {
.toc-src-box :nth-child(2) {
height: 30px;
border-radius: 50%;
margin: 5px;
aspect-ratio: 1;
background: var(--common-shadow-4);
filter: invert(87%) sepia(14%) saturate(216%) hue-rotate(180deg) brightness(81%) contrast(87%);
}
.dark .toc-src-box :nth-child(2) {
filter: none;
}
.toc-src-text {

View File

@@ -1,76 +1,87 @@
<template>
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
<div class="tuc-do-box">
<div class="tuc-do-bg">
<img
:src="data.bg"
alt="role"
:style="{
objectFit: data.bgFit,
}"
/>
<div class="tuc-do-div">
<!-- 左侧箭头 -->
<div class="tuc-arrow-left" @click="handleClick('left')">
<img src="../../assets/icons/arrow-right.svg" alt="left" />
</div>
<div class="tuc-do-quote">* 所有数据以游戏内为准此处仅供参考</div>
<!-- 衣装 -->
<div v-if="data.costume.length > 0" class="tuc-do-costume">
<v-switch v-model="showCostumeSwitch" color="#fb7299" @click="switchBg">
<template #label>
<v-icon>mdi-tshirt-crew-outline</v-icon>
</template>
</v-switch>
</div>
<div v-if="showCostumeSwitch" class="tuc-do-costume-name">
{{ data.costume[0].name }}
</div>
<div v-if="data" class="tuc-do-show">
<!-- 左侧武器跟圣遗物 -->
<div class="tuc-do-left">
<div
class="tuc-dol-item"
<!-- 中间内容 -->
<div class="tuc-do-box">
<div class="tuc-do-bg">
<img
:src="data.bg"
alt="role"
:style="{
opacity: selected.pos === 0 ? '1' : '0.5',
objectFit: data.bgFit,
}"
@click="showDetail(data.weapon, '武器', 0)"
>
<TucDetailItemBox v-model="weaponBox" />
</div>
<div
v-for="(item, index) in data.reliquary"
:key="index"
class="tuc-dol-item"
:style="{
cursor: item ? 'pointer' : 'default',
opacity: selected.pos === index + 1 ? '1' : item ? '0.5' : '1',
}"
@click="showDetail(item, '圣遗物', index + 1)"
>
<TucDetailRelic :model-value="item" :pos="index + 1" />
</div>
</div>
<!-- 右侧环状排列6个命座 -->
<div class="tuc-do-right">
<div class="tuc-dor-box">
<TucDetailConstellation
v-for="item in data.constellation"
:key="item.pos"
class="tuc-dor-item"
:model-value="item"
:style="{
border: selected.pos === item.pos + 5 ? '2px solid var(--tgc-yellow-1)' : '',
}"
@click="showDetail(item, '命座', item.pos + 5)"
/>
</div>
</div>
<!-- 底部说明 -->
<div class="tuc-do-bottom">
<TucDetailDescWeapon v-if="selected.type === '武器'" v-model="selectWeapon" />
<TucDetailDescConstellation
v-if="selected.type === '命座'"
v-model="selectConstellation"
/>
<TucDetailDescRelic v-if="selected.type === '圣遗物'" v-model="selectRelic" />
</div>
<div class="tuc-do-quote">* 所有数据以游戏内为准此处仅供参考</div>
<!-- 衣装 -->
<div v-if="data.costume.length > 0" class="tuc-do-costume">
<v-switch v-model="showCostumeSwitch" color="#fb7299" @click="switchBg">
<template #label>
<v-icon>mdi-tshirt-crew-outline</v-icon>
</template>
</v-switch>
</div>
<div v-if="showCostumeSwitch" class="tuc-do-costume-name">
{{ data.costume[0].name }}
</div>
<div v-if="data" class="tuc-do-show">
<!-- 左侧武器跟圣遗物 -->
<div class="tuc-do-left">
<div
class="tuc-dol-item"
:style="{
opacity: selected.pos === 0 ? '1' : '0.5',
}"
@click="showDetail(data.weapon, '武器', 0)"
>
<TucDetailItemBox v-model="weaponBox" />
</div>
<div
v-for="(item, index) in data.reliquary"
:key="index"
class="tuc-dol-item"
:style="{
cursor: item ? 'pointer' : 'default',
opacity: selected.pos === index + 1 ? '1' : item ? '0.5' : '1',
}"
@click="showDetail(item, '圣遗物', index + 1)"
>
<TucDetailRelic :model-value="item" :pos="index + 1" />
</div>
</div>
<!-- 右侧环状排列6个命座 -->
<div class="tuc-do-right">
<div class="tuc-dor-box">
<TucDetailConstellation
v-for="item in data.constellation"
:key="item.pos"
class="tuc-dor-item"
:model-value="item"
:style="{
border: selected.pos === item.pos + 5 ? '2px solid var(--tgc-yellow-1)' : '',
}"
@click="showDetail(item, '命座', item.pos + 5)"
/>
</div>
</div>
<!-- 底部说明 -->
<div class="tuc-do-bottom">
<TucDetailDescWeapon v-if="selected.type === '武器'" v-model="selectWeapon" />
<TucDetailDescConstellation
v-if="selected.type === '命座'"
v-model="selectConstellation"
/>
<TucDetailDescRelic v-if="selected.type === '圣遗物'" v-model="selectRelic" />
</div>
</div>
</div>
<!-- 右侧箭头 -->
<div class="tuc-arrow-right" @click="handleClick('right')">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
</div>
</div>
</TOverlay>
@@ -94,7 +105,9 @@ interface ToUcDetailProps {
interface ToUcDetailEmits {
(e: "update:modelValue", value: boolean): void;
(e: "cancel"): void;
(e: "clickL"): void;
(e: "clickR"): void;
}
type fixedLenArray<T, N extends number> = [T, ...T[]] & { length: N };
@@ -214,9 +227,13 @@ const weaponBox = computed(() => {
const onCancel = (): void => {
visible.value = false;
emits("cancel");
emits("update:modelValue", false);
};
function handleClick(pos: "left" | "right") {
pos === "left" ? emits("clickL") : emits("clickR");
}
function showDetail(
item:
| TGApp.Sqlite.Character.RoleConstellation
@@ -259,6 +276,40 @@ function switchBg(): void {
}
</script>
<style lang="css" scoped>
.tuc-do-div {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 10px;
}
.tuc-arrow-left,
.tuc-arrow-right {
position: relative;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dark .tuc-arrow-left,
.dark .tuc-arrow-right {
filter: invert(11%) sepia(73%) saturate(11%) hue-rotate(139deg) brightness(97%) contrast(81%);
}
.tuc-arrow-left img {
width: 100%;
height: 100%;
transform: rotate(180deg);
}
.tuc-arrow-right img {
width: 100%;
height: 100%;
}
.tuc-do-box {
position: relative;
width: 500px;

View File

@@ -110,8 +110,8 @@ function getAvatarName(): string {
return props.modelValue.cid === 10000005
? "旅行者-空"
: props.modelValue.cid === 10000007
? "旅行者-荧"
: props.modelValue.name;
? "旅行者-荧"
: props.modelValue.name;
}
// 销毁

View File

@@ -2,13 +2,13 @@
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSub" />
<div class="uc-box">
<div class="uc-top">
<div class="uc-top-title">
<div class="uc-top-title" @click="switchOld">
<span v-if="user">
{{ user.nickname }} UID{{ user.gameUid }} 更新于 {{ getUpdateTime() }}
</span>
<span v-else> 暂无数据 </span>
</div>
<div class="uc-top-btns">
<div class="uc-top-btns" data-html2canvas-ignore>
<v-btn class="uc-top-btn" @click="refreshRoles()">
<template #prepend>
<v-icon>mdi-refresh</v-icon>
@@ -38,14 +38,28 @@
/>
</div>
</div>
<ToUcDetail v-model="visible" :data-val="dataVal" />
<TucDetailOverlay
v-if="!detailDev"
v-model="visible"
@clickL="clickOverlay('left')"
@clickR="clickOverlay('right')"
:data-val="dataVal"
/>
<DucDetailOverlay
v-if="detailDev"
v-model="visible"
@clickL="clickOverlay('left')"
@clickR="clickOverlay('right')"
:data-val="dataVal"
/>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from "vue";
import DucDetailOverlay from "../../components/devCharacter/duc-detail-overlay.vue";
import showSnackbar from "../../components/func/snackbar";
import ToLoading from "../../components/overlay/to-loading.vue";
import ToUcDetail from "../../components/userCharacter/tuc-detail-overlay.vue";
import TucDetailOverlay from "../../components/userCharacter/tuc-detail-overlay.vue";
import TucRoleBox from "../../components/userCharacter/tuc-role-box.vue";
import TGSqlite from "../../plugins/Sqlite";
import { useUserStore } from "../../store/modules/user";
@@ -69,6 +83,25 @@ const roleCookie = computed(() => userStore.getCookieGroup4());
// overlay
const visible = ref(false);
const dataVal = ref<TGApp.Sqlite.Character.UserRole>(<TGApp.Sqlite.Character.UserRole>{});
const selectIndex = ref(0);
const detailDev = ref(true);
function clickOverlay(pos: "left" | "right") {
if (pos === "left") {
if (selectIndex.value === 0) {
selectIndex.value = roleList.value.length - 1;
} else {
selectIndex.value -= 1;
}
} else {
if (selectIndex.value === roleList.value.length - 1) {
selectIndex.value = 0;
} else {
selectIndex.value += 1;
}
}
dataVal.value = roleList.value[selectIndex.value];
}
onMounted(async () => {
loadingTitle.value = "正在获取角色数据";
@@ -77,6 +110,13 @@ onMounted(async () => {
loading.value = false;
});
function switchOld(): void {
detailDev.value = !detailDev.value;
showSnackbar({
text: `已切换到${detailDev.value ? "新" : "旧"}版角色详情页面`,
});
}
async function loadRole(): Promise<void> {
const roleData = await TGSqlite.getUserCharacter(user.gameUid);
if (roleData !== false) {
@@ -86,6 +126,7 @@ async function loadRole(): Promise<void> {
return a.cid - b.cid;
});
roleList.value = roleData;
dataVal.value = roleData[selectIndex.value];
isEmpty.value = false;
}
}
@@ -174,15 +215,14 @@ function getUpdateTime(): string {
}
function selectRole(role: TGApp.Sqlite.Character.UserRole): void {
console.log(role);
dataVal.value = role;
selectIndex.value = roleList.value.indexOf(role);
visible.value = true;
}
</script>
<style lang="css" scoped>
.uc-box {
display: flex;
width: 100%;
flex-direction: column;
padding: 10px;
border: 1px solid var(--common-shadow-2);
@@ -209,6 +249,7 @@ function selectRole(role: TGApp.Sqlite.Character.UserRole): void {
.uc-top-btns {
display: flex;
align-items: center;
gap: 15px;
}

View File

@@ -6,7 +6,7 @@
<span v-if="!isEmpty">{{ getTitle() }} 更新于 {{ recordData.updated }}</span>
<span v-else>原神战绩暂无数据</span>
</div>
<div class="ur-top-btns">
<div class="ur-top-btns" data-html2canvas-ignore>
<v-btn class="ur-top-btn" @click="refresh()">
<template #prepend>
<v-icon>mdi-refresh</v-icon>
@@ -118,7 +118,6 @@ async function shareRecord(): Promise<void> {
<style lang="css" scoped>
.ur-box {
display: flex;
width: 100%;
flex-direction: column;
padding: 10px;
border-radius: 5px;

View File

@@ -1,6 +1,6 @@
<template>
<div class="top-bar">
<div class="top-title">{{ title }}</div>
<div class="top-title" @click="switchHideFin">{{ title }}</div>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
@@ -19,96 +19,92 @@
<div class="wrap">
<!-- 左侧菜单 -->
<div class="left-wrap">
<v-list
v-for="series in seriesList"
<div
v-for="series in allSeriesData"
:key="series.id"
class="card-left"
class="card-series"
@click="selectSeries(series.id)"
>
<div class="version-icon-series">v{{ series.version }}</div>
<v-list-item>
<template #prepend>
<v-img width="40px" style="margin-right: 10px" :src="getIcon(series.id)" />
</template>
<v-list-item-title :title="series.name">
<div class="series-version">v{{ series.version }}</div>
<img alt="icon" class="series-icon" :src="getIcon(series.id)" />
<div class="series-content">
<span :title="series.name">
{{ series.name }}
</v-list-item-title>
<v-list-item-subtitle>
{{ series.finCount }} / {{ series.totalCount }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
</span>
<span> {{ series.finCount }} / {{ series.totalCount }} </span>
</div>
</div>
</div>
<!-- 右侧内容-->
<div class="right-wrap" @scroll="handleScroll">
<v-list
<div class="right-wrap">
<div
v-if="selectedSeries !== 0 && selectedSeries !== 17 && selectedSeries !== -1 && !loading"
:style="{
backgroundImage: 'url(' + getCardImg.bg || null + ')',
backgroundPosition: 'right',
backgroundSize: 'auto 100%',
backgroundRepeat: 'no-repeat',
margin: '10px',
borderRadius: '10px 50px 50px 10px',
border: '1px solid var(--common-shadow-2)',
fontFamily: 'var(--font-title)',
cursor: 'pointer',
position: 'relative',
}"
@click="openImg()"
>
<v-list-item :title="getCardInfo.name" :subtitle="getCardInfo.desc">
<template #prepend>
<v-img width="80px" style="margin-right: 10px" :src="getCardImg.icon" />
</template>
</v-list-item>
</v-list>
<div class="list-empty" :style="{ height: `${emptyHeight}px` }">
<v-list
v-for="achievement in renderAchievement"
:key="achievement.id"
class="card-right"
:style="{ transform: `translateY(${translateY})` }"
:title="seriesList.find((item) => item.id === achievement.series)?.name ?? ''"
class="achi-series"
:style="{ backgroundImage: `url(${curCard.bg})` }"
@click="openImg()"
>
<div v-if="achievement.progress !== 0" class="achievement-progress">
{{ achievement.progress }}
</div>
<v-list-item>
<v-list-item :title="curCard.name" :subtitle="curCard.desc">
<template #prepend>
<v-icon v-if="!achievement.isCompleted" color="var(--tgc-blue-3)">
mdi-circle
</v-icon>
<v-icon v-else class="achievement-finish">
<img alt="finish" src="/source/UI/finish.webp" />
</v-icon>
</template>
<v-list-item-title>
{{ achievement.name }}
<span class="version-icon-single">v{{ achievement.version }}</span>
</v-list-item-title>
<v-list-item-subtitle>{{ achievement.description }}</v-list-item-subtitle>
<template #append>
<span v-show="achievement.isCompleted" class="right-time">{{
achievement.completedTime
}}</span>
<v-card class="reward-card">
<v-img src="/icon/material/201.webp" sizes="32" />
<div class="reward-num">
<span>{{ achievement.reward }}</span>
</div>
</v-card>
<v-img width="80px" style="margin-right: 10px" :src="curCard.icon" />
</template>
</v-list-item>
</v-list>
</div>
<div
v-for="achievement in renderSelect"
:key="achievement.id"
class="card-achi"
:title="allSeriesData.find((item) => item.id === achievement.series)?.name ?? ''"
>
<div class="achi-version">v{{ achievement.version }}</div>
<div class="achi-pre">
<div class="achi-pre-icon">
<v-icon
v-if="!achievement.isCompleted"
color="var(--tgc-blue-3)"
@click="setAchi(achievement, true)"
style="cursor: pointer"
>
mdi-circle
</v-icon>
<v-icon
v-else
class="achievement-finish"
style="cursor: pointer"
@click="setAchi(achievement, false)"
>
<img alt="finish" src="/source/UI/finish.webp" />
</v-icon>
</div>
<div class="achi-pre-info">
<span>
<span>{{ achievement.name }}</span>
<span v-if="achievement.progress !== 0">
{{ achievement.progress }}
</span>
</span>
<span>{{ achievement.description }}</span>
</div>
</div>
<div class="achi-append">
<span v-show="achievement.isCompleted">
{{ achievement.completedTime }}
</span>
<div class="achi-append-icon">
<img alt="icon" src="/icon/material/201.webp" />
<span>{{ achievement.reward }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { dialog, fs, path } from "@tauri-apps/api";
import { computed, nextTick, onBeforeMount, onMounted, ref } from "vue";
import { computed, nextTick, onBeforeMount, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import showConfirm from "../../components/func/confirm";
@@ -118,6 +114,7 @@ import { AppAchievementSeriesData } from "../../data";
import TGSqlite from "../../plugins/Sqlite";
import { useAchievementsStore } from "../../store/modules/achievements";
import { createTGWindow } from "../../utils/TGWindow";
import { getNowStr } from "../../utils/toolFunc";
import { getUiafHeader, readUiafData, verifyUiafData } from "../../utils/UIAF";
// Store
@@ -126,100 +123,77 @@ const achievementsStore = useAchievementsStore();
// loading
const loading = ref<boolean>(true);
const loadingTitle = ref<string>("正在加载数据");
const search = ref<string>("");
const hideFin = ref<boolean>(false);
// data
const title = ref(achievementsStore.title);
const getCardInfo = ref<TGApp.Sqlite.NameCard.SingleTable>(<TGApp.Sqlite.NameCard.SingleTable>{});
const getCardImg = computed(() => {
return {
profile: `/source/nameCard/profile/${getCardInfo.value.name}.webp`,
bg: `/source/nameCard/bg/${getCardInfo.value.name}.webp`,
icon: `/source/nameCard/icon/${getCardInfo.value.name}.webp`,
};
});
let curCard = reactive({ profile: "", bg: "", icon: "", name: "", desc: "" });
// series
const seriesList = ref<TGApp.Sqlite.Achievement.SeriesTable[]>([]);
const allSeriesData = ref<TGApp.Sqlite.Achievement.SeriesTable[]>([]);
const selectedSeries = ref<number>(-1);
const selectedAchievement = ref<TGApp.Sqlite.Achievement.SingleTable[]>([]);
const renderAchievement = computed(() => {
return selectedAchievement.value.slice(start.value, start.value + itemCount.value + 1);
const renderSelect = computed(() => {
if (hideFin.value) {
return selectedAchievement.value.filter((item) => item.isCompleted === 0);
}
return selectedAchievement.value;
});
// virtual list
const start = ref<number>(0);
const itemCount = computed(() => {
return Math.ceil((window.innerHeight - 100) / 76);
});
const emptyHeight = computed(() => {
return selectedAchievement.value.length * 76;
});
const translateY = ref<string>("0px");
// render
const search = ref<string>("");
// route
const route = useRoute();
const router = useRouter();
onBeforeMount(async () => {
const { total, fin } = await TGSqlite.getAchievementsOverview();
await flushOverview();
});
// 更改是否隐藏已完成
async function switchHideFin() {
const text = hideFin.value ? "显示已完成" : "隐藏已完成";
const res = await showConfirm({
title: "是否切换显示已完成?",
text,
});
if (res === false) {
showSnackbar({
color: "warn",
text: "已取消切换",
});
return;
}
hideFin.value = !hideFin.value;
showSnackbar({
text: `${text}`,
});
}
// 刷新概况
async function flushOverview(): Promise<void> {
const { total, fin } = await getAchiOverview();
achievementsStore.flushData(total, fin);
title.value = achievementsStore.title;
});
}
onMounted(async () => {
loading.value = true;
loadingTitle.value = "正在获取成就系列数据";
seriesList.value = await TGSqlite.getAchievementSeries();
achievementsStore.lastVersion = seriesList.value.reduce((prev, curr) => {
return prev.version > curr.version ? prev : curr;
}).version;
allSeriesData.value = await getSeriesData();
achievementsStore.lastVersion = await TGSqlite.getLatestAchievementVersion();
loadingTitle.value = "正在获取成就数据";
selectedAchievement.value = await TGSqlite.getAchievements();
await nextTick(() => {
loading.value = false;
});
selectedAchievement.value = await getAchiData("all");
loading.value = false;
if (route.query.app && typeof route.query.app === "string") {
await handleImportOuter(route.query.app);
} else {
// 等 500ms 动画
setTimeout(() => {
showSnackbar({
text: `已获取 ${renderSelect.value.length} 条成就数据`,
});
}, 500);
}
});
function handleScroll(e: Event): void {
const target: HTMLElement = <HTMLElement>e.target;
// 如果 scrollTop 到底部了
if (target.scrollTop + target.offsetHeight >= target.scrollHeight) {
// 如果 selectedAchievement 的长度小于 itemCount不进行偏移
if (selectedAchievement.value.length <= itemCount.value) {
window.requestAnimationFrame(() => {
start.value = 0;
translateY.value = "0px";
});
return;
}
window.requestAnimationFrame(() => {
start.value = selectedAchievement.value.length - itemCount.value;
translateY.value = `${(selectedAchievement.value.length - itemCount.value) * 76}px`;
});
return;
}
const scrollTop = target.scrollTop;
if (selectedSeries.value !== 0 && selectedSeries.value !== 17 && selectedSeries.value !== -1) {
window.requestAnimationFrame(() => {
if (scrollTop < 86.8) {
start.value = 0;
translateY.value = "0px";
} else {
start.value = Math.floor((scrollTop - 86.8) / 76);
translateY.value = `${scrollTop - 86.8}px`;
}
});
} else {
window.requestAnimationFrame(() => {
start.value = Math.floor(scrollTop / 76);
translateY.value = `${scrollTop}px`;
});
}
}
// 渲染选中的成就系列
async function selectSeries(index: number): Promise<void> {
// 如果选中的是已经选中的系列,则不进行操作
@@ -233,26 +207,37 @@ async function selectSeries(index: number): Promise<void> {
loading.value = true;
loadingTitle.value = "正在获取对应的成就数据";
selectedSeries.value = index;
selectedAchievement.value = await TGSqlite.getAchievements(index);
selectedAchievement.value = await getAchiData("series", index.toString());
loadingTitle.value = "正在查找对应的成就名片";
if (selectedSeries.value !== 0 && selectedSeries.value !== 17) {
getCardInfo.value = await TGSqlite.getNameCard(index);
const cardGet = await TGSqlite.getNameCard(index);
curCard = {
profile: `/source/nameCard/profile/${cardGet.name}.webp`,
bg: `/source/nameCard/bg/${cardGet.name}.webp`,
icon: `/source/nameCard/icon/${cardGet.name}.webp`,
name: cardGet.name,
desc: cardGet.desc,
};
}
// 右侧滚动到顶部
const rightWrap = document.querySelector(".right-wrap");
if (rightWrap) {
rightWrap.scrollTop = 0;
}
await nextTick(() => {
loading.value = false;
// 等 500ms 动画
setTimeout(() => {
showSnackbar({
text: `已获取 ${renderSelect.value.length} 条成就数据`,
});
}, 500);
});
}
// 打开图片
function openImg(): void {
createTGWindow(
getCardImg.value.profile,
"Sub_window",
`Namecard_${getCardInfo.value.name}`,
840,
400,
false,
);
createTGWindow(curCard.profile, "Sub_window", `Namecard_${curCard.name}`, 840, 400, false);
}
async function searchCard(): Promise<void> {
@@ -266,14 +251,23 @@ async function searchCard(): Promise<void> {
selectedSeries.value = -1;
loadingTitle.value = "正在搜索";
loading.value = true;
selectedAchievement.value = await TGSqlite.searchAchievements(search.value);
if (selectedAchievement.value.length === 0) {
showSnackbar({
color: "error",
text: "没有找到对应的成就",
});
}
loading.value = false;
selectedAchievement.value = await getAchiData("search", search.value);
await nextTick(() => {
loading.value = false;
// 等 500ms 动画
setTimeout(() => {
if (renderSelect.value.length === 0) {
showSnackbar({
color: "error",
text: "没有搜索到相关成就",
});
return;
}
showSnackbar({
text: `已获取 ${renderSelect.value.length} 条成就数据`,
});
}, 500);
});
}
// 导入 UIAF 数据,进行数据合并、刷新
@@ -395,8 +389,109 @@ async function handleImportOuter(app: string): Promise<void> {
});
}
}
</script>
// 改变成就状态
async function setAchi(
achievement: TGApp.Sqlite.Achievement.SingleTable,
target: boolean,
): Promise<void> {
const newAchievement = achievement;
if (target) {
// 取消已完成
newAchievement.isCompleted = 1;
newAchievement.completedTime = getNowStr();
} else {
newAchievement.isCompleted = 0;
newAchievement.completedTime = "";
}
renderSelect.value[renderSelect.value.findIndex((item) => item.id === achievement.id)] =
newAchievement;
await setAchiDB(newAchievement);
await flushOverview();
allSeriesData.value[allSeriesData.value.findIndex((item) => item.id === newAchievement.series)] =
(await getSeriesData(newAchievement.series))[0];
showSnackbar({
text: `已将成就 ${newAchievement.name}[${newAchievement.id}] 标记为 ${
target ? "已完成" : "未完成"
}`,
});
}
/* 以下为数据库操作 */
// 获取成就概况
async function getAchiOverview(): Promise<{
total: number;
fin: number;
}> {
const db = await TGSqlite.getDB();
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
const res: Array<{ total: number; fin: number }> = await db.select(sql);
return res[0];
}
// 获取成就系列
async function getSeriesData(series?: number): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
const db = await TGSqlite.getDB();
let sql = "SELECT * FROM AchievementSeries ORDER BY `order`;";
if (series) {
sql = `SELECT *
FROM AchievementSeries
WHERE id = ${series}
ORDER BY \`order\`;`;
}
return await db.select(sql);
}
// 获取成就(某个系列)
async function getAchiData(
type: "all" | "series" | "search",
value?: string,
): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await TGSqlite.getDB();
let sql = "";
if (type === "all" || (type == "series" && value === undefined)) {
sql = "SELECT * FROM Achievements ORDER BY isCompleted, `order`;";
} else if (type === "series") {
sql = `SELECT *
FROM Achievements
WHERE series = ${value}
ORDER BY isCompleted, \`order\`;`;
} else if (type === "search") {
if (value === undefined) {
showSnackbar({
color: "error",
text: "搜索内容不能为空",
});
return [];
}
if (value.startsWith("v")) {
const version = value.replace("v", "");
sql = `SELECT *
FROM Achievements
WHERE version LIKE '%${version}%'
ORDER BY isCompleted, \`order\`;`;
} else {
sql = `SELECT *
FROM Achievements
WHERE name LIKE '%${value}%'
OR description LIKE '%${value}%'
ORDER BY isCompleted, \`order\`;`;
}
}
return await db.select(sql);
}
// 更新成就数据
async function setAchiDB(achievement: TGApp.Sqlite.Achievement.SingleTable): Promise<void> {
const db = await TGSqlite.getDB();
const sql = `UPDATE Achievements
SET isCompleted = ${achievement.isCompleted},
completedTime = '${achievement.completedTime}'
WHERE id = ${achievement.id};`;
await db.execute(sql);
}
</script>
<!-- 顶部栏跟 wrap 大概布局 -->
<style lang="css" scoped>
.top-bar {
display: flex;
@@ -415,6 +510,7 @@ async function handleImportOuter(app: string): Promise<void> {
.top-title {
color: var(--common-text-title);
cursor: pointer;
}
.top-btns {
@@ -437,25 +533,43 @@ async function handleImportOuter(app: string): Promise<void> {
/* 左侧系列 */
.left-wrap {
display: flex;
width: 400px;
height: 100%;
flex-direction: column;
padding-right: 10px;
overflow-y: auto;
row-gap: 10px;
}
/* 右侧成就 */
.right-wrap {
display: flex;
width: 100%;
height: 100%;
overflow-y: auto;
flex-direction: column;
padding-right: 10px;
overflow-y: scroll;
row-gap: 10px;
}
.list-empty {
</style>
<!-- 左侧成就系列 wrap -->
<style lang="css" scoped>
.card-series {
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
border-radius: 10px;
background: var(--box-bg-1);
color: var(--box-text-1);
column-gap: 10px;
cursor: pointer;
}
/* 版本信息 */
.version-icon-series {
.series-version {
position: absolute;
right: 0;
bottom: 0;
@@ -463,6 +577,7 @@ async function handleImportOuter(app: string): Promise<void> {
border-top: 1px solid var(--common-shadow-1);
border-left: 1px solid var(--common-shadow-1);
background: var(--box-bg-2);
border-bottom-right-radius: 10px;
border-top-left-radius: 20px;
color: var(--tgc-yellow-1);
font-family: var(--font-title);
@@ -470,69 +585,129 @@ async function handleImportOuter(app: string): Promise<void> {
text-align: center;
}
.version-icon-single {
.series-icon {
width: 40px;
height: 40px;
padding: 5px;
border-radius: 5px;
background:
linear-gradient(to bottom, rgb(255 255 255 / 15%) 0%, rgb(0 0 0 / 15%) 100%),
radial-gradient(at top center, rgb(255 255 255 / 40%) 0%, rgb(0 0 0 / 40%) 120%) #989898;
background-blend-mode: multiply, multiply;
}
.series-content {
display: flex;
width: 100%;
flex-flow: column wrap;
align-items: flex-start;
justify-content: center;
color: var(--box-text-1);
text-align: left;
}
.series-content :nth-child(1) {
font-family: var(--font-title);
font-size: 14px;
}
.series-content :nth-child(2) {
font-size: 12px;
opacity: 0.8;
}
</style>
<!-- 右侧成就 -->
<style lang="css" scoped>
/* 成就卡片 */
.achi-series {
position: relative;
width: 100%;
height: 80px;
border: 1px solid var(--common-shadow-2);
border-radius: 10px 50px 50px 10px;
background-position: right;
background-repeat: no-repeat;
cursor: pointer;
font-family: var(--font-title);
}
.card-achi {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-radius: 10px;
background: var(--box-bg-1);
color: var(--box-text-7);
}
/* 成就进度 */
.achi-version {
position: absolute;
top: 0;
left: 0;
width: 50px;
border-right: 1px solid var(--common-shadow-1);
border-bottom: 1px solid var(--common-shadow-1);
background: var(--box-bg-2);
border-bottom-right-radius: 20px;
border-top-left-radius: 10px;
color: var(--tgc-pink-1);
font-family: var(--font-title);
font-size: 10px;
text-align: center;
}
.card-left {
border-radius: 10px;
margin-right: 10px;
margin-bottom: 10px;
background: var(--box-bg-1);
color: var(--box-text-1);
cursor: pointer;
.achi-pre {
display: flex;
align-items: center;
justify-content: flex-start;
column-gap: 10px;
}
/* 成就卡片 */
.card-right {
border-radius: 10px;
margin: 10px;
background: var(--box-bg-1);
color: var(--box-text-7);
}
/* 成就进度 */
.achievement-progress {
position: absolute;
top: 0;
left: 0;
width: 65px;
border-right: 1px solid var(--common-shadow-1);
border-bottom: 1px solid var(--common-shadow-1);
background: var(--box-bg-2);
border-bottom-right-radius: 20px;
color: var(--box-text-3);
font-family: var(--font-title);
font-size: 10px;
text-align: center;
.achi-pre-icon {
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
}
.achievement-finish img {
width: 30px;
height: 30px;
filter: invert(51%) sepia(100%) saturate(353%) hue-rotate(42deg) brightness(107%) contrast(91%);
}
/* 成就完成时间 */
.right-time {
margin-right: 10px;
color: var(--box-text-4);
font-size: small;
.achi-pre-info {
display: flex;
width: 100%;
flex-flow: column wrap;
align-items: flex-start;
justify-content: center;
text-align: left;
}
/* 成就奖励 */
.reward-card {
position: relative;
width: 40px;
height: 40px;
border-radius: 5px;
background-image: url("/icon/bg/5-Star.webp");
background-size: cover;
.achi-pre-info :nth-child(1) {
display: flex;
align-items: flex-end;
column-gap: 5px;
font-family: var(--font-title);
font-size: 14px;
}
.reward-num {
.achi-pre-info :nth-child(1) :nth-child(2) {
color: var(--tgc-blue-2);
font-size: 12px;
}
.achi-pre-info :nth-child(2) {
font-size: 12px;
opacity: 0.8;
}
.achi-append-icon span {
position: absolute;
bottom: 0;
left: 0;
@@ -542,7 +717,36 @@ async function handleImportOuter(app: string): Promise<void> {
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 50%);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
color: var(--tgc-white-1);
font-size: 8px;
}
.achi-append {
display: flex;
align-items: center;
justify-content: flex-end;
column-gap: 10px;
}
.achi-append :nth-last-child(2) {
margin-right: 10px;
color: var(--box-text-4);
font-size: small;
}
.achi-append-icon {
position: relative;
width: 40px;
height: 40px;
border-radius: 5px;
background-image: url("/icon/bg/5-Star.webp");
background-size: cover;
}
.achi-append-icon img {
width: 40px;
height: 40px;
}
</style>

View File

@@ -123,6 +123,7 @@
/>
</template>
</v-list-item>
<v-list-item prepend-icon="mdi-refresh" title="刷新设备信息" @click="confirmUpdateDevice" />
<v-list-item prepend-icon="mdi-database-remove" title="清除缓存" @click="confirmDelCache" />
<v-list-item
v-show="showReset"
@@ -161,8 +162,9 @@ import { useAppStore } from "../../store/modules/app";
import { useHomeStore } from "../../store/modules/home";
import { useUserStore } from "../../store/modules/user";
import { getBuildTime } from "../../utils/TGBuild";
import { bytesToSize, getCacheDir } from "../../utils/toolFunc";
import { bytesToSize, getCacheDir, getDeviceInfo } from "../../utils/toolFunc";
import { backupUiafData, restoreUiafData } from "../../utils/UIAF";
import { getDeviceFp } from "../../web/request/getDeviceFp";
import TGRequest from "../../web/request/TGRequest";
import { backupAbyssData, backupCookieData } from "../../web/utils/backupData";
import { restoreAbyssData, restoreCookieData } from "../../web/utils/restoreData";
@@ -201,13 +203,22 @@ const showReset = ref<boolean>(false);
// data
const showHome = ref<string[]>(homeStore.getShowValue());
const userInfo = computed(() => {
const info = userStore.getBriefInfo();
return {
nickname: info.nickname || "未登录",
uid: info.uid || "-1",
desc: info.desc || "未登录",
avatar: info.avatar || "/source/UI/defaultUser.webp",
};
if (!appStore.isLogin) {
return {
nickname: "未登录",
uid: "-1",
desc: "请扫码登录",
avatar: "/source/UI/defaultUser.webp",
};
} else {
const info = userStore.getBriefInfo();
return {
nickname: info.nickname,
uid: info.uid,
desc: info.desc,
avatar: info.avatar,
};
}
});
const vuetifyTheme = computed(() => {
return appStore.theme === "dark" ? "dark" : "light";
@@ -276,8 +287,13 @@ async function confirmRefreshUser(): Promise<void> {
color: "error",
text: "扫码登录后才能刷新用户信息!",
});
appStore.isLogin = false;
return;
}
const deviceInfo = appStore.deviceInfo;
if (deviceInfo.device_fp === "00000000000") {
appStore.deviceInfo = await getDeviceFp(appStore.deviceInfo);
}
let failCount = 0;
loadingTitle.value = "正在验证 ltoken...";
loading.value = true;
@@ -343,6 +359,7 @@ async function confirmRefreshUser(): Promise<void> {
});
} else {
showSnackbar({ text: "刷新成功!" });
appStore.isLogin = true;
}
}
@@ -443,6 +460,28 @@ async function confirmUpdate(title?: string): Promise<void> {
window.location.reload();
}
// 更新设备信息
async function confirmUpdateDevice(): Promise<void> {
const localFp = getDeviceInfo("device_fp");
if (localFp !== "0000000000000") {
const res = await showConfirm({
title: "确认更新设备信息吗?",
text: `DeviceFp:${localFp}`,
});
if (res === false) {
showSnackbar({
text: "已取消更新设备信息",
color: "cancel",
});
return;
}
}
appStore.deviceInfo = await TGRequest.Device.getFp(appStore.deviceInfo);
showSnackbar({
text: "设备信息已更新! DeviceFp: " + appStore.deviceInfo.device_fp,
});
}
// 清除用户缓存
async function confirmDelCache(): Promise<void> {
const CacheDir = await getCacheDir();
@@ -526,7 +565,7 @@ async function tryShowReset(): Promise<void> {
}
const time = getBuildTime();
const code = time.startsWith("dev.") ? "dev" : time;
if (res === code) {
if (res === code || res === "reset1128") {
showReset.value = true;
showSnackbar({
text: "已开启重置数据库选项",
@@ -611,7 +650,10 @@ function submitHome(): void {
border: 1px solid var(--common-shadow-1);
border-radius: 10px;
margin-right: 15px;
background: var(--box-bg-2);
background:
linear-gradient(to bottom, rgb(255 255 255 / 15%) 0%, rgb(0 0 0 / 15%) 100%),
radial-gradient(at top center, rgb(255 255 255 / 40%) 0%, rgb(0 0 0 / 40%) 120%) #989898;
background-blend-mode: multiply, multiply;
}
.config-btn {

View File

@@ -106,7 +106,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onBeforeMount, onMounted, ref } from "vue";
import { nextTick, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import showSnackbar from "../../components/func/snackbar";
@@ -172,12 +172,6 @@ const rawData = ref<RawData>({
},
});
onBeforeMount(() => {
if (gid === "5") {
tabValues.value = ["notice", "activity"];
}
});
onMounted(async () => {
tab.value = "notice";
await firstLoad("notice");

View File

@@ -13,34 +13,9 @@
</div>
</div>
</div>
<h1>角色详情beta</h1>
<div class="test-item">
<div class="role-box">
{{ JSON.stringify(roleItem, null, 2) }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import TGSqlite from "../../plugins/Sqlite";
import { useUserStore } from "../../store/modules/user";
const visible = ref<boolean>(false);
const userStore = useUserStore();
const roleItem = ref<TGApp.Sqlite.Character.UserRole>();
onMounted(async () => {
const user = userStore.getCurAccount();
const roleData = await TGSqlite.getUserCharacter(user.gameUid);
if (roleData !== false) {
roleItem.value = roleData[0];
}
visible.value = false;
});
</script>
<script lang="ts" setup></script>
<style lang="css" scoped>
.test-box {
display: flex;
@@ -91,15 +66,4 @@ onMounted(async () => {
.test-4 {
background: var(--box-bg-4);
}
.role-box {
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 5px;
background: var(--box-bg-1);
color: var(--box-text-1);
gap: 10px;
white-space: pre-wrap;
}
</style>

View File

@@ -15,13 +15,12 @@ declare namespace TGApp.Plugins.Mys.Emoji {
* @description 获取表情包列表返回
* @since Beta v0.3.0
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {Series[]} data.list 表情包列表
* @property {unknown} data.recently_emoticon 最近使用的表情包
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: {
list: Series[];
recently_emoticon: unknown;

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.Gacha {
* @description 获取卡池信息返回
* @since Alpha v0.2.1
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {Data[]} data.list 卡池数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: {
list: Data[];
};

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.GameLogin {
* @description 获取登录二维码返回数据
* @since Beta v0.3.0
* @interface GetLoginQrResponse
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {GetLoginQrData} data 数据
* @return GetLoginQrResponse
*/
interface GetLoginQrResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface GetLoginQrResponse extends TGApp.BBS.Response.BaseWithData {
data: GetLoginQrData;
}
@@ -39,12 +38,11 @@ declare namespace TGApp.Plugins.Mys.GameLogin {
* @description 获取登录状态返回数据
* @since Beta v0.3.0
* @interface GetLoginStatusResponse
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {GetLoginStatusData} data 数据
* @return GetLoginStatusResponse
*/
interface GetLoginStatusResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface GetLoginStatusResponse extends TGApp.BBS.Response.BaseWithData {
data: GetLoginStatusData;
}

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.Lottery {
* @description 抽奖返回数据
* @since Alpha v0.2.1
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullData} data.show_lottery 抽奖数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: {
show_lottery: FullData;
};

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.News {
* @description 咨讯返回数据
* @since Alpha v0.2.1
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullData} data 咨讯数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: FullData;
}

View File

@@ -13,16 +13,15 @@
declare namespace TGApp.Plugins.Mys.Obc {
/**
* @description Mys obc 返回数据
* @since Alpha v0.2.1
* @since Beta v0.3.6
* @interface Response
* @extends TGApp.BBS.Response.Base
* @property {Obc[]} data.list obc 列表
* @extends TGApp.BBS.Response.BaseWithData
* @property {Data[]} data.list obc 列表
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: {
list: Obc[];
list: Data[];
};
}

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.Position {
* @description 热点追踪信息的返回类型
* @since Alpha v0.2.1
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {ObcItem[]} data.list obc 列表
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: {
list: ObcItem[];
};

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.Post {
* @description 帖子返回数据
* @since Alpha v0.2.1
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullData} data.post 帖子数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: {
post: FullData;
};

View File

@@ -21,8 +21,21 @@ declare namespace TGApp.Plugins.Mys.SctPost {
* @return Base
*/
interface Base {
insert: any;
attributes?: any;
}
/**
* @description 帖子结构化数据-空类型
* @since Beta v0.3.4
* @interface Empty
* @property {never} insert - 帖子内容
* @property {never} attributes - 帖子属性
* @return Empty
*/
interface Empty {
insert: never;
attributes: never;
attributes?: never;
}
/**
@@ -41,7 +54,8 @@ declare namespace TGApp.Plugins.Mys.SctPost {
| Text
| Video
| VillaCard
| Vod;
| Vod
| Empty;
/**
* @description 帖子结构化数据-其他类型

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Plugins.Mys.User {
* @description 主页用户信息返回
* @since Alpha v0.2.1
* @interface HomeResponse
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {Home} data 用户信息
* @return HomeResponse
*/
interface HomeResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface HomeResponse extends TGApp.BBS.Response.BaseWithData {
data: Home;
}

View File

@@ -163,16 +163,16 @@ function transferParser(data: TGApp.Plugins.Mys.SctPost.Common): HTMLDivElement
} else if ("villa_card" in data.insert) {
return parseVillaCard(<TGApp.Plugins.Mys.SctPost.VillaCard>data);
}
return parseUnknown(<TGApp.Plugins.Mys.SctPost.Base>data);
return parseUnknown(<TGApp.Plugins.Mys.SctPost.Empty>data);
}
/**
* @description 解析未知数据
* @since Beta v0.3.4
* @param {TGApp.Plugins.Mys.SctPost.Base} data Mys数据
* @param {TGApp.Plugins.Mys.SctPost.Empty} data Mys数据
* @returns {HTMLDivElement} 解析后的未知数据
*/
function parseUnknown(data: TGApp.Plugins.Mys.SctPost.Base): HTMLDivElement {
function parseUnknown(data: TGApp.Plugins.Mys.SctPost.Empty): HTMLDivElement {
const div = document.createElement("div");
div.classList.add("mys-post-unknown");
const code = document.createElement("code");
@@ -251,7 +251,7 @@ function parseDivider(data: TGApp.Plugins.Mys.SctPost.Divider): HTMLDivElement {
const dividerList = ["line_1", "line_2", "line_3", "line_4"];
if (!dividerList.includes(data.insert.divider)) {
console.error("Unknown divider type", data);
return parseUnknown(<TGApp.Plugins.Mys.SctPost.Base>data);
return parseUnknown(<TGApp.Plugins.Mys.SctPost.Empty>data);
}
img.src = `/source/post/divider_${data.insert.divider}.webp`;
div.appendChild(img);

View File

@@ -1,7 +1,7 @@
/**
* @file plugins Sqlite index.ts
* @file plugins/Sqlite/index.ts
* @description Sqlite 数据库操作类
* @since Beta v0.3.3
* @since Beta v0.3.6
*/
import { app } from "@tauri-apps/api";
@@ -198,17 +198,6 @@ class Sqlite {
await this.initDB();
}
/**
* @description 获取成就系列列表
* @since Beta v0.3.3
* @returns {Promise<TGApp.Sqlite.Achievement.SeriesTable[]>}
*/
public async getAchievementSeries(): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
const db = await this.getDB();
const sql = "SELECT * FROM AchievementSeries ORDER BY `order`;";
return await db.select(sql);
}
/**
* @description 获取成就系列对应的名片
* @since Beta v0.3.3
@@ -224,41 +213,6 @@ class Sqlite {
return res[0];
}
/**
* @description 获取成就列表
* @since Beta v0.3.3
* @param {number} [seriesId] 系列 ID
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>}
*/
public async getAchievements(seriesId?: number): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await this.getDB();
let sql;
if (seriesId) {
sql = `SELECT *
FROM Achievements
WHERE series = ${seriesId}
ORDER BY isCompleted, \`order\`;`;
} else {
sql = "SELECT * FROM Achievements ORDER BY isCompleted, `order`;";
}
return await db.select(sql);
}
/**
* @description 获取成就概况
* @since Beta v0.3.3
* @returns {Promise<{total:number,fin:number}>}
*/
public async getAchievementsOverview(): Promise<{
total: number;
fin: number;
}> {
const db = await this.getDB();
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
const res: Array<{ total: number; fin: number }> = await db.select(sql);
return res[0];
}
/**
* @description 获取最新成就版本
* @since Beta v0.3.3
@@ -271,33 +225,6 @@ class Sqlite {
return res[0].version;
}
/**
* @description 查询成就
* @since Beta v0.3.3
* @param {string} keyword 关键词
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>}
*/
public async searchAchievements(
keyword: string,
): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await this.getDB();
let sql;
if (keyword.startsWith("v")) {
const version = keyword.replace("v", "");
sql = `SELECT *
FROM Achievements
WHERE version LIKE '%${version}%'
ORDER BY isCompleted, \`order\`;`;
} else {
sql = `SELECT *
FROM Achievements
WHERE name LIKE '%${keyword}%'
OR description LIKE '%${keyword}%'
ORDER BY isCompleted, \`order\`;`;
}
return await db.select(sql);
}
/**
* @description 合并 UIAF 数据
* @since Beta v0.3.3
@@ -521,6 +448,21 @@ class Sqlite {
await db.execute(item);
}
}
/**
* @description 判断今天是否是某个角色的生日
* @since Beta v0.3.6
* @returns {Promise<false|string>}
*/
public async isBirthday(): Promise<false | string> {
const db = await this.getDB();
const dateNow = new Date();
const date = `${dateNow.getMonth() + 1},${dateNow.getDate()}`;
const sql = `SELECT name FROM AppCharacters WHERE birthday = '${date}';`;
const res: Array<{ name: string }> = await db.select(sql);
if (res.length === 0) return false;
return res.map((item) => item.name).join("、");
}
}
const TGSqlite = new Sqlite();

View File

@@ -1,13 +1,15 @@
/**
* @file store modules app.ts
* @file store/modules/app.ts
* @description App store module
* @since Beta v0.3.3
* @since Beta v0.3.6
*/
import { path } from "@tauri-apps/api";
import { defineStore } from "pinia";
import { reactive, ref } from "vue";
import { getInitDeviceInfo } from "../../utils/toolFunc";
// 用于存储用户数据的路径
const userDataDir = `${await path.appLocalDataDir()}userData`;
// 用于存放数据库的路径
@@ -34,6 +36,8 @@ export const useAppStore = defineStore(
const devMode = ref(false);
// 应用主题
const theme = ref("default");
// 是否登录
const isLogin = ref(false);
const dataPath = reactive({
userDataDir,
@@ -43,6 +47,8 @@ export const useAppStore = defineStore(
const userPath = ref({
UIAF: `${dataPath.userDataDir}/UIAF.json`,
});
// 设备信息
const deviceInfo = ref<TGApp.App.Device.DeviceInfo>(getInitDeviceInfo());
// 初始化
function init(): void {
@@ -52,6 +58,8 @@ export const useAppStore = defineStore(
wiki: false,
};
theme.value = "default";
isLogin.value = false;
initDevice();
}
function getSubmenu(): string[] {
@@ -65,6 +73,10 @@ export const useAppStore = defineStore(
else theme.value = "default";
}
function initDevice(): void {
deviceInfo.value = getInitDeviceInfo();
}
return {
theme,
loading,
@@ -73,6 +85,8 @@ export const useAppStore = defineStore(
devMode,
dataPath,
userPath,
deviceInfo,
isLogin,
init,
getSubmenu,
changeTheme,
@@ -88,7 +102,7 @@ export const useAppStore = defineStore(
{
key: "app",
storage: window.localStorage,
paths: ["devMode", "loading", "buildTime"],
paths: ["devMode", "loading", "buildTime", "isLogin"],
},
{
key: "sidebar",
@@ -100,6 +114,11 @@ export const useAppStore = defineStore(
storage: window.localStorage,
paths: ["theme"],
},
{
key: "deviceInfo",
storage: window.localStorage,
paths: ["deviceInfo"],
},
],
},
);

32
src/types/App/Device.d.ts vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* @file types/App/Device.d.ts
* @description App 设备信息类型定义文件
* @since Beta v0.3.6
*/
/**
* @description App 设备信息类型 namespace
* @since Beta v0.3.6
* @namespace TGApp.App.Device
* @memberof TGApp.App
*/
declare namespace TGApp.App.Device {
/**
* @description 设备信息
* @since Beta v0.3.6
* @interface DeviceInfo
* @property {string} device_id - 设备 ID
* @property {string} model - 设备型号
* @property {string} seed_id - 种子 ID
* @property {string} seed_time - 种子时间
* @property {string} device_fp - 设备指纹
* @return DeviceInfo
*/
interface DeviceInfo {
device_id: string;
model: string;
seed_id: string;
seed_time: string;
device_fp: string;
}
}

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.BBS.Announcement {
* @description 公告列表返回响应类型
* @interface ListResponse
* @since Alpha v0.1.5
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {ListData} data - 公告列表数据
* @return ListResponse
*/
interface ListResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface ListResponse extends TGApp.BBS.Response.BaseWithData {
data: ListData;
}
@@ -28,12 +27,11 @@ declare namespace TGApp.BBS.Announcement {
* @description 公告内容返回响应类型
* @interface ContentResponse
* @since Alpha v0.1.5
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {ContentData} data - 公告内容数据
* @return ContentResponse
*/
interface ContentResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface ContentResponse extends TGApp.BBS.Response.BaseWithData {
data: ContentData;
}

View File

@@ -1,12 +1,12 @@
/**
* @file types/BBS/Response.d.ts
* @description BBS 返回数据类型定义文件
* @since Beta v0.3.5
* @since Beta v0.3.6
*/
/**
* @description BBS 返回数据类型定义
* @since Beta v0.3.5
* @since Beta v0.3.6
* @namespace TGApp.BBS.Response
* @memberof TGApp.BBS
*/
@@ -15,9 +15,9 @@ declare namespace TGApp.BBS.Response {
* @description 基础返回类型,设计米游社接口请求都是这个类型
* @interface Base
* @since Beta v0.3.5
* @property {number} retcode - 响应代码
* @property {never} retcode - 响应代码
* @property {string} message - 响应消息
* @property {any} data - 响应数据
* @property {never} data - 响应数据
* @return Base
*/
interface Base {
@@ -26,6 +26,21 @@ declare namespace TGApp.BBS.Response {
data: never;
}
/**
* @description 基础返回类型-带有 data 的
* @interface BaseWithData
* @since Beta v0.3.6
* @property {0} retcode - 响应代码
* @property {string} message - 响应消息
* @property {any} data - 响应数据
* @return BaseWithData
*/
interface BaseWithData {
retcode: 0;
message: string;
data: any;
}
/**
* @description 获取 ltoken 跟 stoken 的响应数据返回
* @interface getTokensRes
@@ -43,12 +58,11 @@ declare namespace TGApp.BBS.Response {
* @description 获取 ltoken 跟 stoken 的响应数据
* @interface getTokens
* @since Alpha v0.1.5
* @extends Base
* @extends BaseWithData
* @property {getTokensRes[]} data.list - token 列表
* @return getTokens
*/
interface getTokens extends Base {
retcode: 0;
interface getTokens extends BaseWithData {
data: {
list: getTokensRes[];
};
@@ -58,12 +72,11 @@ declare namespace TGApp.BBS.Response {
* @description 根据 stoken 获取 ltoken 的响应数据
* @interface getLTokenBySToken
* @since Alpha v0.1.5
* @extends Base
* @extends BaseWithData
* @property {string} data.ltoken - ltoken 值
* @return getLTokenBySToken
*/
interface getLTokenBySToken extends Base {
retcode: 0;
interface getLTokenBySToken extends BaseWithData {
data: {
ltoken: string;
};
@@ -73,13 +86,12 @@ declare namespace TGApp.BBS.Response {
* @description 根据 stoken 获取 cookie_token 的响应数据
* @interface getCookieTokenBySToken
* @since Alpha v0.1.5
* @extends Base
* @extends BaseWithData
* @property {string} data.uid - 用户 uid
* @property {string} data.cookie_token - cookie_token 值
* @return getCookieTokenBySToken
*/
interface getCookieTokenBySToken extends Base {
retcode: 0;
interface getCookieTokenBySToken extends BaseWithData {
data: {
uid: string;
cookie_token: string;
@@ -90,14 +102,13 @@ declare namespace TGApp.BBS.Response {
* @description 通过 stoken 验证用户信息的返回类型
* @interface verifyUserInfoBySToken
* @since Alpha v0.1.5
* @extends Base
* @extends BaseWithData
* @property {TGApp.BBS.Account.VerifySTokenInfo} data.user_info - 用户信息
* @property {unknown} data.realname_info - 实名信息
* @property {boolean} data.need_realperson - 是否需要实名认证
* @return verifyUserInfoBySToken
*/
interface verifyUserInfoBySToken extends Base {
retcode: 0;
interface verifyUserInfoBySToken extends BaseWithData {
data: {
user_info: TGApp.BBS.Account.VerifySTokenInfo;
realname_info: unknown;
@@ -109,12 +120,11 @@ declare namespace TGApp.BBS.Response {
* @description 通过 gameToken 获取 stoken 的返回类型
* @interface getStokenByGameToken
* @since Beta v0.3.0
* @extends Base
* @extends BaseWithData
* @property {getStokenByGameTokenData} data - 返回数据
* @return getStokenByGameToken
*/
interface getStokenByGameToken extends Base {
retcode: 0;
interface getStokenByGameToken extends BaseWithData {
data: getStokenByGameTokenData;
}
@@ -143,13 +153,12 @@ declare namespace TGApp.BBS.Response {
* @description 通过 gameToken 获取 cookie_token 的返回类型
* @interface getCookieTokenByGameToken
* @since Beta v0.3.0
* @extends Base
* @extends BaseWithData
* @property {string} data.uid - 用户 uid
* @property {string} data.cookie_token - cookie_token 值
* @return getCookieTokenByGameToken
*/
interface getCookieTokenByGameToken extends Base {
retcode: 0;
interface getCookieTokenByGameToken extends BaseWithData {
data: {
uid: string;
cookie_token: string;
@@ -160,18 +169,35 @@ declare namespace TGApp.BBS.Response {
* @description 通过 sToken 获取 actionTicket 的返回类型
* @interface getActionTicketBySToken
* @since Beta v0.3.4
* @extends Base
* @extends BaseWithData
* @property {string} data.ticket - actionTicket 值
* @property {boolean} data.is_verified - 是否验证
* @property {TGApp.BBS.Account.getActionTicketBySTokenInfo} data.account_info - 用户信息
* @return getActionTicketBySToken
*/
interface getActionTicketBySToken extends Base {
retcode: 0;
interface getActionTicketBySToken extends BaseWithData {
data: {
ticket: string;
is_verified: boolean;
account_info: TGApp.BBS.Account.getActionTicketBySTokenInfo;
};
}
/**
* @description 获取 deviceFp 的返回类型
* @interface getDeviceFp
* @since Beta v0.3.6
* @extends BaseWithData
* @property {string} data.device_fp - deviceFp 值
* @property {number} data.code - code 值
* @property {string} data.msg - msg 值
* @return getDeviceFp
*/
interface getDeviceFp extends BaseWithData {
data: {
device_fp: string;
code: number;
msg: string;
};
}
}

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Game.Abyss {
* @description 深渊数据返回类型
* @interface Response
* @since Alpha v0.2.0
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullData} data - 深渊数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: FullData;
}

View File

@@ -16,12 +16,11 @@ declare namespace TGApp.Game.Calculate {
* @since Alpha v0.2.1
* @see TGRequest.User.calculate.getSyncAvatarListAll
* @interface SyncAvatarListResponse
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {Array<AvatarListItem>} data.list - 角色列表
* @return SyncAvatarListResponse
*/
interface SyncAvatarListResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface SyncAvatarListResponse extends TGApp.BBS.Response.BaseWithData {
data: {
list: AvatarListItem[];
};
@@ -57,12 +56,11 @@ declare namespace TGApp.Game.Calculate {
* @since Alpha v0.2.1
* @see TGRequest.User.calculate.getSyncAvatarDetail
* @interface SyncAvatarDetailResponse
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {AvatarDetail} data - 角色详情
* @return SyncAvatarDetailResponse
*/
interface SyncAvatarDetailResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface SyncAvatarDetailResponse extends TGApp.BBS.Response.BaseWithData {
data: TGApp.Game.Calculate.AvatarDetail;
}

View File

@@ -15,13 +15,12 @@ declare namespace TGApp.Game.Character {
* @description 角色列表数据返回类型
* @interface ListResponse
* @since Alpha v0.2.0
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {ListItem[]} data.avatars - 角色列表
* @property {ListRole} data.role - 角色信息
* @return ListResponse
*/
interface ListResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface ListResponse extends TGApp.BBS.Response.BaseWithData {
data: {
avatars: ListItem[];
role: ListRole;

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Game.DailyNotes {
* @description 便笺数据返回
* @since Alpha v0.2.2
* @interface Response
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullInfo} data - 便笺数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: FullInfo;
}

View File

@@ -15,14 +15,13 @@ declare namespace TGApp.Game.Gacha {
* @description 获取 authkey 返回类型
* @interface AuthkeyResponse
* @since Beta v0.3.0
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {number} data.sign_type - 签名类型
* @property {number} data.authkey_ver - authkey 版本
* @property {string} data.authkey - authkey
* @return AuthkeyResponse
*/
interface AuthkeyResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface AuthkeyResponse extends TGApp.BBS.Response.BaseWithData {
data: {
sign_type: number;
authkey_ver: number;
@@ -34,15 +33,14 @@ declare namespace TGApp.Game.Gacha {
* @description 获取抽卡记录返回类型
* @interface GachaLogResponse
* @since Beta v0.3.0
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {number} data.page - 页码
* @property {number} data.size - 每页大小
* @property {number} data.total - 总数
* @property {GachaItem[]} data.list - 抽卡记录列表
* @return GachaLogResponse
*/
interface GachaLogResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface GachaLogResponse extends TGApp.BBS.Response.BaseWithData {
data: {
page: number;
size: number;

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.Game.Record {
* @description 原神战绩数据返回类型
* @interface Response
* @since Alpha v0.2.0
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {FullData} data - 原神战绩数据
* @return Response
*/
interface Response extends TGApp.BBS.Response.Base {
retcode: 0;
interface Response extends TGApp.BBS.Response.BaseWithData {
data: FullData;
}

View File

@@ -15,12 +15,11 @@ declare namespace TGApp.User.Account {
* @description 游戏账号返回类型
* @interface GameResponse
* @since Alpha v0.1.5
* @extends TGApp.BBS.Response.Base
* @extends TGApp.BBS.Response.BaseWithData
* @property {Game[]} data.list 游戏账号列表
* @return GameResponse
*/
interface GameResponse extends TGApp.BBS.Response.Base {
retcode: 0;
interface GameResponse extends TGApp.BBS.Response.BaseWithData {
data: {
list: Game[];
};

View File

@@ -1,14 +1,15 @@
/**
* @file utils/TGClient.ts
* @desc 负责米游社客户端的 callback 处理
* @since Beta v0.3.5
* @since Beta v0.3.6
*/
import { event, invoke, path } from "@tauri-apps/api";
import { event, invoke } from "@tauri-apps/api";
import type { Event } from "@tauri-apps/api/event";
import { WebviewWindow } from "@tauri-apps/api/window";
import { getDeviceID } from "./toolFunc";
import { getDeviceInfo } from "./toolFunc";
import { useAppStore } from "../store/modules/app";
import { useUserStore } from "../store/modules/user";
import TGConstant from "../web/constant/TGConstant";
import TGRequest from "../web/request/TGRequest";
@@ -108,7 +109,7 @@ class TGClient {
/**
* @func getUrl
* @since Beta v0.3.5
* @since Beta v0.3.6
* @desc 获取 url
* @param {string} func - 方法名
* @returns {string} - url
@@ -124,17 +125,17 @@ class TGClient {
case "tavern":
return "https://m.miyoushe.com/ys/#/home/26";
case "birthday":
return "https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html?game_biz=hk4e_cn&bbs_presentation_style=fullscreen&bbs_auth_required=true&bbs_landscape=true&activity_id=20220301153521&mhy_hide_status_bar=true&utm_source=bbs&utm_medium=mys&utm_campaign=arti";
return "https://webstatic.mihoyo.com/ys/event/e20220303-birthday/index.html?activity_id=20220301153521";
case "toolbox":
return "https://webstatic.mihoyo.com/bbs/event/e20200511toolbox/index.html?game_biz=ys_cn";
default:
return this.getUrl("daily_note");
return "https://api-static.mihoyo.com/";
}
}
/**
* @func open
* @since Beta v0.3.4
* @since Beta v0.3.6
* @desc 打开米游社客户端
* @param {string} func - 方法名
* @param {string} url - url
@@ -142,7 +143,15 @@ class TGClient {
*/
async open(func: string, url?: string): Promise<void> {
if (this.window !== null) {
await this.window.close();
try {
await this.window.close();
} catch (e) {
await invoke<InvokeArg>("create_mhy_client", {
func: "default",
url: "https://api-static.mihoyo.com/",
});
await this.open(func, url);
}
}
if (url === undefined) {
url = this.getUrl(func);
@@ -321,16 +330,23 @@ class TGClient {
/**
* @func getHTTPRequestHeaders
* @since Beta v0.3.4
* @since Beta v0.3.6
* @desc 获取米游社客户端的 HTTP 请求头
* @param {string} callback - 回调函数名
* @returns {void} - 无返回值
*/
async getHTTPRequestHeaders(callback: string): Promise<void> {
const localFp = getDeviceInfo("device_fp");
let deviceInfo = useAppStore().deviceInfo;
if (localFp === "0000000000000") {
deviceInfo = await TGRequest.Device.getFp(deviceInfo);
}
const data = {
"user-agent": TGConstant.BBS.UA_MOBILE,
"x-rpc-client_type": "5",
"x-rpc-device_id": getDeviceID(),
"x-rpc-device_id": deviceInfo.device_id,
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-device_fp": deviceInfo.device_fp,
};
await this.callback(callback, data);
}
@@ -406,14 +422,14 @@ class TGClient {
/**
* @func closePage
* @since Beta v0.3.4
* @since Beta v0.3.6
* @desc 关闭米游社客户端的页面
* @returns {void} - 无返回值
*/
async closePage(): Promise<void> {
this.route.pop();
if (this.route.length === 0) {
await this.window?.hide();
await this.window?.close();
return;
}
const url = this.route[this.route.length - 1];
@@ -435,24 +451,23 @@ class TGClient {
/**
* @func onClickImg
* @since Beta v0.3.5
* @since Beta v0.3.6
* @desc 点击图片,下载到本地
* @param {unknown} payload - 请求参数
* @returns {void} - 无返回值
*/
async onClickImg(payload: any): Promise<void> {
const url = payload.image_list[0].url;
const imageType = url.endsWith(".png") ? "png" : url.endsWith(".jpg") ? "jpg" : "png";
const savePath = `${await path.downloadDir()}${path.sep}${Date.now().toString()}.${imageType}`;
const image = payload.image_list[0];
const executeJS = `javascript:(async function() {
const _t = window.__TAURI__;
const defaultPath = await _t.path.downloadDir() + Date.now() + '.${image.format}';
const savePath = await _t.dialog.save({
title: '保存图片',
filters: [{ name: '图片', extensions: ['png'] }],
defaultPath: '${savePath}',
defaultPath: defaultPath,
});
if (savePath) {
const resBlob = await _t.http.fetch('${url}',{
const resBlob = await _t.http.fetch('${image.url}',{
method: 'GET',
responseType: _t.http.ResponseType.Binary
});

View File

@@ -1,7 +1,7 @@
/**
* @file utils TGShare.ts
* @description 生成分享截图并保存到本地
* @since Beta v0.3.4
* @since Beta v0.3.6
*/
import { dialog, fs, http, path } from "@tauri-apps/api";
@@ -75,22 +75,20 @@ function getShareImgBgColor(): string {
/**
* @description 生成分享截图
* @since Beta v0.3.4
* @since Beta v0.3.6
* @param {string} fileName - 文件名
* @param {HTMLElement} element - 元素
* @param {number} scale - 缩放比例
* @param {number} offset - 偏移量
* @returns {Promise<void>} 无返回值
*/
export async function generateShareImg(
fileName: string,
element: HTMLElement,
scale: number = 0.98,
offset: number = 30,
scale: number = 1.2,
): Promise<void> {
const canvas = document.createElement("canvas");
const width = element.clientWidth + offset;
const height = element.clientHeight + offset;
const width = element.clientWidth + 30;
const height = element.clientHeight + 30;
canvas.width = width * scale;
canvas.height = height * scale;
const opts = {
@@ -101,8 +99,8 @@ export async function generateShareImg(
height,
useCORS: true,
canvas,
x: (-offset / 2) * scale,
y: (-offset / 2) * scale,
x: -10,
y: -15,
dpi: 350,
};
const canvasData = await html2canvas(element, opts);

View File

@@ -1,7 +1,7 @@
/**
* @file utils toolFunc.ts
* @file utils/toolFunc.ts
* @description 一些工具函数
* @since Beta v0.3.5
* @since Beta v0.3.6
*/
import { os, path } from "@tauri-apps/api";
@@ -40,17 +40,52 @@ export function timestampToDate(timestamp: number): string {
}
/**
* @description 获取 deviceID
* @since Beta v0.3.4
* @returns {string} deviceID
* @description 获取当前时间, YY-MM-DD HH:MM:SS
* @since Beta v0.3.6
* @returns {string} 当前时间
*/
export function getDeviceID(): string {
let deviceID = localStorage.getItem("deviceID");
if (deviceID === null) {
deviceID = v4();
localStorage.setItem("deviceID", deviceID);
export function getNowStr(): string {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, "0");
const date = now.getDate().toString().padStart(2, "0");
const hour = now.getHours().toString().padStart(2, "0");
const minute = now.getMinutes().toString().padStart(2, "0");
const second = now.getSeconds().toString().padStart(2, "0");
return `${year}-${month}-${date} ${hour}:${minute}:${second}`;
}
/**
* @description 获取设备信息(初始化时)
* @since Beta v0.3.6
* @returns {TGApp.App.Device.DeviceInfo} 设备信息
*/
export function getInitDeviceInfo(): TGApp.App.Device.DeviceInfo {
return {
device_id: v4(),
model: getRandomString(6),
seed_id: v4(),
seed_time: Date.now().toString(),
device_fp: "0000000000000",
};
}
/**
* @description 获取设备信息(登录时)
* @since Beta v0.3.6
* @param {string} key - 设备信息 key
* @returns {string} 设备信息
*/
export function getDeviceInfo(key: "device_id" | "device_fp"): string {
const localDevice = localStorage.getItem("deviceInfo");
let deviceInfo: TGApp.App.Device.DeviceInfo;
if (localDevice === null) {
deviceInfo = getInitDeviceInfo();
localStorage.setItem("deviceInfo", JSON.stringify({ deviceInfo }));
} else {
deviceInfo = JSON.parse(localDevice).deviceInfo;
}
return deviceID;
return deviceInfo[key];
}
/**
@@ -84,3 +119,43 @@ export async function getCacheDir(): Promise<string[] | false> {
}
return false;
}
/**
* @description 获取随机字符串
* @since Beta v0.3.6
* @param {number} length 字符串长度
* @param {string} type
* @returns {string} 随机字符串
*/
export function getRandomString(length: number, type: string = "all"): string {
const char = "abcdefghijklmnopqrstuvwxyz";
const num = "0123456789";
let str: string;
switch (type) {
case "all":
str = char + char.toUpperCase() + num;
break;
case "number":
str = num;
break;
case "lower":
str = char;
break;
case "upper":
str = char.toUpperCase();
break;
case "letter":
str = char + char.toUpperCase();
break;
case "hex":
str = num + "abcdef";
break;
default:
str = char + char.toUpperCase() + num;
}
let res = "";
for (let i = 0; i < length; i++) {
res += str.charAt(Math.floor(Math.random() * str.length));
}
return res;
}

View File

@@ -1,17 +1,18 @@
/**
* @file web constant TGConstant.ts
* @file web/constant/TGConstant.ts
* @description 常量
* @since Beta v0.3.3
* @since Beta v0.3.6
*/
import { BBS_VERSION, BBS_HEADER_AGENT, BBS_APP_ID, BBS_SALT } from "./bbs";
import { BBS_APP_ID, BBS_SALT, BBS_UA_MOBILE, BBS_UA_PC, BBS_VERSION } from "./bbs";
import SERVER from "./server";
import { GAME_BIZ } from "./utils";
const TGConstant = {
BBS: {
VERSION: BBS_VERSION,
USER_AGENT: BBS_HEADER_AGENT,
UA_PC: BBS_UA_PC,
UA_MOBILE: BBS_UA_MOBILE,
APP_ID: BBS_APP_ID,
},
Salt: BBS_SALT,

View File

@@ -1,21 +1,22 @@
/**
* @file web constant bbs.ts
* @file web/constant/bbs.ts
* @description 常量-应用数据
* @since Beta v0.3.3
* @since Beta v0.3.6
*/
export const BBS_VERSION = "2.59.1";
export const BBS_HEADER_AGENT = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/${BBS_VERSION}`;
export const BBS_VERSION = "2.63.1";
export const BBS_UA_PC = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/${BBS_VERSION}`;
export const BBS_UA_MOBILE = `Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/${BBS_VERSION}`;
export const BBS_APP_ID = "bll8iq97cem8";
/**
* @description salt 值
* @version 2.59.1
* @since Beta v0.3.3
* @version 2.63.1
* @since Beta v0.3.6
*/
export const BBS_SALT = {
K2: "awFeNNTsLYcK20LSO60Es8CRVZOjCB1b",
LK2: "6pNd5NnDnbwKxewrPwEoWlSYwhualS2H",
K2: "BIPaooxbWZW02fGHZL1If26mYCljPgst",
LK2: "9ttJY72HxbjwWRNHJvn0n2AYue47nYsK",
X4: "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
X6: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
PROD: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",

View File

@@ -1,7 +1,7 @@
/**
* @file web request TGRequest.ts
* @file web/request/TGRequest.ts
* @description 应用用到的请求函数
* @since Beta v0.3.4
* @since Beta v0.3.6
*/
import { genAuthkey } from "./genAuthkey";
@@ -9,6 +9,7 @@ import { getAbyss } from "./getAbyss";
import { getActionTicketBySToken } from "./getActionTicket";
import { getAnnoContent, getAnnoList } from "./getAnno";
import { getCookieTokenByGameToken, getCookieTokenBySToken } from "./getCookieToken";
import { getDeviceFp } from "./getDeviceFp";
// import * from "./getEnkaData.ts";
import { getGachaLog } from "./getGachaLog";
import { getGameAccountsByCookie, getGameAccountsBySToken } from "./getGameAccounts";
@@ -27,6 +28,9 @@ const TGRequest = {
getList: getAnnoList,
getContent: getAnnoContent,
},
Device: {
getFp: getDeviceFp,
},
User: {
getAuthkey: genAuthkey,
getGachaLog,

View File

@@ -0,0 +1,85 @@
/**
* @file src/web/request/getDeviceFp.ts
* @description 获取设备指纹
* @since Beta v0.3.6
*/
import { http } from "@tauri-apps/api";
import { getInitDeviceInfo } from "../../utils/toolFunc";
import TGConstant from "../constant/TGConstant";
/**
* @description 获取设备指纹
* @since Beta v0.3.6
* @param {TGApp.App.Device.DeviceInfo} Info - 设备信息
* @returns {Promise<string>} 设备指纹
*/
export async function getDeviceFp(
Info?: TGApp.App.Device.DeviceInfo,
): Promise<TGApp.App.Device.DeviceInfo> {
const info = Info ?? getInitDeviceInfo();
const deviceFPHeader = {
cpuType: "arm64-v8a",
romCapacity: "512",
productName: info.model,
romRemain: "256",
manufacturer: "Xiaomi",
appMemory: "512",
hostname: "dg02-pool03-kvm87",
screenSize: "1080x1920",
osVersion: "13",
aaid: "",
vendor: "中国移动",
accelerometer: "true",
buildTags: "release-keys",
model: info.model,
brand: "Xiaomi",
oaid: "",
hardware: "qcom",
deviceType: "OP5913L1",
devId: "unknown",
serialNumber: "unknown",
buildTime: "1588876800000", // 2020-05-08
buildUser: "root",
ramCapacity: "2048",
magnetometer: "true",
display: `OP5913L1-user ${info.model} 10 QKQ1.190825.002 V12.0.1.0.QFJCNXM release-keys`,
ramRemain: "1024",
deviceInfo: "unknown",
gyroscope: "true",
vaid: "",
buildType: "user",
sdkVersion: "29",
board: "sdm660",
};
const url = "https://public-data-api.mihoyo.com/device-fp/api/getFp";
const data = {
device_id: info.device_id,
seed_id: info.seed_id,
platform: "2",
seed_time: info.seed_time,
ext_fields: JSON.stringify(deviceFPHeader),
app_name: "bbs_cn",
bbs_device_id: info.device_id,
device_fp: info.device_fp,
};
const header = {
"User-Agent": `Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/${TGConstant.BBS.VERSION}`,
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "5",
"x-requested-with": "com.mihoyo.hyperion",
Referer: "https://webstatic.mihoyo.com/",
};
info.device_fp = await http
.fetch<TGApp.BBS.Response.getDeviceFp>(url, {
method: "POST",
body: http.Body.json(data),
headers: header,
})
.then((res) => {
if (res.data.data.code === 200) return res.data.data.device_fp;
return "0000000000000";
});
return info;
}

View File

@@ -1,12 +1,13 @@
/**
* @file web utils getRequestHeader.ts
* @file web/utils/getRequestHeader.ts
* @description 获取请求头
* @since Beta v0.3.4
* @since Beta v0.3.6
*/
import Md5 from "js-md5";
import { transCookie, transParams } from "./tools";
import { getDeviceInfo, getRandomString } from "../../utils/toolFunc";
import TGConstant from "../constant/TGConstant";
/**
@@ -40,21 +41,6 @@ function getRandomNumber(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1) + min);
}
/**
* @description 获取随机字符串
* @since Alpha v0.2.0
* @param {number} length 字符串长度
* @returns {string} 随机字符串
*/
function getRandomString(length: number): string {
const str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let res = "";
for (let i = 0; i < length; i++) {
res += str.charAt(Math.floor(Math.random() * str.length));
}
return res;
}
/**
* @description 获取 ds
* @since Beta v0.3.3
@@ -80,7 +66,7 @@ function getDS(method: string, data: string, saltType: string, isSign: boolean):
/**
* @description 获取请求头
* @since Beta v0.3.0
* @since Beta v0.3.6
* @param {Record<string, string>} cookie cookie
* @param {string} method 请求方法
* @param {Record<string, string|number>|string} data 请求数据
@@ -102,11 +88,13 @@ export function getRequestHeader(
ds = getDS(method, transParams(data), saltType, isSign);
}
return {
"user-agent": TGConstant.BBS.USER_AGENT,
"user-agent": TGConstant.BBS.UA_PC,
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "5",
"x-requested-with": "com.mihoyo.hyperion",
referer: "https://webstatic.mihoyo.com",
"x-rpc-device_id": getDeviceInfo("device_id"),
"x-rpc-device_fp": getDeviceInfo("device_fp"),
ds,
cookie: transCookie(cookie),
};

View File

@@ -11,13 +11,12 @@
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["DOM", "ESNext"],
"skipLibCheck": true,
"types": ["vite/client"],
"allowSyntheticDefaultImports": true,
"composite": true
},
"include": [
".yml",
"*.yml",
"package.json",
"src/**/*.d.ts",
"src/**/*.ts",