Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ab31d8f5c | ||
|
|
1af990512d | ||
|
|
d96d451156 | ||
|
|
f029306ebb | ||
|
|
d3c5baa0c2 | ||
|
|
ba0802752c | ||
|
|
ff94e12ff5 | ||
|
|
0fbf1f7c2a | ||
|
|
68809a93c6 | ||
|
|
0edcadef63 | ||
|
|
9f9c30914f | ||
|
|
04cf372798 | ||
|
|
6617a26c90 | ||
|
|
d244423800 | ||
|
|
3366efaadd | ||
|
|
d74e7a7a31 | ||
|
|
2d0b409813 | ||
|
|
942068faea | ||
|
|
0f0f7684d2 | ||
|
|
531cb32f72 | ||
|
|
a368223805 | ||
|
|
6eab6c81f1 | ||
|
|
68594a2a76 | ||
|
|
5d5f22d76e | ||
|
|
65e948c34c | ||
|
|
68dead3d84 | ||
|
|
babc6a9a75 | ||
|
|
6db4ff5ac9 | ||
|
|
ce1b6f365e | ||
|
|
b6ed9668ac | ||
|
|
2a2a190f5f | ||
|
|
5d03a32362 | ||
|
|
33d9ba5c4d | ||
|
|
9020214d23 | ||
|
|
78c3f79bfd | ||
|
|
ee0fc6dbae | ||
|
|
8c51b79558 | ||
|
|
8c1899637f | ||
|
|
56df920a7d | ||
|
|
64c6f4ab8f | ||
|
|
d3902d6e31 | ||
|
|
01e355b0d6 | ||
|
|
c40b3c6ff0 | ||
|
|
4305967ba9 | ||
|
|
78f454bee5 | ||
|
|
e9a38e1474 | ||
|
|
9fb2aa6112 | ||
|
|
a0554e4355 | ||
|
|
f890165894 | ||
|
|
bc22612da7 | ||
|
|
a9ec93b18d | ||
|
|
651cbef0a0 | ||
|
|
41a144fec2 | ||
|
|
3f219ebb82 | ||
|
|
43c85afd1e | ||
|
|
48771f57a0 | ||
|
|
6e3884df58 | ||
|
|
7a6a06bb25 | ||
|
|
eac3691d0b | ||
|
|
3ece987c80 | ||
|
|
145438373b | ||
|
|
b62b0b4902 | ||
|
|
ed878dea9e | ||
|
|
76e9d23f23 | ||
|
|
4cbd8af250 | ||
|
|
467b38feec | ||
|
|
636556d4ce | ||
|
|
01b89444ec | ||
|
|
29536f9181 | ||
|
|
86dfc134dc | ||
|
|
4f8f269787 | ||
|
|
7e0912ef22 | ||
|
|
6a5b65134e | ||
|
|
b40db32697 | ||
|
|
59c1fc9621 | ||
|
|
f98b1913f7 | ||
|
|
264d36490c | ||
|
|
f3dd8287cf | ||
|
|
843ee92670 | ||
|
|
de412a1fd6 | ||
|
|
f0555d69bb | ||
|
|
8b0b5cde28 | ||
|
|
ee92af0f73 |
39
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
|
||||
11
.github/workflows/qodana_code_quality.yml
vendored
@@ -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"
|
||||
|
||||
78
.github/workflows/test.yml
vendored
@@ -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
|
||||
62
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
16
README.md
@@ -2,12 +2,12 @@
|
||||
Author: 目棃
|
||||
Description: 说明文档
|
||||
Date: 2023-03-05
|
||||
Update: 2025-09-09
|
||||
Update: 2025-11-19
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
|
||||
>
|
||||
> 更新于 `2025-09-09 14:37:02`
|
||||
> 更新于 `2025-11-19 13:21:38`
|
||||
|
||||
[](https://deepwiki.com/BTMuli/TeyvatGuide)  
|
||||
|
||||
@@ -65,13 +65,13 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [x] 真境剧诗
|
||||
- [x] 幽境危战
|
||||
- [x] 祈愿数据获取(近一年)
|
||||
- [x] 千星奇域祈愿数据获取(近一年)
|
||||
- [x] 用户收藏帖子获取
|
||||
- [x] 用户关注帖子获取
|
||||
- [x] 一键完成米游币每日任务
|
||||
- [x] 一键完成游戏签到
|
||||
|
||||
- Wiki 功能:
|
||||
- [x] 深渊数据库(Hutao API)
|
||||
- [x] 角色图鉴
|
||||
- [x] 武器图鉴
|
||||
- [x] 名片图鉴
|
||||
@@ -90,7 +90,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
|
||||
## UI 参考 / UI Reference
|
||||
|
||||
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
|
||||
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
|
||||
- [Starward](https://github.com/Scighost/Starward)
|
||||
- [米游社](https://www.miyoushe.com/ys/)
|
||||
- [原神](https://yuanshen.com/)
|
||||
@@ -102,6 +102,8 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- UIAF:[UIAF v1.1](docs/standards/UIAF.md)
|
||||
- UIGF:[UIGF v3.0](docs/standards/UIGF3.md),[UIGF v4.0](docs/standards/UIGF.md)
|
||||
- [macOS 平台门禁属性导致应用无法打开应用的修复指引](docs/macos-gatekeeper/README.md)
|
||||
- [隐私政策](https://app.btmuli.ink/docs/TeyvatGuide/privacy.html)
|
||||
- [如何导入胡桃数据库](https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html)
|
||||
|
||||
## 特定项目 / Special Project
|
||||
|
||||
@@ -136,7 +138,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
本项目在开发过程中参考了诸多相关开源项目,特此鸣谢。
|
||||
|
||||
- [UIGF Organization](https://github.com/UIGF-org)
|
||||
- [Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)
|
||||
- ~~[Snap.Hutao](https://github.com/DGP-Studio/Snap.Hutao)~~
|
||||
- [StarWard](https://github.com/Scighost/Starward)
|
||||
- [xunkong](https://github.com/xunkong/xunkong)
|
||||
- [gs-helper](https://github.com/vikiboss/gs-helper)
|
||||
@@ -146,8 +148,4 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
- [MihoyoBBSTools](https://github.com/Womsxd/MihoyoBBSTools)
|
||||
- [nonebot-plugin-mystool](https://github.com/Ljzd-PRO/nonebot-plugin-mystool)
|
||||
|
||||
感谢 JetBrains 提供的开源许可证。
|
||||
|
||||

|
||||
|
||||
[](https://star-history.com/#BTMuli/TeyvatGuide&Timeline)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
Author: 目棃
|
||||
Description: 项目资源说明
|
||||
Date: 2023-03-10
|
||||
Update: 2025-02-28
|
||||
Update: 2025-11-19
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-10 22:05:44`
|
||||
>
|
||||
> 更新于 `2025-02-28 09:40:33`
|
||||
> 更新于 `2025-11-19 12:31:22`
|
||||
|
||||
## 说明
|
||||
|
||||
@@ -40,8 +40,8 @@ Update: 2025-02-28
|
||||
相关仓库:
|
||||
|
||||
- [TGAssistant](https://github.com/BTMuli/TGAssistant):项目下游仓库,用于处理项目数据。
|
||||
- [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata):胡桃元数据仓库,项目大部分数据来源于此。
|
||||
- [Snap.Static](https://github.com/DGP-Studio/Snap.Static):胡桃静态资源仓库,项目部分图像资源来源于此。
|
||||
- ~~[Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)~~:胡桃元数据仓库,项目大部分数据来源于此。
|
||||
- ~~[Snap.Static](https://github.com/DGP-Studio/Snap.Static)~~:胡桃静态资源仓库,项目部分图像资源来源于此。
|
||||
- [amos-data](https://github.com/yuehaiteam/amos-data):成就数据仓库,成就数据的详细信息来源于此。
|
||||
|
||||
## 字体
|
||||
|
||||
93
package.json
@@ -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
BIN
public/WIKI/character/10000117.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/WIKI/character/10000118.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/WIKI/character/10000122.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/WIKI/nameCard/bg/原神·印象.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/WIKI/nameCard/bg/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/WIKI/nameCard/bg/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/WIKI/nameCard/icon/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
public/WIKI/nameCard/icon/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/WIKI/nameCard/profile/奈芙尔·秘闻.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/WIKI/nameCard/profile/纪行·故墟.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/WIKI/weapon/13434.webp
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/WIKI/weapon/14434.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/WIKI/weapon/14521.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
public/icon/combat/tarot.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/icon/combat/tarot_0.webp
Normal file
|
After Width: | Height: | Size: 836 B |
BIN
public/icon/combat/tarot_1.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_02.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_03.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/icon/constellations/UI_Talent_S_Nefer_04.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/icon/constellations/UI_Talent_U_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/constellations/UI_Talent_U_Nefer_02.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/material/113079.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/icon/nation/千星奇域.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/icon/talents/Skill_E_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/icon/talents/Skill_S_Nefer_01.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_05.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_06.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_07.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/icon/talents/UI_Talent_S_Nefer_08.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/platforms/mhy/hyg.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
1227
src-tauri/Cargo.lock
generated
@@ -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"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ mod utils;
|
||||
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
|
||||
use tauri_utils::config::WebviewUrl;
|
||||
|
||||
static BBS_VERSION: &'static str = "2.93.0";
|
||||
static BBS_VERSION: &'static str = "2.95.1";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "TeyvatGuide",
|
||||
"identifier": "TeyvatGuide",
|
||||
"version": "0.8.1",
|
||||
"version": "0.8.6",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm vite:dev",
|
||||
"beforeBuildCommand": "pnpm vite:build",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div v-if="hasNav" class="tgn-nav">
|
||||
<v-icon size="25" @click="tryGetCode" title="查看兑换码">mdi-code-tags-check</v-icon>
|
||||
</div>
|
||||
<ToLivecode v-model="showOverlay" :data="codeData" v-model:actId="actId" />
|
||||
<ToLivecode v-model="showOverlay" :gid="model" :data="codeData" :actId="actId" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<TOverlay v-model="visible" class="tolc-overlay">
|
||||
<div class="tolc-box">
|
||||
<div class="tolc-title">
|
||||
<span>兑换码</span>
|
||||
<span>{{ gameInfo?.name ?? "" }}兑换码</span>
|
||||
<v-icon
|
||||
size="18px"
|
||||
title="share"
|
||||
@@ -39,16 +39,28 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import useBBSStore from "@store/bbs.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { timestampToDate } from "@utils/toolFunc.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
|
||||
import TMiImg from "./t-mi-img.vue";
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
|
||||
type ToLiveCodeProps = { data: Array<TGApp.BBS.Navigator.CodeData>; actId: string | undefined };
|
||||
type ToLiveCodeProps = {
|
||||
data: Array<TGApp.BBS.Navigator.CodeData>;
|
||||
actId: string | undefined;
|
||||
gid: number;
|
||||
};
|
||||
|
||||
const { gameList } = storeToRefs(useBBSStore());
|
||||
|
||||
const props = defineProps<ToLiveCodeProps>();
|
||||
const visible = defineModel<boolean>({ default: false });
|
||||
const gameInfo = computed<TGApp.BBS.Game.Item | undefined>(() => {
|
||||
return gameList.value.find((i) => i.id === props.gid);
|
||||
});
|
||||
|
||||
function copy(code: string): void {
|
||||
navigator.clipboard.writeText(code);
|
||||
|
||||
@@ -1,40 +1,35 @@
|
||||
<!-- 名片栏组件 -->
|
||||
<template>
|
||||
<div
|
||||
class="top-nc-box"
|
||||
@click="emit('selected', props.data)"
|
||||
:class="props.finish ? '' : 'grey'"
|
||||
:title.attr="props.data.name"
|
||||
:style="{ backgroundImage: `url('/WIKI/nameCard/bg/${props.data.name}.webp')` }"
|
||||
>
|
||||
<v-list-item>
|
||||
<template #title>
|
||||
<div class="title">
|
||||
<TwnTypeTag :type="props.data.type" />
|
||||
<span>{{ props.data.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
<div class="top-nc-bgc" />
|
||||
<div class="top-nc-prepend">
|
||||
<img :src="`/WIKI/nameCard/icon/${props.data.name}.webp`" alt="icon" />
|
||||
</div>
|
||||
<div class="top-nc-info">
|
||||
<div class="top-nc-title">
|
||||
<TwnTypeTag :type="props.data.type" />
|
||||
<span>{{ props.data.name }}</span>
|
||||
</div>
|
||||
<div class="top-nc-desc">
|
||||
<span class="desc" :title="props.data.desc">{{ props.data.desc }}</span>
|
||||
</template>
|
||||
<template #prepend>
|
||||
<img :src="`/WIKI/nameCard/icon/${props.data.name}.webp`" alt="icon" class="icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TwnTypeTag from "@comp/pageWiki/twn-type-tag.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
type TopNameCardProps = { data: TGApp.App.NameCard.Item; finish?: boolean };
|
||||
type TopNameCardEmits = (e: "selected", v: TGApp.App.NameCard.Item) => void;
|
||||
|
||||
const props = withDefaults(defineProps<TopNameCardProps>(), { finish: true });
|
||||
const emit = defineEmits<TopNameCardEmits>();
|
||||
|
||||
const bgImage = computed<string>(() => {
|
||||
if (props.data.name === "原神·印象") return "none;";
|
||||
return `url("/WIKI/nameCard/bg/${props.data.name}.webp")`;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
@@ -42,17 +37,19 @@ const bgImage = computed<string>(() => {
|
||||
.top-nc-box {
|
||||
@include github-styles.github-card-shadow;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px 50px 50px 4px;
|
||||
background-color: var(--box-bg-1);
|
||||
background-image: v-bind(bgImage); /* stylelint-disable-line value-keyword-case */
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
column-gap: 8px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
transition: filter 0.5s ease-in-out;
|
||||
@@ -70,19 +67,60 @@ const bgImage = computed<string>(() => {
|
||||
@include github-styles.github-card-shadow("dark");
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 60px;
|
||||
margin-right: 12px;
|
||||
aspect-ratio: 23 / 15;
|
||||
.top-nc-bgc {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, var(--box-bg-1) 40%, transparent 75%);
|
||||
}
|
||||
|
||||
.title {
|
||||
.top-nc-prepend {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
|
||||
img {
|
||||
height: 60px;
|
||||
aspect-ratio: 23/15;
|
||||
}
|
||||
}
|
||||
|
||||
.top-nc-info {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.top-nc-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
text-shadow: 0 0 2px var(--common-shadow-t-8);
|
||||
.top-nc-desc {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
max-width: calc(100% - 16px);
|
||||
font-size: 14px;
|
||||
opacity: 0.75;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 0 2px var(--box-bg-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -48,7 +48,7 @@ declare function initGeetest(
|
||||
async function displayBox(
|
||||
props: TGApp.BBS.Geetest.CreateRes,
|
||||
): Promise<TGApp.BBS.Geetest.GeetestVerifyRes | false> {
|
||||
return await new Promise<TGApp.BBS.Geetest.GeetestVerifyRes>((resolve) => {
|
||||
return await new Promise<TGApp.BBS.Geetest.GeetestVerifyRes | false>((resolve) => {
|
||||
initGeetest(
|
||||
{
|
||||
gt: props.gt,
|
||||
@@ -64,11 +64,12 @@ async function displayBox(
|
||||
geetestEl.value.innerHTML = "";
|
||||
captchaObj.appendTo("#geetest");
|
||||
captchaObj.onReady(() => (show.value = true));
|
||||
captchaObj.onSuccess(() => {
|
||||
captchaObj.onClose(() => {
|
||||
const validate = captchaObj.getValidate();
|
||||
show.value = false;
|
||||
if (!validate) resolve(false);
|
||||
resolve(validate);
|
||||
});
|
||||
captchaObj.onClose(() => (show.value = false));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
105
src/components/pageAnno/tao-iframe.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<!-- 游戏内公告浮窗 -->
|
||||
<template>
|
||||
<TOverlay v-model="visible">
|
||||
<div class="tao-iframe-box">
|
||||
<!-- TODO:加载完成后修改样式 -->
|
||||
<iframe :src="link" class="tao-iframe" />
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import takumiReq from "@req/takumiReq.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
const { lang } = storeToRefs(useAppStore());
|
||||
const { cookie, account } = storeToRefs(useUserStore());
|
||||
const visible = defineModel<boolean>();
|
||||
|
||||
const authkey = ref<string>("");
|
||||
const link = ref<string>("");
|
||||
|
||||
onMounted(async () => await refreshUrl());
|
||||
|
||||
watch(
|
||||
() => lang.value,
|
||||
async () => {
|
||||
if (!visible.value) return;
|
||||
await refreshUrl();
|
||||
},
|
||||
);
|
||||
|
||||
async function refreshUrl(): Promise<void> {
|
||||
const res = await getUrl();
|
||||
if (res === "") return;
|
||||
link.value = res;
|
||||
}
|
||||
|
||||
async function refreshAuthkey(): Promise<void> {
|
||||
if (!cookie.value || !account.value) {
|
||||
visible.value = false;
|
||||
showSnackbar.warn("请先登录账号");
|
||||
return;
|
||||
}
|
||||
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
|
||||
if (typeof authkeyRes === "string") {
|
||||
authkey.value = authkeyRes;
|
||||
} else {
|
||||
showSnackbar.error("获取authkey失败");
|
||||
visible.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUrl(): Promise<string> {
|
||||
const path = "https://sdk.mihoyo.com/hk4e/announcement/index.html";
|
||||
if (authkey.value === "") await refreshAuthkey();
|
||||
if (authkey.value === "") return "";
|
||||
const param: Record<string, string> = {
|
||||
auth_appid: "announcement",
|
||||
authkey_ver: "1",
|
||||
bundle_id: "hk4e_cn",
|
||||
channel_id: "14",
|
||||
game: "hk4e",
|
||||
game_biz: account.value.gameBiz,
|
||||
lang: lang.value,
|
||||
level: account.value.level,
|
||||
platform: "pc",
|
||||
region: account.value.region,
|
||||
sdk_presentation_style: "fullscreen",
|
||||
sdk_screen_transparent: "true",
|
||||
sign_type: "2",
|
||||
uid: account.value.gameUid,
|
||||
timestamp: Math.floor(Date.now() / 1000).toString(),
|
||||
authkey: authkey.value,
|
||||
};
|
||||
const targetLink = new URL(path);
|
||||
for (const key in param) {
|
||||
targetLink.searchParams.append(key, param[key]);
|
||||
}
|
||||
return targetLink.toString();
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tao-iframe-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 50vw;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
.tao-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -404,6 +404,9 @@ async function tryGetCaptcha(phone: string, aigis?: string): Promise<string | fa
|
||||
if ("retcode" in captchaResp) {
|
||||
if (!captchaResp.data || captchaResp.data === "") {
|
||||
showSnackbar.error(`[${captchaResp.retcode}] ${captchaResp.message}`);
|
||||
await TGLogger.Error(
|
||||
`[tc-userBadge][tryGetCaptcha] ${captchaResp.retcode} ${captchaResp.message}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const aigisResp: TGApp.BBS.CaptchaLogin.CaptchaAigis = JSON.parse(captchaResp.data);
|
||||
@@ -424,6 +427,9 @@ async function tryLoginByCaptcha(
|
||||
if ("retcode" in loginResp) {
|
||||
if (!loginResp.data || loginResp.data === "") {
|
||||
showSnackbar.error(`[${loginResp.retcode}] ${loginResp.message}`);
|
||||
await TGLogger.Error(
|
||||
`[tc-userBadge][tryLoginByCaptcha] ${loginResp.retcode} ${loginResp.message}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const aigisResp: TGApp.BBS.CaptchaLogin.CaptchaAigis = JSON.parse(loginResp.data);
|
||||
|
||||
@@ -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
|
||||
|
||||
147
src/components/pageConfig/tco-imgQuality.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<!-- 设置图片质量浮窗 -->
|
||||
<template>
|
||||
<TOverlay v-model="model" hide blur-val="10px">
|
||||
<div class="toi-box">
|
||||
<div class="toi-top">
|
||||
<div class="toi-title">调整图片质量</div>
|
||||
<div class="toi-desc">设置图片质量,数值越大图片越清晰,但也会占用更多空间</div>
|
||||
</div>
|
||||
<div class="toi-mid">
|
||||
<v-slider
|
||||
thumb-label="always"
|
||||
color="var(--tgc-od-blue)"
|
||||
thumb-color="var(--tgc-od-red)"
|
||||
v-model="quality"
|
||||
:max="100"
|
||||
:min="5"
|
||||
:step="1"
|
||||
hide-details
|
||||
/>
|
||||
<v-number-input
|
||||
class="toi-input"
|
||||
v-model="quality"
|
||||
control-variant="stacked"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
style="max-width: 100px"
|
||||
type="number"
|
||||
:max="100"
|
||||
:min="5"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="toi-bottom">
|
||||
<button class="toi-btn no-btn" @click="onCancel()">取消</button>
|
||||
<button class="toi-btn ok-btn" @click="onConfirm()">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
const { imageQualityPercent } = storeToRefs(useAppStore());
|
||||
|
||||
const model = defineModel<boolean>({ default: false });
|
||||
const quality = ref<number>(imageQualityPercent.value);
|
||||
|
||||
function onCancel(): void {
|
||||
model.value = false;
|
||||
quality.value = imageQualityPercent.value;
|
||||
}
|
||||
|
||||
async function onConfirm(): Promise<void> {
|
||||
if (quality.value === imageQualityPercent.value) {
|
||||
model.value = false;
|
||||
showSnackbar.info(`图片质量未修改`);
|
||||
return;
|
||||
}
|
||||
imageQualityPercent.value = quality.value;
|
||||
model.value = false;
|
||||
showSnackbar.success(`图片质量已修改为 ${quality.value}%`);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.toi-box {
|
||||
display: flex;
|
||||
width: 400px;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--box-bg-1);
|
||||
color: var(--app-page-content);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.toi-top {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-title);
|
||||
row-gap: 4px;
|
||||
text-align: center;
|
||||
|
||||
.toi-title {
|
||||
color: var(--common-text-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.toi-desc {
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.toi-mid {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 12px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
column-gap: 24px;
|
||||
|
||||
.toi-input {
|
||||
max-width: 100px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.toi-bottom {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toi-btn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 20px;
|
||||
border-radius: 24px;
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
|
||||
&.no-btn {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<!-- 近期活动卡片组件(用户)-->
|
||||
<template>
|
||||
<div class="ph-pos-user-card">
|
||||
<div class="ph-pos-user-card" ref="posRef">
|
||||
<div class="ph-puc-top">
|
||||
<div class="title">
|
||||
<v-icon title="已完成" color="var(--tgc-od-green)" v-if="props.pos.is_finished">
|
||||
mdi-checkbox-marked-circle-outline
|
||||
</v-icon>
|
||||
<v-icon v-else title="未完成" color="var(--tgc-od-white)">mdi-circle</v-icon>
|
||||
<span>{{ props.pos.name }}</span>
|
||||
<span @click="sharePos()" title="点击分享">{{ props.pos.name }}</span>
|
||||
</div>
|
||||
<div class="subtitle">
|
||||
<!-- 处理幽境危战 -->
|
||||
@@ -29,13 +30,7 @@
|
||||
<!-- 处理真境剧诗 -->
|
||||
<template v-else-if="props.pos.type === ActCalendarTypeEnum.RoleCombat">
|
||||
<div class="combat-append" @click="toCombat()" title="点击前往剧诗页面">
|
||||
<template v-if="!props.pos.role_combat_detail.is_unlock">
|
||||
<span>未解锁</span>
|
||||
</template>
|
||||
<template v-else-if="!props.pos.role_combat_detail.has_data">
|
||||
<span>尚未挑战</span>
|
||||
</template>
|
||||
<span v-else>第{{ props.pos.role_combat_detail.max_round_id }}幕</span>
|
||||
<span>{{ getCombatStat(props.pos.role_combat_detail) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 处理深境螺旋 -->
|
||||
@@ -69,7 +64,7 @@
|
||||
</div>
|
||||
<div class="ph-puc-duration">
|
||||
<template v-if="isStart">
|
||||
<span title="剩余时间">{{ stamp2LastTime(restTs * 1000) }}</span>
|
||||
<span title="剩余时间" data-html2canvas-ignore>{{ stamp2LastTime(restTs * 1000) }}</span>
|
||||
<span title="活动时间">
|
||||
{{ timestampToDate(Number(props.pos.start_timestamp) * 1000) }} ~
|
||||
{{ timestampToDate(Number(props.pos.end_timestamp) * 1000) }}
|
||||
@@ -98,8 +93,9 @@
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import { ActCalendarTypeEnum } from "@enum/game.js";
|
||||
import { getHardChallengeDesc } from "@Sql/utils/transUserRecord.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { stamp2LastTime, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { computed, onMounted, onUnmounted, ref, useTemplateRef } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
type PhCompPositionUserProps = { pos: TGApp.Game.ActCalendar.ActItem };
|
||||
@@ -111,6 +107,7 @@ const router = useRouter();
|
||||
|
||||
const props = defineProps<PhCompPositionUserProps>();
|
||||
const emits = defineEmits<PhCompPositionUserEmits>();
|
||||
const posEl = useTemplateRef<HTMLDivElement>("posRef");
|
||||
|
||||
const endTs = ref<number>(0);
|
||||
const restTs = ref<number>(0);
|
||||
@@ -129,6 +126,10 @@ onMounted(() => {
|
||||
timer = setInterval(handlePosition, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
});
|
||||
|
||||
function handlePosition(): void {
|
||||
if (restTs.value < 1) {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
@@ -155,9 +156,17 @@ function showMaterial(reward: TGApp.Game.ActCalendar.ActReward): void {
|
||||
emits("clickM", reward);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer !== null) clearInterval(timer);
|
||||
});
|
||||
function getCombatStat(detail: TGApp.Game.ActCalendar.ActRoleCombat): string {
|
||||
if (!detail.is_unlock) return "未解锁";
|
||||
if (!detail.has_data) return "尚未挑战";
|
||||
if (detail.difficulty_id < 5) return `第${detail.max_round_id}幕`;
|
||||
return `月谕模式·第${detail.max_round_id}幕·圣牌${detail.tarot_finished_cnt}`;
|
||||
}
|
||||
|
||||
async function sharePos(): Promise<void> {
|
||||
if (!posEl.value) return;
|
||||
await generateShareImg(`活动-${props.pos.name}.png`, posEl.value, 2);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-pos-user-card {
|
||||
@@ -190,6 +199,10 @@ onUnmounted(() => {
|
||||
justify-content: flex-start;
|
||||
column-gap: 4px;
|
||||
font-family: var(--font-title);
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@@ -267,6 +280,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
@click="toChannel(item)"
|
||||
>
|
||||
<TMiImg :src="item.icon" alt="icon" :ori="true" />
|
||||
<span>{{ item.title }}</span>
|
||||
<span class="toc-list-title">{{ item.title }}</span>
|
||||
<span class="toc-list-id">GID:{{ item.gid }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,32 +60,38 @@ async function toChannel(item: ChannelItem): Promise<void> {
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.toc-box {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--app-page-bg);
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.toc-list {
|
||||
display: grid;
|
||||
margin-top: 10px;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.toc-list-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 5px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-1);
|
||||
column-gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s linear;
|
||||
|
||||
@@ -95,17 +102,22 @@ async function toChannel(item: ChannelItem): Promise<void> {
|
||||
}
|
||||
|
||||
img {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin-right: 10px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 10px;
|
||||
.toc-list-title {
|
||||
margin-right: 8px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.toc-list-id {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 2px;
|
||||
font-size: 6px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -107,9 +107,8 @@ const relicsBox = computed<AvatarRelics>(() => {
|
||||
];
|
||||
});
|
||||
const isFetterMax = computed<boolean>(() => {
|
||||
if (props.modelValue.avatar.id === 10000005 || props.modelValue.avatar.id === 10000007) {
|
||||
return true;
|
||||
}
|
||||
const skipList = [10000005, 10000007, 10000117, 10000118];
|
||||
if (skipList.includes(props.modelValue.avatar.id)) return true;
|
||||
return props.modelValue.avatar.fetter === 10;
|
||||
});
|
||||
const skills = computed<Array<TGApp.Game.Avatar.Skill>>(() =>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<div class="tucfi-label">
|
||||
<slot name="label">{{ props.label }}</slot>
|
||||
</div>
|
||||
<div v-if="props.data === null"><span class="tucfi-data">暂无数据</span></div>
|
||||
<div v-if="!props.data">
|
||||
<span class="tucfi-data">暂无数据</span>
|
||||
</div>
|
||||
<div v-else-if="!Array.isArray(props.data)" class="tucfi-data">
|
||||
<TItemBox :model-value="getBox(props.data)" />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="tuco-box">
|
||||
<TucTile title="最佳记录" :val="props.data.max_round_id" />
|
||||
<TucTile title="最佳记录" :val="getBestVal()" />
|
||||
<TucTile :title="`获得星章-${props.data.medal_num}`" :val="props.data.get_medal_round_list" />
|
||||
<TucTile :title="getTitle()" :val="`第${props.data.max_round_id}幕`" />
|
||||
<TucTile :title="getRoundTitle()" :val="getRoundVal()" />
|
||||
<TucTile title="消耗幻剧之花" :val="props.data.coin_num" />
|
||||
<TucFight label="最快完成演出" :data="props.fights.shortest_avatar_list" />
|
||||
<TucTile title="总耗时" :val="getTime()" />
|
||||
@@ -21,7 +21,7 @@ type TucOverviewProps = { data: TGApp.Game.Combat.Stat; fights: TGApp.Game.Comba
|
||||
|
||||
const props = defineProps<TucOverviewProps>();
|
||||
|
||||
function getTitle(): string {
|
||||
function getRoundTitle(): string {
|
||||
switch (props.data.difficulty_id) {
|
||||
case 0:
|
||||
return "未选择";
|
||||
@@ -33,11 +33,23 @@ function getTitle(): string {
|
||||
return "困难模式";
|
||||
case 4:
|
||||
return "卓越模式";
|
||||
case 5:
|
||||
return "月谕模式";
|
||||
default:
|
||||
return `未知模式${props.data.difficulty_id}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getBestVal(): string {
|
||||
if (props.data.difficulty_id < 5) return `第${props.data.max_round_id}幕`;
|
||||
return `第${props.data.max_round_id}幕·圣牌${props.data.tarot_finished_cnt}`;
|
||||
}
|
||||
|
||||
function getRoundVal(): string {
|
||||
if (props.data.difficulty_id < 5) return `第${props.data.max_round_id}幕`;
|
||||
return `${props.data.tarot_finished_cnt + props.data.max_round_id}`;
|
||||
}
|
||||
|
||||
function getTime(): string {
|
||||
const sec = props.fights.total_use_time % 60;
|
||||
const min = (props.fights.total_use_time - sec) / 60;
|
||||
@@ -48,7 +60,7 @@ function getTime(): string {
|
||||
.tuco-box {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-gap: 8px;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
<!-- 真境剧诗,单轮次卡片组件 -->
|
||||
<template>
|
||||
<div class="tucr-box">
|
||||
<div class="tucr-title">
|
||||
<img :src="`/icon/star/combat${modelValue.is_get_medal ? 1 : 0}.webp`" alt="combat" />
|
||||
<span class="main">第{{ modelValue.round_id }}幕</span>
|
||||
<span class="sub">{{ timestampToDate(Number(modelValue.finish_time) * 1000) }}</span>
|
||||
<img :src="`/icon/combat/${getIcon()}.webp`" alt="combat" />
|
||||
<span class="main" v-if="props.round.is_tarot">
|
||||
圣牌挑战·{{ props.round.tarot_serial_no }}
|
||||
</span>
|
||||
<span class="main" v-else>第{{ props.round.round_id }}幕</span>
|
||||
<span class="sub">{{ timestampToDate(Number(props.round.finish_time) * 1000) }}</span>
|
||||
</div>
|
||||
<div class="tucr-content">
|
||||
<TucSub title="出演角色" class="main">
|
||||
<TucAvatars :model-value="modelValue.avatars" :detail="true" />
|
||||
<TucAvatars :model-value="props.round.avatars" :detail="true" />
|
||||
</TucSub>
|
||||
<TucSub title="辉彩祝福" class="main">
|
||||
<TucBuffs :model-value="modelValue.splendour_buff" />
|
||||
<TucBuffs :model-value="props.round.splendour_buff" />
|
||||
</TucSub>
|
||||
<TucSub :title="`神秘收获(${modelValue.choice_cards.length})`" class="sub">
|
||||
<TucCards :model-value="modelValue.choice_cards" />
|
||||
<TucSub :title="`神秘收获(${props.round.choice_cards.length})`" class="sub">
|
||||
<TucCards :model-value="props.round.choice_cards" />
|
||||
</TucSub>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,7 +30,12 @@ import TucBuffs from "./tuc-buffs.vue";
|
||||
import TucCards from "./tuc-cards.vue";
|
||||
import TucSub from "./tuc-sub.vue";
|
||||
|
||||
defineProps<{ modelValue: TGApp.Game.Combat.RoundData }>();
|
||||
type TucRoundProps = { round: TGApp.Game.Combat.RoundData };
|
||||
const props = defineProps<TucRoundProps>();
|
||||
|
||||
function getIcon(): string {
|
||||
return `${props.round.is_tarot ? "tarot" : "star"}_${props.round.is_get_medal ? "1" : "0"}`;
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tucr-box {
|
||||
@@ -49,8 +58,8 @@ defineProps<{ modelValue: TGApp.Game.Combat.RoundData }>();
|
||||
column-gap: 4px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
aspect-ratio: 1;
|
||||
height: 30px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
<!-- 真境剧诗概况卡片组件 -->
|
||||
<template>
|
||||
<div class="tuct-box">
|
||||
<div class="tuct-title">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
<slot name="title">{{ props.title }}</slot>
|
||||
</div>
|
||||
<div class="tuct-text" v-if="!Array.isArray(val)">
|
||||
<slot name="text">{{ val }}</slot>
|
||||
<div class="tuct-text" v-if="!Array.isArray(props.val)">
|
||||
<slot name="text">{{ props.val }}</slot>
|
||||
</div>
|
||||
<div class="tuct-icons" v-else>
|
||||
<img v-for="(v, idx) in val" :key="idx" :src="`/icon/star/combat${v}.webp`" :alt="`${v}`" />
|
||||
<template v-for="(v, idx) in props.val" :key="idx">
|
||||
<img
|
||||
v-if="idx < 10"
|
||||
:src="`/icon/combat/star_${v}.webp`"
|
||||
:alt="`${v}`"
|
||||
:title="`第${idx + 1}幕`"
|
||||
/>
|
||||
<img v-else :src="`/icon/combat/tarot_${v}.webp`" :alt="`${v}`" :title="`圣牌${idx - 9}`" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ title: string; val: string | number | Array<number> }>();
|
||||
type TucTileProps = { title: string; val: string | number | Array<number> };
|
||||
|
||||
const props = defineProps<TucTileProps>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tuct-box {
|
||||
@@ -47,7 +58,7 @@ defineProps<{ title: string; val: string | number | Array<number> }>();
|
||||
|
||||
img {
|
||||
height: 30px;
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
144
src/components/userGacha/gbr-data-line.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<!-- 千星奇域概览单项组件 -->
|
||||
<template>
|
||||
<div class="gbr-dl-box">
|
||||
<div class="gbr-dl-progress" />
|
||||
<div class="gbr-dl-icon">
|
||||
<img :alt="props.data.name" :src="getIcon()" />
|
||||
</div>
|
||||
<div class="gbr-dl-base">
|
||||
<div class="gbr-dl-name">{{ props.data.name }}</div>
|
||||
<div class="gbr-dl-time">{{ props.data.time }}</div>
|
||||
</div>
|
||||
<div class="gbr-dl-info">
|
||||
<div class="gbr-dl-cnt">{{ props.count }}</div>
|
||||
<div class="gbr-dl-hint" v-if="hint !== ''">{{ hint }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
export type GbrDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGachaB; count: number };
|
||||
|
||||
const props = defineProps<GbrDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
function getIcon(): string {
|
||||
console.log(props.data);
|
||||
// const find = AppGachaBData.find((i) => i.id.toString() === props.data.itemId);
|
||||
// if (!find) return `/source/UI/paimon.webp`;
|
||||
// return `https://api.hakush.in/gi/UI/${find.icon}.webp`;
|
||||
// TODO: 缺失元数据
|
||||
return `/source/UI/paimon.webp`;
|
||||
}
|
||||
|
||||
function getEndHint(): string {
|
||||
if (props.data.gachaType === "1000") return "";
|
||||
if (!props.data.isUp) return "歪";
|
||||
return "";
|
||||
}
|
||||
|
||||
const progressColor = computed<string>(() => {
|
||||
if (hint === "UP" && props.data.rank === "5") return "#d19a66";
|
||||
if (hint === "UP" && props.data.rank === "4") return "#c678dd";
|
||||
if (hint === "歪") return "#e06c75";
|
||||
return "#61afef";
|
||||
});
|
||||
const progressWidth = computed<string>(() => {
|
||||
let final = 10;
|
||||
if (props.data.rank === "5") {
|
||||
if (props.data.gachaType === "302") final = 80;
|
||||
else final = 90;
|
||||
} else if (props.data.rank === "4") final = 10;
|
||||
else return "0%";
|
||||
return ((props.count / final) * 100).toFixed(2) + "%";
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.gbr-dl-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
column-gap: 4px;
|
||||
}
|
||||
|
||||
.gbr-dl-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: v-bind(progressWidth); /* stylelint-disable-line value-keyword-case */
|
||||
max-width: 100%;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
background: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
|
||||
}
|
||||
|
||||
.gbr-dl-icon {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.gbr-dl-base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gbr-dl-name {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.gbr-dl-time {
|
||||
color: var(--box-text-7);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.gbr-dl-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
column-gap: 4px;
|
||||
}
|
||||
|
||||
.gbr-dl-cnt {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.gbr-dl-hint {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--box-bg-3);
|
||||
color: v-bind(progressColor); /* stylelint-disable-line value-keyword-case */
|
||||
font-family: var(--font-title);
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
</style>
|
||||
263
src/components/userGacha/gbr-data-view.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<!-- 千星奇域概览数据视图组件 -->
|
||||
<template>
|
||||
<div class="gbr-dv-container">
|
||||
<div class="gbr-dvt-title">
|
||||
<span>{{ title }}</span>
|
||||
<span>{{ props.dataVal.length }}</span>
|
||||
</div>
|
||||
<div class="gbr-dvt-subtitle">
|
||||
<span v-show="props.dataVal.length === 0">暂无数据</span>
|
||||
<span v-show="props.dataVal.length !== 0">{{ startDate }} ~ {{ endDate }}</span>
|
||||
</div>
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-item">
|
||||
<span>4☆已垫</span>
|
||||
<span>{{ reset4count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-item">
|
||||
<span>5☆已垫</span>
|
||||
<span>{{ reset5count - 1 }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-item">
|
||||
<span>5☆平均</span>
|
||||
<span>{{ star5avg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gbr-mid-list">
|
||||
<div class="gbr-ml-item">
|
||||
<span>5☆统计</span>
|
||||
<span>{{ getTitle("5") }}</span>
|
||||
</div>
|
||||
<div class="gbr-ml-item">
|
||||
<span>4☆统计</span>
|
||||
<span>{{ getTitle("4") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 这边放具体物品的列表 -->
|
||||
<div class="gbr-bottom">
|
||||
<v-tabs v-model="tab" density="compact">
|
||||
<v-tab value="5">5☆</v-tab>
|
||||
<v-tab value="4">4☆</v-tab>
|
||||
<v-tab value="3">3☆</v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab" class="gbr-bottom-window">
|
||||
<v-window-item value="5" class="gbr-b-window-item">
|
||||
<v-virtual-scroll :items="star5List" :item-height="48">
|
||||
<template #default="{ item }">
|
||||
<GbrDataLine :data="item.data" :count="item.count" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
<v-window-item value="4" class="gbr-b-window-item">
|
||||
<v-virtual-scroll :items="star4List" :item-height="48">
|
||||
<template #default="{ item }">
|
||||
<GbrDataLine :data="item.data" :count="item.count" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
<v-window-item value="3" class="gbr-b-window-item">
|
||||
<v-virtual-scroll :items="star3List" :item-height="48">
|
||||
<template #default="{ item }">
|
||||
<GbrDataLine :data="item.data" :count="item.count" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import GbrDataLine, { type GbrDataLineProps } from "./gbr-data-line.vue";
|
||||
|
||||
type GachaDataViewProps = {
|
||||
dataType: "normal" | "boy" | "girl";
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGachaB>;
|
||||
};
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
// data
|
||||
const loading = ref<boolean>(true); // 是否加载完
|
||||
const title = ref<string>(""); // 卡片标题
|
||||
const startDate = ref<string>(""); // 最早的时间
|
||||
const endDate = ref<string>(""); // 最晚的时间
|
||||
const star5List = shallowRef<Array<GbrDataLineProps>>([]); // 5星物品数据
|
||||
const star4List = shallowRef<Array<GbrDataLineProps>>([]); // 4星物品数据
|
||||
const star3List = shallowRef<Array<GbrDataLineProps>>([]);
|
||||
const reset5count = ref<number>(1); // 5星垫抽数量
|
||||
const reset4count = ref<number>(1); // 4星垫抽数量
|
||||
const reset3count = ref<number>(1); // 3星垫抽数量
|
||||
const star3count = ref<number>(0); // 3星物品数量
|
||||
const star5avg = ref<string>(""); // 5星平均抽数
|
||||
const tab = ref<string>("5"); // tab
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function loadData(): void {
|
||||
title.value = getTitle("top");
|
||||
const tempData = props.dataVal;
|
||||
const temp5Data: Array<GbrDataLineProps> = [];
|
||||
const temp4Data: Array<GbrDataLineProps> = [];
|
||||
const temp3Data: Array<GbrDataLineProps> = [];
|
||||
// 按照 id 升序
|
||||
tempData
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
.forEach((item) => {
|
||||
// 处理时间
|
||||
if (startDate.value === "" || item.time < startDate.value) startDate.value = item.time;
|
||||
if (endDate.value === "" || item.time > endDate.value) endDate.value = item.time;
|
||||
if (item.rank === "2") {
|
||||
reset3count.value++;
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
} else if (item.rank === "3") {
|
||||
reset4count.value++;
|
||||
reset5count.value++;
|
||||
temp3Data.push({ data: item, count: reset3count.value });
|
||||
reset3count.value = 1;
|
||||
} else if (item.rank === "4") {
|
||||
reset5count.value++;
|
||||
temp4Data.push({ data: item, count: reset4count.value });
|
||||
reset4count.value = 1;
|
||||
} else if (item.rank === "5") {
|
||||
reset4count.value++;
|
||||
temp5Data.push({ data: item, count: reset5count.value });
|
||||
reset5count.value = 1;
|
||||
}
|
||||
});
|
||||
star5List.value = temp5Data.reverse();
|
||||
star4List.value = temp4Data.reverse();
|
||||
star3List.value = temp3Data.reverse();
|
||||
star5avg.value = getStar5Avg();
|
||||
}
|
||||
|
||||
// 获取标题
|
||||
function getTitle(type: "top" | "5" | "4" | "3"): string {
|
||||
if (type === "top") {
|
||||
if (props.dataType === "normal") return "常驻颂愿";
|
||||
if (props.dataType === "boy") return "活动颂愿(男)";
|
||||
if (props.dataType === "girl") return "活动颂愿(女)";
|
||||
return "";
|
||||
}
|
||||
if (props.dataVal.length === 0) return "暂无数据";
|
||||
if (type === "5") {
|
||||
// 5星物品统计 00.00%
|
||||
return `${star5List.value.length} [${((star5List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
if (type === "4") {
|
||||
// 4星物品统计
|
||||
return `${star4List.value.length} [${((star4List.value.length * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
// 3星物品统计
|
||||
return `${star3count.value} [${((star3count.value * 100) / props.dataVal.length)
|
||||
.toFixed(2)
|
||||
.padStart(5, "0")}%]`;
|
||||
}
|
||||
|
||||
// 获取5星平均抽数
|
||||
function getStar5Avg(): string {
|
||||
const resetList = star5List.value.map((item) => item.count);
|
||||
if (resetList.length === 0) return "0";
|
||||
const total = resetList.reduce((a, b) => a + b);
|
||||
return (total / star5List.value.length).toFixed(2);
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => props.dataVal,
|
||||
() => {
|
||||
star5List.value = [];
|
||||
star4List.value = [];
|
||||
reset5count.value = 1;
|
||||
reset4count.value = 1;
|
||||
star3count.value = 1;
|
||||
startDate.value = "";
|
||||
endDate.value = "";
|
||||
star5avg.value = "";
|
||||
tab.value = "5";
|
||||
loadData();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gbr-dv-container {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.gbr-dvt-title {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.gbr-dvt-subtitle {
|
||||
width: 100%;
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.gbr-mid-list {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
border-top: 1px solid var(--common-shadow-4);
|
||||
color: var(--box-text-7);
|
||||
}
|
||||
|
||||
.gbr-ml-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gbr-bottom {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100% - 150px);
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gbr-bottom-window {
|
||||
position: relative;
|
||||
height: calc(100vh - 428px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gbr-b-window-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
|
||||
:deep(.v-virtual-scroll__item + .v-virtual-scroll__item) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
</style>
|
||||
34
src/components/userGacha/gbr-overview.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<!-- 千星奇域祈愿概览组件 -->
|
||||
<template>
|
||||
<div class="gro-o-container">
|
||||
<GbrDataView :data-val="normalData" data-type="normal" />
|
||||
<GbrDataView :data-val="boyData" data-type="boy" />
|
||||
<GbrDataView :data-val="girlData" data-type="girl" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import GbrDataView from "./gbr-data-view.vue";
|
||||
|
||||
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "1000"),
|
||||
);
|
||||
const girlData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "20011" || item.opGachaType === "20012"),
|
||||
);
|
||||
const boyData = computed<Array<TGApp.Sqlite.GachaRecords.TableGachaB>>(() =>
|
||||
props.modelValue.filter((item) => item.opGachaType === "20021" || item.opGachaType === "20022"),
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gro-o-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
column-gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
</style>
|
||||
62
src/components/userGacha/gbr-table.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<!-- 千星奇域数据表格 -->
|
||||
<template>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="props.modelValue"
|
||||
fixed-header
|
||||
fixed-footer
|
||||
class="gbr-t-box"
|
||||
>
|
||||
<template v-slot:item="{ item }">
|
||||
<tr class="gbr-t-tr">
|
||||
<td>{{ item.time }}</td>
|
||||
<td>{{ getPool(item.opGachaType) }}</td>
|
||||
<td>{{ item.type }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.rank }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGachaB> };
|
||||
|
||||
const props = defineProps<GroTableProps>();
|
||||
|
||||
const headers = <const>[
|
||||
{ title: "时间", align: "center", key: "time" },
|
||||
{ title: "卡池", align: "center", key: "opGachaType" },
|
||||
{ title: "类型", align: "center", key: "type" },
|
||||
{ title: "名称", align: "center", key: "name" },
|
||||
{ title: "星级", align: "center", key: "rank" },
|
||||
];
|
||||
|
||||
function getPool(type: string) {
|
||||
switch (type) {
|
||||
case "1000":
|
||||
return "常驻颂愿";
|
||||
case "2000":
|
||||
return "活动颂愿";
|
||||
case "20011":
|
||||
case "20012":
|
||||
return "活动颂愿-男";
|
||||
case "20021":
|
||||
case "20022":
|
||||
return "活动颂愿-女";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.gbr-t-box {
|
||||
height: calc(100vh - 200px);
|
||||
padding-right: 5px;
|
||||
border-radius: 5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gbr-t-tr {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@ import { computed } from "vue";
|
||||
|
||||
import { AppGachaData } from "@/data/index.js";
|
||||
|
||||
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.SingleTable; count: number };
|
||||
export type GroDataLineProps = { data: TGApp.Sqlite.GachaRecords.TableGacha; count: number };
|
||||
|
||||
const props = defineProps<GroDataLineProps>();
|
||||
const hint = getEndHint();
|
||||
|
||||
@@ -64,7 +64,7 @@ import GroDataLine, { type GroDataLineProps } from "./gro-data-line.vue";
|
||||
|
||||
type GachaDataViewProps = {
|
||||
dataType: "new" | "avatar" | "weapon" | "normal" | "mix";
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.SingleTable>;
|
||||
dataVal: Array<TGApp.Sqlite.GachaRecords.TableGacha>;
|
||||
};
|
||||
|
||||
const props = defineProps<GachaDataViewProps>();
|
||||
|
||||
134
src/components/userGacha/gro-iframe.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<!-- 嵌入游戏内容的iframe组件 -->
|
||||
<template>
|
||||
<div class="gro-iframe-container">
|
||||
<v-tabs class="gro-ic-tabs" v-model="poolTab" align-tabs="start" direction="vertical">
|
||||
<v-tab v-for="(item, index) in tabList" :key="index" :value="item.value">
|
||||
<template v-if="item.beyond">
|
||||
<img src="/icon/nation/千星奇域.webp" title="千星奇域" alt="beyond" />
|
||||
</template>
|
||||
{{ item.label }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<iframe class="gro-iframe" :src="link" style="width: 100%; height: 100%; border: none" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import takumiReq from "@req/takumiReq.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
/**
|
||||
* 卡池类型-ID映射
|
||||
* @remarks
|
||||
* 目前缺失集录&新手池
|
||||
* TODO: 动态获取当前卡池类型&ID映射
|
||||
*/
|
||||
const GachaIdMap: Record<string, string> = {
|
||||
"200": "34ff1a235049182fd199d285110e3e7d292c50cd", // 常驻
|
||||
"301": "182e725d99b742b14839117650d3e79628cc6221", //角色活动
|
||||
"302": "8ff7a7d42bea79b0d54e92fdb58a20f971490372", // 武器活动
|
||||
"400": "bb0486115a7e7c4bd2994135f7d212014b17173b", // 角色活动-2
|
||||
"1000": "f3f5090a8ec0b28f15805c9969aa6c4ec357", // 千星奇域常驻
|
||||
"20011": "a8d0a985efb4ed61eb2e73a86a57237bd116", // 千星奇域角色活动-男
|
||||
"20021": "57016dec6b768231ba1342c01935417a799b", // 千星奇域角色活动-女
|
||||
};
|
||||
|
||||
const tabNormal: ReadonlyArray<GroTab> = [
|
||||
{ label: "常驻祈愿", value: "200" },
|
||||
{ label: "角色活动祈愿", value: "301" },
|
||||
{ label: "武器活动祈愿", value: "302" },
|
||||
{ label: "角色活动祈愿-2", value: "400" },
|
||||
];
|
||||
const tabBeyond: ReadonlyArray<GroTab> = [
|
||||
{ label: "常驻颂愿", value: "1000", beyond: true },
|
||||
{ label: "活动颂愿-男", value: "20011", beyond: true },
|
||||
{ label: "活动颂愿-女", value: "20021", beyond: true },
|
||||
];
|
||||
|
||||
type GroTabKey = keyof typeof GachaIdMap;
|
||||
type GroTab = { label: string; value: string; beyond?: boolean };
|
||||
type GroIframeProps = { mode: "normal" | "beyond" };
|
||||
const props = defineProps<GroIframeProps>();
|
||||
|
||||
const { cookie, account } = storeToRefs(useUserStore());
|
||||
const authkey = ref<string>("");
|
||||
const link = ref<string>("");
|
||||
const poolTab = ref<GroTabKey>("200");
|
||||
const tabList = shallowRef<ReadonlyArray<GroTab>>(props.mode === "beyond" ? tabBeyond : tabNormal);
|
||||
|
||||
onMounted(async () => {
|
||||
link.value = await getUrl();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => poolTab.value,
|
||||
async () => {
|
||||
link.value = await getUrl();
|
||||
},
|
||||
);
|
||||
|
||||
async function getUrl(): Promise<string> {
|
||||
const path = "https://webstatic.mihoyo.com/hk4e/event/e20190909gacha-v3/index.html";
|
||||
const pathB = "https://webstatic.mihoyo.com/hk4e/event/e20250716gacha/index.html";
|
||||
const pathF = poolTab.value.length < 4 ? path : pathB;
|
||||
if (authkey.value === "") await refreshAuthkey();
|
||||
const param: Record<string, string> = {
|
||||
win_mode: "fullscreen",
|
||||
no_joypad_close: "1",
|
||||
authkey_ver: "1",
|
||||
sign_type: "2",
|
||||
auth_appid: "webview_gacha",
|
||||
gacha_id: GachaIdMap[poolTab.value],
|
||||
timestamp: Math.floor(Date.now() / 1000).toString(),
|
||||
lang: "zh-cn",
|
||||
device_type: "pc",
|
||||
region: account.value.region,
|
||||
authkey: authkey.value,
|
||||
game_biz: account.value.gameBiz,
|
||||
};
|
||||
const targetLink = new URL(pathF);
|
||||
for (const key in param) {
|
||||
targetLink.searchParams.append(key, param[key]);
|
||||
}
|
||||
return targetLink.toString();
|
||||
}
|
||||
|
||||
async function refreshAuthkey(): Promise<void> {
|
||||
if (!cookie.value || !account.value) {
|
||||
return;
|
||||
}
|
||||
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
|
||||
if (typeof authkeyRes === "string") {
|
||||
authkey.value = authkeyRes;
|
||||
} else {
|
||||
showSnackbar.error("获取authkey失败");
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.gro-iframe-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gro-ic-tabs {
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.gro-ic-window {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -12,22 +12,22 @@ import { computed, ref, watch } from "vue";
|
||||
|
||||
import GroDataView from "./gro-data-view.vue";
|
||||
|
||||
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.SingleTable> };
|
||||
type GachaOverviewProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGacha> };
|
||||
|
||||
const props = defineProps<GachaOverviewProps>();
|
||||
const newData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const newData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "100"),
|
||||
);
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const normalData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "200"),
|
||||
);
|
||||
const avatarData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const avatarData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "301"),
|
||||
);
|
||||
const weaponData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const weaponData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "302"),
|
||||
);
|
||||
const mixData = computed<Array<TGApp.Sqlite.GachaRecords.SingleTable>>(() =>
|
||||
const mixData = computed<Array<TGApp.Sqlite.GachaRecords.TableGacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "500"),
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ watch(
|
||||
.gro-o-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
grid-column-gap: 8px;
|
||||
column-gap: 8px;
|
||||
grid-template-columns: v-bind(cnCols); /* stylelint-disable-line value-keyword-case */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</v-data-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.SingleTable> };
|
||||
type GroTableProps = { modelValue: Array<TGApp.Sqlite.GachaRecords.TableGacha> };
|
||||
|
||||
const props = defineProps<GroTableProps>();
|
||||
|
||||
|
||||
@@ -62,9 +62,11 @@ import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
type UgoUidProps = { mode: "import" | "export" };
|
||||
type UgoUidItem = { uid: string; length: number; time: string };
|
||||
|
||||
const fpEmptyText = "点击选择文件路径";
|
||||
|
||||
const props = defineProps<UgoUidProps>();
|
||||
const visible = defineModel<boolean>();
|
||||
const fp = ref<string>("未选择");
|
||||
const fp = ref<string>(fpEmptyText);
|
||||
const dataRaw = shallowRef<TGApp.Plugins.UIGF.Schema4>();
|
||||
const data = shallowRef<Array<UgoUidItem>>([]);
|
||||
const selectedData = shallowRef<Array<UgoUidItem>>([]);
|
||||
@@ -91,7 +93,7 @@ async function refreshData(): Promise<void> {
|
||||
data.value = [];
|
||||
dataRaw.value = undefined;
|
||||
if (props.mode === "import") {
|
||||
fp.value = "未选择";
|
||||
fp.value = fpEmptyText;
|
||||
await handleImportData();
|
||||
} else {
|
||||
fp.value = await getDefaultSavePath();
|
||||
@@ -118,7 +120,7 @@ async function selectFile(): Promise<void> {
|
||||
}
|
||||
|
||||
async function handleImportData(): Promise<void> {
|
||||
if (fp.value === "未选择") return;
|
||||
if (fp.value === fpEmptyText) return;
|
||||
try {
|
||||
await showLoading.start("正在导入数据...", "正在验证数据...");
|
||||
const check = await verifyUigfData(fp.value, true);
|
||||
@@ -161,7 +163,7 @@ async function handleExportData(): Promise<void> {
|
||||
data.value = tmpData;
|
||||
}
|
||||
|
||||
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.SingleTable[]): UgoUidItem {
|
||||
function parseDataRaw(data: TGApp.Sqlite.GachaRecords.TableGacha[]): UgoUidItem {
|
||||
const timeList = data.map((item) => new Date(item.time).getTime());
|
||||
return {
|
||||
uid: data[0].uid,
|
||||
@@ -178,7 +180,7 @@ async function handleSelected(): Promise<void> {
|
||||
async function handleImport(): Promise<void> {
|
||||
if (!dataRaw.value) {
|
||||
showSnackbar.error("未获取到数据!");
|
||||
fp.value = "未选择";
|
||||
fp.value = fpEmptyText;
|
||||
return;
|
||||
}
|
||||
if (selectedData.value.length === 0) {
|
||||
@@ -234,6 +236,7 @@ async function handleExport(): Promise<void> {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
column-gap: 10px;
|
||||
@@ -249,6 +252,7 @@ async function handleExport(): Promise<void> {
|
||||
color: var(--tgc-od-white);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ugo-header {
|
||||
|
||||
@@ -278,6 +278,7 @@ async function autoSign(ck: TGApp.App.Account.Cookie, ch?: string): Promise<void
|
||||
}
|
||||
await TGLogger.Script("[米游币任务]正在执行打卡");
|
||||
const ckSign = { stoken: ck.stoken, stuid: ck.stuid, mid: ck.mid };
|
||||
await painterReq.forum.recent(26, 2, 1, undefined, 20, ckSign);
|
||||
const resp = await apiHubReq.sign(ckSign, 2, ch);
|
||||
if (resp.retcode !== 0) {
|
||||
if (resp.retcode !== 1034) {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<!-- TODO: 链接处理重构 -->
|
||||
<template>
|
||||
<div class="tp-image-box" v-if="localUrl !== undefined">
|
||||
<img
|
||||
:src="localUrl"
|
||||
@click="showOverlay = true"
|
||||
:alt="props.data.insert.image"
|
||||
:title="getImageTitle()"
|
||||
/>
|
||||
<img :src="localUrl" @click="showOverlay = true" :alt="oriUrl" :title="getImageTitle()" />
|
||||
<div
|
||||
class="act"
|
||||
@click.stop="showOri = true"
|
||||
@@ -16,7 +12,7 @@
|
||||
<v-icon size="16" color="white">mdi-magnify</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tp-image-load" :title="props.data.insert.image">
|
||||
<div v-else class="tp-image-load" :title="oriUrl">
|
||||
<v-progress-circular :indeterminate="true" color="primary" size="small" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
@@ -26,6 +22,7 @@
|
||||
v-model:link="localUrl"
|
||||
v-model:ori="showOri"
|
||||
v-model:bgColor="bgColor"
|
||||
v-model:format="imgExt"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -39,7 +36,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import VpOverlayImage from "./vp-overlay-image.vue";
|
||||
|
||||
export type TpImage = {
|
||||
insert: { image: string };
|
||||
insert: { image: string | TGApp.BBS.Post.Image };
|
||||
attributes?: {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -54,12 +51,14 @@ const appStore = useAppStore();
|
||||
const { imageQualityPercent } = storeToRefs(appStore);
|
||||
const props = defineProps<TpImageProps>();
|
||||
const showOverlay = ref<boolean>(false);
|
||||
const showOri = ref<boolean>(
|
||||
props.data.insert.image.endsWith(".gif") || imageQualityPercent.value === 100,
|
||||
);
|
||||
|
||||
const localUrl = ref<string>();
|
||||
const bgColor = ref<string>("transparent");
|
||||
|
||||
const oriUrl = ref<string>("");
|
||||
const imgExt = computed<string>(() => getImageExt());
|
||||
const showOri = ref<boolean>(imgExt.value === "gif" || imageQualityPercent.value === 100);
|
||||
|
||||
const imgWidth = computed<string>(() => {
|
||||
if (props.data.attributes === undefined) return "auto";
|
||||
if (props.data.attributes.width >= 690) return "100%";
|
||||
@@ -69,7 +68,8 @@ const imgWidth = computed<string>(() => {
|
||||
console.log("tp-image", props.data.insert.image, props.data.attributes);
|
||||
|
||||
onMounted(async () => {
|
||||
const link = appStore.getImageUrl(props.data.insert.image);
|
||||
oriUrl.value = miniImgUrl();
|
||||
const link = appStore.getImageUrl(oriUrl.value, imgExt.value);
|
||||
localUrl.value = await saveImgLocal(link);
|
||||
});
|
||||
|
||||
@@ -77,9 +77,12 @@ watch(
|
||||
() => showOri.value,
|
||||
async () => {
|
||||
if (!showOri.value) return;
|
||||
await showLoading.start("正在加载原图", props.data.insert.image);
|
||||
await showLoading.start("正在加载原图", oriUrl.value);
|
||||
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
|
||||
localUrl.value = await saveImgLocal(props.data.insert.image);
|
||||
const ext = getImageExt();
|
||||
if (!["png", "jpg", "jpeg", "gif", "webp"].includes(ext.toLowerCase())) {
|
||||
localUrl.value = await saveImgLocal(`${oriUrl.value}?format=jpg`);
|
||||
} else localUrl.value = await saveImgLocal(oriUrl.value);
|
||||
await showLoading.end();
|
||||
},
|
||||
);
|
||||
@@ -88,19 +91,49 @@ onUnmounted(() => {
|
||||
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
|
||||
});
|
||||
|
||||
function miniImgUrl(): string {
|
||||
let url: string;
|
||||
if (typeof props.data.insert.image === "string") {
|
||||
url = props.data.insert.image;
|
||||
} else {
|
||||
url = props.data.insert.image.url;
|
||||
}
|
||||
const link = new URL(url);
|
||||
return `${link.origin}${link.pathname}`;
|
||||
}
|
||||
|
||||
function getImageTitle(): string {
|
||||
if (props.data.attributes == undefined) return "";
|
||||
const res: string[] = [];
|
||||
res.push(`宽度:${props.data.attributes.width}px`);
|
||||
res.push(`高度:${props.data.attributes.height}px`);
|
||||
if (props.data.attributes.size) {
|
||||
const size = bytesToSize(props.data.attributes.size);
|
||||
res.push(`大小:${size}`);
|
||||
if (props.data.attributes) {
|
||||
res.push(`宽度:${props.data.attributes.width}px`);
|
||||
res.push(`高度:${props.data.attributes.height}px`);
|
||||
if (props.data.attributes.size) {
|
||||
const size = bytesToSize(props.data.attributes.size);
|
||||
res.push(`大小:${size}`);
|
||||
}
|
||||
res.push(`格式:${getImageExt()}`);
|
||||
return res.join("\n");
|
||||
}
|
||||
if (props.data.attributes.ext) {
|
||||
res.push(`格式:${props.data.attributes.ext}`);
|
||||
if (typeof props.data.insert.image !== "string") {
|
||||
res.push(`宽度:${props.data.insert.image.width}px`);
|
||||
res.push(`高度:${props.data.insert.image.height}px`);
|
||||
if (props.data.insert.image.size) {
|
||||
const size = bytesToSize(Number(props.data.insert.image.size));
|
||||
res.push(`大小:${size}`);
|
||||
}
|
||||
res.push(`格式:${getImageExt()}`);
|
||||
return res.join("\n");
|
||||
}
|
||||
return res.join("\n");
|
||||
return "";
|
||||
}
|
||||
|
||||
function getImageExt(): string {
|
||||
if (props.data.attributes && props.data.attributes.ext) return props.data.attributes.ext;
|
||||
if (typeof props.data.insert.image !== "string") {
|
||||
return props.data.insert.image.format;
|
||||
}
|
||||
const arr = oriUrl.value.split(".");
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TpUgcLevel from "@comp/viewPost/tp-ugc-level.vue";
|
||||
import TpUgcTag from "@comp/viewPost/tp-ugc-tag.vue";
|
||||
import type { Component } from "vue";
|
||||
|
||||
import TpBackupText from "./tp-backupText.vue";
|
||||
@@ -93,9 +95,11 @@ function getParsedText(data: TpTextType): Array<TpTextType> {
|
||||
|
||||
function getTpName(tp: TGApp.BBS.SctPost.Base): Component {
|
||||
if (tp.children) return TpTexts;
|
||||
if (typeof tp.insert === "undefined") return TpUnknown;
|
||||
if (typeof tp.insert === "string") return TpText;
|
||||
// game_user_info属于backup_text的一种,必须放在backup_text判断的前面
|
||||
// 判断特殊backup_text
|
||||
if ("game_user_info" in tp.insert) return TpUid;
|
||||
if ("ugc_master_tag" in tp.insert) return TpUgcTag;
|
||||
if ("backup_text" in tp.insert) {
|
||||
if (tp.insert.backup_text === "[游戏卡片]" && "reception_card" in tp.insert) return TpGameCard;
|
||||
if (tp.insert.backup_text === "[自定义表情]" && "custom_emoticon" in tp.insert) {
|
||||
@@ -105,6 +109,7 @@ function getTpName(tp: TGApp.BBS.SctPost.Base): Component {
|
||||
}
|
||||
if ("divider" in tp.insert) return TpDivider;
|
||||
if ("image" in tp.insert) return TpImage;
|
||||
if ("level" in tp.insert) return TpUgcLevel;
|
||||
if ("link_card" in tp.insert) return TpLinkCard;
|
||||
if ("mention" in tp.insert) return TpMention;
|
||||
if ("video" in tp.insert) return TpVideo;
|
||||
|
||||
151
src/components/viewPost/tp-ugc-level.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<!-- UGC关卡组件 TODO:UI调整-->
|
||||
<template>
|
||||
<div class="tul-card-box" @click="console.log(props.data)">
|
||||
<TMiImg
|
||||
@click="toLevel()"
|
||||
class="tul-cover"
|
||||
:src="props.data.insert.level.cover.url"
|
||||
alt="cover"
|
||||
/>
|
||||
<div class="tul-content">
|
||||
<div class="tul-top">
|
||||
<div class="tul-title">{{ props.data.insert.level.level_name }}</div>
|
||||
<div class="tul-sub">
|
||||
<div class="tul-info-item" title="游戏类型">
|
||||
<v-icon size="16" color="yellow">mdi-gamepad-variant</v-icon>
|
||||
<span>{{ props.data.insert.level.play_type }}</span>
|
||||
</div>
|
||||
<div class="tul-info-item" title="游玩人数">
|
||||
<v-icon size="16" color="blue">mdi-account-multiple</v-icon>
|
||||
<span>{{ props.data.insert.level.show_limit_play_num_str }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tul-attach" v-if="props.data.insert.level.level_attachment">
|
||||
<span>“</span>
|
||||
<span>{{ props.data.insert.level.level_attachment.content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tul-bottom">
|
||||
<div class="tul-info">
|
||||
<div class="tul-info-item" title="热度">
|
||||
<v-icon size="16" color="orange">mdi-fire</v-icon>
|
||||
<span>{{ props.data.insert.level.hot_score }}</span>
|
||||
</div>
|
||||
<div class="tul-info-item" title="点赞率">
|
||||
<v-icon size="16" color="pink">mdi-thumb-up</v-icon>
|
||||
<span>{{ props.data.insert.level.good_rate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import TGClient from "@utils/TGClient.js";
|
||||
|
||||
type TpUgcLevel = { insert: { level: TGApp.BBS.UGC.Level } };
|
||||
type TpUgcLevelProps = { data: TpUgcLevel };
|
||||
|
||||
const props = defineProps<TpUgcLevelProps>();
|
||||
|
||||
async function toLevel(): Promise<void> {
|
||||
let url = `https://act.miyoushe.com/ys/ugc_community/mx/#/pages/level-detail/index?`;
|
||||
url = `${url}id=${props.data.insert.level.level_id}®ion=${props.data.insert.level.region}`;
|
||||
await TGClient.open("web_act_thin", url.toString());
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tul-card-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
background: var(--box-bg-1);
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.tul-cover {
|
||||
max-width: 50%;
|
||||
max-height: 180px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
scale: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.tul-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.tul-top {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tul-title {
|
||||
color: var(--tgc-od-red);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tul-sub {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
color: var(--tgc-od-white);
|
||||
column-gap: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tul-attach {
|
||||
margin-top: 8px;
|
||||
color: var(--common-text-secondary);
|
||||
font-size: 16px;
|
||||
|
||||
span {
|
||||
color: var(--tgc-od-blue);
|
||||
|
||||
&:first-child {
|
||||
font-family: Genshin, sans-serif;
|
||||
font-size: 20px;
|
||||
line-height: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tul-bottom {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tul-info {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.tul-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
52
src/components/viewPost/tp-ugc-tag.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- 含 ugc_master_tag 的 backup_text -->
|
||||
<template>
|
||||
<span class="tut-box">
|
||||
{{ tagName }}
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import useAppStore from "@store/app.js";
|
||||
import { str2Color } from "@utils/toolFunc.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
|
||||
type TpUgcTag = {
|
||||
insert: {
|
||||
backup_text: string;
|
||||
ugc_master_tag: {
|
||||
id: number;
|
||||
name: string;
|
||||
is_available: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
type TpUgcTagProps = { data: TpUgcTag };
|
||||
|
||||
const props = defineProps<TpUgcTagProps>();
|
||||
const { theme } = storeToRefs(useAppStore());
|
||||
const tagName = computed<string>(() => props.data.insert.ugc_master_tag.name);
|
||||
const isDarkMode = computed<boolean>(() => theme.value === "dark");
|
||||
const tagColor = computed<string>(() => tag2Color(tagName.value, isDarkMode.value));
|
||||
const bgColor = computed<string>(() => `rgba(${tagColor.value.slice(4, -1)}, 0.18)`);
|
||||
|
||||
function tag2Color(str: string, isDarkMode: boolean = false): string {
|
||||
const adjust = isDarkMode ? 80 : -40;
|
||||
return str2Color(str, adjust);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tut-box {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 6px;
|
||||
border-radius: 12px;
|
||||
margin-right: 4px;
|
||||
background: v-bind(bgColor); /* stylelint-disable-line value-keyword-case */
|
||||
color: v-bind(tagColor); /* stylelint-disable-line value-keyword-case */
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
41
src/components/viewPost/tpc-tag.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -472,7 +472,7 @@ async function handleUser(): Promise<void> {
|
||||
}
|
||||
|
||||
.tpr-share-info.bottom {
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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": "单手剑"
|
||||
}
|
||||
|
||||
@@ -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": "单手剑"
|
||||
}
|
||||
|
||||
@@ -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": "法器"
|
||||
}
|
||||
|
||||
@@ -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": "法器"
|
||||
}
|
||||
|
||||
@@ -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": "单手剑"
|
||||
}
|
||||
|
||||