mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-23 05:09:45 +08:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e27a20a7 | ||
|
|
26d1883d98 | ||
|
|
91fd375263 | ||
|
|
3b0ed774df | ||
|
|
5f3f6640a4 | ||
|
|
b1424fb582 | ||
|
|
f64b48c356 | ||
|
|
90242829a9 | ||
|
|
d6ae3765b6 | ||
|
|
92a1775d19 | ||
|
|
fd3822fe70 | ||
|
|
618d3a1632 | ||
|
|
1c3b73bde9 | ||
|
|
c541d67abc | ||
|
|
1d3b1ae78e | ||
|
|
312436b4e2 | ||
|
|
d98663dccb | ||
|
|
653073e684 | ||
|
|
1d09f4817b | ||
|
|
6ca5de28ac | ||
|
|
ff649c2426 | ||
|
|
1c5bebf75e | ||
|
|
b60718aa62 | ||
|
|
c018638e4a | ||
|
|
1906e911c7 | ||
|
|
fbb66b3964 | ||
|
|
561f34cf8b | ||
|
|
5241c08c33 | ||
|
|
630a64323d | ||
|
|
54d2e27054 | ||
|
|
1c69cf07a5 | ||
|
|
715c206945 | ||
|
|
219286f6a1 | ||
|
|
fc3d417961 | ||
|
|
712a09131e | ||
|
|
13e9440c6f | ||
|
|
bed0e528b0 | ||
|
|
43c282efd2 | ||
|
|
b74a3b0bbf | ||
|
|
7c0239391e | ||
|
|
b1060f76c5 |
@@ -5,3 +5,6 @@ src-tauri/target
|
||||
TGAssistant
|
||||
# Package files
|
||||
pnpm-lock.yaml
|
||||
# lint files
|
||||
!.prettierrc.yml
|
||||
!.stylelintrc.yml
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -5,4 +5,4 @@ useTabs: false
|
||||
tabWidth: 2
|
||||
bracketSpacing: true
|
||||
endOfLine: auto
|
||||
trailingComma: "all"
|
||||
trailingComma: all
|
||||
|
||||
@@ -8,4 +8,4 @@ plugins:
|
||||
- stylelint-prettier
|
||||
- stylelint-order
|
||||
rules:
|
||||
"prettier/prettier": true
|
||||
prettier/prettier: true
|
||||
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
 
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
364
docs/UIGF.md
364
docs/UIGF.md
@@ -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 |
|
||||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
|
||||
|
||||
32
package.json
32
package.json
@@ -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
775
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
156
src-tauri/Cargo.lock
generated
156
src-tauri/Cargo.lock
generated
@@ -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]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
35
src/App.vue
35
src/App.vue
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
84
src/components/devCharacter/duc-detail-olb.vue
Normal file
84
src/components/devCharacter/duc-detail-olb.vue
Normal 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>
|
||||
109
src/components/devCharacter/duc-detail-olt.vue
Normal file
109
src/components/devCharacter/duc-detail-olt.vue
Normal 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>
|
||||
75
src/components/devCharacter/duc-detail-ort.vue
Normal file
75
src/components/devCharacter/duc-detail-ort.vue
Normal 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>
|
||||
199
src/components/devCharacter/duc-detail-overlay.vue
Normal file
199
src/components/devCharacter/duc-detail-overlay.vue
Normal 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>
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -18,8 +18,8 @@ const getName = (): string => {
|
||||
return props.modelValue.id === 10000005
|
||||
? "旅行者-空"
|
||||
: props.modelValue.id === 10000007
|
||||
? "旅行者-荧"
|
||||
: props.modelValue.name;
|
||||
? "旅行者-荧"
|
||||
: props.modelValue.name;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -110,8 +110,8 @@ function getAvatarName(): string {
|
||||
return props.modelValue.cid === 10000005
|
||||
? "旅行者-空"
|
||||
: props.modelValue.cid === 10000007
|
||||
? "旅行者-荧"
|
||||
: props.modelValue.name;
|
||||
? "旅行者-荧"
|
||||
: props.modelValue.name;
|
||||
}
|
||||
|
||||
// 销毁
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
5
src/plugins/Mys/types/Emoji.d.ts
vendored
5
src/plugins/Mys/types/Emoji.d.ts
vendored
@@ -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;
|
||||
|
||||
5
src/plugins/Mys/types/Gacha.d.ts
vendored
5
src/plugins/Mys/types/Gacha.d.ts
vendored
@@ -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[];
|
||||
};
|
||||
|
||||
10
src/plugins/Mys/types/GameLogin.d.ts
vendored
10
src/plugins/Mys/types/GameLogin.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
5
src/plugins/Mys/types/Lottery.d.ts
vendored
5
src/plugins/Mys/types/Lottery.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
5
src/plugins/Mys/types/News.d.ts
vendored
5
src/plugins/Mys/types/News.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
11
src/plugins/Mys/types/Obc.d.ts
vendored
11
src/plugins/Mys/types/Obc.d.ts
vendored
@@ -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[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
5
src/plugins/Mys/types/Position.d.ts
vendored
5
src/plugins/Mys/types/Position.d.ts
vendored
@@ -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[];
|
||||
};
|
||||
|
||||
5
src/plugins/Mys/types/Post.d.ts
vendored
5
src/plugins/Mys/types/Post.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
18
src/plugins/Mys/types/SctPost.d.ts
vendored
18
src/plugins/Mys/types/SctPost.d.ts
vendored
@@ -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 帖子结构化数据-其他类型
|
||||
|
||||
5
src/plugins/Mys/types/User.d.ts
vendored
5
src/plugins/Mys/types/User.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
32
src/types/App/Device.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
10
src/types/BBS/Announcement.d.ts
vendored
10
src/types/BBS/Announcement.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
76
src/types/BBS/Response.d.ts
vendored
76
src/types/BBS/Response.d.ts
vendored
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
5
src/types/Game/Abyss.d.ts
vendored
5
src/types/Game/Abyss.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
10
src/types/Game/Calculate.d.ts
vendored
10
src/types/Game/Calculate.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
5
src/types/Game/Character.d.ts
vendored
5
src/types/Game/Character.d.ts
vendored
@@ -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;
|
||||
|
||||
5
src/types/Game/DailyNotes.d.ts
vendored
5
src/types/Game/DailyNotes.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
10
src/types/Game/Gacha.d.ts
vendored
10
src/types/Game/Gacha.d.ts
vendored
@@ -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;
|
||||
|
||||
5
src/types/Game/Record.d.ts
vendored
5
src/types/Game/Record.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
5
src/types/User/Account.d.ts
vendored
5
src/types/User/Account.d.ts
vendored
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
85
src/web/request/getDeviceFp.ts
Normal file
85
src/web/request/getDeviceFp.ts
Normal 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;
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user