Compare commits

...

55 Commits

Author SHA1 Message Date
BTMuli
69cede4274 🚀 v0.7.5 2025-05-09 12:07:00 +08:00
BTMuli
20e942b8a8 🐛 添加时区处理功能
close #155
2025-05-09 12:03:53 +08:00
BTMuli
82c06c3727 ⬆️ 更新依赖项版本 2025-05-09 11:37:12 +08:00
BTMuli
c31cf7aa82 🚀 v0.7.4 2025-05-06 13:55:54 +08:00
BTMuli
ac46adf5cf 🍱 更新卡池 2025-05-06 13:49:41 +08:00
BTMuli
500b6442ac 🐛 修正尺寸计算 2025-05-05 22:07:56 +08:00
BTMuli
c9f5f86919 🐛 修正角色描述中的链接格式问题 2025-05-05 22:07:01 +08:00
BTMuli
27c79aec62 ⬆️ 更新依赖 2025-05-05 21:45:44 +08:00
BTMuli
e98c7a580f 🍱 更新5.6版本JSON资源(除卡池) 2025-05-05 21:40:14 +08:00
BTMuli
777b386b74 🍱 更新5.6版本图像资源 2025-05-05 21:00:12 +08:00
BTMuli
7c7fa1bb1a 🐛 调整浮窗显示逻辑 2025-04-28 13:33:59 +08:00
BTMuli
08dc712c1f 🐛 修复帖子列表刷新时的请求状态 2025-04-28 11:32:55 +08:00
目棃
07ae2ed0c6 🐛 仅用于测试环境 2025-04-16 20:38:58 +08:00
目棃
8d8f3bb07f 🐛 修正封面判断逻辑 2025-04-15 22:52:36 +08:00
目棃
b7d1d5dbd9 🔧 set erasableSyntaxOnly to false 2025-04-12 18:24:14 +08:00
目棃
5286d9914f 🐛 fix url err 2025-04-12 18:22:21 +08:00
目棃
7df0748816 ⬆️ 更新依赖版本和修正应用商店链接 2025-04-12 18:15:20 +08:00
目棃
da17f8cd03 🚀 v0.7.3 2025-04-11 22:22:05 +08:00
目棃
4b77c031c2 🚸 更新系列参数处理逻辑 2025-04-11 22:18:15 +08:00
目棃
3f3e941b68 🍱 更新卡池信息 2025-04-11 20:19:18 +08:00
目棃
2502c27ee5 💄 兑换码浮窗样式迭代 2025-04-11 20:06:09 +08:00
目棃
41fb2a43f7 ⬆️ 更新依赖 2025-04-09 15:47:36 +08:00
目棃
b7c49ce09b 🎨 改进代码结构 2025-04-09 15:45:29 +08:00
目棃
43f9e432cc 🚸 重构日期切换逻辑 2025-04-05 19:30:53 +08:00
目棃
74217e679e ⬆️ 更新依赖 2025-04-05 19:30:17 +08:00
目棃
06a57c9bcf 💄 添加图片数数据 2025-04-01 15:08:16 +08:00
目棃
1ce8c1fddf 💄 优化成就信息展示和样式 2025-04-01 14:54:29 +08:00
目棃
a1cd7ff840 💄 背景色同步 2025-04-01 14:29:51 +08:00
目棃
fce56dcf55 🔧 更新权限配置 2025-04-01 14:20:54 +08:00
目棃
7cf4516865 💄 更新配置文件和优化图片下载逻辑 2025-04-01 14:08:50 +08:00
目棃
01009b36eb ⬆️ 更新依赖 2025-04-01 13:40:55 +08:00
目棃
79067d6f77 🐛 修复显示异常 2025-04-01 13:39:03 +08:00
目棃
978c9c7778 💄 优化角色与武器信息展示 2025-04-01 10:07:30 +08:00
目棃
29542a737e 💄 优化角色信息展示与样式 2025-03-31 16:14:05 +08:00
目棃
0016ec1fc1 🚸 更新Yae链接 2025-03-31 15:55:02 +08:00
目棃
61a89348d1 🎨 优化逻辑 2025-03-31 15:52:09 +08:00
目棃
1c16bfc29a 💄 添加滤镜过渡效果 2025-03-31 15:48:15 +08:00
目棃
454b94f401 🚸 调整满好感区分 2025-03-31 15:37:00 +08:00
目棃
da6a29c5ac 🚸 提供Yae入口 2025-03-31 15:33:15 +08:00
目棃
40d46f41c3 🚸 成就系列未完成/完成区分 2025-03-31 15:24:02 +08:00
目棃
d79de499cf 🚸 显式成就隐藏 2025-03-31 14:52:39 +08:00
目棃
d11d4fe803 💄 调整触发dom 2025-03-31 14:47:04 +08:00
目棃
121e29ad4f 💄 pendant 禁止裁切 2025-03-31 11:51:53 +08:00
目棃
215f0c6500 ♻️ 重构传递数据 2025-03-31 11:49:44 +08:00
目棃
fd34b66148 🚸 select-mode下阻止所有点击 2025-03-31 11:28:26 +08:00
目棃
c154381c3a 🚸 调整层级 2025-03-28 20:03:52 +08:00
目棃
960a3442ff 🚸 调整hint 2025-03-28 14:45:22 +08:00
目棃
ff66dfca30 💄 调整层级 2025-03-28 14:36:35 +08:00
目棃
ef9469643e 💄 use v-icon instead of v-btn to disable active shadow 2025-03-28 14:20:01 +08:00
目棃
b61d235221 💄 调整回复浮窗样式 2025-03-28 14:15:44 +08:00
目棃
400a501f30 💄 调整通用backupText组件样式 2025-03-28 13:50:20 +08:00
目棃
a63edd81bd 🚸 支持其他分区兑换码获取 2025-03-28 13:35:12 +08:00
目棃
3bd1853008 存储分区信息 2025-03-28 12:02:28 +08:00
目棃
1d8e7ec35d 🧪 测试获取合集信息失败 2025-03-28 11:10:15 +08:00
目棃
211f689426 ⬆️ 更新依赖 2025-03-28 10:23:19 +08:00
115 changed files with 4305 additions and 2263 deletions

View File

@@ -55,7 +55,7 @@ jobs:
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.2.0
version: 10.10.0
- name: Install frontend dependencies
run: pnpm install

View File

@@ -16,7 +16,7 @@ jobs:
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.15.0
version: 10.10.0
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: "Qodana Scan"

View File

@@ -2,12 +2,42 @@
Author: 目棃
Description: CHANGELOG
Date: 2024-10-09
Update: 2025-03-27
Update: 2025-05-09
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2024-10-09 15:51:43`
>
> 更新于 `2025-03-27 14:45:54`
> 更新于 `2025-05-09 12:06:20`
## [0.7.5](https://github.com/BTMuli/TeyvatGuide/releases/v0.7.5) (2025-05-09)
- 🐛 处理UIGF时区异常 [`#155`](https://github.com/BTMuli/TeyvatGuide/issues/155)
## [0.7.4](https://github.com/BTMuli/TeyvatGuide/releases/v0.7.4) (2025-05-06)
- 🍱 更新5.6资源
- 🐛 修正首页卡池组件封面判断逻辑
- 🐛 修正主窗口尺寸计算
- 🐛 修正用户收藏帖子获取API链接
- 🐛 修复帖子列表刷新时的请求状态异常
- 🚸 调整咨讯页浮窗显示逻辑
- 🚸 移除正式环境下公告页标题点击产生的JSON子窗口
## [0.7.3](https://github.com/BTMuli/TeyvatGuide/releases/v0.7.3) (2025-04-11)
- 🍱 更新下半卡池信息
- 💄 兑换码浮窗样式迭代
- 💄 帖子卡片添加图片数数据
- 💄 优化成就信息展示和样式
- 💄 帖子图片浮窗背景色同步
- 💄 调整通用backupText组件样式
- 💄 调整回复浮窗样式
- 💄 角色详情调整满好感区分
- 💄 优化角色武器Wiki样式
- 🚸 重构素材日历日期切换逻辑
- 🚸 优化图片下载路径&提示
- 🚸 支持其他分区兑换码获取
- 🚸 收藏页select-mode下阻止所有点击
## [0.7.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.7.2) (2025-03-27)

View File

@@ -2,12 +2,12 @@
Author: 目棃
Description: 说明文档
Date: 2023-03-05
Update: 2025-03-27
Update: 2025-03-31
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
>
> 更新于 `2025-03-27 14:45:50`
> 更新于 `2025-03-31 15:53:10`
![](https://img.shields.io/github/last-commit/BTMuli/TeyvatGuide?style=for-the-badge) ![](https://img.shields.io/github/commits-since/BTMuli/TeyvatGuide/latest?include_prereleases&style=for-the-badge)
@@ -50,7 +50,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 米游社官方帖获取(支持通过 ID 获取)
- [x] 米游社各分区帖子获取(支持通过 ID 获取)
- [x] 米游社话题帖子获取(通过话题点击跳转)
- [x] 成就管理UIAF v1.1),支持 [`YaeAchievement`](https://github.com/HolographicHat/YaeAchievement) 导入
- [x] 成就管理UIAF v1.1),支持 [`Yae`](https://github.com/HolographicHat/Yae) 导入
- [x] 祈愿管理UIGF v3.0UIGF v4.0
- [x] 留影叙佳期画片查看
- [x] 帖子收藏
@@ -79,6 +79,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
- [x] 材料图鉴
- 应用功能:
- [x] 浅色/深色主题切换
- [x] 米游社 JSBridge

View File

@@ -1,9 +1,9 @@
{
"name": "teyvatguide",
"version": "0.7.2",
"version": "0.7.5",
"description": "Game Tool for GenshinImpact player",
"private": true,
"packageManager": "pnpm@10.6.5",
"packageManager": "pnpm@10.10.0",
"type": "module",
"scripts": {
"build": "tauri build",
@@ -70,79 +70,79 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@tauri-apps/api": "^2.4.0",
"@tauri-apps/plugin-deep-link": "^2.2.0",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-http": "^2.4.2",
"@tauri-apps/plugin-log": "^2.3.1",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-deep-link": "^2.2.1",
"@tauri-apps/plugin-dialog": "^2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
"@tauri-apps/plugin-http": "^2.4.3",
"@tauri-apps/plugin-log": "^2.4.0",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-os": "^2.2.1",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "^2.2.0",
"@tauri-apps/plugin-process": "^2.2.1",
"@tauri-apps/plugin-shell": "^2.2.1",
"@tauri-apps/plugin-sql": "^2.2.0",
"ajv": "^8.17.1",
"artplayer": "^5.2.2",
"artplayer": "^5.2.3",
"clipboard": "^2.0.11",
"color-convert": "^3.0.1",
"echarts": "^5.6.0",
"html2canvas": "^1.4.1",
"js-md5": "^0.8.3",
"jsencrypt": "^3.3.2",
"pinia": "^3.0.1",
"pinia": "^3.0.2",
"pinia-plugin-persistedstate": "^4.2.0",
"qrcode.vue": "^3.6.0",
"sass-embedded": "^1.86.0",
"sass-embedded": "^1.87.0",
"uuid": "^11.1.0",
"vue": "^3.5.13",
"vue-echarts": "^7.0.3",
"vue-json-pretty": "^2.4.0",
"vue-router": "^4.5.0",
"vuetify": "^3.7.19",
"vue-router": "^4.5.1",
"vuetify": "^3.8.3",
"wcag-color": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.23.0",
"@tauri-apps/cli": "2.4.0",
"@eslint/js": "^9.26.0",
"@tauri-apps/cli": "2.5.0",
"@types/color-convert": "^2.0.4",
"@types/fs-extra": "^11.0.4",
"@types/js-md5": "^0.7.2",
"@types/node": "^22.13.13",
"@types/node": "^22.15.14",
"@types/uuid": "^10.0.0",
"@typescript-eslint/parser": "^8.28.0",
"@typescript-eslint/parser": "^8.32.0",
"@vitejs/plugin-vue": "^5.2.3",
"concurrently": "^9.1.2",
"eslint": "^9.23.0",
"eslint": "^9.26.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsonc": "^2.20.0",
"eslint-plugin-prettier": "^5.2.5",
"eslint-plugin-vue": "^10.0.0",
"eslint-plugin-yml": "^1.17.0",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-vue": "^10.1.0",
"eslint-plugin-yml": "^1.18.0",
"fs-extra": "^11.3.0",
"globals": "^16.0.0",
"husky": "^9.1.7",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.5.0",
"oxlint": "^0.16.3",
"lint-staged": "^15.5.2",
"oxlint": "^0.16.9",
"prettier": "3.5.3",
"stylelint": "^16.16.0",
"stylelint": "^16.19.1",
"stylelint-config-idiomatic-order": "^10.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": "^6.0.4",
"stylelint-order": "^7.0.0",
"stylelint-prettier": "^5.0.3",
"stylelint-scss": "^6.11.1",
"tsx": "^4.19.3",
"typescript": "^5.8.2",
"typescript-eslint": "^8.28.0",
"vite": "^6.2.3",
"vite-plugin-vue-devtools": "^7.7.2",
"vite-plugin-vuetify": "^2.1.0",
"vue-eslint-parser": "^10.1.1",
"vue-tsc": "^2.2.8",
"stylelint-scss": "^6.12.0",
"tsx": "^4.19.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.0",
"vite": "^6.3.5",
"vite-plugin-vue-devtools": "^7.7.6",
"vite-plugin-vuetify": "^2.1.1",
"vue-eslint-parser": "^10.1.3",
"vue-tsc": "^2.2.10",
"yaml-eslint-parser": "^1.3.0"
}
}

2790
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

819
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "TeyvatGuide"
version = "0.7.2"
version = "0.7.5"
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.1.0", features = [] }
tauri-build = { version = "2.2.0", features = [] }
[dependencies]
chrono = "0.4.40"
log = "0.4.26"
chrono = "0.4.41"
log = "0.4.27"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tauri = { version = "2.4.0", features = [] }
tauri-utils = "2.3.0"
tauri = { version = "2.5.1", features = [] }
tauri-utils = "2.4.0"
url = "2.5.4"
walkdir = "2.5.0"

View File

@@ -4,35 +4,36 @@
"description": "Capability for the sub window",
"windows": ["Sub_window"],
"permissions": [
"core:app:allow-version",
"core:app:default",
"dialog:allow-save",
"core:app:allow-version",
"dialog:default",
"core:event:allow-listen",
"dialog:allow-save",
"core:event:default",
"fs:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"core:path:allow-resolve-directory",
"core:event:allow-listen",
"core:path:default",
"shell:allow-open",
"shell:default",
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"core:webview:allow-set-webview-zoom",
"core:path:allow-resolve-directory",
"core:webview:default",
"core:webview:allow-set-webview-zoom",
"core:window:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-set-always-on-top",
"core:window:allow-set-focus",
"core:window:allow-set-fullscreen",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-show",
"core:window:allow-set-always-on-top",
"core:window:default",
"fs:default",
"http:allow-fetch",
"log:default",
"log:allow-log",
"opener:default",
"shell:default",
"shell:allow-open",
"sql:default",
"sql:allow-execute",
"sql:allow-load",
{ "identifier": "fs:allow-exists", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-mkdir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-dir", "allow": [{ "path": "**" }] },
@@ -40,6 +41,7 @@
{ "identifier": "fs:allow-remove", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-write-file", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-write-text-file", "allow": [{ "path": "**" }] },
{ "identifier": "opener:allow-open-path", "allow": [{ "path": "**" }] },
{
"identifier": "http:default",
"allow": [

View File

@@ -4,40 +4,40 @@
"description": "Capability for the main window",
"windows": ["TeyvatGuide"],
"permissions": [
"core:app:allow-version",
"core:app:default",
"dialog:allow-save",
"dialog:default",
"core:event:allow-listen",
"core:app:allow-version",
"core:event:default",
"fs:default",
"http:allow-fetch",
"log:allow-log",
"log:default",
"core:path:allow-resolve-directory",
"core:event:allow-listen",
"core:path:default",
"process:allow-exit",
"process:default",
"shell:allow-execute",
"shell:allow-open",
"shell:default",
"sql:allow-load",
"sql:allow-execute",
"sql:default",
"core:path:allow-resolve-directory",
"core:webview:default",
"core:webview:allow-create-webview-window",
"core:webview:allow-set-webview-zoom",
"core:webview:default",
"core:window:default",
"core:window:allow-center",
"core:window:allow-close",
"core:window:allow-destroy",
"core:window:allow-set-size",
"core:window:default",
"core:window:allow-is-minimized",
"core:window:allow-set-title",
"core:window:allow-set-focus",
"core:window:allow-set-size",
"core:window:allow-set-title",
"core:window:allow-show",
"core:window:allow-unminimize",
"dialog:default",
"dialog:allow-save",
"fs:default",
"http:allow-fetch",
"log:default",
"log:allow-log",
"opener:default",
"process:default",
"process:allow-exit",
"shell:default",
"shell:allow-open",
"shell:allow-execute",
"sql:default",
"sql:allow-load",
"sql:allow-execute",
{ "identifier": "fs:allow-exists", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-mkdir", "allow": [{ "path": "**" }] },
{ "identifier": "fs:allow-read-dir", "allow": [{ "path": "**" }] },

View File

@@ -1,6 +1,6 @@
//! @file src/client/menu.rs
//! @desc 客户端菜单模块,负责操作米游社客户端菜单
//! @since Beta v0.5.2
//! @since Beta v0.7.4
use crate::client::utils;
use tauri::menu::{Menu, MenuBuilder, MenuEvent, MenuItemBuilder, Submenu, SubmenuBuilder};
@@ -124,7 +124,7 @@ fn handle_menu_open_post(app_handle: &Window) {
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
if window.is_some() {
window.unwrap().eval(&execute_js).ok().unwrap();
window.unwrap().eval(execute_js).ok().unwrap();
}
}
@@ -139,7 +139,7 @@ fn handle_menu_retry(app_handle: &Window) {
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
if window.is_some() {
window.unwrap().eval(&execute_js).ok().unwrap();
window.unwrap().eval(execute_js).ok().unwrap();
}
}
@@ -154,7 +154,7 @@ fn handle_menu_mock_touch(app_handle: &Window) {
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
if window.is_some() {
window.unwrap().eval(&execute_js).ok().unwrap();
window.unwrap().eval(execute_js).ok().unwrap();
}
}
@@ -169,7 +169,7 @@ fn handle_menu_remove_overlay(app_handle: &Window) {
await window.__TAURI__.event.emit('post_mhy_client',JSON.stringify(arg));
})()"#;
if window.is_some() {
window.unwrap().eval(&execute_js).ok().unwrap();
window.unwrap().eval(execute_js).ok().unwrap();
}
}

View File

@@ -1,6 +1,6 @@
//! @file src/client/mod.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.7.0
//! @since Beta v0.7.3
mod menu;
mod utils;
@@ -8,7 +8,7 @@ mod utils;
use tauri::{AppHandle, Manager, WebviewWindowBuilder};
use tauri_utils::config::WebviewUrl;
static BBS_VERSION: &'static str = "2.82.0";
static BBS_VERSION: &'static str = "2.85.1";
#[tauri::command]
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {

View File

@@ -1,6 +1,6 @@
//! @file src/commands.rs
//! @desc 命令模块,负责处理命令
//! @since Beta v0.7.2
//! @since Beta v0.7.4
use tauri::{AppHandle, Emitter, Manager, WebviewWindowBuilder};
use tauri_utils::config::{WebviewUrl, WindowConfig};
@@ -52,7 +52,7 @@ pub async fn create_window(
pub async fn execute_js(app_handle: AppHandle, label: String, js: String) {
let window = app_handle.get_webview_window(&label);
if window.is_some() {
window.unwrap().eval(&js).unwrap();
window.unwrap().eval(js).unwrap();
}
}

View File

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

View File

@@ -42,6 +42,7 @@ onUnmounted(() => window.removeEventListener("scroll", handleScroll));
<style lang="css" scoped>
.back-top {
position: fixed;
z-index: 1;
right: 10px;
bottom: 10px;
width: 40px;

View File

@@ -5,7 +5,7 @@
<span>{{ navItem.name }}</span>
</div>
<div v-if="hasNav" class="tgn-nav">
<v-btn size="25" @click="tryGetCode" title="查看兑换码" icon="mdi-code-tags-check"></v-btn>
<v-icon size="25" @click="tryGetCode" title="查看兑换码">mdi-code-tags-check</v-icon>
</div>
<ToLivecode v-model="showOverlay" :data="codeData" v-model:actId="actId" />
</div>
@@ -38,7 +38,8 @@ const showOverlay = ref<boolean>(false);
const actId = ref<string>();
const hasNav = computed<TGApp.BBS.Navigator.Navigator | undefined>(() => {
return nav.value.find((item) => item.name === "前瞻直播" || item.name === "直播兑换码");
const liveNames = ["前瞻直播", "前瞻节目", "直播兑换码"];
return nav.value.find((item) => liveNames.includes(item.name));
});
onMounted(async () => await loadNav());
@@ -53,7 +54,6 @@ async function loadNav(): Promise<void> {
}
async function tryGetCode(): Promise<void> {
if (props.modelValue !== 2) return;
if (!hasNav.value) return;
const actIdFind = new URL(hasNav.value.app_path).searchParams.get("act_id");
if (!actIdFind) {

View File

@@ -1,5 +1,11 @@
<template>
<div v-if="card" :id="`post-card-${card.postId}`" class="tpc-card">
<div
v-if="card"
:id="`post-card-${card.postId}`"
class="tpc-card"
:class="{ 'select-mode': props.selectMode }"
@click="trySelect()"
>
<div class="tpc-top">
<div class="tpc-cover" @click="toPost()">
<TMiImg :src="card.cover" alt="cover" :ori="true" v-if="card.cover !== ''" />
@@ -11,8 +17,16 @@
<span>{{ card.subtitle }}</span>
</div>
</div>
<div
v-else-if="props.modelValue.post.images.length > 1"
class="tpc-image-cnt"
:title="`图片数:${props.modelValue.post.images.length}`"
>
<v-icon size="10">mdi-folder-multiple-image</v-icon>
<span>{{ props.modelValue.post.images.length }}</span>
</div>
</div>
<div class="tpc-title" :title="card.title" @click="shareCard">{{ card.title }}</div>
<div class="tpc-title" :title="card.title" @click="shareCard()">{{ card.title }}</div>
</div>
<div class="tpc-mid" v-if="card.user !== null">
<TpAvatar
@@ -80,9 +94,10 @@
<span>{{ card.forum.name }}</span>
</div>
<v-checkbox-btn
v-model="isSelected"
v-if="props.selectMode"
class="tpc-select"
@click="emits('onSelected', props.modelValue.post.post_id)"
@click.stop="trySelect()"
data-html2canvas-ignore
/>
<div class="tpc-info-id" v-else>{{ props.modelValue.post.post_id }}</div>
@@ -93,7 +108,7 @@ 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 { emit } from "@tauri-apps/api/event";
import { computed, onMounted, shallowRef, watch } from "vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { generateShareImg } from "@/utils/TGShare.js";
@@ -107,7 +122,7 @@ type TPostCardProps = {
};
type TPostCardEmits = {
(e: "onSelected", v: string): void;
(e: "onUserClick", v: TGApp.BBS.Post.User): void;
(e: "onUserClick", u: TGApp.BBS.Post.User, g: number): void;
};
type RenderForum = { name: string; icon: string; id: number };
type RenderStatus = { stat: number; label: string; color: string };
@@ -136,6 +151,7 @@ const stats: Readonly<Array<RenderStatus>> = [
const route = useRoute();
const router = useRouter();
const props = withDefaults(defineProps<TPostCardProps>(), { selectMode: false });
const isSelected = ref<boolean>(false);
const emits = defineEmits<TPostCardEmits>();
const card = shallowRef<RenderCard>();
@@ -151,7 +167,13 @@ watch(
async () => (card.value = getPostCard(props.modelValue)),
);
function trySelect(): void {
if (props.selectMode) emits("onSelected", props.modelValue.post.post_id);
isSelected.value = !isSelected.value;
}
async function toPost(): Promise<void> {
if (props.selectMode) return;
if (!card.value) return;
if (route.name !== "帖子详情") {
await createPost(card.value);
@@ -231,7 +253,7 @@ function getPostCard(item: TGApp.BBS.Post.FullData): RenderCard {
}
async function shareCard(): Promise<void> {
console.log(props.modelValue);
if (props.selectMode) return;
if (!card.value) return;
const shareDom = document.querySelector<HTMLDivElement>(`#post-card-${card.value.postId}`);
if (shareDom === null) {
@@ -243,18 +265,21 @@ async function shareCard(): Promise<void> {
}
async function toTopic(topic: TGApp.BBS.Post.Topic): Promise<void> {
if (props.selectMode) return;
const gid = props.modelValue.post.game_id;
await emit("active_deep_link", `router?path=/posts/topic/${gid}/${topic.id}`);
}
async function toForum(forum: RenderForum): Promise<void> {
if (props.selectMode) return;
const gid = props.modelValue.post.game_id;
await emit("active_deep_link", `router?path=/posts/forum/${gid}/${forum.id}`);
}
function onUserClick(): void {
if (props.selectMode) return;
if (!card.value || card.value.user === null) return;
emits("onUserClick", card.value.user);
emits("onUserClick", card.value.user, props.modelValue.post.game_id);
}
</script>
<style lang="scss" scoped>
@@ -272,6 +297,10 @@ function onUserClick(): void {
justify-content: space-between;
border-radius: 4px;
row-gap: 8px;
&.select-mode {
cursor: pointer;
}
}
.dark .tpc-card {
@@ -454,6 +483,23 @@ function onUserClick(): void {
font-size: 12px;
}
.tpc-image-cnt {
position: absolute;
bottom: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8px;
border-top-left-radius: 12px;
column-gap: 2px;
font-size: 12px;
color: var(--tgc-white-1);
background: var(--tgc-od-blue);
opacity: 0.8;
box-shadow: -2px -2px 8px var(--tgc-dark-1);
}
.tpc-status {
position: relative;
display: flex;

View File

@@ -205,7 +205,7 @@ const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
if (briefInfo.value && briefInfo.value.nickname) return briefInfo.value;
return { nickname: "未登录", uid: "-1", desc: "请扫码登录", avatar: "/source/UI/lumine.webp" };
});
const themeTitle = computed<string>(() => (theme.value === "default" ? "夜间模式" : "日间模式"));
const themeTitle = computed<string>(() => (theme.value === "default" ? "深色模式" : "浅色模式"));
onMounted(async () => {
themeListener = await event.listen<string>("readTheme", (e: Event<string>) => {

View File

@@ -13,32 +13,27 @@
/>
</div>
<div class="tolc-info">ActID:{{ props.actId }}</div>
<v-list-item v-for="(item, index) in props.data" :key="index">
<template #title>
{{ item.code === "" ? "暂无兑换码" : item.code }}
</template>
<template #subtitle>
<div v-html="item.title"></div>
<div v-for="(item, index) in props.data" :key="index" class="tolc-list-box">
<div class="tolc-list-icon">
<img v-if="item.img === ''" src="/source/UI/empty.webp" alt="empty" />
<TMiImg :src="item.img" :ori="true" v-else alt="award" />
</div>
<div class="tolc-list-info">
<span>{{ item.code === "" ? "暂无兑换码" : item.code }}</span>
<span v-html="item.title" />
<span title="开放时间">{{ timestampToDate(Number(item.to_get_time) * 1000) }}</span>
</template>
<template #prepend>
<div class="tolc-icon">
<img v-if="item.img === ''" src="/source/UI/empty.webp" alt="empty" />
<TMiImg :src="item.img" :ori="true" v-else alt="award" />
</div>
</template>
<template #append>
</div>
<div class="tolc-list-btn">
<v-btn
size="small"
:disabled="item.code === ''"
@click="copy(item.code)"
icon="mdi-content-copy"
variant="outlined"
class="tolc-btn"
data-html2canvas-ignore
/>
</template>
</v-list-item>
</div>
</div>
</div>
</TOverlay>
</template>
@@ -78,11 +73,16 @@ async function shareImg(): Promise<void> {
.tolc-box {
position: relative;
display: flex;
width: 340px;
padding: 10px;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10px 10px 20px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
background: var(--app-page-bg);
row-gap: 12px;
}
.tolc-title {
@@ -104,14 +104,29 @@ async function shareImg(): Promise<void> {
font-size: 10px;
}
.tolc-icon {
.tolc-list-box {
position: relative;
display: flex;
width: 100%;
box-sizing: border-box;
align-items: flex-start;
justify-content: flex-start;
padding: 8px;
border: 1px solid var(--common-shadow-1);
border-radius: 4px;
background: var(--box-bg-1);
color: var(--box-text-1);
column-gap: 12px;
}
.tolc-list-icon {
position: relative;
display: flex;
width: 40px;
height: 40px;
flex-shrink: 0;
align-items: center;
justify-content: center;
margin-right: 10px;
img {
width: 100%;
@@ -120,7 +135,23 @@ async function shareImg(): Promise<void> {
}
}
.tolc-btn {
margin-left: 10px;
.tolc-list-info {
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
:first-child {
font-family: var(--font-title);
}
:last-child {
font-size: 12px;
}
}
.tolc-list-btn {
margin-left: auto;
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<v-list class="top-nc-box" @click="emit('selected', props.data)">
<v-list
class="top-nc-box"
@click="emit('selected', props.data)"
:class="{ grey: !props.finish }"
:title.attr="props.data.name"
>
<v-list-item :title="props.data.name">
<template #subtitle>
<span class="desc" :title="props.data.desc">{{ props.data.desc }}</span>
@@ -17,10 +22,12 @@
<script lang="ts" setup>
import { computed } from "vue";
type TopNameCardProps = { data: TGApp.App.NameCard.Item };
type TopNameCardProps = { data: TGApp.App.NameCard.Item; finish?: boolean };
type TopNameCardEmits = (e: "selected", v: TGApp.App.NameCard.Item) => void;
const props = defineProps<TopNameCardProps>();
const props = withDefaults(defineProps<TopNameCardProps>(), {
finish: true,
});
const emit = defineEmits<TopNameCardEmits>();
const bgImage = computed<string>(() => {
@@ -45,6 +52,15 @@ const bgImage = computed<string>(() => {
background-repeat: no-repeat;
cursor: pointer;
font-family: var(--font-title);
transition: filter 0.5s ease-in-out;
&.grey {
filter: grayscale(1);
}
&.grey:hover {
filter: grayscale(0);
}
}
.dark .top-nc-box {

View File

@@ -90,7 +90,7 @@ defineExpose({ displayBox });
.func-snackbar-container {
position: fixed;
z-index: 999;
z-index: 9999;
bottom: 0;
left: 0;
display: flex;

View File

@@ -47,7 +47,7 @@ async function toGithub(): Promise<void> {
}
async function toStore(): Promise<void> {
await openUrl("https://www.microsoft.com/store/productId/9NLBNNNBNSJN");
await openUrl("https://apps.microsoft.com/detail/9NLBNNNBNSJN");
}
async function toSite(): Promise<void> {

View File

@@ -14,7 +14,7 @@
rounded
class="tc-btn"
:class="{ selected: text.week === btnNow, today: text.week === weekNow }"
@click="btnNow = text.week"
@click="switchDay(text.week)"
>
{{ text.text }}
</v-btn>
@@ -87,6 +87,11 @@ onMounted(() => {
emits("success");
});
function switchDay(day: number): void {
btnNow.value = day;
page.value = 1;
}
function switchType(): void {
selectedType.value = selectedType.value === "character" ? "weapon" : "character";
page.value = 1;

View File

@@ -95,7 +95,7 @@ onMounted(async () => {
async function loadCover(): Promise<void> {
const postId: number | undefined = Number(props.pool.activity_url.split("/").pop()) || undefined;
if (postId === undefined || isNaN(postId)) return;
if (poolCover.value && poolCover.value[postId]) {
if (poolCover.value && postId in poolCover.value && poolCover.value[postId] !== "") {
cover.value = poolCover.value[postId];
return;
}
@@ -105,7 +105,12 @@ async function loadCover(): Promise<void> {
await TGLogger.Error(`[PhPoolCard][${resp.retcode}] ${resp.message}`);
return;
}
cover.value = resp.post.cover;
let coverGet;
if (resp.cover) coverGet = resp.cover.url;
else if (resp.post.cover && resp.post.cover !== "") coverGet = resp.post.cover;
else if (resp.post.images.length > 0) coverGet = resp.post.images[0];
else coverGet = "";
cover.value = coverGet;
if (!poolCover.value) poolCover.value = { [postId]: resp.post.cover };
else poolCover.value[postId] = resp.post.cover;
}

View File

@@ -25,19 +25,21 @@ import { storeToRefs } from "pinia";
import { onMounted, shallowRef } from "vue";
import { type NewsType, useAppStore } from "@/store/modules/app.js";
import apiHubReq from "@/web/request/apiHubReq.js";
import useBBSStore from "@/store/modules/bbs.js";
type ChannelItem = { icon: string; title: string; gid: number };
type ToChannelProps = { gid?: string; curType?: string };
const bbsStore = useBBSStore();
const { recentNewsType } = storeToRefs(useAppStore());
const { gameList } = storeToRefs(bbsStore);
const channelList = shallowRef<Array<ChannelItem>>();
const props = defineProps<ToChannelProps>();
const visible = defineModel<boolean>({ default: false });
onMounted(async () => {
const allGames = await apiHubReq.game();
channelList.value = allGames.map((i) => ({ icon: i.app_icon, title: i.name, gid: i.id }));
if (gameList.value.length === 0) await bbsStore.refreshGameList();
channelList.value = gameList.value.map((i) => ({ icon: i.app_icon, title: i.name, gid: i.id }));
});
async function toChannel(item: ChannelItem): Promise<void> {

View File

@@ -7,7 +7,13 @@
<div class="twc-bi-title">
<span>{{ data.name }}</span>
<span>{{ data.title }}</span>
<span @click="toWiki()"><v-icon>mdi-link</v-icon></span>
<img
title="前往观测枢"
alt="observer"
@click="toWiki()"
v-if="props.item.contentId !== 0"
src="/platforms/mhy/observer.webp"
/>
</div>
<div class="twc-bi-desc">{{ data.description }}</div>
</div>
@@ -168,6 +174,10 @@ async function toBirth(date: string): Promise<void> {
}
</script>
<style lang="css" scoped>
:deep(.v-expansion-panel-title) {
background: var(--common-shadow-1);
}
.twc-box {
display: flex;
flex-direction: column;
@@ -194,11 +204,20 @@ async function toBirth(date: string): Promise<void> {
.twc-bi-title {
display: flex;
width: fit-content;
align-items: center;
justify-content: center;
color: var(--common-text-title);
column-gap: 10px;
column-gap: 8px;
font-family: var(--font-title);
font-size: 20px;
img {
width: 20px;
height: 20px;
cursor: pointer;
object-fit: contain;
}
}
.twc-bi-title :last-child {

View File

@@ -5,7 +5,13 @@
<div class="tww-brief-info">
<div class="tww-brief-title">
<span>{{ data.name }}</span>
<span @click="toWiki()"><v-icon>mdi-link</v-icon></span>
<img
title="前往观测枢"
alt="observer"
@click="toWiki()"
v-if="props.item.contentId !== 0"
src="/platforms/mhy/observer.webp"
/>
</div>
<v-rating
v-if="data.affix"
@@ -105,7 +111,11 @@ async function toWiki(): Promise<void> {
await createObc(props.item.contentId, props.item.name);
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
:deep(.v-expansion-panel-title) {
background: var(--common-shadow-1);
}
.tww-box {
display: flex;
flex-direction: column;
@@ -126,12 +136,21 @@ async function toWiki(): Promise<void> {
}
.tww-brief-title {
width: fit-content;
display: flex;
align-items: center;
justify-content: center;
color: var(--common-text-title);
column-gap: 10px;
column-gap: 8px;
font-family: var(--font-title);
font-size: 20px;
img {
width: 20px;
height: 20px;
object-fit: contain;
cursor: pointer;
}
}
.tww-brief-info :last-child {

View File

@@ -1,7 +1,7 @@
<template>
<div class="tua-al-container">
<div v-if="ncData !== undefined">
<TopNameCard :data="ncData" @selected="showNc = true" />
<TopNameCard :data="ncData" @selected="showNc = true" :finish="isFinish" />
</div>
<v-virtual-scroll :items="renderAchi" :item-height="60" class="tua-al-list">
<template #default="{ item }">
@@ -48,18 +48,22 @@ type TuaAchiListProps = {
isSearch: boolean;
};
type TuaAchiListEmits = {
(e: "update:series", v: number): void;
(e: "update:series", v: number | undefined): void;
(e: "update:isSearch", v: boolean): false;
};
const props = defineProps<TuaAchiListProps>();
const emits = defineEmits<TuaAchiListEmits>();
const nameCard = ref<string>();
const showNc = ref<boolean>(false);
const showOverlay = ref<boolean>(false);
const isFinish = ref<boolean>(false);
const ncData = shallowRef<TGApp.App.NameCard.Item>();
const achievements = shallowRef<Array<TGApp.Sqlite.Achievement.RenderAchi>>([]);
const selectedAchi = shallowRef<TGApp.Sqlite.Achievement.RenderAchi>();
const renderAchi = computed<Array<TGApp.Sqlite.Achievement.RenderAchi>>(() => {
if (props.hideFin) return achievements.value.filter((a) => !a.isCompleted);
return achievements.value;
@@ -82,14 +86,16 @@ async function searchAchi(): Promise<void> {
achievements.value = await TSUserAchi.searchAchi(props.uid, props.search);
if (achievements.value.length > 0) {
showSnackbar.success(`成功获取${achievements.value.length}条成就`);
emits("update:series", -1);
emits("update:series", undefined);
}
emits("update:isSearch", false);
}
async function loadAchi(): Promise<void> {
if (props.isSearch) return;
if (props.isSearch || props.series === undefined) return;
achievements.value = await TSUserAchi.getAchievements(props.uid, props.series);
const ov = await TSUserAchi.getOverview(props.uid, props.series);
isFinish.value = ov.fin === ov.total;
if (!selectedAchi.value && achievements.value.length > 0) {
selectedAchi.value = achievements.value[0];
} else if (selectedAchi.value !== undefined && achievements.value.length > 0) {
@@ -149,7 +155,6 @@ function switchAchiInfo(next: boolean): void {
.tua-al-container {
display: flex;
width: 100%;
height: fit-content;
max-height: 100%;
flex-direction: column;
overflow-y: auto;

View File

@@ -1,21 +1,23 @@
<template>
<div class="achi-container" :title="getAchiTitle()">
<div class="achi-container" :title="getAchiTitle()" @click="selectAchi()">
<div class="achi-version">v{{ data.version }}</div>
<div class="achi-pre">
<div class="achi-pre-icon">
<v-icon v-if="!data.isCompleted" color="var(--tgc-blue-3)" @click="setAchiStat(true)">
<v-icon v-if="!data.isCompleted" color="var(--tgc-blue-3)" @click.stop="setAchiStat(true)">
mdi-circle
</v-icon>
<v-icon v-else class="achi-finish" @click="setAchiStat(false)">
<v-icon v-else class="achi-finish" @click.stop="setAchiStat(false)">
<img alt="finish" src="/source/UI/finish.webp" />
</v-icon>
</div>
<div class="achi-pre-info" @click="selectAchi()">
<span>
<div class="achi-pre-info">
<div class="achi-pre-info__title">
<span>{{ data.name }}</span>
<span v-if="data.progress !== 0">{{ data.progress }}</span>
</span>
<span>{{ data.description }}</span>
<span v-if="data.progress !== 0" class="achi-pre-info__progress">
{{ data.progress }}
</span>
</div>
<div class="achi-pre-info__desc">{{ data.description }}</div>
</div>
</div>
<div class="achi-append">
@@ -155,24 +157,30 @@ async function setAchiStat(stat: boolean): Promise<void> {
align-items: flex-start;
justify-content: center;
text-align: left;
}
.achi-pre-info :nth-child(1) {
display: flex;
align-items: flex-end;
column-gap: 4px;
font-family: var(--font-title);
font-size: 14px;
}
&__title {
display: flex;
align-items: flex-end;
column-gap: 4px;
font-family: var(--font-title);
font-size: 14px;
}
.achi-pre-info :nth-child(1) :nth-child(2) {
color: var(--tgc-blue-2);
font-size: 12px;
}
&__desc {
font-size: 12px;
opacity: 0.8;
}
.achi-pre-info :nth-child(2) {
font-size: 12px;
opacity: 0.8;
&__progress {
@include github-styles.github-tag-dark-gen(#00aeec);
padding: 0 4px;
border-radius: 4px;
height: 21px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
}
.achi-append-icon span {

View File

@@ -3,16 +3,27 @@
class="tuas-card"
@click="selectSeries"
v-if="data"
:class="{ 'tuas-selected': props.cur === props.series }"
:title="data.name"
:class="{
'tuas-selected': props.cur === props.series,
'tuas-radius': showCard,
}"
>
<div class="tuas-version">v{{ data.version }}</div>
<div class="tuas-reward" v-if="showCard">
<img
:class="{ finish: progress === 100 }"
alt="card"
:src="`/WIKI/nameCard/bg/${data.card}.webp`"
/>
</div>
<div class="tuas-icon">
<img alt="icon" :src="`/icon/achievement/${data.icon}.webp`" />
<v-progress-circular
class="progress"
bg-color="var(--tgc-od-white)"
color="var(--tgc-yellow-2)"
:model-value="`${(overview.fin / overview.total) * 100}`"
:model-value="progress"
/>
</div>
<div class="tuas-content">
@@ -32,6 +43,7 @@ import { AppAchievementSeriesData } from "@/data/index.js";
type TuaSeriesProps = { uid: number; series: number; cur: number };
type TuaSeriesEmits = (e: "selectSeries", v: number) => void;
let achiListener: UnlistenFn | null = null;
const props = defineProps<TuaSeriesProps>();
const emits = defineEmits<TuaSeriesEmits>();
@@ -39,7 +51,14 @@ const overview = shallowRef<TGApp.Sqlite.Achievement.Overview>({ fin: 0, total:
const data = computed<TGApp.App.Achievement.Series | undefined>(() =>
AppAchievementSeriesData.find((s) => s.id === props.series),
);
let achiListener: UnlistenFn | null = null;
const progress = computed<number>(() => {
if (overview.value.total === 0) return 0;
return Math.round((overview.value.fin / overview.value.total) * 100);
});
const showCard = computed<boolean>(() => {
if (data.value === undefined) return false;
return data.value.card !== "";
});
onMounted(async () => {
await refreshOverview();
@@ -97,6 +116,19 @@ function selectSeries(): void {
&.tuas-selected {
background: var(--box-bg-1);
}
&.tuas-radius {
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
}
&:hover {
.tuas-reward {
img {
filter: unset;
}
}
}
}
.dark .tuas-card {
@@ -120,6 +152,27 @@ function selectSeries(): void {
font-family: var(--font-title);
font-size: 10px;
text-align: center;
z-index: 3;
}
.tuas-reward {
position: absolute;
top: -1px;
right: -2px;
height: 62px;
z-index: 0;
img {
height: 100%;
object-fit: contain;
opacity: 0.3;
filter: grayscale(1);
transition: filter 0.5s ease-in-out;
&.finish {
filter: unset;
}
}
}
.tuas-icon {
@@ -131,6 +184,7 @@ function selectSeries(): void {
border-radius: 50%;
box-sizing: border-box;
background: var(--tgc-dark-7);
z-index: 1;
img {
width: 100%;
@@ -148,6 +202,8 @@ function selectSeries(): void {
}
.tuas-content {
position: relative;
z-index: 1;
display: flex;
width: 100%;
flex-flow: column wrap;

View File

@@ -18,8 +18,8 @@
</div>
</div>
<div class="tua-abl-mid">
<div class="tua-abl-bg">
<img v-if="isFetterMax" :src="nameCard" alt="nameCard" />
<div class="tua-abl-bg" :class="{ ori: isFetterMax }">
<img :src="nameCard" alt="nameCard" />
</div>
<div class="tua-abl-skills">
<div
@@ -148,6 +148,10 @@ function getWeaponTitle(): string {
border-radius: 4px;
cursor: pointer;
row-gap: 4px;
&:hover .tua-abl-bg {
filter: grayscale(0);
}
}
.dark .tua-ab-box {
@@ -246,6 +250,12 @@ function getWeaponTitle(): string {
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
background: var(--box-bg-3);
filter: grayscale(1);
transition: filter 0.5s ease-in-out;
&.ori {
filter: unset;
}
img {
width: 100%;

View File

@@ -23,6 +23,8 @@ const props = defineProps<TucOverviewProps>();
function getTitle(): string {
switch (props.data.difficulty_id) {
case 0:
return "未选择";
case 1:
return "轻简模式";
case 2:

View File

@@ -1,9 +1,5 @@
<template>
<div class="tp-avatar-box">
<div v-if="props.position === 'right'" class="tpa-text">
<div>{{ props.data.nickname }}</div>
<div :title="authorDesc">{{ authorDesc }}</div>
</div>
<div class="tpa-img">
<div class="tpa-icon">
<TMiImg :ori="true" :src="props.data.avatar_url" alt="avatar" />
@@ -21,7 +17,7 @@
{{ props.data.level_exp.level }}
</div>
</div>
<div v-if="props.position === 'left'" class="tpa-text">
<div class="tpa-text">
<div>{{ props.data.nickname }}</div>
<div :title="authorDesc">{{ authorDesc }}</div>
</div>
@@ -56,6 +52,9 @@ const levelColor = computed<string>(() => {
overflow: hidden;
width: fit-content;
max-width: 100%;
flex-direction: v-bind("props.position === 'left' ? 'row' : 'row-reverse'");
align-items: center;
justify-content: v-bind("props.position === 'left' ? 'flex-start' : 'flex-end'");
}
.tpa-text {
@@ -129,7 +128,6 @@ const levelColor = computed<string>(() => {
img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: contain;
}
}

View File

@@ -58,35 +58,38 @@ console.log("tpBackupText", props.data.insert.backup_text, toRaw(props.data));
.tp-backup-fold {
position: relative;
padding: 10px;
overflow: hidden;
border: 1px solid var(--common-shadow-2);
border-radius: 10px;
margin: 10px auto;
}
.tp-backup-fold summary {
list-style: none;
}
.tp-backup-fold ::marker {
color: var(--common-shadow-4);
content: "";
border-radius: 4px;
margin: 8px auto;
}
.tp-backup-summary {
position: relative;
display: flex;
margin-left: 5px;
width: 100%;
box-sizing: border-box;
align-items: center;
justify-content: flex-start;
padding: 8px;
background: var(--box-bg-1);
cursor: pointer;
font-family: var(--font-title);
}
.tp-backup-marker {
position: relative;
display: inline;
width: 20px;
height: 20px;
width: 24px;
height: 24px;
margin-right: 4px;
}
.tp-backup-details {
padding-left: 20px;
position: relative;
width: 100%;
box-sizing: border-box;
padding: 8px 8px 8px 24px;
border-top: 1px solid var(--common-shadow-1);
}
</style>

View File

@@ -13,7 +13,13 @@
<v-progress-circular :indeterminate="true" color="primary" size="small" />
<span>加载中...</span>
</div>
<VpOverlayImage :image="image" v-model="showOverlay" v-model:link="localUrl" :ori="true" />
<VpOverlayImage
:image="image"
v-model="showOverlay"
v-model:link="localUrl"
:ori="true"
v-model:bgColor="bgColor"
/>
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref } from "vue";
@@ -40,6 +46,7 @@ type TpEmoticonProps = { data: TpCustomEmoticon };
const props = defineProps<TpEmoticonProps>();
const localUrl = ref<string>();
const showOverlay = ref<boolean>(false);
const bgColor = ref<string>("transparent");
const image = computed<TpImage>(() => {
return {
insert: { image: props.data.insert.custom_emoticon.url },
@@ -77,6 +84,7 @@ onUnmounted(() => {
max-width: 100%;
height: auto;
border-radius: 4px;
background: v-bind(bgColor);
}
.tp-emo-info {

View File

@@ -25,6 +25,7 @@
v-model="showOverlay"
v-model:link="localUrl"
v-model:ori="showOri"
v-model:bgColor="bgColor"
/>
</template>
<script lang="ts" setup>
@@ -58,6 +59,7 @@ const showOri = ref<boolean>(
props.data.insert.image.endsWith(".gif") || imageQualityPercent.value === 100,
);
const localUrl = ref<string>();
const bgColor = ref<string>("transparent");
const imgWidth = computed<string>(() => {
if (props.data.attributes === undefined) return "auto";
@@ -117,6 +119,7 @@ function getImageTitle(): string {
height: auto;
border-radius: 4px;
cursor: pointer;
background: v-bind(bgColor);
}
.tp-image-load {

View File

@@ -22,24 +22,31 @@
</template>
<div class="tpr-main-reply">
<div class="tpr-main-filter">
<div class="tpr-title" @click="handleDebug">回复列表</div>
<v-switch
v-model="onlyLz"
color="primary"
:hide-details="true"
title="只看楼主"
@change="reloadReply"
/>
<v-select
class="tpr-select"
density="compact"
v-model="orderType"
:items="orderList"
item-title="label"
item-value="value"
title="排序方式"
/>
<v-btn @click="showOverlay = false" icon="mdi-close" class="tpr-btn-close" size="32" />
<div class="tpr-title">
<span title="刷新" @click="handleDebug">回复列表</span>
<v-btn @click="showOverlay = false" icon="mdi-close" class="tpr-btn-close" size="32" />
</div>
<div class="tpr-subtitle">
<div class="tpr-switch" @click="switchOnlyLz()">
<v-icon v-if="onlyLz" color="var(--tgc-od-green)">
mdi-checkbox-marked-circle-outline
</v-icon>
<v-icon v-else color="var(--tgc-od-white)">mdi-circle</v-icon>
<span>只看楼主</span>
</div>
<div class="tpr-select">
<v-select
:hide-details="true"
variant="outlined"
density="compact"
v-model="orderType"
:items="orderList"
item-title="label"
item-value="value"
label="排序方式"
/>
</div>
</div>
</div>
<v-list class="tpr-reply-list">
<VpReplyItem
@@ -114,6 +121,11 @@ async function showReply(): Promise<void> {
await loadReply();
}
async function switchOnlyLz(): Promise<void> {
onlyLz.value = !onlyLz.value;
await reloadReply();
}
async function reloadReply(): Promise<void> {
lastId.value = undefined;
reply.value = [];
@@ -155,7 +167,7 @@ async function handleDebug(): Promise<void> {
await reloadReply();
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
.tpr-main-box {
position: fixed;
bottom: 20px;
@@ -169,17 +181,10 @@ async function handleDebug(): Promise<void> {
align-items: center;
justify-content: center;
border: 2px solid var(--common-shadow-8);
}
.tpr-btn:hover {
opacity: 0.8;
}
.tpr-btn-close {
border: 1px solid var(--common-shadow-2);
margin-left: auto;
background: var(--tgc-btn-1);
color: var(--btn-text);
&:hover {
opacity: 0.8;
}
}
.tpr-main-reply {
@@ -201,31 +206,62 @@ async function handleDebug(): Promise<void> {
}
.tpr-main-filter {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 40px;
align-items: center;
justify-content: flex-start;
align-items: flex-start;
justify-content: center;
color: var(--app-page-content);
column-gap: 4px;
row-gap: 4px;
}
.tpr-title {
position: relative;
padding: 2px 4px;
border-radius: 4px;
background: var(--tgc-od-blue);
color: var(--tgc-white-1);
cursor: pointer;
font-family: var(--font-title);
white-space: nowrap;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
span {
padding: 2px 4px;
border-radius: 4px;
background: var(--tgc-od-blue);
color: var(--tgc-white-1);
cursor: pointer;
font-family: var(--font-title);
white-space: nowrap;
}
}
.tpr-btn-close {
border: 1px solid var(--common-shadow-2);
margin-left: auto;
background: var(--tgc-btn-1);
color: var(--btn-text);
}
.tpr-subtitle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.tpr-select {
position: relative;
max-width: 100px;
height: 40px;
font-size: 12px;
width: 120px;
}
.tpr-switch {
position: relative;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
font-family: var(--font-title);
}
.tpr-reply-list {

View File

@@ -26,13 +26,14 @@ import TOverlay from "@comp/app/t-overlay.vue";
import TPostcard from "@comp/app/t-postcard.vue";
import { nextTick, onMounted, shallowRef, useTemplateRef, watch } from "vue";
// import bbsReq from "@/web/request/bbsReq.js";
import bbsReq from "@/web/request/bbsReq.js";
import postReq from "@/web/request/postReq.js";
type TpoCollectionProps = { collection: TGApp.BBS.Post.Collection; gid: number };
const props = defineProps<TpoCollectionProps>();
const visible = defineModel<boolean>();
const info = shallowRef<TGApp.BBS.Collection.InfoRes>();
const postList = shallowRef<Array<TGApp.BBS.Post.FullData>>([]);
const postListEl = useTemplateRef<HTMLDivElement>("postListRef");
@@ -49,11 +50,23 @@ watch(
},
);
onMounted(async () => {
onMounted(async () => await Promise.all([refreshInfo(), refreshPosts()]));
async function refreshInfo(): Promise<void> {
const infoResp = await bbsReq.collection(props.collection.collection_id, props.gid);
if ("retcode" in infoResp) {
// showSnackbar.warn(`[合集信息][${infoResp.retcode}] ${infoResp.message}`);
return;
}
info.value = infoResp;
console.log(info.value);
}
async function refreshPosts(): Promise<void> {
postList.value = await postReq.collection(props.collection.collection_id);
});
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
.tpoc-box {
padding: 10px;
border-radius: 5px;

View File

@@ -51,6 +51,7 @@ const props = defineProps<TpoImageProps>();
const visible = defineModel<boolean>();
const localLink = defineModel<string>("link");
const showOri = defineModel<boolean>("ori");
const bgColor = defineModel<string>("bgColor", { default: "transparent" });
const bgMode = ref<number>(0); // 0: transparent, 1: black, 2: white
const isOriSize = ref<boolean>(false);
const buffer = shallowRef<Uint8Array | null>(null);
@@ -65,6 +66,13 @@ function setBlackBg(): void {
bgMode.value = (bgMode.value + 1) % 3;
const bgLabelList = ["透明", "黑色", "白色"];
showSnackbar.success(`背景已切换为${bgLabelList[bgMode.value]}`);
if (bgMode.value === 0) {
bgColor.value = "transparent";
} else if (bgMode.value === 1) {
bgColor.value = "black";
} else {
bgColor.value = "white";
}
}
async function onCopy(): Promise<void> {
@@ -86,14 +94,16 @@ async function onDownload(): Promise<void> {
showOri.value = true;
await nextTick();
}
await showLoading.start("正在下载图片到本地", props.image.insert.image);
const image = props.image.insert.image;
await showLoading.start("正在下载图片到本地", image);
if (buffer.value === null) buffer.value = await getImageBuffer(image);
if (buffer.value.byteLength > 80000000) {
showSnackbar.warn("图片过大,无法下载到本地");
return;
}
await saveCanvasImg(buffer.value, Date.now().toString(), format.value);
let fileName = image.split("/").pop()?.split(".")[0];
if (fileName === undefined) fileName = Date.now().toString();
await saveCanvasImg(buffer.value, fileName, format.value);
await showLoading.end();
}
</script>
@@ -120,9 +130,16 @@ async function onDownload(): Promise<void> {
justify-content: center;
border: 1px solid var(--tgc-od-white);
border-radius: 4px;
background: v-bind("bgMode === 1 ? 'black' : bgMode === 2 ? 'white' : 'transparent'");
cursor: zoom-in;
overflow-y: auto;
img {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
background: v-bind(bgColor);
object-fit: contain;
}
}
.tpoi-top-ori {
@@ -131,13 +148,10 @@ async function onDownload(): Promise<void> {
max-width: 100%;
max-height: 70%;
cursor: zoom-out;
}
.tpoi-top img {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
object-fit: contain;
img {
background: v-bind(bgColor);
}
}
.tpoi-bottom {

View File

@@ -3,7 +3,7 @@
<div class="tops-box">
<div class="tops-top">查找{{ search }}</div>
<div class="tops-act">
<span>分区{{ gameName }}</span>
<span>分区{{ label }}</span>
<v-btn :loading="load" size="small" class="tops-btn" @click="searchPosts()" rounded>
加载更多({{ results.length }})
</v-btn>
@@ -24,27 +24,33 @@
import TOverlay from "@comp/app/t-overlay.vue";
import TPostCard from "@comp/app/t-postcard.vue";
import showSnackbar from "@comp/func/snackbar.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import TGBbs from "@/utils/TGBbs.js";
import useBBSStore from "@/store/modules/bbs.js";
import postReq from "@/web/request/postReq.js";
type ToPostSearchProps = { gid: string; keyword?: string };
const { gameList } = storeToRefs(useBBSStore());
const props = defineProps<ToPostSearchProps>();
const visible = defineModel<boolean>();
const search = ref<string>();
const lastId = ref<string>("");
const game = ref<string>("2");
const gameId = ref<string>("2");
const isLast = ref<boolean>(false);
const load = ref<boolean>(false);
const results = shallowRef<Array<TGApp.BBS.Post.FullData>>([]);
const gameName = computed<string>(
() => TGBbs.channels.find((v) => v.gid.toString() === game.value)?.title || "未知分区",
);
const label = computed<string>(() => {
const gameFind = gameList.value.find((v) => v.id.toString() === gameId.value);
if (gameFind === undefined) return "未知分区";
return gameFind.name;
});
onMounted(async () => {
game.value = props.gid;
gameId.value = props.gid;
if (props.keyword && props.keyword !== "") search.value = props.keyword;
if (visible.value) await searchPosts();
});
@@ -79,8 +85,8 @@ watch(
watch(
() => props.gid,
async () => {
if (game.value !== props.gid) {
game.value = props.gid;
if (gameId.value !== props.gid) {
gameId.value = props.gid;
results.value = [];
lastId.value = "";
isLast.value = false;
@@ -106,7 +112,7 @@ async function searchPosts(): Promise<void> {
load.value = false;
return;
}
const res = await postReq.search(game.value, search.value, lastId.value);
const res = await postReq.search(gameId.value, search.value, lastId.value);
if (lastId.value === "") results.value = res.posts;
else results.value = results.value.concat(res.posts);
lastId.value = res.last_id;

View File

@@ -97,7 +97,6 @@ async function loadUser(): Promise<void> {
return;
}
userInfo.value = resp;
console.log(userInfo.value);
}
async function loadPosts(): Promise<void> {
@@ -108,7 +107,7 @@ async function loadPosts(): Promise<void> {
load.value = false;
return;
}
const resp = await postReq.user.post(props.uid, props.gid, offset.value);
const resp = await postReq.user.post(props.uid, 0, offset.value);
if ("retcode" in resp) {
showSnackbar.warn(`[${resp.retcode}] ${resp.message}`);
load.value = false;
@@ -195,7 +194,6 @@ async function loadPosts(): Promise<void> {
img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: contain;
}
}

View File

@@ -30773,5 +30773,693 @@
"Context": "瓦雷莎常说,自己做过一个噩梦,梦中她与深渊不停地战斗,但对方似乎无穷无尽,无论击败多少敌人,攻势都不见减弱。\n而部族中的诸多战士知道那是确确实实发生过的恶战。\n彼时狡诈的深渊咬准了图兰火山的活跃期从三个不同的方向对「沃陆之邦」发起波状袭击试图逼迫部族战士们分兵再将其逐一击破。\n事发突然全速赶来的支援力量仅有「悬木人」的一批信使「沃陆之邦」几乎只能依靠自己的力量迎战。\n有限的兵力延展至漫长的战线上时每一处防御都显得尤为薄弱。部族要员们谨慎商议后决定将防线分散分层极限部署至最远处留下足够的缓冲距离。「悬木人」的少量援兵绝不能直接参与战斗而是要利用自身善于攀高的优势尽可能占领制高点。\n同时「沃陆之邦」抽调一部分精锐的战士作为机动待命的反攻力量。\n多层防线上的「沃陆之邦」战士负责迟滞深渊的力量他们未必能拦住深渊的大军却能迫使对方提前展开不断拖慢其行动的步伐。\n待到对方进攻的势头耗尽调整对策负责指挥的魔物头目就会被「悬木人」信使们准确查明牢牢定位。\n此时反攻力量就会从后方铆足势头一鼓作气打进对方阵中斩落魔物头目。失去头目的指挥后余下的敌人便可慢慢对付稳重地将其围剿…\n刚刚加入巡逻队不久的瓦雷莎听说加入反攻部队便能直接和对方的头目对决果断举手报名。\n阿卡特和伊安珊曾有顾虑但考虑到兵力实在紧缺瓦雷莎又有着明显的优势最终还是同意了她的请求。\n战术是清晰的实现起来却并不轻松。\n第一次反攻能够突入敌阵的战士尚有十余人瓦雷莎与他们完美配合将头目利落地撞翻碾碎\n第二次反攻仅有瓦雷莎在内的六名战士来到头目跟前以往战士们拿来开玩笑的暴徒巨斧竟显得有些难以躲闪\n最后一次反攻作为防线骨干的伊安珊全力拼搏为反攻部队打开了直取纵深的缺口转头时却发现仅有瓦雷莎按时抵达了阵位…\n遍体鳞伤的瓦雷莎蹒跚而来右手拖着全身骨折的丘丘暴徒将其甩进敌阵砸起一阵魔物的浪花左肩则扛着力竭的前辈将其转交给伊安珊。\n瓦雷莎的双目略显空洞意识已然模糊可她的身体仍在愤怒和责任的驱动下全力运转。\n她从口袋中取出破破烂烂的面具颤抖着戴在脸上低声自言自语。\n「我不是那个胆小的孩子了我要像英雄一样战斗。」\n话音落定不等伊安珊劝阻瓦雷莎便踏碎山石化为茜红流光切开敌阵直刺最后的头目。\n片刻过后魔物浪潮的深处炽亮的轰雷骤然爆发头目的残骸划出一道醒目的弧线高高飞散至半空中…\n待到一切尘埃落定伊安珊奋力挖掘终于在坍塌的战场深处找到了沉沉睡去的瓦雷莎。神之眼的强光仍在闪烁她的身体周围依旧腾跃着危险的雷电。\n瓦雷莎足足昏迷了半个月似乎是因为过度劳累苏醒后她完全不记得之前的那场苦战身体的伤势却已奇迹般恢复并无大碍。\n听着周围人对自己的赞许看着蓦然获得的神之眼以及犒劳自己的盛宴瓦雷莎只知道自己在这场大觉之前做得还算不错…至少没有让大家失望。\n那就先不追问了当务之急是美美地饱餐一顿"
}
]
},
{
"id": 10000112,
"name": "爱可菲",
"title": "明绚千韵",
"description": "闻名枫丹的前德波大饭店主厨,有着「甜点大校」荣誉的「技术料理」先驱,对烹饪的要求无比严格。",
"area": "枫丹",
"brief": {
"camp": "枫丹",
"constellation": "香糕塔座",
"birth": "6月8日",
"cv": { "cn": "蔡海婷", "jp": "佐藤聪美", "en": "埃米莉·卡斯", "kr": "孙廷旼" }
},
"star": 5,
"element": "冰",
"weapon": "长柄武器",
"materials": [
{ "id": 104164, "name": "哀叙冰玉", "star": 5 },
{ "id": 113076, "name": "秘源积气喉", "star": 4 },
{ "id": 101232, "name": "苍晶螺", "star": 0 },
{ "id": 112085, "name": "奇械机芯齿轮", "star": 3 },
{ "id": 104343, "name": "「正义」的哲学", "star": 4 },
{ "id": 113068, "name": "蚀灭的灵犀", "star": 5 }
],
"skills": [
{
"GroupId": 11231,
"Id": 11121,
"Name": "后厨手艺",
"Description": "<color=#FFD780FF>普通攻击</color>\n进行至多三段的连续枪击。\n\n<color=#FFD780FF>重击</color>\n消耗一定体力进行上挑攻击。\n\n<color=#FFD780FF>下落攻击</color>\n从空中下坠冲击地面攻击下落路径上的敌人并在落地时造成范围伤害。",
"Icon": "Skill_A_03"
},
{
"GroupId": 11232,
"Id": 11122,
"Name": "低温烹饪",
"Description": "敬请见证「料理」的真意!依据点按、长按,以不同的方式展现枫丹引以为傲的厨艺。\n\n<color=#FFD780FF>点按</color>\n以「低温冷藏」模式启动全频谱多重任务厨艺机关对周围的敌人造成<color=#99FFFFFF>冰元素范围伤害</color>。\n\n<color=#FFD780FF>厨艺机关·低温冷藏模式</color>\n将跟随当前场上角色并将间歇性对附近的敌人发射「冻霜芭菲」造成<color=#99FFFFFF>冰元素伤害</color>。\n\n<color=#FFD780FF>始基力:荒性</color>\n每隔一段时间爱可菲以「低温冷藏」模式启动厨艺机关时将唤出流涌之刃造成具有荒性的<color=#99FFFFFF>冰元素范围伤害</color>。\n\n<color=#FFD780FF>长按</color>\n以<color=#FFD780FF>即兴烹饪模式</color>启动全频谱多重任务厨艺机关。",
"Icon": "Skill_S_Escoffier_01"
},
{
"GroupId": 11239,
"Id": 11125,
"Name": "花刀技法",
"Description": "展现极致的刀工,造成<color=#99FFFFFF>冰元素范围伤害</color>,并为队伍中附近的所有角色恢复生命值,回复量受益于爱可菲的攻击力。",
"Icon": "Skill_E_Escoffier_01"
},
{
"GroupId": 11221,
"Id": 1122101,
"Name": "美食胜过良药",
"Description": "施放元素爆发<color=#FFD780FF>花刀技法</color>后爱可菲将获得「康复食疗」效果持续9秒每1秒为附近的当前场上角色恢复生命值回复值受益于爱可菲自己攻击力的138.24%。",
"Icon": "UI_Talent_S_Escoffier_05"
},
{
"GroupId": 11222,
"Id": 1122201,
"Name": "灵感浸入调味",
"Description": "当队伍中存在1/2/3/4名<color=#80C0FFFF>水元素</color>或<color=#99FFFFFF>冰元素</color>角色时,爱可菲的元素战技<color=#FFD780FF>低温烹饪</color>或元素爆发<color=#FFD780FF>花刀技法</color>命中敌人时,将使该敌人的<color=#80C0FFFF>水元素抗性</color>与<color=#99FFFFFF>冰元素抗性</color>降低5%/10%/15%/55%持续12秒。",
"Icon": "UI_Talent_S_Escoffier_06"
},
{
"GroupId": 11223,
"Id": 1122301,
"Name": "时时刻刻的即兴料理",
"Description": "长按施放元素战技<color=#FFD780FF>低温烹饪</color>时,将以「即兴烹饪」模式启动全频谱多重任务厨艺机关。\n\n<color=#FFD780FF>厨艺机关·即兴烹饪模式</color>\n·在场上放置厨艺机关。厨艺机关可以吸收元素攻击。吸收的元素能量达到临界值时将使爱可菲事先放入其中的食材转化为美食。\n·爱可菲需要时间采购新的食材每周只能通过这种方式制作一定数量的料理。制作料理的次数每周一凌晨4点重置。",
"Icon": "UI_Talent_S_Escoffier_08"
}
],
"constellation": [
{
"Id": 1121,
"Name": "味蕾绽放的餐前旋舞",
"Description": "队伍中4名角色的元素类型均为<color=#80C0FFFF>水元素</color>或<color=#99FFFFFF>冰元素</color>时,爱可菲施放元素战技<color=#FFD780FF>低温烹饪</color>或元素爆发<color=#FFD780FF>花刀技法</color>后的15秒内队伍中附近的所有角色造成<color=#99FFFFFF>冰元素伤害</color>时的暴击伤害提升60%。\n需要解锁固有天赋「灵感浸入调味」。",
"Icon": "UI_Talent_S_Escoffier_01"
},
{
"Id": 1122,
"Name": "鲜香味腴的炖煮艺术",
"Description": "爱可菲以<color=#FFD780FF>低温冷藏模式</color>启动厨艺机关时将获得「现制名肴」效果持续15秒持续期间爱可菲获得5层「冷煮」除爱可菲外的附近的当前场上角色普通攻击、重击、下落攻击、元素战技和元素爆发对敌人造成<color=#99FFFFFF>冰元素伤害</color>时将消耗1层「冷煮」提升造成的伤害提升值相当于爱可菲攻击力的240%。\n一次<color=#99FFFFFF>冰元素伤害</color>同时命中多名敌人时,会依据命中敌人的数量消耗「冷煮」的层数。",
"Icon": "UI_Talent_S_Escoffier_02"
},
{
"Id": 1123,
"Name": "焦糖褐变的烘烤魔法",
"Description": "元素战技<color=#FFD780FF>低温烹饪</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Escoffier_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 1124,
"Name": "迷迭生香的配比秘方",
"Description": "<color=#FFD780FF>康复食疗</color>的持续时间延长6秒。持续期间通过<color=#FFD780FF>康复食疗</color>触发治疗时有几率使治疗量提升100%并为爱可菲恢复2点元素能量几率相当于爱可菲自己的暴击率。一次<color=#FFD780FF>康复食疗</color>持续期间该效果至多触发7次。\n需要解锁固有天赋「美食胜过良药」。",
"Icon": "UI_Talent_S_Escoffier_03"
},
{
"Id": 1125,
"Name": "千种酱汁的风味交响",
"Description": "元素爆发<color=#FFD780FF>花刀技法</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Escoffier_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 1126,
"Name": "虹彩缤纷的甜点茶话",
"Description": "<color=#FFD780FF>厨艺机关·低温冷藏模式</color>获得强化:\n·队伍中自己的当前场上角色的普通攻击、重击或下落攻击命中敌人时厨艺机关·低温冷藏模式将发射一枚额外的特级冻霜芭菲造成爱可菲500%攻击力的<color=#99FFFFFF>冰元素范围伤害</color>。该伤害视为元素战技伤害。\n该效果每0.5秒至多触发一次一次厨艺机关·低温冷藏模式持续期间该效果至多触发6次。",
"Icon": "UI_Talent_S_Escoffier_04"
}
],
"talks": [
{
"Title": "初次见面…",
"Context": "「技术料理」厨师、「甜点大校」爱可菲,向你致以问候。贵为神明座上宾的你,也应享用提瓦特最顶尖的美食,希望我的作品能让你满意。\n如果对烹饪技艺有兴趣我愿意与你分享所有心得带你前往厨艺奥秘的殿堂…不过成为我的学员前还请做好充分的心理准备我可不接受半路退学哦"
},
{
"Title": "闲聊·背景音乐",
"Context": "最近有什么新的交响乐唱片吗…适当更换烹饪时的背景音乐,有助于找到新的灵感,制作不同的菜肴…"
},
{
"Title": "闲聊·刀工",
"Context": "调料对食材的「渗透」,是许多料理的重中之重。为此,厨师的刀工必须无比精准,最好达到外科手术的级别,勤加练习总归不会错。"
},
{
"Title": "闲聊·敏锐洞察",
"Context": "寻常可见的材料里,也有可能隐藏着惊人的风味物质,将嗅觉、味觉和思维锻炼得足够敏锐,才有机会充分发掘它们…"
},
{
"Title": "下雨的时候…",
"Context": "潮湿的空气,绿叶和土壤的味道相融…下一顿正餐,就做「久雨莲鱼羹」好了。"
},
{
"Title": "雨过天晴…",
"Context": "水汽蒸发,温度回升…这悠闲的感觉,该用怎样的「味觉变量」去传达呢?"
},
{ "Title": "下雪的时候…", "Context": "呵,希望这雪足够洁净,将万物保鲜。" },
{
"Title": "阳光很好…",
"Context": "明亮的阳光…「明亮」的口感,橙酸、树莓叶沫与砂糖的混合物,或许是个新解法。"
},
{
"Title": "早上好…",
"Context": "还有点睡意吗?早餐就该用刚出炉的「海盐羊角面包」、「青蜜莓蝴蝶酥」,佐上「泡泡桔果茶」!保证能唤醒你的活力。"
},
{
"Title": "中午好…",
"Context": "午餐在历史上有过一段不受重视的时期,但在愈发追求生活品质的现代,它就像交响曲中的弦乐组,地位毋庸置疑,不容马虎应付。现在就跟我去找最鲜活的食材,做几道分量扎实的硬菜,怎么样?"
},
{
"Title": "晚上好…",
"Context": "对于每一天的晚餐,都该拿出应对高级宴席的态度。哪怕只有两三道菜,最晚下午三点,也该取出预处理的食材了…欸?现在已经太晚…时间可能不够?别紧张,我又不是要催你下厨——这些食材我几小时前就已经预处理好了,准备享用一顿上乘的晚宴吧!"
},
{
"Title": "晚安…",
"Context": "适量的甜点有助于提升睡眠质量,我给你准备了「绯樱烤苹果味低糖布丁」…不用在乎体重那种虚无缥缈的数字,需要调节膳食平衡时候,我也能让你吃上味道绝不妥协的健康餐!"
},
{
"Title": "关于爱可菲自己·形式束缚",
"Context": "虽说我有着「甜点大校」的称号,但并不代表我只会做甜点。专心致志深耕一种烹饪的厨师值得敬佩,可我不想束缚自己。只要能向极致的味觉享受发起冲击,无论什么形式的料理,无论用到哪个地区的食材,我都愿意尝试。"
},
{
"Title": "关于爱可菲自己·全副武装",
"Context": "在我的烹饪理论中,「准确提取并运用各式风味物质」是核心理念。为此,我需要各种功能的厨具。之前如果需要长期离开常驻的厨房,我得将所有厨具仔细封装,打包至行李箱里。那时娜维娅经常打趣,说我「像长途行军的特巡队一样全副武装」。好在,后来我委托枫丹科学院,制作了「全频谱多重任务厨艺机关」,外出烹饪才变得方便起来…"
},
{
"Title": "关于我们·评判的资格",
"Context": "暴露在空气中的食材会变质,沐浴在阳光下的新芽会茁壮成长…人们对料理的审美,也在不断迭代与发展。经典的配方固然可贵,但不可能永远停滞于它成名的那一刻。当然,创新也不能胡来,否则,改进的方向有可能太过离谱,产生诸如…「堇瓜奶冻通心粉」「绝云椒椒伯爵茶」之类的奇怪作品…我希望你…至少你心中要明确这一根准绳噢!"
},
{
"Title": "关于我们·创新的准绳",
"Context": "厨师的原则是对每位食客尽心尽力,但这也不意味着毫无原则地接受所有人的批评。有人的味觉生来粗放,有人则对菜肴的细致风味毫不上心,更不必说其中隐藏的,由视觉、嗅觉和味觉共同编织的心流。在我看来,唯有你这样的特殊案例——见识过诸多国家的美食,贵为神明的座上宾,还有着细腻的心灵——才有资格,让我接受所有的评判。要是你觉得我的哪道料理不对劲,尽管直说,不用有任何顾虑!"
},
{
"Title": "关于「神之眼」…",
"Context": "我对「神之眼」的原理一无所知,还好这也不妨碍我把它当成宝贵的厨具。正如我常说的,料理是一门科学。这世界上还有许多事物的风味等待揭晓…有的魔物看起来很脏,但细细切作碎末后,能够萃取出非常不错的调味品。如果没有「神之眼」的力量,我想拿下它们,就要付出百倍千倍的代价了…最重要的是,能自如地运用冰元素后,冷藏食材也更方便啦!"
},
{
"Title": "有什么想要分享…",
"Context": "我背后的这个装置呢,正式名称是「自适应运动信号响应型辅助机关」,与「全频谱多重任务厨艺机关」属于同一技术领域的产物,能对我习惯性的小动作作出回应。易于控制,帮厨的效率也无可挑剔…许多人觉得它像「尾巴」,甚至为此渲染我是「恶魔主厨」…如果恶魔的身份能让我更方便地除掉难吃的料理…倒也不错?"
},
{
"Title": "感兴趣的见闻…",
"Context": "如果顺利开发出了远在「德波大蛋糕」之上的甜品,让芙宁娜大人满意的话…我想去提瓦特的每个国家旅居一段时间,为那里的神明烹制料理。如此一来,我就能找到突破自我的方向了。运气好的话,说不定还能尝到神明的拿手菜,获得超脱凡尘的灵感…"
},
{
"Title": "关于芙宁娜·初遇…",
"Context": "我刚入行时,在瓦萨里回廊的小餐馆工作。有一次,芙宁娜大人结束演出后突发奇想,随机选了一家店用餐。我当时忙得晕头转向,听说有客人想见主厨,连刀都没放下,就匆匆去了客人的餐位,结果迎面撞上芙宁娜大人,脑袋里一片空白…后来,我凭本能准备了一份「铃兰糖霜剧院蛋糕」,幸好芙宁娜大人十分满意,还说记住了我的名字。当晚,我翻来覆去,一夜都没睡着,结果第二天就等来了沫芒宫的特聘信…"
},
{
"Title": "关于芙宁娜·现状…",
"Context": "在那以后,芙宁娜大人封我为「甜点大校」,赞赏我在这个领域「能一人成军」。为了不负这个名号,我闭关深造过一段时间,结果好像错过了不少大事件,芙宁娜大人也搬出了沫芒宫…我不懂那些复杂的问题,但我明白,芙宁娜大人永远是我生命中最重要的贵人,只要她有需要,我随时都会以专属厨师的身份回到她的身边,薪酬什么的完全无所谓!"
},
{
"Title": "关于娜维娅…",
"Context": "五岁生日的时候,爸妈第一次带我去了德波大饭店。在饭店门口,我捡到了一顶非常漂亮的帽子,顺着帽子上那种无与伦比的、热情的玫瑰香气,我找到了它的主人。娜维娅的气息至今没变,我一有空就会约她喝点下午茶,聊聊近况,尝尝点心,每分每秒都很开心…只可惜过了那么多年,她那个「甜品就是越甜越好!」的口味也还是没变…算啦,我也不打算去纠正,喜欢什么样的口味,总归是自己说了算。"
},
{
"Title": "关于夏洛蒂…",
"Context": "对于我们厨师而言,夏洛蒂小姐称得上是「影响力倍增者」。她贵为专业的记者,文字功底了得,对于菜肴风味的描写细致入微,配合无可挑剔的摄影技术,转眼间就能完成一则图文并茂的报道。如果有什么想要宣传的料理新品,请她第一时间来试吃,绝对不会错。"
},
{
"Title": "关于希格雯…",
"Context": "护士长吗…唔…嗯…护士长的性格很棒,但她毕竟有着特殊的体质…我理解她,敬畏她,尽量探索了她的味觉世界。她值得成为映影中的传奇主角——一位能兵不血刃,用奶昔除掉目标的刺杀高手…"
},
{
"Title": "关于莱欧斯利…",
"Context": "那位公爵彬彬有礼,做事稳重,是一位老学派的绅士。不过,他似乎没有意识到,「特许食堂」的某些菜品,对我而言…是加刑。"
},
{
"Title": "关于琳妮特…",
"Context": "说起来你可能还不知道,琳妮特也是我们「甜点茶话会」上的一员呢。她跟林尼先生常来德波大饭店演出,我们也有过不少交流。一开始我还以为琳妮特有点冷淡,鼓起勇气请她来茶话会上做客之后,我才发现她的情绪丰富得就像缤纷马卡龙…遇到喜欢的甜点就会扑扇耳朵,遇到不喜欢的就会耷拉下尾巴!等等…尾巴…该不会我也…"
},
{
"Title": "关于「仆人」…",
"Context": "她的身影,我只是远远看过一眼,就再也忘不掉了。起初我警告自己,假如自己做的菜肴有什么瑕疵,让那样的客人感到了不满,后果的恐怖程度完全可以预见。对于不成器的学员,我有时会让他们「想想把这种料理端给阿蕾奇诺女士的下场」。但我后来听娜维娅说…她似乎不是特别苛刻的人?"
},
{
"Title": "关于艾梅莉埃…",
"Context": "你知道吗?「食品调香」也是调香学的一大热门哦。向艾梅莉埃小姐咨询了几种香味物质的提炼方法后,我对「嗅觉与味觉的联合作用」有了更深一层的认知,说不定,以后还能开发出充分运用香味,分层影响口感的全新佳肴…她对我用科学分析料理的观念也相当认可,真是难得呀。"
},
{
"Title": "关于瓦雷莎…",
"Context": "瓦雷莎啊,她的水果中蕴含着「懒洋洋的阳光」,就算让我加价两成,我都觉得值当!所以每次交易后,我都要请她饱餐一顿。她的食量出了名的惊人,但我询问她的用餐体验时,发现她并没有忽视料理的细节。也就是说,瓦雷莎一直「用心在吃」,我喜欢这样的食客!"
},
{
"Title": "关于香菱…",
"Context": "听说璃月有句古话叫「山外有山」,香菱小姐对我而言就像是…「穿过山洞直接抵达了异世界」。她的许多烹饪技法,我闻所未闻,对食材的选取也令人惊叹,请我品尝的料理更是包含着无尽的可能性,不该用传统枫丹料理的思维去分析。遇见她以后,我更加相信了自己的判断——绝不能把自己锁死在厨房里,而是要多去各地走走,充分领略并融合各地的厨艺!"
},
{
"Title": "想要了解爱可菲·其一",
"Context": "对我来说,料理远不止「能量的必需品」或「营养补充物」这么简单,而是人生中最重要的仪式的主角。美好的料理能为人提供希望、力量和愉悦的心情。「烹饪」这件事也如诗歌中的雪翅雁一般,有着高洁的本质,需要我穷尽一生去钻研。"
},
{
"Title": "想要了解爱可菲·其二",
"Context": "很多人并不是无法理解料理中蕴含的美好,而是没有财力或时间去品味。这就与我们厨师的使命息息相关了——不断寻找性价比优异的食材,不断改进食谱,将原本只有少数人能享受的奢侈品普及到每个人的餐桌上。当然,这事说来容易做起来难。我在开始学习烹饪的时候就撞得鼻青脸肿,这才让我走上了「技术料理」的道路…"
},
{
"Title": "想要了解爱可菲·其三",
"Context": "「技术料理」流派的要义,就是「分析」、「萃取」与「调和」…简单来说,就是采用定量分析的方法,确定目标风味所需的各种物质,随后从其他替代食材中提炼出这些原材料,争取将昂贵的料理变得平易近人。所以,我经常捣鼓各种看似和做饭无关的机关,这些都是我的「正统厨具」。"
},
{
"Title": "想要了解爱可菲·其四",
"Context": "理论上,我的「技术料理」流派不止能在压缩菜肴成本上发挥作用,在探索更出彩的料理时,也能充当前锋。当然,我不会幼稚到想要成为什么「天下第一厨师」,或拿下「最强」之类的头衔,我的目标从一开始就很明确——让稀世的菜肴变得不再高贵神秘,并不断探寻地平线上的美味新巅峰。"
},
{
"Title": "想要了解爱可菲·其五",
"Context": "我最向往的生活,是每天都能沐浴在动听的音乐中,验收富含潜力的新食材,拥有充足的时间,找到耐心的冒险伙伴兼食客,去探索百万千万种风味物质的组合…这需要一定的财力支持,我会为此好好挣钱;至于帮助我将一切目标食材纳入囊中的助理,兼最最完美的食客,我心中已经有人选了…唔,你别挪开目光啊!"
},
{
"Title": "爱可菲的爱好…",
"Context": "你应该见过许多厉害的武器工匠吧?不知道,你中不中意…应用了多种锻造技术,呈现出各种花纹的刀?有的像蛋糕卷的切面,有的像正在融合的果酱,有的更像宝石那样,错落斑驳,五光十色…它们切开食材的手感也各不相同哦,有的顺滑得堪比奶油,有的咔嚓咔嚓节奏清晰、有的就像是提琴的琴弦在振动…各有各的享受啊!"
},
{
"Title": "爱可菲的烦恼…",
"Context": "根据一位「品牌形象顾问」的建议,我应当打造一个「富有投资价值的个人职业形象」,这样就可以争取到更多资源,有助于冲击更高的料理境界…不过,我完全不擅长在镜头前表演,只能和他们说,「记录我平时工作与教导学员的场景就够了吧」…结果映影播出后,大家的关注点好像微妙地跑偏了…"
},
{
"Title": "喜欢的食物…",
"Context": "我品尝过提瓦特绝大多数的传统料理,现在,我更在乎菜肴中「出乎意料」和「可供挖掘」的风味搭配。要是能尝到基于经典菜谱,却以奇妙思路对其加笔的杰作,我会相当满意。从经验和概率的角度来分析,在这方面潜力无限的厨师,就是你本人了,我非常期待你以后的作品。"
},
{
"Title": "讨厌的食物…",
"Context": "「近似某种味道」「马马虎虎」「味道还行」…这样的话语,在我的词典中,只会位于「不可饶恕」的页签。任何对风味把控不够精确,偏离了预期目标的菜肴,都会被我倒进垃圾桶。"
},
{
"Title": "收到赠礼·其一",
"Context": "嗯…嗯?嗯…你居然,居然这么有天赋!我该把自己的心得尽快、全部、全心全意地教给你,这样一来,你就能成为…没有上限的天才厨师!"
},
{
"Title": "收到赠礼·其二",
"Context": "唔…中规中矩,没有明显的亮点,但也没有犯下什么错误。凭你的才能,肯定还能让这盘菜继续蜕变吧?我会期待你全力以赴重做它的效果。"
},
{
"Title": "收到赠礼·其三",
"Context": "…凭我们的关系,我就直说了哦,要是我的学员给我端出这样的料理,我肯定会毫不犹豫地把盘子扣到他的脑袋上!但考虑到是你,我会细细吃完,然后…请你仔细听好我的分析,记住自己的问题,下次烹饪时,认真点,对自己和食客的味觉负责。"
},
{
"Title": "生日…",
"Context": "尊贵的你,是给提瓦特带来了独一无二风味的「终极变量」,为你庆祝诞生之日的宴席,至少要有八十九道菜品——头菜就该包含四道肉菜、四道野味、四道糕点和两道汤…不过,粗放地胡吃海喝,对你的身体和料理本身,都是一种不敬…我会将接下来的一周定为你的「诞生周」,来为你细致拆分这道宴席,每天安排十余道精致的料理,以便你尽情享受所有美食,体会一场节奏有致的味蕾狂欢!"
},
{
"Title": "突破的感受·起",
"Context": "感官更敏锐了,食物的香气、质感与色彩,在我的意识中加倍清晰…这很好!能帮我更为准确地烹饪。"
},
{
"Title": "突破的感受·承",
"Context": "能力的演进,不限于力量的增长,还有对各种「变量」一丝不苟的把控…我正在这一领域不断成长。"
},
{
"Title": "突破的感受·转",
"Context": "我能看见…形态与风味共同作用时,张力达到极值的「黄金时间点」;风味物质传递与扩散时,效率最高的脉络;甚至,咬开菜肴表面时,不同频谱的声学信号,对味觉的细微影响…"
},
{
"Title": "突破的感受·合",
"Context": "万分感谢,有了你的支援和启迪,现在的我,足以拿下一切潜在的食材,提取自然界中至臻的风味物质,向极致的料理发起冲击,然后…寻找在那之后无穷无尽的,新的山尖!"
},
{ "Title": "元素战技·其一", "Context": "冷冻时间。" },
{ "Title": "元素战技·其二", "Context": "餐前甜点。" },
{ "Title": "元素战技·其三", "Context": "低温处理。" },
{ "Title": "元素战技·其四", "Context": "即兴烹饪。" },
{ "Title": "元素战技·其五", "Context": "顺从灵感的搭配…" },
{ "Title": "元素战技·其六", "Context": "希望会有惊喜。" },
{ "Title": "元素爆发·其一", "Context": "如雪雁般…振翅!" },
{ "Title": "元素爆发·其二", "Context": "臻品呈现!" },
{ "Title": "元素爆发·其三", "Context": "形与味,迸发!" },
{ "Title": "打开宝箱·其一", "Context": "嗯哼,就像惊喜饼干。" },
{ "Title": "打开宝箱·其二", "Context": "这食材…过期了吗?" },
{ "Title": "打开宝箱·其三", "Context": "宝箱的保藏技术,很不错。" },
{ "Title": "生命值低·其一", "Context": "失误,小失误。" },
{ "Title": "生命值低·其二", "Context": "问题不大…调整火候。" },
{ "Title": "生命值低·其三", "Context": "要谨慎下刀…" },
{ "Title": "同伴生命值低·其一", "Context": "这盘菜得重做…" },
{ "Title": "同伴生命值低·其二", "Context": "别气馁啊!" },
{ "Title": "倒下·其一", "Context": "讨厌的味道…" },
{ "Title": "倒下·其二", "Context": "失控了…" },
{ "Title": "倒下·其三", "Context": "是我的问题吗…" },
{ "Title": "普通受击·其一", "Context": "坏食材…" },
{ "Title": "重受击·其一", "Context": "大、大胆!" },
{ "Title": "重受击·其二", "Context": "不许…露怯!" },
{ "Title": "加入队伍·其一", "Context": "冒险是灵感的调味品…" },
{ "Title": "加入队伍·其二", "Context": "刀具和餐叉,准备就绪。" },
{ "Title": "加入队伍·其三", "Context": "出门散心,留够熟成的时间。" }
],
"stories": [
{
"Title": "角色详细",
"Context": "多年以来,枫丹美食始终以「优雅精致」的特点为提瓦特人所津津乐道。\n要是向美食评论家们问及枫丹最具代表性的名厨「前德波大饭店主厨」爱可菲绝对位于名单的前沿。\n在食客们的心目中爱可菲创立了「技术料理」这一流派是「革新烹饪理论」的先驱者擅长通过科学定量的风味物质构建出华丽而灵动的味觉奇景\n在评论家的文章中爱可菲是能让抱残守缺之人战栗的「恶魔主厨」不断为烹饪界注入生机甚至揭示了料理进化的新方向。在促进行业技术提升的过程中她还将奢华美食的成本有效降低使许多名菜走上大众的餐桌\n在同行和学员们的传说中爱可菲是「严苛规则」的化身绝不可怠慢半分但凡在料理过程中有一丝不慎半秒走神犯下的错误都会招致她冰刀般锐利而精准的批判。面对爱可菲时要对每种食材和调料抱以敬重要对每道烹饪步骤注入十二万分的诚意与谨慎才有可能拿出让她点头的作品\n在娜维娅等好友看来爱可菲其实是个耐心而细致的女孩她时常通过和好友闲聊或是聆听织体复杂的交响乐来寻找烹饪的灵感。与她共同享受下午茶的时光就如打开惊喜的礼物盒每次都能品尝意想不到的美味…\n而在爱可菲自己的眼中她似乎看不到「过去」的荣誉只有「现在」的自己和「将来」的目标…\n即便品尝了自己倾注全部心力的作品在短暂的满意和陶醉后她依然会陷入深思找出最资深的食客也无法觉察的细微缺陷…\n「距离极致的味觉体验…还有不少提升空间」"
},
{
"Title": "角色故事1",
"Context": "爱可菲是个懂事很早的孩子…早得有些惊人。\n自幼时起爱可菲的味觉就相当敏锐这一特质与她的好奇心产生了剧烈的反应——\n父母稍不注意爱可菲就会将厨房中所有食材和调料都尝一遍并按甜咸酸辣由淡至浓摆得整整齐齐向母亲比划自己喜欢其中哪些特定的种类。\n别家孩子漫无目的地玩耍时爱可菲则忙着分辨「哪些面包棍出炉太久不新鲜」并极力示意母亲将这些受潮的面包「换成刚烤的」小手挥呀挥眼睛眨呀眨让母亲误以为她还没吃饱轻笑着唰唰挥刀给她多切几片面包再打上一碗浓汤…\n当然爱可菲的家庭不至于奢侈到面包刚刚发软就丢掉不吃于是两岁的爱可菲便学会了使用海盐、黄油、糖和胡椒给自己的一日三餐调味。\n见此情形身为厨师的父母慢慢意识到了女儿的特别之处不仅为女儿提供了更多调味品给了爱可菲极大的发挥空间。\n在教导爱可菲诗歌与艺术的美学之余父母还开始给爱可菲讲述「烹饪」的历史让爱可菲慢慢意识到厨师这份工作的重大意义牢牢记住「厨师服的一身洁白就像美丽的雪翅雁一样」。\n很快爱可菲就彻底沉浸在美食的世界之中她主动要求跟着父母去工作在餐馆最安静的角落坐下。得到老板允许后父母每做出一道菜都会留下一小份样品给好奇的爱可菲尝尝风味。\n殊不知这是爱可菲认真的「练习」。\n原本特殊的才能进一步演化逐渐达到令人惊叹的程度。\n无论给爱可菲端上什么菜肴只需试吃一口思索两三分钟爱可菲就能清晰分辨其中的全部食材和调味品甚至估算每种成分的重量。\n终于在四岁时爱可菲迈出了决定性的一步——不是甄别和调味而是要独自打造让自己满意、让所有人惊叹的料理。\n「今天的晚饭我来做吧」\n亲手下厨的第一道菜爱可菲便选择了少见的菜式「酥皮三重卷」以炸得酥脆的面衣包裹鱼肉、禽肉和内脏从而呈现出奇妙的复合风味。\n其难度也不容小觑唯有对每种成分的食材都精确处理才能让多种食材的风味协调统合稍有不慎就是无异于对舌尖施刑。\n爱可菲严格按照食谱上的指示卡准每种食材和调料的用量每个步骤的用时小手忙个没停小脸紧张得通红度过了尤为「充实」的两个小时…\n这略显笨拙的尝试并不像童话故事里那般成为天才一鸣惊人的杰作。\n父母吃过「酥皮三重卷」频频点头无不欣慰欢笑真心夸奖爱可菲但晚餐结束后爱可菲独自品味留下的那份样本眉梢慢慢垂落下来。\n她没能做出让自己满意的料理。\n「总感觉不对劲差了好多…」\n当晚爱可菲躺在床上翻来覆去思来想去\n「是不是我…吃过和做过的料理不够多调味不仔细」\n「只是看懂了菜谱照样子把东西做出来还远远不够」\n自此以后爱可菲的生活就有了明确的目标——\n品尝更多美食不断增长自己的美食见闻做出让大家惊叹的珍馐佳肴"
},
{
"Title": "角色故事2",
"Context": "「增长美食见闻」,这条简单的目标的背后,是巨大的经济开销。\n为了不给家庭增添经济负担爱可菲小小年纪就开始了自己在料理界的征战——\n她刻意避开父母的熟人找了间人气不高不低的餐馆努力争取到了学徒的身份在后厨帮工。\n旁观厨师们制作一道道料理协助他们完成关键的工序能让爱可菲迅速熟悉各式菜肴的烹饪难点。\n对于那些被顾客不满的个例爱可菲也能通过浅尝厨房的留样结合记忆中主厨的烹饪操作进行细致的分析。\n同时忙碌之中难免犯错灶台旁发生的意外对着食谱和教材怎么都无法预测。\n餐馆中的厨师原本认为这位学徒的年纪实在太小了就算犯下打翻锅炉的错误他们都能理解。\n但爱可菲主动要求厨师们别留情面所有刻薄的话语刺耳的指责她都照单全收。\n讨厌吗那就再也别犯同样的低级错误。\n爱可菲正需要快速记忆这些难以捉摸的「错误」将它们积累为自己的「经验」。\n仅仅半年的时间爱可菲对烹饪的理解就突飞猛进很快便能独立制作店内的主要菜品。\n看着这个头顶才刚刚高过灶台的小小学徒站在椅子上挥舞着堪比自己体重一半的厨具做出一道道品质优秀的料理…\n餐馆老板都被爱可菲的热情打动给爱可菲开出了正式店员的薪资让她得到了「小主厨」的赞誉。\n而爱可菲在这里赚到的摩拉基本都用在了「品鉴名菜」上。\n每逢假期她便会邀请父母逐一造访枫丹廷内的知名饭店品尝该店的最佳菜式。\n父母还在惊叹于高级料理的新奇口感时爱可菲则在深深思考食材处理的手法记录脑海中不断涌现的灵感。\n品尝越多美食烹饪技艺的进步越明显方可挣到更多摩拉继续品尝更为精致的菜肴…这个正向循环令爱可菲沉醉其中。\n但有一道美食却始终不知其真味。\n传说中曾在百年前风靡一时的「德波大蛋糕」却随着德波餐馆初代主厨的英年早逝而绝迹于世。\n后世之人也常常试图靠历史记载中的只言片语去复现这道美味其中也有不少仿品在枫丹美食界激起过千层的浪花。\n但爱可菲遍尝了一圈这些作品确实甜美却始终有种说不出道不明的遗憾那道传说中的甜品…真就仅此而已\n在爱可菲五岁生日前后刺玫会麾下的大厨又推出了一道仿品据说已经极度接近当年的口味。\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唯有爱可菲在短暂讶异后恢复从容对芙宁娜致以真诚优雅的问候稍作准备就端上了她的作品。\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在她的带领下德波大饭店的厨师班底突破了原有的极限显著提升了所有菜肴的品质最为刁钻的美食评论家都找不出破绽。\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这将是一场永不停歇的寻味之旅不限时间和地点。产生新的烹饪思路、获得新食材的瞬间爱可菲便会开始全神贯注地钻研。\n对于即将面临的挑战爱可菲早已心中了然她的眼中似乎有了倒映——那极致味觉之山尖的色彩必将折映她无尽的灵感辉光是宛如雪翅雁羽翼般的洁白…"
},
{
"Title": "多目标烹饪机关组",
"Context": "经验丰富的大厨总会同时处理多组食材,作为「技术料理」的先驱,爱可菲烹饪之时,更比飞转的齿轮还忙碌。\n如果说其他厨师可以用「成形」的食材调味爱可菲的烹饪则是从底层逻辑构建料理的「味觉流程」需要加倍细致地切分材料进行复杂的烹煮、精炼与提纯平均每道菜品的工序可比传统料理多出六十步。\n为此爱可菲试图探索同时工作的极限开始烹饪前就以秒为单位如演奏乐曲般将每时每刻双手需要执行的任务划分时序正式烹饪前还要进行彩排演练。\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如同不慎饮下过量烈酒之后的记忆归于朦胧爱可菲只能依稀想起得益于某种…不断从背后涌现而来的清冷寒流她灭却了附着在身上的火焰成功撤出了魔物的活动区。\n直到带着稀有的食材重返枫丹爱可菲才发现自己的后背不知何时坠上了一枚寒光凛冽的神之眼。\n惊魂未定被出离惊喜取代这喜悦让爱可菲忘却了全身的伤痛。\n为了时时刻刻保持清醒爱可菲转而将「神之眼」绑在了自己的发带上希望它能随时随地为自己的头脑降温让自己在大火烘焙的厨房中保持物理意义的绝对冷静…\n有了「神之眼」的协助爱可菲总算实现了自己的奢望——随时随地冷藏食材。不仅如此熟练运用它所提供的元素力后爱可菲将其与原本的「厨艺机关」结合更是获得了可观的武力极大程度上拓宽了寻觅食材的目标范围…\n就连厨具的问题都迎刃而解——以元素力坚冰制成的餐刀和汤勺极为耐用更换也尤为便捷为爱可菲省去了携带器具的重量还有亲自清洗餐具的麻烦。\n只不过身为全枫丹…甚至全提瓦特最擅长使用冰霜的主厨爱可菲制作冰激凌的频率并不高…\n或许将来某天她会将其作为一个专门的料理科目认真攻关开发出随时都能制作的冰雪甜点"
}
]
},
{
"id": 10000113,
"name": "伊法",
"title": "蔚风引灵",
"description": "来自花羽会的兽医,由衷热爱着与人、动物,以及其他生灵为伴的生活。",
"area": "纳塔",
"brief": {
"camp": "特拉洛坎",
"constellation": "佩链座",
"birth": "3月23日",
"cv": {
"cn": "吕书君",
"jp": "寺岛惇太&小松昌平",
"en": "乔尼·罗夸斯妥&多米尼克·卡特兰博内",
"kr": "朴基旭"
}
},
"star": 4,
"element": "风",
"weapon": "法器",
"materials": [
{ "id": 104154, "name": "自在松石", "star": 5 },
{ "id": 113072, "name": "龙像的无智核", "star": 4 },
{ "id": 101250, "name": "肉龙掌", "star": 0 },
{ "id": 112103, "name": "横行霸者的利齿", "star": 3 },
{ "id": 104355, "name": "「纷争」的哲学", "star": 4 },
{ "id": 113074, "name": "升扬样本·战车", "star": 5 }
],
"skills": [
{
"GroupId": 11331,
"Id": 11131,
"Name": "祛风妙仪",
"Description": "<color=#FFD780FF>普通攻击</color>\n进行至多三段的攻击造成<color=#80FFD7FF>风元素伤害</color>。\n\n<color=#FFD780FF>重击</color>\n消耗一定体力向前方射击造成<color=#80FFD7FF>风元素范围伤害</color>。\n\n<color=#FFD780FF>下落攻击</color>\n从空中下坠冲击地面攻击下落路径上的敌人并在落地时造成<color=#80FFD7FF>风元素范围伤害</color>。",
"Icon": "Skill_A_Catalyst_MD"
},
{
"GroupId": 11332,
"Id": 11132,
"Name": "空天疾护",
"Description": "到好哥们出场的时候了,哥们!伊法唤来咔库库一同战斗。\n施放后伊法将获得80点夜魂值并进入<color=#FFD780FF>夜魂加持</color>状态。夜魂加持状态期间,伊法将在咔库库的帮助下悬浮,进行普通攻击时,将依据点按、长按,转为以不同的方式进行<color=#FFD780FF>「援护射击」</color>,发射能够为队伍中所有角色恢复生命值的秘药弹。\n\n此外在夜魂加持状态下长按施放元素战技<color=#FFD780FF>空天疾护</color>时,伊法将转为进行具有夜魂性质的下落攻击,松开技能时可以中断下落攻击,并维持悬浮状态;在下落攻击过程中落地时,伊法的夜魂加持将会结束。",
"Icon": "Skill_S_Ifa_01"
},
{
"GroupId": 11339,
"Id": 11135,
"Name": "复合镇静域",
"Description": "向(暂时还没有冷静下来的)敌人发射一枚疗域镇静剂。疗域镇静剂将在命中时炸裂,产生固缚风场,牵引附近的物品与敌人,并造成具有夜魂性质的<color=#80FFD7FF>风元素范围伤害</color>。\n疗域镇静剂命中处于<color=#80C0FFFF>水元素</color>/<color=#FF9999FF>火元素</color>/<color=#99FFFFFF>冰元素</color>/<color=#FFACFFFF>雷元素</color>附着下的敌人时,会为这些敌人施加<color=#FFD780FF>「镇静标记」</color>效果。",
"Icon": "Skill_E_Ifa_01"
},
{
"GroupId": 11321,
"Id": 1132101,
"Name": "场中医者视野",
"Description": "伊法处于夜魂加持状态下时将基于队伍中所有角色当前夜魂值的总和每1点夜魂值都将使伊法获得1点<color=#FFD780FF>「救援要义」</color>。救援要义可以提升队伍中附近的角色触发的扩散反应与感电反应造成的伤害。",
"Icon": "UI_Talent_S_Ifa_05"
},
{
"GroupId": 11322,
"Id": 1132201,
"Name": "互助救援协议",
"Description": "队伍中的附近的角色触发「夜魂迸发」时伊法的元素精通提升80点持续10秒。",
"Icon": "UI_Talent_S_Ifa_06"
},
{
"GroupId": 11323,
"Id": 1132301,
"Name": "夜域赐礼·集念急救",
"Description": "在夜魂值耗竭后,伊法将会转而消耗燃素来维持夜魂加持。\n在纳塔存在燃素机制的区域时能够进行夜魂传递·伊法。当前场上角色位于一定高度的空中时切换伊法登场时触发伊法将进入夜魂加持状态并获得32点夜魂值。自己的队伍每10秒可以触发一次夜魂传递。",
"Icon": "UI_Talent_S_Ifa_07"
},
{
"GroupId": 11325,
"Id": 1132501,
"Name": "温敷战术包扎",
"Description": "在纳塔存在燃素机制的区域时队伍中自己的当前场上角色或受魂附的龙生命值低于40%时伊法将消耗10点燃素为其恢复40%生命值。该效果每10秒至多触发一次在秘境、征讨领域、深境螺旋中无效。",
"Icon": "UI_Talent_S_Ifa_08"
}
],
"constellation": [
{
"Id": 1131,
"Name": "藤典药剂的备制",
"Description": "<color=#FFD780FF>「援护射击」</color>命中敌人时将为伊法恢复6点元素能量。每8秒至多通过这种方式恢复一次元素能量。",
"Icon": "UI_Talent_S_Ifa_01"
},
{
"Id": 1132,
"Name": "祈祷弹道的助灵",
"Description": "伊法处于夜魂加持状态下时基于队伍中所有角色当前夜魂值的总和超过60点的部分每1点夜魂值都将使伊法额外获得4点<color=#FFD780FF>「救援要义」</color>。\n此外伊法持有「救援要义」的上限提升50点。\n需要解锁固有天赋<color=#FFD780FF>场中医者视野</color>。",
"Icon": "UI_Talent_S_Ifa_02"
},
{
"Id": 1133,
"Name": "与夜谈判的驳词",
"Description": "元素战技<color=#FFD780FF>空天疾护</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Ifa_01",
"ExtraLevel": { "Index": 2, "Level": 3 }
},
{
"Id": 1134,
"Name": "糜烂应体的置换",
"Description": "元素爆发<color=#FFD780FF>复合镇静域</color>产生的固缚风场的持续时间延长至3秒。\n此外施放元素爆发<color=#FFD780FF>复合镇静域</color>后伊法的元素精通提升100点持续15秒。",
"Icon": "UI_Talent_S_Ifa_03"
},
{
"Id": 1135,
"Name": "万物同生的共誓",
"Description": "元素爆发<color=#FFD780FF>复合镇静域</color>的技能等级提高3级。\n至多提升至15级。",
"Icon": "UI_Talent_U_Ifa_02",
"ExtraLevel": { "Index": 9, "Level": 3 }
},
{
"Id": 1136,
"Name": "羽结所庇的诺言",
"Description": "伊法长按进行<color=#FFD780FF>「援护射击」</color>时有50%几率发射一枚额外的秘药弹造成相当于伊法攻击力120%的风元素伤害。该伤害视为普通攻击伤害。\n此外不处于战斗状态下时伊法的夜魂加持状态消耗的夜魂值与燃素降低20%。",
"Icon": "UI_Talent_S_Ifa_04"
}
],
"talks": [
{
"Title": "初次见面…",
"Context": "<color=#37FFFF>伊法:</color>你好,我是来自「花羽会」的兽医,名叫伊法。你可能已经从其他人那里听说过我了,不过没关系,无论印象如何,我们都可以从零开始做朋友嘛。\n<color=#37FFFF>伊法:</color>对了,还有这位…这位是咔库库助理,是龙,也是医生,希望你们相处愉快。\n<color=#37FFFF>咔库库:</color>幸会啊,哥们!"
},
{
"Title": "闲聊·休息",
"Context": "哎,世界上哪有那么多一劳永逸的好事。休息够了就动起来吧。"
},
{ "Title": "闲聊·零食", "Context": "一闲下来总想吃点什么…我有糖、水果和肉干,要来点吗?" },
{ "Title": "闲聊·叫声", "Context": "你有听到动物的叫声吗?…唔,难道是我听错了…" },
{
"Title": "下雨的时候…",
"Context": "小心,咔库库可能会甩你一身水…不过转念一想,反正都在淋雨了,躲不躲也没什么差别。"
},
{
"Title": "打雷的时候…",
"Context": "你知道吗,有不少龙害怕打雷呢。每次都要解释好久才能安抚它们,怪好玩的。"
},
{ "Title": "下雪的时候…", "Context": "呜哇,冷风吹得睁不开眼…还是先别飞了。" },
{ "Title": "刮大风了…", "Context": "在帽子被吹走和脖子被勒住之间只能二选其一呢。" },
{
"Title": "早上好…",
"Context": "早上好。不是我想起这么早的,只是再不起床,留在诊所里的病患们会饿得大叫。"
},
{ "Title": "中午好…", "Context": "巧啊,我正准备去吃午饭,要一起吗?" },
{
"Title": "晚上好…",
"Context": "晚上我一般习惯待在家里,如果有紧急病患,会比较容易找到我。没有意外发生的话,那就好好休息。"
},
{ "Title": "晚安…", "Context": "晚安。明天肯定有想不到的新事情要忙,早些休息吧。" },
{
"Title": "关于伊法自己·朋友",
"Context": "我朋友还算挺多的吧,有一些会让我头疼,有一些能缓解我的头疼,还有一些在这两者之间摇摆。挺好的,他们都是很不错的朋友。"
},
{
"Title": "关于伊法自己·满足",
"Context": "不知道其他人是否如此,反正我在被需要时是会产生满足感的。人的一生可能就是这样被一点点填满的吧,要是大家都能找到让自己感到充实的事物就好了。"
},
{
"Title": "关于我们·提醒",
"Context": "<color=#37FFFF>伊法:</color>接下来的行程都安排好了?身体状态如何?东西带齐了没?\n<color=#37FFFF>伊法:</color>嗯,你看起来精神不错,派蒙也吃饱了,好像确实准备妥当…不过稍等,还是让我再确认一下…\n<color=#37FFFF>咔库库:</color>不会吧哥们!"
},
{
"Title": "关于我们·确认",
"Context": "你和我认识这么久,会觉得我有时太啰嗦,或者关怀过度了吗?不会吗?这样正好?呼…那就好。可能因为我总是跟不会说话的动物打交道,所以无论什么都得多次确认才放心。你不会觉得烦就行。"
},
{
"Title": "关于「神之眼」…",
"Context": "这玩意儿还挺好用的呢。有时候龙飞累了,我会让风托住它们一会儿。悄悄地,不要被发现。"
},
{
"Title": "有什么想要分享…",
"Context": "用食物「哄骗」小动物这招确实很好用,但用多了之后它们就会得寸进尺,甚至养成坐享其成的坏习惯。所以能讲道理时,我还是愿意尽量让它们明白事物的原理,比如掉进陷阱就会摔伤、乱吃东西容易中毒。\n虽然需要额外费些功夫但我觉得是值得去做的事情。更何况有时动物其实比人更聪明呢。"
},
{
"Title": "感兴趣的见闻…",
"Context": "只要愿意用心观察,就会发现世界上有趣的东西多着呢。要是时间允许,我可以盯着大自然看一整天。可惜啊,工作总是会自己找上门来。"
},
{
"Title": "关于卡齐娜…",
"Context": "能看出卡齐娜非常喜欢龙。她如果潜心学习,应该可以成为一名不错的龙医生。不过她已经是位了不起的战士了,就不必分心来做我的学生了。"
},
{
"Title": "关于希诺宁…",
"Context": "我可喜欢和希诺宁聊天了,她总有很多了不起的好点子。虽然有时她会因为更重视功能造出看起来很震撼的工具,但我觉得,「看起来震撼」也是功能的一种。"
},
{
"Title": "关于基尼奇…",
"Context": "严肃探讨,阿乔算是生物吗?\n反正之前它来看病我研究了好一阵也没看出哪有问题就跟基尼奇说「照着它脑门多拍拍拍到正常运转就行了。」\n阿乔听完这话马上就没事了你说巧不巧。"
},
{ "Title": "关于恰斯卡…", "Context": "调停人的工作很重要,她能治我治不好的「病」。" },
{
"Title": "关于欧洛伦·别想太多",
"Context": "他这人想法很怪的。和他交往不用思考太多,反正也想不明白。\n总之他心思不坏会用特别的方式关心身边的朋友认识久了你就知道了。"
},
{
"Title": "关于欧洛伦·很想知道",
"Context": "…虽然已经习惯了,但有时还是很想钻进他的脑袋里,看看他每天到底都在想些什么!"
},
{
"Title": "关于茜特菈莉…",
"Context": "奶奶…奶奶很厉害的,睡衣的花样也多,哈哈…\n为了不卷入额外的麻烦一般黑曜石奶奶叫我跟欧洛伦转达什么我就转达什么一字不多一字不少。有些事情知道的越少越好不然要和她孙子一起吃脑瓜崩。"
},
{
"Title": "关于玛薇卡…",
"Context": "有次遭遇紧急出诊,火神大人提出可以载我一程。真的很疯狂,哥们,要在头晕目眩的状态下给龙清理伤口…在那之前我都不知道自己可以做到这种程度,人的潜能就是这样被开发的。"
},
{
"Title": "关于那维莱特…",
"Context": "你是说万一这位先生生病了,希望我能多关照下?欸,这归我管吗?\n嗯…但既然是咔库库的新哥们…那好吧我试试看咯。"
},
{
"Title": "想要了解伊法·其一",
"Context": "<color=#37FFFF>伊法:</color>诊所门口的板子不是我自己画的。真的,你相信我。\n<color=#37FFFF>咔库库:</color>哈哈!"
},
{
"Title": "想要了解伊法·其二",
"Context": "其实我也不确定咔库库究竟是什么时候学会说第一句人话的。只记得某天凌晨听到它大叫「太阳晒屁股了,哥们!」…早上五点就这样醒来,真的很令人震惊。"
},
{
"Title": "想要了解伊法·其三",
"Context": "飞行试炼我早就通过了,理论上可以叫任何一只绒翼龙来帮我,不过自从咔库库来了,它就不太愿意让我再去找别的龙。也行吧,虽然咔库库还小,但既然它觉得自己能行,那我就愿意信任它。"
},
{
"Title": "想要了解伊法·其四",
"Context": "如果可以,我希望自己是能给大家带来安全感的人。能被人信赖很难得,可不是谁都能做到的。"
},
{
"Title": "想要了解伊法·其五",
"Context": "做兽医挺好的。虽然总有别离,但我从不后悔自己的职业选择。小动物就跟小孩子一样,有时难以沟通又很爱惹麻烦,但对我来说,他们都是值得守护的未来。"
},
{
"Title": "伊法的爱好…",
"Context": "花羽会里喜欢唱歌的人还挺多的,我偶尔也写写歌词弹弹琴,是个调节心情的好办法。"
},
{
"Title": "伊法的烦恼…",
"Context": "<color=#37FFFF>伊法:</color>好像没有什么严重到非解决不可的烦恼…哦,希望咔库库以后能学点更有用的话。\n<color=#37FFFF>咔库库:</color>你在胡说什么?"
},
{
"Title": "喜欢的食物…",
"Context": "新鲜水果是最好的,既能充饥又能解渴,切下来的小块正好喂给咔库库。"
},
{ "Title": "讨厌的食物…", "Context": "新鲜水果是很好的,但放在菜里就有点…就不能直接吃吗?" },
{
"Title": "收到赠礼·其一",
"Context": "<color=#37FFFF>咔库库:</color>交给我吧,哥们!\n<color=#37FFFF>伊法:</color>你这小东西倒是抢上了。好吧好吧,分你一半。"
},
{ "Title": "收到赠礼·其二", "Context": "很好吃,我去拿点果汁,你想喝哪种?" },
{
"Title": "收到赠礼·其三",
"Context": "<color=#37FFFF>咔库库:</color>真的假的?哥们,瞧瞧你做的…\n<color=#37FFFF>伊法:</color>不不,能吃就很好了。谢谢你,哥们。"
},
{
"Title": "生日…",
"Context": "生日快乐!祝你身体健康,永远年轻。\n虽然时间的洪流不可阻挡但可以保持心态上的年轻嘛更何况心情不好也是会影响健康的。来今天就忘记烦恼找些轻松愉快的事情做吧。\n对了有什么喜欢的音乐吗我弹给你听。"
},
{ "Title": "突破的感受·起", "Context": "是热身运动吗?" },
{ "Title": "突破的感受·承", "Context": "自己身体健康才有精力照顾别人,对吧?" },
{
"Title": "突破的感受·转",
"Context": "越是不常用的枪械越要重视保养。哎呀,瞧我在说什么呢,哥们。我是医生,非必要情况是不会动枪的。"
},
{
"Title": "突破的感受·合",
"Context": "<color=#37FFFF>伊法:</color>谢啦,哥们!不必客套,有事随时叫我!\n<color=#37FFFF>咔库库:</color>随时叫我,哥们!"
},
{ "Title": "元素战技·其一", "Context": "走吧!" },
{ "Title": "元素战技·其二", "Context": "注意风向!" },
{ "Title": "元素战技·其三", "Context": "<color=#37FFFF>咔库库:</color>飞啦!" },
{
"Title": "元素战技·其四",
"Context": "<color=#37FFFF>咔库库:</color>哎呀!哎呀!\n<color=#37FFFF>伊法:</color>加油!"
},
{ "Title": "元素爆发·其一", "Context": "小心为妙!" },
{ "Title": "元素爆发·其二", "Context": "好聚好散!" },
{ "Title": "元素爆发·其三", "Context": "想跑?没门!" },
{ "Title": "打开宝箱·其一", "Context": "厉害,亏你能找到这个。" },
{ "Title": "打开宝箱·其二", "Context": "今天是幸运日啊。" },
{ "Title": "打开宝箱·其三", "Context": "拿不下的话我叫咔库库帮你。" },
{ "Title": "生命值低·其一", "Context": "呼吸不畅…" },
{ "Title": "生命值低·其二", "Context": "我再坚持下…" },
{
"Title": "生命值低·其三",
"Context": "<color=#37FFFF>咔库库:</color>伊法,伊法!\n<color=#37FFFF>伊法:</color>知道了知道了…"
},
{ "Title": "同伴生命值低·其一", "Context": "来了。" },
{ "Title": "同伴生命值低·其二", "Context": "快做紧急处理。" },
{ "Title": "倒下·其一", "Context": "战争还没结束吗…" },
{ "Title": "倒下·其二", "Context": "抱歉,没帮上忙…" },
{ "Title": "倒下·其三", "Context": "医者的遗憾…" },
{ "Title": "普通受击·其一", "Context": "别吧…" },
{ "Title": "重受击·其一", "Context": "不会吧哥们!" },
{ "Title": "重受击·其二", "Context": "嘶…就不能对我温柔点?" },
{ "Title": "加入队伍·其一", "Context": "要出远门?" },
{ "Title": "加入队伍·其二", "Context": "急救用品都准备好了。" },
{ "Title": "加入队伍·其三", "Context": "预祝我们一路顺风。" }
],
"stories": [
{
"Title": "角色详细",
"Context": "花羽会生活在天空之上,纳塔人都这么说。\n虽然也有腾跃在峭壁之间的悬木人和于山顶遥望星空的烟谜主但唯有花羽会是身披云羽、触碰虹彩的空中骑士。\n向上看的感觉很好。没有遮拦只是一片空旷人类向往飞翔、向往无忧无虑的渴求得到了最大程度的满足。\n可战火总是在脚下蔓延呼救声也随之而来。即便是绒翼龙受伤后也总是向下落的。\n伊法便是向下看的那位骑士。职责所在习惯使然他有了一双善于识别困境的眼睛。\n「人或者动物总要回到地上去的。」伊法只是笑笑。"
},
{
"Title": "角色故事1",
"Context": "「人做龙的医生,所以龙就做人的医生。」欧洛伦这么说道。\n伊法一时语塞「…不是所有听起来对仗的话都有道理。」\n「这样啊…」\n听这小子的语气还挺遗憾。欧洛伦不是真傻有时故意说些不合逻辑的话没准只是想看看伊法有什么反应。伊法虽然心知肚明但难免又审视起自己的人生来究竟怎么一步一步走都到今天这个境地的呢\n身边永远不缺令人无奈的事物。做兽医就像开幼儿园你没法控制一些缺乏理智的生物只能用尽办法安抚。受了伤会疼清理伤口会疼上药会疼打针也会疼说真的如果伊法自己是小猫小狗估计也不好分辨其中的差异。\n痛了就会大叫、会挣扎这是生物的本能。失去痛觉就像失去警报何其危险。\n可能生活也是这样所以总有悲伤与别离来提醒你日子还在继续人、或者龙都在一年一年地长大。\n做医生就是会遇到这种事很正常是职业选择的正常代价伊法这样安慰自己。\n而与代价相对的他收到了感谢收到了人和动物的喜爱也收到了不少情同兄弟姐妹的「哥们」。\n如果所有快乐与幸福可以抵消掉那些无奈与悲伤那么一切都算值得。"
},
{
"Title": "角色故事2",
"Context": "伊法有时觉得,并不是人主动选择了某个位置,而是那个位置自然而然地站上了一个人。\n就像他不记得从什么时候开始自己成为了那个照顾其他人的存在。邻居小孩会管他叫哥哥健康或者不健康的龙会跟在他后面呜呜地叫甚至不少年纪比他大的人都尊称他一声「老师」让他不胜惶恐。\n这同样让他感到自豪和满足。或许是从内心深处惧怕这份信任的消失他有时变本加厉地履行职责给幼龙们做玩具、弹吉他出诊回来后顺路给邻居们带上几袋新鲜水果偶尔还给部族里的小孩子们辅导功课。\n时间就像被谁不小心拨弄过一样。明明那段他犹豫是否要做兽医、连爬上龙背都费力的日子仿佛就在昨天但一转眼他已经被生活团团围住成为了顶顶可靠的大人。\n他没有什么不满甚至十分感激。只是偶尔部族里的长老提起旧事他也顺便想起以前的自己。\n有些小孩子也很喜欢听这种老故事。一个嘴角还没擦掉火山蛋糕渣的小哥们听着他以前背不来书的糗事竟噗噗地笑起来「伊法哥哥以前也是小弟弟呢是『伊法弟弟』」\n已经很久很久没人这样称呼他伊法有一瞬间的错愕。但很快他又恢复过来一边找擦脸的手绢一边乐呵呵地说着「笑什么你今年多大」\n「我六岁」\n「那巧了我五岁以后叫你哥怎么样」"
},
{
"Title": "角色故事3",
"Context": "如果把远亲都算上的话,伊法有个挺大的家族。他家以盛产兽医闻名,但仔细算算,不做这行的人也不少。换言之,于情于理,伊法不一定非要成为兽医。\n学医是很累的。伊法家几乎只有医书给他做童年读物而当书上的字和图例像流水一样从他脑子里晃出去时小伊法愤愤地想我也不是非要做兽医的\n而很快他就过了那段读不进书的时期。不知道是不是因为常年奔走于战场救助病患伊法父母受深渊影响身体一直不是很好。虽然还小、但仍然意识到了什么的伊法经常担忧地想我也不是非要做兽医的…\n然而即便早熟如他也没能在父母过世前长大。十七岁的伊法参加完父母的葬礼心里空荡荡的不知道应该想些什么。\n最终他还是做了兽医。\n或许是为了传承或许只是惯性使然他成为了人们口中医术最好、脾气最好、责任心也最强的龙医生。他本人在做出决定后倒是没想很多战事愈加频繁繁重的工作反而帮他熬过了一段不太好过的时光。\n时至今日战争已成过去在他终于有时间思考人生的时候人生似乎却不需要他思考太多了。\n每个人都有自己要做的事情呢他想希望大家都能开心。"
},
{
"Title": "角色故事4",
"Context": "若是同情心泛滥,把每一只受过伤的小龙都收养进诊所的话,伊法就只能自己搬到野地里去住了。他很了解自己,一旦开了这个头就很难停下,所以给自己下了个不成文的规定,如非必要、如可避免,不长期收养动物(或人,或其他什么类型的生物)。\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大家以后总能见面的他想。"
},
{
"Title": "记录",
"Context": "几乎每天,伊法都会抽出一点时间将诊疗记录分门别类地整理好。在这难得的安宁时段,他翻阅那些或新或旧的病例,回忆种种以前遇到过的病症。这些都是宝贵的经验,值得分享给需要的同行。\n与此同时将纸制品按照同一个页角对齐叠好对伊法来说也是件放松身心的事情。\n这些不是英雄传说不是珍贵古籍只是枯燥术语堆砌成的潦草笔记。除了伊法以外或许不会再有第二个人有兴趣翻开。\n但伊法经常看得入了迷有时想起诊疗过程中的细节便随手再添几笔。\n最后他会把它们物归原位收回柜子里。如果需要再补充些干燥剂或防虫药。\n这个习惯就这样日复一日地持续着和草木一同枯荣和季节一同更迭。"
},
{
"Title": "神之眼",
"Context": "伊法的诊所到了下午都没开门,大家开始觉得事情不对劲了。\n起初邻居们只是以为他前一晚出诊留宿在了其他部族直到有人带着生病的龙来拜访才发现一个突发高烧迷迷糊糊前来应门的伊法。\n来人还没开口伊法已经满嘴嘀咕着没关系没关系给龙检查起身体来。\n屋漏偏逢连夜雨药方开到一半又有人冲进来说是烬城方向发现深渊侵袭需要医生前去救治受伤的绒翼龙。\n伊法于是又嘀咕着没关系没关系跟随那名骑士去往前线。\n就这样折腾了一通当夜伊法喝下邻居拿给他的药昏睡了一整天才醒。醒来后满身虚汗他摸索着想要找点水喝倒是在床头柜上摸到了一枚神之眼。\n因为发高烧的缘故伊法的记忆有些模糊。在他看来这一次和他平日出诊并无区别他也不是第一次带病工作确实想不起自己到底做了什么特别的事才让上天觉得他需要现在、立即拥有一枚神之眼。\n对此众人有很多不同的理解。部族首领觉得这是对他忘我工作的嘉奖隔壁奶奶觉得这是对他牺牲精神的补偿。说是牺牲精神有点过了伊法连连摆手。\n有个看着很早熟的小大人说伊法哥哥听说风能将人们吹到一起所以这是神明在告诉你人在困难时是可以依赖同伴的。说得很有道理伊法没有反驳但情况紧急时并无其他办法他只是做了下意识应该做的事。\n这种东西是讨论不出答案的或许老天只是觉得他体温太高了需要用风吹一吹也说不定。伊法摇摇头神之眼这件事就算翻篇了。\n他平时的确不怎么用得到神之眼至少在咔库库出现之前是这样。咔库库来了之后无论如何都非要带着他飞边飞还要边大叫「再高一点哥们」\n这种时候神之眼总能派上大用场。"
}
]
}
]

View File

@@ -9309,6 +9309,87 @@
"source": [{ "name": "30级以上熔岩辉龙像掉落", "type": "single" }],
"convert": []
},
{
"id": 113073,
"name": "升扬样本·骑士",
"description": "这是物质经过炼金术的活性化反应后,所留下的珍稀样本。\\n秘典的记述中青铜骑士并不知道自己的使命是征服还是守护。它只是经历了炼金术的擢升开始寻找自己的意义罢了。青铜时代是英雄的时代。",
"type": "角色培养素材",
"star": 5,
"source": [{ "name": "70级以上门扉前的弈局挑战奖励", "type": "single" }],
"convert": [
{
"id": "61101",
"source": [
{ "id": "113021", "name": "异梦溶媒", "type": "消耗品", "star": 4, "count": 1 },
{ "id": "113074", "name": "升扬样本·战车", "type": "角色培养素材", "star": 5, "count": 1 }
]
},
{
"id": "61102",
"source": [
{ "id": "113021", "name": "异梦溶媒", "type": "消耗品", "star": 4, "count": 1 },
{ "id": "113075", "name": "升扬样本·王族", "type": "角色培养素材", "star": 5, "count": 1 }
]
}
]
},
{
"id": 113074,
"name": "升扬样本·战车",
"description": "这是物质经过炼金术的活性化反应后,所留下的珍稀样本。\\n棋子的「车」词源乃是古代须弥语言的「战车」。在占卜牌中「战车」的画面必须驾驭黑白双色的神驹这也是炼金术里驾驭性质相反之物的平衡之道。",
"type": "角色培养素材",
"star": 5,
"source": [{ "name": "70级以上门扉前的弈局挑战奖励", "type": "single" }],
"convert": [
{
"id": "61103",
"source": [
{ "id": "113021", "name": "异梦溶媒", "type": "消耗品", "star": 4, "count": 1 },
{ "id": "113073", "name": "升扬样本·骑士", "type": "角色培养素材", "star": 5, "count": 1 }
]
},
{
"id": "61104",
"source": [
{ "id": "113021", "name": "异梦溶媒", "type": "消耗品", "star": 4, "count": 1 },
{ "id": "113075", "name": "升扬样本·王族", "type": "角色培养素材", "star": 5, "count": 1 }
]
}
]
},
{
"id": 113075,
"name": "升扬样本·王族",
"description": "这是物质经过炼金术的活性化反应后,所留下的珍稀样本。\\n「王后」棋子曾经的名字是「宰相」。但是随着游戏规则的发展它变成了象征外戚势力的最强重子实至名归的黄金王族。\\n普通士兵的升变和炼金术的物质嬗变或许有些相似之处吧。但是就算成为了黄金也只能用倒置的城堡来表示。",
"type": "角色培养素材",
"star": 5,
"source": [{ "name": "70级以上门扉前的弈局挑战奖励", "type": "single" }],
"convert": [
{
"id": "61105",
"source": [
{ "id": "113021", "name": "异梦溶媒", "type": "消耗品", "star": 4, "count": 1 },
{ "id": "113073", "name": "升扬样本·骑士", "type": "角色培养素材", "star": 5, "count": 1 }
]
},
{
"id": "61106",
"source": [
{ "id": "113021", "name": "异梦溶媒", "type": "消耗品", "star": 4, "count": 1 },
{ "id": "113074", "name": "升扬样本·战车", "type": "角色培养素材", "star": 5, "count": 1 }
]
}
]
},
{
"id": 113076,
"name": "秘源积气喉",
"description": "自不再活动、看似龙首的秘源机兵上取下的特殊结构。\\n据说这个结构原本是为了使各种物体能够挣脱星体的束缚而设计的首先被运用在了各种秘源机关之上。或许如果一切顺利的话这项技术本来将被应用在其他更重要的地方吧…",
"type": "角色培养素材",
"star": 4,
"source": [{ "name": "30级以上秘源机兵·统御械掉落", "type": "single" }],
"convert": []
},
{
"id": 114001,
"name": "高塔孤王的破瓦",
@@ -10884,6 +10965,15 @@
"source": [],
"convert": []
},
{
"id": 121234,
"name": "绮灿而神秘的赠礼",
"description": "装有星形宝石的水晶球,光彩绚烂,是通过魔女会的试炼后得到的赠礼。依照转交人的描述,普通人或许无法驾驭这一器物。只是目前,这颗水晶球暂时无法发挥出它原本的力量,似乎在等待一个合适的时机…",
"type": "任务道具",
"star": 5,
"source": [],
"convert": []
},
{
"id": 129001,
"name": "星间之泪",

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"id": 0,
"order": 1,
"name": "天地万象",
"version": "5.5",
"version": "5.6",
"card": "",
"icon": "UI_AchievementIcon_O001"
},

View File

@@ -5069,7 +5069,7 @@
"series": 0,
"order": 1122,
"name": "「…腐肉朽处花争妍。」",
"description": "终结十位已歿武士的怨念。",
"description": "终结十位已武士的怨念。",
"reward": 5,
"version": "2.0",
"trigger": { "type": "GROUP_NOTIFY" }
@@ -12392,6 +12392,26 @@
"version": "5.5",
"trigger": { "type": "COMBAT_CONFIG_COMMON" }
},
{
"id": 82296,
"series": 0,
"order": 2296,
"name": "「深蓝」",
"description": "解开弈局中的所有谜题。",
"reward": 5,
"version": "5.6",
"trigger": { "type": "Unknown" }
},
{
"id": 82297,
"series": 0,
"order": 2297,
"name": "液流冰结",
"description": "连续使用冰元素进行攻击,阻碍「液流动量」的积累。",
"reward": 5,
"version": "5.6",
"trigger": { "type": "Unknown" }
},
{
"id": 84000,
"series": 0,
@@ -14806,6 +14826,36 @@
"task": [{ "questId": 5029, "name": "当一切镌刻成碑", "type": "魔神任务" }]
}
},
{
"id": 84342,
"series": 0,
"order": 5121,
"name": "如孩童的躁动般",
"description": "与伙伴们一同抵御自雪山而来的魔物。",
"reward": 5,
"version": "5.6",
"trigger": { "type": "Unknown" }
},
{
"id": 84343,
"series": 0,
"order": 5122,
"name": "瓦尔普吉斯的游戏",
"description": "通过魔女会设下的试炼。",
"reward": 5,
"version": "5.6",
"trigger": { "type": "Unknown" }
},
{
"id": 84344,
"series": 0,
"order": 5123,
"name": "悖理",
"description": "完成「悖理」。",
"reward": 5,
"version": "5.6",
"trigger": { "type": "Unknown" }
},
{
"id": 84501,
"series": 10,

View File

@@ -111,6 +111,22 @@
],
"source": { "index": 5, "area": "枫丹", "name": "琅诵" }
},
{
"id": 10000112,
"contentId": 504976,
"dropDays": [2, 5, 7],
"name": "爱可菲",
"itemType": "character",
"star": 5,
"weapon": "长柄武器",
"element": "冰",
"materials": [
{ "id": 104341, "name": "「正义」的教导", "star": 2 },
{ "id": 104342, "name": "「正义」的指引", "star": 3 },
{ "id": 104343, "name": "「正义」的哲学", "star": 4 }
],
"source": { "index": 5, "area": "枫丹", "name": "箴铭" }
},
{
"id": 10000107,
"contentId": 503612,
@@ -1535,6 +1551,22 @@
],
"source": { "index": 5, "area": "枫丹", "name": "琅诵" }
},
{
"id": 10000113,
"contentId": 504977,
"dropDays": [3, 6, 7],
"name": "伊法",
"itemType": "character",
"star": 4,
"weapon": "法器",
"element": "风",
"materials": [
{ "id": 104353, "name": "「纷争」的教导", "star": 2 },
{ "id": 104354, "name": "「纷争」的指引", "star": 3 },
{ "id": 104355, "name": "「纷争」的哲学", "star": 4 }
],
"source": { "index": 6, "area": "纳塔", "name": "旋复" }
},
{
"id": 11501,
"contentId": 293,
@@ -2431,6 +2463,22 @@
],
"source": { "index": 5, "area": "枫丹", "name": "奇械" }
},
{
"id": 13514,
"contentId": 0,
"dropDays": [2, 5, 7],
"name": "香韵奏者",
"itemType": "weapon",
"star": 5,
"weapon": "长柄武器",
"materials": [
{ "id": 114053, "name": "纯圣露滴的滤渣", "star": 2 },
{ "id": 114054, "name": "纯圣露滴的凝华", "star": 3 },
{ "id": 114055, "name": "纯圣露滴的醴泉", "star": 4 },
{ "id": 114056, "name": "纯圣露滴的真粹", "star": 5 }
],
"source": { "index": 5, "area": "枫丹", "name": "匠理" }
},
{
"id": 11401,
"contentId": 208,
@@ -3583,6 +3631,22 @@
],
"source": { "index": 5, "area": "枫丹", "name": "机思" }
},
{
"id": 15432,
"contentId": 0,
"dropDays": [1, 4, 7],
"name": "冷寂迸音",
"itemType": "weapon",
"star": 4,
"weapon": "弓",
"materials": [
{ "id": 114049, "name": "悠古弦音的残章", "star": 2 },
{ "id": 114050, "name": "悠古弦音的断章", "star": 3 },
{ "id": 114051, "name": "悠古弦音的乐章", "star": 4 },
{ "id": 114052, "name": "悠古弦音的回响", "star": 5 }
],
"source": { "index": 5, "area": "枫丹", "name": "机思" }
},
{
"id": 15430,
"contentId": 503391,

View File

@@ -1,4 +1,16 @@
[
{
"id": 10000112,
"contentId": 504976,
"name": "爱可菲",
"title": "明绚千韵",
"area": "枫丹",
"birthday": [6, 8],
"star": 5,
"element": "冰",
"weapon": "长柄武器",
"nameCard": "爱可菲·韵味"
},
{
"id": 10000111,
"contentId": 504570,
@@ -647,6 +659,18 @@
"weapon": "单手剑",
"nameCard": "神里绫华·扇子"
},
{
"id": 10000113,
"contentId": 504977,
"name": "伊法",
"title": "蔚风引灵",
"area": "纳塔",
"birthday": [3, 23],
"star": 4,
"element": "风",
"weapon": "法器",
"nameCard": "伊法·哥们"
},
{
"id": 10000110,
"contentId": 504621,

View File

@@ -2592,5 +2592,77 @@
"postId": "62885089",
"up5List": [14519, 14515],
"up4List": [11430, 12430, 13430, 14430, 15430]
},
{
"name": "煅火的祝赐",
"version": "5.5",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/03/31/91052d7052db09f410fdb3b4e7b00ee5_1628504185012104420.jpg",
"from": "2025-04-15T18:00:00+08:00",
"to": "2025-05-06T14:59:00+08:00",
"type": 301,
"postId": "63412595",
"up5List": [10000103],
"up4List": [10000076, 10000024, 10000048]
},
{
"name": "杯装之诗",
"version": "5.5",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/03/31/06fbe801d25d72e5ebfe3147b453d5c0_6888294552686067061.jpg",
"from": "2025-04-15T18:00:00+08:00",
"to": "2025-05-06T14:59:00+08:00",
"type": 400,
"postId": "63412596",
"up5List": [10000022],
"up4List": [10000076, 10000024, 10000048]
},
{
"name": "神铸赋形",
"version": "5.5",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/03/31/0b568269ad1623d5afa149bb00b03f95_2662259576865916543.jpg",
"from": "2025-04-15T18:00:00+08:00",
"to": "2025-05-06T14:59:00+08:00",
"type": 302,
"postId": "63412594",
"up5List": [11516, 15503],
"up4List": [11403, 12402, 13407, 14401, 15401]
},
{
"name": "莓色香颂",
"version": "5.6",
"order": 1,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/04/22/e3bbe5840f24c4ab0635019da5c71b37_6891739823487935002.png",
"from": "2025-05-07T06:00:00+08:00",
"to": "2025-05-27T17:59:00+08:00",
"type": 301,
"postId": "64170334",
"up5List": [10000112],
"up4List": [10000113, 10000105, 10000074]
},
{
"name": "刺玫的铭誓",
"version": "5.6",
"order": 1,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/04/22/7ef46e7ae7a8e6fa6e93082b327bf0d5_6139600013638855277.png",
"from": "2025-05-07T06:00:00+08:00",
"to": "2025-05-27T17:59:00+08:00",
"type": 400,
"postId": "64170335",
"up5List": [10000091],
"up4List": [10000113, 10000105, 10000074]
},
{
"name": "神铸赋形",
"version": "5.6",
"order": 1,
"banner": "https://sdk.hoyoverse.com/upload/ann/2025/04/22/b28047489c758bf229e84e95fd745576_8959763759873585938.png",
"from": "2025-05-07T06:00:00+08:00",
"to": "2025-05-27T17:59:00+08:00",
"type": 302,
"postId": "64170337",
"up5List": [13514, 12512],
"up4List": [11427, 12427, 13427, 15427, 14402]
}
]

View File

@@ -1091,6 +1091,20 @@
"desc": "名片纹饰。「如果需要的话,即使在梦中也应该不懈锻炼…这种事,只要努力一下就能做到!」话是这么说,但是真的能够做到吗?",
"source": "伊安珊的好感等级达到10级时获取。"
},
{
"id": 210241,
"name": "爱可菲·韵味",
"type": "好感",
"desc": "名片纹饰。对于爱可菲来说,韵律和味道是有对应关系的。至于她为什么不吃着美味的餐点来作曲,理由也很简单。稳定的味道值得信赖,一直写同一首曲子那就没什么意义了。",
"source": "爱可菲的好感等级达到10级时获取。"
},
{
"id": 210242,
"name": "伊法·哥们",
"type": "好感",
"desc": "名片纹饰。「『和伊法搞好关系有什么好处吗?』有的,哥们,包有的。」「有的,哥们,有的。」",
"source": "伊法的好感等级达到10级时获取。"
},
{
"id": 210057,
"name": "庆典·无相",
@@ -1490,6 +1504,13 @@
"desc": "名片纹饰。在玩耍和追逐中学会战斗的方式,在伤口和苦痛中取得不被磨灭的勇气,然后将此作为起点,我们开始打探世界的边垒。",
"source": "纪行系统奖励获取。"
},
{
"id": 210243,
"name": "纪行·舞剧",
"type": "纪行",
"desc": "名片纹饰。拍摄映影的场记也是一个重要的工作,作用就是记录拍摄场次的各种信息。仔细一想,你就是最好的场记,因为你已经记住了一切。",
"source": "纪行系统奖励获取。"
},
{
"id": 210001,
"name": "原神·印象",

View File

@@ -24,6 +24,7 @@
{ "id": 14504, "contentId": 1222, "name": "尘世之锁", "star": 5, "weapon": "法器" },
{ "id": 14502, "contentId": 297, "name": "四风原典", "star": 5, "weapon": "法器" },
{ "id": 14501, "contentId": 227, "name": "天空之卷", "star": 5, "weapon": "法器" },
{ "id": 13514, "contentId": 0, "name": "香韵奏者", "star": 5, "weapon": "长柄武器" },
{ "id": 13513, "contentId": 501725, "name": "柔灯挽歌", "star": 5, "weapon": "长柄武器" },
{ "id": 13512, "contentId": 501137, "name": "赤月之形", "star": 5, "weapon": "长柄武器" },
{ "id": 13511, "contentId": 4794, "name": "赤沙之杖", "star": 5, "weapon": "长柄武器" },
@@ -55,6 +56,7 @@
{ "id": 11503, "contentId": 2129, "name": "苍古自由之誓", "star": 5, "weapon": "单手剑" },
{ "id": 11502, "contentId": 215, "name": "天空之刃", "star": 5, "weapon": "单手剑" },
{ "id": 11501, "contentId": 293, "name": "风鹰剑", "star": 5, "weapon": "单手剑" },
{ "id": 15432, "contentId": 0, "name": "冷寂迸音", "star": 4, "weapon": "弓" },
{ "id": 15431, "contentId": 501964, "name": "碎链", "star": 4, "weapon": "弓" },
{ "id": 15430, "contentId": 503391, "name": "缀花之翎", "star": 4, "weapon": "弓" },
{ "id": 15427, "contentId": 7406, "name": "测距规", "star": 4, "weapon": "弓" },

View File

@@ -64,9 +64,20 @@
</template>
</v-app-bar>
<div class="uc-box">
<div class="uc-top">
<div class="uc-top-title">UID{{ uidCur }}</div>
<div class="uc-top-info">
<div class="uc-box-top">
<div class="uc-box-title">
<span class="uc-box-uid">UID{{ uidCur }}</span>
<span
class="uc-ov-item"
v-for="(item, index) in roleOverview"
:key="index"
:title="`${item.label}${item.cnt}`"
>
<img :src="`/icon/element/${item.label}.webp`" alt="element" />
<span>{{ item.cnt }}</span>
</span>
</div>
<div class="uc-box-info">
<span>角色详情</span>
<span>|Render by TeyvatGuide v{{ version }}|</span>
<span>更新于 {{ getUpdateTime() }}</span>
@@ -111,10 +122,11 @@ import { AppCharacterData } from "@/data/index.js";
import { useUserStore } from "@/store/modules/user.js";
import TGLogger from "@/utils/TGLogger.js";
import { generateShareImg } from "@/utils/TGShare.js";
import { timestampToDate } from "@/utils/toolFunc.js";
import { getZhElement, timestampToDate } from "@/utils/toolFunc.js";
import TakumiRecordGenshinApi from "@/web/request/recordReq.js";
type TabItem = { label: string; value: string };
type OverviewItem = { element: string; cnt: number; label: string };
const modeList: Readonly<Array<TabItem>> = [
{ label: "经典视图", value: "classic" },
@@ -134,6 +146,7 @@ const showMode = ref<"classic" | "card" | "dev">("dev");
const resetSelect = ref<boolean>(false);
const uidCur = ref<string>();
const uidList = shallowRef<Array<string>>([]);
const roleOverview = shallowRef<Array<OverviewItem>>([]);
const roleList = shallowRef<Array<TGApp.Sqlite.Character.UserRole>>([]);
const selectedList = shallowRef<Array<TGApp.Sqlite.Character.UserRole>>([]);
const dataVal = shallowRef<TGApp.Sqlite.Character.UserRole>();
@@ -194,6 +207,20 @@ function getOrderedList(
});
}
function getOverview(data: Array<TGApp.Sqlite.Character.UserRole>): Array<OverviewItem> {
const overview: Array<OverviewItem> = [];
for (const role of data) {
const element = role.avatar.element;
const index = overview.findIndex((item) => item.element === element);
if (index === -1) {
overview.push({ element, cnt: 1, label: `${getZhElement(element)}元素` });
} else {
overview[index].cnt += 1;
}
}
return overview.sort((a, b) => b.cnt - a.cnt);
}
async function loadUid(): Promise<void> {
uidList.value = await TSUserAvatar.getAllUid();
if (uidList.value.length === 0) uidList.value = [account.value.gameUid];
@@ -209,6 +236,7 @@ async function loadRole(): Promise<void> {
roleList.value = [];
const roleData = await TSUserAvatar.getAvatars(Number(uidCur.value));
roleList.value = getOrderedList(roleData);
roleOverview.value = getOverview(roleData);
selectedList.value = roleList.value;
dataVal.value = roleData[selectIndex.value];
isEmpty.value = roleList.value.length === 0;
@@ -391,7 +419,29 @@ function handleSwitch(next: boolean): void {
dataVal.value = selectedList.value[selectIndex.value];
}
</script>
<style lang="css" scoped>
<style lang="scss" scoped>
@use "@styles/github.styles.scss" as github-styles;
.uc-top-title {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
margin-left: 8px;
gap: 8px;
img {
width: 32px;
height: 32px;
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
}
.uc-select {
display: flex;
align-items: center;
@@ -420,37 +470,45 @@ function handleSwitch(next: boolean): void {
gap: 8px;
}
.uc-top {
.uc-box-top {
display: flex;
width: 100%;
height: 50px;
align-items: center;
align-items: flex-end;
justify-content: space-between;
padding: 10px;
padding: 8px 0;
border-bottom: 1px solid var(--common-shadow-2);
}
.uc-top-title {
.uc-box-title {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
margin-left: 8px;
gap: 8px;
}
.uc-box-uid {
@include github-styles.github-tag-dark-gen(#ffcd0c);
padding: 2px 4px;
border-radius: 4px;
}
.uc-ov-item {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
border-radius: 4px;
font-family: var(--font-title);
font-size: 18px;
img {
width: 32px;
height: 32px;
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
flex-shrink: 0;
width: 28px;
height: 28px;
}
}
.uc-top-info {
.uc-box-info {
z-index: -1;
font-size: 14px;
opacity: 0.8;

View File

@@ -3,7 +3,7 @@
<template #prepend>
<div class="achi-prepend">
<img alt="icon" src="../../assets/icons/achievements.svg" />
<span @click="switchHideFin">我的成就</span>
<span>我的成就</span>
<v-select
density="compact"
v-model="uidCur"
@@ -35,6 +35,16 @@
<v-btn class="top-btn" prepend-icon="mdi-export" @click="exportJson()">导出</v-btn>
<v-btn class="top-btn" prepend-icon="mdi-plus" @click="createUid()">新建存档</v-btn>
<v-btn class="top-btn" prepend-icon="mdi-delete" @click="deleteUid()">删除存档</v-btn>
<div class="top-switch" @click="switchHideFin">
<v-icon v-if="hideFin" color="var(--tgc-od-green)">
mdi-checkbox-marked-circle-outline
</v-icon>
<v-icon v-else color="var(--tgc-od-white)">mdi-circle</v-icon>
<span>隐藏已完成</span>
</div>
</div>
<div class="top-link" title="使用Yae导入" @click="toYae()">
<v-icon>mdi-comment-question-outline</v-icon>
</div>
</template>
</v-app-bar>
@@ -71,6 +81,7 @@ import { path } from "@tauri-apps/api";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { open, save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { openUrl } from "@tauri-apps/plugin-opener";
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
@@ -267,6 +278,10 @@ async function deleteUid(): Promise<void> {
uidCur.value = uidList.value[0];
}
async function toYae(): Promise<void> {
await openUrl("https://github.com/HolographicHat/Yae");
}
onUnmounted(async () => {
if (achiListener !== null) {
achiListener();
@@ -291,7 +306,6 @@ onUnmounted(async () => {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
cursor: pointer;
}
}
@@ -325,6 +339,27 @@ onUnmounted(async () => {
font-family: var(--font-title);
}
.top-switch {
position: relative;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
font-family: var(--font-title);
}
.top-link {
margin-left: auto;
margin-right: 16px;
cursor: pointer;
color: var(--tgc-od-white);
&:hover {
color: var(--tgc-od-orange);
}
}
.wrap {
display: flex;
height: calc(100vh - 144px);

View File

@@ -5,7 +5,7 @@
<v-select
v-model="curGid"
class="home-tool-select"
:items="gameList"
:items="games"
:hide-details="true"
item-value="gid"
variant="outlined"
@@ -72,9 +72,9 @@ import { storeToRefs } from "pinia";
import { type Component, computed, onMounted, ref, shallowRef, watch } from "vue";
import { useAppStore } from "@/store/modules/app.js";
import useBBSStore from "@/store/modules/bbs.js";
import { useHomeStore } from "@/store/modules/home.js";
import TGLogger from "@/utils/TGLogger.js";
import apiHubReq from "@/web/request/apiHubReq.js";
type SFComp = Component & {
__file?: string;
@@ -84,14 +84,16 @@ type SFComp = Component & {
};
type SelectItem = { icon: string; title: string; gid: number };
const bbsStore = useBBSStore();
const { devMode, isLogin } = storeToRefs(useAppStore());
const { gameList } = storeToRefs(bbsStore);
const homeStore = useHomeStore();
const showItemsAll: Array<string> = ["素材日历", "限时祈愿", "近期活动"];
const curGid = ref<number>(2);
const gameList = shallowRef<Array<SelectItem>>();
const games = shallowRef<Array<SelectItem>>();
const loadItems = shallowRef<Array<string>>([]);
const components = shallowRef<Array<SFComp>>([]);
const showItems = computed<Array<string>>({
@@ -100,13 +102,13 @@ const showItems = computed<Array<string>>({
});
onMounted(async () => {
await bbsStore.refreshGameList();
// @ts-expect-error-next-line The import.meta meta-property is not allowed in files which will build into CommonJS output.
const isProdEnv = import.meta.env.MODE === "production";
if (isProdEnv && devMode.value) devMode.value = false;
if (isLogin.value) {
await showLoading.start("正在加载首页小部件");
const allGames = await apiHubReq.game();
gameList.value = allGames.map((i) => ({ icon: i.app_icon, title: i.name, gid: i.id }));
games.value = gameList.value.map((i) => ({ icon: i.app_icon, title: i.name, gid: i.id }));
}
await loadComp();
});

View File

@@ -90,10 +90,17 @@
</v-app-bar>
<div class="pc-posts">
<div v-for="item in curPosts" :key="item.post.post_id">
<TPostCard @onSelected="handleSelected" :model-value="item" :select-mode="selectedMode" />
<TPostCard
@onSelected="handleSelected"
:model-value="item"
:select-mode="selectedMode"
:user-click="true"
@onUserClick="handleUserClick"
/>
</div>
</div>
<ToCollectPost @submit="load" :post="selectedPost" v-model="showOverlay" />
<ToCollectPost @submit="load" :post="selectedPost" v-model="showCollect" />
<VpOverlayUser v-model="showUser" :gid="curGid" :uid="curUid" />
</template>
<script lang="ts" setup>
import TPostCard from "@comp/app/t-postcard.vue";
@@ -101,6 +108,7 @@ import showDialog from "@comp/func/dialog.js";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import ToCollectPost from "@comp/pageCollect/to-collectPost.vue";
import VpOverlayUser from "@comp/viewPost/vp-overlay-user.vue";
import TSUserCollection from "@Sqlite/modules/userCollect.js";
import { event } from "@tauri-apps/api";
import type { UnlistenFn } from "@tauri-apps/api/event";
@@ -116,12 +124,17 @@ let collectListener: UnlistenFn | null = null;
const curSelect = ref<string>("未分类");
const page = ref<number>(1);
const curUid = ref<string>("");
const curGid = ref<number>(2);
const selectedMode = ref<boolean>(false);
const showOverlay = ref<boolean>(false);
const showUser = ref<boolean>(false);
const showCollect = ref<boolean>(false);
const sortId = ref<boolean>(false);
const selectedPost = shallowRef<Array<string>>([]);
const collections = shallowRef<Array<TGApp.Sqlite.UserCollection.UFCollection>>([]);
const selected = shallowRef<Array<TGApp.Sqlite.UserCollection.UFPost>>([]);
const length = computed<number>(() => Math.ceil(selected.value.length / 12));
const view = computed<number>(() => (length.value === 1 ? 1 : length.value > 5 ? 5 : length.value));
const curPosts = computed<Array<TGApp.BBS.Post.FullData>>(() =>
@@ -178,10 +191,11 @@ async function load(): Promise<void> {
}
function toSelect(): void {
if (showUser.value) showUser.value = false;
if (selectedMode.value) {
selectedMode.value = false;
if (selectedPost.value.length === 0) return;
showOverlay.value = true;
showCollect.value = true;
return;
}
selectedPost.value = [];
@@ -382,6 +396,13 @@ async function mergePosts(posts: Array<TGApp.BBS.Post.FullData>, collect: string
if (!res) await TGLogger.Error(`[PostCollect] mergePosts [${post.post.post_id}]`);
}
}
function handleUserClick(user: TGApp.BBS.Post.User, gid: number): void {
if (showCollect.value) showCollect.value = false;
curGid.value = gid;
curUid.value = user.uid;
showUser.value = true;
}
</script>
<style lang="css" scoped>
.pc-top {

View File

@@ -132,9 +132,11 @@ import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import VpOverlayUser from "@comp/viewPost/vp-overlay-user.vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { storeToRefs } from "pinia";
import { computed, nextTick, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import useBBSStore from "@/store/modules/bbs.js";
import TGLogger from "@/utils/TGLogger.js";
import { createPost } from "@/utils/TGWindow.js";
import ApiHubReq from "@/web/request/apiHubReq.js";
@@ -162,6 +164,7 @@ const showSearch = ref<boolean>(false);
const curUid = ref<string>("");
const showUser = ref<boolean>(false);
const { gameList } = storeToRefs(useBBSStore());
const selectedForum = shallowRef<SortSelect>();
const sortGameList = shallowRef<Array<SortSelectGame>>([]);
const postRaw = shallowRef<PostRaw>({ isLast: false, lastId: "", total: 0 });
@@ -176,6 +179,7 @@ const curForums = computed<Array<SortSelect>>(() => {
onMounted(async () => {
await showLoading.start("正在加载帖子数据");
await loadForums();
await nextTick();
let { gid, forum } = route.query;
if (!gid) gid = route.params.gid;
if (!forum) forum = route.params.forum;
@@ -219,11 +223,10 @@ watch(
// 初始化
async function loadForums(): Promise<void> {
const allGames = await ApiHubReq.game();
const allForums = await ApiHubReq.forum();
const gameList: Array<SortSelectGame> = [];
const list: Array<SortSelectGame> = [];
for (const gameForum of allForums) {
const gameFind = allGames.find((i) => i.id === gameForum.game_id);
const gameFind = gameList.value.find((i) => i.id === gameForum.game_id);
if (!gameFind) continue;
const gameItem: SortSelectGame = {
gid: gameForum.game_id,
@@ -233,9 +236,9 @@ async function loadForums(): Promise<void> {
.map((i) => ({ text: i.name, value: i.id, icon: i.icon_pure })),
text: gameFind.name,
};
gameList.push(gameItem);
list.push(gameItem);
}
sortGameList.value = gameList;
sortGameList.value = list;
}
function getForum(forum: number): SortSelect {
@@ -342,8 +345,9 @@ function searchPost(): void {
} else createPost(search.value);
}
function handleUserClick(user: TGApp.BBS.Post.User): void {
function handleUserClick(user: TGApp.BBS.Post.User, gid: number): void {
if (showSearch.value) showSearch.value = false;
curGid.value = gid;
curUid.value = user.uid;
showUser.value = true;
}

View File

@@ -32,7 +32,7 @@
@click="firstLoad(tab, true)"
icon="mdi-refresh"
/>
<v-btn class="post-news-btn" @click="showList = true" icon="mdi-view-list" />
<v-btn class="post-news-btn" @click="handleList()" icon="mdi-view-list" />
<v-btn
class="post-news-btn"
@click="switchAnno"
@@ -68,12 +68,11 @@ import showSnackbar from "@comp/func/snackbar.js";
import ToChannel from "@comp/pageNews/to-channel.vue";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import { storeToRefs } from "pinia";
import type { Ref } from "vue";
import { computed, onMounted, reactive, ref, shallowRef } from "vue";
import { computed, onMounted, reactive, Ref, ref, shallowRef } from "vue";
import { useRoute, useRouter } from "vue-router";
import { type NewsType, useAppStore } from "@/store/modules/app.js";
import TGBbs from "@/utils/TGBbs.js";
import useBBSStore from "@/store/modules/bbs.js";
import TGLogger from "@/utils/TGLogger.js";
import { createPost } from "@/utils/TGWindow.js";
import painterReq from "@/web/request/painterReq.js";
@@ -84,11 +83,15 @@ type RawData = { [key in NewsType]: Ref<RawItem> };
const router = useRouter();
const { recentNewsType } = storeToRefs(useAppStore());
const { gameList } = storeToRefs(useBBSStore());
const { gid } = <{ gid: string }>useRoute().params;
const tabValues: Readonly<Array<NewsType>> = ["notice", "activity", "news"];
const tabMap: Readonly<Record<NewsType, string>> = { notice: "1", activity: "2", news: "3" };
const gameName = TGBbs.channels.find((v) => v.gid.toString() === gid)?.title || "未知分区";
const label = computed<string>(() => {
const game = gameList.value.find((v) => v.id.toString() === gid);
return game?.name || "未知分区";
});
const loading = ref<boolean>(false);
const showList = ref<boolean>(false);
@@ -117,20 +120,22 @@ async function firstLoad(key: NewsType, refresh: boolean = false): Promise<void>
if (rawData[key].lastId !== 0) {
if (!refresh) {
loading.value = false;
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
return;
}
postData[key] = [];
rawData[key].lastId = 0;
}
await showLoading.start(`正在获取${gameName}${rawData[key].name}数据`);
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
await showLoading.start(`正在获取${label.value}${rawData[key].name}数据`);
const getData = await painterReq.news(gid, tabMap[key]);
await showLoading.update(`数量:${getData.list.length},是否最后一页:${getData.is_last}`);
rawData[key] = { isLast: getData.is_last, name: rawData[key].name, lastId: getData.list.length };
postData[key] = getData.list;
await showLoading.end();
await TGLogger.Info(`[News][${gid}][firstLoad] 获取${rawData[key].name}数据成功`);
showSnackbar.success(`获取${gameName}${rawData[key].name}数据成功,共 ${getData.list.length}`);
showSnackbar.success(
`获取${label.value}${rawData[key].name}数据成功,共 ${getData.list.length}`,
);
loading.value = false;
}
@@ -139,6 +144,11 @@ async function switchAnno(): Promise<void> {
await router.push("/announcements");
}
function handleList(): void {
if (showSearch.value === true) showSearch.value = false;
showList.value = true;
}
// 加载更多
async function loadMore(key: NewsType): Promise<void> {
if (loading.value) return;
@@ -148,7 +158,7 @@ async function loadMore(key: NewsType): Promise<void> {
loading.value = false;
return;
}
await showLoading.start(`正在获取${gameName}${rawData[key].name}数据`);
await showLoading.start(`正在获取${label.value}${rawData[key].name}数据`);
const mod = rawData[key].lastId % 20;
const pageSize = mod === 0 ? 20 : 20 - mod;
const getData = await painterReq.news(gid, tabMap[key], pageSize, rawData[key].lastId);
@@ -174,6 +184,7 @@ async function searchPost(): Promise<void> {
}
const numCheck = Number(search.value);
if (isNaN(numCheck)) {
if (showList.value === true) showList.value = false;
showSearch.value = true;
return;
}

View File

@@ -69,9 +69,14 @@
@click:append="searchPost"
@keyup.enter="searchPost"
/>
<v-btn :loading="isReq" class="post-topic-btn" @click="firstLoad()" prepend-icon="mdi-refresh"
>刷新</v-btn
<v-btn
:loading="isReq"
class="post-topic-btn"
@click="firstLoad()"
prepend-icon="mdi-refresh"
>
刷新
</v-btn>
</div>
</v-app-bar>
<div class="post-topic-grid">
@@ -96,11 +101,12 @@ import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import VpOverlaySearch from "@comp/viewPost/vp-overlay-search.vue";
import VpOverlayUser from "@comp/viewPost/vp-overlay-user.vue";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import useBBSStore from "@/store/modules/bbs.js";
import { createPost } from "@/utils/TGWindow.js";
import apiHubReq from "@/web/request/apiHubReq.js";
import postReq from "@/web/request/postReq.js";
import topicReq from "@/web/request/topicReq.js";
@@ -121,7 +127,7 @@ const curUid = ref<string>("");
const showUser = ref<boolean>(false);
const isReq = ref<boolean>(false);
const allGames = shallowRef<Array<TGApp.BBS.Game.Item>>([]);
const { gameList } = storeToRefs(useBBSStore());
const postRaw = shallowRef<PostMiniData>({ isLast: false, lastId: "", total: 0 });
const topicInfo = shallowRef<TGApp.BBS.Topic.InfoRes>();
const posts = shallowRef<Array<TGApp.BBS.Post.FullData>>([]);
@@ -150,7 +156,6 @@ onMounted(async () => {
curGid.value = Number(gid);
curTopic.value = topic;
await showLoading.start(`正在加载话题${topic}信息`);
allGames.value = await apiHubReq.game();
const info = await topicReq.info(gid, topic);
if ("retcode" in info) {
await showLoading.end();
@@ -163,7 +168,7 @@ onMounted(async () => {
tmpGame = info.game_info_list.find((i) => i.id === curGid.value);
}
if (tmpGame === undefined) tmpGame = info.game_info_list[0];
const gameFind = allGames.value.find((i) => i.id === tmpGame?.id);
const gameFind = gameList.value.find((i) => i.id === tmpGame?.id);
curGame.value = { ...tmpGame, icon: gameFind?.app_icon };
await firstLoad();
});
@@ -215,6 +220,7 @@ async function freshPostData(): Promise<void> {
if (showUser.value) showUser.value = false;
if (postRaw.value.isLast) {
showSnackbar.warn("已经到底了");
isReq.value = false;
return;
}
await showLoading.start(`正在刷新${topicInfo.value?.topic.name}帖子列表`);
@@ -257,16 +263,17 @@ function searchPost(): void {
} else createPost(search.value);
}
function getGameList(gameList: Array<TGApp.BBS.Topic.GameInfo> | undefined): Array<GameList> {
if (!gameList) return [];
return gameList.map((item) => {
const game = allGames.value.find((i) => i.id === item.id);
function getGameList(list: Array<TGApp.BBS.Topic.GameInfo> | undefined): Array<GameList> {
if (!list) return [];
return list.map((item) => {
const game = gameList.value.find((i) => i.id === item.id);
return { ...item, icon: game?.app_icon };
});
}
function handleUserClick(user: TGApp.BBS.Post.User): void {
function handleUserClick(user: TGApp.BBS.Post.User, gid: number): void {
if (showSearch.value) showSearch.value = false;
curGid.value = gid;
curUid.value = user.uid;
showUser.value = true;
}

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