Compare commits

...

41 Commits

Author SHA1 Message Date
目棃
5358b07157 🚀 v0.6.2 2024-10-31 11:27:08 +08:00
目棃
6a78bf059c 💄 调整卡片UI,维持名片比例 2024-10-31 10:13:24 +08:00
目棃
41e71357ef 💄 队伍出场页面UI调整 2024-10-30 22:49:33 +08:00
目棃
9d08880c10 💄 角色持有适配差距 2024-10-30 22:18:44 +08:00
目棃
a812e0781b 👽️ 修正咨讯Api 2024-10-30 18:07:33 +08:00
目棃
f08668e3da 💄 修复图标渲染异常 2024-10-30 17:40:14 +08:00
目棃
a544e6bb6b 🍱 更新下半卡池 2024-10-29 22:56:14 +08:00
目棃
c006a3f84d ♻️ 角色使用率&出场率 2024-10-29 18:38:11 +08:00
目棃
05cee4d8e9 ♻️ 深渊数据库重构,概览显示差距 2024-10-29 17:05:14 +08:00
目棃
06345bf5db 🐛 修复release打包失败 2024-10-27 13:11:19 +08:00
目棃
1c25942920 💄 修复视频封面位置异常 2024-10-27 12:43:29 +08:00
目棃
c17f9f4f0a ♻️ 调整代码结构 2024-10-27 12:38:45 +08:00
目棃
980b7ffd45 💄 深渊期数上对齐 2024-10-26 20:12:22 +08:00
目棃
35fcee9b3b 💄 修复回顶组件宽度异常[mac] 2024-10-26 20:05:24 +08:00
目棃
6701464002 🐛 修复用户登录状态异常 2024-10-26 18:22:15 +08:00
目棃
5f1d07968c 💄 mac隐藏游戏目录设置&游戏启动badge 2024-10-26 15:39:27 +08:00
目棃
83bfd5d1eb 🎨 调整保存时的hint 2024-10-26 15:08:02 +08:00
目棃
0050ee773e 💄 调整未登录时的部分内容渲染 2024-10-26 11:21:56 +08:00
目棃
7749406c90 ✏️ 修正部分类型 2024-10-25 10:11:56 +08:00
目棃
26041948ef ♻️ 请求重构,合并postWapi跟apiHub相关请求 2024-10-24 23:51:45 +08:00
目棃
41db04b7a2 💄 子回复取消保持,点击其他隐藏 2024-10-24 22:14:35 +08:00
目棃
003344c722 ⬆️ 走ssh更新,统一更新日志路径,更新依赖 2024-10-24 22:10:01 +08:00
目棃
bb9c9d55e6 🚀 v0.6.1 2024-10-22 20:51:36 +08:00
目棃
0d37f4e82b ✏️ 修正类型错误 2024-10-22 20:46:13 +08:00
目棃
e5a917b07b 💄优化select 2024-10-22 20:44:21 +08:00
目棃
4cef204f72 💄调整副标题 2024-10-17 14:16:04 +08:00
目棃
97444190ab 💄深渊分享显示应用信息,圣遗物详情推荐属性高亮 2024-10-17 11:51:41 +08:00
目棃
632b4e88ec 🎨 调整插入账户sql 2024-10-17 11:12:27 +08:00
目棃
c7861cc213 🎨 调整skippedFloor数据获取逻辑 2024-10-17 11:01:12 +08:00
目棃
2a5a408098 💄显示跳过楼层 2024-10-16 19:08:23 +08:00
目棃
1794df2b8a 👽️ 适配深渊新字段 2024-10-16 18:59:05 +08:00
目棃
f8121d504c 🐛 修复数据库初始化异常 2024-10-16 18:41:37 +08:00
目棃
be24bdc7ce 🎨 调整点击逻辑 2024-10-13 12:10:00 +08:00
目棃
18507b6273 🐛 修复封面503 2024-10-12 15:16:06 +08:00
目棃
2e152965df 🐛 修复数据未即时刷新 2024-10-12 10:58:47 +08:00
目棃
d735d0d098 🎨 调整卡片封面加载逻辑 2024-10-11 18:07:03 +08:00
目棃
631fbfc29c 🐛 修复banner为空时的渲染异常 2024-10-11 17:53:20 +08:00
目棃
67aa3b7363 ♻️ 公告卡片组件抽离,支持分享 2024-10-11 17:02:07 +08:00
目棃
454f9e9750 🎨 虚拟列表性能优化 2024-10-11 16:31:53 +08:00
目棃
6b34ae612c 💄 处理特定情况下的内容溢出,调整Sql创建 2024-10-10 09:30:30 +08:00
目棃
3112b2e41a ⬆️ 更新依赖,补充缺失的素材日历数据 2024-10-10 09:22:09 +08:00
109 changed files with 8643 additions and 2276 deletions

View File

@@ -43,11 +43,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 22.8.0
node-version: 23.1.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.10.0
version: 9.12.3
- name: Install frontend dependencies
run: pnpm install

View File

@@ -12,11 +12,11 @@ jobs:
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 22.8.0
node-version: 23.1.0
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.10.0
version: 9.12.3
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: "Qodana Scan"

View File

@@ -2,12 +2,39 @@
Author: 目棃
Description: CHANGELOG
Date: 2024-10-09
Update: 2024-10-09
Update: 2024-10-31
---
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2024-10-09 15:51:43`
>
> 更新于 `2024-10-09 15:59:58`
> 更新于 `2024-10-31 10:36:33`
## [0.6.2](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.2)
- 🐛 修复用户登录状态异常 [`#132`](https://github.com/BTMuli/TeyvatGudie/issues/132)
- 💄 帖子子回复取消保持,点击其他隐藏
- 💄 调整未登录时的部分内容渲染
- 💄 调整保存时图片的hint
- 💄 `mac`:修复回顶组件宽度异常
- 💄 `mac`:修复视频封面位置异常
- 💄 调整角色卡片UI维持名片比例
- ♻️ 深渊数据库重构,概览显示差距
- 🍱 更新下半卡池
- 👽️ 修正咨讯Api
## [0.6.1](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.1) (2024-10-22)
- 🐛 新用户数据库初始化异常 [`#131`](https://github.com/BTMuli/TeyavtGuide/issues/131)
- 🐛 修复角色数据未即时刷新
- 🐛 修复`openSystemBrowser`回调执行异常
- ♻️ 公告卡片组件抽离,支持分享
- 🎨 成就页面&名片图鉴页面采用虚拟列表优化性能
- 🎨 调整卡片封面加载逻辑
- 💄 处理特定情况下的内容溢出
- 💄 适配深渊新字段,显示跳过楼层
- 💄深渊分享显示应用信息,圣遗物详情推荐属性高亮
- 💄调整帖子子窗口副标题样式
- 💄调整留影叙佳期选项样式
## [0.6.0](https://github.com/BTMuli/TeyvatGuide/releases/v0.6.0) (2024-10-09)

View File

@@ -1,7 +1,7 @@
{
"name": "TeyvatGuide",
"version": "0.6.0",
"description": "Game Tool for Genshin Impact player",
"version": "0.6.2",
"description": "Game Tool for GenshinImpact player",
"private": true,
"type": "module",
"scripts": {
@@ -66,18 +66,18 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@tauri-apps/api": "^2.0.0-rc.4",
"@tauri-apps/api": "^2.0.3",
"@tauri-apps/plugin-deep-link": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
"@tauri-apps/plugin-fs": "^2.0.0",
"@tauri-apps/plugin-http": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.1",
"@tauri-apps/plugin-fs": "^2.0.1",
"@tauri-apps/plugin-http": "^2.0.1",
"@tauri-apps/plugin-log": "^2.0.0",
"@tauri-apps/plugin-os": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.1",
"@tauri-apps/plugin-sql": "^2.0.0",
"ajv": "^8.17.1",
"artplayer": "^5.1.7",
"artplayer": "^5.2.0",
"clipboard": "^2.0.11",
"color-convert": "^2.0.1",
"echarts": "^5.5.1",
@@ -85,54 +85,54 @@
"js-md5": "^0.8.3",
"jsencrypt": "^3.3.2",
"pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.0.2",
"pinia-plugin-persistedstate": "^4.1.1",
"uuid": "^10.0.0",
"vue": "^3.5.10",
"vue": "^3.5.12",
"vue-echarts": "^7.0.3",
"vue-json-viewer": "^3.0.4",
"vue-router": "^4.4.5",
"vuetify": "^3.7.2",
"vuetify": "^3.7.3",
"wcag-color": "^1.1.1",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.11.1",
"@tauri-apps/cli": "2.0.0",
"@eslint/js": "^9.13.0",
"@tauri-apps/cli": "2.0.4",
"@types/color-convert": "^2.0.4",
"@types/js-md5": "^0.7.2",
"@types/node": "^22.7.4",
"@types/node": "^22.7.9",
"@types/uuid": "^10.0.0",
"@typescript-eslint/parser": "^8.8.0",
"@typescript-eslint/parser": "^8.11.0",
"@vitejs/plugin-vue": "^5.1.4",
"concurrently": "^9.0.1",
"eslint": "^9.11.1",
"eslint-config-love": "^83.0.0",
"eslint": "^9.13.0",
"eslint-config-love": "^89.0.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-n": "^17.10.3",
"eslint-plugin-n": "^17.11.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-vue": "^9.28.0",
"eslint-plugin-vue": "^9.29.1",
"eslint-plugin-yml": "^1.14.0",
"globals": "^15.10.0",
"globals": "^15.11.0",
"husky": "^9.1.6",
"jsonc-eslint-parser": "^2.4.0",
"lint-staged": "^15.2.10",
"oxlint": "^0.9.9",
"oxlint": "^0.10.2",
"prettier": "3.3.3",
"stylelint": "^16.9.0",
"stylelint": "^16.10.0",
"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.10.0",
"stylelint-order": "^6.0.4",
"stylelint-prettier": "^5.0.2",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
"vite": "^5.4.8",
"vite-plugin-vue-devtools": "^7.4.6",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
"vite-plugin-vue-devtools": "^7.5.3",
"vite-plugin-vuetify": "^2.0.4",
"vue-eslint-parser": "^9.4.3",
"yaml-eslint-parser": "^1.2.3"

1296
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

522
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.6.0"
version = "0.6.2"
description = "Game Tool for Genshin Impact player"
authors = ["BTMuli <bt-muli@outlook.com>"]
license = "MIT"
@@ -15,62 +15,72 @@ tauri-build = { version = "2.0.0", features = [] }
[dependencies]
chrono = "0.4.38"
log = "0.4.22"
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
tauri = { version = "2.0.0", features = [] }
tauri-utils = "2.0.0"
serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0.132"
tauri = { version = "2.0.6", features = [] }
tauri-utils = "2.0.2"
url = "2.5.2"
walkdir = "2.5.0"
# deep link 插件
[dependencies.tauri-plugin-deep-link]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# dialog 插件
[dependencies.tauri-plugin-dialog]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# fs 插件
[dependencies.tauri-plugin-fs]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# http 插件
[dependencies.tauri-plugin-http]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
features = ["unsafe-headers"]
# log 插件
[dependencies.tauri-plugin-log]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# os 插件
[dependencies.tauri-plugin-os]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# process 插件
[dependencies.tauri-plugin-process]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# shell 插件
[dependencies.tauri-plugin-shell]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# single-instance 插件
[dependencies.tauri-plugin-single-instance]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
# sqlite 插件
[dependencies.tauri-plugin-sql]
git = "https://github.com/tauri-apps/plugins-workspace"
#git = "ssh://git@github.com/tauri-apps/plugins-workspace.git"
git = "https://github.com/tauri-apps/plugins-workspace.git"
branch = "v2"
features = ["sqlite"]

File diff suppressed because one or more lines are too long

View File

@@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": ["identifier", "permissions"],
"properties": {

View File

@@ -1,6 +1,6 @@
//! @file src/client/mod.rs
//! @desc 客户端模块,负责操作米游社客户端
//! @since Beta v0.5.2
//! @since Beta v0.6.2
mod menu;
mod utils;
@@ -12,7 +12,7 @@ use tauri_utils::config::WebviewUrl;
pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
let mut win_width = 400.0;
let mut win_height = 800.0;
let win_ua = "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.75.1";
let win_ua = "Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/2.76.1";
let url_parse;
if url != "" {
url_parse = WebviewUrl::External(url.parse().unwrap());
@@ -37,6 +37,7 @@ pub async fn create_mhy_client(handle: AppHandle, func: String, url: String) {
.title("米游社")
.center()
.user_agent(win_ua)
// todo mac环境下没看到menu
.menu(menu::create_mhy_menu(handle.clone()))
.on_menu_event(move |app, event| menu::handle_menu_event(app, event))
.build()

View File

@@ -1,20 +1,24 @@
//! @file src/main.rs
//! @desc 主模块,用于启动应用
//! @since Beta v0.5.2
//! @since Beta v0.6.2
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::{Emitter, Manager};
mod client;
mod commands;
mod plugins;
mod utils;
use crate::client::create_mhy_client;
use crate::commands::{create_window, execute_js, get_dir_size, init_app};
use crate::plugins::{build_log_plugin, build_si_plugin};
use tauri::{generate_context, generate_handler, Builder, Manager, Window, WindowEvent};
// 窗口事件处理
fn window_event_handler(app: &tauri::Window, event: &tauri::WindowEvent) {
fn window_event_handler(app: &Window, event: &WindowEvent) {
match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
WindowEvent::CloseRequested { api, .. } => {
api.prevent_close();
if app.label() == "TeyvatGuide" {
// 子窗口 label 的数组
@@ -33,11 +37,9 @@ fn window_event_handler(app: &tauri::Window, event: &tauri::WindowEvent) {
}
fn main() {
tauri::Builder::default()
Builder::default()
.on_window_event(move |app, event| window_event_handler(app, event))
.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| {
app.emit("active_deep_link", argv).unwrap();
}))
.plugin(build_si_plugin())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
@@ -46,7 +48,7 @@ fn main() {
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_sql::Builder::default().build())
.plugin(plugins::build_log_plugin())
.plugin(build_log_plugin())
.setup(|_app| {
let _window = _app.get_webview_window("TeyvatGuide");
#[cfg(debug_assertions)]
@@ -55,13 +57,13 @@ fn main() {
}
Ok(())
})
.invoke_handler(tauri::generate_handler![
commands::init_app,
commands::create_window,
commands::execute_js,
commands::get_dir_size,
client::create_mhy_client,
.invoke_handler(generate_handler![
init_app,
create_window,
execute_js,
get_dir_size,
create_mhy_client
])
.run(tauri::generate_context!())
.run(generate_context!())
.expect("error while running tauri application");
}

View File

@@ -1,28 +1,33 @@
//! @file src/plugins.rs
//! @desc 插件模块,用于注册插件
//! @since Beta v0.5.3
//! @since Beta v0.6.2
use super::utils;
use crate::utils::get_current_date;
use log::LevelFilter;
use tauri::plugin::TauriPlugin;
use tauri::Runtime;
use tauri_plugin_log::{Target, TargetKind, TimezoneStrategy};
use tauri::{Emitter, Runtime};
use tauri_plugin_log::{Builder, Target, TargetKind, TimezoneStrategy};
use tauri_plugin_single_instance::init;
// 单例插件
pub fn build_si_plugin<R: Runtime>() -> TauriPlugin<R> {
init(move |app, argv, _cwd| app.emit("active_deep_link", argv).unwrap())
}
// 日志插件
pub fn build_log_plugin<R: Runtime>() -> TauriPlugin<R> {
if cfg!(debug_assertions) {
return tauri_plugin_log::Builder::default()
.targets([Target::new(TargetKind::Webview)])
.timezone_strategy(TimezoneStrategy::UseLocal)
.level(LevelFilter::Debug)
.build();
}
tauri_plugin_log::Builder::default()
#[cfg(debug_assertions)]
Builder::default()
.targets([Target::new(TargetKind::Webview)])
.timezone_strategy(TimezoneStrategy::UseLocal)
.level(LevelFilter::Debug)
.build::<R>();
Builder::default()
.targets([
Target::new(TargetKind::Webview),
Target::new(TargetKind::LogDir { file_name: utils::get_current_date().into() }),
Target::new(TargetKind::LogDir { file_name: get_current_date().into() }),
])
.timezone_strategy(TimezoneStrategy::UseLocal)
.level(LevelFilter::Info)
.build()
.build::<R>()
}

View File

@@ -1,7 +1,7 @@
{
"productName": "TeyvatGuide",
"identifier": "TeyvatGuide",
"version": "0.6.0",
"version": "0.6.2",
"build": {
"beforeDevCommand": "pnpm vite:dev",
"beforeBuildCommand": "pnpm vite:build",

View File

@@ -100,6 +100,7 @@ onMounted(async () => {
function listenOnInit(): void {
console.info("[App][listenOnInit] 监听初始化事件!");
event.listen("initApp", async () => {
await checkAppLoad();
await checkDeviceFp();
try {
await checkUserLoad();
@@ -112,6 +113,19 @@ function listenOnInit(): void {
});
}
async function checkAppLoad(): Promise<void> {
let checkDB = false;
try {
checkDB = await TGSqlite.check();
} catch (error) {
if (error instanceof Error) {
await TGLogger.Error(`[App][checkAppLoad] ${error.name}: ${error.message}`);
} else console.error(error);
}
if (!checkDB) await TGSqlite.update();
else await TGLogger.Info("[App][checkAppLoad] 数据库已成功加载!");
}
// 检测 deviceFp
async function checkDeviceFp(): Promise<void> {
const appData = await TGSqlite.getAppData();
@@ -138,10 +152,12 @@ async function checkUserLoad(): Promise<void> {
await mkdir(appStore.userDir, { recursive: true });
// 检测用户数据
const uidDB = await TSUserAccount.account.getAllUid();
if (uidDB.length === 0) {
if (uidDB.length === 0 && appStore.isLogin) {
showSnackbar({ text: "未检测到可用UID请重新登录!", color: "warn" });
appStore.isLogin = false;
return;
}
if (!appStore.isLogin) appStore.isLogin = true;
// 然后获取最近的UID
if (userStore.uid.value === undefined || !uidDB.includes(userStore.uid.value)) {
userStore.uid.value = uidDB[0];
@@ -247,7 +263,7 @@ async function checkUpdate(): Promise<void> {
appStore.buildTime = getBuildTime();
await TGSqlite.update();
showSnackbar({ text: "数据库已更新!", color: "success", timeout: 3000 });
window.open("https://app.btmuli.ink/docs/Changelogs.html");
window.open("https://app.btmuli.ink/docs/TeyvatGuide/changelogs.html");
}
}

View File

@@ -56,6 +56,7 @@ onUnmounted(() => {
position: fixed;
right: 10px;
bottom: 10px;
width: 40px;
height: 40px;
border-radius: 50%;
transition: all 0.3s ease-in-out;

View File

@@ -29,7 +29,11 @@
</div>
</template>
</v-list-item>
<v-list-item title="游戏安装目录" :subtitle="appStore.gameDir.value">
<v-list-item
title="游戏安装目录"
:subtitle="appStore.gameDir.value"
v-if="platform() === 'windows'"
>
<template #prepend>
<div class="config-icon">
<v-icon>mdi-gamepad</v-icon>

View File

@@ -4,12 +4,15 @@
<div class="tgb-title">原神启动</div>
<v-btn size="small" icon="mdi-rocket" variant="outlined" @click="tryPlayGame()" />
</div>
<v-list-item v-if="account">
<v-list-item v-if="account.uid">
<v-list-item-title class="tgb-name">
{{ account.nickname }}({{ account.regionName }})
</v-list-item-title>
<v-list-item-subtitle>{{ account.gameUid }} Lv.{{ account.level }}</v-list-item-subtitle>
</v-list-item>
<v-list-item v-else>
<v-list-item-title>未登录请先登录!</v-list-item-title>
</v-list-item>
</div>
</template>
<script lang="ts" setup>

View File

@@ -52,7 +52,13 @@
:loading="loading"
title="刷新用户信息"
/>
<v-btn variant="outlined" @click="confirmCopyCookie" icon="mdi-cookie" title="复制Cookie" />
<v-btn
variant="outlined"
@click="confirmCopyCookie"
:disabled="!userStore.cookie.value"
icon="mdi-cookie"
title="复制Cookie"
/>
<v-menu location="start">
<template v-slot:activator="{ props }">
<v-btn
@@ -102,6 +108,7 @@ import { computed, ref } from "vue";
import Mys from "../../plugins/Mys/index.js";
import TSUserAccount from "../../plugins/Sqlite/modules/userAccount.js";
import { useAppStore } from "../../store/modules/app.js";
import { useUserStore } from "../../store/modules/user.js";
import TGLogger from "../../utils/TGLogger.js";
import TGRequest from "../../web/request/TGRequest.js";
@@ -115,6 +122,7 @@ interface TcUserBadgeEmits {
const emits = defineEmits<TcUserBadgeEmits>();
const userStore = storeToRefs(useUserStore());
const appStore = storeToRefs(useAppStore());
const loading = ref<boolean>(false);
const accounts = ref<TGApp.App.Account.User[]>([]);
@@ -213,6 +221,7 @@ async function tryCaptchaLogin(): Promise<void> {
userStore.uid.value = briefInfo.uid;
userStore.briefInfo.value = briefInfo;
userStore.cookie.value = ck;
appStore.isLogin.value = true;
emits("loadOuter", { show: true, title: "正在获取游戏账号" });
const gameRes = await TGRequest.User.bySToken.getAccounts(ck.stoken, ck.stuid);
if (!Array.isArray(gameRes)) {

View File

@@ -38,7 +38,6 @@ let confirmInstance: VNode;
/**
* @function showConfirm
* @since Beta v0.3.9
* @todo 重载重构
* @description 弹出 confirm
* @param {TGApp.Component.Confirm.Params} props confirm 的参数
* @return {Promise<string | boolean | undefined>} 点击确认返回 true点击取消返回 false点击外部返回 undefined

View File

@@ -35,7 +35,6 @@ const renderBox = (props: TGApp.Component.Snackbar.Params): VNode => {
let snackbarInstance: VNode;
// todo 参数重构
function showSnackbar(props: TGApp.Component.Snackbar.Params): void {
if (snackbarInstance !== undefined) {
const boxVue = <SnackbarInstance>snackbarInstance.component;

View File

@@ -4,22 +4,22 @@
</div>
</template>
<script lang="ts" setup>
import type { EChartsOption } from "echarts";
import { PieChart } from "echarts/charts";
// about import err,see:https://github.com/apache/echarts/issues/19992
import { PieChart } from "echarts/charts.js";
import {
LegendComponent,
TitleComponent,
TooltipComponent,
ToolboxComponent,
} from "echarts/components";
import { use } from "echarts/core";
import { LabelLayout } from "echarts/features";
import { CanvasRenderer } from "echarts/renderers";
} from "echarts/components.js";
import { use } from "echarts/core.js";
import { LabelLayout } from "echarts/features.js";
import { CanvasRenderer } from "echarts/renderers.js";
import type { EChartsOption } from "echarts/types/dist/shared.js";
import { provide } from "vue";
import VChart, { THEME_KEY } from "vue-echarts";
// echarts
use([
TitleComponent,
TooltipComponent,
@@ -67,9 +67,7 @@ const defaultOptions = <EChartsOption>{
bottom: "10%",
},
],
tooltip: {
trigger: "item",
},
tooltip: { trigger: "item" },
legend: {
type: "scroll",
orient: "vertical",
@@ -136,36 +134,50 @@ const defaultOptions = <EChartsOption>{
};
// 获取卡池分布的数据
// todo 重构以完善类型
function getPoolData(): EChartsOption {
const data = JSON.parse(JSON.stringify(defaultOptions));
data.title[0].subtext = `${props.modelValue.length} 条数据`;
data.series[0].data = [
{ value: props.modelValue.filter((item) => item.uigfType === "100").length, name: "新手祈愿" },
{ value: props.modelValue.filter((item) => item.uigfType === "200").length, name: "常驻祈愿" },
{
value: props.modelValue.filter((item) => item.uigfType === "301").length,
name: "角色活动祈愿",
},
{
value: props.modelValue.filter((item) => item.uigfType === "302").length,
name: "武器活动祈愿",
},
{
value: props.modelValue.filter((item) => item.uigfType === "500").length,
name: "集录祈愿",
},
];
data.series[1].data = [
{ value: props.modelValue.filter((item) => item.rank === "3").length, name: "3星" },
{ value: props.modelValue.filter((item) => item.rank === "4").length, name: "4星" },
{ value: props.modelValue.filter((item) => item.rank === "5").length, name: "5星" },
];
const data: EChartsOption = JSON.parse(JSON.stringify(defaultOptions));
if (data.title !== undefined && Array.isArray(data.title)) {
data.title[0].subtext = `${props.modelValue.length} 条数据`;
}
if (data.series !== undefined && Array.isArray(data.series)) {
data.series[0].data = [
{
value: props.modelValue.filter((item) => item.uigfType === "100").length,
name: "新手祈愿",
},
{
value: props.modelValue.filter((item) => item.uigfType === "200").length,
name: "常驻祈愿",
},
{
value: props.modelValue.filter((item) => item.uigfType === "301").length,
name: "角色活动祈愿",
},
{
value: props.modelValue.filter((item) => item.uigfType === "302").length,
name: "武器活动祈愿",
},
{
value: props.modelValue.filter((item) => item.uigfType === "500").length,
name: "集录祈愿",
},
];
data.series[1].data = [
{ value: props.modelValue.filter((item) => item.rank === "3").length, name: "3星" },
{ value: props.modelValue.filter((item) => item.rank === "4").length, name: "4星" },
{ value: props.modelValue.filter((item) => item.rank === "5").length, name: "5星" },
];
}
const tempSet = new Set<string>();
const tempRecord = new Map<string, number>();
// 角色池分析
let tempList = props.modelValue.filter((item) => item.uigfType === "301");
let star3 = tempList.filter((item) => item.rank === "3").length;
data.title[3].subtext = `${tempList.length} 条数据, 其中三星武器 ${star3}`;
if (data.title !== undefined && Array.isArray(data.title)) {
data.title[3].subtext = `${tempList.length} 条数据, 其中三星武器 ${star3}`;
}
tempList
.filter((item) => item.rank !== "3")
.forEach((item) => {
@@ -176,18 +188,22 @@ function getPoolData(): EChartsOption {
tempRecord.set(item.name, 1);
}
});
data.series[2].data = Array.from(tempRecord).map((item) => {
return {
value: item[1],
name: item[0],
};
});
if (data.series !== undefined && Array.isArray(data.series)) {
data.series[2].data = Array.from(tempRecord).map((item) => {
return {
value: item[1],
name: item[0],
};
});
}
tempSet.clear();
tempRecord.clear();
// 武器池分析
tempList = props.modelValue.filter((item) => item.uigfType === "302");
star3 = tempList.filter((item) => item.rank === "3").length;
data.title[4].subtext = `${tempList.length} 条数据,其中三星武器 ${star3}`;
if (data.title !== undefined && Array.isArray(data.title)) {
data.title[4].subtext = `${tempList.length} 条数据,其中三星武器 ${star3}`;
}
tempList
.filter((item) => item.rank !== "3")
.forEach((item) => {
@@ -198,12 +214,14 @@ function getPoolData(): EChartsOption {
tempRecord.set(item.name, 1);
}
});
data.series[3].data = Array.from(tempRecord).map((item) => {
return {
value: item[1],
name: item[0],
};
});
if (data.series !== undefined && Array.isArray(data.series)) {
data.series[3].data = Array.from(tempRecord).map((item) => {
return {
value: item[1],
name: item[0],
};
});
}
return data;
}
</script>

View File

@@ -86,7 +86,7 @@ interface TPoolEmits {
const emits = defineEmits<TPoolEmits>();
function poolLastInterval(postId: number): TGApp.Plugins.Mys.Gacha.RenderCard {
function poolLastInterval(postId: number): TGApp.Plugins.Mys.Gacha.RenderCard | undefined {
const pool = poolCards.value.find((pool) => pool.postId === postId);
if (!pool) return;
if (poolTimeGet.value[postId] === "未开始") {
@@ -162,7 +162,7 @@ function checkCover(data: TGApp.Plugins.Mys.Gacha.Data[]): boolean {
return false;
}
const cover = homeStore.poolCover;
if (cover === undefined) return false;
if (cover.value === undefined) return false;
let checkList = data.length;
Object.entries(cover).forEach(([key, value]: [string, unknown]) => {
const pool = data.find((item: TGApp.Plugins.Mys.Gacha.Data) => item.id.toString() === key);

View File

@@ -1,41 +1,70 @@
<template>
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="20px">
<div class="hta-oo-box">
<v-btn
:loading="loadShare"
class="hta-oob-share"
@click="share()"
data-html2canvas-ignore
variant="flat"
icon="mdi-share-variant"
size="24px"
/>
<div class="hta-oob-title">数据收集统计</div>
<div class="hta-oob-item">
<span>当期深渊ID</span>
<span>{{ props.data.ScheduleId }}</span>
<span>上传记录总数</span>
<span>{{ props.data.RecordTotal }}</span>
</div>
<HtaOverviewLine
label="当期深渊ID"
:cur="dataCur.ScheduleId"
:last="dataLast.ScheduleId"
:show-diff="false"
/>
<HtaOverviewLine
label="上传记录总数"
:cur="dataCur.RecordTotal"
:last="dataLast.RecordTotal"
/>
<div class="hta-oob-title">深渊数据统计</div>
<div class="hta-oob-item">
<span>总计深渊记录</span>
<span>{{ props.data.SpiralAbyssTotal }}</span>
<span>通关深渊记录</span>
<span>{{ props.data.SpiralAbyssPassed }}</span>
<span>满星深渊记录</span>
<span>{{ props.data.SpiralAbyssFullStar }}</span>
<span>平均获取渊星</span>
<span>{{
(props.data.SpiralAbyssStarTotal / props.data.SpiralAbyssTotal).toFixed(2)
}}</span>
<span>平均战斗次数</span>
<span>{{
(props.data.SpiralAbyssBattleTotal / props.data.SpiralAbyssTotal).toFixed(2)
}}</span>
</div>
<HtaOverviewLine
label="总计深渊记录"
:cur="dataCur.SpiralAbyssTotal"
:last="dataLast.SpiralAbyssTotal"
/>
<HtaOverviewLine
label="通关深渊记录"
:cur="dataCur.SpiralAbyssPassed"
:last="dataLast.SpiralAbyssPassed"
/>
<HtaOverviewLine
label="满星深渊记录"
:cur="dataCur.SpiralAbyssFullStar"
:last="dataLast.SpiralAbyssFullStar"
/>
<HtaOverviewLine
label="平均获取渊星"
:cur="dataCur.SpiralAbyssStarTotal / dataCur.SpiralAbyssTotal"
:last="dataLast.SpiralAbyssStarTotal / dataLast.SpiralAbyssTotal"
/>
<HtaOverviewLine
label="平均战斗次数"
:cur="dataCur.SpiralAbyssBattleTotal / dataCur.SpiralAbyssTotal"
:last="dataLast.SpiralAbyssBattleTotal / dataLast.SpiralAbyssTotal"
/>
<div class="hta-oob-extra">更新于 {{ timestampToDate(props.data.cur.Timestamp) }}</div>
</div>
</TOverlay>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { computed, ref } from "vue";
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
import { generateShareImg } from "../../utils/TGShare.js";
import { timestampToDate } from "../../utils/toolFunc.js";
import TOverlay from "../main/t-overlay.vue";
import HtaOverviewLine from "./hta-overview-line.vue";
interface HtaOverlayOverviewProps {
modelValue: boolean;
data: TGApp.Plugins.Hutao.Abyss.OverviewData;
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.OverviewData>;
}
interface HtaOverlayOverviewEmits {
@@ -46,6 +75,9 @@ interface HtaOverlayOverviewEmits {
const props = defineProps<HtaOverlayOverviewProps>();
const emits = defineEmits<HtaOverlayOverviewEmits>();
const dataCur = computed(() => props.data.cur);
const dataLast = computed(() => props.data.last);
const loadShare = ref<boolean>(false);
const visible = computed({
get: () => props.modelValue,
@@ -58,42 +90,52 @@ function onCancel(): void {
visible.value = false;
emits("cancel");
}
async function share(): Promise<void> {
loadShare.value = true;
const shareEl = <HTMLElement>document.querySelector(".hta-oo-box");
const fileName = `深渊数据统计_${timestampToDate(dataCur.value.Timestamp)}.png`;
await generateShareImg(fileName, shareEl, 2);
loadShare.value = false;
}
</script>
<style lang="css" scoped>
.hta-oo-box {
position: relative;
display: flex;
width: 300px;
padding: 10px;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
background: var(--box-bg-1);
row-gap: 8px;
}
.hta-oo-box:nth-child(3) {
margin-top: 10px;
.hta-oob-share {
position: absolute;
top: 8px;
right: 8px;
}
.hta-oob-title {
width: 100%;
border-bottom: 1px solid var(--common-shadow-4);
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
.hta-oob-item {
display: grid;
.hta-oob-extra {
position: absolute;
z-index: -1;
right: 4px;
bottom: 4px;
display: flex;
align-items: center;
justify-content: space-between;
grid-gap: 5px;
grid-template-columns: repeat(2, 1fr);
}
.hta-oob-item :nth-child(2n-1) {
color: var(--box-text-2);
text-align: left;
}
.hta-oob-item :nth-child(2n) {
color: var(--box-text-3);
text-align: right;
justify-content: flex-end;
font-size: 12px;
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="hta-ol-container">
<div class="hta-ol-title">{{ props.label }}</div>
<div class="hta-ol-val">
<div class="hta-olv-cur">{{ getNumStr(props.cur) }}</div>
<div
v-if="props.showDiff"
class="hta-olv-diff"
:title="`上期数据:${getNumStr(props.last)}`"
:class="{ 'hta-olv-up': isUp, 'hta-olv-down': !isUp }"
>
{{ getDiff(props.cur, props.last) }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
interface HtaOverviewLineProps {
label: string;
cur: number;
last: number;
showDiff?: boolean;
}
const props = withDefaults(defineProps<HtaOverviewLineProps>(), {
showDiff: true,
});
const isUp = props.cur - props.last > 0;
function getNumStr(num: number): string {
if (Number.isInteger(num)) return num.toString();
return num.toFixed(2);
}
function getDiff(cur: number, last: number): string {
if (cur === last) return "-";
if (cur > last) return `${getNumStr(cur - last)}`;
return `${getNumStr(last - cur)}`;
}
</script>
<style lang="css" scoped>
.hta-ol-container {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
}
.hta-ol-title {
color: var(--tgc-od-white);
}
.hta-ol-val {
display: flex;
align-items: center;
justify-content: center;
}
.hta-olv-diff {
font-size: 12px;
}
.hta-olv-cur {
color: var(--tgc-od-blue);
}
.hta-olv-up {
color: var(--tgc-od-red);
}
.hta-olv-down {
color: var(--tgc-od-green);
}
</style>

View File

@@ -1,50 +1,108 @@
<template>
<div class="hta-th-box">
<v-data-table :headers="headers" :items="props.modelValue">
<template v-slot:item="{ item }">
<tr class="hta-th-tr">
<td class="hta-th-icon">
<TibWikiAbyss2 v-model="item.AvatarId" />
</td>
<td>{{ (item.HoldingRate * 100).toFixed(3) }}%</td>
<td v-for="rate in item.Constellations" :key="rate.Item">
{{ (rate.Rate * 100).toFixed(3) }}%
</td>
</tr>
</template>
</v-data-table>
</div>
<v-data-table :headers="headers" fixed-header :items="holdData" height="calc(100vh - 160px)">
<template v-slot:item="{ item }">
<tr class="hta-th-tr">
<td class="hta-th-icon">
<TibWikiAbyss2 v-model="item.AvatarId" />
</td>
<td>
<span>{{ (item.HoldingRate.cur * 100).toFixed(3) }}%</span>
<span
v-if="item.HoldingRate.cur !== item.HoldingRate.last"
:class="getRateClass(item.HoldingRate.cur, item.HoldingRate.last)"
>
{{ getRateStr(item.HoldingRate.cur, item.HoldingRate.last) }}
</span>
</td>
<td v-for="rate in item.Constellations" :key="rate.Item">
<span>{{ (rate.RateCur * 100).toFixed(3) }}%</span>
<span
v-if="rate.RateCur !== rate.RateLast"
:class="getRateClass(rate.RateCur, rate.RateLast)"
:title="`${(rate.RateLast * 100).toFixed(3)}%`"
>
{{ getRateStr(rate.RateCur, rate.RateLast) }}
</span>
</td>
</tr>
</template>
</v-data-table>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
import TibWikiAbyss2 from "../itembox/tib-wiki-abyss-2.vue";
interface HtaTabHoldProps {
modelValue: TGApp.Plugins.Hutao.Abyss.AvatarHold[];
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarHold[]>;
}
interface HtaTabHoldConstellation {
Item: number;
RateCur: number;
RateLast: number;
}
interface HtaTabHoldData {
AvatarId: number;
HoldingRate: AbyssDataItem<number>;
Constellations: Array<HtaTabHoldConstellation>;
}
const props = defineProps<HtaTabHoldProps>();
const holdData = ref<HtaTabHoldData[]>([]);
const headers = [
{ title: "角色", align: "center", key: "AvatarId" },
{ title: "持有", align: "center", key: "HoldingRate" },
{ title: "0命", align: "center", key: "Constellations[0].Rate" },
{ title: "1命", align: "center", key: "Constellations[1].Rate" },
{ title: "2命", align: "center", key: "Constellations[2].Rate" },
{ title: "3命", align: "center", key: "Constellations[3].Rate" },
{ title: "4命", align: "center", key: "Constellations[4].Rate" },
{ title: "5命", align: "center", key: "Constellations[5].Rate" },
{ title: "6命", align: "center", key: "Constellations[6].Rate" },
{ title: "持有", align: "center", key: "HoldingRate.cur" },
{ title: "0命", align: "center", key: "Constellations[0].RateCur" },
{ title: "1命", align: "center", key: "Constellations[1].RateCur" },
{ title: "2命", align: "center", key: "Constellations[2].RateCur" },
{ title: "3命", align: "center", key: "Constellations[3].RateCur" },
{ title: "4命", align: "center", key: "Constellations[4].RateCur" },
{ title: "5命", align: "center", key: "Constellations[5].RateCur" },
{ title: "6命", align: "center", key: "Constellations[6].RateCur" },
];
</script>
<style lang="css" scoped>
.hta-th-box {
height: 100%;
max-height: calc(100vh - 120px);
padding-right: 5px;
border-radius: 5px;
overflow-y: auto;
onMounted(() => {
for (const avatar of props.data.cur) {
const avatarLast = props.data.last.find((a) => a.AvatarId === avatar.AvatarId);
if (!avatarLast) continue;
const Rate: AbyssDataItem<number> = {
cur: avatar.HoldingRate,
last: avatarLast?.HoldingRate ?? 0,
};
const Constellations: Array<HtaTabHoldConstellation> = [];
for (const constellation of avatar.Constellations) {
const constellationLast = avatarLast?.Constellations.find(
(c) => c.Item === constellation.Item,
);
if (!constellationLast) continue;
Constellations.push({
Item: constellation.Item,
RateCur: constellation.Rate,
RateLast: constellationLast.Rate,
});
}
holdData.value.push({
AvatarId: avatar.AvatarId,
HoldingRate: Rate,
Constellations: Constellations,
});
}
});
function getRateClass(cur: number, last: number): string {
return cur > last ? "rate-up" : "rate-down";
}
function getRateStr(cur: number, last: number): string {
const diff = Math.abs(cur - last) * 100;
return `(${cur > last ? "↑" : "↓"}${diff.toFixed(3)}%)`;
}
</script>
<style lang="css" scoped>
.hta-th-tr {
height: 100px;
text-align: center;
@@ -53,4 +111,12 @@ const headers = [
.hta-th-icon {
width: 100px;
}
.rate-up {
color: var(--tgc-od-red);
}
.rate-down {
color: var(--tgc-od-green);
}
</style>

View File

@@ -15,29 +15,19 @@
<div v-if="select" class="hta-tt-flex">
<div class="hta-tuf-box">
<div class="hta-tuf-title">上半</div>
<div v-for="items in selectItem.Up" :key="items.Rate" class="hta-tuf-item">
<div class="hta-tuf-item-icons">
<TibWikiAbyss2
v-for="item in items.Item.split(',')"
:key="item"
:model-value="item"
/>
</div>
<div class="hta-tuf-item-rate">上场{{ items.Rate }}</div>
</div>
<v-virtual-scroll :items="selectItem.Up" item-height="100" class="hta-tuf-item">
<template #default="{ item }">
<HtaTeamLine :model-value="item" />
</template>
</v-virtual-scroll>
</div>
<div class="hta-tuf-box">
<div class="hta-tuf-title">下半</div>
<div v-for="items in selectItem.Down" :key="items.Rate" class="hta-tuf-item">
<div class="hta-tuf-item-icons">
<TibWikiAbyss2
v-for="item in items.Item.split(',')"
:key="item"
:model-value="item"
/>
</div>
<div class="hta-tuf-item-rate">上场{{ items.Rate }}</div>
</div>
<v-virtual-scroll :items="selectItem.Down" item-height="100" class="hta-tuf-item">
<template #default="{ item }">
<HtaTeamLine :model-value="item" />
</template>
</v-virtual-scroll>
</div>
</div>
</v-window-item>
@@ -47,7 +37,7 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import TibWikiAbyss2 from "../itembox/tib-wiki-abyss-2.vue";
import HtaTeamLine from "./hta-team-line.vue";
interface HtaTabTeamProps {
modelValue: TGApp.Plugins.Hutao.Abyss.TeamCombination[];
@@ -117,29 +107,10 @@ onMounted(async () => {
}
.hta-tuf-item {
display: flex;
position: relative;
width: 100%;
height: 100px;
align-items: center;
justify-content: flex-start;
padding: 5px;
max-height: calc(100vh - 200px);
border-radius: 5px;
background: var(--box-bg-1);
column-gap: 10px;
}
.hta-tuf-item-icons {
display: grid;
column-gap: 10px;
grid-template-columns: repeat(4, 1fr);
}
.hta-tuf-item-rate {
display: flex;
width: calc(100% - 360px);
height: 100%;
align-items: center;
justify-content: center;
font-family: var(--font-title);
}
</style>

View File

@@ -13,7 +13,11 @@
:value="selectItem.Floor.toString()"
>
<div class="hta-tu-grid">
<TibWikiAbyss v-for="item in selectItem.Ranks" :key="item.Item" :model-value="item" />
<TibWikiAbyss
v-for="(item, index) in selectItem.Ranks"
:key="index"
:model-value="item"
/>
</div>
</v-window-item>
</v-window>
@@ -22,30 +26,47 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
import TibWikiAbyss from "../itembox/tib-wiki-abyss.vue";
interface HtaTabUseProps {
modelValue: TGApp.Plugins.Hutao.Abyss.AvatarUp[];
interface HtaTabUpProps {
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>;
}
const props = defineProps<HtaTabUseProps>();
interface HtaTabUpData {
Floor: number;
Ranks: Array<AbyssDataItem<{ Item: number; Rate: number }>>;
}
const props = defineProps<HtaTabUpProps>();
// data
const tab = ref<string>("9");
const select = ref<TGApp.Plugins.Hutao.Abyss.AvatarUp[]>([]);
const select = ref<Array<HtaTabUpData>>([]);
onMounted(async () => {
props.modelValue.forEach((item) => {
item.Ranks.sort((a, b) => b.Rate - a.Rate);
select.value.push(item);
});
for (const floor of props.data.cur) {
const floorLast = props.data.last.find((f) => f.Floor === floor.Floor);
const floorRank = {
Floor: floor.Floor,
Ranks: <Array<AbyssDataItem<{ Item: number; Rate: number }>>>[],
};
floor.Ranks.sort((a, b) => b.Rate - a.Rate);
for (const rank of floor.Ranks) {
const rankLast = floorLast?.Ranks.find((r) => r.Item === rank.Item);
floorRank.Ranks.push({
cur: rank,
last: rankLast ?? { Item: rank.Item, Rate: 0 },
});
}
select.value.push(floorRank);
}
});
</script>
<style lang="css" scoped>
.hta-tu-box {
display: flex;
height: 100%;
padding-top: 10px;
column-gap: 10px;
}
@@ -63,11 +84,11 @@ onMounted(async () => {
display: grid;
overflow: auto;
width: 100%;
height: 100%;
max-height: calc(100vh - 100px);
align-items: center;
justify-content: center;
padding: 5px;
grid-gap: 5px;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
padding: 10px;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(180px, 0.2fr));
}
</style>

View File

@@ -13,7 +13,11 @@
:value="selectItem.Floor.toString()"
>
<div class="hta-tus-grid">
<TibWikiAbyss v-for="item in selectItem.Ranks" :key="item.Item" :model-value="item" />
<TibWikiAbyss
v-for="(item, index) in selectItem.Ranks"
:key="index"
:model-value="item"
/>
</div>
</v-window-item>
</v-window>
@@ -22,35 +26,51 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
import TibWikiAbyss from "../itembox/tib-wiki-abyss.vue";
interface HtaTabUseProps {
modelValue: TGApp.Plugins.Hutao.Abyss.AvatarUse[];
data: AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>;
}
interface HtaTabUseData {
Floor: number;
Ranks: Array<AbyssDataItem<{ Item: number; Rate: number }>>;
}
const props = defineProps<HtaTabUseProps>();
// data
const tab = ref<string>("9");
const select = ref<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>([]);
const select = ref<Array<HtaTabUseData>>([]);
onMounted(async () => {
props.modelValue.forEach((item) => {
item.Ranks.sort((a, b) => b.Rate - a.Rate);
select.value.push(item);
});
for (const floor of props.data.cur) {
const floorLast = props.data.last.find((f) => f.Floor === floor.Floor);
const floorRank = {
Floor: floor.Floor,
Ranks: <Array<AbyssDataItem<{ Item: number; Rate: number }>>>[],
};
floor.Ranks.sort((a, b) => b.Rate - a.Rate);
for (const rank of floor.Ranks) {
const rankLast = floorLast?.Ranks.find((r) => r.Item === rank.Item);
floorRank.Ranks.push({
cur: rank,
last: rankLast ?? { Item: rank.Item, Rate: 0 },
});
}
select.value.push(floorRank);
}
});
</script>
<style lang="css" scoped>
.hta-tus-box {
display: flex;
height: 100%;
padding-top: 10px;
column-gap: 10px;
}
.hta-tus-tab {
width: 100px;
height: 100%;
color: var(--box-text-4);
}
@@ -64,11 +84,11 @@ onMounted(async () => {
display: grid;
overflow: auto;
width: 100%;
height: 100%;
max-height: calc(100vh - 100px);
align-items: center;
justify-content: center;
padding: 5px;
grid-gap: 5px;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
padding: 10px;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(180px, 0.2fr));
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<div class="hta-tl-box">
<div class="hta-tl-item">
<TibWikiAbyss2
v-for="item in props.modelValue.Item.split(',')"
:key="item"
:model-value="item"
/>
</div>
<div class="hta-tl-rate">上场{{ props.modelValue.Rate }}</div>
</div>
</template>
<script lang="ts" setup>
import TibWikiAbyss2 from "../itembox/tib-wiki-abyss-2.vue";
interface HtaTeamLineProps {
modelValue: { Item: string; Rate: number };
}
const props = defineProps<HtaTeamLineProps>();
</script>
<style lang="css" scoped>
.hta-tl-box {
position: relative;
display: flex;
width: calc(100% - 20px);
align-items: center;
justify-content: space-between;
padding: 10px;
border-radius: 5px;
margin: 10px;
background: var(--box-bg-2);
}
.hta-tl-item {
display: grid;
column-gap: 10px;
grid-template-columns: repeat(4, 1fr);
}
.hta-tl-rate {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
font-family: var(--font-title);
}
</style>

View File

@@ -19,7 +19,7 @@ const box = computed<TItemBoxData>(() => {
return {
bg: `/icon/bg/${avatar.value?.star ?? 5}-Star.webp`,
clickable: false,
display: "outer",
display: "inner",
height: "80px",
icon: `/WIKI/character/${props.modelValue}.webp`,
innerHeight: 20,
@@ -32,6 +32,8 @@ const box = computed<TItemBoxData>(() => {
: `/icon/weapon/${avatar.value.weapon}.webp`,
ltSize: "20px",
size: "80px",
innerIcon: `/icon/weapon/${avatar.value?.weapon}.webp`,
innerBlur: "5px",
};
});

View File

@@ -1,46 +1,95 @@
<template>
<TItemBox :model-value="box" />
<div class="twa-container">
<TItemBox :model-value="box" />
<div class="twa-diff">
<span>{{ avatar?.name ?? "旅行者" }}</span>
<span>{{ `${(props.modelValue.cur.Rate * 100).toFixed(3)}%` }}</span>
<span :class="diffUp ? 'up' : 'down'">{{ getDiffStr() }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { AppCharacterData } from "../../data/index.js";
import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
import TItemBox, { type TItemBoxData } from "../main/t-itembox.vue";
interface TibWikiAbyssProps {
modelValue: {
Item: number;
Rate: number;
};
modelValue: AbyssDataItem<{ Item: number; Rate: number }>;
}
const props = defineProps<TibWikiAbyssProps>();
const avatar = ref<TGApp.App.Character.WikiBriefInfo>();
const diffUp = computed(() => props.modelValue.cur.Rate > props.modelValue.last.Rate);
const box = computed<TItemBoxData>(() => {
return {
bg: `/icon/bg/${avatar.value?.star}-Star.webp`,
clickable: false,
display: "outer",
height: "100px",
display: "inner",
icon: `/WIKI/character/${avatar.value?.id}.webp`,
innerHeight: 20,
innerText: (props.modelValue.Rate * 100).toFixed(3) + "%",
innerHeight: 0,
innerText: avatar.value?.name ?? "旅行者",
lt:
avatar.value === undefined
? ""
: avatar.value.element !== ""
? `/icon/element/${avatar.value.element}元素.webp`
: `/icon/weapon/${avatar.value.weapon}.webp`,
ltSize: "20px",
outerHeight: 20,
outerText: avatar.value?.name ?? "旅行者",
size: "80px",
ltSize: "15px",
size: "60px",
height: "60px",
};
});
onMounted(async () => {
avatar.value = AppCharacterData.find((a) => a.id === props.modelValue.Item);
avatar.value = AppCharacterData.find((a) => a.id === props.modelValue.cur.Item);
});
function getDiffStr() {
if (props.modelValue.cur.Rate === props.modelValue.last.Rate) return "";
if (props.modelValue.last.Rate > props.modelValue.cur.Rate) {
return `${((props.modelValue.last.Rate - props.modelValue.cur.Rate) * 100).toFixed(3)}%`;
}
return `${((props.modelValue.cur.Rate - props.modelValue.last.Rate) * 100).toFixed(3)}%`;
}
</script>
<style lang="css" scoped>
.twa-container {
display: flex;
height: 60px;
align-items: center;
justify-content: flex-start;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
background: var(--box-bg-1);
column-gap: 5px;
}
.twa-diff {
display: flex;
height: 100%;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
color: var(--box-text-4);
font-size: 12px;
:first-child {
font-family: var(--font-title);
font-size: 15px;
}
}
.twa-diff .up {
color: var(--tgc-od-red);
font-family: var(--font-title);
}
.twa-diff .down {
color: var(--tgc-od-green);
font-family: var(--font-title);
}
</style>

View File

@@ -0,0 +1,203 @@
<template>
<div :id="`anno_card_${props.modelValue.id}`" class="anno-card">
<div class="anno-cover" :title="props.modelValue.title" @click="createAnno">
<img :src="localBanner" alt="cover" v-if="localBanner" />
<v-progress-circular
color="primary"
:indeterminate="true"
v-else-if="props.modelValue.banner !== ''"
/>
<img src="/source/UI/defaultCover.webp" alt="cover" v-else />
<div class="anno-info">
<div class="anno-time">
<v-icon>mdi-clock-time-four-outline</v-icon>
<span>{{ props.modelValue.timeStr }}</span>
</div>
</div>
</div>
<div class="anno-title" :title="props.modelValue.title" @click="shareAnno">
{{ parseTitle(props.modelValue.subtitle) }}
</div>
<div class="anno-label" :title="`标签:${props.modelValue.tagLabel}`">
<img :src="localTag" alt="tag" v-if="localTag" />
<v-icon v-else>mdi-tag</v-icon>
<span>{{ props.modelValue.tagLabel }}</span>
</div>
<div class="anno-id">{{ props.modelValue.id }}</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from "vue";
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
import { createTGWindow } from "../../utils/TGWindow.js";
interface TAnnoCardProps {
region: string;
modelValue: TGApp.App.Announcement.ListCard;
lang: string;
}
const props = defineProps<TAnnoCardProps>();
const localBanner = ref<string>();
const localTag = ref<string>();
onMounted(async () => {
if (props.modelValue.banner !== "") {
localBanner.value = await saveImgLocal(props.modelValue.banner);
}
localTag.value = await saveImgLocal(props.modelValue.tagIcon);
});
watch(
() => props.modelValue,
async () => {
if (localBanner.value && localBanner.value.startsWith("blob:")) {
URL.revokeObjectURL(localBanner.value);
localBanner.value = undefined;
}
if (props.modelValue.banner !== "") {
localBanner.value = await saveImgLocal(props.modelValue.banner);
}
if (localTag.value && localTag.value.startsWith("blob:")) {
URL.revokeObjectURL(localTag.value);
localTag.value = undefined;
}
localTag.value = await saveImgLocal(props.modelValue.tagIcon);
},
);
onUnmounted(() => {
if (localBanner.value) URL.revokeObjectURL(localBanner.value);
if (localTag.value) URL.revokeObjectURL(localTag.value);
});
function parseTitle(title: string): string {
const dom = new DOMParser().parseFromString(title, "text/html");
return dom.body.innerText;
}
async function createAnno(): Promise<void> {
const annoPath = `/anno_detail/${props.region}/${props.modelValue.id}/${props.lang}`;
const annoTitle = `Anno_${props.modelValue.id} ${props.modelValue.title}`;
await TGLogger.Info(`[Announcements][createAnno][${props.modelValue.id}] 打开公告窗口`);
await createTGWindow(annoPath, "Sub_window", annoTitle, 960, 720, false, false);
}
async function shareAnno(): Promise<void> {
const fileName = `AnnoCard_${props.modelValue.id}_${props.modelValue.subtitle}`;
const element = <HTMLElement>document.querySelector(`#anno_card_${props.modelValue.id}`);
await generateShareImg(fileName, element, 2.5);
}
</script>
<style lang="css" scoped>
.anno-card {
position: relative;
overflow: hidden;
width: 100%;
border: 1px solid var(--common-shadow-1);
border-radius: 5px;
box-shadow: 2px 2px 5px var(--common-shadow-2);
}
.anno-cover {
position: relative;
display: flex;
overflow: hidden;
width: 100%;
align-items: center;
justify-content: center;
aspect-ratio: 36 / 13;
cursor: pointer;
}
.anno-cover img {
width: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
}
.anno-title {
position: relative;
overflow: hidden;
width: 100%;
padding: 5px;
cursor: pointer;
font-size: 18px;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
.anno-info {
position: absolute;
bottom: 0;
left: 0;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: rgb(0 0 0/50%);
font-size: 12px;
}
.anno-time {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 5px;
color: var(--tgc-white-1);
gap: 5px;
}
.anno-label {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: var(--common-shadow-2);
border-bottom-left-radius: 5px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
text-shadow: 0 0 5px var(--tgc-dark-1);
}
.anno-label img {
width: 20px;
height: 20px;
margin-right: 5px;
}
.anno-cover img:hover {
transform: scale(1.1);
transition: all 0.3s linear;
}
.anno-id {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5px;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: var(--common-shadow-1);
border-bottom-right-radius: 5px;
border-top-left-radius: 5px;
box-shadow: 2px 2px 5px var(--tgc-dark-1);
color: var(--tgc-white-1);
font-size: 12px;
text-shadow: 0 0 5px var(--tgc-dark-1);
}
</style>

View File

@@ -51,7 +51,7 @@ watch(
);
async function loadNav(): Promise<void> {
nav.value = await Mys.Posts.nav(props.modelValue);
nav.value = await Mys.ApiHub.homeNew(props.modelValue);
}
async function tryGetCode(): Promise<void> {

View File

@@ -1,93 +1,38 @@
<template>
<div
class="tib-box"
:style="{
width: modelValue.size,
height: modelValue.height,
cursor: modelValue.clickable ? 'pointer' : 'default',
}"
>
<div
class="tib-bg"
:style="{
width: modelValue.size,
height: modelValue.size,
}"
>
<div class="tib-box">
<div class="tib-bg">
<slot name="bg">
<img :src="modelValue.bg" alt="bg" />
<img :src="props.modelValue.bg" alt="bg" />
</slot>
</div>
<div
class="tib-icon"
:style="{
width: modelValue.size,
height: modelValue.size,
}"
>
<div class="tib-icon">
<slot name="icon">
<img :src="modelValue.icon" alt="icon" />
<img :src="props.modelValue.icon" alt="icon" />
</slot>
</div>
<div
class="tib-cover"
:style="{
width: modelValue.size,
height: modelValue.size,
}"
>
<div
class="tib-lt"
:style="{
width: modelValue.ltSize,
height: modelValue.ltSize,
}"
>
<img :src="modelValue.lt" alt="lt" />
<div class="tib-cover">
<div class="tib-lt">
<img :src="props.modelValue.lt" alt="lt" />
</div>
<div
v-show="modelValue.rt"
class="tib-rt"
:style="{
width: modelValue.rtSize,
height: modelValue.rtSize,
}"
>
{{ modelValue.rt }}
<div v-show="props.modelValue.rt" class="tib-rt">
{{ props.modelValue.rt }}
</div>
<div
class="tib-inner"
:style="{
height: `${props.modelValue.innerHeight ?? 0}px`,
fontSize: `${props.modelValue.innerHeight ? props.modelValue.innerHeight / 2 : 0}px`,
}"
>
<div class="tib-inner">
<slot name="inner-icon">
<img
v-show="modelValue.innerIcon"
:src="modelValue.innerIcon"
v-show="props.modelValue.innerIcon"
:src="props.modelValue.innerIcon"
alt="inner-icon"
:style="{
width: `${props.modelValue.innerHeight ?? 0}px`,
height: `${props.modelValue.innerHeight ?? 0}px`,
}"
/>
</slot>
<slot name="inner-text">
<span :title="modelValue.innerText">{{ modelValue.innerText }}</span>
<span :title="props.modelValue.innerText">{{ props.modelValue.innerText }}</span>
</slot>
</div>
</div>
<div
v-if="modelValue.display === 'outer'"
class="tib-outer"
:style="{
height: `${props.modelValue.outerHeight ?? 0}px`,
fontSize: `${props.modelValue.outerHeight ? props.modelValue.outerHeight / 2 : 0}px`,
}"
>
<div v-if="props.modelValue.display === 'outer'" class="tib-outer">
<slot name="outer-text">
<span>{{ modelValue.outerText }}</span>
<span>{{ props.modelValue.outerText }}</span>
</slot>
</div>
</div>
@@ -109,6 +54,7 @@ export interface TItemBoxData {
innerText: string;
outerHeight?: number;
outerText?: string;
innerBlur?: string;
}
interface TItemBoxProps {
@@ -116,10 +62,23 @@ interface TItemBoxProps {
}
const props = defineProps<TItemBoxProps>();
const size = props.modelValue.size;
const height = props.modelValue.height;
const cursor = props.modelValue.clickable ? "pointer" : "default";
const sizeLt = props.modelValue.ltSize;
const sizeRt = props.modelValue.rtSize;
const sizeInner = `${props.modelValue.innerHeight ?? 0}px`;
const fontSizeInner = props.modelValue.innerHeight ? `${props.modelValue.innerHeight / 2}px` : "0";
const sizeOuter = `${props.modelValue.outerHeight ?? 0}px`;
const fontSizeOuter = props.modelValue.outerHeight ? `${props.modelValue.outerHeight / 2}px` : "0";
const innerBlur = props.modelValue.innerBlur ?? "0";
</script>
<style lang="css" scoped>
.tib-box {
position: relative;
width: v-bind(size);
height: v-bind(height);
cursor: v-bind(cursor);
}
.tib-bg {
@@ -127,6 +86,8 @@ const props = defineProps<TItemBoxProps>();
top: 0;
left: 0;
overflow: hidden;
width: v-bind(size);
height: v-bind(size);
border-radius: 5px;
}
@@ -139,6 +100,8 @@ const props = defineProps<TItemBoxProps>();
.tib-icon {
position: relative;
overflow: hidden;
width: v-bind(size);
height: v-bind(size);
border-radius: 5px;
}
@@ -153,6 +116,8 @@ const props = defineProps<TItemBoxProps>();
top: 0;
left: 0;
display: flex;
width: v-bind(size);
height: v-bind(size);
flex-direction: column;
align-items: center;
justify-content: center;
@@ -164,6 +129,8 @@ const props = defineProps<TItemBoxProps>();
top: 3%;
left: 3%;
display: flex;
width: v-bind(sizeLt);
height: v-bind(sizeLt);
align-items: center;
justify-content: center;
}
@@ -179,6 +146,8 @@ const props = defineProps<TItemBoxProps>();
top: 0;
right: 0;
display: flex;
width: v-bind(sizeRt);
height: v-bind(sizeRt);
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 40%);
@@ -194,16 +163,22 @@ const props = defineProps<TItemBoxProps>();
left: 0;
display: flex;
width: 100%;
height: v-bind(sizeInner);
align-items: center;
justify-content: center;
-webkit-backdrop-filter: blur(v-bind(innerBlur));
backdrop-filter: blur(v-bind(innerBlur));
background: rgb(20 20 20 / 40%);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
color: var(--tgc-white-1);
font-family: var(--font-title);
font-size: v-bind(fontSizeInner);
}
.tib-inner img {
width: v-bind(sizeInner);
height: v-bind(sizeInner);
margin-right: 5px;
}
@@ -219,9 +194,11 @@ const props = defineProps<TItemBoxProps>();
bottom: 0;
display: flex;
width: 100%;
height: v-bind(sizeOuter);
align-items: center;
justify-content: center;
color: var(--common-text-title);
font-size: v-bind(fontSizeOuter);
text-align: center;
}
</style>

View File

@@ -1,7 +1,9 @@
<template>
<div v-if="card" :id="`post-card-${card.postId}`" class="tpc-card">
<div class="tpc-cover">
<img :src="card.cover" alt="cover" @click="createPost(card)" />
<div class="tpc-cover" @click="createPost(card)">
<img :src="localCover" alt="cover" v-if="localCover" />
<v-progress-circular color="primary" :indeterminate="true" v-else-if="card.cover !== ''" />
<img src="/source/UI/defaultCover.webp" alt="cover" v-else />
<div v-if="isAct" class="tpc-act">
<div class="tpc-status" :style="{ background: card.status?.colorCss }">
{{ card.status?.status }}
@@ -57,9 +59,9 @@
</div>
</template>
<script lang="ts" setup>
import { computed, onBeforeMount, ref } from "vue";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { generateShareImg } from "../../utils/TGShare.js";
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
import { createPost } from "../../utils/TGWindow.js";
import TpAvatar from "../post/tp-avatar.vue";
@@ -79,6 +81,7 @@ const props = withDefaults(defineProps<TPostCardProps>(), {
const emits = defineEmits<TPostCardEmits>();
const isAct = ref<boolean>(false);
const card = ref<TGApp.Plugins.Mys.News.RenderCard>();
const localCover = ref<string>();
const selectedList = computed({
get: () => props.selected,
set: (v) => {
@@ -87,8 +90,26 @@ const selectedList = computed({
},
});
onBeforeMount(() => {
card.value = getPostCard(props.modelValue);
onMounted(async () => await reload(props.modelValue));
watch(() => props.modelValue, reload);
async function reload(data: TGApp.Plugins.Mys.Post.FullData): Promise<void> {
if (localCover.value) {
URL.revokeObjectURL(localCover.value);
localCover.value = undefined;
}
card.value = getPostCard(data);
if (card.value && card.value.cover !== "") {
localCover.value = await saveImgLocal(card.value.cover);
}
}
onUnmounted(() => {
if (localCover.value) {
URL.revokeObjectURL(localCover.value);
localCover.value = undefined;
}
});
/**
@@ -138,15 +159,7 @@ function getActivityStatus(status: number): TGApp.Plugins.Mys.News.RenderStatus
}
}
/**
* @description 获取封面图
* @since Beta v0.4.5
* @param {TGApp.Plugins.Mys.Post.FullData} item 咨讯列表项
* @returns {string} 封面图链接
*/
function getPostCover(item: TGApp.Plugins.Mys.Post.FullData): string {
// 默认封面图
const defaultCover = "/source/UI/defaultCover.webp";
let cover;
if (item.cover) {
cover = item.cover.url;
@@ -155,14 +168,14 @@ function getPostCover(item: TGApp.Plugins.Mys.Post.FullData): string {
} else if (item.post.images.length > 0) {
cover = item.post.images[0];
}
if (cover === undefined) return defaultCover;
if (cover === undefined) return "";
if (cover.endsWith(".gif")) return cover;
return `${cover}?x-oss-process=image/format,png`;
}
/**
* @description 获取公共属性
* @since Beta v0.4.5
* @since Beta v0.6.1
* @param {TGApp.Plugins.Mys.Post.FullData} item 咨讯列表项
* @returns {TGApp.Plugins.Mys.News.RenderCard} 渲染用咨讯列表项
*/
@@ -238,6 +251,7 @@ async function shareCard(): Promise<void> {
justify-content: center;
aspect-ratio: 36 / 13;
background: var(--common-shadow-2);
cursor: pointer;
}
.tpc-cover img {
@@ -306,7 +320,6 @@ async function shareCard(): Promise<void> {
}
.tpc-cover img:hover {
cursor: pointer;
transform: scale(1.1);
transition: all 0.3s linear;
}

View File

@@ -134,7 +134,7 @@ async function searchPosts() {
load.value = false;
return;
}
const res = await Mys.Posts.search(game.value, search.value, lastId.value);
const res = await Mys.Post.searchPosts(game.value, search.value, lastId.value);
if (lastId.value === "") {
results.value = res.posts;
} else {

View File

@@ -75,6 +75,8 @@ function getTextStyle(): StyleValue {
const style = <Array<StyleValue>>[];
const data: TpText = props.data;
style.push("white-space: pre-wrap");
style.push("line-break: anywhere");
style.push("word-break: break-all");
if (data.attributes) {
const ruleBold: StyleValue = "fontFamily: var(--font-title)";
const ruleItalic: StyleValue = "fontStyle: italic";

View File

@@ -175,6 +175,7 @@ function getVodTime(): string {
}
.tp-vod-cover {
position: absolute;
max-width: 100%;
object-fit: cover;
}

View File

@@ -55,8 +55,8 @@ const votes = ref<TpVoteInfo>();
onMounted(async () => {
const vote = props.data.insert.vote;
const voteInfo = await Mys.Vote.get(vote.id, vote.uid);
const voteResult = await Mys.Vote.result(vote.id, vote.uid);
const voteInfo = await Mys.ApiHub.getVotes(vote.id, vote.uid);
const voteResult = await Mys.ApiHub.getVoteResult(vote.id, vote.uid);
votes.value = {
title: voteInfo.title,
count: voteResult.user_cnt,

View File

@@ -99,7 +99,7 @@ watch(
);
onMounted(async () => {
const collectionPosts = await Mys.PostCollect(props.collection.collection_id);
const collectionPosts = await Mys.Post.getPostFullInCollection(props.collection.collection_id);
const tempArr: TpoCollectionItem[] = [];
for (const postItem of collectionPosts) {
const post: TpoCollectionItem = {

View File

@@ -125,7 +125,7 @@ async function reloadReply(): Promise<void> {
async function loadReply(): Promise<void> {
loading.value = true;
const resp = await Mys.Post.reply(
const resp = await Mys.Post.getPostReplies(
props.postId,
props.gid,
isHot.value,

View File

@@ -4,9 +4,7 @@
class="tpr-bubble"
v-if="props.modelValue.user.reply_bubble !== null"
:title="props.modelValue.user.reply_bubble.name"
:style="{
backgroundColor: props.modelValue.user.reply_bubble.bg_color,
}"
:style="{ backgroundColor: props.modelValue.user.reply_bubble.bg_color }"
>
<img :src="props.modelValue.user.reply_bubble.url" alt="bubble" />
</div>
@@ -52,9 +50,7 @@
activator="parent"
location="end"
:close-on-content-click="false"
:no-click-animation="true"
v-model="showSub"
:persistent="true"
>
<v-list class="tpr-reply-sub" width="300px" max-height="400px">
<TprReply
@@ -211,17 +207,14 @@ async function showReply(): Promise<void> {
async function loadSub(): Promise<void> {
loading.value = true;
const resp = await Mys.Post.replySub(
const resp = await Mys.Post.getSubReplies(
props.modelValue.reply.floor_id,
props.modelValue.reply.game_id,
props.modelValue.reply.post_id,
lastId.value,
);
if ("retcode" in resp) {
showSnackbar({
text: `[${resp.retcode}] ${resp.message}`,
color: "error",
});
showSnackbar({ text: `[${resp.retcode}] ${resp.message}`, color: "error" });
loading.value = false;
return;
}
@@ -229,12 +222,7 @@ async function loadSub(): Promise<void> {
lastId.value = resp.last_id;
subReplies.value = subReplies.value.concat(resp.list);
loading.value = false;
if (isLast.value) {
showSnackbar({
text: "没有更多了",
color: "info",
});
}
if (isLast.value) showSnackbar({ text: "没有更多了", color: "info" });
}
async function exportData(): Promise<void> {

View File

@@ -3,10 +3,12 @@
<div v-if="ncData !== undefined">
<TopNameCard :data="ncData" @selected="showNc = true" />
</div>
<!-- todo 虚拟列表优化 -->
<div v-for="(item, index) in renderAchi" :key="index">
<TuaAchi :modelValue="item" @select-achi="selectAchi" />
</div>
<v-virtual-scroll :items="renderAchi" :item-height="60" class="tua-al-list">
<template #default="{ item }">
<TuaAchi :modelValue="item" @select-achi="selectAchi" />
<div style="height: 10px" />
</template>
</v-virtual-scroll>
<ToNameCard v-model="showNc" :data="ncData" v-if="ncData" />
<ToAchiInfo
v-if="selectedAchi"
@@ -144,11 +146,14 @@ function switchAchiInfo(next: boolean): void {
width: 100%;
height: 100%;
flex-direction: column;
padding-right: 10px;
overflow-y: scroll;
overflow-y: auto;
row-gap: 10px;
}
.tua-al-list {
padding-right: 10px;
}
.card-arrow {
position: relative;
display: flex;

View File

@@ -115,6 +115,7 @@ async function setAchiStat(stat: boolean): Promise<void> {
.achi-container {
position: relative;
display: flex;
height: 60px;
align-items: center;
justify-content: space-between;
padding: 10px;

View File

@@ -27,19 +27,19 @@
<span>Lv.{{ skill.level }}</span>
</div>
</div>
</div>
<div class="tua-abl-bottom">
<div class="tua-abl-fetter">
<img src="/icon/material/105.webp" alt="fetter" />
<span>{{ props.modelValue.avatar.fetter }}</span>
</div>
<div class="tua-abl-other">
<span v-if="!isFetterMax">
<v-icon>mdi-lock-outline</v-icon>
</span>
<span v-if="props.modelValue.costumes.length > 0">
<v-icon>mdi-tshirt-crew-outline</v-icon>
</span>
<div class="tua-abl-bottom">
<div class="tua-abl-fetter">
<img src="/icon/material/105.webp" alt="fetter" />
<span>{{ props.modelValue.avatar.fetter }}</span>
</div>
<div class="tua-abl-other">
<span v-if="!isFetterMax">
<v-icon>mdi-lock-outline</v-icon>
</span>
<span v-if="props.modelValue.costumes.length > 0">
<v-icon>mdi-tshirt-crew-outline</v-icon>
</span>
</div>
</div>
</div>
</div>
@@ -186,13 +186,17 @@ function getWeaponTitle(): string {
}
.tua-abl-bottom {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
padding: 5px;
border-radius: 5px;
background: var(--box-bg-3);
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
background: rgb(0 0 0 / 40%);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
font-family: var(--font-title);
}
@@ -211,10 +215,14 @@ function getWeaponTitle(): string {
.tua-abl-mid {
position: relative;
display: flex;
overflow: hidden;
width: 100%;
height: 80px;
flex-direction: column;
align-items: center;
justify-content: space-between;
border-radius: 5px;
aspect-ratio: 21/10;
}
.tua-abl-bg {
@@ -223,10 +231,10 @@ function getWeaponTitle(): string {
left: 0;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
border-radius: 5px;
aspect-ratio: 21/10;
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
background: var(--box-bg-3);
@@ -235,13 +243,13 @@ function getWeaponTitle(): string {
width: 100%;
height: 100%;
border-radius: 5px;
object-fit: fill;
}
}
.tua-abl-skills {
position: relative;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
padding: 5px;

View File

@@ -35,14 +35,18 @@
:src="propMain.icon"
alt="propMain"
/>
<span>{{ propMain !== false ? propMain.name : "未知属性" }}</span>
<span :style="getPropMainStyle()">
{{ propMain !== false ? propMain.name : "未知属性" }}
</span>
</span>
<span>{{ props.modelValue.main_property.value }}</span>
</div>
<div v-for="(prop, index) in propSubs" :key="index" class="tua-dcr-prop">
<span class="tua-prop-sub">
<img v-if="prop !== false && prop.icon !== ''" :src="prop.icon" alt="propSub" />
<span>{{ prop !== false ? prop.name : "未知属性" }}</span>
<span :style="getPropSubStyle(prop, props.recommend.sub_property_list)">
{{ prop !== false ? prop.name : "未知属性" }}
</span>
<span class="tua-prop-time" v-if="props.modelValue.sub_property_list[index].times !== 0">
{{ props.modelValue.sub_property_list[index].times }}
</span>
@@ -60,6 +64,7 @@ import { useUserStore } from "../../store/modules/user.js";
interface TuaDcRelicProps {
modelValue: TGApp.Game.Avatar.Relic | false;
pos: "1" | "2" | "3" | "4" | "5";
recommend: TGApp.Game.Avatar.PropRecommend;
}
const props = defineProps<TuaDcRelicProps>();
@@ -85,6 +90,45 @@ function getRelicTitle(): string {
if (props.modelValue === false) return getRelicPos();
return props.modelValue.name;
}
function getPropMainStyle(): string {
if (props.modelValue === false) return "";
if (props.pos === "3") {
if (
props.recommend.sand_main_property_list.includes(props.modelValue.main_property.property_type)
) {
return "color: var(--tgc-yellow-1);";
}
}
if (props.pos === "4") {
if (
props.recommend.goblet_main_property_list.includes(
props.modelValue.main_property.property_type,
)
) {
return "color: var(--tgc-yellow-1);";
}
}
if (props.pos === "5") {
if (
props.recommend.circlet_main_property_list.includes(
props.modelValue.main_property.property_type,
)
) {
return "color: var(--tgc-yellow-1);";
}
}
return "";
}
function getPropSubStyle(
propItem: TGApp.Game.Avatar.PropMapItem | false,
propsR: number[],
): string {
if (propItem === false) return "";
if (propsR.includes(propItem.property_type)) return "color: var(--tgc-yellow-1);";
return "";
}
</script>
<style lang="css" scoped>
.tua-dcr-box {
@@ -232,7 +276,6 @@ function getRelicTitle(): string {
.tua-prop-time {
width: 14px;
height: 14px;
padding-bottom: 1px;
border: 1px solid rgb(255 255 255 / 20%);
border-radius: 4px;
background: rgb(0 0 0 / 20%);

View File

@@ -32,11 +32,31 @@
:uid="props.modelValue.uid"
:updated="props.modelValue.updated"
/>
<TuaDcRelic :model-value="relicList[0]" pos="1" />
<TuaDcRelic :model-value="relicList[1]" pos="2" />
<TuaDcRelic :model-value="relicList[2]" pos="3" />
<TuaDcRelic :model-value="relicList[3]" pos="4" />
<TuaDcRelic :model-value="relicList[4]" pos="5" />
<TuaDcRelic
:model-value="relicList[0]"
pos="1"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[1]"
pos="2"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[2]"
pos="3"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[3]"
pos="4"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
<TuaDcRelic
:model-value="relicList[4]"
pos="5"
:recommend="props.modelValue.propRecommend.recommend_properties"
/>
</div>
<!-- 左下命座跟天赋 -->
<div class="tua-dc-lb">

View File

@@ -1,5 +1,5 @@
<template>
<TOverlay v-model="visible" hide blur-val="5px" :to-click="onCancel">
<TOverlay v-model="visible" :hide="true" blur-val="5px" :to-click="onCancel">
<div class="tdo-box">
<div class="tdo-avatars-container">
<v-tabs
@@ -15,15 +15,7 @@
@click="onAvatarClick(avatar)"
:title="avatar.avatar.name"
>
<div
class="tdo-avatar"
:style="{
backgroundColor:
props.avatar.avatar.id === avatar.avatar.id
? 'var(--tgc-od-white)'
: 'transparent',
}"
>
<div class="tdo-avatar" :style="getAvatarBg(avatar)">
<img :src="avatar.avatar.side_icon" :alt="avatar.avatar.name" />
</div>
</v-tab>
@@ -89,6 +81,7 @@ const modeTab = computed<"classic" | "card" | "dev">({
get: () => props.mode,
set: (val) => emits("update:mode", val),
});
const avatarsWidth = computed<string>(() => {
switch (props.mode) {
case "classic":
@@ -121,6 +114,13 @@ function handleClick(pos: "left" | "right"): void {
function onAvatarClick(avatar: TGApp.Sqlite.Character.UserRole): void {
emits("toAvatar", avatar);
}
function getAvatarBg(avatar: TGApp.Sqlite.Character.UserRole): string {
if (props.avatar.avatar.id === avatar.avatar.id) {
return "background-color:var(--tgc-od-white);";
}
return "background-color:transparent;";
}
</script>
<style lang="css" scoped>
.tdo-box {

View File

@@ -63,9 +63,9 @@
v-for="(item, index) in data?.talks"
:key="index"
>
<template #title
><span class="twc-text-item-title">{{ item.Title }}</span></template
>
<template #title>
<span class="twc-text-item-title">{{ item.Title }}</span>
</template>
<template #text>
<span class="twc-text-item-content" v-html="parseHtmlText(item.Context)" />
</template>
@@ -82,12 +82,12 @@
v-for="(item, index) in data.stories"
:key="index"
>
<template #title
><span class="twc-text-item-title">{{ item.Title }}</span></template
>
<template #text
><span class="twc-text-item-content">{{ item.Context }}</span></template
>
<template #title>
<span class="twc-text-item-title">{{ item.Title }}</span>
</template>
<template #text>
<span class="twc-text-item-content">{{ item.Context }}</span>
</template>
</v-expansion-panel>
</v-expansion-panels>
</template>
@@ -101,8 +101,7 @@ import { computed, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { WikiCharacterData, AppNameCardsData, AppCharacterData } from "../../data/index.js";
import Mys from "../../plugins/Mys/index.js";
import { createTGWindow } from "../../utils/TGWindow.js";
import { createObc } from "../../utils/TGWindow.js";
import { parseHtmlText } from "../../utils/toolFunc.js";
import showSnackbar from "../func/snackbar.js";
import TItembox, { TItemBoxData } from "../main/t-itembox.vue";
@@ -143,10 +142,7 @@ const nameCard = ref<TGApp.App.NameCard.Item>();
async function loadData(): Promise<void> {
const res = WikiCharacterData.find((item) => item.id === props.item.id);
if (res === undefined) {
showSnackbar({
text: `未获取到角色 ${props.item.name} 的 Wiki 数据`,
color: "error",
});
showSnackbar({ text: `未获取到角色 ${props.item.name} 的 Wiki 数据`, color: "error" });
return;
}
data.value = res;
@@ -157,38 +153,22 @@ async function loadData(): Promise<void> {
} else {
hasNc.value = false;
}
showSnackbar({
text: `成功获取角色 ${props.item.name} 的 Wiki 数据`,
color: "success",
});
showSnackbar({ text: `成功获取角色 ${props.item.name} 的 Wiki 数据` });
}
watch(
() => props.item,
async () => {
await loadData();
},
async () => await loadData(),
);
onMounted(async () => await loadData());
async function toWiki(): Promise<void> {
if (props.item.contentId === 0) {
showSnackbar({
text: `角色 ${props.item.name} 暂无详情`,
color: "warn",
});
showSnackbar({ text: `角色 ${props.item.name} 暂无详情`, color: "warn" });
return;
}
const url = Mys.Api.Obc.replace("{contentId}", props.item.contentId.toString());
await createTGWindow(
url,
"Sub_window",
`Content_${props.item.contentId} ${props.item.name}`,
1200,
800,
true,
);
await createObc(props.item.contentId, props.item.name);
}
async function toBirth(date: string): Promise<void> {

View File

@@ -54,8 +54,7 @@
import { computed, onMounted, ref, watch } from "vue";
import { WikiWeaponData } from "../../data/index.js";
import Mys from "../../plugins/Mys/index.js";
import { createTGWindow } from "../../utils/TGWindow.js";
import { createObc } from "../../utils/TGWindow.js";
import { parseHtmlText } from "../../utils/toolFunc.js";
import showSnackbar from "../func/snackbar.js";
import TItembox, { TItemBoxData } from "../main/t-itembox.vue";
@@ -88,17 +87,11 @@ const selectItems = ref<number[]>([]);
async function loadData(): Promise<void> {
const res = WikiWeaponData.find((item) => item.id === props.item.id);
if (res === undefined) {
showSnackbar({
text: `未获取到武器 ${props.item.name} 的 Wiki 数据`,
color: "error",
});
showSnackbar({ text: `未获取到武器 ${props.item.name} 的 Wiki 数据`, color: "error" });
return;
}
data.value = res;
showSnackbar({
text: `成功获取武器 ${props.item.name} 的 Wiki 数据`,
color: "success",
});
showSnackbar({ text: `成功获取武器 ${props.item.name} 的 Wiki 数据` });
if (data.value?.affix === undefined) return;
selectItems.value = data.value?.affix.Descriptions.map((item) => item.Level) ?? [];
}
@@ -112,21 +105,10 @@ onMounted(async () => await loadData());
async function toWiki(): Promise<void> {
if (props.item.contentId === 0) {
showSnackbar({
text: `武器 ${props.item.name} 暂无详情`,
color: "warn",
});
showSnackbar({ text: `武器 ${props.item.name} 暂无详情`, color: "warn" });
return;
}
const url = Mys.Api.Obc.replace("{contentId}", props.item.contentId.toString());
await createTGWindow(
url,
"Sub_window",
`Content_${props.item.contentId} ${props.item.name}`,
1200,
800,
true,
);
await createObc(props.item.contentId, props.item.name);
}
</script>
<style lang="css" scoped>

View File

@@ -527,6 +527,50 @@
"icon": "/icon/nation/枫丹.webp"
}
},
{
"id": 10000103,
"contentId": 502306,
"dropDays": [2, 5, 7],
"name": "希诺宁",
"itemType": "character",
"star": 5,
"bg": "/icon/bg/5-Star.webp",
"weaponIcon": "/icon/weapon/单手剑.webp",
"elementIcon": "/icon/element/岩元素.webp",
"icon": "/WIKI/character/10000103.webp",
"materials": [
{
"id": 104350,
"name": "「焚燔」的教导",
"star": 2,
"starIcon": "/icon/star/2.webp",
"bg": "/icon/bg/2-Star.webp",
"icon": "/icon/material/104350.webp"
},
{
"id": 104351,
"name": "「焚燔」的指引",
"star": 3,
"starIcon": "/icon/star/3.webp",
"bg": "/icon/bg/3-Star.webp",
"icon": "/icon/material/104351.webp"
},
{
"id": 104352,
"name": "「焚燔」的哲学",
"star": 4,
"starIcon": "/icon/star/4.webp",
"bg": "/icon/bg/4-Star.webp",
"icon": "/icon/material/104352.webp"
}
],
"source": {
"index": 6,
"area": "纳塔",
"name": "空华",
"icon": "/icon/nation/纳塔.webp"
}
},
{
"id": 10000033,
"contentId": 1220,
@@ -8776,7 +8820,7 @@
},
{
"id": 12430,
"contentId": 0,
"contentId": 503098,
"dropDays": [3, 6, 7],
"name": "硕果钩",
"itemType": "weapon",

View File

@@ -2285,5 +2285,41 @@
"postId": "58373656",
"up5List": [11516, 11514],
"up4List": [11430, 12403, 13430, 14401, 15405]
},
{
"name": "月草的赐慧",
"version": "5.1",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2024/10/16/f7b8f8421bf71302fd94eb98b23cb9cf_702597644740369872.png",
"from": "2024-10-29T18:00:00+08:00",
"to": "2024-11-19T14:59:00+08:00",
"type": 301,
"postId": "58825440",
"up5List": [10000073],
"up4List": [10000097, 10000025, 10000065]
},
{
"name": "赤团开时",
"version": "5.1",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2024/10/16/4ca1e671b64731ddd84836bb267fa06c_4542790859761833080.png",
"from": "2024-10-29T18:00:00+08:00",
"to": "2024-11-19T14:59:00+08:00",
"type": 400,
"postId": "58825441",
"up5List": [10000046],
"up4List": [10000097, 10000025, 10000065]
},
{
"name": "神铸赋形",
"version": "5.1",
"order": 2,
"banner": "https://sdk.hoyoverse.com/upload/ann/2024/10/16/1e9f7936bbfc23093a259c96bdd3ff12_881218391629954749.png",
"from": "2024-10-29T18:00:00+08:00",
"to": "2024-11-19T14:59:00+08:00",
"type": 302,
"postId": "58825442",
"up5List": [14511, 13501],
"up4List": [11405, 12430, 13401, 14409, 15401]
}
]

View File

@@ -1054,7 +1054,7 @@
},
{
"id": 12430,
"contentId": 0,
"contentId": 503098,
"name": "硕果钩",
"star": 4,
"bg": "/icon/bg/4-Star.webp",

View File

@@ -13,8 +13,7 @@
clearable
variant="outlined"
label="角色"
:item-value="(item: TGApp.Archive.Birth.RoleItem) => item.role_birthday"
:item-title="(item: TGApp.Archive.Birth.RoleItem) => item.name"
:item-value="(item: TGApp.Archive.Birth.RoleItem) => item"
:item-props="(item: TGApp.Archive.Birth.RoleItem) => getItemProps(item)"
>
</v-select>
@@ -46,36 +45,44 @@ const page = ref(1);
const length = ref(0);
const visible = ref(0);
const renderItems = ref<TGApp.Archive.Birth.DrawItem[]>([]);
const curSelect = ref<string | null>(null);
const curSelect = ref<TGApp.Archive.Birth.RoleItem | null>(null);
const selectedItem = ref<TGApp.Archive.Birth.DrawItem[]>([]);
const current = ref<TGApp.Archive.Birth.DrawItem>();
const isAether = ref<boolean>(false);
const showOverlay = ref(false);
const route = useRoute();
watch(page, (val) => {
const start = (val - 1) * 12;
const end = start + 12;
selectedItem.value = renderItems.value.slice(start, end);
});
watch(
() => page.value,
() => {
const start = (page.value - 1) * 12;
const end = start + 12;
selectedItem.value = renderItems.value.slice(start, end);
},
);
watch(curSelect, (val) => {
if (val) {
renderItems.value = ArcBirDraw.filter((item) => item.birthday === val);
} else {
renderItems.value = ArcBirDraw;
}
length.value = Math.ceil(renderItems.value.length / 12);
page.value = 1;
selectedItem.value = renderItems.value.slice(0, 12);
visible.value = length.value > 5 ? 5 : length.value;
});
watch(
() => curSelect.value,
() => {
if (curSelect.value) {
renderItems.value = ArcBirDraw.filter(
(item) => item.birthday === curSelect.value?.role_birthday,
);
} else {
renderItems.value = ArcBirDraw;
}
length.value = Math.ceil(renderItems.value.length / 12);
page.value = 1;
selectedItem.value = renderItems.value.slice(0, 12);
visible.value = length.value > 5 ? 5 : length.value;
},
);
onMounted(() => {
let { date } = route.params;
if (date) {
if (Array.isArray(date)) date = date[0];
curSelect.value = date;
const dataFind = ArcBirRole.find((i) => i.role_birthday === date);
if (dataFind) curSelect.value = dataFind;
} else {
renderItems.value = ArcBirDraw;
selectedItem.value = renderItems.value.slice(0, 12);
@@ -97,7 +104,7 @@ async function toAct(): Promise<void> {
function getItemProps(item: TGApp.Archive.Birth.RoleItem) {
return {
title: `${item.name} ${item.role_birthday}`,
subtitle: item.text,
subtitle: new DOMParser().parseFromString(item.text, "text/html").body.textContent,
prependAvatar: item.head_icon,
};
}

View File

@@ -52,19 +52,27 @@
class="ua-window-item"
>
<div :id="`user-abyss-${item.id}`" class="uaw-i-ref">
<div class="uaw-title">
<span></span>
<span>{{ item.id }}</span>
<span> UID</span>
<span>{{ uidCur }}</span>
<span>更新于</span>
<span>{{ item.updated }}</span>
<div class="uaw-top">
<div class="uaw-title">
<span></span>
<span>{{ item.id }}</span>
<span> UID</span>
<span>{{ uidCur }}</span>
<span>更新于</span>
<span>{{ item.updated }}</span>
</div>
<div class="uaw-share">Render by TeyvatGuide v{{ version }}</div>
</div>
<TSubLine>统计周期 {{ item.startTime }} ~ {{ item.endTime }}</TSubLine>
<div class="uaw-o-box">
<TuaOverview title="战斗次数" :val-text="item.totalBattleTimes" />
<TuaOverview title="获得渊星" :val-text="item.totalStar" />
<TuaOverview title="最深抵达" :val-text="item.maxFloor" />
<TuaOverview
title="最深抵达"
:val-text="
item.skippedFloor !== '' ? `${item.maxFloor}(${item.skippedFloor})` : item.maxFloor
"
/>
<TuaOverview title="最多击破" :val-icons="item.defeatRank" />
<TuaOverview title="最多承伤" :val-icons="item.takeDamageRank" />
<TuaOverview title="最强一击" :val-icons="item.damageRank" />
@@ -90,6 +98,7 @@
</div>
</template>
<script lang="ts" setup>
import { getVersion } from "@tauri-apps/api/app";
import { storeToRefs } from "pinia";
import { onMounted, ref, watch, computed } from "vue";
@@ -120,6 +129,7 @@ const user = computed<TGApp.Sqlite.Account.Game>(() => userStore.account.value);
const localAbyss = ref<TGApp.Sqlite.Abyss.SingleTable[]>([]);
const abyssRef = ref<HTMLElement>(<HTMLElement>{});
const version = ref<string>();
const uidList = ref<string[]>();
const uidCur = ref<string>();
@@ -128,6 +138,7 @@ const abyssIdList = computed<number[]>(() => {
});
onMounted(async () => {
version.value = await getVersion();
await TGLogger.Info("[UserAbyss][onMounted] 打开角色深渊页面");
loadingTitle.value = "正在加载深渊数据";
uidList.value = await TSUserAbyss.getAllUid();
@@ -178,14 +189,8 @@ async function refreshAbyss(): Promise<void> {
await TGLogger.Info("[UserAbyss][getAbyssData] 更新深渊数据");
loadingTitle.value = `正在获取${user.value.gameUid}的深渊数据`;
loading.value = true;
const cookie = {
account_id: userStore.cookie.value.account_id,
cookie_token: userStore.cookie.value.cookie_token,
ltoken: userStore.cookie.value.ltoken,
ltuid: userStore.cookie.value.ltuid,
};
loadingTitle.value = `正在获取${user.value.gameUid}的上期深渊数据`;
const resP = await TGRequest.User.byCookie.getAbyss(cookie, "2", user.value);
const resP = await TGRequest.User.byCookie.getAbyss(userStore.cookie.value, "2", user.value);
if (!("retcode" in resP)) {
await TGLogger.Info("[UserAbyss][getAbyssData] 成功获取上期深渊数据");
loadingTitle.value = `正在保存${user.value.gameUid}的上期深渊数据`;
@@ -198,7 +203,7 @@ async function refreshAbyss(): Promise<void> {
return;
}
loadingTitle.value = `正在获取${user.value.gameUid}的上期深渊数据`;
const res = await TGRequest.User.byCookie.getAbyss(cookie, "1", user.value);
const res = await TGRequest.User.byCookie.getAbyss(userStore.cookie.value, "1", user.value);
if (!("retcode" in res)) {
loadingTitle.value = `正在保存${user.value.gameUid}的本期深渊数据`;
await TSUserAbyss.saveAbyss(user.value.gameUid, res);
@@ -349,7 +354,7 @@ async function deleteAbyss(): Promise<void> {
.ua-box {
display: flex;
height: calc(100vh - 100px);
align-items: center;
align-items: flex-start;
justify-content: center;
border: 1px solid var(--common-shadow-4);
border-radius: 5px;
@@ -380,6 +385,13 @@ async function deleteAbyss(): Promise<void> {
gap: 5px;
}
.uaw-top {
display: flex;
width: 100%;
align-items: flex-end;
justify-content: space-between;
}
.uaw-title {
display: flex;
align-items: center;
@@ -394,6 +406,12 @@ async function deleteAbyss(): Promise<void> {
color: var(--tgc-yellow-1);
}
.uaw-share {
z-index: -1;
font-size: 12px;
opacity: 0.8;
}
.uaw-o-box {
display: grid;
width: 100%;

View File

@@ -280,11 +280,15 @@ async function refresh(): Promise<void> {
loadData.value = false;
return;
}
const cookie = {
account_id: userStore.cookie.value.account_id,
cookie_token: userStore.cookie.value.cookie_token,
};
const listRes = await TGRequest.User.byCookie.getAvatarList(cookie, user.value.gameUid);
const indexRes = await TGRequest.User.byCookie.getAvatarIndex(userStore.cookie.value, user.value);
if (indexRes.retcode !== 0) {
showSnackbar({ text: `[${indexRes.retcode}] ${indexRes.message}` });
await TGLogger.Error(JSON.stringify(indexRes.message));
loading.value = false;
loadData.value = false;
return;
}
const listRes = await TGRequest.User.byCookie.getAvatarList(userStore.cookie.value, user.value);
if (!Array.isArray(listRes)) {
showSnackbar({ text: `[${listRes.retcode}] ${listRes.message}`, color: "error" });
await TGLogger.Error(`[Character][refreshRoles][${user.value.gameUid}] 获取角色列表失败`);
@@ -298,7 +302,11 @@ async function refresh(): Promise<void> {
const idList = listRes.map((i) => i.id.toString());
loadingTitle.value = "正在获取角色数据";
loadingSub.value = `${idList.length}个角色`;
const res = await TGRequest.User.byCookie.getAvatarDetail(cookie, user.value.gameUid, idList);
const res = await TGRequest.User.byCookie.getAvatarDetail(
userStore.cookie.value,
user.value,
idList,
);
if ("retcode" in res) {
showSnackbar({ text: `[${res.retcode}] ${res.message}`, color: "error" });
await TGLogger.Error(`[Character][refreshRoles][${user.value.gameUid}] 获取角色数据失败`);

View File

@@ -1,38 +1,48 @@
<template>
<ToLoading v-model="loading" :title="loadingTitle" :subtitle="loadingSub" />
<div class="hta-box">
<div class="hta-top">
<v-tabs v-model="tab" align-tabs="start" class="hta-tab">
<v-tab value="use">角色使用</v-tab>
<v-tab value="up">角色出场</v-tab>
<v-tab value="team">队伍出场</v-tab>
<v-tab value="hold">角色持有</v-tab>
</v-tabs>
<div class="hta-title">
<span>胡桃数据库</span>
<span @click="showDialog = true">更新于 {{ getUpdated() }}</span>
<ToLoading v-model="loading" :title="loadT" />
<v-app-bar>
<template #prepend>
<div class="hta-top-prepend">
<img src="/source/UI/wikiAbyss.webp" alt="icon" />
<span>深渊统计</span>
<v-select
v-model="tab"
:items="abyssList"
item-title="label"
item-value="value"
density="compact"
variant="outlined"
/>
</div>
</div>
</template>
<template #append>
<div class="hta-top-append">
<span @click="show()" v-if="overview">
更新于 {{ timestampToDate(overview.cur.Timestamp) }}
</span>
</div>
</template>
</v-app-bar>
<div class="hta-box">
<v-window v-model="tab" class="hta-tab-item">
<v-window-item value="use">
<HtaTabUse v-if="avatarUse.length > 0" v-model="avatarUse" />
<HtaTabUse v-if="abyssData.use !== null" :data="abyssData.use" />
</v-window-item>
<v-window-item value="up">
<HtaTabUp v-if="avatarUp.length > 0" v-model="avatarUp" />
<HtaTabUp v-if="abyssData.up !== null" :data="abyssData.up" />
</v-window-item>
<v-window-item value="team">
<HtaTabTeam v-model="teamCombination" />
<HtaTabTeam v-if="abyssData.team !== null" :model-value="abyssData.team" />
</v-window-item>
<v-window-item value="hold">
<HtaTabHold v-model="avatarHold" />
<HtaTabHold v-if="abyssData.hold !== null" :data="abyssData.hold" />
</v-window-item>
</v-window>
</div>
<HtaOverlayOverview v-model="showDialog" :data="overview" />
<HtaOverlayOverview v-if="overview" v-model="showDialog" :data="overview" />
</template>
<script lang="ts" setup>
// vue
import { onMounted, ref } from "vue";
import { onMounted, ref, watch } from "vue";
import HtaOverlayOverview from "../../components/hutaoAbyss/hta-overlay-overview.vue";
import HtaTabHold from "../../components/hutaoAbyss/hta-tab-hold.vue";
@@ -41,93 +51,158 @@ import HtaTabUp from "../../components/hutaoAbyss/hta-tab-up.vue";
import HtaTabUse from "../../components/hutaoAbyss/hta-tab-use.vue";
import ToLoading from "../../components/overlay/to-loading.vue";
import Hutao from "../../plugins/Hutao/index.js";
import { timestampToDate } from "../../utils/toolFunc.js";
enum AbyssTabEnum {
use = "角色使用",
up = "角色出场",
team = "队伍出场",
hold = "角色持有",
}
type AbyssTab = keyof typeof AbyssTabEnum;
type AbyssList = Array<{ label: AbyssTabEnum; value: AbyssTab }>;
export type AbyssDataItem<T> = { cur: T; last: T };
export type AbyssDataItemType<T extends AbyssTab> = T extends "use"
? AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>
: T extends "up"
? AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUp[]>
: T extends "team"
? TGApp.Plugins.Hutao.Abyss.TeamCombination[]
: T extends "hold"
? AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarHold[]>
: null;
type AbyssData = {
[key in AbyssTab]: AbyssDataItemType<key> | null;
};
const abyssList: AbyssList = [
{ label: AbyssTabEnum.use, value: "use" },
{ label: AbyssTabEnum.up, value: "up" },
{ label: AbyssTabEnum.team, value: "team" },
{ label: AbyssTabEnum.hold, value: "hold" },
];
// loading
const loading = ref<boolean>(false);
const loadingTitle = ref<string>("");
const loadingSub = ref<string>();
// overview overlay
const loadT = ref<string>("");
const showDialog = ref<boolean>(false);
// data
const overview = ref<TGApp.Plugins.Hutao.Abyss.OverviewData>(
<TGApp.Plugins.Hutao.Abyss.OverviewData>{},
const overview = ref<AbyssDataItem<TGApp.Plugins.Hutao.Abyss.OverviewData>>();
const tab = ref<AbyssTab>("use");
const abyssData = ref<AbyssData>({ use: null, up: null, team: null, hold: null });
watch(
() => tab.value,
async () => await refreshData(tab.value),
);
const tab = ref<string>("use");
const avatarUse = ref<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>([]);
const avatarUp = ref<TGApp.Plugins.Hutao.Abyss.AvatarUp[]>([]);
const teamCombination = ref<TGApp.Plugins.Hutao.Abyss.TeamCombination[]>([]);
const avatarHold = ref<TGApp.Plugins.Hutao.Abyss.AvatarHold[]>([]);
onMounted(async () => {
loadingTitle.value = "正在获取深渊数据";
loadT.value = "正在获取深渊数据";
loading.value = true;
loadingTitle.value = "正在获取深渊概览";
overview.value = await Hutao.Abyss.getOverview();
loadingTitle.value = "正在获取深渊角色使用";
avatarUse.value = await Hutao.Abyss.avatar.getUseRate();
loadingTitle.value = "正在获取深渊角色出场";
avatarUp.value = await Hutao.Abyss.avatar.getUpRate();
loadingTitle.value = "正在获取深渊队伍出场";
teamCombination.value = await Hutao.Abyss.getTeamCollect();
loadingTitle.value = "正在获取深渊角色持有";
avatarHold.value = await Hutao.Abyss.avatar.getHoldRate();
loadT.value = "正在获取深渊概览";
overview.value = {
cur: await Hutao.Abyss.getOverview(),
last: await Hutao.Abyss.getOverview(true),
};
loadT.value = "正在获取深渊数据";
const useData = <AbyssDataItem<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>>await getData("use");
abyssData.value = { use: useData, up: null, team: null, hold: null };
loading.value = false;
});
function getUpdated(): string {
return new Date(overview.value.Timestamp)
.toLocaleString("zh-CN", { hour12: false })
.replace(/\//g, "-");
function show(): void {
showDialog.value = !showDialog.value;
}
async function refreshData(type: AbyssTab): Promise<void> {
if (abyssData.value && abyssData.value[type] !== null) return;
if (loading.value) return;
loading.value = true;
loadT.value = `正在获取 ${AbyssTabEnum[type]} 数据`;
const data = await getData(type);
switch (type) {
case "use":
abyssData.value.use = <AbyssDataItemType<"use">>data;
break;
case "up":
abyssData.value.up = <AbyssDataItemType<"up">>data;
break;
case "team":
abyssData.value.team = <AbyssDataItemType<"team">>data;
break;
case "hold":
abyssData.value.hold = <AbyssDataItemType<"hold">>data;
break;
}
loading.value = false;
}
async function getData(type: AbyssTab): Promise<AbyssDataItemType<AbyssTab>> {
switch (type) {
case "use":
return {
cur: await Hutao.Abyss.avatar.getUseRate(),
last: await Hutao.Abyss.avatar.getUseRate(true),
};
case "up":
return {
cur: await Hutao.Abyss.avatar.getUpRate(),
last: await Hutao.Abyss.avatar.getUpRate(true),
};
case "team":
return await Hutao.Abyss.getTeamCollect();
case "hold":
return {
cur: await Hutao.Abyss.avatar.getHoldRate(),
last: await Hutao.Abyss.avatar.getHoldRate(true),
};
}
}
</script>
<style lang="css" scoped>
.hta-top-prepend {
position: relative;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
img {
width: 32px;
height: 32px;
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
:last-child {
height: 50px;
margin-top: 20px;
}
}
.hta-top-append {
display: flex;
align-items: flex-end;
justify-content: center;
cursor: pointer;
font-family: var(--font-title);
}
.hta-box {
overflow: auto;
width: 100%;
max-height: calc(100vh - 30px);
box-sizing: border-box;
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px var(--common-shadow-4);
}
.hta-top {
display: flex;
width: 100%;
align-items: center;
justify-content: flex-start;
color: var(--common-text-title);
column-gap: 10px;
font-family: var(--font-title);
}
.hta-tab {
height: 50px;
margin-bottom: 10px;
}
.hta-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-left: auto;
column-gap: 10px;
font-size: 20px;
}
.hta-title :last-child {
font-family: var(--font-text);
}
.hta-title :last-child:hover {
cursor: pointer;
text-decoration: underline;
}
.hta-tab-item {
width: 100%;
height: calc(100% - 60px);
height: 100%;
}
</style>

View File

@@ -32,8 +32,7 @@ import TwcCharacter from "../../components/wiki/twc-character.vue";
import TwcListItem from "../../components/wiki/twc-list-item.vue";
import TwoSelectC, { SelectedCValue } from "../../components/wiki/two-select-c.vue";
import { AppCharacterData } from "../../data/index.js";
import Mys from "../../plugins/Mys/index.js";
import { createTGWindow } from "../../utils/TGWindow.js";
import { createObc } from "../../utils/TGWindow.js";
const id = useRoute().params.id.toString() ?? "0";
const showSelect = ref(false);
@@ -55,18 +54,15 @@ const curItem = ref<TGApp.App.Character.WikiBriefInfo>({
onBeforeMount(() => {
if (id === "0") {
curItem.value = cardsInfo.value[0];
} else {
const item = cardsInfo.value.find((item) => item.id.toString() === id);
if (item) {
curItem.value = item;
} else {
showSnackbar({
text: `角色 ${id} 不存在`,
color: "warn",
});
curItem.value = cardsInfo.value[0];
}
return;
}
const item = cardsInfo.value.find((item) => item.id.toString() === id);
if (item) {
curItem.value = item;
return;
}
showSnackbar({ text: `角色 ${id} 不存在`, color: "warn" });
curItem.value = cardsInfo.value[0];
});
watch(resetSelect, (val) => {
@@ -82,16 +78,10 @@ function handleSelect(val: SelectedCValue) {
return val.area.includes(item.area);
});
if (filterC.length === 0) {
showSnackbar({
text: "未找到符合条件的角色",
color: "warn",
});
showSnackbar({ text: "未找到符合条件的角色", color: "warn" });
return;
}
showSnackbar({
text: `筛选出符合条件的角色 ${filterC.length}`,
color: "success",
});
showSnackbar({ text: `筛选出符合条件的角色 ${filterC.length}` });
cardsInfo.value = filterC;
}
@@ -106,10 +96,7 @@ async function switchC(item: TGApp.App.Character.WikiBriefInfo): Promise<void> {
async function toOuter(item?: TGApp.App.Character.WikiBriefInfo): Promise<void> {
if (!item) return;
if (item.contentId === 0) {
showSnackbar({
text: `角色 ${item.name} 暂无观测枢页面`,
color: "warn",
});
showSnackbar({ text: `角色 ${item.name} 暂无观测枢页面`, color: "warn" });
return;
}
const confirm = await showConfirm({
@@ -117,21 +104,10 @@ async function toOuter(item?: TGApp.App.Character.WikiBriefInfo): Promise<void>
text: "是否打开观测枢页面?",
});
if (!confirm) {
showSnackbar({
text: "已取消",
color: "cancel",
});
showSnackbar({ text: "已取消", color: "cancel" });
return;
}
const url = Mys.Api.Obc.replace("{contentId}", item.contentId.toString());
await createTGWindow(
url,
"Sub_window",
`Content_${item.contentId} ${item.name}`,
1200,
800,
true,
);
await createObc(item.contentId, item.name);
}
</script>
<style scoped>

View File

@@ -4,7 +4,7 @@
v-model="search"
prepend-inner-icon="mdi-magnify"
label="搜索"
hide-details
:hide-details="true"
variant="outlined"
@click:prepend-inner="searchNamecard"
@keyup.enter="searchNamecard"

View File

@@ -35,8 +35,7 @@ import TwcListItem from "../../components/wiki/twc-list-item.vue";
import TwcWeapon from "../../components/wiki/twc-weapon.vue";
import TwoSelectW, { SelectedWValue } from "../../components/wiki/two-select-w.vue";
import { AppWeaponData } from "../../data/index.js";
import Mys from "../../plugins/Mys/index.js";
import { createTGWindow } from "../../utils/TGWindow.js";
import { createObc } from "../../utils/TGWindow.js";
const id = useRoute().params.id.toString() ?? "0";
const showSelect = ref(false);
@@ -55,18 +54,15 @@ const curItem = ref<TGApp.App.Weapon.WikiBriefInfo>({
onBeforeMount(() => {
if (id === "0") {
curItem.value = cardsInfo.value[0];
} else {
const item = cardsInfo.value.find((item) => item.id.toString() === id);
if (item) {
curItem.value = item;
} else {
showSnackbar({
text: `武器 ${id} 不存在`,
color: "warn",
});
curItem.value = cardsInfo.value[0];
}
return;
}
const item = cardsInfo.value.find((item) => item.id.toString() === id);
if (item) {
curItem.value = item;
return;
}
showSnackbar({ text: `武器 ${id} 不存在`, color: "warn" });
curItem.value = cardsInfo.value[0];
});
function handleSelectW(val: SelectedWValue) {
@@ -79,15 +75,10 @@ function handleSelectW(val: SelectedWValue) {
return val.weapon.includes(match[1]);
});
if (filterW.length === 0) {
showSnackbar({
text: "未找到符合条件的武器",
color: "warn",
});
showSnackbar({ text: "未找到符合条件的武器", color: "warn" });
return;
}
showSnackbar({
text: `找到 ${filterW.length} 件符合条件的武器`,
});
showSnackbar({ text: `找到 ${filterW.length} 件符合条件的武器` });
cardsInfo.value = filterW;
}
@@ -98,10 +89,7 @@ async function switchW(item: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
async function toOuter(item?: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
if (!item) return;
if (item.contentId === 0) {
showSnackbar({
text: `武器 ${item.name} 暂无观测枢页面`,
color: "warn",
});
showSnackbar({ text: `武器 ${item.name} 暂无观测枢页面`, color: "warn" });
return;
}
const confirm = await showConfirm({
@@ -109,21 +97,10 @@ async function toOuter(item?: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
text: "是否打开观测枢页面?",
});
if (!confirm) {
showSnackbar({
text: "已取消",
color: "cancel",
});
showSnackbar({ text: "已取消", color: "cancel" });
return;
}
const url = Mys.Api.Obc.replace("{contentId}", item.contentId.toString());
await createTGWindow(
url,
"Sub_window",
`Content_${item.contentId} ${item.name}`,
1200,
800,
true,
);
await createObc(item.contentId, item.name);
}
</script>
<style scoped>

View File

@@ -31,16 +31,17 @@
</template>
</v-app-bar>
<div class="wrap">
<div class="left-wrap">
<TuaSeries
v-for="(series, index) in seriesList"
:key="index"
@click="selectSeries(series)"
v-model:cur="selectedSeries"
:uid="uidCur"
:series="series"
/>
</div>
<v-virtual-scroll class="left-wrap" :items="seriesList" item-height="60">
<template #default="{ item }">
<TuaSeries
@click="selectSeries(item)"
v-model:cur="selectedSeries"
:uid="uidCur"
:series="item"
/>
<div style="height: 10px" />
</template>
</v-virtual-scroll>
<TuaAchiList
:uid="uidCur"
:hideFin="hideFin"
@@ -352,12 +353,9 @@ async function deleteUid(): Promise<void> {
}
.left-wrap {
display: flex;
width: 400px;
height: 100%;
flex-direction: column;
padding-right: 10px;
overflow-y: auto;
row-gap: 10px;
}
</style>

View File

@@ -42,22 +42,13 @@
<v-window v-model="tab">
<v-window-item v-for="(value, index) in tabValues" :key="index" :value="value">
<div class="anno-grid">
<v-card :rounded="true" v-for="item in annoCards[value]" :key="item.id">
<div class="anno-cover" :title="item.title">
<img :src="item.banner" alt="cover" @click="createAnno(item)" />
<div class="anno-info">
<div class="anno-time">
<v-icon>mdi-clock-time-four-outline</v-icon>
<span>{{ item.timeStr }}</span>
</div>
</div>
</div>
<div class="anno-title" :title="item.title">{{ parseTitle(item.subtitle) }}</div>
<div class="anno-label" :title="`标签:${item.tagLabel}`">
<img :src="item.tagIcon" alt="tag" />
<span>{{ item.tagLabel }}</span>
</div>
</v-card>
<TAnnocard
v-for="item in annoCards[value]"
:key="item.id"
:model-value="item"
:region="curRegion"
:lang="curLang"
/>
</div>
</v-window-item>
</v-window>
@@ -68,10 +59,10 @@ import { nextTick, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import showSnackbar from "../../components/func/snackbar.js";
import TAnnocard from "../../components/main/t-annocard.vue";
import ToLoading from "../../components/overlay/to-loading.vue";
import { useAppStore } from "../../store/modules/app.js";
import TGLogger from "../../utils/TGLogger.js";
import { createTGWindow } from "../../utils/TGWindow.js";
import { AnnoLang, AnnoServer } from "../../web/request/getAnno.js";
import TGRequest from "../../web/request/TGRequest.js";
import TGUtils from "../../web/utils/TGUtils.js";
@@ -235,22 +226,10 @@ function getAnnoTime(content: string): string | false {
return false;
}
function parseTitle(title: string): string {
const dom = new DOMParser().parseFromString(title, "text/html");
return dom.body.innerText;
}
async function switchNews(): Promise<void> {
await TGLogger.Info("[Announcements][switchNews] 切换米游社咨讯");
await router.push("/news/2");
}
async function createAnno(item: TGApp.App.Announcement.ListCard): Promise<void> {
const annoPath = `/anno_detail/${curRegion.value}/${item.id}/${curLang.value}`;
const annoTitle = `Anno_${item.id} ${item.title}`;
await TGLogger.Info(`[Announcements][createAnno][${item.id}] 打开公告窗口`);
await createTGWindow(annoPath, "Sub_window", annoTitle, 960, 720, false, false);
}
</script>
<style lang="css" scoped>
@@ -292,85 +271,4 @@ async function createAnno(item: TGApp.App.Announcement.ListCard): Promise<void>
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
}
.anno-cover {
position: relative;
display: flex;
overflow: hidden;
width: 100%;
align-items: center;
justify-content: center;
aspect-ratio: 36 / 13;
}
.anno-cover img {
min-width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
transition: all 0.3s linear;
}
.anno-title {
position: relative;
overflow: hidden;
width: 100%;
padding: 5px;
font-size: 18px;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
.anno-info {
position: absolute;
bottom: 0;
left: 0;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: rgb(0 0 0/50%);
font-size: 12px;
}
.anno-time {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 5px;
color: var(--tgc-white-1);
gap: 5px;
}
.anno-label {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px;
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
background: var(--common-shadow-2);
border-bottom-left-radius: 5px;
box-shadow: 0 0 10px var(--tgc-dark-1);
color: var(--tgc-white-1);
text-shadow: 0 0 5px var(--tgc-dark-1);
}
.anno-label img {
width: 20px;
height: 20px;
margin-right: 5px;
}
.anno-cover img:hover {
cursor: pointer;
transform: scale(1.1);
transition: all 0.3s linear;
}
</style>

View File

@@ -103,7 +103,7 @@
<div class="config-right">
<TcAppBadge />
<TcUserBadge @loadOuter="loadHandle" />
<TcGameBadge />
<TcGameBadge v-if="platform() === 'windows'" />
</div>
</template>
@@ -111,6 +111,7 @@
import { core } from "@tauri-apps/api";
import { open } from "@tauri-apps/plugin-dialog";
import { remove } from "@tauri-apps/plugin-fs";
import { platform } from "@tauri-apps/plugin-os";
import { exit } from "@tauri-apps/plugin-process";
import { onMounted, ref } from "vue";

View File

@@ -151,7 +151,7 @@ async function firstLoad(key: NewsKey, refresh: boolean = false): Promise<void>
}
loadingTitle.value = `正在获取${rawData.value[key].name}数据...`;
loading.value = true;
const getData = await Mys.News(gid, NewsType[key]);
const getData = await Mys.Painter.getNewsList(gid, NewsType[key]);
rawData.value[key].isLast = getData.is_last;
rawData.value[key].lastId = getData.list.length;
postData.value[key] = getData.list;

View File

@@ -1,6 +1,13 @@
<template>
<ToLoading v-model="loading" :title="loadingTitle" />
<div class="posts-box">
<v-app-bar>
<template #prepend>
<div class="posts-top">
<img src="/source/UI/posts.png" alt="posts" />
<span>帖子</span>
<!-- todo 提供话题入口 -->
</div>
</template>
<div class="posts-switch">
<v-select
v-model="curGid"
@@ -45,11 +52,13 @@
<span>刷新</span>
</v-btn>
</div>
<TGameNav :model-value="curGid" />
<div class="posts-grid">
<div v-for="post in posts" :key="post.post.post_id">
<TPostCard :model-value="post" v-if="post" />
</div>
<template #extension>
<TGameNav :model-value="curGid" />
</template>
</v-app-bar>
<div class="posts-grid">
<div v-for="post in posts" :key="post.post.post_id">
<TPostCard :model-value="post" v-if="post" />
</div>
</div>
<ToPostSearch :gid="curGid.toString()" v-model="showSearch" :keyword="search" />
@@ -212,12 +221,8 @@ const search = ref<string>("");
const showSearch = ref<boolean>(false);
onBeforeMount(async () => {
if (gid && typeof gid === "string") {
curGid.value = Number(gid);
}
if (forum && typeof forum === "string") {
curForum.value = Number(forum);
}
if (gid && typeof gid === "string") curGid.value = Number(gid);
if (forum && typeof forum === "string") curForum.value = Number(forum);
});
onMounted(async () => {
@@ -247,7 +252,6 @@ watch(
});
},
);
watch(
() => curForum.value,
async () => {
@@ -262,8 +266,6 @@ watch(
}
},
);
// 监听排序变化
watch(
() => curSortType.value,
async (newVal) => {
@@ -285,7 +287,7 @@ async function freshPostData(): Promise<void> {
);
loading.value = true;
loadingTitle.value = `正在加载 ${gameLabel}-${forumLabel}-${sortLabel} 数据`;
const postsGet = await Mys.Posts.get(curForum.value, curSortType.value, 12);
const postsGet = await Mys.Post.getForumPostList(curForum.value, curSortType.value, 12);
posts.value = postsGet.list;
await nextTick();
loading.value = false;
@@ -309,22 +311,34 @@ function searchPost(): void {
}
</script>
<style lang="css" scoped>
.posts-box {
.posts-top {
display: flex;
flex-direction: column;
row-gap: 10px;
align-items: center;
justify-content: center;
gap: 10px;
img {
width: 32px;
height: 32px;
}
span {
color: var(--common-text-title);
font-family: var(--font-title);
font-size: 20px;
}
}
.posts-switch {
display: flex;
align-items: flex-end;
justify-content: flex-start;
padding: 5px;
column-gap: 10px;
justify-content: center;
margin: 0 10px;
gap: 10px;
}
.post-switch-item {
width: fit-content;
width: 250px;
height: 50px;
}

View File

@@ -1,19 +1,17 @@
/**
* @file plugins Hutao api abyss.ts
* @file plugins/Hutao/api/abyss.ts
* @description Hutao API 深渊相关
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
* @since Beta v0.6.2
*/
const BaseUrl = "https://homa.snapgenshin.com/";
const AbyssUrl = `${BaseUrl}Statistics/`;
export const DataUploadApi = `${BaseUrl}Record/Upload?returningRank=false`;
export const UidCheckApi = `${BaseUrl}Record/Check?Uid={uid}`;
export const UidRankApi = `${BaseUrl}Record/Rank?Uid={uid}`;
export const OverviewApi = `${BaseUrl}Statistics/Overview`;
export const AvatarUpRateApi = `${BaseUrl}Statistics/Avatar/AttendanceRate`;
export const AvatarUseRateApi = `${BaseUrl}Statistics/Avatar/UtilizationRate`;
export const AvatarHoldRateApi = `${BaseUrl}Statistics/Avatar/HoldingRate`;
export const AvatarCollocationApi = `${BaseUrl}Statistics/Avatar/AvatarCollocation`;
export const WeaponCollocationApi = `${BaseUrl}Statistics/Weapon/WeaponCollocation`;
export const TeamCombinationApi = `${BaseUrl}Statistics/Team/Combination`;
export const OverviewApi = `${AbyssUrl}Overview`;
export const AvatarUpRateApi = `${AbyssUrl}Avatar/AttendanceRate`;
export const AvatarUseRateApi = `${AbyssUrl}Avatar/UtilizationRate`;
export const AvatarHoldRateApi = `${AbyssUrl}Avatar/HoldingRate`;
export const AvatarCollocationApi = `${AbyssUrl}Avatar/AvatarCollocation`;
export const WeaponCollocationApi = `${AbyssUrl}Weapon/WeaponCollocation`;
export const TeamCombinationApi = `${AbyssUrl}Team/Combination`;

View File

@@ -1,7 +1,6 @@
/**
* @file plugins Hutao api index.ts
* @file plugins/Hutao/api/index.ts
* @description Hutao API
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
@@ -13,18 +12,12 @@ import {
DataUploadApi,
OverviewApi,
TeamCombinationApi,
UidCheckApi,
UidRankApi,
WeaponCollocationApi,
} from "./abyss.js";
const HutaoApi = {
Abyss: {
upload: DataUploadApi,
user: {
check: UidCheckApi,
rank: UidRankApi,
},
overview: OverviewApi,
avatar: {
upRate: AvatarUpRateApi,

View File

@@ -1,8 +1,7 @@
/**
* @file plugins Hutao index.ts
* @file plugins/Hutao/index.ts
* @description Hutao 插件入口
* @author BTMuli <bt-muli@outlook.com>
* @since Beta v0.3.0
* @since Beta v0.6.2
*/
import getAvatarCollect from "./request/getAvatarCollect.js";
@@ -11,7 +10,6 @@ import getAvatarUpRate from "./request/getAvatarUpRate.js";
import getAvatarUseRate from "./request/getAvatarUseRate.js";
import getOverview from "./request/getOverview.js";
import getTeamCollect from "./request/getTeamCollect.js";
import getWeaponCollect from "./request/getWeaponCollect.js";
import uploadData from "./request/uploadData.js";
import { transAvatars, transLocal } from "./utils/transLocal.js";
@@ -25,7 +23,6 @@ const Hutao = {
},
getOverview,
getTeamCollect,
getWeaponCollect,
postData: uploadData,
utils: {
transData: transLocal,

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/request/getAvatarCollect.ts
* @description 获取角色搭配数据
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
@@ -9,13 +9,17 @@ import HutaoApi from "../api/index.js";
/**
* @description 获取角色搭配数据
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {boolean} isLast 是否获取上期数据
* @return {Promise<TGApp.Plugins.Hutao.Abyss.AvatarCollocation[]>}
*/
async function getAvatarCollect(): Promise<TGApp.Plugins.Hutao.Abyss.AvatarCollocation[]> {
async function getAvatarCollect(
isLast: boolean = false,
): Promise<TGApp.Plugins.Hutao.Abyss.AvatarCollocation[]> {
const url = HutaoApi.Abyss.avatar.collect;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarCollocationResponse>(url, {
method: "GET",
query: { Last: isLast },
});
return resp.data;
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/request/getAvatarHoldRate.ts
* @description Hutao API 获取角色持有率数据请求方法
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
@@ -9,12 +9,18 @@ import HutaoApi from "../api/index.js";
/**
* @description 获取角色持有率数据
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {boolean} isLast 是否获取上期数据
* @returns {Promise<TGApp.Plugins.Hutao.Abyss.AvatarHold[]>}
*/
async function getAvatarHoldRate(): Promise<TGApp.Plugins.Hutao.Abyss.AvatarHold[]> {
async function getAvatarHoldRate(
isLast: boolean = false,
): Promise<TGApp.Plugins.Hutao.Abyss.AvatarHold[]> {
const url = HutaoApi.Abyss.avatar.holdRate;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarHoldResponse>(url, { method: "GET" });
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarHoldResponse>(url, {
method: "GET",
query: { Last: isLast },
});
return resp.data;
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/request/getAvatarUpRate.ts
* @description 获取角色上场率数据
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
@@ -9,12 +9,18 @@ import HutaoApi from "../api/index.js";
/**
* @description 获取角色上场率数据
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {boolean} isLast 是否获取上期数据
* @return {Promise<TGApp.Plugins.Hutao.Abyss.AvatarUp[]>}
*/
async function getAvatarUpRate(): Promise<TGApp.Plugins.Hutao.Abyss.AvatarUp[]> {
async function getAvatarUpRate(
isLast: boolean = false,
): Promise<TGApp.Plugins.Hutao.Abyss.AvatarUp[]> {
const url = HutaoApi.Abyss.avatar.upRate;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarUpResponse>(url, { method: "GET" });
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarUpResponse>(url, {
method: "GET",
query: { Last: isLast },
});
return resp.data;
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/request/getAvatarUseRate.ts
* @description 获取角色使用率
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
@@ -9,12 +9,18 @@ import HutaoApi from "../api/index.js";
/**
* @description 获取角色使用率
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {boolean} isLast 是否获取上期数据
* @return {Promise<TGApp.Plugins.Hutao.Abyss.AvatarUse[]>}
*/
async function getAvatarUseRate(): Promise<TGApp.Plugins.Hutao.Abyss.AvatarUse[]> {
async function getAvatarUseRate(
isLast: boolean = false,
): Promise<TGApp.Plugins.Hutao.Abyss.AvatarUse[]> {
const url = HutaoApi.Abyss.avatar.useRate;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarUseResponse>(url, { method: "GET" });
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.AvatarUseResponse>(url, {
method: "GET",
query: { Last: isLast },
});
return resp.data;
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/request/getOverview.ts
* @description 获取深渊概览数据
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
@@ -9,12 +9,18 @@ import HutaoApi from "../api/index.js";
/**
* @description 获取深渊概览数据
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {boolean} isLast 是否获取上期数据
* @return {Promise<TGApp.Plugins.Hutao.Abyss.OverviewData>}
*/
async function getOverview(): Promise<TGApp.Plugins.Hutao.Abyss.OverviewData> {
async function getOverview(
isLast: boolean = false,
): Promise<TGApp.Plugins.Hutao.Abyss.OverviewData> {
const url = HutaoApi.Abyss.overview;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.OverviewResponse>(url, { method: "GET" });
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.OverviewResponse>(url, {
method: "GET",
query: { Last: isLast },
});
return resp.data;
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Hutao/request/getTeamCollect.ts
* @description 获取队伍搭配数据
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
@@ -9,13 +9,17 @@ import HutaoApi from "../api/index.js";
/**
* @description 获取队伍搭配数据
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {boolean} isLast 是否获取上期数据
* @return {Promise<TGApp.Plugins.Hutao.Abyss.TeamCombination[]>}
*/
async function getTeamCollect(): Promise<TGApp.Plugins.Hutao.Abyss.TeamCombination[]> {
async function getTeamCollect(
isLast: boolean = false,
): Promise<TGApp.Plugins.Hutao.Abyss.TeamCombination[]> {
const url = HutaoApi.Abyss.team;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.TeamCombinationResponse>(url, {
method: "GET",
query: { Last: isLast },
});
return resp.data;
}

View File

@@ -1,23 +0,0 @@
/**
* @file plugins/Hutao/request/getWeaponCollect.ts
* @description 获取武器搭配
* @since Beta v0.5.0
*/
import TGHttp from "../../../utils/TGHttp.js";
import HutaoApi from "../api/index.js";
/**
* @description 获取武器搭配
* @since Beta v0.5.0
* @return {Promise<TGApp.Plugins.Hutao.Abyss.WeaponCollocation[]>}
*/
async function getWeaponCollect(): Promise<TGApp.Plugins.Hutao.Abyss.WeaponCollocation[]> {
const url = HutaoApi.Abyss.weapon;
const resp = await TGHttp<TGApp.Plugins.Hutao.Abyss.WeaponCollocationResponse>(url, {
method: "GET",
});
return resp.data;
}
export default getWeaponCollect;

View File

@@ -1,12 +0,0 @@
/**
* @file plugins/Mys/api/index.ts
* @description Mys API
* @since Beta v0.4.5
*/
const MysApi = {
Obc: "https://bbs.mihoyo.com/ys/obc/content/{contentId}/detail?bbs_presentation_style=no_header",
PostReferer: "https://bbs.mihoyo.com/",
};
export default MysApi;

View File

@@ -1,41 +1,24 @@
/**
* @file plugins/Mys/index.ts
* @description Mys plugin index
* @since Beta v0.5.5
* @since Beta v0.6.2
*/
import MysApi from "./api/index.js";
import * as ApiHub from "./request/apiHubReq.js";
import { getCaptcha, doCaptchaLogin } from "./request/doCaptchaLogin.js";
import { getLoginQr, getLoginStatus } from "./request/doGameLogin.js";
import { getCollectionPosts } from "./request/getCollectionData.js";
import getForumList from "./request/getForumList.js";
import getGachaData from "./request/getGachaData.js";
import getHomeNavigator from "./request/getHomeNavigator.js";
import getLotteryData from "./request/getLotteryData.js";
import getNewsList from "./request/getNewsList.js";
import { getPositionData } from "./request/getPositionData.js";
import getPostData from "./request/getPostData.js";
import { getPostReply, getPostSubRoot, getPostSubReply } from "./request/getPostReply.js";
import { getVoteInfo, getVoteResult } from "./request/getVoteData.js";
import searchPosts from "./request/searchPost.js";
import * as Painter from "./request/painterReq.js";
import * as Post from "./request/postReq.js";
import { getGachaCard } from "./utils/getGachaCard.js";
import getLotteryCard from "./utils/getLotteryCard.js";
import getPositionCard from "./utils/getPositionCard.js";
const Mys = {
Api: MysApi,
Post: {
get: getPostData,
reply: getPostReply,
replySubRoot: getPostSubRoot,
replySub: getPostSubReply,
},
PostCollect: getCollectionPosts,
Posts: {
get: getForumList,
nav: getHomeNavigator,
search: searchPosts,
},
Post,
Painter,
ApiHub,
Gacha: {
get: getGachaData,
card: getGachaCard,
@@ -44,9 +27,8 @@ const Mys = {
get: getPositionData,
card: getPositionCard,
},
News: getNewsList,
Lottery: {
get: getLotteryData,
get: Painter.lotteryUserShow,
card: getLotteryCard,
},
User: {
@@ -55,10 +37,6 @@ const Mys = {
getCaptcha,
login: doCaptchaLogin,
},
Vote: {
get: getVoteInfo,
result: getVoteResult,
},
};
export default Mys;

View File

@@ -0,0 +1,64 @@
/**
* @file plugins/Mys/request/apiHubReq.ts
* @description apiHub下的请求
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
// MysApiHubApiBaseUrl => Mahabu
const Mahabu = "https://bbs-api.miyoushe.com/apihub/api/";
const Referer = "https://bbs.mihoyo.com/";
/**
* @description 获取投票信息
* @since Beta v0.6.2
* @param {string} id 投票 ID
* @param {string} uid 用户 ID
* @return {Promise<TGApp.Plugins.Mys.Vote.Info>}
*/
export async function getVotes(id: string, uid: string): Promise<TGApp.Plugins.Mys.Vote.Info> {
return (
await TGHttp<TGApp.Plugins.Mys.Vote.InfoResponse>(`${Mahabu}getVotes`, {
method: "GET",
headers: { "Content-Type": "application/json", referer: Referer },
query: { owner_uid: uid, vote_ids: id },
})
).data.data[0];
}
/**
* @description 获取投票结果
* @since Beta v0.6.2
* @param {string} id 投票 ID
* @param {string} uid 用户 ID
* @return {Promise<TGApp.Plugins.Mys.Vote.Result>}
*/
export async function getVoteResult(
id: string,
uid: string,
): Promise<TGApp.Plugins.Mys.Vote.Result> {
return (
await TGHttp<TGApp.Plugins.Mys.Vote.ResultResponse>(`${Mahabu}getVotesResult`, {
method: "GET",
headers: { "Content-Type": "application/json", referer: Referer },
query: { owner_uid: uid, vote_ids: id },
})
).data.data[0];
}
/**
* @description 获取首页导航列表
* @since Beta v0.6.2
* @param {number} gid GID
* @return {Promise<TGApp.BBS.Navigator.Navigator[]>}
*/
export async function homeNew(gid: number = 2): Promise<TGApp.BBS.Navigator.Navigator[]> {
return (
await TGHttp<TGApp.BBS.Navigator.HomeResponse>(`${Mahabu}home/new`, {
method: "GET",
headers: { "x-rpc-client_type": "2" },
query: { gids: gid },
})
).data.navigator;
}

View File

@@ -1,27 +0,0 @@
/**
* @file plugins/Mys/request/getCollectionPosts.ts
* @description Mys 获取合集帖子
* @since Beta v0.5.5
*/
import TGHttp from "../../../utils/TGHttp.js";
import MysApi from "../api/index.js";
/**
* @description 获取合集帖子
* @since Beta v0.5.0
* @param {string} collectionId 合集 ID
* @returns {Promise<TGApp.Plugins.Mys.Post.FullData[]>}
*/
export async function getCollectionPosts(
collectionId: string,
): Promise<TGApp.Plugins.Mys.Collection.Data[]> {
const url = "https://bbs-api.miyoushe.com/post/wapi/getPostFullInCollection";
const params = { collection_id: collectionId };
const resp = await TGHttp<TGApp.Plugins.Mys.Collection.ResponsePosts>(url, {
method: "GET",
query: params,
headers: { "Content-Type": "application/json", Referer: MysApi.PostReferer },
});
return resp.data.posts;
}

View File

@@ -1,35 +0,0 @@
/**
* @file plugins/Mys/request/getForumList.ts
* @description Mys 插件特定论坛请求
* @since Beta v0.5.1
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 获取特定论坛列表
* @since Beta v0.5.1
* @param {number} forumId 特定论坛 ID
* @param {number} type 排序方式: 0-按热度排序1-最新回复2-按时间排序
* @param {number} page_size 每页数量
* @return {Promise<TGApp.Plugins.Mys.Forum.FullData>}
*/
async function getForumList(
forumId: number,
type: number = 0,
page_size: number = 20,
): Promise<TGApp.Plugins.Mys.Forum.FullData> {
const url = "https://bbs-api.miyoushe.com/post/wapi/getForumPostList";
const params = {
forum_id: forumId.toString(),
sort_type: type.toString(),
page_size: page_size.toString(),
};
const resp = await TGHttp<TGApp.Plugins.Mys.Forum.Response>(url, {
method: "GET",
query: params,
});
return resp.data;
}
export default getForumList;

View File

@@ -1,27 +0,0 @@
/**
* @file plugins/Mys/request/getHomeNavigator.ts
* @description Mys 插件首页导航请求
* @since Beta v0.5.1
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 获取首页导航列表
* @since Beta v0.5.1
* @param {number} gid GID
* @return {Promise<TGApp.BBS.Navigator.Navigator[]>}
*/
async function getHomeNavigator(gid: number = 2): Promise<TGApp.BBS.Navigator.Navigator[]> {
const url = "https://bbs-api.miyoushe.com/apihub/api/home/new";
const params = { gids: gid.toString() };
const header = { "x-rpc-client_type": "2" };
const resp = await TGHttp<TGApp.BBS.Navigator.HomeResponse>(url, {
method: "GET",
headers: header,
query: params,
});
return resp.data.navigator;
}
export default getHomeNavigator;

View File

@@ -1,29 +0,0 @@
/**
* @file plugins/Mys/request/getLotteryData.ts
* @description Mys 插件抽奖接口
* @since Beta v0.5.0
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 获取抽奖信息
* @since Beta v0.5.0
* @param {string} lotteryId 抽奖 ID
* @return {Promise<TGApp.BBS.Response.Base|TGApp.Plugins.Mys.Lottery.FullData>}
*/
async function getLotteryData(
lotteryId: string,
): Promise<TGApp.BBS.Response.Base | TGApp.Plugins.Mys.Lottery.FullData> {
const url = "https://bbs-api.miyoushe.com/painter/wapi/lottery/user/show";
const params = { id: lotteryId };
const resp = await TGHttp<TGApp.BBS.Response.Base | TGApp.Plugins.Mys.Lottery.Response>(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
query: params,
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.show_lottery;
}
export default getLotteryData;

View File

@@ -1,39 +0,0 @@
/**
* @file plugins/Mys/request/getNewsList.ts
* @description Mys 插件咨讯请求
* @since Beta v0.5.0
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 获取 News 列表
* @since Beta v0.5.0
* @param {string} gid GID
* @param {string} newsType 咨讯类型: 1 为公告2 为活动3 为咨讯
* @param {number} pageSize 返回数量
* @param {number} lastId 上一次请求的最后一条数据的 id
* @return {Promise<TGApp.Plugins.Mys.News.FullData>}
*/
async function getNewsList(
gid: string = "2",
newsType: string = "1",
pageSize: number = 20,
lastId: number = 0,
): Promise<TGApp.Plugins.Mys.News.FullData> {
const url = "https://bbs-api.mihoyo.com/post/wapi/getNewsList";
const params = {
gids: gid,
page_size: pageSize.toString(),
type: newsType,
last_id: lastId.toString(),
};
const resp = await TGHttp<TGApp.Plugins.Mys.News.Response>(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
query: params,
});
return resp.data;
}
export default getNewsList;

View File

@@ -1,27 +0,0 @@
/**
* @file plugins Mys request getPostData.ts
* @description Mys帖子请求
* @since Beta v0.5.0
*/
import TGHttp from "../../../utils/TGHttp.js";
import MysApi from "../api/index.js";
/**
* @description 获取帖子信息
* @since Beta v0.5.0
* @param {number} postId 帖子 ID
* @return {Promise<TGApp.Plugins.Mys.Post.FullData>}
*/
async function getPostData(postId: number): Promise<TGApp.Plugins.Mys.Post.FullData> {
const url = "https://bbs-api.mihoyo.com/post/wapi/getPostFull";
const params = { post_id: postId.toString() };
const resp = await TGHttp<TGApp.Plugins.Mys.Post.Response>(url, {
method: "GET",
headers: { referer: MysApi.PostReferer },
query: params,
});
return resp.data.post;
}
export default getPostData;

View File

@@ -1,115 +0,0 @@
/**
* @file plugins/Mys/request/getPostReply.ts
* @description Mys 插件帖子回复请求
* @since Beta v0.5.5
*/
import TGHttp from "../../../utils/TGHttp.js";
import MysApi from "../api/index.js";
/**
* @description 获取帖子回复信息
* @since Beta v0.5.5
* @param {string} postId 帖子 ID
* @param {number} gid 社区 ID
* @param {boolean} isHot 是否热门
* @param {boolean} onlyMaster 是否只看楼主
* @param {number} orderType 排序类型
* @param {string} lastId 最后 ID
* @param {number} size 每页大小
* @return {Promise<TGApp.Plugins.Mys.Reply.ReplyData|TGApp.BBS.Response.Base>}
*/
export async function getPostReply(
postId: string,
gid: number,
isHot: boolean = true,
lastId?: string,
onlyMaster: boolean = false,
orderType?: 1 | 2,
size: number = 20,
): Promise<TGApp.Plugins.Mys.Reply.ReplyData | TGApp.BBS.Response.Base> {
const params: Record<string, string | number | boolean> = {
post_id: postId,
gids: gid,
is_hot: isHot,
size: size,
};
if (lastId) {
params["last_id"] = lastId;
}
if (orderType) {
params["order_type"] = orderType;
}
if (onlyMaster) {
params["is_hot"] = false;
params["only_master"] = onlyMaster;
}
const link = "https://bbs-api.miyoushe.com/post/wapi/getPostReplies";
const resp = await TGHttp<TGApp.Plugins.Mys.Reply.Response>(link, {
method: "GET",
headers: { referer: MysApi.PostReferer },
query: params,
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 获取帖子子回复根信息
* @since Beta v0.5.5
* @param {number} gid 社区 ID
* @param {string} postId 帖子 ID
* @param {string} replyId 回复 ID
* @return {Promise<TGApp.Plugins.Mys.Reply.SubRootData|TGApp.BBS.Response.Base>}
*/
export async function getPostSubRoot(
gid: number,
postId: string,
replyId: string,
): Promise<TGApp.Plugins.Mys.Reply.SubRootData | TGApp.BBS.Response.Base> {
const link = "https://bbs-api.miyoushe.com/post/wapi/getRootReplyInfo";
const params = { gids: gid, post_id: postId, reply_id: replyId };
const resp = await TGHttp<TGApp.Plugins.Mys.Reply.SubRootResponse>(link, {
method: "GET",
headers: { referer: MysApi.PostReferer },
query: params,
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 获取帖子子回复信息
* @since Beta v0.5.5
* @param {number} floorId 楼层 ID
* @param {number} gid 社区 ID
* @param {string} postId 帖子 ID
* @param {string} lastId 最后 ID
* @param {number} size 每页大小
* @return {Promise<TGApp.Plugins.Mys.Reply.SubData|TGApp.BBS.Response.Base>}
*/
export async function getPostSubReply(
floorId: number,
gid: number,
postId: string,
lastId?: string,
size: number = 20,
): Promise<TGApp.Plugins.Mys.Reply.SubData | TGApp.BBS.Response.Base> {
const params: Record<string, string | number> = {
floor_id: floorId,
gids: gid,
post_id: postId,
size: size,
};
if (lastId) {
params["last_id"] = lastId;
}
const link = "https://bbs-api.miyoushe.com/post/wapi/getSubReplies";
const resp = await TGHttp<TGApp.Plugins.Mys.Reply.SubResponse>(link, {
method: "GET",
headers: { referer: MysApi.PostReferer },
query: params,
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}

View File

@@ -1,47 +0,0 @@
/**
* @file plugins/Mys/request/getVoteData.ts
* @description Mys 插件投票请求
* @since Beta v0.5.0
*/
import TGHttp from "../../../utils/TGHttp.js";
import MysApi from "../api/index.js";
/**
* @description 获取投票信息
* @since Beta v0.5.0
* @param {string} id 投票 ID
* @param {string} uid 用户 ID
* @return {Promise<TGApp.Plugins.Mys.Vote.Info>}
*/
export async function getVoteInfo(id: string, uid: string): Promise<TGApp.Plugins.Mys.Vote.Info> {
const url = "https://bbs-api.miyoushe.com/apihub/api/getVotes";
const params = { owner_uid: uid, vote_ids: id };
const resp = await TGHttp<TGApp.Plugins.Mys.Vote.InfoResponse>(url, {
method: "GET",
headers: { "Content-Type": "application/json", Referer: MysApi.PostReferer },
query: params,
});
return resp.data.data[0];
}
/**
* @description 获取投票结果
* @since Beta v0.5.0
* @param {string} id 投票 ID
* @param {string} uid 用户 ID
* @return {Promise<TGApp.Plugins.Mys.Vote.Result>}
*/
export async function getVoteResult(
id: string,
uid: string,
): Promise<TGApp.Plugins.Mys.Vote.Result> {
const url = "https://bbs-api.miyoushe.com/apihub/api/getVotesResult";
const params = { owner_uid: uid, vote_ids: id };
const resp = await TGHttp<TGApp.Plugins.Mys.Vote.ResultResponse>(url, {
method: "GET",
headers: { "Content-Type": "application/json", Referer: MysApi.PostReferer },
query: params,
});
return resp.data.data[0];
}

View File

@@ -0,0 +1,54 @@
/**
* @file plugins/Mys/request/painterReq.ts
* @description painter下的请求
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
// MysPainterApiBaseUrl => Mpabu
const Mpabu = "https://bbs-api.miyoushe.com/painter/wapi/";
/**
* @description 获取 News 列表
* @since Beta v0.6.2
* @param {string} gid GID
* @param {string} newsType 咨讯类型: 1 为公告2 为活动3 为咨讯
* @param {number} pageSize 返回数量
* @param {number} lastId 上一次请求的最后一条数据的 id
* @return {Promise<TGApp.Plugins.Mys.News.FullData>}
*/
export async function getNewsList(
gid: string = "2",
newsType: string = "1",
pageSize: number = 20,
lastId: number = 0,
): Promise<TGApp.Plugins.Mys.News.FullData> {
return (
await TGHttp<TGApp.Plugins.Mys.News.Response>(`${Mpabu}getNewsList`, {
method: "GET",
headers: { "Content-Type": "application/json" },
query: { gids: gid, page_size: pageSize, type: newsType, last_id: lastId },
})
).data;
}
/**
* @description 获取抽奖信息
* @since Beta v0.6.2
* @param {string} lotteryId 抽奖 ID
* @return {Promise<TGApp.BBS.Response.Base|TGApp.Plugins.Mys.Lottery.FullData>}
*/
export async function lotteryUserShow(
lotteryId: string,
): Promise<TGApp.BBS.Response.Base | TGApp.Plugins.Mys.Lottery.FullData> {
const resp = await TGHttp<TGApp.BBS.Response.Base | TGApp.Plugins.Mys.Lottery.Response>(
`${Mpabu}lottery/user/show`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
query: { id: lotteryId },
},
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.show_lottery;
}

View File

@@ -0,0 +1,169 @@
/**
* @file plugins/Mys/request/postReq.ts
* @description 帖子相关的获取
* @since Beta v0.6.2
*/
import TGHttp from "../../../utils/TGHttp.js";
// MysPostApiBaseUrl => Mpabu
const Mpabu = "https://bbs-api.mihoyo.com/post/wapi/";
const Referer = "https://bbs.mihoyo.com/";
/**
* @description 获取特定论坛列表
* @since Beta v0.6.2
* @param {number} forumId 特定论坛 ID
* @param {number} type 排序方式: 0-按热度排序1-最新回复2-按时间排序
* @param {number} page_size 每页数量
* @return {Promise<TGApp.Plugins.Mys.Forum.FullData>}
*/
export async function getForumPostList(
forumId: number,
type: number = 0,
page_size: number = 20,
): Promise<TGApp.Plugins.Mys.Forum.FullData> {
return (
await TGHttp<TGApp.Plugins.Mys.Forum.Response>(`${Mpabu}getForumPostList`, {
method: "GET",
query: { forum_id: forumId, sort_type: type, page_size: page_size },
})
).data;
}
/**
* @description 获取单个帖子信息
* @since Beta v0.6.2
* @param {number} postId 帖子 ID
* @return {Promise<TGApp.Plugins.Mys.Post.FullData>}
*/
export async function getPostFull(postId: number): Promise<TGApp.Plugins.Mys.Post.FullData> {
return (
await TGHttp<TGApp.Plugins.Mys.Post.Response>(`${Mpabu}getPostFull`, {
method: "GET",
headers: { referer: Referer },
query: { post_id: postId },
})
).data.post;
}
/**
* @description 获取合集帖子
* @since Beta v0.6.2
* @param {string} collectionId 合集 ID
* @returns {Promise<TGApp.Plugins.Mys.Post.FullData[]>}
*/
export async function getPostFullInCollection(
collectionId: string,
): Promise<TGApp.Plugins.Mys.Collection.Data[]> {
return (
await TGHttp<TGApp.Plugins.Mys.Collection.ResponsePosts>(`${Mpabu}getPostFullInCollection`, {
method: "GET",
headers: { "Content-Type": "application/json", referer: Referer },
query: { collection_id: collectionId },
})
).data.posts;
}
/**
* @description 获取帖子回复信息
* @since Beta v0.6.2
* @param {string} postId 帖子 ID
* @param {number} gid 社区 ID
* @param {boolean} isHot 是否热门
* @param {boolean} onlyMaster 是否只看楼主
* @param {number} orderType 排序类型
* @param {string} lastId 最后 ID
* @param {number} size 每页大小
* @return {Promise<TGApp.Plugins.Mys.Reply.ReplyData|TGApp.BBS.Response.Base>}
*/
export async function getPostReplies(
postId: string,
gid: number,
isHot: boolean = true,
lastId?: string,
onlyMaster: boolean = false,
orderType?: 1 | 2,
size: number = 20,
): Promise<TGApp.Plugins.Mys.Reply.ReplyData | TGApp.BBS.Response.Base> {
type GprParam = {
post_id: string;
gids: number;
is_hot: boolean;
size: number;
last_id?: string;
order_type?: 1 | 2;
only_master?: boolean;
};
const params: GprParam = { post_id: postId, gids: gid, is_hot: isHot, size: size };
if (lastId) params.last_id = lastId;
if (orderType) params.order_type = orderType;
if (onlyMaster) {
params.is_hot = false;
params.only_master = onlyMaster;
}
const resp = await TGHttp<TGApp.Plugins.Mys.Reply.Response>(`${Mpabu}getPostReplies`, {
method: "GET",
headers: { referer: Referer },
query: params,
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 获取帖子子回复信息
* @since Beta v0.6.2
* @param {number} floorId 楼层 ID
* @param {number} gid 社区 ID
* @param {string} postId 帖子 ID
* @param {string} lastId 最后 ID
* @param {number} size 每页大小
* @return {Promise<TGApp.Plugins.Mys.Reply.SubData|TGApp.BBS.Response.Base>}
*/
export async function getSubReplies(
floorId: number,
gid: number,
postId: string,
lastId?: string,
size: number = 20,
): Promise<TGApp.Plugins.Mys.Reply.SubData | TGApp.BBS.Response.Base> {
type GsrParam = {
floor_id: number;
gids: number;
post_id: string;
size: number;
last_id?: string;
};
const params: GsrParam = { floor_id: floorId, gids: gid, post_id: postId, size: size };
if (lastId) params.last_id = lastId;
const resp = await TGHttp<TGApp.Plugins.Mys.Reply.SubResponse>(`${Mpabu}getSubReplies`, {
method: "GET",
headers: { referer: Referer },
query: params,
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
/**
* @description 搜索帖子
* @since Beta v0.6.2
* @param {string} gid 游戏分区 ID
* @param {string} keyword 关键词
* @param {string} lastId 最后一条帖子 ID
* @return {Promise<TGApp.Plugins.Mys.Search.PostsResponseData>} 返回帖子列表
*/
export async function searchPosts(
gid: string = "2",
keyword: string,
lastId: string,
): Promise<TGApp.Plugins.Mys.Search.PostsResponseData> {
return (
await TGHttp<TGApp.Plugins.Mys.Search.PostsResponse>(`${Mpabu}searchPosts`, {
method: "GET",
headers: { "Content-Type": "application/json" },
query: { gids: gid, keyword, last_id: lastId, size: 20 },
})
).data;
}

View File

@@ -1,32 +0,0 @@
/**
* @file plugins/Mys/request/searchPost.ts
* @description 帖子搜索
* @since Beta v0.5.0
*/
import TGHttp from "../../../utils/TGHttp.js";
/**
* @description 搜索帖子
* @since Beta v0.5.0
* @param {string} gid 游戏分区 ID
* @param {string} keyword 关键词
* @param {string} last_id 最后一条帖子 ID
* @return {Promise<TGApp.Plugins.Mys.Search.PostsResponseData>} 返回帖子列表
*/
async function searchPosts(
gid: string = "2",
keyword: string,
last_id: string,
): Promise<TGApp.Plugins.Mys.Search.PostsResponseData> {
const url = "https://bbs-api.miyoushe.com/post/wapi/searchPosts";
const params = { gids: gid, keyword, last_id, size: "20" };
const resp = await TGHttp<TGApp.Plugins.Mys.Search.PostsResponse>(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
query: params,
});
return resp.data;
}
export default searchPosts;

View File

@@ -1,15 +1,15 @@
/**
* @file plugins/Mys/utils/getGachaCard.ts
* @description Mys 插件抽卡工具
* @since Beta v0.5.0
* @since Beta v0.6.2
*/
import { AppCharacterData } from "../../../data/index.js";
import getPostData from "../request/getPostData.js";
import { getPostFull } from "../request/postReq.js";
/**
* @description 根据单个卡池信息转为渲染用的卡池信息
* @since Beta v0.5.0
* @since Beta v0.6.2
* @param {TGApp.Plugins.Mys.Gacha.Data} data 卡池信息
* @param {string} poolCover 卡池封面
* @returns {Promise<TGApp.Plugins.Mys.Gacha.RenderCard>}
@@ -28,7 +28,7 @@ async function getGachaItemCard(
} else {
try {
console.log("调用 getPostData");
const post = await getPostData(postId);
const post = await getPostFull(postId);
cover = post.cover?.url ?? post.post.images[0];
} catch (error) {
console.error(error);

View File

@@ -1,14 +1,12 @@
/**
* @file plugins/Mys/utils/getPositionCard.ts
* @description Mys 插件热点追踪工具
* @since Beta v0.5.3
* @since Beta v0.6.2
*/
import Mys from "../index.js";
/**
* @description 根据热点追踪信息转为渲染用的数据
* @since Beta v0.5.3
* @since Beta v0.6.2
* @param {TGApp.Plugins.Mys.Position.Data[]} positionData 列表
* @returns {TGApp.Plugins.Mys.Position.RenderCard[]} 返回列表
*/
@@ -25,7 +23,7 @@ function getPositionCard(
}
let link = position.url;
if (position.url === "" && position.content_id !== 0) {
link = Mys.Api.Obc.replace("{contentId}", position.content_id.toString());
link = `https://bbs.mihoyo.com/ys/obc/content/${position.content_id}/detail?bbs_presentation_style=no_header`;
}
const card: TGApp.Plugins.Mys.Position.RenderCard = {
title: position.title,

View File

@@ -1,12 +1,14 @@
/**
* @file plugins/Sqlite/index.ts
* @description Sqlite 数据库操作类
* @since Beta v0.6.0
* @since Beta v0.6.1
*/
import { app } from "@tauri-apps/api";
import Database from "@tauri-apps/plugin-sql";
import TGLogger from "../../utils/TGLogger.js";
import initDataSql from "./sql/initData.js";
import { insertAppData } from "./sql/insertData.js";
@@ -55,6 +57,27 @@ class Sqlite {
return this.db;
}
/**
* @description 检测是否需要创建数据库
* @since Beta v0.6.1
* @returns {Promise<boolean>}
*/
public async check(): Promise<boolean> {
try {
const db = await this.getDB();
let isVerified = false;
const sqlT = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;";
const res: Array<{ name: string }> = await db.select(sqlT);
if (this.tables.every((item) => res.map((i) => i.name).includes(item))) {
isVerified = true;
}
return isVerified;
} catch (e) {
await TGLogger.Error(JSON.stringify(e));
return false;
}
}
/**
* @description 初始化数据库
* @since Beta v0.4.5
@@ -116,6 +139,24 @@ class Sqlite {
for (const item of sqlD) {
await db.execute(item);
}
// 检测是否存在字段
await this.updateAbyss();
}
/**
* @description 更新 SpiralAbyss 表
* @since Beta v0.6.1
* @returns {Promise<void>}
*/
public async updateAbyss(): Promise<void> {
const db = await this.getDB();
try {
await db.select("SELECT skippedFloor FROM SpiralAbyss;");
} catch (e) {
await TGLogger.Error(JSON.stringify(e));
const sql = "ALTER TABLE SpiralAbyss ADD skippedFloor TEXT DEFAULT ''";
await db.execute(sql);
}
}
/**

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/modules/userAbyss.ts
* @description Sqlite-用户深渊模块
* @since Beta v0.6.0
* @since Beta v0.6.1
*/
import { path } from "@tauri-apps/api";
@@ -50,7 +50,7 @@ function getRestoreSql(data: TGApp.Sqlite.Abyss.SingleTable): string {
/**
* @description 获取深渊数据的插入更新Sql
* @since Beta v0.6.0
* @since Beta v0.6.1
* @param {string} uid - 用户UID
* @param {TGApp.Game.Abyss.FullData} data -深渊数据
* @returns {string}
@@ -66,15 +66,16 @@ function getInsertSql(uid: string, data: TGApp.Game.Abyss.FullData): string {
const normalSkillRank = transCharacterData(data.normal_skill_rank);
const energySkillRank = transCharacterData(data.energy_skill_rank);
const floors = transFloorData(data.floors);
const skippedFloor = data.skipped_floor;
const timeNow = timestampToDate(new Date().getTime());
return `
INSERT INTO SpiralAbyss (uid, id, startTime, endTime, totalBattleTimes, totalWinTimes,
maxFloor, totalStar, isUnlock, revealRank, defeatRank, damageRank,
takeDamageRank, normalSkillRank, energySkillRank, floors, updated)
INSERT INTO SpiralAbyss (uid, id, startTime, endTime, totalBattleTimes, totalWinTimes, maxFloor,
totalStar, isUnlock, revealRank, defeatRank, damageRank, takeDamageRank,
normalSkillRank, energySkillRank, floors, skippedFloor, updated)
VALUES ('${uid}', ${data.schedule_id}, '${startTime}', '${endTime}', ${data.total_battle_times},
${data.total_win_times}, '${data.max_floor}', ${data.total_star},
${isUnlock}, '${revealRank}', '${defeatRank}', '${damageRank}', '${takeDamageRank}',
'${normalSkillRank}', '${energySkillRank}', '${floors}', '${timeNow}')
${data.total_win_times}, '${data.max_floor}', ${data.total_star}, ${isUnlock},
'${revealRank}', '${defeatRank}', '${damageRank}', '${takeDamageRank}', '${normalSkillRank}',
'${energySkillRank}', '${floors}', '${skippedFloor}', '${timeNow}')
ON CONFLICT(uid, id) DO UPDATE
SET startTime = '${startTime}',
endTime = '${endTime}',
@@ -90,6 +91,7 @@ function getInsertSql(uid: string, data: TGApp.Game.Abyss.FullData): string {
normalSkillRank = '${normalSkillRank}',
energySkillRank = '${energySkillRank}',
floors = '${floors}',
skippedFloor = '${skippedFloor}',
updated = '${timeNow}';
`;
}

View File

@@ -1,7 +1,7 @@
/**
* @file plugins/Sqlite/modules/userAccounts.ts
* @description 用户账户模块
* @since Beta v0.6.0
* @since Beta v0.6.1
*/
import { path } from "@tauri-apps/api";
@@ -38,6 +38,24 @@ function getInsertGameAccountSql(uid: string, data: TGApp.BBS.Account.GameAccoun
`;
}
/**
* @description 获取插入账号数据的 sql
* @since Beta v0.6.1
* @param {TGApp.App.Account.User} user
* @returns {string}
*/
function getInsertAccountSql(user: TGApp.App.Account.User): string {
const table = transUser(user);
return `
INSERT INTO UserAccount(uid, cookie, brief, updated)
VALUES ('${table.uid}', '${table.cookie}', '${table.brief}', '${table.updated}')
ON CONFLICT(uid) DO UPDATE
SET cookie = '${table.cookie}',
brief = '${table.brief}',
updated = '${table.updated}';
`;
}
/**
* @description 数据库转成可用数据
* @since Beta v0.6.0
@@ -109,19 +127,14 @@ async function getUserAccount(uid: string): Promise<TGApp.App.Account.User | fal
/**
* @description 更新用户数据
* @since Beta v0.6.0
* @since Beta v0.6.1
* @param {TGApp.App.Account.User} data - 用户cookie
* @returns {Promise<void>}
*/
async function saveAccount(data: TGApp.App.Account.User): Promise<void> {
const db = await TGSqlite.getDB();
const table = transUser(data);
const timeNow = timestampToDate(new Date().getTime());
await db.execute(
"INSERT INTO UserAccount(uid, cookie, brief, updated) VALUES \
(?,?,?,?) ON CONFLICT (uid) DO UPDATE SET cookie=?,brief=?,updated=?",
[table.uid, table.cookie, table.brief, timeNow, table.cookie, table.brief, timeNow],
);
const sql = getInsertAccountSql(data);
await db.execute(sql);
}
/**

View File

@@ -1,9 +1,7 @@
-- @file plugins/Sqlite/sql/createTable.sql
-- @brief sqlite数据库创建表语句
-- @since Beta v0.6.0
-- @since Beta v0.6.1
-- @brief 重新创建成就数据表
drop table if exists Achievements;
-- @brief 创建成就数据表
create table if not exists Achievements
(
@@ -16,12 +14,6 @@ create table if not exists Achievements
primary key (id, uid)
);
-- @brief 移除成就系列数据表
drop table if exists AchievementSeries;
-- @brief 移除角色数据表
drop table if exists AppCharacters;
-- @brief 创建应用数据表
create table if not exists AppData
(
@@ -39,9 +31,6 @@ create table if not exists UserAccount
updated text
);
-- @brief 重新创建账号数据表
drop table if exists GameAccount;
-- @brief 创建游戏账号数据表
create table if not exists GameAccount
(
@@ -58,9 +47,6 @@ create table if not exists GameAccount
primary key (uid, gameUid)
);
-- @brief 移除名片表
drop table if exists NameCard;
-- @brief 创建深渊数据表
create table if not exists SpiralAbyss
(
@@ -80,6 +66,7 @@ create table if not exists SpiralAbyss
normalSkillRank text,
energySkillRank text,
floors text,
skippedFloor text,
updated text,
primary key (uid, id)
);

View File

@@ -26,7 +26,7 @@ declare namespace TGApp.Game.Abyss {
/**
* @description 深渊数据类型
* @interface FullData
* @since Alpha v0.2.0
* @since Beta v0.6.1
* @property {number} schedule_id - 深渊周期 ID
* @property {string} start_time - 深渊开始时间,单位:秒
* @property {string} end_time - 深渊结束时间,单位:秒
@@ -42,6 +42,8 @@ declare namespace TGApp.Game.Abyss {
* @property {Floor[]} floors - 深渊各层数据
* @property {number} total_star - 总星数
* @property {boolean} is_unlock - 是否解锁
* @property {boolean} is_just_skipped_floor - 是否跳过楼层
* @property {boolean} skipped_floor - 跳过楼层
* @return FullData
*/
interface FullData {
@@ -60,6 +62,8 @@ declare namespace TGApp.Game.Abyss {
floors: Floor[];
total_star: number;
is_unlock: boolean;
is_just_skipped_floor: boolean;
skipped_floor: string;
}
/**

View File

@@ -1,19 +1,19 @@
/**
* @file types/Sqlite/Abyss.d.ts
* @description 数据库深境螺旋相关类型定义文件
* @since Beta v0.3.9
* @since Beta v0.6.1
*/
/**
* @description 数据库深渊类型命名
* @since Beta v0.3.9
* @since Beta v0.6.1
* @namespace TGApp.Sqlite.Abyss
* @memberof TGApp.Sqlite
*/
declare namespace TGApp.Sqlite.Abyss {
/**
* @description 数据库-深境螺旋表
* @since Alpha v0.2.0
* @since Beta v0.6.1
* @interface SingleTable
* @property {string} uid - 用户 UID
* @property {number} id - 深境螺旋 ID
@@ -32,6 +32,7 @@ declare namespace TGApp.Sqlite.Abyss {
* @property {Character[]} normalSkillRank - 元素战技释放数
* @property {Character[]} energySkillRank - 元素爆发次数
* @property {Floor[]} floors - 深境螺旋各层数据
* @property {string} skippedFloor - 跳过楼层
* @property {string} updated - 更新时间
* @return SingleTable
*/
@@ -52,6 +53,7 @@ declare namespace TGApp.Sqlite.Abyss {
normalSkillRank: string; // Character[]
energySkillRank: string; // Character[]
floors: string; // Floor[]
skippedFloor: string;
updated: string;
}

View File

@@ -153,7 +153,7 @@ class TGClient {
/**
* @func handleCallback
* @since Beta v0.6.0
* @since Beta v0.6.1
* @desc 处理米游社客户端的 callback
* @param {Event<string>} arg - 事件参数
* @returns {Promise<void>} - 返回值
@@ -237,7 +237,7 @@ class TGClient {
break;
case "openSystemBrowser":
await this.openSystemBrowser(
<TGApp.Plugins.JSBrigde.Arg<TGApp.Plugins.JSBridge.OpenSystemBrowserPayload>>argParse,
<TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.OpenSystemBrowserPayload>>argParse,
);
break;
case "pushPage":
@@ -763,14 +763,16 @@ class TGClient {
/**
* @func openSystemBrowser
* @since Beta v0.6.0
* @since Beta v0.6.1
* @desc 打开系统浏览器
* @param {TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.OpenSystemBrowserPayload>} arg - 方法参数
* @returns {Promise<void>}
*/
async openSystemBrowser(arg: TGApp.Plugins.JSBridge.OpenSystemBrowserPayload): Promise<void> {
async openSystemBrowser(
arg: TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.OpenSystemBrowserPayload>,
): Promise<void> {
console.log(`[openSystemBrowser] ${JSON.stringify(arg)}`);
const url = arg.open_url;
const url = arg.payload.open_url;
window.open(url);
}

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