Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ab31d8f5c | ||
|
|
1af990512d | ||
|
|
d96d451156 | ||
|
|
f029306ebb | ||
|
|
d3c5baa0c2 | ||
|
|
ba0802752c | ||
|
|
ff94e12ff5 | ||
|
|
0fbf1f7c2a | ||
|
|
68809a93c6 | ||
|
|
0edcadef63 | ||
|
|
9f9c30914f | ||
|
|
04cf372798 | ||
|
|
6617a26c90 | ||
|
|
d244423800 | ||
|
|
3366efaadd | ||
|
|
d74e7a7a31 | ||
|
|
2d0b409813 | ||
|
|
942068faea | ||
|
|
0f0f7684d2 | ||
|
|
531cb32f72 | ||
|
|
a368223805 | ||
|
|
6eab6c81f1 | ||
|
|
68594a2a76 | ||
|
|
5d5f22d76e | ||
|
|
65e948c34c | ||
|
|
68dead3d84 | ||
|
|
babc6a9a75 | ||
|
|
6db4ff5ac9 | ||
|
|
ce1b6f365e | ||
|
|
b6ed9668ac | ||
|
|
2a2a190f5f | ||
|
|
5d03a32362 | ||
|
|
33d9ba5c4d | ||
|
|
9020214d23 | ||
|
|
78c3f79bfd | ||
|
|
ee0fc6dbae | ||
|
|
8c51b79558 | ||
|
|
8c1899637f | ||
|
|
56df920a7d | ||
|
|
64c6f4ab8f | ||
|
|
d3902d6e31 | ||
|
|
01e355b0d6 | ||
|
|
c40b3c6ff0 | ||
|
|
4305967ba9 | ||
|
|
78f454bee5 | ||
|
|
e9a38e1474 | ||
|
|
9fb2aa6112 | ||
|
|
a0554e4355 | ||
|
|
f890165894 | ||
|
|
bc22612da7 | ||
|
|
a9ec93b18d | ||
|
|
651cbef0a0 | ||
|
|
41a144fec2 | ||
|
|
3f219ebb82 | ||
|
|
43c85afd1e | ||
|
|
48771f57a0 | ||
|
|
6e3884df58 | ||
|
|
7a6a06bb25 | ||
|
|
eac3691d0b | ||
|
|
3ece987c80 | ||
|
|
145438373b | ||
|
|
b62b0b4902 | ||
|
|
ed878dea9e |
7
.github/workflows/qodana_code_quality.yml
vendored
@@ -1,8 +1,9 @@
|
||||
name: Qodana
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
|
||||
46
CHANGELOG.md
@@ -2,12 +2,54 @@
|
||||
Author: 目棃
|
||||
Description: CHANGELOG
|
||||
Date: 2025-09-09
|
||||
Update: 2025-09-27
|
||||
Update: 2025-11-19
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2025-09-09 14:30:56`
|
||||
>
|
||||
> 更新于 `2025-09-27 10:18:07`
|
||||
> 更新于 `2025-11-19 14:08:20`
|
||||
|
||||
## [0.8.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.6) (2025-11-19)
|
||||
|
||||
> 关于胡桃数据库导入功能的说明请参考 [导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
|
||||
|
||||
- 👽️ 移除剧诗概览,支持导入胡桃剧诗数据
|
||||
- 👽️ 移除深渊上传,支持导入胡桃深渊数据
|
||||
- 🔥 移除胡桃深渊统计页面
|
||||
- 🚸 调整导入祈愿记录浮窗ui,显示导入进度
|
||||
- 🐛 修复图片渲染异常
|
||||
- 🥅 处理清除缓存异常,清除缓存后重启
|
||||
- 🚸 帖子详情添加AIGC相关注释
|
||||
- 🚸 添加跳转视频链接
|
||||
- 📝 更新相关文档
|
||||
|
||||
## [0.8.5](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.5) (2025-11-10)
|
||||
|
||||
- 🍱 更新下半数据
|
||||
|
||||
## [0.8.4](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.4) (2025-10-27)
|
||||
|
||||
- 👽️ 公告添加千星奇域分类
|
||||
- 🚸 兑换码浮窗显示游戏名称
|
||||
- ✨ 嵌入官方公告页面(已登录)
|
||||
- ✨ 嵌入官方祈愿详情(已登录)
|
||||
- ✨ 完善投稿活动类型声明,渲染投稿活动&交互
|
||||
- 🐛 修复部分帖子解析异常
|
||||
- ✨ 重构帖子解析逻辑,增加新类型解析
|
||||
- 💄 调整名片样式
|
||||
- ✨ 添加getRegionRoleInfo事件处理
|
||||
- 🐛 公告解析剔除多余换行
|
||||
- ✨ 千星奇域祈愿页面草创
|
||||
|
||||
## [0.8.3](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.3) (2025-10-22)
|
||||
|
||||
- 🍱 更新6.1版本数据
|
||||
- 👽️ 适配月谕圣牌模式
|
||||
- 🐛 重构帖子数据解析,修复HEIC格式图片渲染异常
|
||||
- 🐛 修复切换角色导致ck对应异常
|
||||
- 🚸 优化图片调整浮窗样式
|
||||
- ♻️ 重构gt返回逻辑
|
||||
- 💄 调整布局
|
||||
|
||||
## [0.8.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.2) (2025-09-27)
|
||||
|
||||
|
||||
16
README.md
@@ -2,12 +2,12 @@
|
||||
Author: 目棃
|
||||
Description: 说明文档
|
||||
Date: 2023-03-05
|
||||
Update: 2025-09-09
|
||||
Update: 2025-11-19
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
|
||||
>
|
||||
> 更新于 `2025-09-09 14:37:02`
|
||||
> 更新于 `2025-11-19 13:21:38`
|
||||
|
||||
[](https://deepwiki.com/BTMuli/TeyvatGuide)  
|
||||
|
||||
@@ -65,13 +65,13 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [x] 真境剧诗
|
||||
- [x] 幽境危战
|
||||
- [x] 祈愿数据获取(近一年)
|
||||
- [x] 千星奇域祈愿数据获取(近一年)
|
||||
- [x] 用户收藏帖子获取
|
||||
- [x] 用户关注帖子获取
|
||||
- [x] 一键完成米游币每日任务
|
||||
- [x] 一键完成游戏签到
|
||||
|
||||
- Wiki 功能:
|
||||
- [x] 深渊数据库(Hutao API)
|
||||
- [x] 角色图鉴
|
||||
- [x] 武器图鉴
|
||||
- [x] 名片图鉴
|
||||
@@ -90,7 +90,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
|
||||
## UI 参考 / UI Reference
|
||||
|
||||
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
|
||||
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
|
||||
- [Starward](https://github.com/Scighost/Starward)
|
||||
- [米游社](https://www.miyoushe.com/ys/)
|
||||
- [原神](https://yuanshen.com/)
|
||||
@@ -102,6 +102,8 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- UIAF:[UIAF v1.1](docs/standards/UIAF.md)
|
||||
- UIGF:[UIGF v3.0](docs/standards/UIGF3.md),[UIGF v4.0](docs/standards/UIGF.md)
|
||||
- [macOS 平台门禁属性导致应用无法打开应用的修复指引](docs/macos-gatekeeper/README.md)
|
||||
- [隐私政策](https://app.btmuli.ink/docs/TeyvatGuide/privacy.html)
|
||||
- [如何导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
|
||||
|
||||
## 特定项目 / Special Project
|
||||
|
||||
@@ -136,7 +138,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
本项目在开发过程中参考了诸多相关开源项目,特此鸣谢。
|
||||
|
||||
- [UIGF Organization](https://github.com/UIGF-org)
|
||||
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
|
||||
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
|
||||
- [StarWard](https://github.com/Scighost/Starward)
|
||||
- [xunkong](https://github.com/xunkong/xunkong)
|
||||
- [gs-helper](https://github.com/vikiboss/gs-helper)
|
||||
@@ -146,8 +148,4 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [MihoyoBBSTools](https://github.com/Womsxd/MihoyoBBSTools)
|
||||
- [nonebot-plugin-mystool](https://github.com/Ljzd-PRO/nonebot-plugin-mystool)
|
||||
|
||||
感谢 JetBrains 提供的开源许可证。
|
||||
|
||||

|
||||
|
||||
[](https://star-history.com/#BTMuli/TeyvatGuide&Timeline)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
Author: 目棃
|
||||
Description: 项目资源说明
|
||||
Date: 2023-03-10
|
||||
Update: 2025-02-28
|
||||
Update: 2025-11-19
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-10 22:05:44`
|
||||
>
|
||||
> 更新于 `2025-02-28 09:40:33`
|
||||
> 更新于 `2025-11-19 12:31:22`
|
||||
|
||||
## 说明
|
||||
|
||||
@@ -40,8 +40,8 @@ Update: 2025-02-28
|
||||
相关仓库:
|
||||
|
||||
- [TGAssistant](https://github.com/BTMuli/TGAssistant):项目下游仓库,用于处理项目数据。
|
||||
- [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata):胡桃元数据仓库,项目大部分数据来源于此。
|
||||
- [Snap.Static](https://github.com/DGP-Studio/Snap.Static):胡桃静态资源仓库,项目部分图像资源来源于此。
|
||||
- ~~[Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)~~:胡桃元数据仓库,项目大部分数据来源于此。
|
||||
- ~~[Snap.Static](https://github.com/DGP-Studio/Snap.Static)~~:胡桃静态资源仓库,项目部分图像资源来源于此。
|
||||
- [amos-data](https://github.com/yuehaiteam/amos-data):成就数据仓库,成就数据的详细信息来源于此。
|
||||
|
||||
## 字体
|
||||
|
||||
92
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "teyvatguide",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.6",
|
||||
"description": "Game Tool for GenshinImpact player",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.16.1",
|
||||
"packageManager": "pnpm@10.22.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
@@ -71,68 +71,66 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "7.4.47",
|
||||
"@tauri-apps/api": "^2.8.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||
"@tauri-apps/plugin-http": "^2.5.2",
|
||||
"@tauri-apps/plugin-log": "^2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.1",
|
||||
"@tauri-apps/plugin-process": "^2.3.0",
|
||||
"@tauri-apps/plugin-shell": "^2.3.1",
|
||||
"@tauri-apps/plugin-sql": "^2.3.0",
|
||||
"@tauri-apps/api": "^2.9.0",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.5",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-log": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.5.2",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"@tauri-apps/plugin-sql": "^2.3.1",
|
||||
"ajv": "^8.17.1",
|
||||
"artplayer": "^5.3.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"color-convert": "^3.1.2",
|
||||
"color-convert": "^3.1.3",
|
||||
"echarts": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"js-md5": "^0.8.3",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"sass-embedded": "^1.92.1",
|
||||
"swiper": "^12.0.1",
|
||||
"sass-embedded": "^1.93.3",
|
||||
"swiper": "^12.0.3",
|
||||
"uuid": "^13.0.0",
|
||||
"vue": "^3.5.21",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-json-pretty": "^2.5.0",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuetify": "^3.10.0",
|
||||
"vue": "^3.5.24",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-json-pretty": "^2.6.0",
|
||||
"vue-router": "^4.6.3",
|
||||
"vuetify": "^3.10.11",
|
||||
"wcag-color": "^1.1.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@btmuli/stylelint-plugin-color": "^0.1.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.35.0",
|
||||
"@tauri-apps/cli": "2.8.4",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@tauri-apps/cli": "2.9.4",
|
||||
"@types/color-convert": "^2.0.4",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/js-md5": "^0.8.0",
|
||||
"@types/node": "^24.4.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/parser": "^8.43.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20250914.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/parser": "^8.47.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251118.1",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"app-root-path": "^3.1.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsonc": "^2.20.1",
|
||||
"eslint-plugin-jsonc": "^2.21.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
"eslint-plugin-yml": "^1.18.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"globals": "^16.4.0",
|
||||
"eslint-plugin-vue": "^10.5.1",
|
||||
"eslint-plugin-yml": "^1.19.0",
|
||||
"fs-extra": "^11.3.2",
|
||||
"globals": "^16.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsonc-eslint-parser": "^2.4.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"oxlint": "^1.15.0",
|
||||
"jsonc-eslint-parser": "^2.4.1",
|
||||
"lint-staged": "^16.2.6",
|
||||
"oxlint": "^1.29.0",
|
||||
"prettier": "3.6.2",
|
||||
"stylelint": "^16.24.0",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-idiomatic-order": "^10.0.0",
|
||||
"stylelint-config-standard-scss": "^16.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
@@ -141,14 +139,14 @@
|
||||
"stylelint-order": "^7.0.0",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"stylelint-scss": "^6.12.1",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.43.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-vue-devtools": "^8.0.2",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.47.0",
|
||||
"vite": "npm:rolldown-vite@^7.2.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.5",
|
||||
"vite-plugin-vuetify": "^2.1.2",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.7",
|
||||
"vue-tsc": "^3.1.4",
|
||||
"yaml-eslint-parser": "^1.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
3328
pnpm-lock.yaml
generated
BIN
public/WIKI/character/10000117.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/WIKI/character/10000118.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/WIKI/character/10000122.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/WIKI/nameCard/bg/原神·印象.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/WIKI/nameCard/bg/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/WIKI/nameCard/bg/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/WIKI/nameCard/icon/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
public/WIKI/nameCard/icon/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/WIKI/nameCard/profile/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/WIKI/nameCard/profile/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/WIKI/weapon/13434.webp
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/WIKI/weapon/14434.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/WIKI/weapon/14521.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
public/icon/combat/tarot.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/icon/combat/tarot_0.webp
Normal file
|
After Width: | Height: | Size: 836 B |
BIN
public/icon/combat/tarot_1.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_02.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_03.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_04.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/icon/constellations/UI_Talent_U_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/constellations/UI_Talent_U_Nefer_02.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/material/113079.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/icon/nation/千星奇域.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/icon/talents/Skill_E_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/icon/talents/Skill_S_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_05.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_06.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_07.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_08.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
1157
src-tauri/Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "TeyvatGuide"
|
||||
version = "0.8.2"
|
||||
version = "0.8.6"
|
||||
description = "Game Tool for Genshin Impact player"
|
||||
authors = ["BTMuli <bt-muli@outlook.com>"]
|
||||
license = "MIT"
|
||||
@@ -17,15 +17,15 @@ name = "teyvat_guide_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.4.1", features = [] }
|
||||
tauri-build = { version = "2.5.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
log = "0.4.28"
|
||||
serde = { version = "1.0.221", features = ["derive"] }
|
||||
serde_json = "1.0.144"
|
||||
tauri = { version = "2.8.5", features = [] }
|
||||
tauri-utils = "2.7.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
tauri = { version = "2.9.3", features = [] }
|
||||
tauri-utils = "2.8.0"
|
||||
url = "2.5.7"
|
||||
walkdir = "2.5.0"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ mod utils;
|
||||
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
|
||||
use tauri_utils::config::WebviewUrl;
|
||||
|
||||
static BBS_VERSION: &'static str = "2.93.0";
|
||||
static BBS_VERSION: &'static str = "2.95.1";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "TeyvatGuide",
|
||||
"identifier": "TeyvatGuide",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.6",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm vite:dev",
|
||||
"beforeBuildCommand": "pnpm vite:build",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div v-if="hasNav" class="tgn-nav">
|
||||
<v-icon size="25" @click="tryGetCode" title="查看兑换码">mdi-code-tags-check</v-icon>
|
||||
</div>
|
||||
<ToLivecode v-model="showOverlay" :data="codeData" v-model:actId="actId" />
|
||||
<ToLivecode v-model="showOverlay" :gid="model" :data="codeData" :actId="actId" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -112,7 +112,10 @@
|
||||
}"
|
||||
v-else
|
||||
>
|
||||
{{ props.modelValue.post.post_id }}
|
||||
<span>{{ props.modelValue.post.post_id }}</span>
|
||||
<template v-if="isDevEnv">
|
||||
<span data-html2canvas-ignore>[{{ props.modelValue.post.view_type }}]</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -154,7 +157,8 @@ export type RenderCard = {
|
||||
topics: Array<TGApp.BBS.Post.Topic>;
|
||||
reasons: Array<TGApp.BBS.Post.RecommendTags>;
|
||||
};
|
||||
|
||||
// @ts-expect-error The import.meta meta-property is not allowed in files which will build into CommonJS output.
|
||||
const isDevEnv = import.meta.env.MODE === "development";
|
||||
const stats: Readonly<Array<RenderStatus>> = [
|
||||
{ stat: 0, label: "未知", color: "var(--tgc-od-red)" },
|
||||
{ stat: 1, label: "进行中", color: "var(--tgc-od-green)" },
|
||||
|
||||
@@ -110,11 +110,6 @@
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list class="side-list-menu sub" density="compact" :nav="true">
|
||||
<v-list-item class="side-item-menu" title="深渊数据库" :link="true" href="/wiki/abyss">
|
||||
<template #prepend>
|
||||
<img src="/source/UI/wikiAbyss.webp" alt="abyssIcon" class="side-icon-menu" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item class="side-item-menu" title="角色图鉴" :link="true" href="/wiki/character">
|
||||
<template #prepend>
|
||||
<img src="/source/UI/wikiAvatar.webp" alt="characterIcon" class="side-icon-menu" />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<TOverlay v-model="visible" class="tolc-overlay">
|
||||
<div class="tolc-box">
|
||||
<div class="tolc-title">
|
||||
<span>兑换码</span>
|
||||
<span>{{ gameInfo?.name ?? "" }}兑换码</span>
|
||||
<v-icon
|
||||
size="18px"
|
||||
title="share"
|
||||
@@ -39,16 +39,28 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import useBBSStore from "@store/bbs.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { timestampToDate } from "@utils/toolFunc.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
|
||||
import TMiImg from "./t-mi-img.vue";
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
|
||||
type ToLiveCodeProps = { data: Array<TGApp.BBS.Navigator.CodeData>; actId: string | undefined };
|
||||
type ToLiveCodeProps = {
|
||||
data: Array<TGApp.BBS.Navigator.CodeData>;
|
||||
actId: string | undefined;
|
||||
gid: number;
|
||||
};
|
||||
|
||||
const { gameList } = storeToRefs(useBBSStore());
|
||||
|
||||
const props = defineProps<ToLiveCodeProps>();
|
||||
const visible = defineModel<boolean>({ default: false });
|
||||
const gameInfo = computed<TGApp.BBS.Game.Item | undefined>(() => {
|
||||
return gameList.value.find((i) => i.id === props.gid);
|
||||
});
|
||||
|
||||
function copy(code: string): void {
|
||||
navigator.clipboard.writeText(code);
|
||||
|
||||
@@ -1,40 +1,35 @@
|
||||
<!-- 名片栏组件 -->
|
||||
<template>
|
||||
<div
|
||||
class="top-nc-box"
|
||||
@click="emit('selected', props.data)"
|
||||
:class="props.finish ? '' : 'grey'"
|
||||
:title.attr="props.data.name"
|
||||
:style="{ backgroundImage: `url('/WIKI/nameCard/bg/${props.data.name}.webp')` }"
|
||||
>
|
||||
<v-list-item>
|
||||
<template #title>
|
||||
<div class="title">
|
||||
<TwnTypeTag :type="props.data.type" />
|
||||
<span>{{ props.data.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
<div class="top-nc-bgc" />
|
||||
<div class="top-nc-prepend">
|
||||
<img :src="`/WIKI/nameCard/icon/${props.data.name}.webp`" alt="icon" />
|
||||
</div>
|
||||
<div class="top-nc-info">
|
||||
<div class="top-nc-title">
|
||||
<TwnTypeTag :type="props.data.type" />
|
||||
<span>{{ props.data.name }}</span>
|
||||
</div>
|
||||
<div class="top-nc-desc">
|
||||
<span class="desc" :title="props.data.desc">{{ props.data.desc }}</span>
|
||||
</template>
|
||||
<template #prepend>
|
||||
<img :src="`/WIKI/nameCard/icon/${props.data.name}.webp`" alt="icon" class="icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TwnTypeTag from "@comp/pageWiki/twn-type-tag.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
type TopNameCardProps = { data: TGApp.App.NameCard.Item; finish?: boolean };
|
||||
type TopNameCardEmits = (e: "selected", v: TGApp.App.NameCard.Item) => void;
|
||||
|
||||
const props = withDefaults(defineProps<TopNameCardProps>(), { finish: true });
|
||||
const emit = defineEmits<TopNameCardEmits>();
|
||||
|
||||
const bgImage = computed<string>(() => {
|
||||
if (props.data.name === "原神·印象") return "none;";
|
||||
return `url("/WIKI/nameCard/bg/${props.data.name}.webp")`;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
@@ -42,17 +37,19 @@ const bgImage = computed<string>(() => {
|
||||
.top-nc-box {
|
||||
@include github-styles.github-card-shadow;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px 50px 50px 4px;
|
||||
background-color: var(--box-bg-1);
|
||||
background-image: v-bind(bgImage); /* stylelint-disable-line value-keyword-case */
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
column-gap: 8px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
transition: filter 0.5s ease-in-out;
|
||||
@@ -70,19 +67,60 @@ const bgImage = computed<string>(() => {
|
||||
@include github-styles.github-card-shadow("dark");
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 60px;
|
||||
margin-right: 12px;
|
||||
aspect-ratio: 23 / 15;
|
||||
.top-nc-bgc {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, var(--box-bg-1) 40%, transparent 75%);
|
||||
}
|
||||
|
||||
.title {
|
||||
.top-nc-prepend {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
|
||||
img {
|
||||
height: 60px;
|
||||
aspect-ratio: 23/15;
|
||||
}
|
||||
}
|
||||
|
||||
.top-nc-info {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.top-nc-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
text-shadow: 0 0 2px var(--common-shadow-t-8);
|
||||
.top-nc-desc {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
max-width: calc(100% - 16px);
|
||||
font-size: 14px;
|
||||
opacity: 0.75;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 0 2px var(--box-bg-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -48,7 +48,7 @@ declare function initGeetest(
|
||||
async function displayBox(
|
||||
props: TGApp.BBS.Geetest.CreateRes,
|
||||
): Promise<TGApp.BBS.Geetest.GeetestVerifyRes | false> {
|
||||
return await new Promise<TGApp.BBS.Geetest.GeetestVerifyRes>((resolve) => {
|
||||
return await new Promise<TGApp.BBS.Geetest.GeetestVerifyRes | false>((resolve) => {
|
||||
initGeetest(
|
||||
{
|
||||
gt: props.gt,
|
||||
@@ -64,11 +64,12 @@ async function displayBox(
|
||||
geetestEl.value.innerHTML = "";
|
||||
captchaObj.appendTo("#geetest");
|
||||
captchaObj.onReady(() => (show.value = true));
|
||||
captchaObj.onSuccess(() => {
|
||||
captchaObj.onClose(() => {
|
||||
const validate = captchaObj.getValidate();
|
||||
show.value = false;
|
||||
if (!validate) resolve(false);
|
||||
resolve(validate);
|
||||
});
|
||||
captchaObj.onClose(() => (show.value = false));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
105
src/components/pageAnno/tao-iframe.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<!-- 游戏内公告浮窗 -->
|
||||
<template>
|
||||
<TOverlay v-model="visible">
|
||||
<div class="tao-iframe-box">
|
||||
<!-- TODO:加载完成后修改样式 -->
|
||||
<iframe :src="link" class="tao-iframe" />
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import takumiReq from "@req/takumiReq.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
const { lang } = storeToRefs(useAppStore());
|
||||
const { cookie, account } = storeToRefs(useUserStore());
|
||||
const visible = defineModel<boolean>();
|
||||
|
||||
const authkey = ref<string>("");
|
||||
const link = ref<string>("");
|
||||
|
||||
onMounted(async () => await refreshUrl());
|
||||
|
||||
watch(
|
||||
() => lang.value,
|
||||
async () => {
|
||||
if (!visible.value) return;
|
||||
await refreshUrl();
|
||||
},
|
||||
);
|
||||
|
||||
async function refreshUrl(): Promise<void> {
|
||||
const res = await getUrl();
|
||||
if (res === "") return;
|
||||
link.value = res;
|
||||
}
|
||||
|
||||
async function refreshAuthkey(): Promise<void> {
|
||||
if (!cookie.value || !account.value) {
|
||||
visible.value = false;
|
||||
showSnackbar.warn("请先登录账号");
|
||||
return;
|
||||
}
|
||||
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
|
||||
if (typeof authkeyRes === "string") {
|
||||
authkey.value = authkeyRes;
|
||||
} else {
|
||||
showSnackbar.error("获取authkey失败");
|
||||
visible.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUrl(): Promise<string> {
|
||||
const path = "https://sdk.mihoyo.com/hk4e/announcement/index.html";
|
||||
if (authkey.value === "") await refreshAuthkey();
|
||||
if (authkey.value === "") return "";
|
||||
const param: Record<string, string> = {
|
||||
auth_appid: "announcement",
|
||||
authkey_ver: "1",
|
||||
bundle_id: "hk4e_cn",
|
||||
channel_id: "14",
|
||||
game: "hk4e",
|
||||
game_biz: account.value.gameBiz,
|
||||
lang: lang.value,
|
||||
level: account.value.level,
|
||||
platform: "pc",
|
||||
region: account.value.region,
|
||||
sdk_presentation_style: "fullscreen",
|
||||
sdk_screen_transparent: "true",
|
||||
sign_type: "2",
|
||||
uid: account.value.gameUid,
|
||||
timestamp: Math.floor(Date.now() / 1000).toString(),
|
||||
authkey: authkey.value,
|
||||
};
|
||||
const targetLink = new URL(path);
|
||||
for (const key in param) {
|
||||
targetLink.searchParams.append(key, param[key]);
|
||||
}
|
||||
return targetLink.toString();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tao-iframe-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 50vw;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
.tao-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -404,6 +404,9 @@ async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | fa
|
||||
if ("retcode" in captchaResp) {
|
||||
if (!captchaResp.data || captchaResp.data === "") {
|
||||
showSnackbar.error(`[${captchaResp.retcode}] ${captchaResp.message}`);
|
||||
await TGLogger.Error(
|
||||
`[tc-userBadge][tryGetCaptcha] ${captchaResp.retcode} ${captchaResp.message}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const aigisResp: TGApp.BBS.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
|
||||
@@ -424,6 +427,9 @@ async function tryLoginByCaptcha(
|
||||
if ("retcode" in loginResp) {
|
||||
if (!loginResp.data || loginResp.data === "") {
|
||||
showSnackbar.error(`[${loginResp.retcode}] ${loginResp.message}`);
|
||||
await TGLogger.Error(
|
||||
`[tc-userBadge][tryLoginByCaptcha] ${loginResp.retcode} ${loginResp.message}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const aigisResp: TGApp.BBS.CaptchaLogin.CaptchaAigis = JSON.parse(loginResp.data);
|
||||
|
||||
147
src/components/pageConfig/tco-imgQuality.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<!-- 设置图片质量浮窗 -->
|
||||
<template>
|
||||
<TOverlay v-model="model" hide blur-val="10px">
|
||||
<div class="toi-box">
|
||||
<div class="toi-top">
|
||||
<div class="toi-title">调整图片质量</div>
|
||||
<div class="toi-desc">设置图片质量,数值越大图片越清晰,但也会占用更多空间</div>
|
||||
</div>
|
||||
<div class="toi-mid">
|
||||
<v-slider
|
||||
thumb-label="always"
|
||||
color="var(--tgc-od-blue)"
|
||||
thumb-color="var(--tgc-od-red)"
|
||||
v-model="quality"
|
||||
:max="100"
|
||||
:min="5"
|
||||
:step="1"
|
||||
hide-details
|
||||
/>
|
||||
<v-number-input
|
||||
class="toi-input"
|
||||
v-model="quality"
|
||||
control-variant="stacked"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
style="max-width: 100px"
|
||||
type="number"
|
||||
:max="100"
|
||||
:min="5"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="toi-bottom">
|
||||
<button class="toi-btn no-btn" @click="onCancel()">取消</button>
|
||||
<button class="toi-btn ok-btn" @click="onConfirm()">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
const { imageQualityPercent } = storeToRefs(useAppStore());
|
||||
|
||||
const model = defineModel<boolean>({ default: false });
|
||||
const quality = ref<number>(imageQualityPercent.value);
|
||||
|
||||
function onCancel(): void {
|
||||
model.value = false;
|
||||
quality.value = imageQualityPercent.value;
|
||||
}
|
||||
|
||||
async function onConfirm(): Promise<void> {
|
||||
if (quality.value === imageQualityPercent.value) {
|
||||
model.value = false;
|
||||
showSnackbar.info(`图片质量未修改`);
|
||||
return;
|
||||
}
|
||||
imageQualityPercent.value = quality.value;
|
||||
model.value = false;
|
||||
showSnackbar.success(`图片质量已修改为 ${quality.value}%`);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.toi-box {
|
||||
display: flex;
|
||||
width: 400px;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--box-bg-1);
|
||||
color: var(--app-page-content);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.toi-top {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-title);
|
||||
row-gap: 4px;
|
||||
text-align: center;
|
||||
|
||||
.toi-title {
|
||||
color: var(--common-text-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.toi-desc {
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.toi-mid {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 12px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
column-gap: 24px;
|
||||
|
||||
.toi-input {
|
||||
max-width: 100px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.toi-bottom {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toi-btn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 20px;
|
||||
border-radius: 24px;
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
|
||||
&.no-btn {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
<!-- 近期活动卡片组件(用户)-->
|
||||
<template>
|
||||
<div class="ph-pos-user-card">
|
||||
<div class="ph-pos-user-card" ref="posRef">
|
||||
<div class="ph-puc-top">
|
||||
<div class="title">
|
||||
<v-icon title="已完成" color="var(--tgc-od-green)" v-if="props.pos.is_finished">
|
||||
mdi-checkbox-marked-circle-outline
|
||||
</v-icon>
|
||||
<v-icon v-else title="未完成" color="var(--tgc-od-white)">mdi-circle</v-icon>
|
||||
<span>{{ props.pos.name }}</span>
|
||||
<span @click="sharePos()" title="点击分享">{{ props.pos.name }}</span>
|
||||
</div>
|
||||
<div class="subtitle">
|
||||
<!-- 处理幽境危战 -->
|
||||
@@ -29,13 +30,7 @@
|
||||
<!-- 处理真境剧诗 -->
|
||||
<template v-else-if="props.pos.type === ActCalendarTypeEnum.RoleCombat">
|
||||
<div class="combat-append" @click="toCombat()" title="点击前往剧诗页面">
|
||||
<template v-if="!props.pos.role_combat_detail.is_unlock">
|
||||
<span>未解锁</span>
|
||||
</template>
|
||||
<template v-else-if="!props.pos.role_combat_detail.has_data">
|
||||
<span>尚未挑战</span>
|
||||
</template>
|
||||
<span v-else>第{{ props.pos.role_combat_detail.max_round_id }}幕</span>
|
||||
<span>{{ getCombatStat(props.pos.role_combat_detail) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 处理深境螺旋 -->
|
||||
@@ -69,7 +64,7 @@
|
||||
</div>
|
||||
<div class="ph-puc-duration">
|
||||
<template v-if="isStart">
|
||||
<span title="剩余时间">{{ stamp2LastTime(restTs * 1000) }}</span>
|
||||
<span title="剩余时间" data-html2canvas-ignore>{{ stamp2LastTime(restTs * 1000) }}</span>
|
||||
<span title="活动时间">
|
||||
{{ timestampToDate(Number(props.pos.start_timestamp) * 1000) }} ~
|
||||
{{ timestampToDate(Number(props.pos.end_timestamp) * 1000) }}
|
||||
@@ -98,8 +93,9 @@
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import { ActCalendarTypeEnum } from "@enum/game.js";
|
||||
import { getHardChallengeDesc } from "@Sql/utils/transUserRecord.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { computed, onMounted, onUnmounted, ref, useTemplateRef } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
type PhCompPositionUserProps = { pos: TGApp.Game.ActCalendar.ActItem };
|
||||
@@ -111,6 +107,7 @@ const router = useRouter();
|
||||
|
||||
const props = defineProps<PhCompPositionUserProps>();
|
||||
const emits = defineEmits<PhCompPositionUserEmits>();
|
||||
const posEl = useTemplateRef<HTMLDivElement>("posRef");
|
||||
|
||||
const endTs = ref<number>(0);
|
||||
const restTs = ref<number>(0);
|
||||
@@ -129,6 +126,10 @@ onMounted(() => {
|
||||
timer = setInterval(handlePosition, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
});
|
||||
|
||||
function handlePosition(): void {
|
||||
if (restTs.value < 1) {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
@@ -155,9 +156,17 @@ function showMaterial(reward: TGApp.Game.ActCalendar.ActReward): void {
|
||||
emits("clickM", reward);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
});
|
||||
function getCombatStat(detail: TGApp.Game.ActCalendar.ActRoleCombat): string {
|
||||
if (!detail.is_unlock) return "未解锁";
|
||||
if (!detail.has_data) return "尚未挑战";
|
||||
if (detail.difficulty_id < 5) return `第${detail.max_round_id}幕`;
|
||||
return `月谕模式·第${detail.max_round_id}幕·圣牌${detail.tarot_finished_cnt}`;
|
||||
}
|
||||
|
||||
async function sharePos(): Promise<void> {
|
||||
if (!posEl.value) return;
|
||||
await generateShareImg(`活动-${props.pos.name}.png`, posEl.value, 2);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-pos-user-card {
|
||||
@@ -190,6 +199,10 @@ onUnmounted(() => {
|
||||
justify-content: flex-start;
|
||||
column-gap: 4px;
|
||||
font-family: var(--font-title);
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@@ -267,6 +280,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
@click="toChannel(item)"
|
||||
>
|
||||
<TMiImg :src="item.icon" alt="icon" :ori="true" />
|
||||
<span>{{ item.title }}</span>
|
||||
<span class="toc-list-title">{{ item.title }}</span>
|
||||
<span class="toc-list-id">GID:{{ item.gid }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,32 +60,38 @@ async function toChannel(item: ChannelItem): Promise<void> {
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.toc-box {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--app-page-bg);
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.toc-list {
|
||||
display: grid;
|
||||
margin-top: 10px;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.toc-list-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 5px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-1);
|
||||
column-gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s linear;
|
||||
|
||||
@@ -95,17 +102,22 @@ async function toChannel(item: ChannelItem): Promise<void> {
|
||||
}
|
||||
|
||||
img {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin-right: 10px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 10px;
|
||||
.toc-list-title {
|
||||
margin-right: 8px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.toc-list-id {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 2px;
|
||||
font-size: 6px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -107,9 +107,8 @@ const relicsBox = computed<AvatarRelics>(() => {
|
||||
];
|
||||
});
|
||||
const isFetterMax = computed<boolean>(() => {
|
||||
if (props.modelValue.avatar.id === 10000005 || props.modelValue.avatar.id === 10000007) {
|
||||
return true;
|
||||
}
|
||||
const skipList = [10000005, 10000007, 10000117, 10000118];
|
||||
if (skipList.includes(props.modelValue.avatar.id)) return true;
|
||||
return props.modelValue.avatar.fetter === 10;
|
||||
});
|
||||
const skills = computed<Array<TGApp.Game.Avatar.Skill>>(() =>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<div class="tucfi-label">
|
||||
<slot name="label">{{ props.label }}</slot>
|
||||
</div>
|
||||
<div v-if="props.data === null"><span class="tucfi-data">暂无数据</span></div>
|
||||
<div v-if="!props.data">
|
||||
<span class="tucfi-data">暂无数据</span>
|
||||
</div>
|
||||
<div v-else-if="!Array.isArray(props.data)" class="tucfi-data">
|
||||
<TItemBox :model-value="getBox(props.data)" />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="tuco-box">
|
||||
<TucTile title="最佳记录" :val="props.data.max_round_id" />
|
||||
<TucTile title="最佳记录" :val="getBestVal()" />
|
||||
<TucTile :title="`获得星章-${props.data.medal_num}`" :val="props.data.get_medal_round_list" />
|
||||
<TucTile :title="getTitle()" :val="`第${props.data.max_round_id}幕`" />
|
||||
<TucTile :title="getRoundTitle()" :val="getRoundVal()" />
|
||||
<TucTile title="消耗幻剧之花" :val="props.data.coin_num" />
|
||||
<TucFight label="最快完成演出" :data="props.fights.shortest_avatar_list" />
|
||||
<TucTile title="总耗时" :val="getTime()" />
|
||||
@@ -21,7 +21,7 @@ type TucOverviewProps = { data: TGApp.Game.Combat.Stat; fights: TGApp.Game.Comba
|
||||
|
||||
const props = defineProps<TucOverviewProps>();
|
||||
|
||||
function getTitle(): string {
|
||||
function getRoundTitle(): string {
|
||||
switch (props.data.difficulty_id) {
|
||||
case 0:
|
||||
return "未选择";
|
||||
@@ -33,11 +33,23 @@ function getTitle(): string {
|
||||
return "困难模式";
|
||||
case 4:
|
||||
return "卓越模式";
|
||||
case 5:
|
||||
return "月谕模式";
|
||||
default:
|
||||
return `未知模式${props.data.difficulty_id}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getBestVal(): string {
|
||||
if (props.data.difficulty_id < 5) return `第${props.data.max_round_id}幕`;
|
||||
return `第${props.data.max_round_id}幕·圣牌${props.data.tarot_finished_cnt}`;
|
||||
}
|
||||
|
||||
function getRoundVal(): string {
|
||||
if (props.data.difficulty_id < 5) return `第${props.data.max_round_id}幕`;
|
||||
return `${props.data.tarot_finished_cnt + props.data.max_round_id}`;
|
||||
}
|
||||
|
||||
function getTime(): string {
|
||||
const sec = props.fights.total_use_time % 60;
|
||||
const min = (props.fights.total_use_time - sec) / 60;
|
||||
@@ -48,7 +60,7 @@ function getTime(): string {
|
||||
.tuco-box {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-gap: 8px;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
<!-- 真境剧诗,单轮次卡片组件 -->
|
||||
<template>
|
||||
<div class="tucr-box">
|
||||
<div class="tucr-title">
|
||||
<img :src="`/icon/star/combat${modelValue.is_get_medal ? 1 : 0}.webp`" alt="combat" />
|
||||
<span class="main">第{{ modelValue.round_id }}幕</span>
|
||||
<span class="sub">{{ timestampToDate(Number(modelValue.finish_time) * 1000) }}</span>
|
||||
<img :src="`/icon/combat/${getIcon()}.webp`" alt="combat" />
|
||||
<span class="main" v-if="props.round.is_tarot">
|
||||
圣牌挑战·{{ props.round.tarot_serial_no }}
|
||||
</span>
|
||||
<span class="main" v-else>第{{ props.round.round_id }}幕</span>
|
||||
<span class="sub">{{ timestampToDate(Number(props.round.finish_time) * 1000) }}</span>
|
||||
</div>
|
||||
<div class="tucr-content">
|
||||
<TucSub title="出演角色" class="main">
|
||||
<TucAvatars :model-value="modelValue.avatars" :detail="true" />
|
||||
<TucAvatars :model-value="props.round.avatars" :detail="true" />
|
||||
</TucSub>
|
||||
<TucSub title="辉彩祝福" class="main">
|
||||
<TucBuffs :model-value="modelValue.splendour_buff" />
|
||||
<TucBuffs :model-value="props.round.splendour_buff" />
|
||||
</TucSub>
|
||||
<TucSub :title="`神秘收获(${modelValue.choice_cards.length})`" class="sub">
|
||||
<TucCards :model-value="modelValue.choice_cards" />
|
||||
<TucSub :title="`神秘收获(${props.round.choice_cards.length})`" class="sub">
|
||||
<TucCards :model-value="props.round.choice_cards" />
|
||||
</TucSub>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,7 +30,12 @@ import TucBuffs from "./tuc-buffs.vue";
|
||||
import TucCards from "./tuc-cards.vue";
|
||||
import TucSub from "./tuc-sub.vue";
|
||||
|
||||
defineProps<{ modelValue: TGApp.Game.Combat.RoundData }>();
|
||||
type TucRoundProps = { round: TGApp.Game.Combat.RoundData };
|
||||
const props = defineProps<TucRoundProps>();
|
||||
|
||||
function getIcon(): string {
|
||||
return `${props.round.is_tarot ? "tarot" : "star"}_${props.round.is_get_medal ? "1" : "0"}`;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tucr-box {
|
||||
@@ -49,8 +58,8 @@ defineProps<{ modelValue: TGApp.Game.Combat.RoundData }>();
|
||||
column-gap: 4px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
aspect-ratio: 1;
|
||||
height: 30px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
<!-- 真境剧诗概况卡片组件 -->
|
||||
<template>
|
||||
<div class="tuct-box">
|
||||
<div class="tuct-title">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
<slot name="title">{{ props.title }}</slot>
|
||||
</div>
|
||||
<div class="tuct-text" v-if="!Array.isArray(val)">
|
||||
<slot name="text">{{ val }}</slot>
|
||||
<div class="tuct-text" v-if="!Array.isArray(props.val)">
|
||||
<slot name="text">{{ props.val }}</slot>
|
||||
</div>
|
||||
<div class="tuct-icons" v-else>
|
||||
<img v-for="(v, idx) in val" :key="idx" :src="`/icon/star/combat${v}.webp`" :alt="`${v}`" />
|
||||
<template v-for="(v, idx) in props.val" :key="idx">
|
||||
<img
|
||||
v-if="idx < 10"
|
||||
:src="`/icon/combat/star_${v}.webp`"
|
||||
:alt="`${v}`"
|
||||
:title="`第${idx + 1}幕`"
|
||||
/>
|
||||
<img v-else :src="`/icon/combat/tarot_${v}.webp`" :alt="`${v}`" :title="`圣牌${idx - 9}`" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ title: string; val: string | number | Array<number> }>();
|
||||
type TucTileProps = { title: string; val: string | number | Array<number> };
|
||||
|
||||
const props = defineProps<TucTileProps>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuct-box {
|
||||
@@ -47,7 +58,7 @@ defineProps<{ title: string; val: string | number | Array<number> }>();
|
||||
|
||||
img {
|
||||
height: 30px;
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
144
src/components/userGacha/gbr-data-line.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<!-- 千星奇域概览单项组件 -->
|
||||
<template>
|
||||
<div class="gbr-dl-box">
|
||||
<div class="gbr-dl-progress" />
|
||||
<div class="gbr-dl-icon">
|
||||
<img :alt="props.data.name" :src="getIcon()" />
|
||||
</div>
|
||||
<div class="gbr-dl-base">
|
||||
<div class="gbr-dl-name">{{ props.data.name }}</div>
|
||||
<div class="gbr-dl-time">{{ props.data.time }}</div>
|
||||
</div>
|
||||
<div class="gbr-dl-info">
|
||||
<div class="gbr-dl-cnt">{{ props.count }}</div>
|
||||
<div class="gbr-dl-hint" v-if="hint !== ''">{{ hint }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
export type GbrDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGachaB; count: number };
|
||||
|
||||
const props = defineProps<GbrDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
function getIcon(): string {
|
||||
console.log(props.data);
|
||||
// const find = AppGachaBData.find((i) => i.id.toString() === props.data.itemId);
|
||||
// if (!find) return `/source/UI/paimon.webp`;
|
||||
// return `https://api.hakush.in/gi/UI/${find.icon}.webp`;
|
||||
// TODO: 缺失元数据
|
||||
return `/source/UI/paimon.webp`;
|
||||
}
|
||||
|
||||
function getEndHint(): string {
|
||||
if (props.data.gachaType === "1000") return "";
|
||||
if (!props.data.isUp) return "歪";
|
||||
return "";
|
||||
}
|
||||
|
||||
const progressColor = computed<string>(() => {
|
||||
if (hint === "UP" && props.data.rank === "5") return "#d19a66";
|
||||
if (hint === "UP" && props.data.rank === "4") return "#c678dd";
|
||||
if (hint === "歪") return "#e06c75";
|
||||
return "#61afef";
|
||||
});
|
||||
const progressWidth = computed<string>(() => {
|
||||
let final = 10;
|
||||
if (props.data.rank === "5") {
|
||||
if (props.data.gachaType === "302") final = 80;
|
||||
else final = 90;
|
||||
} else if (props.data.rank === "4") final = 10;
|
||||
else return "0%";
|
||||
return ((props.count / final) * 100).toFixed(2) + "%";
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.gbr-dl-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
column-gap: 4px;
|
||||
}
|
||||
|
||||
.gbr-dl-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: v-bind(progressWidth); /* stylelint-disable-line value-keyword-case */
|
||||
max-width: 100%;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
background: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
|
||||
}
|
||||
|
||||
.gbr-dl-icon {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.gbr-dl-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gbr-dl-name {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.gbr-dl-time {
|
||||
color: var(--box-text-7);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.gbr-dl-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
column-gap: 4px;
|
||||
}
|
||||
|
||||
.gbr-dl-cnt {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.gbr-dl-hint {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--box-bg-3);
|
||||
color: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
|
||||
font-family: var(--font-title);
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
</style>
|
||||
263
src/components/userGacha/gbr-data-view.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<!-- 千星奇域概览数据视图组件 -->
|
||||
<template>
|
||||
<div class="gbr-dv-container">
|
||||
<div class="gbr-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gbr-dvt-subtitle">
|
||||
<span v-show="props.dataVal.length === 0">暂无数据</span>
|
||||
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
|
||||
</div>
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-item">
|
||||
<span>4☆已垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-item">
|
||||
<span>5☆已垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-item">
|
||||
<span>5☆平均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-item">
|
||||
<span>5☆统计</span>
|
||||
<span>{{ getTitle("5") }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-item">
|
||||
<span>4☆统计</span>
|
||||
<span>{{ getTitle("4") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 这边放具体物品的列表 -->
|
||||
<div class="gbr-bottom">
|
||||
<v-tabs v-model="tab" density="compact">
|
||||
<v-tab value="5">5☆</v-tab>
|
||||
<v-tab value="4">4☆</v-tab>
|
||||
<v-tab value="3">3☆</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="gbr-bottom-window">
|
||||
<v-window-item value="5" class="gbr-b-window-item">
|
||||
<v-virtual-scroll :items="star5List" :item-height="48">
|
||||
<template #default="{ item }">
|
||||
<GbrDataLine :data="item.data" :count="item.count" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
<v-window-item value="4" class="gbr-b-window-item">
|
||||
<v-virtual-scroll :items="star4List" :item-height="48">
|
||||
<template #default="{ item }">
|
||||
<GbrDataLine :data="item.data" :count="item.count" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
<v-window-item value="3" class="gbr-b-window-item">
|
||||
<v-virtual-scroll :items="star3List" :item-height="48">
|
||||
<template #default="{ item }">
|
||||
<GbrDataLine :data="item.data" :count="item.count" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import GbrDataLine, { type GbrDataLineProps } from "./gbr-data-line.vue";
|
||||
|
||||
type GachaDataViewProps = {
|
||||
dataType: "normal" | "boy" | "girl";
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGachaB>;
|
||||
};
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
// data
|
||||
const loading = ref<boolean>(true); // 是否加载完
|
||||
const title = ref<string>(""); // 卡片标题
|
||||
const startDate = ref<string>(""); // 最早的时间
|
||||
const endDate = ref<string>(""); // 最晚的时间
|
||||
const star5List = shallowRef<Array<GbrDataLineProps>>([]); // 5星物品数据
|
||||
const star4List = shallowRef<Array<GbrDataLineProps>>([]); // 4星物品数据
|
||||
const star3List = shallowRef<Array<GbrDataLineProps>>([]);
|
||||
const reset5count = ref<number>(1); // 5星垫抽数量
|
||||
const reset4count = ref<number>(1); // 4星垫抽数量
|
||||
const reset3count = ref<number>(1); // 3星垫抽数量
|
||||
const star3count = ref<number>(0); // 3星物品数量
|
||||
const star5avg = ref<string>(""); // 5星平均抽数
|
||||
const tab = ref<string>("5"); // tab
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function loadData(): void {
|
||||
title.value = getTitle("top");
|
||||
const tempData = props.dataVal;
|
||||
const temp5Data: Array<GbrDataLineProps> = [];
|
||||
const temp4Data: Array<GbrDataLineProps> = [];
|
||||
const temp3Data: Array<GbrDataLineProps> = [];
|
||||
// 按照 id 升序
|
||||
tempData
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
.forEach((item) => {
|
||||
// 处理时间
|
||||
if (startDate.value === "" || item.time < startDate.value) startDate.value = item.time;
|
||||
if (endDate.value === "" || item.time > endDate.value) endDate.value = item.time;
|
||||
if (item.rank === "2") {
|
||||
reset3count.value++;
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
} else if (item.rank === "3") {
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
temp3Data.push({ data: item, count: reset3count.value });
|
||||
reset3count.value = 1;
|
||||
} else if (item.rank === "4") {
|
||||
reset5count.value++;
|
||||
temp4Data.push({ data: item, count: reset4count.value });
|
||||
reset4count.value = 1;
|
||||
} else if (item.rank === "5") {
|
||||
reset4count.value++;
|
||||
temp5Data.push({ data: item, count: reset5count.value });
|
||||
reset5count.value = 1;
|
||||
}
|
||||
});
|
||||
star5List.value = temp5Data.reverse();
|
||||
star4List.value = temp4Data.reverse();
|
||||
star3List.value = temp3Data.reverse();
|
||||
star5avg.value = getStar5Avg();
|
||||
}
|
||||
|
||||
// 获取标题
|
||||
function getTitle(type: "top" | "5" | "4" | "3"): string {
|
||||
if (type === "top") {
|
||||
if (props.dataType === "normal") return "常驻颂愿";
|
||||
if (props.dataType === "boy") return "活动颂愿(男)";
|
||||
if (props.dataType === "girl") return "活动颂愿(女)";
|
||||
return "";
|
||||
}
|
||||
if (props.dataVal.length === 0) return "暂无数据";
|
||||
if (type === "5") {
|
||||
// 5星物品统计 00.00%
|
||||
return `${star5List.value.length} [${((star5List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
if (type === "4") {
|
||||
// 4星物品统计
|
||||
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
// 3星物品统计
|
||||
return `${star3count.value} [${((star3count.value * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
|
||||
// 获取5星平均抽数
|
||||
function getStar5Avg(): string {
|
||||
const resetList = star5List.value.map((item) => item.count);
|
||||
if (resetList.length === 0) return "0";
|
||||
const total = resetList.reduce((a, b) => a + b);
|
||||
return (total / star5List.value.length).toFixed(2);
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.dataVal,
|
||||
() => {
|
||||
star5List.value = [];
|
||||
star4List.value = [];
|
||||
reset5count.value = 1;
|
||||
reset4count.value = 1;
|
||||
star3count.value = 1;
|
||||
startDate.value = "";
|
||||
endDate.value = "";
|
||||
star5avg.value = "";
|
||||
tab.value = "5";
|
||||
loadData();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gbr-dv-container {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.gbr-dvt-title {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.gbr-dvt-subtitle {
|
||||
width: 100%;
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.gbr-mid-list {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
border-top: 1px solid var(--common-shadow-4);
|
||||
color: var(--box-text-7);
|
||||
}
|
||||
|
||||
.gbr-ml-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gbr-bottom {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100% - 150px);
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gbr-bottom-window {
|
||||
position: relative;
|
||||
height: calc(100vh - 428px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gbr-b-window-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
:deep(.v-virtual-scroll__item + .v-virtual-scroll__item) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
</style>
|
||||
34
src/components/userGacha/gbr-overview.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<!-- 千星奇域祈愿概览组件 -->
|
||||
<template>
|
||||
<div class="gro-o-container">
|
||||
<GbrDataView :data-val="normalData" data-type="normal" />
|
||||
<GbrDataView :data-val="boyData" data-type="boy" />
|
||||
<GbrDataView :data-val="girlData" data-type="girl" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import GbrDataView from "./gbr-data-view.vue";
|
||||
|
||||
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "1000"),
|
||||
);
|
||||
const girlData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "20011" || item.opGachaType === "20012"),
|
||||
);
|
||||
const boyData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "20021" || item.opGachaType === "20022"),
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-o-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
column-gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
</style>
|
||||
62
src/components/userGacha/gbr-table.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<!-- 千星奇域数据表格 -->
|
||||
<template>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="props.modelValue"
|
||||
fixed-header
|
||||
fixed-footer
|
||||
class="gbr-t-box"
|
||||
>
|
||||
<template v-slot:item="{ item }">
|
||||
<tr class="gbr-t-tr">
|
||||
<td>{{ item.time }}</td>
|
||||
<td>{{ getPool(item.opGachaType) }}</td>
|
||||
<td>{{ item.type }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.rank }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
|
||||
|
||||
const props = defineProps<GroTableProps>();
|
||||
|
||||
const headers = <const>[
|
||||
{ title: "时间", align: "center", key: "time" },
|
||||
{ title: "卡池", align: "center", key: "opGachaType" },
|
||||
{ title: "类型", align: "center", key: "type" },
|
||||
{ title: "名称", align: "center", key: "name" },
|
||||
{ title: "星级", align: "center", key: "rank" },
|
||||
];
|
||||
|
||||
function getPool(type: string) {
|
||||
switch (type) {
|
||||
case "1000":
|
||||
return "常驻颂愿";
|
||||
case "2000":
|
||||
return "活动颂愿";
|
||||
case "20011":
|
||||
case "20012":
|
||||
return "活动颂愿-男";
|
||||
case "20021":
|
||||
case "20022":
|
||||
return "活动颂愿-女";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gbr-t-box {
|
||||
height: calc(100vh - 200px);
|
||||
padding-right: 5px;
|
||||
border-radius: 5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gbr-t-tr {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@ import { computed } from "vue";
|
||||
|
||||
import { AppGachaData } from "@/data/index.js";
|
||||
|
||||
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.SingleTable; count: number };
|
||||
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGacha; count: number };
|
||||
|
||||
const props = defineProps<GroDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
@@ -64,7 +64,7 @@ import GroDataLine, { type GroDataLineProps } from "./gro-data-line.vue";
|
||||
|
||||
type GachaDataViewProps = {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.SingleTable>;
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGacha>;
|
||||
};
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
134
src/components/userGacha/gro-iframe.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<!-- 嵌入游戏内容的iframe组件 -->
|
||||
<template>
|
||||
<div class="gro-iframe-container">
|
||||
<v-tabs class="gro-ic-tabs" v-model="poolTab" align-tabs="start" direction="vertical">
|
||||
<v-tab v-for="(item, index) in tabList" :key="index" :value="item.value">
|
||||
<template v-if="item.beyond">
|
||||
<img src="/icon/nation/千星奇域.webp" title="千星奇域" alt="beyond" />
|
||||
</template>
|
||||
{{ item.label }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<iframe class="gro-iframe" :src="link" style="width: 100%; height: 100%; border: none" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import takumiReq from "@req/takumiReq.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
/**
|
||||
* 卡池类型-ID映射
|
||||
* @remarks
|
||||
* 目前缺失集录&新手池
|
||||
* TODO: 动态获取当前卡池类型&ID映射
|
||||
*/
|
||||
const GachaIdMap: Record<string, string> = {
|
||||
"200": "34ff1a235049182fd199d285110e3e7d292c50cd", // 常驻
|
||||
"301": "182e725d99b742b14839117650d3e79628cc6221", //角色活动
|
||||
"302": "8ff7a7d42bea79b0d54e92fdb58a20f971490372", // 武器活动
|
||||
"400": "bb0486115a7e7c4bd2994135f7d212014b17173b", // 角色活动-2
|
||||
"1000": "f3f5090a8ec0b28f15805c9969aa6c4ec357", // 千星奇域常驻
|
||||
"20011": "a8d0a985efb4ed61eb2e73a86a57237bd116", // 千星奇域角色活动-男
|
||||
"20021": "57016dec6b768231ba1342c01935417a799b", // 千星奇域角色活动-女
|
||||
};
|
||||
|
||||
const tabNormal: ReadonlyArray<GroTab> = [
|
||||
{ label: "常驻祈愿", value: "200" },
|
||||
{ label: "角色活动祈愿", value: "301" },
|
||||
{ label: "武器活动祈愿", value: "302" },
|
||||
{ label: "角色活动祈愿-2", value: "400" },
|
||||
];
|
||||
const tabBeyond: ReadonlyArray<GroTab> = [
|
||||
{ label: "常驻颂愿", value: "1000", beyond: true },
|
||||
{ label: "活动颂愿-男", value: "20011", beyond: true },
|
||||
{ label: "活动颂愿-女", value: "20021", beyond: true },
|
||||
];
|
||||
|
||||
type GroTabKey = keyof typeof GachaIdMap;
|
||||
type GroTab = { label: string; value: string; beyond?: boolean };
|
||||
type GroIframeProps = { mode: "normal" | "beyond" };
|
||||
const props = defineProps<GroIframeProps>();
|
||||
|
||||
const { cookie, account } = storeToRefs(useUserStore());
|
||||
const authkey = ref<string>("");
|
||||
const link = ref<string>("");
|
||||
const poolTab = ref<GroTabKey>("200");
|
||||
const tabList = shallowRef<ReadonlyArray<GroTab>>(props.mode === "beyond" ? tabBeyond : tabNormal);
|
||||
|
||||
onMounted(async () => {
|
||||
link.value = await getUrl();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => poolTab.value,
|
||||
async () => {
|
||||
link.value = await getUrl();
|
||||
},
|
||||
);
|
||||
|
||||
async function getUrl(): Promise<string> {
|
||||
const path = "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v3/index.html";
|
||||
const pathB = "https://webstatic.mihoyo.com/hk4e/event/e20250716gacha/index.html";
|
||||
const pathF = poolTab.value.length < 4 ? path : pathB;
|
||||
if (authkey.value === "") await refreshAuthkey();
|
||||
const param: Record<string, string> = {
|
||||
win_mode: "fullscreen",
|
||||
no_joypad_close: "1",
|
||||
authkey_ver: "1",
|
||||
sign_type: "2",
|
||||
auth_appid: "webview_gacha",
|
||||
gacha_id: GachaIdMap[poolTab.value],
|
||||
timestamp: Math.floor(Date.now() / 1000).toString(),
|
||||
lang: "zh-cn",
|
||||
device_type: "pc",
|
||||
region: account.value.region,
|
||||
authkey: authkey.value,
|
||||
game_biz: account.value.gameBiz,
|
||||
};
|
||||
const targetLink = new URL(pathF);
|
||||
for (const key in param) {
|
||||
targetLink.searchParams.append(key, param[key]);
|
||||
}
|
||||
return targetLink.toString();
|
||||
}
|
||||
|
||||
async function refreshAuthkey(): Promise<void> {
|
||||
if (!cookie.value || !account.value) {
|
||||
return;
|
||||
}
|
||||
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
|
||||
if (typeof authkeyRes === "string") {
|
||||
authkey.value = authkeyRes;
|
||||
} else {
|
||||
showSnackbar.error("获取authkey失败");
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.gro-iframe-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gro-ic-tabs {
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.gro-ic-window {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -12,22 +12,22 @@ import { computed, ref, watch } from "vue";
|
||||
|
||||
import GroDataView from "./gro-data-view.vue";
|
||||
|
||||
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.SingleTable> };
|
||||
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGacha> };
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
const newData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const newData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "100"),
|
||||
);
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "200"),
|
||||
);
|
||||
const avatarData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const avatarData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "301"),
|
||||
);
|
||||
const weaponData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const weaponData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "302"),
|
||||
);
|
||||
const mixData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const mixData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "500"),
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ watch(
|
||||
.gro-o-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
grid-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
grid-template-columns: v-bind(cnCols); /* stylelint-disable-line value-keyword-case */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</v-data-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.SingleTable> };
|
||||
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGacha> };
|
||||
|
||||
const props = defineProps<GroTableProps>();
|
||||
|
||||
|
||||
@@ -62,9 +62,11 @@ import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
type UgoUidProps = { mode: "import" | "export" };
|
||||
type UgoUidItem = { uid: string; length: number; time: string };
|
||||
|
||||
const fpEmptyText = "点击选择文件路径";
|
||||
|
||||
const props = defineProps<UgoUidProps>();
|
||||
const visible = defineModel<boolean>();
|
||||
const fp = ref<string>("未选择");
|
||||
const fp = ref<string>(fpEmptyText);
|
||||
const dataRaw = shallowRef<TGApp.Plugins.UIGF.Schema4>();
|
||||
const data = shallowRef<Array<UgoUidItem>>([]);
|
||||
const selectedData = shallowRef<Array<UgoUidItem>>([]);
|
||||
@@ -91,7 +93,7 @@ async function refreshData(): Promise<void> {
|
||||
data.value = [];
|
||||
dataRaw.value = undefined;
|
||||
if (props.mode === "import") {
|
||||
fp.value = "未选择";
|
||||
fp.value = fpEmptyText;
|
||||
await handleImportData();
|
||||
} else {
|
||||
fp.value = await getDefaultSavePath();
|
||||
@@ -118,7 +120,7 @@ async function selectFile(): Promise<void> {
|
||||
}
|
||||
|
||||
async function handleImportData(): Promise<void> {
|
||||
if (fp.value === "未选择") return;
|
||||
if (fp.value === fpEmptyText) return;
|
||||
try {
|
||||
await showLoading.start("正在导入数据...", "正在验证数据...");
|
||||
const check = await verifyUigfData(fp.value, true);
|
||||
@@ -161,7 +163,7 @@ async function handleExportData(): Promise<void> {
|
||||
data.value = tmpData;
|
||||
}
|
||||
|
||||
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.SingleTable[]): UgoUidItem {
|
||||
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.TableGacha[]): UgoUidItem {
|
||||
const timeList = data.map((item) => new Date(item.time).getTime());
|
||||
return {
|
||||
uid: data[0].uid,
|
||||
@@ -178,7 +180,7 @@ async function handleSelected(): Promise<void> {
|
||||
async function handleImport(): Promise<void> {
|
||||
if (!dataRaw.value) {
|
||||
showSnackbar.error("未获取到数据!");
|
||||
fp.value = "未选择";
|
||||
fp.value = fpEmptyText;
|
||||
return;
|
||||
}
|
||||
if (selectedData.value.length === 0) {
|
||||
@@ -234,6 +236,7 @@ async function handleExport(): Promise<void> {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
@@ -249,6 +252,7 @@ async function handleExport(): Promise<void> {
|
||||
color: var(--tgc-od-white);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ugo-header {
|
||||
|
||||
@@ -278,6 +278,7 @@ async function autoSign(ck: TGApp.App.Account.Cookie, ch?: string): Promise<void
|
||||
}
|
||||
await TGLogger.Script("[米游币任务]正在执行打卡");
|
||||
const ckSign = { stoken: ck.stoken, stuid: ck.stuid, mid: ck.mid };
|
||||
await painterReq.forum.recent(26, 2, 1, undefined, 20, ckSign);
|
||||
const resp = await apiHubReq.sign(ckSign, 2, ch);
|
||||
if (resp.retcode !== 0) {
|
||||
if (resp.retcode !== 1034) {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<!-- TODO: 链接处理重构 -->
|
||||
<template>
|
||||
<div class="tp-image-box" v-if="localUrl !== undefined">
|
||||
<img
|
||||
:src="localUrl"
|
||||
@click="showOverlay = true"
|
||||
:alt="props.data.insert.image"
|
||||
:title="getImageTitle()"
|
||||
/>
|
||||
<img :src="localUrl" @click="showOverlay = true" :alt="oriUrl" :title="getImageTitle()" />
|
||||
<div
|
||||
class="act"
|
||||
@click.stop="showOri = true"
|
||||
@@ -16,7 +12,7 @@
|
||||
<v-icon size="16" color="white">mdi-magnify</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tp-image-load" :title="props.data.insert.image">
|
||||
<div v-else class="tp-image-load" :title="oriUrl">
|
||||
<v-progress-circular :indeterminate="true" color="primary" size="small" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
@@ -26,6 +22,7 @@
|
||||
v-model:link="localUrl"
|
||||
v-model:ori="showOri"
|
||||
v-model:bgColor="bgColor"
|
||||
v-model:format="imgExt"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -39,7 +36,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import VpOverlayImage from "./vp-overlay-image.vue";
|
||||
|
||||
export type TpImage = {
|
||||
insert: { image: string };
|
||||
insert: { image: string | TGApp.BBS.Post.Image };
|
||||
attributes?: {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -54,12 +51,14 @@ const appStore = useAppStore();
|
||||
const { imageQualityPercent } = storeToRefs(appStore);
|
||||
const props = defineProps<TpImageProps>();
|
||||
const showOverlay = ref<boolean>(false);
|
||||
const showOri = ref<boolean>(
|
||||
props.data.insert.image.endsWith(".gif") || imageQualityPercent.value === 100,
|
||||
);
|
||||
|
||||
const localUrl = ref<string>();
|
||||
const bgColor = ref<string>("transparent");
|
||||
|
||||
const oriUrl = ref<string>("");
|
||||
const imgExt = computed<string>(() => getImageExt());
|
||||
const showOri = ref<boolean>(imgExt.value === "gif" || imageQualityPercent.value === 100);
|
||||
|
||||
const imgWidth = computed<string>(() => {
|
||||
if (props.data.attributes === undefined) return "auto";
|
||||
if (props.data.attributes.width >= 690) return "100%";
|
||||
@@ -69,7 +68,8 @@ const imgWidth = computed<string>(() => {
|
||||
console.log("tp-image", props.data.insert.image, props.data.attributes);
|
||||
|
||||
onMounted(async () => {
|
||||
const link = appStore.getImageUrl(props.data.insert.image);
|
||||
oriUrl.value = miniImgUrl();
|
||||
const link = appStore.getImageUrl(oriUrl.value, imgExt.value);
|
||||
localUrl.value = await saveImgLocal(link);
|
||||
});
|
||||
|
||||
@@ -77,9 +77,12 @@ watch(
|
||||
() => showOri.value,
|
||||
async () => {
|
||||
if (!showOri.value) return;
|
||||
await showLoading.start("正在加载原图", props.data.insert.image);
|
||||
await showLoading.start("正在加载原图", oriUrl.value);
|
||||
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
|
||||
localUrl.value = await saveImgLocal(props.data.insert.image);
|
||||
const ext = getImageExt();
|
||||
if (!["png", "jpg", "jpeg", "gif", "webp"].includes(ext.toLowerCase())) {
|
||||
localUrl.value = await saveImgLocal(`${oriUrl.value}?format=jpg`);
|
||||
} else localUrl.value = await saveImgLocal(oriUrl.value);
|
||||
await showLoading.end();
|
||||
},
|
||||
);
|
||||
@@ -88,19 +91,49 @@ onUnmounted(() => {
|
||||
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
|
||||
});
|
||||
|
||||
function miniImgUrl(): string {
|
||||
let url: string;
|
||||
if (typeof props.data.insert.image === "string") {
|
||||
url = props.data.insert.image;
|
||||
} else {
|
||||
url = props.data.insert.image.url;
|
||||
}
|
||||
const link = new URL(url);
|
||||
return `${link.origin}${link.pathname}`;
|
||||
}
|
||||
|
||||
function getImageTitle(): string {
|
||||
if (props.data.attributes == undefined) return "";
|
||||
const res: string[] = [];
|
||||
res.push(`宽度:${props.data.attributes.width}px`);
|
||||
res.push(`高度:${props.data.attributes.height}px`);
|
||||
if (props.data.attributes.size) {
|
||||
const size = bytesToSize(props.data.attributes.size);
|
||||
res.push(`大小:${size}`);
|
||||
if (props.data.attributes) {
|
||||
res.push(`宽度:${props.data.attributes.width}px`);
|
||||
res.push(`高度:${props.data.attributes.height}px`);
|
||||
if (props.data.attributes.size) {
|
||||
const size = bytesToSize(props.data.attributes.size);
|
||||
res.push(`大小:${size}`);
|
||||
}
|
||||
res.push(`格式:${getImageExt()}`);
|
||||
return res.join("\n");
|
||||
}
|
||||
if (props.data.attributes.ext) {
|
||||
res.push(`格式:${props.data.attributes.ext}`);
|
||||
if (typeof props.data.insert.image !== "string") {
|
||||
res.push(`宽度:${props.data.insert.image.width}px`);
|
||||
res.push(`高度:${props.data.insert.image.height}px`);
|
||||
if (props.data.insert.image.size) {
|
||||
const size = bytesToSize(Number(props.data.insert.image.size));
|
||||
res.push(`大小:${size}`);
|
||||
}
|
||||
res.push(`格式:${getImageExt()}`);
|
||||
return res.join("\n");
|
||||
}
|
||||
return res.join("\n");
|
||||
return "";
|
||||
}
|
||||
|
||||
function getImageExt(): string {
|
||||
if (props.data.attributes && props.data.attributes.ext) return props.data.attributes.ext;
|
||||
if (typeof props.data.insert.image !== "string") {
|
||||
return props.data.insert.image.format;
|
||||
}
|
||||
const arr = oriUrl.value.split(".");
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TpUgcLevel from "@comp/viewPost/tp-ugc-level.vue";
|
||||
import TpUgcTag from "@comp/viewPost/tp-ugc-tag.vue";
|
||||
import type { Component } from "vue";
|
||||
|
||||
import TpBackupText from "./tp-backupText.vue";
|
||||
@@ -93,9 +95,11 @@ function getParsedText(data: TpTextType): Array<TpTextType> {
|
||||
|
||||
function getTpName(tp: TGApp.BBS.SctPost.Base): Component {
|
||||
if (tp.children) return TpTexts;
|
||||
if (typeof tp.insert === "undefined") return TpUnknown;
|
||||
if (typeof tp.insert === "string") return TpText;
|
||||
// game_user_info属于backup_text的一种,必须放在backup_text判断的前面
|
||||
// 判断特殊backup_text
|
||||
if ("game_user_info" in tp.insert) return TpUid;
|
||||
if ("ugc_master_tag" in tp.insert) return TpUgcTag;
|
||||
if ("backup_text" in tp.insert) {
|
||||
if (tp.insert.backup_text === "[游戏卡片]" && "reception_card" in tp.insert) return TpGameCard;
|
||||
if (tp.insert.backup_text === "[自定义表情]" && "custom_emoticon" in tp.insert) {
|
||||
@@ -105,6 +109,7 @@ function getTpName(tp: TGApp.BBS.SctPost.Base): Component {
|
||||
}
|
||||
if ("divider" in tp.insert) return TpDivider;
|
||||
if ("image" in tp.insert) return TpImage;
|
||||
if ("level" in tp.insert) return TpUgcLevel;
|
||||
if ("link_card" in tp.insert) return TpLinkCard;
|
||||
if ("mention" in tp.insert) return TpMention;
|
||||
if ("video" in tp.insert) return TpVideo;
|
||||
|
||||
151
src/components/viewPost/tp-ugc-level.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<!-- UGC关卡组件 TODO:UI调整-->
|
||||
<template>
|
||||
<div class="tul-card-box" @click="console.log(props.data)">
|
||||
<TMiImg
|
||||
@click="toLevel()"
|
||||
class="tul-cover"
|
||||
:src="props.data.insert.level.cover.url"
|
||||
alt="cover"
|
||||
/>
|
||||
<div class="tul-content">
|
||||
<div class="tul-top">
|
||||
<div class="tul-title">{{ props.data.insert.level.level_name }}</div>
|
||||
<div class="tul-sub">
|
||||
<div class="tul-info-item" title="游戏类型">
|
||||
<v-icon size="16" color="yellow">mdi-gamepad-variant</v-icon>
|
||||
<span>{{ props.data.insert.level.play_type }}</span>
|
||||
</div>
|
||||
<div class="tul-info-item" title="游玩人数">
|
||||
<v-icon size="16" color="blue">mdi-account-multiple</v-icon>
|
||||
<span>{{ props.data.insert.level.show_limit_play_num_str }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tul-attach" v-if="props.data.insert.level.level_attachment">
|
||||
<span>“</span>
|
||||
<span>{{ props.data.insert.level.level_attachment.content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tul-bottom">
|
||||
<div class="tul-info">
|
||||
<div class="tul-info-item" title="热度">
|
||||
<v-icon size="16" color="orange">mdi-fire</v-icon>
|
||||
<span>{{ props.data.insert.level.hot_score }}</span>
|
||||
</div>
|
||||
<div class="tul-info-item" title="点赞率">
|
||||
<v-icon size="16" color="pink">mdi-thumb-up</v-icon>
|
||||
<span>{{ props.data.insert.level.good_rate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import TGClient from "@utils/TGClient.js";
|
||||
|
||||
type TpUgcLevel = { insert: { level: TGApp.BBS.UGC.Level } };
|
||||
type TpUgcLevelProps = { data: TpUgcLevel };
|
||||
|
||||
const props = defineProps<TpUgcLevelProps>();
|
||||
|
||||
async function toLevel(): Promise<void> {
|
||||
let url = `https://act.miyoushe.com/ys/ugc_community/mx/#/pages/level-detail/index?`;
|
||||
url = `${url}id=${props.data.insert.level.level_id}®ion=${props.data.insert.level.region}`;
|
||||
await TGClient.open("web_act_thin", url.toString());
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tul-card-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
background: var(--box-bg-1);
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.tul-cover {
|
||||
max-width: 50%;
|
||||
max-height: 180px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
scale: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.tul-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.tul-top {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tul-title {
|
||||
color: var(--tgc-od-red);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tul-sub {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
color: var(--tgc-od-white);
|
||||
column-gap: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tul-attach {
|
||||
margin-top: 8px;
|
||||
color: var(--common-text-secondary);
|
||||
font-size: 16px;
|
||||
|
||||
span {
|
||||
color: var(--tgc-od-blue);
|
||||
|
||||
&:first-child {
|
||||
font-family: Genshin, sans-serif;
|
||||
font-size: 20px;
|
||||
line-height: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tul-bottom {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tul-info {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.tul-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
52
src/components/viewPost/tp-ugc-tag.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- 含 ugc_master_tag 的 backup_text -->
|
||||
<template>
|
||||
<span class="tut-box">
|
||||
{{ tagName }}
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import useAppStore from "@store/app.js";
|
||||
import { str2Color } from "@utils/toolFunc.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
|
||||
type TpUgcTag = {
|
||||
insert: {
|
||||
backup_text: string;
|
||||
ugc_master_tag: {
|
||||
id: number;
|
||||
name: string;
|
||||
is_available: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
type TpUgcTagProps = { data: TpUgcTag };
|
||||
|
||||
const props = defineProps<TpUgcTagProps>();
|
||||
const { theme } = storeToRefs(useAppStore());
|
||||
const tagName = computed<string>(() => props.data.insert.ugc_master_tag.name);
|
||||
const isDarkMode = computed<boolean>(() => theme.value === "dark");
|
||||
const tagColor = computed<string>(() => tag2Color(tagName.value, isDarkMode.value));
|
||||
const bgColor = computed<string>(() => `rgba(${tagColor.value.slice(4, -1)}, 0.18)`);
|
||||
|
||||
function tag2Color(str: string, isDarkMode: boolean = false): string {
|
||||
const adjust = isDarkMode ? 80 : -40;
|
||||
return str2Color(str, adjust);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tut-box {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 6px;
|
||||
border-radius: 12px;
|
||||
margin-right: 4px;
|
||||
background: v-bind(bgColor); /* stylelint-disable-line value-keyword-case */
|
||||
color: v-bind(tagColor); /* stylelint-disable-line value-keyword-case */
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -23,6 +23,7 @@
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { getImageBuffer, saveCanvasImg, saveImgLocal } from "@utils/TGShare.js";
|
||||
import { getVideoDuration } from "@utils/toolFunc.js";
|
||||
import Artplayer, { type Option } from "artplayer";
|
||||
@@ -106,7 +107,7 @@ onMounted(async () => {
|
||||
name: "download-cover",
|
||||
index: 0,
|
||||
position: "right",
|
||||
html: `<i class="mdi mdi-download"></i>`,
|
||||
html: `<span class="mdi mdi-image-check"></span>`,
|
||||
tooltip: "下载封面",
|
||||
click: async () => {
|
||||
await showLoading.start("正在下载封面", props.data.insert.vod.cover);
|
||||
@@ -117,6 +118,17 @@ onMounted(async () => {
|
||||
await showLoading.end();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "download-video",
|
||||
index: 0,
|
||||
position: "right",
|
||||
html: `<span class="mdi mdi-video-check"></span>`,
|
||||
tooltip: "下载视频",
|
||||
click: async () => {
|
||||
if (!container.value) return;
|
||||
await openUrl(container.value.url);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
container.value = new Artplayer(option);
|
||||
|
||||
@@ -5,28 +5,46 @@
|
||||
<img :src="localLink" alt="图片" @click="isOriSize = !isOriSize" />
|
||||
</div>
|
||||
<div class="tpoi-bottom">
|
||||
<div class="tpoi-info" v-if="props.image.attributes">
|
||||
<p v-if="props.image.attributes.size" class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(props.image.attributes.size ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>{{ props.image.attributes.width }}x{{ props.image.attributes.height }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<template v-if="typeof props.image.insert.image !== 'string'">
|
||||
<div class="tpoi-info">
|
||||
<p class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(Number(props.image.insert.image.size) ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>
|
||||
{{ props.image.insert.image.width }}x{{ props.image.insert.image.height }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="props.image.attributes">
|
||||
<div class="tpoi-info">
|
||||
<p v-if="props.image.attributes.size" class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(props.image.attributes.size ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>{{ props.image.attributes.width }}x{{ props.image.attributes.height }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tpoi-tools">
|
||||
<v-icon @click="setBlackBg" title="切换背景色" v-if="showOri">
|
||||
mdi-format-color-fill
|
||||
</v-icon>
|
||||
<v-icon @click="showOri = true" title="查看原图" v-else>mdi-magnify</v-icon>
|
||||
<v-icon @click="onCopy" title="复制到剪贴板" v-if="format !== 'gif'">
|
||||
mdi-content-copy
|
||||
</v-icon>
|
||||
<v-icon @click="onCopy" title="复制到剪贴板" v-if="showCopy">mdi-content-copy</v-icon>
|
||||
<v-icon @click="onDownload" title="下载到本地">mdi-download</v-icon>
|
||||
<v-icon @click="visible = false" title="关闭浮窗">mdi-close</v-icon>
|
||||
</div>
|
||||
@@ -51,14 +69,14 @@ const visible = defineModel<boolean>();
|
||||
const localLink = defineModel<string>("link");
|
||||
const showOri = defineModel<boolean>("ori");
|
||||
const bgColor = defineModel<string>("bgColor", { default: "transparent" });
|
||||
const format = defineModel<string>("format", { default: "png" });
|
||||
const bgMode = ref<number>(0); // 0: transparent, 1: black, 2: white
|
||||
const isOriSize = ref<boolean>(false);
|
||||
const buffer = shallowRef<Uint8Array | null>(null);
|
||||
const format = computed<string>(() => {
|
||||
if (props.image.attributes?.ext) return props.image.attributes.ext;
|
||||
const imageFormat = props.image.insert.image.split(".").pop();
|
||||
if (imageFormat !== undefined) return imageFormat;
|
||||
return "png";
|
||||
const oriLink = computed<string>(() => miniImgUrl());
|
||||
const showCopy = computed<boolean>(() => {
|
||||
// 只能显示 png/jpg/jpeg/webp 格式的复制按钮
|
||||
return ["png", "jpg", "jpeg", "webp"].includes(format.value.toLowerCase());
|
||||
});
|
||||
|
||||
function setBlackBg(): void {
|
||||
@@ -80,8 +98,7 @@ async function onCopy(): Promise<void> {
|
||||
await nextTick();
|
||||
}
|
||||
await showLoading.start("正在复制图片到剪贴板");
|
||||
const image = props.image.insert.image;
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(oriLink.value);
|
||||
const size = bytesToSize(buffer.value.byteLength);
|
||||
await copyToClipboard(buffer.value);
|
||||
await showLoading.end();
|
||||
@@ -93,18 +110,28 @@ async function onDownload(): Promise<void> {
|
||||
showOri.value = true;
|
||||
await nextTick();
|
||||
}
|
||||
const image = props.image.insert.image;
|
||||
await showLoading.start("正在下载图片到本地", image);
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
await showLoading.start("正在下载图片到本地", oriLink.value);
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(oriLink.value);
|
||||
if (buffer.value.byteLength > 80000000) {
|
||||
showSnackbar.warn("图片过大,无法下载到本地");
|
||||
return;
|
||||
}
|
||||
let fileName = image.split("/").pop()?.split(".")[0];
|
||||
let fileName = oriLink.value.split("/").pop()?.split(".")[0];
|
||||
if (fileName === undefined) fileName = Date.now().toString();
|
||||
await saveCanvasImg(buffer.value, fileName, format.value);
|
||||
await showLoading.end();
|
||||
}
|
||||
|
||||
function miniImgUrl(): string {
|
||||
let url: string;
|
||||
if (typeof props.image.insert.image === "string") {
|
||||
url = props.image.insert.image;
|
||||
} else {
|
||||
url = props.image.insert.image.url;
|
||||
}
|
||||
const link = new URL(url);
|
||||
return `${link.origin}${link.pathname}`;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tpoi-box {
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"GroupId": 4922,
|
||||
"Id": 492201,
|
||||
"Name": "炎昼风物诗",
|
||||
"Description": "施放<color=#FFD780FF>琉金云间草</color>后的15秒内,附近的队伍中所有其他角色(不包括宵宫自己)攻击力提高10%。此外,依据宵宫自己施放<color=#FFD780FF>琉金云间草</color>时固有天赋「袖火百景图」的叠加层数,将额外提升上述的攻击力效果,每层提升1%攻击力。",
|
||||
"Description": "施放<color=#FFD780FF>琉金云间草</color>后的15秒内,附近的队伍中所有其他角色(不包括宵宫自己)攻击力提高10%。此外,依据宵宫自己施放<color=#FFD780FF>琉金云间草</color>时突破天赋「袖火百景图」的叠加层数,将额外提升上述的攻击力效果,每层提升1%攻击力。",
|
||||
"Icon": "UI_Talent_S_Yoimiya_06"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 672,
|
||||
"Name": "漫行山薮",
|
||||
"Description": "固有天赋「飞叶迴斜」转变为:\n<color=#FFD780FF>飞叶轮</color>返回时,将为角色赋予固有天赋「飞叶迴斜」的「新叶」状态,持续对身边的敌人造成相当于柯莱攻击力40%的<color=#99FF88FF>草元素伤害</color>,持续3秒。\n从施放<color=#FFD780FF>拂花偈叶</color>开始,到此次新叶状态消失前,如果队伍中自己的角色触发了燃烧、原激化、超激化、蔓激化、绽放、超绽放、烈绽放或月绽放反应,则使此次新叶效果的持续时间延长3秒。\n新叶效果至多通过这种方式延长一次持续时间;在持续期间再次产生新叶效果时,将移除原有的效果。新叶效果造成的伤害视为元素战技伤害。\n需要解锁固有天赋「飞叶迴斜」。",
|
||||
"Description": "突破天赋「飞叶迴斜」转变为:\n<color=#FFD780FF>飞叶轮</color>返回时,将为角色赋予突破天赋「飞叶迴斜」的「新叶」状态,持续对身边的敌人造成相当于柯莱攻击力40%的<color=#99FF88FF>草元素伤害</color>,持续3秒。\n从施放<color=#FFD780FF>拂花偈叶</color>开始,到此次新叶状态消失前,如果队伍中自己的角色触发了燃烧、原激化、超激化、蔓激化、绽放、超绽放、烈绽放或月绽放反应,则使此次新叶效果的持续时间延长3秒。\n新叶效果至多通过这种方式延长一次持续时间;在持续期间再次产生新叶效果时,将移除原有的效果。新叶效果造成的伤害视为元素战技伤害。\n需要解锁突破天赋「飞叶迴斜」。",
|
||||
"Icon": "UI_Talent_S_Collei_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 702,
|
||||
"Name": "星天的花雨",
|
||||
"Description": "处于「金杯的丰馈」状态下的角色对敌人造成<color=#80C0FFFF>水元素伤害</color>后,该敌人的<color=#80C0FFFF>水元素抗性</color>降低35%,持续10秒;对敌人造成绽放或月绽放反应伤害后,该敌人的<color=#99FF88FF>草元素抗性</color>降低35%,持续10秒。\n需要解锁固有天赋「折旋落英之庭」。",
|
||||
"Description": "处于「金杯的丰馈」状态下的角色对敌人造成<color=#80C0FFFF>水元素伤害</color>后,该敌人的<color=#80C0FFFF>水元素抗性</color>降低35%,持续10秒;对敌人造成绽放或月绽放反应伤害后,该敌人的<color=#99FF88FF>草元素抗性</color>降低35%,持续10秒。\n需要解锁突破天赋「折旋落英之庭」。",
|
||||
"Icon": "UI_Talent_S_Nilou_02"
|
||||
},
|
||||
{
|
||||
@@ -84,7 +84,7 @@
|
||||
"GroupId": 7021,
|
||||
"Id": 702101,
|
||||
"Name": "折旋落英之庭",
|
||||
"Description": "队伍中所有角色的元素类型均为<color=#99FF88FF>草元素</color>与<color=#80C0FFFF>水元素</color>,并且至少有一名草元素角色、一名水元素角色时:\n妮露完成<color=#FFD780FF>七域舞步</color>的第三段舞步会为附近的所有角色赋予持续30秒的「金杯的丰馈」状态。\n处于金杯的丰馈状态下的角色受到<color=#99FF88FF>草元素攻击</color>会使附近的所有角色元素精通提升100点,持续10秒;此外,触发绽放或月绽放反应时,将取代草原核产生「丰穰之核」。\n相比草原核,丰穰之核在产生后将迅速迸发,并且具有更大的影响范围。\n丰穰之核无法触发超绽放与烈绽放反应,与草原核共享数量上限。丰穰之核的攻击视为绽放反应的草原核的攻击。\n当队伍中角色的元素类型不满足本固有天赋的条件时,将移除已有的金杯的丰馈效果。",
|
||||
"Description": "队伍中所有角色的元素类型均为<color=#99FF88FF>草元素</color>与<color=#80C0FFFF>水元素</color>,并且至少有一名草元素角色、一名水元素角色时:\n妮露完成<color=#FFD780FF>七域舞步</color>的第三段舞步会为附近的所有角色赋予持续30秒的「金杯的丰馈」状态。\n处于金杯的丰馈状态下的角色受到<color=#99FF88FF>草元素攻击</color>会使附近的所有角色元素精通提升100点,持续10秒;此外,触发绽放或月绽放反应时,将取代草原核产生「丰穰之核」。\n相比草原核,丰穰之核在产生后将迅速迸发,并且具有更大的影响范围。\n丰穰之核无法触发超绽放与烈绽放反应,与草原核共享数量上限。丰穰之核的攻击视为绽放反应的草原核的攻击。\n当队伍中角色的元素类型不满足本突破天赋的条件时,将移除已有的金杯的丰馈效果。",
|
||||
"Icon": "UI_Talent_S_Nilou_05"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 711,
|
||||
"Name": "立仪·俯览昼冥",
|
||||
"Description": "施放<color=#FFD780FF>圣仪·煟煌随狼行</color>后,赛诺普通攻击的攻击速度提升20%,持续10秒。若施放<color=#FFD780FF>秘仪·律渊渡魂</color>时触发了固有天赋「落羽的裁择」的「裁定」效果,将刷新该效果的持续时间。\n需要解锁固有天赋「落羽的裁择」。",
|
||||
"Description": "施放<color=#FFD780FF>圣仪·煟煌随狼行</color>后,赛诺普通攻击的攻击速度提升20%,持续10秒。若施放<color=#FFD780FF>秘仪·律渊渡魂</color>时触发了突破天赋「落羽的裁择」的「裁定」效果,将刷新该效果的持续时间。\n需要解锁突破天赋「落羽的裁择」。",
|
||||
"Icon": "UI_Talent_S_Cyno_01"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 716,
|
||||
"Name": "羽仪·裁落钧衡",
|
||||
"Description": "施放<color=#FFD780FF>圣仪·煟煌随狼行</color>或触发固有天赋「落羽的裁择」的「裁定」后,赛诺将获得4层「豺祭」效果。赛诺的普通攻击命中敌人时,将消耗一层「豺祭」效果,并发射一道渡荒之雷。\n「豺祭」持续8秒,至多叠加8层,并将在圣仪·煟煌随狼行的启途誓使状态结束后移除。\n每0.4秒至多通过这种方式发射一道渡荒之雷。\n需要解锁固有天赋「落羽的裁择」。",
|
||||
"Description": "施放<color=#FFD780FF>圣仪·煟煌随狼行</color>或触发突破天赋「落羽的裁择」的「裁定」后,赛诺将获得4层「豺祭」效果。赛诺的普通攻击命中敌人时,将消耗一层「豺祭」效果,并发射一道渡荒之雷。\n「豺祭」持续8秒,至多叠加8层,并将在圣仪·煟煌随狼行的启途誓使状态结束后移除。\n每0.4秒至多通过这种方式发射一道渡荒之雷。\n需要解锁突破天赋「落羽的裁择」。",
|
||||
"Icon": "UI_Talent_S_Cyno_04"
|
||||
}
|
||||
],
|
||||
@@ -91,7 +91,7 @@
|
||||
"GroupId": 7122,
|
||||
"Id": 712201,
|
||||
"Name": "九弓的执命",
|
||||
"Description": "基于赛诺的元素精通,提高自身以下攻击造成的伤害值:\n·启途誓使状态下的普通攻击:元素精通的150%;\n·固有天赋「落羽的裁择」的渡荒之雷:元素精通的250%。",
|
||||
"Description": "基于赛诺的元素精通,提高自身以下攻击造成的伤害值:\n·启途誓使状态下的普通攻击:元素精通的150%;\n·突破天赋「落羽的裁择」的渡荒之雷:元素精通的250%。",
|
||||
"Icon": "UI_Talent_S_Cyno_06"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"camp": "阿如村",
|
||||
"constellation": "箭盾座",
|
||||
"birth": "5月3日",
|
||||
"cv": { "cn": "张琦", "jp": "柚木凉香", "en": "莎蕾·科尔比", "kr": "全暎洙" }
|
||||
"cv": { "cn": "张琦", "jp": "柚木凉香", "en": "莎蕾·科尔比&匿名", "kr": "全暎洙" }
|
||||
},
|
||||
"constellation": [
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 751,
|
||||
"Name": "初番·茂风流羽行",
|
||||
"Description": "在<color=#FFD780FF>优风倾姿</color>状态下,流浪者进行空居·不生断与空居·刀风界的攻击速度提升10%。\n此外,固有天赋「梦迹一风」发射的风矢能额外造成25%攻击力的伤害,该效果需要解锁固有天赋「梦迹一风」。",
|
||||
"Description": "在<color=#FFD780FF>优风倾姿</color>状态下,流浪者进行空居·不生断与空居·刀风界的攻击速度提升10%。\n此外,突破天赋「梦迹一风」发射的风矢能额外造成25%攻击力的伤害,该效果需要解锁突破天赋「梦迹一风」。",
|
||||
"Icon": "UI_Talent_S_Wanderer_01"
|
||||
},
|
||||
{
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
"Id": 754,
|
||||
"Name": "四番·花月歌浮舟",
|
||||
"Description": "施放<color=#FFD780FF>羽画·风姿华歌</color>时,若触发了固有天赋「拾玉得花」依据元素类型强化的效果,还将随机获得1种本次施放未触发的强化效果。同时至多获得3种元素对应的强化效果。\n需要解锁固有天赋「拾玉得花」。",
|
||||
"Description": "施放<color=#FFD780FF>羽画·风姿华歌</color>时,若触发了突破天赋「拾玉得花」依据元素类型强化的效果,还将随机获得1种本次施放未触发的强化效果。同时至多获得3种元素对应的强化效果。\n需要解锁突破天赋「拾玉得花」。",
|
||||
"Icon": "UI_Talent_S_Wanderer_03"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 802,
|
||||
"Name": "伴行的旅路",
|
||||
"Description": "<color=#FFD780FF>星霜的流旋</color>的霜流矢首次命中敌人时,或是冰星信标命中敌人时,将产生1层固有天赋「速射牵制」的侦明效果。\n需要解锁固有天赋「速射牵制」。",
|
||||
"Description": "<color=#FFD780FF>星霜的流旋</color>的霜流矢首次命中敌人时,或是冰星信标命中敌人时,将产生1层突破天赋「速射牵制」的侦明效果。\n需要解锁突破天赋「速射牵制」。",
|
||||
"Icon": "UI_Talent_S_Mika_02"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 806,
|
||||
"Name": "依随的策援",
|
||||
"Description": "<color=#FFD780FF>星霜的流旋</color>灵风状态下的侦明效果叠加层数上限提升1层。需要解锁固有天赋「速射牵制」。\n此外,处于灵风状态下的当前场上角色,其物理伤害的暴击伤害提高60%。",
|
||||
"Description": "<color=#FFD780FF>星霜的流旋</color>灵风状态下的侦明效果叠加层数上限提升1层。需要解锁突破天赋「速射牵制」。\n此外,处于灵风状态下的当前场上角色,其物理伤害的暴击伤害提高60%。",
|
||||
"Icon": "UI_Talent_S_Mika_04"
|
||||
}
|
||||
],
|
||||
@@ -91,7 +91,7 @@
|
||||
"GroupId": 8022,
|
||||
"Id": 802201,
|
||||
"Name": "地貌测绘",
|
||||
"Description": "同时处于<color=#FFD780FF>苍翎的颂愿</color>的鹰翎状态与<color=#FFD780FF>星霜的流旋</color>的灵风状态下的当前场上角色,在攻击造成暴击时,灵风状态将产生1层固有天赋「速射牵制」的侦明效果。在一次灵风状态的持续期间内,通过这种方式至多产生1层侦明效果。\n此外,灵风状态下侦明效果叠加层数上限提升1层。\n需要解锁固有天赋「速射牵制」。",
|
||||
"Description": "同时处于<color=#FFD780FF>苍翎的颂愿</color>的鹰翎状态与<color=#FFD780FF>星霜的流旋</color>的灵风状态下的当前场上角色,在攻击造成暴击时,灵风状态将产生1层突破天赋「速射牵制」的侦明效果。在一次灵风状态的持续期间内,通过这种方式至多产生1层侦明效果。\n此外,灵风状态下侦明效果叠加层数上限提升1层。\n需要解锁突破天赋「速射牵制」。",
|
||||
"Icon": "UI_Talent_S_Mika_06"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
{
|
||||
"Id": 861,
|
||||
"Name": "予行恶者以惩惧",
|
||||
"Description": "固有天赋「公理终有辩诉之日」的「恩典之诫」转变为:\n莱欧斯利的生命值低于60%,或在<color=#FFD780FF>冰牙突驰</color>的寒烈的惩裁状态持续期间,斥逐拳的第五段攻击命中时,将获得「恩典之诫」,每2.5秒至多获得一次「恩典之诫」效果。\n\n此外,惩戒·凌跃拳获得以下强化:\n·造成的伤害提升进一步提升至200%;\n·命中时,若处于寒烈的惩裁状态下,该状态的持续时间延长4秒,在一次寒烈的惩裁状态持续期间,至多通过这种方式延长一次。\n\n需要解锁固有天赋「公理终有辩诉之日」。",
|
||||
"Description": "突破天赋「公理终有辩诉之日」的「恩典之诫」转变为:\n莱欧斯利的生命值低于60%,或在<color=#FFD780FF>冰牙突驰</color>的寒烈的惩裁状态持续期间,斥逐拳的第五段攻击命中时,将获得「恩典之诫」,每2.5秒至多获得一次「恩典之诫」效果。\n\n此外,惩戒·凌跃拳获得以下强化:\n·造成的伤害提升进一步提升至200%;\n·命中时,若处于寒烈的惩裁状态下,该状态的持续时间延长4秒,在一次寒烈的惩裁状态持续期间,至多通过这种方式延长一次。\n\n需要解锁突破天赋「公理终有辩诉之日」。",
|
||||
"Icon": "UI_Talent_S_Wriothesley_01"
|
||||
},
|
||||
{
|
||||
"Id": 862,
|
||||
"Name": "予骄暴者以镣锁",
|
||||
"Description": "施放<color=#FFD780FF>黑金狼噬</color>时,固有天赋「罪业终有报偿之时」的每层「检偿之敕」效果都将使造成的伤害提升40%。\n需要解锁固有天赋「罪业终有报偿之时」。",
|
||||
"Description": "施放<color=#FFD780FF>黑金狼噬</color>时,突破天赋「罪业终有报偿之时」的每层「检偿之敕」效果都将使造成的伤害提升40%。\n需要解锁突破天赋「罪业终有报偿之时」。",
|
||||
"Icon": "UI_Talent_S_Wriothesley_02"
|
||||
},
|
||||
{
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
"Id": 864,
|
||||
"Name": "予贞苦者以拯赎",
|
||||
"Description": "惩戒·凌跃拳为莱欧斯利恢复生命值的回复量提升为50%生命值上限,需要解锁固有天赋「公理终有辩诉之日」。\n此外,莱欧斯利受到治疗时,若此次治疗回复量溢出,将根据莱欧斯利是否处于当前场上产生不同的效果:处于当前场上时,莱欧斯利的攻击速度提升20%,持续4秒;处于队伍后台时,队伍中所有自己的角色攻击速度提升10%,持续6秒。上述这两种攻击速度提升效果无法叠加。",
|
||||
"Description": "惩戒·凌跃拳为莱欧斯利恢复生命值的回复量提升为50%生命值上限,需要解锁突破天赋「公理终有辩诉之日」。\n此外,莱欧斯利受到治疗时,若此次治疗回复量溢出,将根据莱欧斯利是否处于当前场上产生不同的效果:处于当前场上时,莱欧斯利的攻击速度提升20%,持续4秒;处于队伍后台时,队伍中所有自己的角色攻击速度提升10%,持续6秒。上述这两种攻击速度提升效果无法叠加。",
|
||||
"Icon": "UI_Talent_S_Wriothesley_03"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 866,
|
||||
"Name": "予无罪者以念抚",
|
||||
"Description": "惩戒·凌跃拳的暴击率提升10%,暴击伤害提升80%,并能够额外生成冰锥,造成原本100%的<color=#99FFFFFF>冰元素伤害</color>,通过这种方式造成的伤害视为重击伤害。\n需要解锁固有天赋「公理终有辩诉之日」。",
|
||||
"Description": "惩戒·凌跃拳的暴击率提升10%,暴击伤害提升80%,并能够额外生成冰锥,造成原本100%的<color=#99FFFFFF>冰元素伤害</color>,通过这种方式造成的伤害视为重击伤害。\n需要解锁突破天赋「公理终有辩诉之日」。",
|
||||
"Icon": "UI_Talent_S_Wriothesley_04"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
{
|
||||
"Id": 871,
|
||||
"Name": "尊荣的创定",
|
||||
"Description": "那维莱特登场时,获得一层固有天赋「古海孑遗的权柄」的「遗龙之荣」。需要解锁固有天赋「古海孑遗的权柄」。\n此外,进行重击蓄力·诉论心证与重击·衡平推裁时,提高那维莱特的抗打断能力。",
|
||||
"Description": "那维莱特登场时,获得一层突破天赋「古海孑遗的权柄」的「遗龙之荣」。需要解锁突破天赋「古海孑遗的权柄」。\n此外,进行重击蓄力·诉论心证与重击·衡平推裁时,提高那维莱特的抗打断能力。",
|
||||
"Icon": "UI_Talent_S_Neuvillette_01"
|
||||
},
|
||||
{
|
||||
"Id": 872,
|
||||
"Name": "律法的命诫",
|
||||
"Description": "固有天赋「古海孑遗的权柄」获得强化:每存在一层「遗龙之荣」,就使重击·衡平推裁的暴击伤害提升14%,至多通过这种方式提升42%。\n需要解锁固有天赋「古海孑遗的权柄」。",
|
||||
"Description": "突破天赋「古海孑遗的权柄」获得强化:每存在一层「遗龙之荣」,就使重击·衡平推裁的暴击伤害提升14%,至多通过这种方式提升42%。\n需要解锁突破天赋「古海孑遗的权柄」。",
|
||||
"Icon": "UI_Talent_S_Neuvillette_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 901,
|
||||
"Name": "稳固阵线的魄力",
|
||||
"Description": "当前场上处于「协同战法」状态下的角色(不包括夏沃蕾自己)触发超载反应时,将恢复6点元素能量。此效果每10秒至多触发一次。\n需要解锁固有天赋「尖兵协同战法」。",
|
||||
"Description": "当前场上处于「协同战法」状态下的角色(不包括夏沃蕾自己)触发超载反应时,将恢复6点元素能量。此效果每10秒至多触发一次。\n需要解锁突破天赋「尖兵协同战法」。",
|
||||
"Icon": "UI_Talent_S_Chevreuse_01"
|
||||
},
|
||||
{
|
||||
@@ -84,7 +84,7 @@
|
||||
"GroupId": 9021,
|
||||
"Id": 902101,
|
||||
"Name": "尖兵协同战法",
|
||||
"Description": "队伍中所有角色的元素类型均为<color=#FF9999FF>火元素</color>与<color=#FFACFFFF>雷元素</color>,并且至少有一名火元素角色、一名雷元素角色时:\n夏沃蕾将为队伍中附近的角色施加「协同战法」:角色触发超载反应后,受本次反应影响的敌人的<color=#FF9999FF>火元素</color>与<color=#FFACFFFF>雷元素</color>抗性降低40%,持续6秒。\n当队伍中角色的元素类型不满足本固有天赋的条件时,将移除已有的协同战法效果。",
|
||||
"Description": "队伍中所有角色的元素类型均为<color=#FF9999FF>火元素</color>与<color=#FFACFFFF>雷元素</color>,并且至少有一名火元素角色、一名雷元素角色时:\n夏沃蕾将为队伍中附近的角色施加「协同战法」:角色触发超载反应后,受本次反应影响的敌人的<color=#FF9999FF>火元素</color>与<color=#FFACFFFF>雷元素</color>抗性降低40%,持续6秒。\n当队伍中角色的元素类型不满足本突破天赋的条件时,将移除已有的协同战法效果。",
|
||||
"Icon": "UI_Talent_S_Chevreuse_05"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 932,
|
||||
"Name": "鹤唳远人间",
|
||||
"Description": "施放<color=#FFD780FF>朝起鹤云</color>的步天梯后,闲云的攻击力提升20%,持续15秒。\n此外,固有天赋「细想应是洞中仙」的效果获得提升:<color=#FFD780FF>暮集竹星</color>的竹星拥有仙力助推时,附近的当前场上角色的下落攻击坠地冲击造成的伤害提升,提升值相当于闲云的攻击力的400%。通过这种方式,至多使附近的当前场上角色的下落攻击坠地冲击伤害提升18000。\n一次下落攻击坠地冲击伤害中,只会对一名敌人造成上述伤害提升效果,每个角色每0.4秒至多触发一次。\n该效果需要解锁固有天赋「细想应是洞中仙」。",
|
||||
"Description": "施放<color=#FFD780FF>朝起鹤云</color>的步天梯后,闲云的攻击力提升20%,持续15秒。\n此外,突破天赋「细想应是洞中仙」的效果获得提升:<color=#FFD780FF>暮集竹星</color>的竹星拥有仙力助推时,附近的当前场上角色的下落攻击坠地冲击造成的伤害提升,提升值相当于闲云的攻击力的400%。通过这种方式,至多使附近的当前场上角色的下落攻击坠地冲击伤害提升18000。\n一次下落攻击坠地冲击伤害中,只会对一名敌人造成上述伤害提升效果,每个角色每0.4秒至多触发一次。\n该效果需要解锁突破天赋「细想应是洞中仙」。",
|
||||
"Icon": "UI_Talent_S_Liuyun_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 941,
|
||||
"Name": "正绢六通",
|
||||
"Description": "<color=#FFD780FF>羽袖一触</color>的自动制御人形「袖」的攻击范围提升50%。\n此外,如果队伍中存在千织以外的<color=#FFE699FF>岩元素</color>角色,羽袖一触的突进结束时,还将触发以下效果:\n·额外唤出一名袖。通过这种方式唤出的千织自己的袖,与通过岩元素创造物额外唤出的千织自己的袖至多同时存在一名;\n·触发固有天赋「锦上添花」,此效果需要解锁固有天赋「锦上添花」。",
|
||||
"Description": "<color=#FFD780FF>羽袖一触</color>的自动制御人形「袖」的攻击范围提升50%。\n此外,如果队伍中存在千织以外的<color=#FFE699FF>岩元素</color>角色,羽袖一触的突进结束时,还将触发以下效果:\n·额外唤出一名袖。通过这种方式唤出的千织自己的袖,与通过岩元素创造物额外唤出的千织自己的袖至多同时存在一名;\n·触发、突破天赋「锦上添花」,此效果需要解锁突破天赋「锦上添花」。",
|
||||
"Icon": "UI_Talent_S_Chiori_01"
|
||||
},
|
||||
{
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
"Id": 944,
|
||||
"Name": "衣裁三礼",
|
||||
"Description": "触发固有天赋「量体裁衣」的后续效果后的8秒内,队伍中自己的当前场上角色的普通攻击、重击或下落攻击命中附近的敌人时,将在敌人附近唤出一名简易自动制御人形「绢」。每1秒至多通过上述方式唤出一名绢,一次固有天赋「量体裁衣」的当意即妙或裁锦持续期间,至多通过上述方式唤出3名绢。上述效果每15秒最多触发一次。 \n需要解锁固有天赋「量体裁衣」。",
|
||||
"Description": "触发突破天赋「量体裁衣」的后续效果后的8秒内,队伍中自己的当前场上角色的普通攻击、重击或下落攻击命中附近的敌人时,将在敌人附近唤出一名简易自动制御人形「绢」。每1秒至多通过上述方式唤出一名绢,一次突破天赋「量体裁衣」的当意即妙或裁锦持续期间,至多通过上述方式唤出3名绢。上述效果每15秒最多触发一次。 \n需要解锁突破天赋「量体裁衣」。",
|
||||
"Icon": "UI_Talent_S_Chiori_02"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 946,
|
||||
"Name": "万理一空",
|
||||
"Description": "触发固有天赋「量体裁衣」的后续效果后,千织自己的<color=#FFD780FF>羽袖一触</color>的冷却时间减少12秒。需要解锁固有天赋「量体裁衣」。\n此外,千织自己的普通攻击造成的伤害提升,提升值相当于千织自己的防御力的235%。",
|
||||
"Description": "触发突破天赋「量体裁衣」的后续效果后,千织自己的<color=#FFD780FF>羽袖一触</color>的冷却时间减少12秒。需要解锁突破天赋「量体裁衣」。\n此外,千织自己的普通攻击造成的伤害提升,提升值相当于千织自己的防御力的235%。",
|
||||
"Icon": "UI_Talent_S_Chiori_04"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 951,
|
||||
"Name": "「最快乐的精灵,可否懂得焦虑」",
|
||||
"Description": "<color=#FFD780FF>弹跳水疗法</color>的激愈水球能额外弹跳3次;前3次弹跳不会使较大激愈水球变小。\n此外,固有天赋「应有适当的休憩」的效果获得提升:激愈水球弹跳时,将为希格雯赋予1层静养计数,并使静养计数产生的伤害值提升改为:希格雯的生命值上限超过30000的部分,每1000点生命值上限将提升100点,通过这种方式,至多提升3500点伤害。此效果需要解锁固有天赋「应有适当的休憩」。",
|
||||
"Description": "<color=#FFD780FF>弹跳水疗法</color>的激愈水球能额外弹跳3次;前3次弹跳不会使较大激愈水球变小。\n此外,突破天赋「应有适当的休憩」的效果获得提升:激愈水球弹跳时,将为希格雯赋予1层静养计数,并使静养计数产生的伤害值提升改为:希格雯的生命值上限超过30000的部分,每1000点生命值上限将提升100点,通过这种方式,至多提升3500点伤害。此效果需要解锁突破天赋「应有适当的休憩」。",
|
||||
"Icon": "UI_Talent_S_Sigewinne_01"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 962,
|
||||
"Name": "「所有的赏与罚皆自我出…」",
|
||||
"Description": "<color=#FFD780FF>血偿勒令</color>施加时即为血偿勒令·结。\n阿蕾奇诺回收血偿勒令·结时,将在前方唤出厄月血火,造成相当于她的攻击力900%的<color=#FF9999FF>火元素范围伤害</color>,并使她的所有元素抗性与物理抗性提升20%,持续15秒。此效果每10秒至多触发一次。\n需要解锁固有天赋「唯苦痛可偿还」。",
|
||||
"Description": "<color=#FFD780FF>血偿勒令</color>施加时即为血偿勒令·结。\n阿蕾奇诺回收血偿勒令·结时,将在前方唤出厄月血火,造成相当于她的攻击力900%的<color=#FF9999FF>火元素范围伤害</color>,并使她的所有元素抗性与物理抗性提升20%,持续15秒。此效果每10秒至多触发一次。\n需要解锁突破天赋「唯苦痛可偿还」。",
|
||||
"Icon": "UI_Talent_S_Arlecchino_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 972,
|
||||
"Name": "寂秘纸草经",
|
||||
"Description": "满足以下任意一项条件时,赛索斯的<color=#FFACFFFF>雷元素伤害加成</color>提升15%,持续10秒,至多叠加2层,每层独立计时:\n·通过瞄准射击消耗元素能量,此条件需要解锁固有天赋「黑鸢的密喻」;\n·通过<color=#FFD780FF>古仪·鸣砂掣雷</color>触发元素反应恢复元素能量;\n·施放<color=#FFD780FF>秘仪·瞑光贯影</color>。",
|
||||
"Description": "满足以下任意一项条件时,赛索斯的<color=#FFACFFFF>雷元素伤害加成</color>提升15%,持续10秒,至多叠加2层,每层独立计时:\n·通过瞄准射击消耗元素能量,此条件需要解锁突破天赋「黑鸢的密喻」;\n·通过<color=#FFD780FF>古仪·鸣砂掣雷</color>触发元素反应恢复元素能量;\n·施放<color=#FFD780FF>秘仪·瞑光贯影</color>。",
|
||||
"Icon": "UI_Talent_S_Sethos_02"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 976,
|
||||
"Name": "巡日塔门书",
|
||||
"Description": "<color=#FFD780FF>贯影箭</color>命中敌人后,将返还因固有天赋「黑鸢的密喻」消耗的元素能量,此效果每15秒至多触发一次,需要解锁固有天赋「黑鸢的密喻」。",
|
||||
"Description": "<color=#FFD780FF>贯影箭</color>命中敌人后,将返还因突破天赋「黑鸢的密喻」消耗的元素能量,此效果每15秒至多触发一次,需要解锁突破天赋「黑鸢的密喻」。",
|
||||
"Icon": "UI_Talent_S_Sethos_04"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 982,
|
||||
"Name": "「自此,直面长夜之危」",
|
||||
"Description": "固有天赋「破夜的明焰」的效果获得强化:队伍中附近的角色对敌人触发<color=#FFACFFFF>雷元素相关反应</color>后,将基于克洛琳德攻击力的30%,提升克洛琳德的普通攻击与<color=#FFD780FF>残光将终</color>造成的<color=#FFACFFFF>雷元素伤害</color>。此效果持续15秒,至多叠加3层,每层独立计时。处于3层状态下时,克洛琳德的抗打断能力提升。通过这种方式至多使克洛琳德的上述攻击造成的伤害提升2700。\n需要解锁固有天赋「破夜的明焰」。",
|
||||
"Description": "突破天赋「破夜的明焰」的效果获得强化:队伍中附近的角色对敌人触发<color=#FFACFFFF>雷元素相关反应</color>后,将基于克洛琳德攻击力的30%,提升克洛琳德的普通攻击与<color=#FFD780FF>残光将终</color>造成的<color=#FFACFFFF>雷元素伤害</color>。此效果持续15秒,至多叠加3层,每层独立计时。处于3层状态下时,克洛琳德的抗打断能力提升。通过这种方式至多使克洛琳德的上述攻击造成的伤害提升2700。\n需要解锁突破天赋「破夜的明焰」。",
|
||||
"Icon": "UI_Talent_S_Clorinde_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
{
|
||||
"Id": 991,
|
||||
"Name": "淡香浸析",
|
||||
"Description": "<color=#FFD780FF>撷萃调香</color>与固有天赋「余薰」的清露香氛造成的伤害提升20%,后者需要解锁固有天赋「余薰」。\n此外,队伍中附近的角色对敌人触发燃烧反应或对处于燃烧状态下的敌人造成<color=#99FF88FF>草元素伤害</color>时,会额外生成一枚<color=#FFD780FF>香韵</color>。此效果每2.9秒至多触发一次。",
|
||||
"Description": "<color=#FFD780FF>撷萃调香</color>与突破天赋「余薰」的清露香氛造成的伤害提升20%,后者需要解锁突破天赋「余薰」。\n此外,队伍中附近的角色对敌人触发燃烧反应或对处于燃烧状态下的敌人造成<color=#99FF88FF>草元素伤害</color>时,会额外生成一枚<color=#FFD780FF>香韵</color>。此效果每2.9秒至多触发一次。",
|
||||
"Icon": "UI_Talent_S_Emilie_01"
|
||||
},
|
||||
{
|
||||
"Id": 992,
|
||||
"Name": "湖光顶调",
|
||||
"Description": "<color=#FFD780FF>撷萃调香</color>、<color=#FFD780FF>香氛演绎</color>或固有天赋「余薰」的清露香氛(需解锁该固有天赋)命中敌人时,该敌人的<color=#99FF88FF>草元素抗性</color>降低30%,持续10秒。",
|
||||
"Description": "<color=#FFD780FF>撷萃调香</color>、<color=#FFD780FF>香氛演绎</color>或突破天赋「余薰」的清露香氛(需解锁该突破天赋)命中敌人时,该敌人的<color=#99FF88FF>草元素抗性</color>降低30%,持续10秒。",
|
||||
"Icon": "UI_Talent_S_Emilie_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
{
|
||||
"Id": 1016,
|
||||
"Name": "瑞兽之形",
|
||||
"Description": "迴猎贯鳞炮在命中敌人后,将在敌人间弹跳一次,造成基尼奇的攻击力700%的<color=#99FF88FF>草元素伤害</color>。\n如果本次迴猎贯鳞炮触发了固有天赋「焰灵的契约」或命之座「星虎之掌」对迴猎贯鳞炮的增强效果,它所引发的弹跳攻击也会获得上述效果的增强。",
|
||||
"Description": "迴猎贯鳞炮在命中敌人后,将在敌人间弹跳一次,造成基尼奇的攻击力700%的<color=#99FF88FF>草元素伤害</color>。\n如果本次迴猎贯鳞炮触发了突破天赋「焰灵的契约」或命之座「星虎之掌」对迴猎贯鳞炮的增强效果,它所引发的弹跳攻击也会获得上述效果的增强。",
|
||||
"Icon": "UI_Talent_S_Kinich_04"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 1022,
|
||||
"Name": "全力以赴玛拉妮!",
|
||||
"Description": "进入夜魂加持状态时,玛拉妮获得2层<color=#FFD780FF>踏鲨破浪</color>的浪势充能;玛拉妮获得豚豚球时,获得1层浪势充能。\n此外,玛拉妮在一次夜魂加持期间获得2个豚豚球的2秒内,额外恢复12点夜魂值。该效果需要解锁固有天赋「耐热型淡水豚豚」。",
|
||||
"Description": "进入夜魂加持状态时,玛拉妮获得2层<color=#FFD780FF>踏鲨破浪</color>的浪势充能;玛拉妮获得豚豚球时,获得1层浪势充能。\n此外,玛拉妮在一次夜魂加持期间获得2个豚豚球的2秒内,额外恢复12点夜魂值。该效果需要解锁突破天赋「耐热型淡水豚豚」。",
|
||||
"Icon": "UI_Talent_S_Mualani_02"
|
||||
},
|
||||
{
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
"Id": 1024,
|
||||
"Name": "鲨鲨主食是豚豚。",
|
||||
"Description": "获得豚豚球时,玛拉妮恢复8点元素能量;该效果需要解锁固有天赋「耐热型淡水豚豚」。\n此外,<color=#FFD780FF>爆瀑飞弹</color>造成的伤害提升75%。",
|
||||
"Description": "获得豚豚球时,玛拉妮恢复8点元素能量;该效果需要解锁突破天赋「耐热型淡水豚豚」。\n此外,<color=#FFD780FF>爆瀑飞弹</color>造成的伤害提升75%。",
|
||||
"Icon": "UI_Talent_S_Mualani_03"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"GroupId": 10332,
|
||||
"Id": 11032,
|
||||
"Name": "音火锻淬",
|
||||
"Description": "让热情的律动响彻大地!希诺宁切换至用于高速战斗的刃轮装束,并向前突进一段距离,基于希诺宁的防御力,造成具有夜魂性质的<color=#FFE699FF>岩元素伤害</color>。\n施放后,希诺宁将获得45点夜魂值,进入夜魂加持状态,在这种状态下,希诺宁将切换至「刃轮巡猎」模式。\n\n<color=#FFD780FF>源音采样</color>\n希诺宁拥有三枚采样器,根据队伍中其他角色的元素类型,能够呈现出不同的「音型」,并在激活时降低附近的敌人的对应元素抗性;\n三枚采样器初始记录的「源音采样」均为<color=#FFE699FF>岩元素</color>,队伍中每存在一名<color=#FF9999FF>火元素</color>、<color=#80C0FFFF>水元素</color>、<color=#99FFFFFF>冰元素</color>或<color=#FFACFFFF>雷元素</color>角色,一枚<color=#FFE699FF>岩元素</color>采样便将转变为对应的元素类型;\n处于夜魂加持状态下时,希诺宁携带的<color=#FFE699FF>岩元素</color>「源音采样」将始终保持激活状态;解锁固有天赋「四境四象回声」后,希诺宁可以通过触发固有天赋「四境四象回声」的效果为自己恢复夜魂值;在夜魂值达到上限时,希诺宁将会消耗全部夜魂值,激活希诺宁自己携带的三枚「源音采样」,持续15秒;\n「源音采样」激活时,附近的敌人的对应元素抗性降低,同元素类型「源音采样」的效果不能叠加;希诺宁处于队伍后台时,同样能产生该效果。\n\n<color=#FFD780FF>夜魂加持·希诺宁</color>\n持续消耗夜魂值。夜魂值耗尽时,或是再次施放时,希诺宁的夜魂加持将会结束。夜魂加持状态具有如下特性:\n·切换至「刃轮巡猎」模式,提升希诺宁的移动速度与攀爬速度,且攀爬时能够以豹猫形态进行高速腾跃;\n·希诺宁的夜魂加持状态具有如下限制:处于夜魂加持状态下时,希诺宁的夜魂值有9秒的时间限制,超过时间限制后,希诺宁的夜魂值将立刻耗竭。\n\n处于夜魂加持状态下时,夜魂值耗竭后,希诺宁将无法通过固有天赋「四境四象回声」产生夜魂值。\n\n<i>从某种意义上来说,锻造宝石与混音的原理或许有一定相通之处:滤除杂质,只保留最为闪耀之物。\n不过希诺宁向来是无所谓的,毕竟两者对她而言都是无比快乐的事情嘛。</i>",
|
||||
"Description": "让热情的律动响彻大地!希诺宁切换至用于高速战斗的刃轮装束,并向前突进一段距离,基于希诺宁的防御力,造成具有夜魂性质的<color=#FFE699FF>岩元素伤害</color>。\n施放后,希诺宁将获得45点夜魂值,进入夜魂加持状态,在这种状态下,希诺宁将切换至「刃轮巡猎」模式。\n\n<color=#FFD780FF>源音采样</color>\n希诺宁拥有三枚采样器,根据队伍中其他角色的元素类型,能够呈现出不同的「音型」,并在激活时降低附近的敌人的对应元素抗性;\n三枚采样器初始记录的「源音采样」均为<color=#FFE699FF>岩元素</color>,队伍中每存在一名<color=#FF9999FF>火元素</color>、<color=#80C0FFFF>水元素</color>、<color=#99FFFFFF>冰元素</color>或<color=#FFACFFFF>雷元素</color>角色,一枚<color=#FFE699FF>岩元素</color>采样便将转变为对应的元素类型;\n处于夜魂加持状态下时,希诺宁携带的<color=#FFE699FF>岩元素</color>「源音采样」将始终保持激活状态;解锁突破天赋「四境四象回声」后,希诺宁可以通过触发突破天赋「四境四象回声」的效果为自己恢复夜魂值;在夜魂值达到上限时,希诺宁将会消耗全部夜魂值,激活希诺宁自己携带的三枚「源音采样」,持续15秒;\n「源音采样」激活时,附近的敌人的对应元素抗性降低,同元素类型「源音采样」的效果不能叠加;希诺宁处于队伍后台时,同样能产生该效果。\n\n<color=#FFD780FF>夜魂加持·希诺宁</color>\n持续消耗夜魂值。夜魂值耗尽时,或是再次施放时,希诺宁的夜魂加持将会结束。夜魂加持状态具有如下特性:\n·切换至「刃轮巡猎」模式,提升希诺宁的移动速度与攀爬速度,且攀爬时能够以豹猫形态进行高速腾跃;\n·希诺宁的夜魂加持状态具有如下限制:处于夜魂加持状态下时,希诺宁的夜魂值有9秒的时间限制,超过时间限制后,希诺宁的夜魂值将立刻耗竭。\n\n处于夜魂加持状态下时,夜魂值耗竭后,希诺宁将无法通过突破天赋「四境四象回声」产生夜魂值。\n\n<i>从某种意义上来说,锻造宝石与混音的原理或许有一定相通之处:滤除杂质,只保留最为闪耀之物。\n不过希诺宁向来是无所谓的,毕竟两者对她而言都是无比快乐的事情嘛。</i>",
|
||||
"Icon": "Skill_S_Xilonen_01"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
{
|
||||
"Id": 1041,
|
||||
"Name": "弹匣,躁动的轮盘",
|
||||
"Description": "触发固有天赋「子弹的戏法」中的「附灵转化」时,会额外使第二枚装入弹匣的<color=#FFD780FF>追影弹</color>转化为<color=#FFD780FF>焕光追影弹</color>。\n触发「附灵转化」的几率增加:若队伍中存在符合元素转化条件的元素类型的角色,则触发「附灵转化」的几率提升33.3%,至多提升至100%。\n\n需要解锁固有天赋「子弹的戏法」。\n此外,不处于战斗状态下时,恰斯卡的夜魂加持状态消耗的夜魂值与燃素降低30%。",
|
||||
"Description": "触发突破天赋「子弹的戏法」中的「附灵转化」时,会额外使第二枚装入弹匣的<color=#FFD780FF>追影弹</color>转化为<color=#FFD780FF>焕光追影弹</color>。\n触发「附灵转化」的几率增加:若队伍中存在符合元素转化条件的元素类型的角色,则触发「附灵转化」的几率提升33.3%,至多提升至100%。\n\n需要解锁突破天赋「子弹的戏法」。\n此外,不处于战斗状态下时,恰斯卡的夜魂加持状态消耗的夜魂值与燃素降低30%。",
|
||||
"Icon": "UI_Talent_S_Chasca_01"
|
||||
},
|
||||
{
|
||||
"Id": 1042,
|
||||
"Name": "枪口,灼烧的轻烟",
|
||||
"Description": "恰斯卡登场时,获得一层固有天赋「子弹的戏法」的「焕影之灵」。该效果需要解锁固有天赋「子弹的戏法」。\n此外,施放元素战技<color=#FFD780FF>灵缰追影</color>中的<color=#FFD780FF>多重瞄准</color>时,恰斯卡的<color=#FFD780FF>焕光追影弹</color>命中敌人时,会根据<color=#FFD780FF>焕光追影弹</color>的元素类型,造成相当于恰斯卡攻击力400%的对应元素范围伤害,该伤害视为重击伤害。每次施放<color=#FFD780FF>多重瞄准</color>至多触发一次该效果。",
|
||||
"Description": "恰斯卡登场时,获得一层突破天赋「子弹的戏法」的「焕影之灵」。该效果需要解锁突破天赋「子弹的戏法」。\n此外,施放元素战技<color=#FFD780FF>灵缰追影</color>中的<color=#FFD780FF>多重瞄准</color>时,恰斯卡的<color=#FFD780FF>焕光追影弹</color>命中敌人时,会根据<color=#FFD780FF>焕光追影弹</color>的元素类型,造成相当于恰斯卡攻击力400%的对应元素范围伤害,该伤害视为重击伤害。每次施放<color=#FFD780FF>多重瞄准</color>至多触发一次该效果。",
|
||||
"Icon": "UI_Talent_S_Chasca_02"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 1046,
|
||||
"Name": "相决,斗争的荣光",
|
||||
"Description": "恰斯卡进行<color=#FFD780FF>多重瞄准</color>所需的蓄力时间减少,且触发固有天赋「子弹的戏法」中的「附灵转化」后,恰斯卡将获得「命袭」状态,在接下来的3秒内,恰斯卡下一次施放元素战技<color=#FFD780FF>灵缰追影</color>中的<color=#FFD780FF>多重瞄准</color>时,会立即完成蓄力,并且本次<color=#FFD780FF>多重瞄准</color>中的<color=#FFD780FF>追影弹</color>和<color=#FFD780FF>焕光追影弹</color>的暴击伤害提升120%。每3秒至多获得一次「命袭」效果。\n\n上述效果需要解锁固有天赋「子弹的戏法」。",
|
||||
"Description": "恰斯卡进行<color=#FFD780FF>多重瞄准</color>所需的蓄力时间减少,且触发突破天赋「子弹的戏法」中的「附灵转化」后,恰斯卡将获得「命袭」状态,在接下来的3秒内,恰斯卡下一次施放元素战技<color=#FFD780FF>灵缰追影</color>中的<color=#FFD780FF>多重瞄准</color>时,会立即完成蓄力,并且本次<color=#FFD780FF>多重瞄准</color>中的<color=#FFD780FF>追影弹</color>和<color=#FFD780FF>焕光追影弹</color>的暴击伤害提升120%。每3秒至多获得一次「命袭」效果。\n\n上述效果需要解锁突破天赋「子弹的戏法」。",
|
||||
"Icon": "UI_Talent_S_Chasca_04"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 1051,
|
||||
"Name": "林雾间的行迹",
|
||||
"Description": "元素战技<color=#FFD780FF>暝色缒索</color>中的<color=#FFD780FF>宿灵球</color>能额外弹跳2次。\n此外,<color=#FFD780FF>宿灵球</color>命中敌人后,将为该敌人施加「夜暝」效果,持续12秒;对处于「夜暝」效果的影响下的敌人,固有天赋「夜翳的通感」触发的「显象超感」造成的伤害提升50%,该效果需要解锁固有天赋「夜翳的通感」。",
|
||||
"Description": "元素战技<color=#FFD780FF>暝色缒索</color>中的<color=#FFD780FF>宿灵球</color>能额外弹跳2次。\n此外,<color=#FFD780FF>宿灵球</color>命中敌人后,将为该敌人施加「夜暝」效果,持续12秒;对处于「夜暝」效果的影响下的敌人,突破天赋「夜翳的通感」触发的「显象超感」造成的伤害提升50%,该效果需要解锁突破天赋「夜翳的通感」。",
|
||||
"Icon": "UI_Talent_S_Olorun_01"
|
||||
},
|
||||
{
|
||||
@@ -42,7 +42,7 @@
|
||||
{
|
||||
"Id": 1056,
|
||||
"Name": "致深泉的颂赞",
|
||||
"Description": "触发固有天赋「夜翳的通感」的「显象超感」后,会使队伍中自己的当前场上角色的攻击力提升10%,持续9秒。该效果至多叠加3层,每层独立计算持续时间。\n此外,施放元素爆发<color=#FFD780FF>黯声回响</color>时,将触发一次等同于「显象超感」的效果,造成原本200%的伤害。\n该效果需要解锁固有天赋「夜翳的通感」。",
|
||||
"Description": "触发突破天赋「夜翳的通感」的「显象超感」后,会使队伍中自己的当前场上角色的攻击力提升10%,持续9秒。该效果至多叠加3层,每层独立计算持续时间。\n此外,施放元素爆发<color=#FFD780FF>黯声回响</color>时,将触发一次等同于「显象超感」的效果,造成原本200%的伤害。\n该效果需要解锁突破天赋「夜翳的通感」。",
|
||||
"Icon": "UI_Talent_S_Olorun_04"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
"Id": 1064,
|
||||
"Name": "「领袖」的觉悟",
|
||||
"Description": "固有天赋「基扬戈兹」的效果获得提升:\n施放元素爆发<color=#FFD780FF>燔天之时</color>后的伤害提升效果不再随时间降低,并额外获得10%伤害加成。\n需要解锁固有天赋「基扬戈兹」。",
|
||||
"Description": "突破天赋「基扬戈兹」的效果获得提升:\n施放元素爆发<color=#FFD780FF>燔天之时</color>后的伤害提升效果不再随时间降低,并额外获得10%伤害加成。\n需要解锁突破天赋「基扬戈兹」。",
|
||||
"Icon": "UI_Talent_S_Mavuika_03"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"Id": 1072,
|
||||
"Name": "吞心者的巡行",
|
||||
"Description": "茜特菈莉的元素精通提升125点,处于白曜护盾的庇护下或是伊兹帕帕跟随的其他角色的元素精通提升250点。\n此外,施放元素战技<color=#FFD780FF>霜昼黑星</color>时,也会为附近的当前场上角色赋予白曜护盾;\n\n此外,固有天赋「五重天的寒雨」的效果获得提升:\n伊兹帕帕存在期间,队伍中附近的角色触发冻结反应或融化反应后,受本次反应影响的敌人的<color=#FF9999FF>火元素</color>与<color=#80C0FFFF>水元素</color>抗性还会额外降低20%,持续12秒,该效果需要解锁固有天赋「五重天的寒雨」。",
|
||||
"Description": "茜特菈莉的元素精通提升125点,处于白曜护盾的庇护下或是伊兹帕帕跟随的其他角色的元素精通提升250点。\n此外,施放元素战技<color=#FFD780FF>霜昼黑星</color>时,也会为附近的当前场上角色赋予白曜护盾;\n\n此外,突破天赋「五重天的寒雨」的效果获得提升:\n伊兹帕帕存在期间,队伍中附近的角色触发冻结反应或融化反应后,受本次反应影响的敌人的<color=#FF9999FF>火元素</color>与<color=#80C0FFFF>水元素</color>抗性还会额外降低20%,持续12秒,该效果需要解锁突破天赋「五重天的寒雨」。",
|
||||
"Icon": "UI_Talent_S_Citlali_02"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"Id": 1081,
|
||||
"Name": "「若有人兮云之际」",
|
||||
"Description": "触发固有天赋「四戈封刀灵卜」中的元素转化后,蓝砚在这次元素战技<color=#FFD780FF>凤缕随翦舞</color>中向敌人抛出翦月环时,将额外抛出一枚翦月环。\n需要解锁固有天赋「四戈封刀灵卜」。",
|
||||
"Description": "触发突破天赋「四戈封刀灵卜」中的元素转化后,蓝砚在这次元素战技<color=#FFD780FF>凤缕随翦舞</color>中向敌人抛出翦月环时,将额外抛出一枚翦月环。\n需要解锁突破天赋「四戈封刀灵卜」。",
|
||||
"Icon": "UI_Talent_S_Lanyan_01"
|
||||
},
|
||||
{
|
||||
|
||||