Compare commits

...

83 Commits

Author SHA1 Message Date
BTMuli
2ab31d8f5c 🚀 v0.8.6 2025-11-19 14:09:48 +08:00
BTMuli
1af990512d 📝 更新README 2025-11-19 13:55:53 +08:00
BTMuli
d96d451156 👽️ 调整读取格式 2025-11-19 13:50:47 +08:00
BTMuli
f029306ebb 🚸 添加跳转视频链接 2025-11-19 13:40:25 +08:00
BTMuli
d3c5baa0c2 📝 更新资源说明文档 2025-11-19 13:22:01 +08:00
BTMuli
ba0802752c 🔊 完善log 2025-11-19 00:51:49 +08:00
BTMuli
ff94e12ff5 🚸 调整默认文本 2025-11-19 00:07:13 +08:00
BTMuli
0fbf1f7c2a 🚸 添加AIGC相关注释 2025-11-18 23:01:51 +08:00
BTMuli
68809a93c6 支撑导入剧诗数据 2025-11-18 22:51:32 +08:00
BTMuli
0edcadef63 👽️ 移除剧诗概览,支撑导入剧诗数据 2025-11-18 22:42:46 +08:00
BTMuli
9f9c30914f 🔥 移除胡桃深渊统计页面 2025-11-18 22:29:24 +08:00
BTMuli
04cf372798 🎨 路由重定向 2025-11-18 22:27:27 +08:00
BTMuli
6617a26c90 👽️ 移除深渊上传,支撑导入胡桃深渊数据 2025-11-18 22:20:58 +08:00
BTMuli
d244423800 🚸 调整导入浮窗ui,显示导入进度 2025-11-18 22:02:07 +08:00
BTMuli
3366efaadd 🐛 处理拓展解析异常 2025-11-15 20:36:50 +08:00
BTMuli
d74e7a7a31 🥅 处理异常,清除缓存后重启 2025-11-15 14:54:06 +08:00
BTMuli
2d0b409813 🐛 修复图片渲染异常 2025-11-15 14:37:05 +08:00
BTMuli
942068faea 🚀 v0.8.5 2025-11-10 16:34:14 +08:00
BTMuli
0f0f7684d2 🍱 更新下半数据 2025-11-10 16:31:01 +08:00
BTMuli
531cb32f72 🚀 v0.8.4 2025-10-27 19:48:35 +08:00
BTMuli
a368223805 千星奇域页面 2025-10-27 19:42:02 +08:00
BTMuli
6eab6c81f1 🍱 增加千星奇域元数据 2025-10-27 17:26:33 +08:00
BTMuli
68594a2a76 🐛 剔除多余换行 2025-10-27 12:33:09 +08:00
BTMuli
5d5f22d76e 🚸 添加prefix 2025-10-25 23:05:13 +08:00
BTMuli
65e948c34c 添加getRegionRoleInfo事件处理 2025-10-25 21:04:04 +08:00
BTMuli
68dead3d84 🔥 考虑合并祈愿,不单独分页 2025-10-25 19:46:03 +08:00
BTMuli
babc6a9a75 嵌入祈愿详情 2025-10-25 19:45:17 +08:00
BTMuli
6db4ff5ac9 👽️ 移除非必需参数 2025-10-25 18:59:26 +08:00
BTMuli
ce1b6f365e 🏷️ 调整类型注释 2025-10-25 14:07:46 +08:00
BTMuli
b6ed9668ac 🚸 完善类型,添加交互 2025-10-25 13:00:10 +08:00
BTMuli
2a2a190f5f 🐛 修复部分帖子渲染异常 2025-10-25 12:31:03 +08:00
BTMuli
5d03a32362 💄 调整名片样式 2025-10-24 23:35:12 +08:00
BTMuli
33d9ba5c4d 重构帖子解析逻辑,增加新类型解析
*PostID:69886846,69915487
2025-10-24 22:21:03 +08:00
BTMuli
9020214d23 🐛 修复部分帖子解析异常 2025-10-24 20:13:51 +08:00
BTMuli
78c3f79bfd 🧑‍💻 JSON内容复制 2025-10-24 19:58:31 +08:00
BTMuli
ee0fc6dbae 完善投稿活动类型声明,渲染投稿活动&交互
*PostID:69823686
2025-10-24 19:27:49 +08:00
BTMuli
8c51b79558 🐛 修复浮窗显示异常 2025-10-24 18:05:50 +08:00
BTMuli
8c1899637f 🌱 暂时将千星奇域移到祈愿子tab 2025-10-24 16:21:30 +08:00
BTMuli
56df920a7d 嵌入官方公告页面(已登录) 2025-10-24 16:15:05 +08:00
BTMuli
64c6f4ab8f 🚸 兑换码浮窗显示游戏名称 2025-10-24 12:20:15 +08:00
BTMuli
d3902d6e31 🌱 千星奇域抽卡记录页面 2025-10-23 23:56:23 +08:00
BTMuli
01e355b0d6 千星奇域抽卡记录获取 2025-10-23 23:09:58 +08:00
BTMuli
c40b3c6ff0 👽️ 公告添加千星奇域分类 2025-10-23 22:40:30 +08:00
BTMuli
4305967ba9 🚀 v0.8.3 2025-10-22 13:59:10 +08:00
BTMuli
78f454bee5 🍱 更新卡池数据 2025-10-22 13:50:08 +08:00
BTMuli
e9a38e1474 👽️ 奇偶不lock好感卡片 2025-10-22 13:13:22 +08:00
BTMuli
9fb2aa6112 🍱 更新6.1资源 2025-10-22 12:34:43 +08:00
BTMuli
a0554e4355 🚸 首页活动组件(用户)分享图生成 2025-10-17 18:13:34 +08:00
BTMuli
f890165894 💄 微调月谕模式ui 2025-10-17 18:06:38 +08:00
BTMuli
bc22612da7 💄 调整布局 2025-10-15 11:09:42 +08:00
BTMuli
a9ec93b18d 🐛 修复JS脚本执行异常 2025-10-15 00:14:01 +08:00
BTMuli
651cbef0a0 ♻️ 提取剧诗Icon 2025-10-08 12:05:51 +08:00
BTMuli
41a144fec2 🚸 优化图片调整浮窗样式 2025-10-08 11:49:47 +08:00
BTMuli
3f219ebb82 ♻️ 重构gt返回逻辑 2025-10-08 10:36:17 +08:00
BTMuli
43c85afd1e 📝 移除oss认证 2025-10-08 10:08:54 +08:00
BTMuli
48771f57a0 🚸 降低验证触发概率 2025-10-04 12:09:17 +08:00
BTMuli
6e3884df58 💄 添加圣牌图标 2025-10-02 12:26:52 +08:00
BTMuli
7a6a06bb25 🐛 修正链接判断逻辑 2025-10-02 12:18:25 +08:00
BTMuli
eac3691d0b 🐛 修复切换角色导致ck对应异常 2025-10-01 11:10:12 +08:00
BTMuli
3ece987c80 👽️ 适配月谕圣牌模式 2025-10-01 10:37:46 +08:00
BTMuli
145438373b 🐛 修复下载链接异常 2025-09-28 23:23:40 +08:00
BTMuli
b62b0b4902 🐛 重构数据解析,修复HEIC格式图片渲染异常 2025-09-28 23:13:55 +08:00
BTMuli
ed878dea9e 💄 微调样式 2025-09-27 21:40:23 +08:00
BTMuli
76e9d23f23 🚀 v0.8.2 2025-09-27 12:52:22 +08:00
BTMuli
4cbd8af250 💄 调整成就样式 2025-09-26 12:16:26 +08:00
BTMuli
467b38feec 🚸 搜索时隐藏已有浮窗 2025-09-26 12:15:18 +08:00
BTMuli
636556d4ce 🍱 更新元数据 2025-09-26 12:14:23 +08:00
BTMuli
01b89444ec 💄 调整位置 2025-09-26 10:33:26 +08:00
BTMuli
29536f9181 👽️ 适配新版块 2025-09-25 13:00:22 +08:00
BTMuli
86dfc134dc 💄 微调样式 2025-09-24 19:54:58 +08:00
BTMuli
4f8f269787 🐛 修复特定情况下切换角色浮窗异常 2025-09-24 11:38:52 +08:00
BTMuli
7e0912ef22 🍱 更新元数据 2025-09-22 01:29:48 +08:00
BTMuli
6a5b65134e 动态处理游戏卡片组件 2025-09-19 15:01:57 +08:00
BTMuli
b40db32697 💄 调整UI 2025-09-18 23:38:50 +08:00
BTMuli
59c1fc9621 💄 优化滚动截屏处理,调整抽奖浮窗UI 2025-09-18 23:19:59 +08:00
BTMuli
f98b1913f7 💄 微调 2025-09-18 18:39:48 +08:00
BTMuli
264d36490c 💄 对齐圆角 2025-09-18 18:34:55 +08:00
BTMuli
f3dd8287cf 💄 调整帖子详情页面样式 2025-09-18 18:30:27 +08:00
BTMuli
843ee92670 💄 调整公告卡片样式 2025-09-18 16:25:51 +08:00
BTMuli
de412a1fd6 💄 调整帖子卡片样式 2025-09-18 16:16:15 +08:00
BTMuli
f0555d69bb 🚸 处理话题desc溢出 2025-09-17 15:24:45 +08:00
BTMuli
8b0b5cde28 🚸 隐藏刷新后的loading关闭 2025-09-15 17:50:16 +08:00
BTMuli
ee92af0f73 卡池轮播 2025-09-14 22:52:08 +08:00
269 changed files with 19740 additions and 12541 deletions

View File

@@ -3,6 +3,11 @@ on:
push:
tags:
- v*
workflow_dispatch:
inputs:
tag:
description: "Tag to release"
required: false
jobs:
publish-tauri:
@@ -18,7 +23,7 @@ jobs:
- platform: macos-latest
args: "--target x86_64-apple-darwin"
target: "macos-intel"
- platform: macos-latest
- platform: macos-15-intel
args: "--target aarch64-apple-darwin"
target: "macos-arm"
runs-on: ${{ matrix.settings.platform }}
@@ -27,19 +32,16 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add SSH known hosts
run: ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Add Github RSA
run: |
echo "${{ secrets.KNOWN_GITHUB_RSA }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Test SSH connection
run: ssh -T git@github.com || true
- name: Add Rust targets(macOS Intel)
if: matrix.settings.target == 'macos-intel'
run: rustup target add x86_64-apple-darwin
- name: Add Rust targets(macOS ARM)
if: matrix.settings.target == 'macos-arm'
run: rustup target add aarch64-apple-darwin
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
@@ -48,14 +50,23 @@ jobs:
with:
workspaces: "./src-tauri -> target"
- name: Add Rust targets(macOS Intel)
if: matrix.settings.target == 'macos-intel'
run: rustup target add x86_64-apple-darwin
- name: Add Rust targets(macOS ARM)
if: matrix.settings.target == 'macos-arm'
run: rustup target add aarch64-apple-darwin
- name: Output toolchain
run: rustup show
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 24.2.0
node-version: 24.8.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.15.1
version: 10.16.1
- name: Install frontend dependencies
run: pnpm install

View File

@@ -1,8 +1,9 @@
name: Qodana
on:
push:
branches:
- master
workflow_dispatch:
# push:
# branches:
# - master
jobs:
qodana:
@@ -12,11 +13,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 23.3.0
node-version: 24.8.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.10.0
version: 10.16.1
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: "Qodana Scan"

View File

@@ -1,78 +0,0 @@
name: Build Test
on:
workflow_dispatch:
inputs:
version:
description: "Version to build"
required: true
default: "0.7.6"
jobs:
build-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
settings:
- platform: windows-latest
args: ""
target: "windows"
bundlePath: msi/
- platform: macos-latest
args: "x86_64-apple-darwin"
target: "macos-intel"
bundlePath: macos/TeyvatGuide.app
- platform: macos-latest
args: "aarch64-apple-darwin"
target: "macos-arm"
bundlePath: macos/TeyvatGuide.app
runs-on: ${{ matrix.settings.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add SSH known hosts
run: ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
- name: Test SSH connection
run: ssh -T git@github.com || true
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
- name: Add Rust targets(macOS Intel)
if: matrix.settings.target == 'macos-intel'
run: rustup target add x86_64-apple-darwin
- name: Add Rust targets(macOS ARM)
if: matrix.settings.target == 'macos-arm'
run: rustup target add aarch64-apple-darwin
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 23.3.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.10.0
- name: Install frontend dependencies
run: pnpm install
- name: Update version
run: pnpm upv ${{ github.event.inputs.version }}
- name: Build app
run: pnpm build --target ${{ matrix.settings.args }}
if: matrix.settings.args != ''
- name: Build app (no target)
run: pnpm build
if: matrix.settings.args == ''
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: TeyvatGuide_v${{ github.event.inputs.version }}_${{ matrix.settings.target }}
path: src-tauri/target/${{ matrix.settings.args }}/release/bundle/${{ matrix.settings.bundlePath }}
if-no-files-found: error

View File

@@ -2,12 +2,70 @@
Author: 目棃
Description: CHANGELOG
Date: 2025-09-09
Update: 2025-09-11
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-11 13:38:00`
> 更新于 `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)
- 🍱 更新元数据
- 🐛 修复特定情况下切换角色浮窗异常
- ✨ 动态处理游戏卡片组件
- 👽️ 适配新版块
- 💄 首页卡池组件改成轮播
- 💄 调整帖子卡片样式
- 💄 调整公告卡片样式
- 💄 调整帖子详情页面样式
- 💄 优化滚动截屏处理调整抽奖浮窗UI
- 💄 调整成就项浮窗样式
- 🚸 隐藏危战刷新后的loading关闭
- 🚸 处理话题desc溢出
- 🚸 搜索成就时隐藏已有浮窗
## [0.8.1](https://github.com/BTMuli/TeyvatGuide/releases/v0.8.1) (2025-09-11)

View File

@@ -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`
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/BTMuli/TeyvatGuide) ![](https://img.shields.io/github/last-commit/BTMuli/TeyvatGuide) ![](https://img.shields.io/github/commits-since/BTMuli/TeyvatGuide/latest?include_prereleases)
@@ -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 提供的开源许可证。
![JetBrains logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png)
[![Star History Chart](https://api.star-history.com/svg?repos=BTMuli/TeyvatGuide&type=Timeline)](https://star-history.com/#BTMuli/TeyvatGuide&Timeline)

View File

@@ -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):成就数据仓库,成就数据的详细信息来源于此。
## 字体

View File

@@ -1,9 +1,9 @@
{
"name": "teyvatguide",
"version": "0.8.1",
"version": "0.8.6",
"description": "Game Tool for GenshinImpact player",
"private": true,
"packageManager": "pnpm@10.15.1",
"packageManager": "pnpm@10.22.0",
"type": "module",
"scripts": {
"build": "tauri build",
@@ -71,83 +71,82 @@
},
"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.0",
"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",
"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.9.7",
"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.3.1",
"@types/uuid": "^10.0.0",
"@typescript-eslint/parser": "^8.43.0",
"@typescript/native-preview": "7.0.0-dev.20250908.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.3.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.14.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": "^15.0.1",
"stylelint-config-standard-scss": "^16.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.8.0",
"stylelint-high-performance-animation": "^1.11.0",
"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.1",
"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.6",
"vue-tsc": "^3.1.4",
"yaml-eslint-parser": "^1.3.0"
}
}

3439
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

1227
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "TeyvatGuide"
version = "0.8.1"
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.219", features = ["derive"] }
serde_json = "1.0.143"
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"

View File

@@ -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) {

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2",
"productName": "TeyvatGuide",
"identifier": "TeyvatGuide",
"version": "0.8.1",
"version": "0.8.6",
"build": {
"beforeDevCommand": "pnpm vite:dev",
"beforeBuildCommand": "pnpm vite:build",

View File

@@ -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>

View File

@@ -1,7 +1,12 @@
<template>
<div class="tpw-box" data-html2canvas-ignore>
<div class="tpw-btn" @click="switchPin()" :title="isPined ? '取消置顶' : '窗口置顶'">
<v-icon :color="isPined ? 'yellow' : 'inherit'">
<div
class="tpw-box"
:class="isPined ? 'active' : ''"
:title="isPined ? '取消置顶' : '窗口置顶'"
data-html2canvas-ignore
>
<div class="tpw-btn" @click="switchPin()">
<v-icon size="20">
{{ isPined ? "mdi-pin-off" : "mdi-pin" }}
</v-icon>
</div>
@@ -22,27 +27,56 @@ async function switchPin(): Promise<void> {
showSnackbar.success(isPined.value ? "已将窗口置顶!" : "已经取消窗口置顶!");
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;
.tpw-box {
@include github-styles.github-card;
position: fixed;
top: 70px;
left: 20px;
border: 2px solid var(--common-shadow-8);
top: 64px;
left: 16px;
display: flex;
width: 36px;
height: 36px;
box-sizing: border-box;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
:hover {
opacity: 0.8;
&.active {
background: var(--tgc-btn-1);
box-shadow: 1px 3px 6px var(--common-shadow-2);
color: var(--btn-text);
}
&:hover:not(.active) {
background: var(--common-shadow-1);
}
}
.dark .tpw-box {
border: 1px solid var(--common-shadow-1);
box-shadow: 1px 3px 6px var(--common-shadow-t-2);
&:not(.active) {
@include github-styles.github-card("dark");
&:hover {
background: var(--common-shadow-6);
}
}
}
.tpw-btn {
position: relative;
z-index: 1;
display: flex;
width: 24px;
height: 24px;
width: 20px;
height: 20px;
align-items: center;
justify-content: center;
margin: 5px;
rotate: 30deg;
}
</style>

View File

@@ -39,13 +39,15 @@
<div class="tpc-bottom">
<div class="tpc-tags">
<div v-for="(reason, idx) in card.reasons" :key="idx" class="tpc-reason" title="推荐理由">
<v-icon size="12">mdi-lightbulb-on</v-icon>
<v-icon size="10">mdi-lightbulb-on</v-icon>
<span>{{ reason.text }}</span>
</div>
<div v-for="topic in card.topics" :key="topic.id" class="tpc-tag" @click="toTopic(topic)">
<v-icon size="12">mdi-tag</v-icon>
<span>{{ topic.name }}</span>
</div>
<TpcTag
v-for="topic in card.topics"
:key="topic.id"
@click="toTopic(topic)"
:tag="topic.name"
/>
</div>
<div class="tpc-data" v-if="card.data !== null">
<div class="tpc-info-item" :title="`浏览数:${card.data.view}`">
@@ -86,6 +88,9 @@
</div>
<div
class="tpc-forum"
:style="{
background: str2Color(`${card.forum.id}${card.forum.name}`, -60),
}"
v-if="card.forum !== null && card.forum.name !== ''"
:title="`频道: ${card.forum.name}`"
@click="toForum(card.forum)"
@@ -100,17 +105,29 @@
@click.stop="trySelect()"
data-html2canvas-ignore
/>
<div class="tpc-info-id" v-else>{{ props.modelValue.post.post_id }}</div>
<div
class="tpc-info-id"
:style="{
background: str2Color(`${props.modelValue.post.post_id}`, 0),
}"
v-else
>
<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>
<script lang="ts" setup>
import TMiImg from "@comp/app/t-mi-img.vue";
import showSnackbar from "@comp/func/snackbar.js";
import TpAvatar from "@comp/viewPost/tp-avatar.vue";
import TpcTag from "@comp/viewPost/tpc-tag.vue";
import { emit } from "@tauri-apps/api/event";
import { generateShareImg } from "@utils/TGShare.js";
import { createPost } from "@utils/TGWindow.js";
import { timestampToDate } from "@utils/toolFunc.js";
import { str2Color, timestampToDate } from "@utils/toolFunc.js";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
@@ -140,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)" },
@@ -326,14 +344,18 @@ function onUserClick(): void {
aspect-ratio: 69 / 32;
background: var(--common-shadow-2);
cursor: pointer;
}
.tpc-cover img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
&:hover {
transform: scale(1.2);
}
}
}
.tpc-mid {
@@ -375,31 +397,17 @@ function onUserClick(): void {
gap: 4px;
}
.tpc-tag {
@include github-styles.github-tag-dark-gen(#e06c63);
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 4px;
cursor: pointer;
gap: 4px;
&:hover {
@include github-styles.github-tag-dark-gen(#00aeec);
}
}
.tpc-reason {
@include github-styles.github-tag-dark-gen(#d19a66);
display: flex;
box-sizing: border-box;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 4px;
gap: 4px;
padding: 0 6px;
border-radius: 12px;
gap: 2px;
user-select: none;
}
.tpc-forum {
@@ -410,14 +418,19 @@ function onUserClick(): void {
align-items: center;
justify-content: flex-start;
padding: 4px;
background: var(--tgc-od-white);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border-bottom-left-radius: 4px;
border-top-right-radius: 4px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
cursor: pointer;
opacity: 0.8;
text-shadow: 0 0 4px var(--tgc-dark-1);
img {
width: 20px;
height: 20px;
margin-right: 4px;
}
}
.tpc-select {
@@ -437,17 +450,6 @@ function onUserClick(): void {
color: var(--box-text-5);
}
.tpc-forum img {
width: 20px;
height: 20px;
margin-right: 5px;
}
.tpc-cover img:hover {
transform: scale(1.1);
transition: all 0.3s linear;
}
.tpc-data {
display: flex;
width: fit-content;
@@ -491,14 +493,15 @@ function onUserClick(): void {
display: flex;
align-items: center;
justify-content: center;
padding: 0 8px;
padding-right: 4px;
padding-left: 8px;
background: var(--tgc-od-blue);
border-top-left-radius: 12px;
box-shadow: -2px -2px 8px var(--tgc-dark-1);
box-shadow: -1px -1px 6px var(--tgc-dark-1);
color: var(--tgc-white-1);
column-gap: 2px;
font-size: 12px;
opacity: 0.8;
line-height: 18px;
}
.tpc-status {
@@ -547,15 +550,13 @@ function onUserClick(): void {
align-items: center;
justify-content: center;
padding: 0 4px;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: var(--tgc-od-orange);
border-bottom-right-radius: 4px;
border-top-left-radius: 4px;
box-shadow: 2px 2px 4px var(--tgc-dark-1);
box-shadow: 1px 1px 6px var(--tgc-dark-1);
color: var(--tgc-white-1);
font-size: 12px;
opacity: 0.8;
text-shadow: 0 0 4px var(--tgc-dark-1);
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="share-box" title="分享">
<div class="share-btn" @click="shareContent()">
<v-icon>mdi-share-variant</v-icon>
<v-icon size="20">mdi-share-variant</v-icon>
</div>
</div>
</template>
@@ -36,27 +36,38 @@ async function shareContent(): Promise<void> {
await TGLogger.Info("[TShareBtn][shareContent] 生成分享图片完成");
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;
.share-box {
position: fixed;
top: 20px;
right: 20px;
border: 2px solid var(--common-shadow-8);
top: 16px;
right: 16px;
display: flex;
width: 36px;
height: 36px;
box-sizing: border-box;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--tgc-btn-1);
box-shadow: 1px 3px 6px var(--common-shadow-2);
color: var(--btn-text);
cursor: pointer;
}
.share-box:hover {
opacity: 0.8;
.dark .share-box {
border: 1px solid var(--common-shadow-1);
box-shadow: 1px 3px 6px var(--common-shadow-t-2);
}
.share-btn {
position: relative;
z-index: 1;
display: flex;
width: 24px;
height: 24px;
width: 20px;
height: 20px;
align-items: center;
justify-content: center;
padding-right: 3px;
margin: 5px;
}
</style>

View File

@@ -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" />

View File

@@ -1,8 +1,8 @@
<template>
<div class="switch-box" :title="theme === 'default' ? '切换到深色模式' : '切换到浅色模式'">
<div class="switch-box" :title="isDefault ? '切换到深色模式' : '切换到浅色模式'">
<div class="switch-btn" @click="switchTheme()">
<v-icon>
{{ theme === "default" ? "mdi-weather-night" : "mdi-weather-sunny" }}
<v-icon size="20">
{{ isDefault ? "mdi-weather-night" : "mdi-weather-sunny" }}
</v-icon>
</div>
</div>
@@ -12,10 +12,12 @@ import useAppStore from "@store/app.js";
import { event } from "@tauri-apps/api";
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
import { storeToRefs } from "pinia";
import { onMounted, onUnmounted } from "vue";
import { computed, onMounted, onUnmounted } from "vue";
const { theme } = storeToRefs(useAppStore());
const appStore = useAppStore();
const { theme } = storeToRefs(appStore);
const isDefault = computed<boolean>(() => theme.value === "default");
let themeListener: UnlistenFn | null = null;
onMounted(async () => {
@@ -36,26 +38,38 @@ onUnmounted(() => {
}
});
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;
.switch-box {
position: fixed;
top: 20px;
left: 20px;
border: 2px solid var(--common-shadow-8);
top: 16px;
left: 16px;
display: flex;
width: 36px;
height: 36px;
box-sizing: border-box;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--tgc-btn-1);
box-shadow: 1px 3px 6px var(--common-shadow-2);
color: var(--btn-text);
cursor: pointer;
}
.switch-box:hover {
opacity: 0.8;
.dark .switch-box {
border: 1px solid var(--common-shadow-1);
box-shadow: 1px 3px 6px var(--common-shadow-t-2);
}
.switch-btn {
position: relative;
z-index: 1;
display: flex;
width: 24px;
height: 24px;
width: 20px;
height: 20px;
align-items: center;
justify-content: center;
margin: 5px;
}
</style>

View File

@@ -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);

View File

@@ -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>

View File

@@ -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));
},
);
});

View File

@@ -72,7 +72,7 @@ async function displayBox(params: LoadingParams): Promise<void> {
defineExpose({ displayBox });
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
.func-loading-outer-enter-active,
.func-loading-outer-leave-active,
.func-loading-inner-enter-active {
@@ -156,7 +156,6 @@ defineExpose({ displayBox });
column-gap: 5px;
font-family: var(--font-title);
font-size: 2rem;
font-weight: 600;
}
.loading-subtitle {

View File

@@ -13,11 +13,17 @@
<div :title="model.title" class="anno-title" @click="shareAnno">
{{ parseTitle(model.subtitle) }}
</div>
<div :title="`标签:${model.tagLabel}`" class="anno-label">
<div
:title="`标签:${model.tagLabel}`"
class="anno-label"
:style="{ background: str2Color(`${model.tagIcon}${model.tagLabel}`, 40) }"
>
<TMiImg :src="model.tagIcon" alt="tag" :ori="true" />
<span>{{ model.tagLabel }}</span>
</div>
<div class="anno-id">{{ model.id }}</div>
<div class="anno-id" :style="{ background: str2Color(`${model.id}`, 0) }">
ID:{{ model.id }}
</div>
</div>
</template>
<script lang="ts" setup>
@@ -28,7 +34,7 @@ import useAppStore from "@store/app.js";
import TGLogger from "@utils/TGLogger.js";
import { generateShareImg } from "@utils/TGShare.js";
import { createTGWindow } from "@utils/TGWindow.js";
import { decodeRegExp } from "@utils/toolFunc.js";
import { decodeRegExp, str2Color } from "@utils/toolFunc.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, watch } from "vue";
@@ -157,13 +163,18 @@ function getAnnoTime(content: string): string | false {
justify-content: center;
aspect-ratio: 36 / 13;
cursor: pointer;
}
.anno-cover img {
width: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
img {
width: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
&:hover {
transform: scale(1.1);
transition: all 0.3s linear;
}
}
}
.anno-title {
@@ -178,6 +189,7 @@ function getAnnoTime(content: string): string | false {
font-size: 18px;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
.anno-info {
@@ -211,23 +223,16 @@ function getAnnoTime(content: string): string | false {
align-items: center;
justify-content: flex-start;
padding: 4px;
background-color: var(--tgc-od-white);
border-bottom-left-radius: 6px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
opacity: 0.8;
text-shadow: 0 0 4px var(--tgc-dark-1);
}
.anno-label img {
width: 20px;
height: 20px;
margin-right: 4px;
}
.anno-cover img:hover {
transform: scale(1.1);
transition: all 0.3s linear;
img {
width: 20px;
height: 20px;
margin-right: 4px;
}
}
.anno-id {
@@ -239,12 +244,10 @@ function getAnnoTime(content: string): string | false {
justify-content: center;
padding: 0 4px;
background: var(--tgc-od-orange);
border-bottom-right-radius: 6px;
border-top-left-radius: 6px;
border-bottom-right-radius: 4px;
box-shadow: 0 0 8px var(--tgc-dark-1);
color: var(--tgc-white-1);
font-size: 12px;
opacity: 0.8;
text-shadow: 0 0 4px var(--tgc-dark-1);
}
</style>

View 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>

View File

@@ -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);

View File

@@ -65,6 +65,11 @@ const selects: Array<ToGameLoginSelect> = [
// value: 9,
// icon: "/platforms/mhy/hna.webp",
// },
// {
// title: "星布谷地",
// value: 10,
// icon: "/platforms/mhy/hyg.webp",
// },
];
// eslint-disable-next-line no-undef

View 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>

View File

@@ -2,18 +2,39 @@
<THomeCard :append="false">
<template #title>限时祈愿</template>
<template #default>
<!-- TODO: 当数量超过2时改为走轮播显示2个 -->
<div class="pool-grid">
<div class="pool-grid" v-if="pools.length < 3">
<PhPoolCard v-for="(pool, idx) in pools" :key="idx" :pool="pool" />
</div>
<!-- TODO: 优化Swiper效果 -->
<Swiper
v-else
:slides-per-view="2"
:space-between="12"
:loop="true"
:centered-slides="true"
:navigation="true"
:autoplay="{ delay: 3000, disableOnInteraction: false }"
:modules="swiperModules"
class="pool-swiper"
>
<SwiperSlide v-for="(pool, idx) in pools" :key="idx">
<PhPoolCard :pool="pool" />
</SwiperSlide>
</Swiper>
</template>
</THomeCard>
</template>
<script lang="ts" setup>
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import showSnackbar from "@comp/func/snackbar.js";
import PhPoolCard from "@comp/pageHome/ph-pool-card.vue";
import takumiReq from "@req/takumiReq.js";
import TGLogger from "@utils/TGLogger.js";
import { Autoplay, A11y } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import { onMounted, shallowRef } from "vue";
import THomeCard from "./ph-comp-card.vue";
@@ -22,13 +43,16 @@ type TPoolEmits = (e: "success") => void;
const emits = defineEmits<TPoolEmits>();
const pools = shallowRef<Array<TGApp.BBS.Obc.GachaItem>>([]);
const swiperModules = [Autoplay, A11y];
onMounted(async () => {
const resp = await takumiReq.obc.gacha();
if (Array.isArray(resp)) pools.value = resp;
else {
showSnackbar.error(`获取限时祈愿失败:[${resp.retcode}-${resp.message}`);
await TGLogger.Error(`获取限时祈愿失败:[${resp.retcode}-${resp.message}`);
if (Array.isArray(resp)) {
if (resp.length < 3) pools.value = resp;
else pools.value = [...resp, ...resp];
} else {
showSnackbar.error(`获取限时祈愿失败:[${resp.retcode}]${resp.message}`);
await TGLogger.Error(`获取限时祈愿失败:[${resp.retcode}]${resp.message}`);
}
emits("success");
});

View File

@@ -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;

View File

@@ -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>

View File

@@ -84,6 +84,7 @@ async function searchAchi(): Promise<void> {
nameCard.value = undefined;
ncData.value = undefined;
achievements.value = await TSUserAchi.searchAchi(props.uid, props.search);
if (showOverlay.value) showOverlay.value = false;
if (achievements.value.length > 0) {
showSnackbar.success(`成功获取${achievements.value.length}条成就`);
emits("update:series", undefined);

View File

@@ -23,15 +23,14 @@
<div class="tua-ao-mid-title">
<span>原石奖励</span>
<span>{{ props.data.reward }}</span>
<img src="/icon/material/201.webp" alt="原石" />
</div>
<div class="tua-ao-mid-title">
<span>触发方式</span>
<span>{{
props.data.trigger.task ? "完成以下所有任务" : props.data.trigger.type
}}</span>
<span>{{ parseTriggerType() }}</span>
</div>
<div class="tua-ao-mid-item" v-for="item in props.data.trigger.task" :key="item.questId">
<v-icon>mdi-alert-decagram</v-icon>
<v-icon size="16">mdi-alert-decagram</v-icon>
<span class="tua-ao-click" @click="searchDirect(item.name)">{{ item.name }}</span>
<span>{{ item.type }}</span>
</div>
@@ -41,11 +40,11 @@
<span>是否完成</span>
<span>{{ props.data.isCompleted ? "是" : "否" }}</span>
</div>
<div class="tua-ao-bottom-title">
<div class="tua-ao-bottom-title" v-if="props.data.isCompleted">
<span>完成时间</span>
<span>{{ props.data.completedTime }}</span>
</div>
<div class="tua-ao-bottom-title">
<div class="tua-ao-bottom-title" v-if="props.data.progress > 0">
<span>当前进度</span>
<span>{{ props.data.progress }}</span>
</div>
@@ -93,6 +92,19 @@ async function share(): Promise<void> {
const fileName = `【成就详情】【${props.data.id}】-${props.data.name}`;
await generateShareImg(fileName, achiBox);
}
function parseTriggerType(): string {
switch (props.data.trigger.type) {
case "FINISH_QUEST_AND":
case "FINISH_PARENT_QUEST_AND":
return "完成以下所有任务";
case "FINISH_QUEST_OR":
case "FINISH_PARENT_QUEST_OR":
return "完成以下任意任务";
default:
return props.data.trigger.type;
}
}
</script>
<style lang="css" scoped>
.tua-ao-container {
@@ -112,7 +124,7 @@ async function share(): Promise<void> {
padding: 8px;
border-radius: 4px;
background: var(--box-bg-1);
row-gap: 8px;
row-gap: 4px;
}
.tua-ao-top {
@@ -121,49 +133,72 @@ async function share(): Promise<void> {
flex-direction: column;
align-items: center;
justify-content: space-between;
:first-child {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
:last-child {
font-size: 14px;
font-style: italic;
opacity: 0.8;
}
}
.tua-ao-top :first-child {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 24px;
}
.tua-ao-mid-title,
.tua-ao-bottom-title {
font-size: 18px;
}
.tua-ao-mid-title :first-child,
.tua-ao-bottom-title :first-child {
font-family: var(--font-title);
}
.tua-ao-mid-title :last-child {
color: var(--box-text-3);
.tua-ao-mid-title {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
:first-child {
font-family: var(--font-title);
font-size: 18px;
}
:not(:first-child) {
color: var(--tgc-od-orange);
}
img {
width: 24px;
height: 24px;
}
}
.tua-ao-mid-item {
display: flex;
align-items: center;
justify-content: flex-start;
padding-left: 15px;
margin-top: 5px;
column-gap: 5px;
font-size: 18px;
}
padding-left: 16px;
column-gap: 4px;
font-size: 14px;
.tua-ao-mid-item :first-child {
color: var(--box-text-5);
:first-child {
color: var(--box-text-5);
}
}
.tua-ao-extra {
position: absolute;
right: 10px;
bottom: 10px;
right: 4px;
bottom: 0;
color: var(--tgc-od-white);
font-size: 12px;
}
.tua-ao-click {
cursor: pointer;
text-align: center;
}
</style>

View File

@@ -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>>(() =>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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();

View File

@@ -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>();

View 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>

View File

@@ -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>

View File

@@ -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>();

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -60,6 +60,7 @@ import lunaReq from "@req/lunaReq.js";
import miscReq from "@req/miscReq.js";
import takumiReq from "@req/takumiReq.js";
import TSUserAccount from "@Sqlm/userAccount.js";
import useBBSStore from "@store/bbs.js";
import useUserStore from "@store/user.js";
import TGLogger from "@utils/TGLogger.js";
import { storeToRefs } from "pinia";
@@ -75,6 +76,8 @@ type SignAccount = {
};
const { cookie, uid } = storeToRefs(useUserStore());
const { gameList } = storeToRefs(useBBSStore());
const loadScript = defineModel<boolean>();
const loadState = ref<boolean>(false);
const loadSign = ref<boolean>(false);
@@ -91,31 +94,11 @@ watch(
onMounted(async () => await loadData());
function getGameInfo(biz: string): SignGameInfo {
switch (biz) {
// 崩坏2
case "bh2_cn":
return { title: "崩坏2", icon: "/platforms/mhy/bh2.webp", gid: 3 };
// 崩坏3
case "bh3_cn":
return { title: "崩坏3", icon: "/platforms/mhy/bh3.webp", gid: 1 };
// 原神
case "hk4e_cn":
return { title: "原神", icon: "/platforms/mhy/ys.webp", gid: 2 };
// 崩坏:星穹铁道
case "hkrpg_cn":
return { title: "崩坏:星穹铁道", icon: "/platforms/mhy/sr.webp", gid: 6 };
// 未定事件簿
case "nxx_cn":
return { title: "未定事件簿", icon: "/platforms/mhy/wd.webp", gid: 4 };
// 绝区零
case "nap_cn":
return { title: "绝区零", icon: "/platforms/mhy/zzz.webp", gid: 8 };
// 崩坏:因缘精灵
case "hna_cn":
return { title: "崩坏:因缘精灵", icon: "/platform/s/mhy/hna.webp", gid: 9 };
default:
return { title: biz, icon: "/platforms/mhy/mys.webp", gid: 0 };
}
const enName = biz.split("_")[0];
if (!enName) return { title: biz, icon: "/platforms/mhy/mys.webp", gid: 0 };
const findGame = gameList.value.find((i) => i.op_name === enName);
if (findGame) return { title: findGame.name, icon: findGame.app_icon, gid: findGame.id };
return { title: biz, icon: "/platforms/mhy/mys.webp", gid: 0 };
}
async function loadData(): Promise<void> {

View File

@@ -5,7 +5,7 @@
class="tp-backup-lottery"
:title="`ID: ${props.data.insert.lottery.id}`"
>
<v-icon size="16">mdi-gift</v-icon>
<v-icon size="14">mdi-gift-outline</v-icon>
<span>{{ props.data.insert.lottery.toast }}</span>
</span>
<VpOverlayLottery
@@ -47,12 +47,17 @@ console.log("tpBackupText", props.data.insert.backup_text, toRaw(props.data));
</script>
<style lang="css" scoped>
.tp-backup-lottery {
margin-right: 4px;
color: #00c3ffff;
position: relative;
display: inline-flex;
width: fit-content;
align-items: center;
justify-content: center;
color: #00c3ff;
column-gap: 2px;
cursor: pointer;
:first-child {
margin-right: 2px;
span {
font-size: 12px;
}
}

View File

@@ -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>

View File

@@ -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;

View 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}&region=${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>

View 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>

View File

@@ -2,10 +2,10 @@
<div class="tpu-box">
<div class="tpu-top">
<span @click="copyUid()" data-html2canvas-ignore>
<v-icon>mdi-content-copy</v-icon>
<v-icon size="12">mdi-content-copy</v-icon>
<span>复制</span>
</span>
<span class="tpu-game">{{ getGameName() }}</span>
<span class="tpu-game">{{ gameInfo?.name ?? "未知游戏" }}</span>
</div>
<div class="tpu-main">UID {{ props.data.insert.game_user_info.game_uid }}</div>
<div class="tpu-sub">
@@ -19,7 +19,9 @@
</template>
<script lang="ts" setup>
import showSnackbar from "@comp/func/snackbar.js";
import { computed } from "vue";
import useBBSStore from "@store/bbs.js";
import { storeToRefs } from "pinia";
import { onMounted, ref, shallowRef } from "vue";
type TpUid = {
insert: {
@@ -35,57 +37,68 @@ type TpUid = {
};
type TpUidProps = { data: TpUid };
const { gameUidCards, gameList } = storeToRefs(useBBSStore());
const defaultCard: TGApp.BBS.AppConfig.GameUidCardConf = {
main_text_color: "#a17a58ff",
is_open: true,
background_color: "#ffffff",
image_url: "/source/post/tp_uid_bg.webp",
};
const props = defineProps<TpUidProps>();
const nickname = computed<string>(() =>
decodeURIComponent(props.data.insert.game_user_info.nickname),
);
const nickname = ref<string>();
const gameInfo = shallowRef<TGApp.BBS.Game.Item>();
const cardInfo = shallowRef<TGApp.BBS.AppConfig.GameUidCardConf>(defaultCard);
console.log("tpUid", props.data.insert.game_user_info);
function copyUid(): void {
navigator.clipboard.writeText(props.data.insert.game_user_info.game_uid);
onMounted(async () => {
nickname.value = decodeURIComponent(props.data.insert.game_user_info.nickname);
gameInfo.value = getGameInfo();
if (gameInfo.value) cardInfo.value = gameUidCards.value[gameInfo.value.id] ?? defaultCard;
});
async function copyUid(): Promise<void> {
await navigator.clipboard.writeText(props.data.insert.game_user_info.game_uid);
showSnackbar.success("已复制UID");
}
function getGameName(): string {
const gameBiz = props.data.insert.game_user_info.game_biz;
if (gameBiz.startsWith("hkrpg")) return "崩坏·星穹铁道";
if (gameBiz.startsWith("hk4e")) return "原神";
if (gameBiz.startsWith("nap")) return "绝区零";
if (gameBiz.startsWith("bh2")) return "崩坏学园2";
if (gameBiz.startsWith("bh3")) return "崩坏3";
if (gameBiz.startsWith("nxx")) return "未定事件簿";
return "未知游戏";
function getGameInfo(): TGApp.BBS.Game.Item | undefined {
const enName = props.data.insert.game_user_info.game_biz.split("_")[0];
if (!enName) return undefined;
return gameList.value.find((g) => g.op_name === enName);
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
.tpu-box {
position: relative;
display: flex;
max-width: 100%;
box-sizing: border-box;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 5px;
border: 1px solid var(--common-shadow-2);
border-radius: 3px;
background-color: #f4efe9ff;
background-image: url("/source/post/tp_uid_bg.webp");
padding: 4px 4px 8px;
border-radius: 2px;
background-color: v-bind("cardInfo.background_color");
background-image: v-bind("'url(' + cardInfo.image_url + ')'");
background-position: right bottom;
background-repeat: no-repeat;
background-size: contain;
color: #a17a58ff;
color: v-bind("cardInfo.main_text_color");
}
.tpu-top {
display: flex;
align-items: center;
justify-content: flex-end;
justify-content: center;
margin-left: auto;
column-gap: 5px;
column-gap: 4px;
font-size: 12px;
:first-child {
cursor: pointer;
font-size: 12px;
}
}
@@ -93,27 +106,28 @@ function getGameName(): string {
display: flex;
align-items: center;
justify-content: center;
column-gap: 5px;
font-family: var(--font-title);
font-size: 16px;
}
.tpu-sub {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
column-gap: 2px;
font-size: 14px;
font-size: 12px;
}
.tpu-game {
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
border: 1px solid #a17a584d;
border-radius: 3px;
font-size: 12px;
opacity: 0.7;
padding: 0 2px;
border: 1px solid v-bind("cardInfo.main_text_color");
border-radius: 2px;
font-size: 10px;
opacity: 0.75;
}
</style>

View File

@@ -23,10 +23,10 @@
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 from "artplayer";
import type { Option } from "artplayer/types/option.js";
import Artplayer, { type Option } from "artplayer";
import { onMounted, onUnmounted, ref, shallowRef, toRaw } from "vue";
type TpVod = {
@@ -107,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);
@@ -118,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);
@@ -126,26 +137,22 @@ onMounted(async () => {
onUnmounted(() => {
container.value?.destroy();
if (coverBuffer.value) {
coverBuffer.value = null;
}
if (coverUrl.value) {
URL.revokeObjectURL(coverUrl.value);
}
if (coverBuffer.value) coverBuffer.value = null;
if (coverUrl.value) URL.revokeObjectURL(coverUrl.value);
});
</script>
<style lang="css" scoped>
.tp-vod-box {
position: relative;
max-width: 100%;
margin: 10px auto;
margin: 8px auto;
aspect-ratio: v-bind(vodAspectRatio); /* stylelint-disable-line value-keyword-case */
}
.tp-vod-container {
overflow: hidden;
max-width: 100%;
border-radius: 10px;
border-radius: 8px;
aspect-ratio: v-bind(vodAspectRatio); /* stylelint-disable-line value-keyword-case */
}
@@ -159,7 +166,7 @@ onUnmounted(() => {
width: 100%;
align-items: center;
justify-content: center;
border-radius: 10px;
border-radius: 8px;
aspect-ratio: v-bind(vodAspectRatio); /* stylelint-disable-line value-keyword-case */
}
@@ -181,31 +188,31 @@ onUnmounted(() => {
.tp-vod-time {
position: absolute;
bottom: 10px;
left: 10px;
bottom: 8px;
left: 8px;
display: flex;
align-items: center;
padding: 2px 5px;
border-radius: 5px;
padding: 2px 4px;
border-radius: 4px;
background: #00000080;
color: var(--tgc-white-4);
font-family: var(--font-title);
font-size: 12px;
gap: 5px;
gap: 4px;
}
.tp-vod-view {
position: absolute;
top: 10px;
right: 10px;
top: 8px;
right: 8px;
display: flex;
align-items: center;
padding: 2px 5px;
border-radius: 5px;
padding: 2px 4px;
border-radius: 4px;
background: #00000080;
color: var(--tgc-white-4);
font-family: var(--font-title);
font-size: 12px;
gap: 5px;
gap: 4px;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<span class="tag-label" :title="`点击跳转#${props.tag}#`">
{{ props.tag }}
</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 TpcTagProps = { tag: string };
const props = defineProps<TpcTagProps>();
const { theme } = storeToRefs(useAppStore());
const isDarkMode = computed<boolean>(() => theme.value === "dark");
const tagColor = computed<string>(() => tag2Color(props.tag, 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>
.tag-label {
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
border-radius: 12px;
background: v-bind(bgColor); /* stylelint-disable-line value-keyword-case */
color: v-bind(tagColor); /* stylelint-disable-line value-keyword-case */
cursor: pointer;
gap: 2px;
&:hover {
opacity: 0.8;
}
}
</style>

View File

@@ -1,12 +1,12 @@
<template>
<div class="tbc-box" data-html2canvas-ignore>
<div class="tbc-box" :class="isCollected ? 'active' : ''" data-html2canvas-ignore>
<div class="tbc-btn" @click="switchCollect()" :title="isCollected ? '取消收藏' : '收藏'">
<v-icon :color="isCollected ? 'yellow' : 'inherit'">
<v-icon size="20">
{{ isCollected ? "mdi-star" : "mdi-star-outline" }}
</v-icon>
</div>
<div class="tbc-edit" title="编辑收藏" v-if="isCollected" @click="showEdit = !showEdit">
<v-icon size="small">mdi-pencil</v-icon>
<v-icon size="8">mdi-pencil</v-icon>
</div>
</div>
<VpOverlayCollect v-model="showEdit" :post="props.data" @submit="refresh()" />
@@ -80,38 +80,77 @@ async function switchCollect(): Promise<void> {
showSnackbar.success("取消收藏成功");
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;
.tbc-box {
@include github-styles.github-card;
position: fixed;
top: 70px;
right: 20px;
border: 2px solid var(--common-shadow-8);
top: 64px;
right: 16px;
display: flex;
width: 36px;
height: 36px;
box-sizing: border-box;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
:hover {
opacity: 0.8;
&.active {
background: var(--tgc-btn-1);
box-shadow: 1px 3px 6px var(--common-shadow-2);
color: var(--btn-text);
}
&:hover:not(.active) {
background: var(--common-shadow-1);
}
}
.dark .tbc-box {
border: 1px solid var(--common-shadow-1);
box-shadow: 1px 3px 6px var(--common-shadow-t-2);
&:not(.active) {
@include github-styles.github-card("dark");
&:hover {
background: var(--common-shadow-6);
}
}
}
.tbc-btn {
display: flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
margin: 5px;
}
.tbc-edit {
position: absolute;
right: -10px;
bottom: -10px;
position: relative;
z-index: 1;
display: flex;
width: 20px;
height: 20px;
align-items: center;
justify-content: center;
}
.tbc-edit {
@include github-styles.github-card;
position: absolute;
z-index: 2;
right: -4px;
bottom: -4px;
display: flex;
width: 16px;
height: 16px;
box-sizing: border-box;
align-items: center;
justify-content: center;
border: unset;
border-radius: 50%;
cursor: pointer;
}
.dark .tbc-edit {
@include github-styles.github-card("dark");
}
</style>

View File

@@ -7,18 +7,21 @@
:persistent="true"
:no-click-animation="true"
z-index="60"
:offset="[8, 400]"
:offset="[4, 400]"
>
<template #activator="{ props }">
<v-btn
:loading="loading"
class="tpr-btn"
size="38px"
variant="outlined"
@click="showReply()"
icon="mdi-message-text-outline"
v-bind="props"
/>
size="36"
>
<template #default>
<v-icon size="20">mdi-message-text-outline</v-icon>
</template>
</v-btn>
</template>
<div class="tpr-main-reply">
<div class="tpr-main-filter">
@@ -169,21 +172,33 @@ async function handleDebug(): Promise<void> {
<style lang="scss" scoped>
.tpr-main-box {
position: fixed;
bottom: 20px;
left: 20px;
bottom: 16px;
left: 16px;
display: flex;
width: 36px;
height: 36px;
box-sizing: border-box;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--tgc-btn-1);
box-shadow: 1px 3px 6px var(--common-shadow-2);
color: var(--btn-text);
cursor: pointer;
}
.dark .tpr-main-box {
border: 1px solid var(--common-shadow-1);
box-shadow: 1px 3px 6px var(--common-shadow-t-2);
}
.tpr-btn {
display: flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
border: 2px solid var(--common-shadow-8);
&:hover {
opacity: 0.8;
}
position: relative;
z-index: 1;
width: 36px;
height: 36px;
border: unset;
border-radius: 50%;
}
.tpr-main-reply {
@@ -197,7 +212,7 @@ async function handleDebug(): Promise<void> {
justify-content: flex-start;
padding: 8px;
border: 1px solid var(--common-shadow-1);
border-radius: 4px;
border-radius: 8px;
background: var(--app-page-bg);
box-shadow: 2px 2px 8px var(--common-shadow-4);
overflow-y: auto;

View File

@@ -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 {

View File

@@ -3,34 +3,35 @@
<div class="tpol-box" v-if="card">
<div class="tpol-title">
<span>抽奖详情</span>
<span class="tpol-time">{{ timeStatus }}</span>
<span>{{ timeStatus }}</span>
</div>
<div class="tpol-list">
<div class="tpol-info">
<TpAvatar :data="card.creator" position="left" />
<div class="tpolr-title">参与方式{{ upWay }}</div>
<div class="tpolr-title">
<span>奖品详情</span>
<div v-for="reward in card.rewards" :key="reward.name" class="reward-subtitle">
{{ reward.name }} {{ reward.goal }}
</div>
<div>参与方式{{ upWay }}</div>
<div>奖品详情</div>
<div v-for="reward in card.rewards" :key="reward.name" class="tpol-info-reward">
<v-icon size="12" color="var(--tgc-pink-1)">mdi-gift</v-icon>
<span>{{ reward.name }}</span>
<span>{{ reward.goal }}</span>
</div>
</div>
<div class="tpol-title" v-if="timeStatus === '已开奖'">中奖详情</div>
<div v-if="timeStatus === '已开奖'">
<div v-for="reward in card.rewards" :key="reward.name" class="tpol-list">
<div class="tpolr-title">{{ reward.name }} {{ reward.win }}/{{ reward.goal }}</div>
<div class="tpol-grid">
<template v-if="timeStatus === '已开奖'">
<template v-for="reward in card.rewards" :key="reward.name">
<div class="vpol-reward-title">{{ reward.name }} {{ reward.win }}/{{ reward.goal }}</div>
<div class="vpol-reward-users">
<TpAvatar
v-for="user in reward.users"
:key="user.uid"
:data="user"
position="left"
class="lottery-sub-list"
class="tpolr-user"
@click="onUserClick(user)"
/>
</div>
</div>
</div>
<div class="tpol-id">ID:{{ card.id }}</div>
</template>
</template>
<div class="tpol-id" @click="shareLottery()">ID:{{ card.id }}</div>
</div>
</TOverlay>
</template>
@@ -39,6 +40,9 @@ import TOverlay from "@comp/app/t-overlay.vue";
import showSnackbar from "@comp/func/snackbar.js";
import TpAvatar from "@comp/viewPost/tp-avatar.vue";
import painterReq from "@req/painterReq.js";
import { emit } from "@tauri-apps/api/event";
import { generateShareImg } from "@utils/TGShare.js";
import { stamp2LastTime } from "@utils/toolFunc.js";
import { onUnmounted, ref, shallowRef, watch } from "vue";
type TpoLotteryProps = { lottery: string | undefined };
@@ -76,13 +80,13 @@ watch(
async function load(): Promise<void> {
if (!props.lottery) return;
if (card.value) return;
const cardGet = await painterReq.lottery(props.lottery);
if ("retcode" in cardGet) {
showSnackbar.error(`[${cardGet.retcode}] ${cardGet.message}`);
const resp = await painterReq.lottery(props.lottery);
if ("retcode" in resp) {
showSnackbar.error(`[${resp.retcode}] ${resp.message}`);
return;
}
jsonData.value = cardGet;
if (cardGet.status === "Settled") timeStatus.value = "已开奖";
jsonData.value = resp;
if (resp.status === "Settled") timeStatus.value = "已开奖";
else {
if (timer !== undefined) {
clearInterval(timer);
@@ -90,7 +94,7 @@ async function load(): Promise<void> {
}
timer = setInterval(flushTimeStatus, 1000);
}
card.value = transLotteryCard(cardGet);
card.value = transLotteryCard(resp);
upWay.value = getUpWay(card.value?.upWay);
}
@@ -109,18 +113,13 @@ function flushTimeStatus(): void {
if (!jsonData.value) return;
const timeNow = new Date().getTime();
const timeDiff = Number(jsonData.value.draw_time) * 1000 - timeNow;
if (timeDiff <= 0) {
if (timeDiff > 0) timeStatus.value = stamp2LastTime(timeDiff);
else {
timeStatus.value = "已开奖";
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
} else {
const day = Math.floor(timeDiff / (24 * 3600 * 1000));
const hour = Math.floor((timeDiff % (24 * 3600 * 1000)) / (3600 * 1000));
const minute = Math.floor((timeDiff % (3600 * 1000)) / (60 * 1000));
const second = Math.floor((timeDiff % (60 * 1000)) / 1000);
timeStatus.value = `${day}${hour}小时${minute}${second}`;
}
}
@@ -144,6 +143,18 @@ function transLotteryCard(lotteryData: TGApp.BBS.Lottery.FullData): RenderCard {
};
}
async function onUserClick(user: TGApp.BBS.Post.User): Promise<void> {
const uid = user.uid;
await emit("userMention", uid);
}
async function shareLottery(): Promise<void> {
const shareDom = document.querySelector<HTMLDivElement>(".tpol-box");
if (!shareDom) return;
const fileName = `lottery-${card.value?.id}.png`;
await generateShareImg(fileName, shareDom, 2, true);
}
onUnmounted(() => {
if (timer !== undefined) {
clearInterval(timer);
@@ -153,64 +164,102 @@ onUnmounted(() => {
</script>
<style lang="css" scoped>
.tpol-box {
position: relative;
display: flex;
width: 800px;
max-width: 800px;
max-height: 50vh;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding: 8px;
border-radius: 8px;
border-radius: 2px;
background: var(--box-bg-1);
overflow-y: auto;
row-gap: 8px;
row-gap: 4px;
}
.tpol-title {
position: relative;
display: flex;
width: 100%;
align-items: flex-end;
justify-content: flex-start;
color: var(--common-text-title);
column-gap: 8px;
font-family: var(--font-title);
font-size: 20px;
:last-child {
color: var(--tgc-red-1);
font-size: 18px;
}
}
.tpol-time {
margin-left: 8px;
color: var(--tgc-red-1);
}
.tpol-list {
.tpol-info {
position: relative;
display: flex;
width: 100%;
box-sizing: border-box;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding: 4px;
border: 1px solid var(--common-shadow-1);
border-radius: 4px;
background: var(--box-bg-2);
}
.tpolr-title {
margin-bottom: 10px;
margin-left: 4px;
font-size: 16px;
.tpol-info-reward {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
margin-left: 16px;
color: var(--tgc-od-white);
column-gap: 4px;
font-size: 12px;
:last-child {
font-style: italic;
text-decoration: underline;
}
}
.reward-subtitle {
font-size: 16px;
opacity: 0.5;
}
.tpol-grid {
display: grid;
gap: 4px;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.lottery-sub-list {
.vpol-reward-title {
position: relative;
width: 100%;
color: var(--box-text-2);
text-align: center;
}
.vpol-reward-users {
position: relative;
display: flex;
width: 100%;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 8px;
}
.tpolr-user {
position: relative;
width: fit-content;
height: 50px;
box-sizing: border-box;
border: 1px solid var(--common-shadow-2);
border-radius: 30px 4px 4px 30px;
background: var(--box-bg-3);
padding-right: 20px;
border: 1px solid var(--common-shadow-1);
border-radius: 50px;
background: var(--box-bg-2);
cursor: pointer;
}
.tpol-id {
font-size: 14px;
text-align: right;
position: absolute;
top: 4px;
right: 4px;
cursor: pointer;
font-size: 12px;
}
</style>

View File

@@ -472,7 +472,7 @@ async function handleUser(): Promise<void> {
}
.tpr-share-info.bottom {
bottom: 4px;
left: 4px;
bottom: 8px;
left: 8px;
}
</style>

View File

@@ -1,8 +1,4 @@
{
"id": 10000002,
"name": "神里绫华",
"title": "白鹭霜华",
"description": "稻妻「社奉行」神里家的大小姐。端庄而文雅,聪慧又坚韧。",
"area": "稻妻",
"brief": {
"camp": "社奉行",
@@ -10,9 +6,49 @@
"birth": "9月28日",
"cv": { "cn": "小N", "jp": "早见沙织", "en": "艾丽卡·门德斯", "kr": "李侑俐" }
},
"star": 5,
"constellation": [
{
"Id": 21,
"Name": "霜杀墨染樱",
"Description": "神里绫华的普通攻击或重击对敌人造成<color=#99FFFFFF>冰元素伤害</color>时有50%的几率使<color=#FFD780FF>神里流·冰华</color>的冷却时间缩减0.3秒。该效果每0.1秒只能触发一次。",
"Icon": "UI_Talent_S_Ayaka_01"
},
{
"Id": 22,
"Name": "三重雪关扉",
"Description": "施放<color=#FFD780FF>神里流·霜灭</color>时会额外释放两股规模较小的霜见雪关扉各自能造成原本20%的伤害。",
"Icon": "UI_Talent_S_Ayaka_02"
},
{
"Id": 23,
"Name": "花白锦画纸吹雪",
"Description": "<color=#FFD780FF>神里流·霜灭</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Ayaka_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 24,
"Name": "盈缺流返",
"Description": "敌人受到<color=#FFD780FF>神里流·霜灭</color>的霜见雪关扉造成的伤害后防御力降低30%持续6秒。",
"Icon": "UI_Talent_S_Ayaka_03"
},
{
"Id": 25,
"Name": "花云钟入月",
"Description": "<color=#FFD780FF>神里流·冰华</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Ayaka_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 26,
"Name": "间水月",
"Description": "每过10秒神里绫华会获得「薄冰舞踏」使重击造成的伤害提高298%。薄冰舞踏效果将在重击命中敌人的0.5秒后清除,并重新开始计算时间。",
"Icon": "UI_Talent_S_Ayaka_04"
}
],
"description": "稻妻「社奉行」神里家的大小姐。端庄而文雅,聪慧又坚韧。",
"element": "冰",
"weapon": "单手剑",
"id": 10000002,
"materials": [
{ "id": 104164, "name": "哀叙冰玉", "star": 5 },
{ "id": 113023, "name": "恒常机关之心", "star": 4 },
@@ -21,6 +57,7 @@
{ "id": 104325, "name": "「风雅」的哲学", "star": 4 },
{ "id": 113018, "name": "血玉之枝", "star": 5 }
],
"name": "神里绫华",
"skills": [
{
"GroupId": 231,
@@ -72,44 +109,39 @@
"Icon": "UI_Talent_Combine_Weapon_Double"
}
],
"constellation": [
"star": 5,
"stories": [
{
"Id": 21,
"Name": "霜杀墨染樱",
"Description": "神里绫华的普通攻击或重击对敌人造成<color=#99FFFFFF>冰元素伤害</color>时有50%的几率使<color=#FFD780FF>神里流·冰华</color>的冷却时间缩减0.3秒。该效果每0.1秒只能触发一次。",
"Icon": "UI_Talent_S_Ayaka_01"
"Title": "角色详细",
"Context": "继承稻妻城中至为尊崇的三家名门之一——神里家族的,是一对兄妹。\n哥哥绫人出任「家主」一职掌管政务妹妹绫华贵为「公主」平日主理家族内外事宜。\n绫华常出现在社交场合与民间交集也较多。因此更被人们所熟悉的她反而获得了高于兄长的名望被雅称为「白鹭公主」。\n众所周知神里家的女儿绫华小姐容姿端丽、品行高洁是深受民众钦慕的人物。"
},
{
"Id": 22,
"Name": "三重雪关扉",
"Description": "施放<color=#FFD780FF>神里流·霜灭</color>时会额外释放两股规模较小的霜见雪关扉各自能造成原本20%的伤害。",
"Icon": "UI_Talent_S_Ayaka_02"
"Title": "角色故事1",
"Context": "放眼稻妻领土,未上至雷电将军视听的事务大多由「评定所」处理。\n「评定所」的议事权分属三家人称三奉行即——「社奉行」、「天领奉行」与「勘定奉行」。\n拥有此三项奉行权利的家族其姓氏为神里、九条与柊乃是稻妻无人不知无人不晓的御三家。\n而神里绫华正是社奉行神里家的大小姐远近闻名的「白鹭公主」。\n问及她为何被称为白鹭公主稻妻人各有说辞——\n「因为绫华大人如白鹭般优雅高洁您请看她清丽秀美的外表她聪敏得体的谈吐难道当不起一声公主吗」\n「绫华大人她呀虽说身份高贵对我们却是礼貌中透着亲切。她个性善良仁厚很愿意对他人伸出援手。您知道吗就是她力排众议帮着收容了庶人托马。」\n众说纷纭却无人说得出「白鹭公主」之名的确切来由。\n不过绫华受民众爱戴的事倒是可见一斑。"
},
{
"Id": 23,
"Name": "花白锦画纸吹雪",
"Description": "<color=#FFD780FF>神里流·霜灭</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Ayaka_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
"Title": "角色故事2",
"Context": "身为社奉行神里家的女儿,绫华常提防着贵胄门庭之间的权力争斗。\n她年纪尚轻却已名望隆盛不免遇到嫉妒神里家兄妹的名门子弟暗中挑衅。\n营造公众形象一事本是形式主义但对神里家而言地位使然再无意义的惯例也有其社交意义。\n不在稻妻关系网中转圜腾挪便会坐不稳社奉行之位。因此兄妹俩达成了一致。\n兄长绫人政务繁忙且不喜抛头露面到公众场合现身以树立神里家形象一事便交给了举止优美、擅长与人打交道的妹妹绫华来办。\n凭借端庄娴静、风雅有礼的姿态绫华在各种社交场合都占据了一席之地。无论是跟潜在的合作伙伴交涉还是与难缠的贵族周旋她都进退有据无可挑剔。\n此外家族内部诸多事物也由绫华掌理。如果没有她内宅恐怕早已陷入混乱。"
},
{
"Id": 24,
"Name": "盈缺流返",
"Description": "敌人受到<color=#FFD780FF>神里流·霜灭</color>的霜见雪关扉造成的伤害后防御力降低30%持续6秒。",
"Icon": "UI_Talent_S_Ayaka_03"
"Title": "角色故事3",
"Context": "秋季的一个午后,绫华办完琐事走在回程路上,偶然听见一间老宅中传来苍老的歌声。\n屋内住着一位双目失明的老妇人。干瘦的手指拨动琴弦木琴便发出流水般的声响。\n兴许是耳朵灵敏的关系老人听见脚步声询问门外的人是谁。绫华不想她感到困扰便说自己是迷路误入此地的附近居民。\n身为社奉行绫华对民生十分熟悉很快认出这位膝下无子的孤寡老人正是天气晴朗时在街边弹唱卖艺的那位。\n曲是过时的老调歌亦如此。目不能视的老人早已落后他人太多。即便在追寻永恒的国度之中也会有活得如此艰辛的人。\n出于好意隐瞒身份的绫华陪老人聊了好一会儿。老人当她是普通少女给她讲木琴的做法和弹法还将自己收藏的茶叶分了一些给她。\n与神里家常备的极品茶叶相比这些粗茶几乎算是草根。但绫华珍重地收下并再三向她道谢。\n这一天她屡次想起双亲。父母若是还在想必也会有像这样老去的一日。\n回到家中绫华将这个故事告诉兄长绫人。兄妹俩一同享用了老人赠予的粗茶。\n此后每隔一段时日绫华都亲自前去探望老妇人依然用着附近居民的名义每次都为她带去一些平民爱用的生活必需品。\n「街口的绯樱树又开花了」绫华笑着告诉老人「如您的琴声一般美丽。」"
},
{
"Id": 25,
"Name": "花云钟入月",
"Description": "<color=#FFD780FF>神里流·冰华</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Ayaka_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
"Title": "角色故事4",
"Context": "在民众的想象中,贵族世家吃穿用度规格远超常人,那高贵如神里绫华,一定也过着极其奢侈、极其肆意的生活吧。\n然而这样的想法只对了一半。\n单从形式而言绫华的生活确实较寻常百姓讲究得多。\n平日里钻研花道、茶道品名茶、赏奇花的开销也着实不小。不过这些都是身为贵族大小姐的她必须掌握的技能并无「肆意」一说。\n真正能让绫华露出笑容的反而是那些寻常百姓也能享受的小事。\n譬如制作点心譬如在池塘边捞金鱼又譬如躲起来阅读八重堂新出的小说…无一不是小事。\n这种时刻的她不是受人景仰的白鹭公主不是掌管神里大宅的绫华大人而是普普通通的「少女绫华」。\n卸下端庄持重的对外形象绽放出随性自在的真我。唯有作为「少女绫华」时她才能放下重担做普通女孩也能做的事。\n深夜肚子饿了避开佣人偷偷溜到厨房哼着歌为自己做一碗茶泡饭茶道课时偷偷根据茶叶的形状占卜恋爱运势…等等。\n虽然从未向他人说明但绫华无比珍惜自己作为普通少女的时间。因为这样的自由时刻实在很少。"
},
{
"Id": 26,
"Name": "间水月",
"Description": "每过10秒神里绫华会获得「薄冰舞踏」使重击造成的伤害提高298%。薄冰舞踏效果将在重击命中敌人的0.5秒后清除,并重新开始计算时间。",
"Icon": "UI_Talent_S_Ayaka_04"
"Title": "角色故事5",
"Context": "指导绫华诸门技艺的老师们曾欣慰地表示:无论茶道、剑道或棋道,诸般风雅,绫华都已完全掌握。\n她俨然是一位文武双全、风姿超绝的贵族小姐了。能教导这样一位学生身为老师也感到非常愉快。\n但遗憾之情…当真完全没有吗绫华默默想道。\n茶心和敬清寂的正心。\n剑心锐不可当的武心。\n棋心审时度势的慧心。\n茶心、剑心、棋心俱是她心此外却也有着一颗对友人的真心。\n绫华始终等待着世上能出现一个与她平等相对、并肩而立的友人。\n那个人不会将她看作「社奉行」或「白鹭公主」不会为礼数和地位所束缚。最好还能懂得各种知识见识过各种奇闻异事…必要时还能给她讲讲故事。\n如此才能成为她绫华的好友。\n「也并不是那么难的事吧…可这样的人究竟在哪里呢」"
},
{
"Title": "杜若丸",
"Context": "「来来来,」\n「来玩手鞠。」\n「一下两下三下」\n「手鞠高高飞过神樱树。」\n「四下、五下、六下」\n「手鞠高高飞过影向山。」\n「七下、八下、九下」\n「手鞠又回到原处」\n「又回到小绫华手中。」\n这是绫华幼年最喜欢的儿歌。\n那时的她给最喜爱的手鞠取名为「杜若丸」每天一边拍打着五彩缤纷的杜若丸一边哼唱这首儿歌。\n听到绫华的歌声父母与哥哥都会忍不住露出微笑甚至和她一同玩闹。一家人围成圈轮流抛掷手鞠。\n不过那都是许久之前的事了。绫华早已不再把玩手鞠。\n她成为了能独当一面的大人物。而象征着童年、承载着珍贵回忆的杜若丸也被收起来安置在绫华的柜子里。"
},
{
"Title": "神之眼",
"Context": "好些年前,家中发生重大变故,重担落到了兄长绫人肩上。那时,绫华还不是如今这副成熟能干的模样。\n她本是爱玩的孩子并不懂得家族责任更缺乏应对种种人物的手腕与经验。\n但看着病床上的母亲与劳碌的兄长绫华意识到她非得成长起来不可。\n于是她决定首先拾起荒废许久的剑术与诗歌——这是身为贵族的基础修养若能掌握此二者她便算是面上过得去的神里家千金了能代替兄长出席一些祭典之类的场合为他分忧。\n事实证明绫华并非天赋异禀也曾为诗歌背不下来、写字不够风雅、剑术毫无章法之类的事而苦恼。\n她从未动摇过——一遍背不下来的诗就背五十遍一遍写不好的字就练五十遍一遍使不好的剑术就使五十遍。\n「千般锤磨素振亦无人可当。」——这是母亲告诉她的话。\n母亲离世后她不再是从前那个小绫华。如今的她是神里绫华将军御下三家之一社奉行神里家的大小姐。\n剑术训练成为了日常生活的一环从开始那日延续至今从未间断。\n不知第多少天绫华终于能做到一击退敌。霎时道场内冰花凝结道场正中她的刀尖上挂着一枚如冰棱般璀璨的「神之眼」。\n千般锤磨素振亦无人可当就连神明也为之动容。"
}
],
"talks": [
@@ -287,38 +319,6 @@
{ "Title": "加入队伍·其二", "Context": "烦请赐教。" },
{ "Title": "加入队伍·其三", "Context": "呵呵,请多关照。" }
],
"stories": [
{
"Title": "角色详细",
"Context": "继承稻妻城中至为尊崇的三家名门之一——神里家族的,是一对兄妹。\n哥哥绫人出任「家主」一职掌管政务妹妹绫华贵为「公主」平日主理家族内外事宜。\n绫华常出现在社交场合与民间交集也较多。因此更被人们所熟悉的她反而获得了高于兄长的名望被雅称为「白鹭公主」。\n众所周知神里家的女儿绫华小姐容姿端丽、品行高洁是深受民众钦慕的人物。"
},
{
"Title": "角色故事1",
"Context": "放眼稻妻领土,未上至雷电将军视听的事务大多由「评定所」处理。\n「评定所」的议事权分属三家人称三奉行即——「社奉行」、「天领奉行」与「勘定奉行」。\n拥有此三项奉行权利的家族其姓氏为神里、九条与柊乃是稻妻无人不知无人不晓的御三家。\n而神里绫华正是社奉行神里家的大小姐远近闻名的「白鹭公主」。\n问及她为何被称为白鹭公主稻妻人各有说辞——\n「因为绫华大人如白鹭般优雅高洁您请看她清丽秀美的外表她聪敏得体的谈吐难道当不起一声公主吗」\n「绫华大人她呀虽说身份高贵对我们却是礼貌中透着亲切。她个性善良仁厚很愿意对他人伸出援手。您知道吗就是她力排众议帮着收容了庶人托马。」\n众说纷纭却无人说得出「白鹭公主」之名的确切来由。\n不过绫华受民众爱戴的事倒是可见一斑。"
},
{
"Title": "角色故事2",
"Context": "身为社奉行神里家的女儿,绫华常提防着贵胄门庭之间的权力争斗。\n她年纪尚轻却已名望隆盛不免遇到嫉妒神里家兄妹的名门子弟暗中挑衅。\n营造公众形象一事本是形式主义但对神里家而言地位使然再无意义的惯例也有其社交意义。\n不在稻妻关系网中转圜腾挪便会坐不稳社奉行之位。因此兄妹俩达成了一致。\n兄长绫人政务繁忙且不喜抛头露面到公众场合现身以树立神里家形象一事便交给了举止优美、擅长与人打交道的妹妹绫华来办。\n凭借端庄娴静、风雅有礼的姿态绫华在各种社交场合都占据了一席之地。无论是跟潜在的合作伙伴交涉还是与难缠的贵族周旋她都进退有据无可挑剔。\n此外家族内部诸多事物也由绫华掌理。如果没有她内宅恐怕早已陷入混乱。"
},
{
"Title": "角色故事3",
"Context": "秋季的一个午后,绫华办完琐事走在回程路上,偶然听见一间老宅中传来苍老的歌声。\n屋内住着一位双目失明的老妇人。干瘦的手指拨动琴弦木琴便发出流水般的声响。\n兴许是耳朵灵敏的关系老人听见脚步声询问门外的人是谁。绫华不想她感到困扰便说自己是迷路误入此地的附近居民。\n身为社奉行绫华对民生十分熟悉很快认出这位膝下无子的孤寡老人正是天气晴朗时在街边弹唱卖艺的那位。\n曲是过时的老调歌亦如此。目不能视的老人早已落后他人太多。即便在追寻永恒的国度之中也会有活得如此艰辛的人。\n出于好意隐瞒身份的绫华陪老人聊了好一会儿。老人当她是普通少女给她讲木琴的做法和弹法还将自己收藏的茶叶分了一些给她。\n与神里家常备的极品茶叶相比这些粗茶几乎算是草根。但绫华珍重地收下并再三向她道谢。\n这一天她屡次想起双亲。父母若是还在想必也会有像这样老去的一日。\n回到家中绫华将这个故事告诉兄长绫人。兄妹俩一同享用了老人赠予的粗茶。\n此后每隔一段时日绫华都亲自前去探望老妇人依然用着附近居民的名义每次都为她带去一些平民爱用的生活必需品。\n「街口的绯樱树又开花了」绫华笑着告诉老人「如您的琴声一般美丽。」"
},
{
"Title": "角色故事4",
"Context": "在民众的想象中,贵族世家吃穿用度规格远超常人,那高贵如神里绫华,一定也过着极其奢侈、极其肆意的生活吧。\n然而这样的想法只对了一半。\n单从形式而言绫华的生活确实较寻常百姓讲究得多。\n平日里钻研花道、茶道品名茶、赏奇花的开销也着实不小。不过这些都是身为贵族大小姐的她必须掌握的技能并无「肆意」一说。\n真正能让绫华露出笑容的反而是那些寻常百姓也能享受的小事。\n譬如制作点心譬如在池塘边捞金鱼又譬如躲起来阅读八重堂新出的小说…无一不是小事。\n这种时刻的她不是受人景仰的白鹭公主不是掌管神里大宅的绫华大人而是普普通通的「少女绫华」。\n卸下端庄持重的对外形象绽放出随性自在的真我。唯有作为「少女绫华」时她才能放下重担做普通女孩也能做的事。\n深夜肚子饿了避开佣人偷偷溜到厨房哼着歌为自己做一碗茶泡饭茶道课时偷偷根据茶叶的形状占卜恋爱运势…等等。\n虽然从未向他人说明但绫华无比珍惜自己作为普通少女的时间。因为这样的自由时刻实在很少。"
},
{
"Title": "角色故事5",
"Context": "指导绫华诸门技艺的老师们曾欣慰地表示:无论茶道、剑道或棋道,诸般风雅,绫华都已完全掌握。\n她俨然是一位文武双全、风姿超绝的贵族小姐了。能教导这样一位学生身为老师也感到非常愉快。\n但遗憾之情…当真完全没有吗绫华默默想道。\n茶心和敬清寂的正心。\n剑心锐不可当的武心。\n棋心审时度势的慧心。\n茶心、剑心、棋心俱是她心此外却也有着一颗对友人的真心。\n绫华始终等待着世上能出现一个与她平等相对、并肩而立的友人。\n那个人不会将她看作「社奉行」或「白鹭公主」不会为礼数和地位所束缚。最好还能懂得各种知识见识过各种奇闻异事…必要时还能给她讲讲故事。\n如此才能成为她绫华的好友。\n「也并不是那么难的事吧…可这样的人究竟在哪里呢」"
},
{
"Title": "杜若丸",
"Context": "「来来来,」\n「来玩手鞠。」\n「一下两下三下」\n「手鞠高高飞过神樱树。」\n「四下、五下、六下」\n「手鞠高高飞过影向山。」\n「七下、八下、九下」\n「手鞠又回到原处」\n「又回到小绫华手中。」\n这是绫华幼年最喜欢的儿歌。\n那时的她给最喜爱的手鞠取名为「杜若丸」每天一边拍打着五彩缤纷的杜若丸一边哼唱这首儿歌。\n听到绫华的歌声父母与哥哥都会忍不住露出微笑甚至和她一同玩闹。一家人围成圈轮流抛掷手鞠。\n不过那都是许久之前的事了。绫华早已不再把玩手鞠。\n她成为了能独当一面的大人物。而象征着童年、承载着珍贵回忆的杜若丸也被收起来安置在绫华的柜子里。"
},
{
"Title": "神之眼",
"Context": "好些年前,家中发生重大变故,重担落到了兄长绫人肩上。那时,绫华还不是如今这副成熟能干的模样。\n她本是爱玩的孩子并不懂得家族责任更缺乏应对种种人物的手腕与经验。\n但看着病床上的母亲与劳碌的兄长绫华意识到她非得成长起来不可。\n于是她决定首先拾起荒废许久的剑术与诗歌——这是身为贵族的基础修养若能掌握此二者她便算是面上过得去的神里家千金了能代替兄长出席一些祭典之类的场合为他分忧。\n事实证明绫华并非天赋异禀也曾为诗歌背不下来、写字不够风雅、剑术毫无章法之类的事而苦恼。\n她从未动摇过——一遍背不下来的诗就背五十遍一遍写不好的字就练五十遍一遍使不好的剑术就使五十遍。\n「千般锤磨素振亦无人可当。」——这是母亲告诉她的话。\n母亲离世后她不再是从前那个小绫华。如今的她是神里绫华将军御下三家之一社奉行神里家的大小姐。\n剑术训练成为了日常生活的一环从开始那日延续至今从未间断。\n不知第多少天绫华终于能做到一击退敌。霎时道场内冰花凝结道场正中她的刀尖上挂着一枚如冰棱般璀璨的「神之眼」。\n千般锤磨素振亦无人可当就连神明也为之动容。"
}
]
"title": "白鹭霜华",
"weapon": "单手剑"
}

View File

@@ -1,8 +1,4 @@
{
"id": 10000003,
"name": "琴",
"title": "蒲公英骑士",
"description": "正直严谨的蒲公英骑士,蒙德西风骑士团的代理团长。",
"area": "蒙德",
"brief": {
"camp": "西风骑士团",
@@ -10,9 +6,49 @@
"birth": "3月14日",
"cv": { "cn": "林簌", "jp": "斋藤千和", "en": "斯蒂芬妮·萨瑟兰", "kr": "安荣美" }
},
"star": 5,
"constellation": [
{
"Id": 31,
"Name": "流转剑脊的暴风",
"Description": "<color=#FFD780FF>风压剑</color>长按超过1秒后提升牵引速度并使造成的伤害提升40%。",
"Icon": "UI_Talent_S_Qin_01"
},
{
"Id": 32,
"Name": "守护众人的坚盾",
"Description": "琴获得元素晶球或元素微粒时队伍中所有角色获得15%攻击速度和15%移动速度提升持续15秒。",
"Icon": "UI_Talent_S_Qin_02"
},
{
"Id": 33,
"Name": "西风吹拂之时",
"Description": "<color=#FFD780FF>蒲公英之风</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Qin_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 34,
"Name": "蒲公英的国土",
"Description": "在<color=#FFD780FF>蒲公英之风</color>的领域内,所有敌人的<color=#80FFD7FF>风元素抗性</color>下降40%。",
"Icon": "UI_Talent_S_Qin_03"
},
{
"Id": 35,
"Name": "须臾一瞬的烈风",
"Description": "<color=#FFD780FF>风压剑</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Qin_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 36,
"Name": "恩眷万民的狮牙",
"Description": "在<color=#FFD780FF>蒲公英之风</color>的领域内角色受到的伤害降低35%。\n离开领域后这个效果会在承受3次攻击或10秒后消失。",
"Icon": "UI_Talent_S_Qin_04"
}
],
"description": "正直严谨的蒲公英骑士,蒙德西风骑士团的代理团长。",
"element": "风",
"weapon": "单手剑",
"id": 10000003,
"materials": [
{ "id": 104154, "name": "自在松石", "star": 5 },
{ "id": 113001, "name": "飓风之种", "star": 4 },
@@ -21,6 +57,7 @@
{ "id": 104306, "name": "「抗争」的哲学", "star": 4 },
{ "id": 113003, "name": "东风之翎", "star": 5 }
],
"name": "琴",
"skills": [
{
"GroupId": 331,
@@ -65,44 +102,39 @@
"Icon": "UI_Talent_Cook_Heal"
}
],
"constellation": [
"star": 5,
"stories": [
{
"Id": 31,
"Name": "流转剑脊的暴风",
"Description": "<color=#FFD780FF>风压剑</color>长按超过1秒后提升牵引速度并使造成的伤害提升40%。",
"Icon": "UI_Talent_S_Qin_01"
"Title": "角色详细",
"Context": "西风骑士团是守护蒙德的剑与盾。\n除了清扫荒野的魔物、守护城市与道路安全之外骑士团最重要的工作是维护蒙德的秩序。\n虽说蒙德是自由之都但无边际的散漫只会带来混沌与彷徨。\n琴非常明白这一道理所以才坚持严谨刻苦的作风以最严苛的要求对待自己。\n——然后一不留神就在月初消耗完了当月所有的咖啡配额。"
},
{
"Id": 32,
"Name": "守护众人的坚盾",
"Description": "琴获得元素晶球或元素微粒时队伍中所有角色获得15%攻击速度和15%移动速度提升持续15秒。",
"Icon": "UI_Talent_S_Qin_02"
"Title": "角色故事1",
"Context": "古恩希尔德家族是古老的骑士家族。据说自第一篇史诗写成之日,就已开始守护蒙德。\n但历史悠久的血脉必然伴随着沉重负担。琴自幼便被母亲作为骑士道继承者培养。\n象征骑士风度的形体、礼仪锻造骑士灵魂的历史、诗歌武装骑士精神的剑艺、体能…琴全都要训练。唯有如此才能实践古恩希尔德的家训「永护蒙德」。\n早年的酒馆里有这样一句玩笑话古恩希尔德家族的长子长女在学会喊「妈妈」之前就已能够念出「永护蒙德」。\n从《林间风故事拔萃》抬起头看见同龄孩子举着风车欢笑着跑过窗前。小小的琴自然是明白其中含义的。\n而如今从累牍公文中抬起头看见蒙德的未来举着风车欢笑着跑过街道。代理团长大人对从前那些岁月依然毫无悔恨。\n「这是正确的事。无论多辛苦正确的事就应全力以赴。」"
},
{
"Id": 33,
"Name": "西风吹拂之时",
"Description": "<color=#FFD780FF>蒲公英之风</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Qin_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
"Title": "角色故事2",
"Context": "「琴团长十分可靠。」\n「若是遇到难题找她帮忙一定没错。」\n在蒙德骑士和民众都十分仰赖琴。\n如果有人需要帮助事情又合乎道理那她一定会出手相助。\n即使是街市上的小口角即使是恋爱的渺小烦恼…\n即使与骑士团的本职工作毫无关系只要有人对琴开口琴就会对他们伸出援手。\n「要问原因的话——帮助受困之人不正是骑士职责所在吗」\n对她而言骑士的职责先于「代理团长」的勤务。对她而言亲手帮助有需要的人最为实在。\n这也是她对麾下骑士的要求。\n图书管理员丽莎曾经劝琴放松「应该适当享受淑女的下午茶时光」。\n可对琴来说骑士的职责先于淑女的身份。\n「琴团长十分可靠。」\n人们一如既往地给予她这一评价。\n令她烦恼的是一天时间只有那么多。即使牺牲睡眠也无法帮助所有人。\n要成为可靠的人需要付出难以想象的努力。"
},
{
"Id": 34,
"Name": "蒲公英的国土",
"Description": "在<color=#FFD780FF>蒲公英之风</color>的领域内,所有敌人的<color=#80FFD7FF>风元素抗性</color>下降40%。",
"Icon": "UI_Talent_S_Qin_03"
"Title": "角色故事3",
"Context": "琴代行团长职责的如今,不少人已经忘记在她之上仍有一位「大团长」。\n而她本人从未在意过这种事。骑士团内的地位与称号丝毫不会影响她行事的原则。\n热忱、正直与一丝不苟琴身上这些优良品质有两大来源。\n其一是自幼接受的教育和训练已将骑士道精神深深植入她灵魂深处。\n其二是现已缺席的西风骑士团团长法尔伽曾对她悉心教导。\n洒脱而散漫的北风骑士法尔伽他给琴的成长带来了巨大影响。\n「大团长请您认真对待工作直面蒙德对您的期待。」\n「小姑娘你是我的副手为我分担工作天经地义。这样大团长才有精力做更重要的事啊。」\n「……」\n他是征服与创造传奇的骑士而她是守护和平与自由的骑士。\n琴并不讨厌法尔伽。法尔伽的行事风格或许有其独到之处。琴需要完成的是那些大团长没完成的正确之事。\n半年前法尔伽率领西风骑士中的精锐再一次离开蒙德踏上远征之路。\n远征——这是很符合大团长风格的冒险。\n「骑士团就交给你了。反正这些年也是你在做团长的工作。」\n「放心交给我吧大团长。」\n站在窗前目送团长的她心想\n等你回来将会见到一座更加温暖、繁荣、平和的蒙德城。"
},
{
"Id": 35,
"Name": "须臾一瞬的烈风",
"Description": "<color=#FFD780FF>风压剑</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Qin_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
"Title": "角色故事4",
"Context": "风起地的神树,是初代「蒲公英骑士」道路的终点。\n据记载建立西风骑士团、重振蒙德的初代蒲公英骑士温妮莎曾在生命结束之刻来到此地。\n她永别了衷心守护的城邦留下传奇与一株树苗。\n这株树苗在千风的眷顾中在无数的日月照耀下成长为参天巨木。\n琴十五岁时获授「蒲公英骑士」之名。\n「蒲公英骑士」又名「狮牙骑士」随骑士团制度一同沿袭至今是最杰出的骑士才能获得的荣誉。\n那日授衔仪式结束后琴从庆祝活动中悄然离开追随着一直憧憬的英雄骑士的脚步来到那棵大树前。\n「蒲公英骑士」之名象征着温妮莎的抗争与仁爱。自己如何能拥有资格来继承如此伟大的称号\n蒙德城重建至今已逾千年。自己何德何能能守护这座古老、自由、骄傲的城邦\n隐藏在稳重成熟外表下的是一颗刚刚完成骑士礼尚未做好准备的少女之心。\n风从远方吹来将骑士拥入怀中轻柔地拂去她的疑虑与不安展露出黄金般的决心\n「永护蒙德。」\n——成为温妮莎那样温柔坚定的战士为同胞战斗为自由抗争。这便是那句简明严厉的家训中蕴含的心意吧。\n如今每当感到疲惫动摇琴仍会来到这棵树下接受风的洗礼。风洗净她的困顿令她重拾前行的力量。\n风起地的神树是初代「狮牙骑士」道路的终点。\n同时也是「蒲公英骑士」琴的起点。"
},
{
"Id": 36,
"Name": "恩眷万民的狮牙",
"Description": "在<color=#FFD780FF>蒲公英之风</color>的领域内角色受到的伤害降低35%。\n离开领域后这个效果会在承受3次攻击或10秒后消失。",
"Icon": "UI_Talent_S_Qin_04"
"Title": "角色故事5",
"Context": "琴团长有一个秘密。\n古恩希尔德家族是古老的骑士家族。这一骄傲的血脉来自琴的母亲芙蕾德莉卡。\n琴的父亲则是名贯大陆的冒险家——西蒙·佩奇。来到蒙德后他扫去身上风沙以崭新的姿态加入西风教会一路升为西风教会总管人称「拂晓的枢机卿」。\n曾经的爱侣最终分道扬镳。年幼的琴牵着母亲的手望着父亲与妹妹芭芭拉远去的背影。\n后来芭芭拉也与父亲一样加入西风教会成为了一位深受蒙德人喜爱的牧师。\n琴一直想要亲近这位血脉相通的妹妹但面对芭芭拉躲闪的眼神不知该如何开口。\n或许这份相似的笨拙就是姐妹心意仍然相通的表现吧。\n琴团长还有一个秘密。\n尽管熟读历史典籍尽管身戴「蒲公英骑士」之名尽管成为了深受信赖的代理团长…\n琴依然十分中意恋爱小说。\n并非因为在训练与勤务中虚度了少女年华也不是因为父母婚姻有裂痕。\n琴仅仅是向往故事所描绘的两情相悦向往蛛丝般精妙又脆弱的情感。\n身为骑士凡事要以蒙德城和西风骑士团为最优先。\n但…\n「如果我自己也能…」\n深夜的办公室里琴再一次读完《少女薇拉的忧郁》。\n「要是有时间去黎明前的誓言岬看看也没关系。没关系吧…」\n琴低下头在洒满星辉的窗台边偷偷想着。"
},
{
"Title": "琴团长生活作息表·第十七版",
"Context": "这是一张精细到分钟的作息表。\n从清晨跟安柏一起晨跑并顺路为丽莎购置早饭到夜间亲手洗衣服并把换洗物件收纳整齐大小琐事均有条目可见时间安排得相当紧凑。\n每件做完的事情上都有勾号标记因外力延误的事项则备注了详细原因。\n在因身体不适未能按时完成的「蒙德城公共设施评审报告」条目后备注了「凌晨3时已完成取消本月末的书籍购置奖励。」\n琴有时想这张表格的灵感来源或许是十年前母亲为自己绘制的训练表。"
},
{
"Title": "神之眼",
"Context": "单论实力,琴早已是蒙德城数一数二的剑士。但在琴心中,比起成为一把能够刺穿腐朽与黑暗的利剑,她更愿意做守护歌声与自由的坚盾。\n「维护」向来比「破坏」更难。\n从小队队长晋升为副团长时出现在琴面前的就是巨大的挑战。外有愚人众施加的邦交压力内有叛徒——前任督察长的同党。想在如此形势下重振旗鼓开拓局面并非易事。\n但琴独自扛住了来自外界的压力引领一众骑士粉碎深渊教团的诸多阴谋重新为西风骑士团树立威望。\n琴永远不会忘记得到「神之眼」的那一刻。感受着掌间升起的微风顷刻间四周沉寂下来空间化作黑白唯有古恩希尔德家族古老而严厉的家训浮现在她脑海之中。\n「永护蒙德。」"
}
],
"talks": [
@@ -272,38 +304,6 @@
{ "Title": "加入队伍·其二", "Context": "已做好万全准备。" },
{ "Title": "加入队伍·其三", "Context": "听凭风引。" }
],
"stories": [
{
"Title": "角色详细",
"Context": "西风骑士团是守护蒙德的剑与盾。\n除了清扫荒野的魔物、守护城市与道路安全之外骑士团最重要的工作是维护蒙德的秩序。\n虽说蒙德是自由之都但无边际的散漫只会带来混沌与彷徨。\n琴非常明白这一道理所以才坚持严谨刻苦的作风以最严苛的要求对待自己。\n——然后一不留神就在月初消耗完了当月所有的咖啡配额。"
},
{
"Title": "角色故事1",
"Context": "古恩希尔德家族是古老的骑士家族。据说自第一篇史诗写成之日,就已开始守护蒙德。\n但历史悠久的血脉必然伴随着沉重负担。琴自幼便被母亲作为骑士道继承者培养。\n象征骑士风度的形体、礼仪锻造骑士灵魂的历史、诗歌武装骑士精神的剑艺、体能…琴全都要训练。唯有如此才能实践古恩希尔德的家训「永护蒙德」。\n早年的酒馆里有这样一句玩笑话古恩希尔德家族的长子长女在学会喊「妈妈」之前就已能够念出「永护蒙德」。\n从《林间风故事拔萃》抬起头看见同龄孩子举着风车欢笑着跑过窗前。小小的琴自然是明白其中含义的。\n而如今从累牍公文中抬起头看见蒙德的未来举着风车欢笑着跑过街道。代理团长大人对从前那些岁月依然毫无悔恨。\n「这是正确的事。无论多辛苦正确的事就应全力以赴。」"
},
{
"Title": "角色故事2",
"Context": "「琴团长十分可靠。」\n「若是遇到难题找她帮忙一定没错。」\n在蒙德骑士和民众都十分仰赖琴。\n如果有人需要帮助事情又合乎道理那她一定会出手相助。\n即使是街市上的小口角即使是恋爱的渺小烦恼…\n即使与骑士团的本职工作毫无关系只要有人对琴开口琴就会对他们伸出援手。\n「要问原因的话——帮助受困之人不正是骑士职责所在吗」\n对她而言骑士的职责先于「代理团长」的勤务。对她而言亲手帮助有需要的人最为实在。\n这也是她对麾下骑士的要求。\n图书管理员丽莎曾经劝琴放松「应该适当享受淑女的下午茶时光」。\n可对琴来说骑士的职责先于淑女的身份。\n「琴团长十分可靠。」\n人们一如既往地给予她这一评价。\n令她烦恼的是一天时间只有那么多。即使牺牲睡眠也无法帮助所有人。\n要成为可靠的人需要付出难以想象的努力。"
},
{
"Title": "角色故事3",
"Context": "琴代行团长职责的如今,不少人已经忘记在她之上仍有一位「大团长」。\n而她本人从未在意过这种事。骑士团内的地位与称号丝毫不会影响她行事的原则。\n热忱、正直与一丝不苟琴身上这些优良品质有两大来源。\n其一是自幼接受的教育和训练已将骑士道精神深深植入她灵魂深处。\n其二是现已缺席的西风骑士团团长法尔伽曾对她悉心教导。\n洒脱而散漫的北风骑士法尔伽他给琴的成长带来了巨大影响。\n「大团长请您认真对待工作直面蒙德对您的期待。」\n「小姑娘你是我的副手为我分担工作天经地义。这样大团长才有精力做更重要的事啊。」\n「……」\n他是征服与创造传奇的骑士而她是守护和平与自由的骑士。\n琴并不讨厌法尔伽。法尔伽的行事风格或许有其独到之处。琴需要完成的是那些大团长没完成的正确之事。\n半年前法尔伽率领西风骑士中的精锐再一次离开蒙德踏上远征之路。\n远征——这是很符合大团长风格的冒险。\n「骑士团就交给你了。反正这些年也是你在做团长的工作。」\n「放心交给我吧大团长。」\n站在窗前目送团长的她心想\n等你回来将会见到一座更加温暖、繁荣、平和的蒙德城。"
},
{
"Title": "角色故事4",
"Context": "风起地的神树,是初代「蒲公英骑士」道路的终点。\n据记载建立西风骑士团、重振蒙德的初代蒲公英骑士温妮莎曾在生命结束之刻来到此地。\n她永别了衷心守护的城邦留下传奇与一株树苗。\n这株树苗在千风的眷顾中在无数的日月照耀下成长为参天巨木。\n琴十五岁时获授「蒲公英骑士」之名。\n「蒲公英骑士」又名「狮牙骑士」随骑士团制度一同沿袭至今是最杰出的骑士才能获得的荣誉。\n那日授衔仪式结束后琴从庆祝活动中悄然离开追随着一直憧憬的英雄骑士的脚步来到那棵大树前。\n「蒲公英骑士」之名象征着温妮莎的抗争与仁爱。自己如何能拥有资格来继承如此伟大的称号\n蒙德城重建至今已逾千年。自己何德何能能守护这座古老、自由、骄傲的城邦\n隐藏在稳重成熟外表下的是一颗刚刚完成骑士礼尚未做好准备的少女之心。\n风从远方吹来将骑士拥入怀中轻柔地拂去她的疑虑与不安展露出黄金般的决心\n「永护蒙德。」\n——成为温妮莎那样温柔坚定的战士为同胞战斗为自由抗争。这便是那句简明严厉的家训中蕴含的心意吧。\n如今每当感到疲惫动摇琴仍会来到这棵树下接受风的洗礼。风洗净她的困顿令她重拾前行的力量。\n风起地的神树是初代「狮牙骑士」道路的终点。\n同时也是「蒲公英骑士」琴的起点。"
},
{
"Title": "角色故事5",
"Context": "琴团长有一个秘密。\n古恩希尔德家族是古老的骑士家族。这一骄傲的血脉来自琴的母亲芙蕾德莉卡。\n琴的父亲则是名贯大陆的冒险家——西蒙·佩奇。来到蒙德后他扫去身上风沙以崭新的姿态加入西风教会一路升为西风教会总管人称「拂晓的枢机卿」。\n曾经的爱侣最终分道扬镳。年幼的琴牵着母亲的手望着父亲与妹妹芭芭拉远去的背影。\n后来芭芭拉也与父亲一样加入西风教会成为了一位深受蒙德人喜爱的牧师。\n琴一直想要亲近这位血脉相通的妹妹但面对芭芭拉躲闪的眼神不知该如何开口。\n或许这份相似的笨拙就是姐妹心意仍然相通的表现吧。\n琴团长还有一个秘密。\n尽管熟读历史典籍尽管身戴「蒲公英骑士」之名尽管成为了深受信赖的代理团长…\n琴依然十分中意恋爱小说。\n并非因为在训练与勤务中虚度了少女年华也不是因为父母婚姻有裂痕。\n琴仅仅是向往故事所描绘的两情相悦向往蛛丝般精妙又脆弱的情感。\n身为骑士凡事要以蒙德城和西风骑士团为最优先。\n但…\n「如果我自己也能…」\n深夜的办公室里琴再一次读完《少女薇拉的忧郁》。\n「要是有时间去黎明前的誓言岬看看也没关系。没关系吧…」\n琴低下头在洒满星辉的窗台边偷偷想着。"
},
{
"Title": "琴团长生活作息表·第十七版",
"Context": "这是一张精细到分钟的作息表。\n从清晨跟安柏一起晨跑并顺路为丽莎购置早饭到夜间亲手洗衣服并把换洗物件收纳整齐大小琐事均有条目可见时间安排得相当紧凑。\n每件做完的事情上都有勾号标记因外力延误的事项则备注了详细原因。\n在因身体不适未能按时完成的「蒙德城公共设施评审报告」条目后备注了「凌晨3时已完成取消本月末的书籍购置奖励。」\n琴有时想这张表格的灵感来源或许是十年前母亲为自己绘制的训练表。"
},
{
"Title": "神之眼",
"Context": "单论实力,琴早已是蒙德城数一数二的剑士。但在琴心中,比起成为一把能够刺穿腐朽与黑暗的利剑,她更愿意做守护歌声与自由的坚盾。\n「维护」向来比「破坏」更难。\n从小队队长晋升为副团长时出现在琴面前的就是巨大的挑战。外有愚人众施加的邦交压力内有叛徒——前任督察长的同党。想在如此形势下重振旗鼓开拓局面并非易事。\n但琴独自扛住了来自外界的压力引领一众骑士粉碎深渊教团的诸多阴谋重新为西风骑士团树立威望。\n琴永远不会忘记得到「神之眼」的那一刻。感受着掌间升起的微风顷刻间四周沉寂下来空间化作黑白唯有古恩希尔德家族古老而严厉的家训浮现在她脑海之中。\n「永护蒙德。」"
}
]
"title": "蒲公英骑士",
"weapon": "单手剑"
}

View File

@@ -1,8 +1,4 @@
{
"id": 10000006,
"name": "丽莎",
"title": "蔷薇魔女",
"description": "慵懒而博学的图书管理员,须弥教令院「两百年一见」的天才毕业生。",
"area": "蒙德",
"brief": {
"camp": "西风骑士团",
@@ -10,9 +6,49 @@
"birth": "6月9日",
"cv": { "cn": "钟可", "jp": "田中理惠", "en": "玛拉·朱诺", "kr": "朴高夽" }
},
"star": 4,
"constellation": [
{
"Id": 41,
"Name": "无限的电回路",
"Description": "<color=#FFD780FF>苍雷</color>长按时每个命中的敌人都会为丽莎恢复2点元素能量。\n通过这种方式一次至多回复10点元素能量。",
"Icon": "UI_Talent_S_Lisa_01"
},
{
"Id": 42,
"Name": "空间电势结界",
"Description": "<color=#FFD780FF>苍雷</color>长按时,具有如下效果:\n·防御力提升25%\n·提高丽莎的抗打断能力。",
"Icon": "UI_Talent_S_Lisa_02"
},
{
"Id": 43,
"Name": "谐振的雷光",
"Description": "<color=#FFD780FF>蔷薇的雷光</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Lisa_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 44,
"Name": "如雨的电浆",
"Description": "<color=#FFD780FF>蔷薇的雷光</color>攻击时放出的闪电增加至1~3道。",
"Icon": "UI_Talent_S_Lisa_03"
},
{
"Id": 45,
"Name": "等离态的落雷",
"Description": "<color=#FFD780FF>苍雷</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Lisa_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 46,
"Name": "脉冲的魔女",
"Description": "丽莎登场时对附近的敌人施加3层<color=#FFD780FF>苍雷</color>的引雷效果。\n该效果每5秒只能触发一次。",
"Icon": "UI_Talent_S_Lisa_04"
}
],
"description": "慵懒而博学的图书管理员,须弥教令院「两百年一见」的天才毕业生。",
"element": "雷",
"weapon": "法器",
"id": 10000006,
"materials": [
{ "id": 104144, "name": "最胜紫晶", "star": 5 },
{ "id": 113002, "name": "雷光棱镜", "star": 4 },
@@ -21,6 +57,7 @@
{ "id": 104309, "name": "「诗文」的哲学", "star": 4 },
{ "id": 113004, "name": "东风之爪", "star": 5 }
],
"name": "丽莎",
"skills": [
{
"GroupId": 431,
@@ -65,44 +102,39 @@
"Icon": "UI_Talent_Combine_Potion"
}
],
"constellation": [
"star": 4,
"stories": [
{
"Id": 41,
"Name": "无限的电回路",
"Description": "<color=#FFD780FF>苍雷</color>长按时每个命中的敌人都会为丽莎恢复2点元素能量。\n通过这种方式一次至多回复10点元素能量。",
"Icon": "UI_Talent_S_Lisa_01"
"Title": "角色详细",
"Context": "西风骑士团的图书管理员,胸怀万卷的优雅淑女。\n据说她是须弥教令院两百年一遇的天才魔女。\n虽然不知道具体理由但在须弥国进修两年之后她最终回到了蒙德。\n目前丽莎在西风骑士团工作职位是管理骑士团藏书的图书管理员。"
},
{
"Id": 42,
"Name": "空间电势结界",
"Description": "<color=#FFD780FF>苍雷</color>长按时,具有如下效果:\n·防御力提升25%\n·提高丽莎的抗打断能力。",
"Icon": "UI_Talent_S_Lisa_02"
"Title": "角色故事1",
"Context": "丽莎的职责,一是整理图书馆中大量藏书,二是保障骑士团的药剂供应。\n蒙德人遇见丽莎的场合不是去骑士团总部借阅图书就是去骑士团总部归还图书。\n而丽莎往往会懒洋洋地坐在接待台打着哈欠帮来客进行登记。\n有时她的工作状态会让人产生「图书管理员这样子真的没问题吗…」的疑问。\n但丽莎的工作总是完美无缺挑不出一点纰漏。"
},
{
"Id": 43,
"Name": "谐振的雷光",
"Description": "<color=#FFD780FF>蔷薇的雷光</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Lisa_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
"Title": "角色故事2",
"Context": "作为须弥教令院学者居勒什口中「两百年一见」的高材生,丽莎的博学毋须多言。\n无论是荒野妖魔的禁忌知识还是元素花药的处理方法抑或是只用两层瓷碗便能减少一次蒸馏过程的酿酒方法…\n丽莎都能简明易懂地解释个中原理以至于年轻骑士与炼金术士中形成了「丽莎女士一定知道」的共识。\n当然前提是你要在合适的时间去拜访她。\n如果不小心在上午的回笼觉时间或者午饭后的茶点时间打扰丽莎后果可是不堪设想的。"
},
{
"Id": 44,
"Name": "如雨的电浆",
"Description": "<color=#FFD780FF>蔷薇的雷光</color>攻击时放出的闪电增加至1~3道。",
"Icon": "UI_Talent_S_Lisa_03"
"Title": "角色故事3",
"Context": "初见丽莎的人往往会对她形成「不愧是教令院天才毕业生」的第一印象。\n但实际上她在生活态度上与其说高效不如说单纯是因为怕麻烦而倾向于能懒则懒。\n药剂的调配和补充用度的工作通过凯亚丢给了霍夫曼和斯万每天的药草则是让芙萝拉差唐娜送来。\n唯独书籍跟档案的整理丽莎是必须亲力亲为的。\n只有亲自管理这些知识丽莎才会安心。"
},
{
"Id": 45,
"Name": "等离态的落雷",
"Description": "<color=#FFD780FF>苍雷</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Lisa_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
"Title": "角色故事4",
"Context": "丽莎进入西风骑士团时日尚短的时候,曾被团长指派接任第八小队队长一职。\n这件事引来了小队佐官尼姆芙的强烈不满她并不认可丽莎这么一个「学院派」来担此高位。\n经时任庶务长凯亚批准丽莎与尼姆芙进行了魔法方面的「实战练习」。\n练习只持续了两分钟随后丽莎婉拒了第八小队队长一职理由是「尼姆芙佐官的能力堪当此任。」\n随后的一年里不断有来自尼姆芙的队长推荐书放到团长办公桌上意向栏里只有一个名字丽莎·敏兹。\n这些推荐书最终转交到丽莎手里丽莎每次都只能现编理由来推脱。\n她的加入固然能让第八小队变强但这种强大不仅没有必要更会增添常人无法理解的风险。\n丽莎有控制大多数局面的自信可额外的危险意味着额外的工作这一点上她绝对无法接受。"
},
{
"Id": 46,
"Name": "脉冲的魔女",
"Description": "丽莎登场时对附近的敌人施加3层<color=#FFD780FF>苍雷</color>的引雷效果。\n该效果每5秒只能触发一次。",
"Icon": "UI_Talent_S_Lisa_04"
"Title": "角色故事5",
"Context": "亲眼目睹须弥雨林中呢喃狂言的学者与评议会中如有大智慧的智者之后,丽莎深深理解了渊海般的「学识」会给人烙下怎样的痕迹。\n代价如此沉重…到底要承担多少才能从灵魂深处掘出这样的知识呢\n丽莎对这一切心生反感离开了须弥。\n从那以后丽莎不再对任何事物展露出完全认真的态度。\n「对神给予的奇迹要求太多就要好好想想——神开出的价码自己到底付不付得起。」\n丽莎回到蒙德后仅对三名她认为有必要听的人作此告诫。"
},
{
"Title": "特制加热釜",
"Context": "特制加热釜,一种用蒙德人无法理解的设计打造的定制品。同时具备延时加热、辅料半自动投放和有限的保温功能。\n这是丽莎动用了大笔预算和炼金工坊两周的工作时间造出来的「特种设备」。\n用它的话一切精确的加热操作都可以在扳动不超过两次把手的情况下完成。\n然而它最常见的使用场合是用于在丽莎整理书籍的时候把茶泡好并维持在最佳的口感。\n毕竟丽莎一天中最重要的日程是悠闲愉快的下午茶时间啊。"
},
{
"Title": "神之眼",
"Context": "「神之眼」——神选者、改变世间之人的证明。\n抑或是在挖掘魔导奥秘的前路上一个顺理成章的小小注脚。\n为了探索魔导必须要了解元素。而比起从旧书堆中获取知识实战是更好的方式。\n啊看来需要一颗「神之眼」呢。\n想到这件事的刹那「神之眼」就这么出现在了丽莎手中。\n取得「神之眼」的丽莎获得了她想要的知识却也察觉了知识中暗藏的奥秘。\n神出于某些原因给予人改变一切的钥匙却未说明其所需要的代价。这让丽莎对「真相」心生恐惧。\n挂在脖子上的「神之眼」成了悬在丽莎心头散发甜蜜香气的万丈深渊。\n所以偶尔地丽莎会向那些她认为有意思的人传授她对各种事情的见解。\n或许丽莎一直暗中期待着有能力探明「神之眼」背后真相的人将因此出现在她面前。"
}
],
"talks": [
@@ -274,38 +306,6 @@
{ "Title": "加入队伍·其二", "Context": "我会看着你的。" },
{ "Title": "加入队伍·其三", "Context": "要出门了吗?" }
],
"stories": [
{
"Title": "角色详细",
"Context": "西风骑士团的图书管理员,胸怀万卷的优雅淑女。\n据说她是须弥教令院两百年一遇的天才魔女。\n虽然不知道具体理由但在须弥国进修两年之后她最终回到了蒙德。\n目前丽莎在西风骑士团工作职位是管理骑士团藏书的图书管理员。"
},
{
"Title": "角色故事1",
"Context": "丽莎的职责,一是整理图书馆中大量藏书,二是保障骑士团的药剂供应。\n蒙德人遇见丽莎的场合不是去骑士团总部借阅图书就是去骑士团总部归还图书。\n而丽莎往往会懒洋洋地坐在接待台打着哈欠帮来客进行登记。\n有时她的工作状态会让人产生「图书管理员这样子真的没问题吗…」的疑问。\n但丽莎的工作总是完美无缺挑不出一点纰漏。"
},
{
"Title": "角色故事2",
"Context": "作为须弥教令院学者居勒什口中「两百年一见」的高材生,丽莎的博学毋须多言。\n无论是荒野妖魔的禁忌知识还是元素花药的处理方法抑或是只用两层瓷碗便能减少一次蒸馏过程的酿酒方法…\n丽莎都能简明易懂地解释个中原理以至于年轻骑士与炼金术士中形成了「丽莎女士一定知道」的共识。\n当然前提是你要在合适的时间去拜访她。\n如果不小心在上午的回笼觉时间或者午饭后的茶点时间打扰丽莎后果可是不堪设想的。"
},
{
"Title": "角色故事3",
"Context": "初见丽莎的人往往会对她形成「不愧是教令院天才毕业生」的第一印象。\n但实际上她在生活态度上与其说高效不如说单纯是因为怕麻烦而倾向于能懒则懒。\n药剂的调配和补充用度的工作通过凯亚丢给了霍夫曼和斯万每天的药草则是让芙萝拉差唐娜送来。\n唯独书籍跟档案的整理丽莎是必须亲力亲为的。\n只有亲自管理这些知识丽莎才会安心。"
},
{
"Title": "角色故事4",
"Context": "丽莎进入西风骑士团时日尚短的时候,曾被团长指派接任第八小队队长一职。\n这件事引来了小队佐官尼姆芙的强烈不满她并不认可丽莎这么一个「学院派」来担此高位。\n经时任庶务长凯亚批准丽莎与尼姆芙进行了魔法方面的「实战练习」。\n练习只持续了两分钟随后丽莎婉拒了第八小队队长一职理由是「尼姆芙佐官的能力堪当此任。」\n随后的一年里不断有来自尼姆芙的队长推荐书放到团长办公桌上意向栏里只有一个名字丽莎·敏兹。\n这些推荐书最终转交到丽莎手里丽莎每次都只能现编理由来推脱。\n她的加入固然能让第八小队变强但这种强大不仅没有必要更会增添常人无法理解的风险。\n丽莎有控制大多数局面的自信可额外的危险意味着额外的工作这一点上她绝对无法接受。"
},
{
"Title": "角色故事5",
"Context": "亲眼目睹须弥雨林中呢喃狂言的学者与评议会中如有大智慧的智者之后,丽莎深深理解了渊海般的「学识」会给人烙下怎样的痕迹。\n代价如此沉重…到底要承担多少才能从灵魂深处掘出这样的知识呢\n丽莎对这一切心生反感离开了须弥。\n从那以后丽莎不再对任何事物展露出完全认真的态度。\n「对神给予的奇迹要求太多就要好好想想——神开出的价码自己到底付不付得起。」\n丽莎回到蒙德后仅对三名她认为有必要听的人作此告诫。"
},
{
"Title": "特制加热釜",
"Context": "特制加热釜,一种用蒙德人无法理解的设计打造的定制品。同时具备延时加热、辅料半自动投放和有限的保温功能。\n这是丽莎动用了大笔预算和炼金工坊两周的工作时间造出来的「特种设备」。\n用它的话一切精确的加热操作都可以在扳动不超过两次把手的情况下完成。\n然而它最常见的使用场合是用于在丽莎整理书籍的时候把茶泡好并维持在最佳的口感。\n毕竟丽莎一天中最重要的日程是悠闲愉快的下午茶时间啊。"
},
{
"Title": "神之眼",
"Context": "「神之眼」——神选者、改变世间之人的证明。\n抑或是在挖掘魔导奥秘的前路上一个顺理成章的小小注脚。\n为了探索魔导必须要了解元素。而比起从旧书堆中获取知识实战是更好的方式。\n啊看来需要一颗「神之眼」呢。\n想到这件事的刹那「神之眼」就这么出现在了丽莎手中。\n取得「神之眼」的丽莎获得了她想要的知识却也察觉了知识中暗藏的奥秘。\n神出于某些原因给予人改变一切的钥匙却未说明其所需要的代价。这让丽莎对「真相」心生恐惧。\n挂在脖子上的「神之眼」成了悬在丽莎心头散发甜蜜香气的万丈深渊。\n所以偶尔地丽莎会向那些她认为有意思的人传授她对各种事情的见解。\n或许丽莎一直暗中期待着有能力探明「神之眼」背后真相的人将因此出现在她面前。"
}
]
"title": "蔷薇魔女",
"weapon": "法器"
}

View File

@@ -1,8 +1,4 @@
{
"id": 10000014,
"name": "芭芭拉",
"title": "闪耀偶像",
"description": "蒙德城的大家都喜欢芭芭拉。「偶像」这个词是她从一本杂志里看到的。",
"area": "蒙德",
"brief": {
"camp": "西风教会",
@@ -10,9 +6,49 @@
"birth": "7月5日",
"cv": { "cn": "宋媛媛", "jp": "鬼头明里", "en": "劳拉·斯特尔", "kr": "尹娥煐" }
},
"star": 4,
"constellation": [
{
"Id": 141,
"Name": "彩色歌谣",
"Description": "芭芭拉每10秒恢复1点元素能量。",
"Icon": "UI_Talent_S_Barbara_01"
},
{
"Id": 142,
"Name": "元气迸发",
"Description": "<color=#FFD780FF>演唱,开始♪</color>的冷却时间降低15%\n技能持续期间当前场上自己的角色获得15%<color=#80C0FFFF>水元素伤害加成</color>。",
"Icon": "UI_Talent_S_Barbara_02"
},
{
"Id": 143,
"Name": "明日之星",
"Description": "<color=#FFD780FF>闪耀奇迹♪</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Barbara_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 144,
"Name": "努力即魔法",
"Description": "芭芭拉使用重击时每命中一个敌人就恢复1点元素能量。\n通过这种方式一次至多回复5点元素能量。",
"Icon": "UI_Talent_S_Barbara_03"
},
{
"Id": 145,
"Name": "纯真的羁绊",
"Description": "<color=#FFD780FF>演唱,开始♪</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Barbara_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 146,
"Name": "将一切美好献给你",
"Description": "芭芭拉处于队伍后台时,队伍中自己的角色倒下时,则立即:\n·复苏该角色\n·将该角色生命值恢复至100%。\n该效果每15分钟只能触发一次。",
"Icon": "UI_Talent_S_Barbara_04"
}
],
"description": "蒙德城的大家都喜欢芭芭拉。「偶像」这个词是她从一本杂志里看到的。",
"element": "水",
"weapon": "法器",
"id": 10000014,
"materials": [
{ "id": 104124, "name": "涤净青金", "star": 5 },
{ "id": 113012, "name": "净水之心", "star": 4 },
@@ -21,6 +57,7 @@
{ "id": 104303, "name": "「自由」的哲学", "star": 4 },
{ "id": 113007, "name": "北风之环", "star": 5 }
],
"name": "芭芭拉",
"skills": [
{
"GroupId": 1431,
@@ -65,44 +102,39 @@
"Icon": "UI_Talent_Cook_Heal"
}
],
"constellation": [
"star": 4,
"stories": [
{
"Id": 141,
"Name": "彩色歌谣",
"Description": "芭芭拉每10秒恢复1点元素能量。",
"Icon": "UI_Talent_S_Barbara_01"
"Title": "角色详细",
"Context": "芭芭拉是西风教会的祈礼牧师,同时也是蒙德城的闪耀偶像。\n「只要看到芭芭拉心情就能变好。」——蒙德城里流传着这样的说法。\n实际上不只是心情伤口或者身体不适也能一起治愈。\n人们知道芭芭拉可以通过水元素的「神之眼」释放奇妙的魔法。\n但在芭芭拉心中最神奇的魔法只有「切实的努力」而已。"
},
{
"Id": 142,
"Name": "元气迸发",
"Description": "<color=#FFD780FF>演唱,开始♪</color>的冷却时间降低15%\n技能持续期间当前场上自己的角色获得15%<color=#80C0FFFF>水元素伤害加成</color>。",
"Icon": "UI_Talent_S_Barbara_02"
"Title": "角色故事1",
"Context": "蒙德城里,人人都喜欢芭芭拉。\n其实在最开始芭芭拉的歌声对于蒙德居民来说十分陌生甚至让人有些不习惯。\n因为蒙德一直以来流传的歌大部分都是吟游诗人弹唱的民谣。\n好在蒙德有着「自由」的精神令人愉快的新事物可以和喜闻乐见的「传统」一起繁荣。\n人们接受了她的歌声逐渐被她的活力感染甚至也开始学着哼唱。\n「艾伯特先生不要再唱啦你唱的真的没调子」"
},
{
"Id": 143,
"Name": "明日之星",
"Description": "<color=#FFD780FF>闪耀奇迹♪</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Barbara_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
"Title": "角色故事2",
"Context": "可是对于这样的成果,芭芭拉心情复杂。\n偶像的职责就是让大家喜爱自己。这么来看的话芭芭拉做得很好。\n她的选择并没有错。\n——但是另一方面偶像需要去抚慰大家心灵的疲劳她真的做到了吗\n她为盲眼的葛罗丽唱了歌安慰她远行的恋人一定会归来她为生病的安娜唱了歌祝福她疾病一定会被治愈。\n但在歌声结束后她们脸上的笑容并未停留太久。\n芭芭拉陷入了迷茫之中。"
},
{
"Id": 144,
"Name": "努力即魔法",
"Description": "芭芭拉使用重击时每命中一个敌人就恢复1点元素能量。\n通过这种方式一次至多回复5点元素能量。",
"Icon": "UI_Talent_S_Barbara_03"
"Title": "角色故事3",
"Context": "芭芭拉从小就是个性格阳光的孩子。尽管她有些笨拙,做事常常失败,也总能飞快振作起来,再试一次。\n与芭芭拉截然相反的「那个人」便是她的姐姐众人口中的「家族骄傲」。\n仿佛是对着字典里「优秀」的解释一般成长起来的姐姐完全遥不可及。\n芭芭拉最初努力的动机并没有多么伟大仅仅是想要赢过姐姐一次。\n可是无论剑术还是学习她都比不上姐姐。\n一向阳光向上的芭芭拉对此不免有些失落。\n「努力是最神奇的魔法但在努力也没有用的时候应该怎么办呢」"
},
{
"Id": 145,
"Name": "纯真的羁绊",
"Description": "<color=#FFD780FF>演唱,开始♪</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Barbara_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
"Title": "角色故事4",
"Context": "芭芭拉没有想过放弃。\n不如说她的韧性令她的父亲「拂晓的枢机卿」西蒙都感到吃惊。\n芭芭拉给自己制定的「失落时间」只有三十秒。\n三十秒过后无论如何都要重新打起精神来。\n「既然不擅长战斗那我就负责后勤吧」\n在父亲的引导下芭芭拉成为了一位治疗者。\n伤员和病患是如此痛苦芭芭拉的善良又是如此闪耀。\n「想获得他人认可」的渴望不知不觉地变成了「想帮助他人」的简单信念。"
},
{
"Id": 146,
"Name": "将一切美好献给你",
"Description": "芭芭拉处于队伍后台时,队伍中自己的角色倒下时,则立即:\n·复苏该角色\n·将该角色生命值恢复至100%。\n该效果每15分钟只能触发一次。",
"Icon": "UI_Talent_S_Barbara_04"
"Title": "角色故事5",
"Context": "「谢谢。」这是芭芭拉听到最多的话。\n感到迷茫时有人握住了她的手。\n「你的陪伴让我感觉好多了。」\n对芭芭拉而言大家脸上重新洋溢出的笑容就是她最好的奖章。\n所以在夜里按摩着肿疼的小腿或者咕嘟咕嘟喝着护嗓子的杏白茶时芭芭拉都会想起每一个向她投以善意的人。\n「我也是在大家的鼓励下前进的」\n而且那个笑容说不定正是健康的证明——歌声也许真的能够将人们治愈。\n至于那份想要超越姐姐成为全蒙德的人气第一的小小好胜心并没有被芭芭拉摒弃而是被她藏进了最深的心底。\n「如果能够变得更加优秀也许就能为姐姐分担一点责任了吧。」\n她是这样想的。\n「唔唔…芭芭拉冲呀」"
},
{
"Title": "艾莉丝的偶像杂志",
"Context": "「偶…像?」\n最初芭芭拉听到这个陌生词汇的时候疑惑写满了她的整张脸。\n「人们应该崇拜的不是世间的七神吗」\n「不止哟。」魔女会元老成员之一阅人万千的艾莉丝蛊惑般地说「看了这个你就明白了。」\n总之从这本不知道是哪个世界带回来的「偶像杂志」中芭芭拉知道了有那么一种职业工作的内容就是努力被大家喜爱。\n而优秀的偶像不仅收获欢欣更能够通过歌喉和舞步治愈人们的心灵。\n芭芭拉一遍遍踩着拍子练习着新曲子。她终于在人们的笑容里找到了属于自己的喜悦。\n后来有一天艾莉丝一脸愁容地告诉芭芭拉「提瓦特偶像团」计划似乎流产的时候芭芭拉的演出已经在蒙德城小有名气了。\n「唔…既然只剩下我了偶像的意义…就由我来发扬吧」\n怀着小小的野心芭芭拉今天也在偷偷练习着新的曲子。"
},
{
"Title": "神之眼",
"Context": "芭芭拉获得「神之眼」时,并没在做什么伟大的事。\n当时她刚刚进入教会不久正在照顾一个高烧不退的孩子。\n无论芭芭拉怎么哄孩子都哭闹不休。\n有人说他已经服下药剂可对亲人的想念无从缓解。\n又有人说唱歌给孩子听吧这样他就会好起来。\n到那时为止芭芭拉从未唱过歌。不过她不会在这种时刻退缩。\n就算从来没有唱过歌她也无法丢下需要照顾的孩子。\n芭芭拉抱着高烧不退的孩子唱起了自己记得的唯一一首摇篮曲。\n最开始她唱得很笨拙连词都忘记了只能轻轻哼唱出旋律。\n孩子平静了一些于是她继续唱着这首歌不知唱了多少遍嗓子都变得干哑。直到怀里的孩子安然睡去疲惫的芭芭拉才靠着墙睡着了。\n翌日清晨她醒来发觉孩子的烧已经退了。也许是因为她的歌声也许是因为不知何时落在她手边的「神之眼」。\n但芭芭拉并没有把注意力放在这件事上。看着孩子的笑靥她真心地感到快乐。\n「用歌声治愈所有人」——芭芭拉的神之眼就诞生于这样单纯而温柔的梦想。"
}
],
"talks": [
@@ -259,38 +291,6 @@
{ "Title": "加入队伍·其二", "Context": "我会加油的!" },
{ "Title": "加入队伍·其三", "Context": "一起努力吧!" }
],
"stories": [
{
"Title": "角色详细",
"Context": "芭芭拉是西风教会的祈礼牧师,同时也是蒙德城的闪耀偶像。\n「只要看到芭芭拉心情就能变好。」——蒙德城里流传着这样的说法。\n实际上不只是心情伤口或者身体不适也能一起治愈。\n人们知道芭芭拉可以通过水元素的「神之眼」释放奇妙的魔法。\n但在芭芭拉心中最神奇的魔法只有「切实的努力」而已。"
},
{
"Title": "角色故事1",
"Context": "蒙德城里,人人都喜欢芭芭拉。\n其实在最开始芭芭拉的歌声对于蒙德居民来说十分陌生甚至让人有些不习惯。\n因为蒙德一直以来流传的歌大部分都是吟游诗人弹唱的民谣。\n好在蒙德有着「自由」的精神令人愉快的新事物可以和喜闻乐见的「传统」一起繁荣。\n人们接受了她的歌声逐渐被她的活力感染甚至也开始学着哼唱。\n「艾伯特先生不要再唱啦你唱的真的没调子」"
},
{
"Title": "角色故事2",
"Context": "可是对于这样的成果,芭芭拉心情复杂。\n偶像的职责就是让大家喜爱自己。这么来看的话芭芭拉做得很好。\n她的选择并没有错。\n——但是另一方面偶像需要去抚慰大家心灵的疲劳她真的做到了吗\n她为盲眼的葛罗丽唱了歌安慰她远行的恋人一定会归来她为生病的安娜唱了歌祝福她疾病一定会被治愈。\n但在歌声结束后她们脸上的笑容并未停留太久。\n芭芭拉陷入了迷茫之中。"
},
{
"Title": "角色故事3",
"Context": "芭芭拉从小就是个性格阳光的孩子。尽管她有些笨拙,做事常常失败,也总能飞快振作起来,再试一次。\n与芭芭拉截然相反的「那个人」便是她的姐姐众人口中的「家族骄傲」。\n仿佛是对着字典里「优秀」的解释一般成长起来的姐姐完全遥不可及。\n芭芭拉最初努力的动机并没有多么伟大仅仅是想要赢过姐姐一次。\n可是无论剑术还是学习她都比不上姐姐。\n一向阳光向上的芭芭拉对此不免有些失落。\n「努力是最神奇的魔法但在努力也没有用的时候应该怎么办呢」"
},
{
"Title": "角色故事4",
"Context": "芭芭拉没有想过放弃。\n不如说她的韧性令她的父亲「拂晓的枢机卿」西蒙都感到吃惊。\n芭芭拉给自己制定的「失落时间」只有三十秒。\n三十秒过后无论如何都要重新打起精神来。\n「既然不擅长战斗那我就负责后勤吧」\n在父亲的引导下芭芭拉成为了一位治疗者。\n伤员和病患是如此痛苦芭芭拉的善良又是如此闪耀。\n「想获得他人认可」的渴望不知不觉地变成了「想帮助他人」的简单信念。"
},
{
"Title": "角色故事5",
"Context": "「谢谢。」这是芭芭拉听到最多的话。\n感到迷茫时有人握住了她的手。\n「你的陪伴让我感觉好多了。」\n对芭芭拉而言大家脸上重新洋溢出的笑容就是她最好的奖章。\n所以在夜里按摩着肿疼的小腿或者咕嘟咕嘟喝着护嗓子的杏白茶时芭芭拉都会想起每一个向她投以善意的人。\n「我也是在大家的鼓励下前进的」\n而且那个笑容说不定正是健康的证明——歌声也许真的能够将人们治愈。\n至于那份想要超越姐姐成为全蒙德的人气第一的小小好胜心并没有被芭芭拉摒弃而是被她藏进了最深的心底。\n「如果能够变得更加优秀也许就能为姐姐分担一点责任了吧。」\n她是这样想的。\n「唔唔…芭芭拉冲呀」"
},
{
"Title": "艾莉丝的偶像杂志",
"Context": "「偶…像?」\n最初芭芭拉听到这个陌生词汇的时候疑惑写满了她的整张脸。\n「人们应该崇拜的不是世间的七神吗」\n「不止哟。」魔女会元老成员之一阅人万千的艾莉丝蛊惑般地说「看了这个你就明白了。」\n总之从这本不知道是哪个世界带回来的「偶像杂志」中芭芭拉知道了有那么一种职业工作的内容就是努力被大家喜爱。\n而优秀的偶像不仅收获欢欣更能够通过歌喉和舞步治愈人们的心灵。\n芭芭拉一遍遍踩着拍子练习着新曲子。她终于在人们的笑容里找到了属于自己的喜悦。\n后来有一天艾莉丝一脸愁容地告诉芭芭拉「提瓦特偶像团」计划似乎流产的时候芭芭拉的演出已经在蒙德城小有名气了。\n「唔…既然只剩下我了偶像的意义…就由我来发扬吧」\n怀着小小的野心芭芭拉今天也在偷偷练习着新的曲子。"
},
{
"Title": "神之眼",
"Context": "芭芭拉获得「神之眼」时,并没在做什么伟大的事。\n当时她刚刚进入教会不久正在照顾一个高烧不退的孩子。\n无论芭芭拉怎么哄孩子都哭闹不休。\n有人说他已经服下药剂可对亲人的想念无从缓解。\n又有人说唱歌给孩子听吧这样他就会好起来。\n到那时为止芭芭拉从未唱过歌。不过她不会在这种时刻退缩。\n就算从来没有唱过歌她也无法丢下需要照顾的孩子。\n芭芭拉抱着高烧不退的孩子唱起了自己记得的唯一一首摇篮曲。\n最开始她唱得很笨拙连词都忘记了只能轻轻哼唱出旋律。\n孩子平静了一些于是她继续唱着这首歌不知唱了多少遍嗓子都变得干哑。直到怀里的孩子安然睡去疲惫的芭芭拉才靠着墙睡着了。\n翌日清晨她醒来发觉孩子的烧已经退了。也许是因为她的歌声也许是因为不知何时落在她手边的「神之眼」。\n但芭芭拉并没有把注意力放在这件事上。看着孩子的笑靥她真心地感到快乐。\n「用歌声治愈所有人」——芭芭拉的神之眼就诞生于这样单纯而温柔的梦想。"
}
]
"title": "闪耀偶像",
"weapon": "法器"
}

View File

@@ -1,8 +1,4 @@
{
"id": 10000015,
"name": "凯亚",
"title": "寒风剑士",
"description": "异国面容的剑斗士,西风骑士团的头脑派人物。",
"area": "蒙德",
"brief": {
"camp": "西风骑士团",
@@ -10,9 +6,49 @@
"birth": "11月30日",
"cv": { "cn": "孙晔", "jp": "鸟海浩辅", "en": "乔西·蒙塔纳·麦科伊", "kr": "郑周元" }
},
"star": 4,
"constellation": [
{
"Id": 151,
"Name": "卓越的血脉",
"Description": "对受到<color=#99FFFFFF>冰元素</color>影响的敌人凯亚的普通攻击与重击暴击率提升15%。",
"Icon": "UI_Talent_S_Kaeya_01"
},
{
"Id": 152,
"Name": "无尽的霜舞",
"Description": "在<color=#FFD780FF>凛冽轮舞</color>的持续时间内击败敌人时持续时间延长2.5秒最多不会超过15秒。",
"Icon": "UI_Talent_S_Kaeya_02"
},
{
"Id": 153,
"Name": "凛冽的冰戏",
"Description": "<color=#FFD780FF>霜袭</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Kaeya_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 154,
"Name": "极寒的轻吻",
"Description": "凯亚的生命值低于20%时自动触发:\n生成一个伤害吸收量等于生命值上限30%的护盾持续20秒。\n该护盾对<color=#99FFFFFF>冰元素伤害</color>有250%的吸收效果。\n该效果每60秒只能触发一次。",
"Icon": "UI_Talent_S_Kaeya_03"
},
{
"Id": 155,
"Name": "至冷的拥抱",
"Description": "<color=#FFD780FF>凛冽轮舞</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Kaeya_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 156,
"Name": "轮旋的冰凌",
"Description": "<color=#FFD780FF>凛冽轮舞</color>会产生一个额外的寒冰之棱并在施放时返还15点元素能量。",
"Icon": "UI_Talent_S_Kaeya_04"
}
],
"description": "异国面容的剑斗士,西风骑士团的头脑派人物。",
"element": "冰",
"weapon": "单手剑",
"id": 10000015,
"materials": [
{ "id": 104164, "name": "哀叙冰玉", "star": 5 },
{ "id": 113010, "name": "极寒之核", "star": 4 },
@@ -21,6 +57,7 @@
{ "id": 104309, "name": "「诗文」的哲学", "star": 4 },
{ "id": 113008, "name": "北风的魂匣", "star": 5 }
],
"name": "凯亚",
"skills": [
{
"GroupId": 1531,
@@ -65,44 +102,39 @@
"Icon": "UI_Talent_Explosion_Sprint"
}
],
"constellation": [
"star": 4,
"stories": [
{
"Id": 151,
"Name": "卓越的血脉",
"Description": "对受到<color=#99FFFFFF>冰元素</color>影响的敌人凯亚的普通攻击与重击暴击率提升15%。",
"Icon": "UI_Talent_S_Kaeya_01"
"Title": "角色详细",
"Context": "凯亚·亚尔伯里奇,酒业大亨「莱艮芬德」家的义子。\n他已很久没有称呼迪卢克·莱艮芬德为「义兄」了。\n目前他担任西风骑士团的骑兵队长是一位可靠的行动派、深得琴信任的人物。\n在蒙德这座城邦的范围之内但凡发生了什么突发事件凯亚总会是那个收尾善后的人。"
},
{
"Id": 152,
"Name": "无尽的霜舞",
"Description": "在<color=#FFD780FF>凛冽轮舞</color>的持续时间内击败敌人时持续时间延长2.5秒最多不会超过15秒。",
"Icon": "UI_Talent_S_Kaeya_02"
"Title": "角色故事1",
"Context": "说来有趣,最容易遇到这位骑兵队长的地方不是骑士团总部,而是夜色中的酒馆。\n凯亚时常独自坐在吧台前掂着蒙德著名的调制酒「午后之死」与好酒的蒙德市民们攀谈。\n他在蒙德的酒客与年长者中格外受欢迎甚至有着「最值得托付外孙女的男人」之名。\n一边戏谑调侃一边慢品美酒如此亲切的男性让人很难把他跟西风骑士团骑兵队长一职联系起来。\n与凯亚插科打诨的人中既有醺醉的猎人也有嗜酒的贼徒。但无论来客警惕心多强都会在凯亚巧妙的诱导下吐露真言。\n至于之后的事到底是一场脱离控制的噩梦还是漫漫长夜中又一个无伤大雅的玩笑恐怕要取决于这位酒友不小心说漏嘴的内容了。\n「每个人都有自己的秘密但不是每个人都懂得该怎么对待它。」\n带着令人有些恼火的微笑凯亚这样说道。"
},
{
"Id": 153,
"Name": "凛冽的冰戏",
"Description": "<color=#FFD780FF>霜袭</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Kaeya_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
"Title": "角色故事2",
"Context": "「正义并非绝对的原则,而是武力与计谋的精妙平衡所铸就的结果。至于过程如何…无需太为难自己。」\n在大团长法尔伽面前凯亚曾说过这样的话。\n只要结果合乎期待凯亚并不在乎事情以什么方式结束。\n这种原则缔造了他不拘一格的行事方式以及随意而为的人生态度。\n正如「午后之死」那随性肆意的暴烈口感一般。\n不过肆意的行事风格也为他招来许多非议。\n曾有一次为逼盗贼首领与他正面作战凯亚故意触发远古的遗迹守卫。这一举动精准封锁住对手退路同时也让凯亚和同僚陷入危险。\n每当这种时刻一向信任他的代理团长琴也会摇头。\n但凯亚毫不在意。不如说他人被迫作出选择的窘迫模样甚至能让他觉得享受——\n他乐于捕捉同伴选择共战时一闪而过的犹豫也乐于欣赏敌人背水一战前竭力遮掩的恐惧。"
},
{
"Id": 154,
"Name": "极寒的轻吻",
"Description": "凯亚的生命值低于20%时自动触发:\n生成一个伤害吸收量等于生命值上限30%的护盾持续20秒。\n该护盾对<color=#99FFFFFF>冰元素伤害</color>有250%的吸收效果。\n该效果每60秒只能触发一次。",
"Icon": "UI_Talent_S_Kaeya_03"
"Title": "角色故事3",
"Context": "历史悠久的酿酒业为蒙德带来财富,蒙德的富庶又引来贪婪的盗贼与寻巢的魔物。\n这些游荡在阴影中的祸患不仅来源繁杂聚集的理由也多种多样。\n为了抵御侵扰蒙德的盗匪与魔物凯亚不只以他的剑也以他的头脑和幽默感制服敌人。\n有年轻骑士花费数年时间总结其中的规律最终得出一个令他目瞪口呆的结论——\n如果名酒「午后之死」不在供应期城内外的袭扰报告便会随之骤减直到…直到下一轮新酒上市\n忐忑的年轻骑士将报告交给擅长情报工作的骑兵队长凯亚希望从他那里得到一些建议。\n面对骑士忐忑的表情凯亚带着怪异的微笑回应「想法不错我会纳入参考。」"
},
{
"Id": 155,
"Name": "至冷的拥抱",
"Description": "<color=#FFD780FF>凛冽轮舞</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Kaeya_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
"Title": "角色故事4",
"Context": "一般而言,凯亚是个相当健谈的人。唯独对过去的经历,他讳莫如深。\n哪怕大团长要求他详述出身他也不愿细谈旧事而是用圆滑的说法近乎敷衍地带过身世\n「十几年前夏末的午后父亲带着我经过晨曦酒庄。」\n「『我去买几瓶葡萄汁路上消渴。』我记得他是这样说的。但他走后再也没有回来。」\n「如果不是克利普斯老爷向我伸出慈悲之手或许我就熬不过当晚那场暴雨了。」\n看似合乎情理的平淡叙述却是精心掩藏了真相的谎言。\n凯亚不曾说起过那个午后真正的故事——\n「这是你的机会你是我们最后的希望。」\n亲生父亲捏紧凯亚单薄的肩头视线越过他望向了更远的地方。\n地平线另一头有着父子俩遥远的故乡坎瑞亚。\n凯亚从未忘记那混杂着憎恨与希冀的眼神。"
},
{
"Id": 156,
"Name": "轮旋的冰凌",
"Description": "<color=#FFD780FF>凛冽轮舞</color>会产生一个额外的寒冰之棱并在施放时返还15点元素能量。",
"Icon": "UI_Talent_S_Kaeya_04"
"Title": "角色故事5",
"Context": "许多市民仍记得若干年前蒙德城中最惹眼的两位少年。\n一位是无可挑剔的年轻绅士迪卢克。他是手执长剑的优雅剑士有着友善的笑容与自信的身姿。\n另一位是异国面容的庶务长凯亚。他是迪卢克的好友、助战者与「头脑」为迪卢克的一切战斗扫尾洗尘。\n他们配合默契从不失手如同一对心智相通的双子从明处和暗处守护着蒙德的安全。\n…直到凯亚记忆深处那个阴沉的日子迪卢克护送的车队在森林中遭到巨大魔物袭击。\n那是凯亚第一次也是唯一一次失手。\n纵使快马加鞭他赶到时局势也已经无法挽回。\n他与迪卢克共同的「父亲」操纵着来历不明的不详力量击退魔物随即被力量反噬死于非命。\n凯亚和迪卢克都被眼前这一幕震惊失去了骑士应有的冷静。\n「原来克利普斯老爷这样的人物也会委身于危险的邪力。」\n险恶的念头闪过脑海凯亚却报以微笑——\n「这样的世界真是…有趣。」\n共同的「父亲」倒在了血泊之中。两位少年在这一夜走上分歧的道路。"
},
{
"Title": "一份名单",
"Context": "一份写在骑士团公文纸上的名单,夹在《安杰洛斯探案集》的书页间。\n上面清晰地记载着蒙德的市井盗匪游荡于城外的盗贼佣兵和盗宝团中层以上人员的组织地位、游荡范围以及详尽的个人信息。\n其中有十几个名字被打了圈旁边标注着「免得太无聊」。\n凯亚对这份名单的评论是「喝醉了写着玩的。」\n你隐约觉得凯亚是故意让你看到这份名单的但你没有证据。"
},
{
"Title": "神之眼",
"Context": "凯亚·亚尔伯里奇得到「神之眼」那晚,蒙德下着滂沱大雨。\n这天下午克利普斯·莱艮芬德强行使用邪力遭到「邪眼」的反噬。为使父亲解脱迪卢克·莱艮芬德亲手杀死了他。\n身为养子的凯亚始终陪在一旁却无法真正融入这场父子惨剧。\n当夜如同悼念克利普斯一般蒙德下起了暴雨。\n凯亚有着不为人知的一面——他是坎瑞亚安插在蒙德的棋子理应为坎瑞亚效力。为了这份使命他被生父毅然抛弃在异国。那时向他敞开怀抱的正是克利普斯与蒙德。\n如果坎瑞亚与蒙德发生战争他应该站在哪一边狠心舍弃他的生父与收留抚养他的养父他应该帮助谁\n长久以来凯亚都为无解的困境感到痛苦。对不露真心的他来说忠诚与使命、真诚与幸福从来不能兼得。\n而克利普斯之死打破了天平的平衡。凯亚因而感到解脱又为这份自私感到羞愧。作为养子他本该救下克利普斯却来晚一步作为义弟他理应帮迪卢克分担痛苦却躲在兄弟背后思考着那个古老的阴谋。\n出于罪恶感凯亚敲响了迪卢克的房门。倾盆大雨掩去谎言的气味秘密在这一夜被和盘托出。\n凯亚早已料到迪卢克会愤怒。兄弟二人拔剑相向他却觉得这是说谎者应得的惩罚。\n可当他们缠斗在一起凯亚第一次感到强大的元素力在他身体中迸发。多年来他始终将自己藏在迪卢克的光辉之下这是第一次他用真实的自己面对义兄。\n冰冷、脆弱的元素之力沿剑尖涌向迪卢克的火焰。红与蓝碰撞爆出惊人的飓风。凯亚的「神之眼」便是在此刻悄然降临。\n那天之后凯亚与义兄之间起了些变化。但他绝口不提就像他也不会交代「神之眼」的来历。\n即使它是全力一战的纪念是向亲人吐露真心换来的结果他仍将之看作警示余生每一天都背负着谎言的重量而活。"
}
],
"talks": [
@@ -272,38 +304,6 @@
{ "Title": "加入队伍·其二", "Context": "去找点乐子吧。" },
{ "Title": "加入队伍·其三", "Context": "早就准备好喽。" }
],
"stories": [
{
"Title": "角色详细",
"Context": "凯亚·亚尔伯里奇,酒业大亨「莱艮芬德」家的义子。\n他已很久没有称呼迪卢克·莱艮芬德为「义兄」了。\n目前他担任西风骑士团的骑兵队长是一位可靠的行动派、深得琴信任的人物。\n在蒙德这座城邦的范围之内但凡发生了什么突发事件凯亚总会是那个收尾善后的人。"
},
{
"Title": "角色故事1",
"Context": "说来有趣,最容易遇到这位骑兵队长的地方不是骑士团总部,而是夜色中的酒馆。\n凯亚时常独自坐在吧台前掂着蒙德著名的调制酒「午后之死」与好酒的蒙德市民们攀谈。\n他在蒙德的酒客与年长者中格外受欢迎甚至有着「最值得托付外孙女的男人」之名。\n一边戏谑调侃一边慢品美酒如此亲切的男性让人很难把他跟西风骑士团骑兵队长一职联系起来。\n与凯亚插科打诨的人中既有醺醉的猎人也有嗜酒的贼徒。但无论来客警惕心多强都会在凯亚巧妙的诱导下吐露真言。\n至于之后的事到底是一场脱离控制的噩梦还是漫漫长夜中又一个无伤大雅的玩笑恐怕要取决于这位酒友不小心说漏嘴的内容了。\n「每个人都有自己的秘密但不是每个人都懂得该怎么对待它。」\n带着令人有些恼火的微笑凯亚这样说道。"
},
{
"Title": "角色故事2",
"Context": "「正义并非绝对的原则,而是武力与计谋的精妙平衡所铸就的结果。至于过程如何…无需太为难自己。」\n在大团长法尔伽面前凯亚曾说过这样的话。\n只要结果合乎期待凯亚并不在乎事情以什么方式结束。\n这种原则缔造了他不拘一格的行事方式以及随意而为的人生态度。\n正如「午后之死」那随性肆意的暴烈口感一般。\n不过肆意的行事风格也为他招来许多非议。\n曾有一次为逼盗贼首领与他正面作战凯亚故意触发远古的遗迹守卫。这一举动精准封锁住对手退路同时也让凯亚和同僚陷入危险。\n每当这种时刻一向信任他的代理团长琴也会摇头。\n但凯亚毫不在意。不如说他人被迫作出选择的窘迫模样甚至能让他觉得享受——\n他乐于捕捉同伴选择共战时一闪而过的犹豫也乐于欣赏敌人背水一战前竭力遮掩的恐惧。"
},
{
"Title": "角色故事3",
"Context": "历史悠久的酿酒业为蒙德带来财富,蒙德的富庶又引来贪婪的盗贼与寻巢的魔物。\n这些游荡在阴影中的祸患不仅来源繁杂聚集的理由也多种多样。\n为了抵御侵扰蒙德的盗匪与魔物凯亚不只以他的剑也以他的头脑和幽默感制服敌人。\n有年轻骑士花费数年时间总结其中的规律最终得出一个令他目瞪口呆的结论——\n如果名酒「午后之死」不在供应期城内外的袭扰报告便会随之骤减直到…直到下一轮新酒上市\n忐忑的年轻骑士将报告交给擅长情报工作的骑兵队长凯亚希望从他那里得到一些建议。\n面对骑士忐忑的表情凯亚带着怪异的微笑回应「想法不错我会纳入参考。」"
},
{
"Title": "角色故事4",
"Context": "一般而言,凯亚是个相当健谈的人。唯独对过去的经历,他讳莫如深。\n哪怕大团长要求他详述出身他也不愿细谈旧事而是用圆滑的说法近乎敷衍地带过身世\n「十几年前夏末的午后父亲带着我经过晨曦酒庄。」\n「『我去买几瓶葡萄汁路上消渴。』我记得他是这样说的。但他走后再也没有回来。」\n「如果不是克利普斯老爷向我伸出慈悲之手或许我就熬不过当晚那场暴雨了。」\n看似合乎情理的平淡叙述却是精心掩藏了真相的谎言。\n凯亚不曾说起过那个午后真正的故事——\n「这是你的机会你是我们最后的希望。」\n亲生父亲捏紧凯亚单薄的肩头视线越过他望向了更远的地方。\n地平线另一头有着父子俩遥远的故乡坎瑞亚。\n凯亚从未忘记那混杂着憎恨与希冀的眼神。"
},
{
"Title": "角色故事5",
"Context": "许多市民仍记得若干年前蒙德城中最惹眼的两位少年。\n一位是无可挑剔的年轻绅士迪卢克。他是手执长剑的优雅剑士有着友善的笑容与自信的身姿。\n另一位是异国面容的庶务长凯亚。他是迪卢克的好友、助战者与「头脑」为迪卢克的一切战斗扫尾洗尘。\n他们配合默契从不失手如同一对心智相通的双子从明处和暗处守护着蒙德的安全。\n…直到凯亚记忆深处那个阴沉的日子迪卢克护送的车队在森林中遭到巨大魔物袭击。\n那是凯亚第一次也是唯一一次失手。\n纵使快马加鞭他赶到时局势也已经无法挽回。\n他与迪卢克共同的「父亲」操纵着来历不明的不详力量击退魔物随即被力量反噬死于非命。\n凯亚和迪卢克都被眼前这一幕震惊失去了骑士应有的冷静。\n「原来克利普斯老爷这样的人物也会委身于危险的邪力。」\n险恶的念头闪过脑海凯亚却报以微笑——\n「这样的世界真是…有趣。」\n共同的「父亲」倒在了血泊之中。两位少年在这一夜走上分歧的道路。"
},
{
"Title": "一份名单",
"Context": "一份写在骑士团公文纸上的名单,夹在《安杰洛斯探案集》的书页间。\n上面清晰地记载着蒙德的市井盗匪游荡于城外的盗贼佣兵和盗宝团中层以上人员的组织地位、游荡范围以及详尽的个人信息。\n其中有十几个名字被打了圈旁边标注着「免得太无聊」。\n凯亚对这份名单的评论是「喝醉了写着玩的。」\n你隐约觉得凯亚是故意让你看到这份名单的但你没有证据。"
},
{
"Title": "神之眼",
"Context": "凯亚·亚尔伯里奇得到「神之眼」那晚,蒙德下着滂沱大雨。\n这天下午克利普斯·莱艮芬德强行使用邪力遭到「邪眼」的反噬。为使父亲解脱迪卢克·莱艮芬德亲手杀死了他。\n身为养子的凯亚始终陪在一旁却无法真正融入这场父子惨剧。\n当夜如同悼念克利普斯一般蒙德下起了暴雨。\n凯亚有着不为人知的一面——他是坎瑞亚安插在蒙德的棋子理应为坎瑞亚效力。为了这份使命他被生父毅然抛弃在异国。那时向他敞开怀抱的正是克利普斯与蒙德。\n如果坎瑞亚与蒙德发生战争他应该站在哪一边狠心舍弃他的生父与收留抚养他的养父他应该帮助谁\n长久以来凯亚都为无解的困境感到痛苦。对不露真心的他来说忠诚与使命、真诚与幸福从来不能兼得。\n而克利普斯之死打破了天平的平衡。凯亚因而感到解脱又为这份自私感到羞愧。作为养子他本该救下克利普斯却来晚一步作为义弟他理应帮迪卢克分担痛苦却躲在兄弟背后思考着那个古老的阴谋。\n出于罪恶感凯亚敲响了迪卢克的房门。倾盆大雨掩去谎言的气味秘密在这一夜被和盘托出。\n凯亚早已料到迪卢克会愤怒。兄弟二人拔剑相向他却觉得这是说谎者应得的惩罚。\n可当他们缠斗在一起凯亚第一次感到强大的元素力在他身体中迸发。多年来他始终将自己藏在迪卢克的光辉之下这是第一次他用真实的自己面对义兄。\n冰冷、脆弱的元素之力沿剑尖涌向迪卢克的火焰。红与蓝碰撞爆出惊人的飓风。凯亚的「神之眼」便是在此刻悄然降临。\n那天之后凯亚与义兄之间起了些变化。但他绝口不提就像他也不会交代「神之眼」的来历。\n即使它是全力一战的纪念是向亲人吐露真心换来的结果他仍将之看作警示余生每一天都背负着谎言的重量而活。"
}
]
"title": "寒风剑士",
"weapon": "单手剑"
}

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