mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-16 04:03:17 +08:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f121644bc4 | ||
|
|
1d810117b0 | ||
|
|
be7c294f7e | ||
|
|
0e1bcdaffe | ||
|
|
51d47c7ca6 | ||
|
|
28f05a757d | ||
|
|
c2db42d9f7 | ||
|
|
49855ea118 | ||
|
|
f5da601620 | ||
|
|
9f707db9f7 | ||
|
|
d30a70d4aa | ||
|
|
036b3c47a7 | ||
|
|
77c513b516 | ||
|
|
69f40cd495 | ||
|
|
9c79e0b822 | ||
|
|
c56b05b4f1 | ||
|
|
b5c7c6e8b1 | ||
|
|
480f1739f5 | ||
|
|
b0a480d65b | ||
|
|
d7aee50cc5 | ||
|
|
fe176ad418 | ||
|
|
5c2556a0c3 | ||
|
|
320e53b567 | ||
|
|
21698dc728 | ||
|
|
9b4b6fb7ab | ||
|
|
8c8f8e3a2d | ||
|
|
5e6e7ee047 | ||
|
|
2872d0f983 | ||
|
|
67e242308e | ||
|
|
a2df7b2d22 | ||
|
|
7b8be1adf9 | ||
|
|
9c73290033 | ||
|
|
6aaf9ea7d9 | ||
|
|
ada60d0d3b | ||
|
|
bbe329d677 | ||
|
|
47ed849f70 | ||
|
|
0d65ba7168 | ||
|
|
da2285a8d0 | ||
|
|
572180234f | ||
|
|
1497533f14 | ||
|
|
3b6970d8c3 | ||
|
|
8fc90d7144 | ||
|
|
c716cf79ed | ||
|
|
9057e613c7 | ||
|
|
a929e2cbe8 | ||
|
|
2d321aad9c | ||
|
|
43de734884 | ||
|
|
b3997815e1 | ||
|
|
52cbfb9f6b | ||
|
|
6d2d2b18d1 | ||
|
|
f7df9ec804 | ||
|
|
3f2ea530fe | ||
|
|
240356da0a | ||
|
|
57de268f06 | ||
|
|
3c238a0f0b | ||
|
|
d6dbddaf87 | ||
|
|
2a84e25f4a | ||
|
|
d1a4b6e97d | ||
|
|
f4a9069ea4 | ||
|
|
7cfd47c36b | ||
|
|
fcc5d3db15 | ||
|
|
2f19691a57 | ||
|
|
83ddadd451 | ||
|
|
b965cccbf1 | ||
|
|
8b60a7f8dd | ||
|
|
04c9907490 | ||
|
|
adef358534 | ||
|
|
fcdad22d94 | ||
|
|
bbc142ac2d | ||
|
|
7169bc202e |
@@ -1,3 +1,3 @@
|
||||
VITE_SENTRY_RELEASE=TeyvatGuide@0.9.6
|
||||
VITE_COMMIT_HASH=40ffb41f
|
||||
VITE_BUILD_TIME=1772034641
|
||||
VITE_SENTRY_RELEASE=TeyvatGuide@0.9.8
|
||||
VITE_COMMIT_HASH=1d810117
|
||||
VITE_BUILD_TIME=1773380800
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 10.23.0
|
||||
version: 10.32.1
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
- name: Setup sentry-cli
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,4 +10,5 @@ dist
|
||||
*.tsbuildinfo
|
||||
|
||||
# Sentry Config File
|
||||
.env.development.local
|
||||
.env.development.local
|
||||
package-lock.json
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -2,12 +2,44 @@
|
||||
Author: 目棃
|
||||
Description: CHANGELOG
|
||||
Date: 2025-09-09
|
||||
Update: 2026-02-25
|
||||
Update: 2026-03-13
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2025-09-09 14:30:56`
|
||||
>
|
||||
> 更新于 `2026-02-25 23:50:25`
|
||||
> 更新于 `2026-03-13 13:41:58`
|
||||
|
||||
## [0.9.8](https://github.com/BTMuli/TeyvatGuide/releases/v0.9.8) (2026-03-13)
|
||||
|
||||
- 🍱 更新下半卡池数据
|
||||
- 🐛 处理UI框架升级导致的分享图生成异常
|
||||
- 🐛 修复采用ck登录后本地ck未同步更新
|
||||
- ✏️ 修正深渊最深抵达描述计算逻辑
|
||||
- ⚡️大幅提升UIGF导入速度 [`#222`](https://github.com/BTMuli/TeyvatGuide/issues/222)
|
||||
- ✨ 角色列表页展示当前筛选&排序
|
||||
- ✨ 定时检测版本更新并提醒 [`#231`](https://github.com/BTMuli/TeyvatGuide/issues/231)
|
||||
- 🔒️ 调整用户数据目录选取&旧目录删除处理,增加子目录检测&二次确认 [`#228`](https://github.com/BTMuli/TeyvatGuide/issues/228)
|
||||
- 🚸 导入胡桃深渊/剧诗/危战数据前进行提示
|
||||
- 🚸 设置页刷新信息允许仅刷新Cookie而不刷新游戏账号
|
||||
- 🚸 搜索框增加清空按钮,并进行对应适配处理
|
||||
- 🚸 完善非回正模式下的窗口位置&大小处理 [`#199`](https://github.com/BTMuli/TeyvatGuide/pull/199) [`#223`](https://github.com/BTMuli/TeyvatGuide/pull/223)
|
||||
- 🚸 实用脚本支持一键执行多账号 by [HLFromZ](https://github.com/BTMuli/TeyvatGuide/pull/227)
|
||||
- 🚸 角色列表页新增`等级>=70`筛选 [`#229`](https://github.com/BTMuli/TeyvatGuide/issues/229)
|
||||
- 🚸 角色列表页新增满好感筛选
|
||||
- 🚸 处理帖子标题为空时的渲染&事件
|
||||
- 🚸 调整部分图片缓存策略
|
||||
- 🚸 增加个人主页&合集主页的外部跳转
|
||||
- 💄 优化调整多处样式 [`#221`](https://github.com/BTMuli/TeyvatGuide/issues/221)
|
||||
- 💄 调整展开后的侧边栏宽度
|
||||
- 💄 自定义表情:调整浮窗信息显示逻辑,优化自定义表情label显示判断
|
||||
|
||||
## [0.9.7](https://github.com/BTMuli/TeyvatGuide/releases/v0.9.7) (2026-02-26)
|
||||
|
||||
- 🐛 修复脚本页面账号切换异常
|
||||
- 🚸 调整游戏安装目录选取逻辑,调整大小写处理 [`#219`](https://github.com/BTMuli/TeyvatGuide/issues/219)
|
||||
- 💄 替换部分侧边栏图标
|
||||
- 💄 调整浅色模式下滚动条可见度
|
||||
- 💄 调整部分页面UI
|
||||
|
||||
## [0.9.6](https://github.com/BTMuli/TeyvatGuide/releases/v0.9.6) (2026-02-26)
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
Author: 目棃
|
||||
Description: 说明文档
|
||||
Date: 2023-03-05
|
||||
Update: 2026-02-25
|
||||
Update: 2026-03-13
|
||||
---
|
||||
|
||||
> 本文档 [`Frontmatter`](https://github.com/BTMuli/MuCli#Frontmatter) 由 [MuCli](https://github.com/BTMuli/Mucli) 自动生成于 `2023-03-05 14:41:55`
|
||||
>
|
||||
> 更新于 `2026-02-25 19:28:26`
|
||||
> 更新于 `2026-03-13 13:46:30`
|
||||
|
||||
[](https://deepwiki.com/BTMuli/TeyvatGuide) [](https://app.fossa.com/projects/git%2Bgithub.com%2FBTMuli%2FTeyvatGuide?ref=badge_shield)
|
||||
|
||||
@@ -89,10 +89,7 @@ Game Tool for Genshin Impact player, supports Windows and macOS.
|
||||
|
||||
## 贡献者 / Contributors
|
||||
|
||||
- [BTMuli](https://github.com/BTMuli)
|
||||
- [舰队的偶像岛风酱!](https://github.com/frg2089)
|
||||
- [jerry765](https://github.com/jerry765)
|
||||
- [AuroraZiling](https://github.com/AuroraZiling)
|
||||
[Contributors](https://github.com/BTMuli/TeyvatGuide/graphs/contributors)
|
||||
|
||||
## UI 参考 / UI Reference
|
||||
|
||||
|
||||
50
package.json
50
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "teyvatguide",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.8",
|
||||
"description": "Game Tool for GenshinImpact player",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.30.1",
|
||||
"packageManager": "pnpm@10.32.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsx scripts/auto-build.ts",
|
||||
@@ -72,9 +72,9 @@
|
||||
"dependencies": {
|
||||
"@date-fns/tz": "^1.4.1",
|
||||
"@mdi/font": "7.4.47",
|
||||
"@sentry/core": "^10.40.0",
|
||||
"@sentry/vite-plugin": "^5.1.0",
|
||||
"@sentry/vue": "^10.40.0",
|
||||
"@sentry/core": "^10.43.0",
|
||||
"@sentry/vite-plugin": "^5.1.1",
|
||||
"@sentry/vue": "^10.43.0",
|
||||
"@skipperndt/plugin-machine-uid": "^0.1.3",
|
||||
"@tauri-apps/api": "^2.10.1",
|
||||
"@tauri-apps/plugin-cli": "^2.4.1",
|
||||
@@ -89,7 +89,7 @@
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-sql": "^2.3.2",
|
||||
"ajv": "^8.18.0",
|
||||
"artplayer": "^5.3.0",
|
||||
"artplayer": "^5.4.0",
|
||||
"colord": "^2.9.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"echarts": "^6.0.0",
|
||||
@@ -101,61 +101,61 @@
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"qrcode.vue": "^3.8.0",
|
||||
"rsa-oaep-encryption": "^1.1.0",
|
||||
"sass-embedded": "^1.97.3",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"swiper": "^12.1.2",
|
||||
"uuid": "^13.0.0",
|
||||
"vue": "^3.5.29",
|
||||
"vue": "^3.5.30",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-json-pretty": "^2.6.0",
|
||||
"vue-router": "^5.0.3",
|
||||
"vuetify": "^4.0.0",
|
||||
"vuetify": "^4.0.2",
|
||||
"wcag-color": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@btmuli/stylelint-plugin-color": "^0.1.0",
|
||||
"@eslint/eslintrc": "^3.3.4",
|
||||
"@eslint/eslintrc": "^3.3.5",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@microsoft/tsdoc": "^0.16.0",
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@tauri-apps/cli": "2.10.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/js-md5": "^0.8.0",
|
||||
"@types/json-bigint": "^1.0.4",
|
||||
"@types/node": "^25.3.0",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"@types/node": "^25.4.0",
|
||||
"@typescript-eslint/parser": "^8.57.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260222.1",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"app-root-path": "^3.1.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"envfile": "^7.1.0",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsonc": "^3.1.0",
|
||||
"eslint-plugin-jsonc": "^3.1.2",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-tsdoc": "^0.5.1",
|
||||
"eslint-plugin-tsdoc": "^0.5.2",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"eslint-plugin-yml": "^3.3.0",
|
||||
"fs-extra": "^11.3.3",
|
||||
"globals": "^17.3.0",
|
||||
"eslint-plugin-yml": "^3.3.1",
|
||||
"fs-extra": "^11.3.4",
|
||||
"globals": "^17.4.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsonc-eslint-parser": "^3.1.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"oxlint": "^1.50.0",
|
||||
"lint-staged": "16.3.3",
|
||||
"oxlint": "^1.54.0",
|
||||
"postcss-preset-env": "^11.2.0",
|
||||
"prettier": "3.8.1",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint": "^17.4.0",
|
||||
"stylelint-config-idiomatic-order": "^10.0.0",
|
||||
"stylelint-config-standard-scss": "^17.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^3.0.0",
|
||||
"stylelint-high-performance-animation": "^2.0.0",
|
||||
"stylelint-order": "^7.0.1",
|
||||
"stylelint-order": "^8.0.0",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"stylelint-scss": "^7.0.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"typescript-eslint": "^8.57.0",
|
||||
"vite": "npm:rolldown-vite@^7.3.1",
|
||||
"vite-plugin-vue-devtools": "^8.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.7",
|
||||
"vite-plugin-vuetify": "^2.1.3",
|
||||
"vue-eslint-parser": "^10.4.0",
|
||||
"vue-tsc": "^3.2.5",
|
||||
|
||||
2477
pnpm-lock.yaml
generated
2477
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
BIN
public/UI/nav/subRecord.webp
Normal file
BIN
public/UI/nav/subRecord.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/UI/nav/subSign.webp
Normal file
BIN
public/UI/nav/subSign.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/UI/nav/userBag.webp
Normal file
BIN
public/UI/nav/userBag.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
577
src-tauri/Cargo.lock
generated
577
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "TeyvatGuide"
|
||||
version = "0.9.6"
|
||||
version = "0.9.8"
|
||||
description = "Game Tool for Genshin Impact player"
|
||||
authors = ["BTMuli <bt-muli@outlook.com>"]
|
||||
license = "MIT"
|
||||
@@ -17,10 +17,10 @@ name = "teyvat_guide_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.5", features = [] }
|
||||
tauri-build = { version = "2.5.6", features = [] }
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.43"
|
||||
chrono = "0.4.44"
|
||||
image = "0.25.9"
|
||||
log = "0.4.29"
|
||||
prost = "=0.14.1"
|
||||
@@ -28,8 +28,8 @@ prost-types = "=0.14.1"
|
||||
sentry = { version = "0.46.2", features = ["backtrace", "panic"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
tauri = { version = "2.10.2", features = ["tray-icon"] }
|
||||
tauri-utils = "2.8.2"
|
||||
tauri = { version = "2.10.3", features = ["tray-icon"] }
|
||||
tauri-utils = "2.8.3"
|
||||
tauri-plugin-machine-uid = "0.1.3"
|
||||
url = "2.5.8"
|
||||
walkdir = "2.5.0"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"core:window:allow-set-title",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-show",
|
||||
"core:window:default",
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"core:window:allow-set-always-on-top",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-title",
|
||||
"core:window:allow-show",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"core:window:allow-destroy",
|
||||
"core:window:allow-is-minimized",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-title",
|
||||
"core:window:allow-show",
|
||||
@@ -58,7 +59,7 @@
|
||||
{ "url": "https://*.mihoyo.com/*" },
|
||||
{ "url": "https://homa.gentle.house/*" },
|
||||
{ "url": "https://*.hoyoverse.com/*" },
|
||||
{ "url": "https://api.hakush.in/*" }
|
||||
{ "url": "https://api.github.com/repos/BTMuli/TeyvatGuide/releases/latest" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "TeyvatGuide",
|
||||
"identifier": "TeyvatGuide",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.8",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm vite:dev",
|
||||
"beforeBuildCommand": "pnpm vite:build",
|
||||
@@ -39,7 +39,9 @@
|
||||
"label": "TeyvatGuide",
|
||||
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required",
|
||||
"width": 1600,
|
||||
"minWidth": 1600,
|
||||
"height": 900,
|
||||
"minHeight": 900,
|
||||
"center": true,
|
||||
"visible": false
|
||||
}
|
||||
|
||||
24
src/App.vue
24
src/App.vue
@@ -28,12 +28,12 @@ import useAppStore from "@store/app.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { app, core, event, webviewWindow } from "@tauri-apps/api";
|
||||
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { type CliMatches, getMatches } from "@tauri-apps/plugin-cli";
|
||||
import { mkdir } from "@tauri-apps/plugin-fs";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { getWindowSize, resizeWindow } from "@utils/TGWindow.js";
|
||||
import { resizeWindow, setWindowPos } from "@utils/TGWindow.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
@@ -74,7 +74,11 @@ onMounted(async () => {
|
||||
textScaleListener = await event.listen<void>("text_scale_change", resizeWindow);
|
||||
const isShow = await win.isVisible();
|
||||
if (!isShow) {
|
||||
await win.center();
|
||||
if (needResize.value === "false") {
|
||||
await setWindowPos();
|
||||
} else {
|
||||
await win.center();
|
||||
}
|
||||
await win.show();
|
||||
}
|
||||
if (showFeedback.value) {
|
||||
@@ -270,15 +274,11 @@ function handleThemeListen(event: Event<string>): void {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleResizeListen(event: Event<string>): Promise<void> {
|
||||
const win = getCurrentWindow();
|
||||
const webview = webviewWindow.getCurrentWebviewWindow();
|
||||
if (event.payload !== "false") {
|
||||
await resizeWindow();
|
||||
await win.center();
|
||||
await getCurrentWindow().center();
|
||||
} else {
|
||||
const size = getWindowSize(webview.label);
|
||||
await win.setSize(new LogicalSize(size.width, size.height));
|
||||
await webview.setZoom(1);
|
||||
await setWindowPos();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,7 +485,11 @@ async function handleCommands(cmds: CliMatches): Promise<void> {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
<style lang="scss" scoped>
|
||||
.v-application {
|
||||
color: var(--app-page-content);
|
||||
}
|
||||
|
||||
.app-container {
|
||||
height: 100%;
|
||||
background: var(--app-page-bg);
|
||||
|
||||
@@ -93,7 +93,7 @@ html {
|
||||
::-webkit-scrollbar-thumb {
|
||||
min-height: 48px;
|
||||
border-radius: 4px;
|
||||
background: var(--tgc-od-white);
|
||||
background: var(--app-scroll-bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 主题样式文件-深色主题
|
||||
* @since v0.8.9
|
||||
* @since v0.9.7
|
||||
*/
|
||||
|
||||
/* dark mode */
|
||||
@@ -11,6 +11,7 @@ html.dark {
|
||||
--app-page-content: #d0d0d0ff;
|
||||
--app-side-bg: #151c26ff;
|
||||
--app-side-content: #ddddddff;
|
||||
--app-scroll-bg: var(--tgc-od-white);
|
||||
|
||||
/* box container */
|
||||
--box-bg-1: #21252bff;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 主题样式文件-默认(浅色)主题
|
||||
* @since v0.8.9
|
||||
* @since v0.9.7
|
||||
*/
|
||||
|
||||
/* default(light) theme */
|
||||
@@ -11,6 +11,7 @@ html.default {
|
||||
--app-page-content: #2f2f2fff;
|
||||
--app-side-bg: #f2f2f2ff;
|
||||
--app-side-content: #222222ff;
|
||||
--app-scroll-bg: var(--tgc-yellow-3);
|
||||
|
||||
/* box container */
|
||||
--box-bg-1: #f9f6f2ff;
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
<!-- 版块小组件菜单 -->
|
||||
<template>
|
||||
<div class="tgn-container">
|
||||
<div v-for="navItem in nav" :key="navItem.id" class="tgn-nav" @click="toNav(navItem)">
|
||||
<TMiImg :ori="true" :src="navItem.icon" alt="navIcon" />
|
||||
<span>{{ navItem.name }}</span>
|
||||
</div>
|
||||
<div v-if="hasNav" class="tgn-nav" title="查看兑换码">
|
||||
<v-icon v-if="!loadCode" color="var(--tgc-od-orange)" size="25" @click="tryGetCode">
|
||||
mdi-code-tags-check
|
||||
</v-icon>
|
||||
<v-progress-circular v-else color="var(--tgc-od-orange)" indeterminate size="25" />
|
||||
</div>
|
||||
<ToLivecode v-model="showOverlay" :actId="actId" :data="codeData" :gid="model" />
|
||||
<TGameNavItem
|
||||
v-for="navItem in nav"
|
||||
:key="navItem.id"
|
||||
:label="navItem.name"
|
||||
:mini
|
||||
class="tgn-nav"
|
||||
@click="toNav(navItem)"
|
||||
>
|
||||
<template #icon>
|
||||
<TMiImg :size="28" :ori="true" :src="navItem.icon" alt="navIcon" />
|
||||
</template>
|
||||
</TGameNavItem>
|
||||
<TGameNavItem v-if="hasNav" :mini class="tgn-nav" label="兑换码">
|
||||
<template #icon>
|
||||
<v-icon v-if="!loadCode" color="var(--tgc-od-orange)" size="28" @click="tryGetCode">
|
||||
mdi-code-tags-check
|
||||
</v-icon>
|
||||
<v-progress-circular v-else color="var(--tgc-od-orange)" indeterminate size="28" />
|
||||
</template>
|
||||
</TGameNavItem>
|
||||
<ToLivecode v-model="showOverlay" :actId="actId" :data="codeData" :gid />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -28,12 +38,15 @@ import { createPost } from "@utils/TGWindow.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
import TGameNavItem from "./t-gameNavItem.vue";
|
||||
import TMiImg from "./t-mi-img.vue";
|
||||
import ToLivecode from "./to-livecode.vue";
|
||||
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
type TGameNavProps = { gid: number; mini?: boolean };
|
||||
|
||||
const model = defineModel<number>({ default: 2 });
|
||||
const props = withDefaults(defineProps<TGameNavProps>(), { gid: 2, mini: false });
|
||||
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
|
||||
const actId = ref<string>();
|
||||
const showOverlay = ref<boolean>(false);
|
||||
@@ -50,7 +63,7 @@ const hasNav = computed<TGApp.BBS.Navigator.Navigator | undefined>(() => {
|
||||
onMounted(async () => await loadNav());
|
||||
|
||||
watch(
|
||||
() => model.value,
|
||||
() => props.gid,
|
||||
async () => await loadNav(),
|
||||
);
|
||||
|
||||
@@ -60,7 +73,7 @@ watch(
|
||||
*/
|
||||
async function loadNav(): Promise<void> {
|
||||
try {
|
||||
nav.value = await ApiHubReq.home(model.value);
|
||||
nav.value = await ApiHubReq.home(props.gid);
|
||||
console.debug(`[TGameNav][loadNav] 组件数据:`, nav.value);
|
||||
if (loadCode.value) loadCode.value = false;
|
||||
} catch (e) {
|
||||
@@ -158,7 +171,7 @@ async function toBBS(link: URL): Promise<void> {
|
||||
}
|
||||
if (link.hostname === "forum") {
|
||||
const forumId = link.pathname.split("/").pop();
|
||||
const localPath = `/posts/forum/${model.value}/${forumId}`;
|
||||
const localPath = `/posts/forum/${props.gid}/${forumId}`;
|
||||
await emit("active_deep_link", `router?path=${localPath}`);
|
||||
return;
|
||||
}
|
||||
@@ -179,31 +192,10 @@ async function toBBS(link: URL): Promise<void> {
|
||||
.tgn-nav {
|
||||
@include github-styles.github-card;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
color: var(--tgc-white-1);
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: none;
|
||||
margin-left: 4px;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tgn-nav {
|
||||
|
||||
50
src/components/app/t-gameNavItem.vue
Normal file
50
src/components/app/t-gameNavItem.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- 版块组件项 -->
|
||||
<template>
|
||||
<div class="tgni-box" :title="props.label">
|
||||
<slot name="icon"></slot>
|
||||
<span ref="TgniLabelRef">{{ props.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, useTemplateRef } from "vue";
|
||||
|
||||
type TGameNavItemProps = { label: string; mini: boolean };
|
||||
|
||||
const props = defineProps<TGameNavItemProps>();
|
||||
const labelEl = useTemplateRef<HTMLSpanElement>("TgniLabelRef");
|
||||
const width = computed<string>(() => {
|
||||
if (!labelEl.value || props.mini) return "38px";
|
||||
return `${labelEl.value.clientWidth + 42}px`;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
|
||||
.tgni-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 38px;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
color: var(--tgc-white-1);
|
||||
column-gap: 4px;
|
||||
cursor: pointer;
|
||||
transition: width ease-in-out 0.5s;
|
||||
|
||||
span {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
width: v-bind(width);
|
||||
transition: width ease-in-out 0.5s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -34,6 +34,7 @@ async function switchPin(): Promise<void> {
|
||||
@include github-styles.github-card;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 64px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
|
||||
@@ -31,6 +31,7 @@ function switchPostWidth(): void {
|
||||
@include github-styles.github-card;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 112px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
data-html2canvas-ignore
|
||||
@click.stop="trySelect()"
|
||||
/>
|
||||
<div v-else class="tpc-info-id">
|
||||
<div v-else class="tpc-info-id" @click="shareCard()">
|
||||
<span>{{ props.modelValue.post.post_id }}</span>
|
||||
<template v-if="isDevEnv">
|
||||
<span data-html2canvas-ignore>[{{ props.modelValue.post.view_type }}]</span>
|
||||
@@ -230,7 +230,7 @@ function getCommonCard(item: TGApp.BBS.Post.FullData): RenderCard {
|
||||
const findG = forumList.value.find((i) => i.game_id === item.post.game_id);
|
||||
if (findG) {
|
||||
console.log(findG, item);
|
||||
const findF = findG.forums.find((i) => i.id === item.forum.id);
|
||||
const findF = findG.forums.find((i) => i.id === item.forum!.id);
|
||||
if (findF) forumIcon = findF.icon_pure;
|
||||
}
|
||||
forumData = { name: item.forum.name, icon: forumIcon, id: item.forum.id };
|
||||
@@ -575,6 +575,7 @@ function onUserClick(): void {
|
||||
border-top-left-radius: 4px;
|
||||
box-shadow: 1px 1px 6px var(--tgc-dark-1);
|
||||
color: var(--tgc-white-1);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-shadow: 0 0 4px var(--tgc-dark-1);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ async function shareContent(): Promise<void> {
|
||||
|
||||
.share-box {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- 应用侧边栏 -->
|
||||
<template>
|
||||
<v-navigation-drawer :permanent="true" :rail="rail" class="tsb-box">
|
||||
<v-navigation-drawer :width="160" :permanent="true" :rail="rail" class="tsb-box">
|
||||
<v-list :nav="true" class="side-list" density="compact">
|
||||
<v-list-item
|
||||
:append-icon="!rail ? 'mdi-chevron-left' : undefined"
|
||||
@@ -42,26 +42,26 @@
|
||||
<v-list-item :link="true" :title.attr="'背包材料'" href="/bag/material">
|
||||
<template #title>背包材料</template>
|
||||
<template #prepend>
|
||||
<img alt="materialBagIcon" class="side-icon" src="/icon/material/121234.webp" />
|
||||
<img alt="materialBagIcon" class="side-icon" src="/UI/nav/userBag.webp" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-item :link="true" :title.attr="'原神战绩'" href="/user/record">
|
||||
<template #title>原神战绩</template>
|
||||
<template #prepend>
|
||||
<img alt="record" class="side-icon-menu" src="/UI/nav/userRecord.webp" />
|
||||
<img alt="record" class="side-icon" src="/UI/nav/userRecord.webp" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item :link="true" :title.attr="'角色列表'" href="/user/characters">
|
||||
<template #title>角色列表</template>
|
||||
<template #prepend>
|
||||
<img alt="characters" class="side-icon-menu" src="/UI/nav/userAvatar.webp" />
|
||||
<img alt="characters" class="side-icon" src="/UI/nav/userAvatar.webp" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item :link="true" :title.attr="'祈愿记录'" href="/user/gacha">
|
||||
<template #title>祈愿记录</template>
|
||||
<template #prepend>
|
||||
<img alt="gacha" class="side-icon-menu" src="/UI/nav/userGacha.webp" />
|
||||
<img alt="gacha" class="side-icon" src="/UI/nav/userGacha.webp" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<!-- 高难挑战,包括深渊&剧诗&危战 -->
|
||||
@@ -182,7 +182,7 @@
|
||||
@click="openClient('sign_in')"
|
||||
>
|
||||
<template #prepend>
|
||||
<img alt="sing_in" class="side-icon-menu" src="/UI/nav/userGacha.webp" />
|
||||
<img alt="sing_in" class="side-icon-menu" src="/UI/nav/subSign.webp" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
@@ -192,7 +192,7 @@
|
||||
@click="openClient('game_record')"
|
||||
>
|
||||
<template #prepend>
|
||||
<img alt="game_record" class="side-icon-menu" src="/UI/nav/userRecord.webp" />
|
||||
<img alt="game_record" class="side-icon-menu" src="/UI/nav/subRecord.webp" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
@@ -330,7 +330,7 @@ import useUserStore from "@store/user.js";
|
||||
import { event, path, webviewWindow } from "@tauri-apps/api";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { exists } from "@tauri-apps/plugin-fs";
|
||||
import { readDir } from "@tauri-apps/plugin-fs";
|
||||
import mhyClient from "@utils/TGClient.js";
|
||||
import { isRunInAdmin, tryCopyYae, tryReadGameVer, YAE_GAME_VER } from "@utils/TGGame.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
@@ -690,6 +690,7 @@ async function addByCookie(): Promise<void> {
|
||||
};
|
||||
uid.value = briefRes.uid;
|
||||
briefInfo.value = briefInfoGet;
|
||||
cookie.value = ck;
|
||||
isLogin.value = true;
|
||||
await showLoading.update("正在保存用户数据");
|
||||
await TSUserAccount.account.saveAccount({
|
||||
@@ -729,11 +730,17 @@ async function tryLaunchGame(): Promise<void> {
|
||||
showSnackbar.warn("请先登录!");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${gameDir.value}${path.sep()}YuanShen.exe`;
|
||||
if (!(await exists(gamePath))) {
|
||||
if (gameDir.value === "未设置") {
|
||||
showSnackbar.warn("请前往设置页面设置游戏安装目录");
|
||||
return;
|
||||
}
|
||||
const dirRead = await readDir(gameDir.value);
|
||||
const find = dirRead.find((i) => i.isFile && i.name.toLowerCase() === "yuanshen.exe");
|
||||
if (!find) {
|
||||
showSnackbar.warn("未检测到原神本体应用!");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${gameDir.value}${path.sep()}${find.name}`;
|
||||
const resp = await passportReq.authTicket(account.value, cookie.value);
|
||||
if (typeof resp !== "string") {
|
||||
showSnackbar.error(`[${resp.retcode}] ${resp.message}`);
|
||||
@@ -785,9 +792,6 @@ async function tryLaunchGame(): Promise<void> {
|
||||
.side-list {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
@@ -800,7 +804,7 @@ async function tryLaunchGame(): Promise<void> {
|
||||
|
||||
.bottom-menu {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ onUnmounted(() => {
|
||||
|
||||
.switch-box {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- Loading 组件 -->
|
||||
<template>
|
||||
<transition name="func-loading">
|
||||
<div v-show="showBox || showOuter" class="loading-overlay">
|
||||
<div v-show="showBox || showOuter" ref="LoadingRef" class="loading-overlay">
|
||||
<transition name="func-loading-inner">
|
||||
<div v-show="showInner" class="loading-container">
|
||||
<div class="loading-box">
|
||||
@@ -29,20 +29,22 @@
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import bbsReq from "@req/bbsReq.js";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
import { LoadingParams } from "./loading.js";
|
||||
|
||||
const defaultIcon = "/UI/app/loading.webp";
|
||||
|
||||
const props = defineProps<LoadingParams>();
|
||||
|
||||
const showBox = ref<boolean>(false);
|
||||
const showOuter = ref<boolean>(false);
|
||||
const showInner = ref<boolean>(false);
|
||||
|
||||
const props = defineProps<LoadingParams>();
|
||||
const iconUrl = ref<string>(defaultIcon);
|
||||
const data = shallowRef<LoadingParams>(props);
|
||||
const localEmojis = shallowRef<Array<string>>([]);
|
||||
const iconUrl = ref<string>(defaultIcon);
|
||||
const loadingEl = useTemplateRef<HTMLDivElement>("LoadingRef");
|
||||
|
||||
watch(
|
||||
() => showBox.value,
|
||||
@@ -60,7 +62,11 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => await displayBox(props));
|
||||
onMounted(async () => {
|
||||
await displayBox(props);
|
||||
loadingEl.value?.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||
});
|
||||
onUnmounted(() => loadingEl.value?.removeEventListener("contextmenu", (e) => e.preventDefault()));
|
||||
|
||||
async function getRandomEmoji(): Promise<void> {
|
||||
if (localEmojis.value.length === 0) {
|
||||
|
||||
@@ -70,13 +70,13 @@ import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TGSqlite from "@Sql/index.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import { path } from "@tauri-apps/api";
|
||||
import { sep } from "@tauri-apps/api/path";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { exists, readDir, remove } from "@tauri-apps/plugin-fs";
|
||||
import { readDir, remove } from "@tauri-apps/plugin-fs";
|
||||
import { openPath } from "@tauri-apps/plugin-opener";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
import { backUpUserData } from "@utils/dataBS.js";
|
||||
import { tryReadGameVer } from "@utils/TGGame.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
@@ -110,7 +110,7 @@ onMounted(async () => {
|
||||
|
||||
async function confirmCUD(): Promise<void> {
|
||||
const oriDir = userDir.value;
|
||||
const changeCheck = await showDialog.check("确认修改用户数据路径吗?");
|
||||
const changeCheck = await showDialog.check("确认修改用户数据路径吗?", "请选取空目录");
|
||||
if (!changeCheck) {
|
||||
showSnackbar.cancel("已取消修改");
|
||||
return;
|
||||
@@ -124,18 +124,43 @@ async function confirmCUD(): Promise<void> {
|
||||
showSnackbar.warn("路径未修改!");
|
||||
return;
|
||||
}
|
||||
const dirRead = await readDir(dir);
|
||||
if (dirRead.length !== 0) {
|
||||
showSnackbar.warn("请选择空目录");
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(`[TcDataDir] 修改用户数据目录: ${userDir.value} → ${dir}`);
|
||||
userDir.value = dir;
|
||||
await TGSqlite.saveAppData("userDir", dir);
|
||||
await backUpUserData(dir);
|
||||
showSnackbar.success("已修改用户数据路径!");
|
||||
const delCheck = await showDialog.check("是否删除原用户数据目录?");
|
||||
if (delCheck) {
|
||||
await remove(oriDir, { recursive: true });
|
||||
showSnackbar.success("已删除原用户数据目录!");
|
||||
if (!delCheck) {
|
||||
showSnackbar.cancel(`取消删除原数据目录`);
|
||||
return;
|
||||
}
|
||||
showSnackbar.info("即将刷新页面...");
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
||||
window.location.reload();
|
||||
const delDirRead = await readDir(oriDir);
|
||||
if (delDirRead.some((i) => i.isDirectory)) {
|
||||
const check = await showDialog.check(`检测到子目录,确定删除?`, oriDir);
|
||||
if (!check) {
|
||||
showSnackbar.cancel(`取消删除原数据目录`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const delCheck2 = await showDialog.check("无法通过回收站恢复,确认删除?", oriDir);
|
||||
if (!delCheck2) {
|
||||
showSnackbar.cancel(`取消删除原数据目录`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await remove(oriDir, { recursive: true });
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
showSnackbar.error(err.message);
|
||||
} else showSnackbar.error(`${err}`);
|
||||
return;
|
||||
}
|
||||
showSnackbar.success("已删除原用户数据目录!");
|
||||
}
|
||||
|
||||
async function confirmCGD(): Promise<void> {
|
||||
@@ -146,31 +171,32 @@ async function confirmCGD(): Promise<void> {
|
||||
const oriEmpty = gameDir.value === "未设置";
|
||||
const editCheck = await showDialog.check(
|
||||
oriEmpty ? "确认设置游戏目录?" : "确认修改游戏目录?",
|
||||
oriEmpty ? "请选择 Yuanshen.exe 所在目录" : `当前:${gameDir.value}`,
|
||||
oriEmpty ? "请选择 YuanShen.exe 所在目录" : `当前:${gameDir.value}`,
|
||||
);
|
||||
if (!editCheck) {
|
||||
showSnackbar.cancel(oriEmpty ? "已取消设置" : "已取消修改");
|
||||
return;
|
||||
}
|
||||
const dir: string | null = await open({
|
||||
directory: true,
|
||||
defaultPath: oriEmpty ? undefined : gameDir.value,
|
||||
const file: string | null = await open({
|
||||
defaultPath: oriEmpty ? undefined : `${gameDir.value}${path.sep()}YuanShen.exe`,
|
||||
multiple: false,
|
||||
});
|
||||
if (dir === null) {
|
||||
if (file === null) {
|
||||
showSnackbar.warn("路径不能为空!");
|
||||
return;
|
||||
}
|
||||
if (!oriEmpty && gameDir.value === dir) {
|
||||
if (!file.toLowerCase().endsWith("yuanshen.exe")) {
|
||||
showSnackbar.warn("请选中游戏本体(YuanShen.exe)");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!oriEmpty &&
|
||||
`${gameDir.value}${path.sep()}YuanShen.exe`.toLowerCase() === file.toLowerCase()
|
||||
) {
|
||||
showSnackbar.warn("路径未修改!");
|
||||
return;
|
||||
}
|
||||
// 校验是否存在游戏本体
|
||||
if (!(await exists(`${dir}${path.sep()}YuanShen.exe`))) {
|
||||
showSnackbar.warn("未检测到游戏本体");
|
||||
return;
|
||||
}
|
||||
gameDir.value = dir;
|
||||
gameDir.value = file.substring(0, file.lastIndexOf(path.sep()));
|
||||
showSnackbar.success(oriEmpty ? "成功设置游戏目录" : "成功修改游戏目录");
|
||||
}
|
||||
|
||||
@@ -204,7 +230,7 @@ async function confirmCLD(): Promise<void> {
|
||||
await showLoading.start("正在清理日志文件...");
|
||||
for (const file of delFiles) {
|
||||
await showLoading.update(`正在清理 ${file.name}`);
|
||||
const filePath = `${logDir.value}${sep()}${file.name}`;
|
||||
const filePath = `${logDir.value}${path.sep()}${file.name}`;
|
||||
await remove(filePath);
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="tgb-box">
|
||||
<div class="tgb-top">
|
||||
<div class="tgb-title">✨原神,启动!</div>
|
||||
<v-btn size="small" icon="mdi-rocket" variant="outlined" @click="tryPlayGame()" />
|
||||
<v-btn icon="mdi-rocket" size="small" variant="outlined" @click="tryPlayGame()" />
|
||||
</div>
|
||||
<v-list-item v-if="account.uid">
|
||||
<v-list-item-title class="tgb-name">
|
||||
@@ -22,7 +22,7 @@ import useAppStore from "@store/app.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { path } from "@tauri-apps/api";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { exists } from "@tauri-apps/plugin-fs";
|
||||
import { readDir } from "@tauri-apps/plugin-fs";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
@@ -42,11 +42,13 @@ async function tryPlayGame(): Promise<void> {
|
||||
showSnackbar.warn("未设置游戏安装目录!");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${gameDir.value}${path.sep()}YuanShen.exe`;
|
||||
if (!(await exists(gamePath))) {
|
||||
const dirRead = await readDir(gameDir.value);
|
||||
const find = dirRead.find((i) => i.isFile && i.name.toLowerCase() === "yuanshen.exe");
|
||||
if (!find) {
|
||||
showSnackbar.warn("未检测到原神本体应用!");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${gameDir.value}${path.sep()}${find.name}`;
|
||||
const resp = await passportReq.authTicket(account.value, cookie.value);
|
||||
if (typeof resp !== "string") {
|
||||
showSnackbar.error(`[${resp.retcode}] ${resp.message}`);
|
||||
|
||||
@@ -89,6 +89,7 @@ async function toDonate(): Promise<void> {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding: 8px;
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item v-for="ac in accounts" :key="ac.uid">
|
||||
<template #prepend>
|
||||
<v-avatar>
|
||||
<v-img :src="ac.brief.avatar" alt="avatar" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title>{{ ac.brief.nickname }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ ac.brief.uid }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
@@ -252,7 +257,7 @@ async function tryCodeLogin(): Promise<void> {
|
||||
showLoginQr.value = true;
|
||||
}
|
||||
|
||||
async function refreshUser(uid: string) {
|
||||
async function refreshUser(uid: string, full: boolean) {
|
||||
let account = await TSUserAccount.account.getAccount(uid);
|
||||
if (!account) {
|
||||
showSnackbar.warn(`未获取到${uid}账号数据,请重新登录!`);
|
||||
@@ -318,15 +323,19 @@ async function refreshUser(uid: string) {
|
||||
};
|
||||
await TGLogger.Info("[tc-userBadge][refreshUserInfo] 获取用户信息成功");
|
||||
}
|
||||
if (!full) {
|
||||
await showLoading.end();
|
||||
return;
|
||||
}
|
||||
await TSUserAccount.account.saveAccount(account);
|
||||
await showLoading.update("正在获取账号信息");
|
||||
await showLoading.update("正在获取游戏账号信息");
|
||||
const accountRes = await takumiReq.bind.gameRoles(ck);
|
||||
if (Array.isArray(accountRes)) {
|
||||
await showLoading.update("获取账号信息成功");
|
||||
await showLoading.update("获取游戏账号信息成功");
|
||||
await TGLogger.Info("[tc-userBadge][refreshUserInfo] 获取账号信息成功");
|
||||
await TSUserAccount.game.saveAccounts(account.uid, accountRes);
|
||||
} else {
|
||||
await showLoading.update("获取账号信息失败");
|
||||
await showLoading.update("获取游戏账号信息失败");
|
||||
showSnackbar.error(`[${accountRes.retcode}]${accountRes.message}`);
|
||||
await TGLogger.Error("[tc-userBadge][refreshUserInfo] 获取账号信息失败");
|
||||
await TGLogger.Error(
|
||||
@@ -359,12 +368,16 @@ async function loadAccount(ac: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function confirmRefreshUser(ac: string): Promise<void> {
|
||||
const freshCheck = await showDialog.check("确认刷新用户信息吗?", "将会重新获取用户信息");
|
||||
if (!freshCheck) {
|
||||
const freshCheck = await showDialog.checkF({
|
||||
title: "确认刷新用户信息?",
|
||||
text: "将刷新用户Cookie跟游戏账号",
|
||||
cancelLabel: "仅刷新Cookie",
|
||||
});
|
||||
if (freshCheck === undefined) {
|
||||
showSnackbar.cancel("已取消刷新用户信息");
|
||||
return;
|
||||
}
|
||||
await refreshUser(ac);
|
||||
await refreshUser(ac, freshCheck);
|
||||
if (uid.value === ac) {
|
||||
showSnackbar.success("成功刷新用户信息");
|
||||
return;
|
||||
@@ -510,6 +523,7 @@ async function addByCookie(): Promise<void> {
|
||||
};
|
||||
uid.value = briefRes.uid;
|
||||
briefInfo.value = briefGet;
|
||||
cookie.value = ck;
|
||||
isLogin.value = true;
|
||||
await showLoading.update("正在保存用户数据");
|
||||
await TSUserAccount.account.saveAccount({
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<div v-if="pools.length < 3" class="pool-grid">
|
||||
<PhPoolCard v-for="(pool, idx) in pools" :key="idx" :pool="pool" />
|
||||
</div>
|
||||
<!-- TODO: 优化Swiper效果 -->
|
||||
<Swiper
|
||||
v-else
|
||||
:autoplay="{ delay: 3000, disableOnInteraction: false }"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
// Reward state enum TODO:完善类型
|
||||
// Reward state enum
|
||||
const RewardState = <const>{
|
||||
NORMAL: 0,
|
||||
SIGNED: 1,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
class="toc-list-item"
|
||||
@click="toChannel(item)"
|
||||
>
|
||||
<TMiImg :ori="true" :src="item.icon" alt="icon" />
|
||||
<img :src="item.icon" alt="icon" />
|
||||
<span class="toc-list-title">{{ item.title }}</span>
|
||||
<span class="toc-list-id">GID:{{ item.gid }}</span>
|
||||
</div>
|
||||
@@ -19,7 +19,6 @@
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import bbsEnum from "@enum/bbs.js";
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="twc-bi-desc">{{ data.description }}</div>
|
||||
</div>
|
||||
<div class="twc-bi-grid1">
|
||||
<div class="twc-bi-grid">
|
||||
<div class="twc-big-item">
|
||||
<span>{{ data.elePrefix }}</span>
|
||||
<span>{{ data.element }}</span>
|
||||
@@ -40,7 +40,7 @@
|
||||
<span>{{ data.brief.birth }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="twc-bi-grid2">
|
||||
<div class="twc-bi-grid">
|
||||
<div class="twc-big-item">
|
||||
<span>汉语CV</span>
|
||||
<span>{{ data.brief.cv.cn }}</span>
|
||||
@@ -182,7 +182,7 @@ async function toBirth(date: string): Promise<void> {
|
||||
await router.push({ name: "留影叙佳期", params: { date: birth } });
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.v-expansion-panel-title) {
|
||||
background: var(--common-shadow-1);
|
||||
}
|
||||
@@ -191,7 +191,7 @@ async function toBirth(date: string): Promise<void> {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
row-gap: 10px;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.twc-brief {
|
||||
@@ -201,7 +201,9 @@ async function toBirth(date: string): Promise<void> {
|
||||
}
|
||||
|
||||
.twc-brief-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@@ -212,8 +214,10 @@ async function toBirth(date: string): Promise<void> {
|
||||
}
|
||||
|
||||
.twc-bi-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--common-text-title);
|
||||
@@ -240,16 +244,12 @@ async function toBirth(date: string): Promise<void> {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.twc-bi-grid1 {
|
||||
display: grid;
|
||||
column-gap: 10px;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.twc-bi-grid2 {
|
||||
display: grid;
|
||||
column-gap: 10px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
.twc-bi-grid {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
.twc-big-item {
|
||||
|
||||
@@ -86,7 +86,13 @@ function handleSearch(kw: string): void {
|
||||
|
||||
async function searchAchi(): Promise<void> {
|
||||
if (!props.isSearch) return;
|
||||
if (!props.search || props.search === "") {
|
||||
if (!props.search) {
|
||||
achievements.value = await TSUserAchi.getAchievements(props.uid, props.series);
|
||||
showSnackbar.success("已重置");
|
||||
emits("update:isSearch", false);
|
||||
return;
|
||||
}
|
||||
if (props.search === "") {
|
||||
showSnackbar.warn("请输入搜索内容");
|
||||
emits("update:isSearch", false);
|
||||
return;
|
||||
|
||||
@@ -265,7 +265,6 @@ function getWeaponTitle(): string {
|
||||
justify-content: flex-end;
|
||||
border-radius: 4px;
|
||||
aspect-ratio: 21/10;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
.tua-abl-bg {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<div class="tua-dc-container">
|
||||
<div class="tua-dc-avatar">
|
||||
<TMiImg :ori="true" :src="fullIcon" alt="avatar" />
|
||||
<img :src="fullIcon" alt="avatar" />
|
||||
</div>
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
@@ -76,7 +76,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TSUserAvatar from "@Sqlm/userAvatar.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
|
||||
233
src/components/userAvatar/tua-select-vals.vue
Normal file
233
src/components/userAvatar/tua-select-vals.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<!-- 筛选&&排序条件展示 -->
|
||||
<template>
|
||||
<div v-if="props.isSelected || isOrdered" class="tua-sv-container">
|
||||
<div v-if="props.isSelected" class="tua-sv-selects">
|
||||
<div class="tua-sv-title">筛选</div>
|
||||
<div
|
||||
v-if="props.selectOpts.costume.length === 1"
|
||||
:class="props.selectOpts.costume[0] === 'true' ? 'pass' : 'ban'"
|
||||
class="tua-svs-item"
|
||||
>
|
||||
<v-icon size="14">mdi-tshirt-crew</v-icon>
|
||||
<v-icon v-if="props.selectOpts.costume[0] === 'false'" size="14">mdi-block-helper</v-icon>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectOpts.fetter.length === 1"
|
||||
:class="props.selectOpts.fetter[0] === 'true' ? 'pass' : 'ban'"
|
||||
class="tua-svs-item"
|
||||
>
|
||||
<span>好感:{{ getFetterLabel(props.selectOpts.fetter[0]) }}</span>
|
||||
</div>
|
||||
<div v-if="props.selectOpts.star.length === 1" class="tua-svs-item">
|
||||
<span>{{ getStarLabel(props.selectOpts.star[0]) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectOpts.level.length === 1"
|
||||
:class="props.selectOpts.level[0] === 'true' ? 'pass' : 'ban'"
|
||||
class="tua-svs-item"
|
||||
>
|
||||
<span>等级:{{ getLevelLabel(props.selectOpts.level[0]) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectOpts.weapon.length > 0 && props.selectOpts.weapon.length < FULL_WEAPON"
|
||||
class="tua-svs-item weapon"
|
||||
>
|
||||
<img
|
||||
v-for="(weapon, idx) in props.selectOpts.weapon"
|
||||
:key="idx"
|
||||
:alt="weapon"
|
||||
:src="`/icon/weapon/${weapon}.webp`"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectOpts.element.length > 0 && props.selectOpts.element.length < FULL_ELEMENT"
|
||||
class="tua-svs-item"
|
||||
>
|
||||
<img
|
||||
v-for="(element, idx) in props.selectOpts.element"
|
||||
:key="idx"
|
||||
:alt="element"
|
||||
:src="`/icon/element/${element}元素.webp`"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectOpts.area.length > 0 && props.selectOpts.area.length < FULL_AREA"
|
||||
class="tua-svs-item"
|
||||
>
|
||||
<span>地区:</span>
|
||||
<span v-for="(area, idx) in props.selectOpts.area" :key="idx">{{ area }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isOrdered" class="tua-sv-order">
|
||||
<div class="tua-sv-title">排序</div>
|
||||
<div
|
||||
v-if="props.isLevelUp !== null"
|
||||
:class="props.isLevelUp ? 'up' : 'down'"
|
||||
class="tua-svo-item"
|
||||
>
|
||||
<span>等级</span>
|
||||
<span>{{ props.isLevelUp ? "↑" : "↓" }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isFetterUp !== null"
|
||||
:class="props.isFetterUp ? 'up' : 'down'"
|
||||
class="tua-svo-item"
|
||||
>
|
||||
<span>好感</span>
|
||||
<span>{{ props.isFetterUp ? "↑" : "↓" }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isConstUp !== null"
|
||||
:class="props.isConstUp ? 'up' : 'down'"
|
||||
class="tua-svo-item"
|
||||
>
|
||||
<span>命座</span>
|
||||
<span>{{ props.isConstUp ? "↑" : "↓" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import type { UavSelectModel } from "./uav-select.vue";
|
||||
|
||||
type TuaSelectValsProps = {
|
||||
isLevelUp: boolean | null;
|
||||
isFetterUp: boolean | null;
|
||||
isConstUp: boolean | null;
|
||||
isSelected: boolean;
|
||||
selectOpts: UavSelectModel;
|
||||
};
|
||||
|
||||
const FULL_WEAPON: Readonly<number> = 5;
|
||||
const FULL_ELEMENT: Readonly<number> = 7;
|
||||
const FULL_AREA: Readonly<number> = 14;
|
||||
|
||||
const props = defineProps<TuaSelectValsProps>();
|
||||
|
||||
const isOrdered = computed<boolean>(() => {
|
||||
return !(props.isLevelUp === null && props.isFetterUp === null && props.isConstUp === null);
|
||||
});
|
||||
|
||||
function getFetterLabel(fetter: string): string {
|
||||
if (fetter === "true") return "已满";
|
||||
return "未满";
|
||||
}
|
||||
|
||||
function getStarLabel(star: string): string {
|
||||
if (star === "4") return "⭐⭐⭐⭐";
|
||||
return "⭐⭐⭐⭐⭐";
|
||||
}
|
||||
|
||||
function getLevelLabel(level: string): string {
|
||||
if (level === "true") return "≥70";
|
||||
return "<70";
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
|
||||
.tua-sv-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.tua-sv-title {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.tua-sv-selects {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px 12px;
|
||||
}
|
||||
|
||||
.tua-svs-item {
|
||||
@include github-styles.github-tag-dark-gen(#61afef);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
border-radius: 12px;
|
||||
column-gap: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
&.pass {
|
||||
@include github-styles.github-tag-dark-gen(#98c379);
|
||||
}
|
||||
|
||||
&.ban {
|
||||
@include github-styles.github-tag-dark-gen(#e06c75);
|
||||
}
|
||||
|
||||
&.weapon {
|
||||
img {
|
||||
filter: invert(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
& + .tua-svs-item {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tua-svs-item {
|
||||
&.weapon {
|
||||
img {
|
||||
filter: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tua-sv-order {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
.tua-svo-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
border-radius: 12px;
|
||||
column-gap: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
&.down {
|
||||
@include github-styles.github-tag-dark-gen(#98c379);
|
||||
}
|
||||
|
||||
&.up {
|
||||
@include github-styles.github-tag-dark-gen(#e06c75);
|
||||
}
|
||||
|
||||
& + .tua-svo-item {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,6 +8,12 @@
|
||||
<UavSelectChips v-model:selected="costumeSelected" :items="costumeOpts" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">好感</div>
|
||||
<div class="uav-select-props">
|
||||
<UavSelectChips v-model:selected="fetterSelected" :items="fetterOpts" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">星级</div>
|
||||
<div class="uav-select-props">
|
||||
@@ -15,8 +21,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">武器</div>
|
||||
<div class="uav-select-title">等级</div>
|
||||
<div class="uav-select-props">
|
||||
<UavSelectChips v-model:selected="levelSelected" :items="levelOpts" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="uav-select-item">
|
||||
<div class="uav-select-title">武器</div>
|
||||
<div class="uav-select-props weapon">
|
||||
<UavSelectChips v-model:selected="weaponSelected" :items="weaponOpts" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,8 +63,12 @@ import { ref, watch } from "vue";
|
||||
export type UavSelectModel = {
|
||||
/** 皮肤 */
|
||||
costume: Array<string>;
|
||||
/** 满好感 */
|
||||
fetter: Array<string>;
|
||||
/** 星级 */
|
||||
star: Array<string>;
|
||||
/** 等级 */
|
||||
level: Array<string>;
|
||||
/** 武器 */
|
||||
weapon: Array<string>;
|
||||
/** 元素 */
|
||||
@@ -67,10 +83,18 @@ const costumeOpts: Array<UavSelectChipsItem> = [
|
||||
{ label: "有", value: "true", title: "有衣装" },
|
||||
{ label: "无", value: "false", title: "无衣装" },
|
||||
];
|
||||
const fetterOpts: Array<UavSelectChipsItem> = [
|
||||
{ label: "已满", value: "true", title: "满好感" },
|
||||
{ label: "未满", value: "false", title: "好感未满" },
|
||||
];
|
||||
const starOpts: Array<UavSelectChipsItem> = [
|
||||
{ label: "⭐⭐⭐⭐", value: "4", title: "四星" },
|
||||
{ label: "⭐⭐⭐⭐⭐", value: "5", title: "五星" },
|
||||
];
|
||||
const levelOpts: Array<UavSelectChipsItem> = [
|
||||
{ label: "≥70", value: "true", title: "不低于70级" },
|
||||
{ label: "<70", value: "false", title: "低于70级" },
|
||||
];
|
||||
const weaponOpts: Array<UavSelectChipsItem> = ["单手剑", "双手剑", "弓", "法器", "长柄武器"].map(
|
||||
(i) => ({ label: i, value: i, title: i, icon: `/icon/weapon/${i}.webp` }),
|
||||
);
|
||||
@@ -97,13 +121,15 @@ const areaOpts: Array<UavSelectChipsItem> = [
|
||||
const emits = defineEmits<UavSelectEmits>();
|
||||
|
||||
const costumeSelected = ref<Array<string>>([]);
|
||||
const fetterSelected = ref<Array<string>>([]);
|
||||
const starSelected = ref<Array<string>>([]);
|
||||
const levelSelected = ref<Array<string>>([]);
|
||||
const weaponSelected = ref<Array<string>>([]);
|
||||
const elementSelected = ref<Array<string>>([]);
|
||||
const areaSelected = ref<Array<string>>([]);
|
||||
|
||||
const model = defineModel<UavSelectModel>({
|
||||
default: { costume: [], star: [], weapon: [], area: [], element: [] },
|
||||
default: { costume: [], fetter: [], star: [], weapon: [], area: [], element: [] },
|
||||
});
|
||||
const visible = defineModel<boolean>("show");
|
||||
|
||||
@@ -112,7 +138,9 @@ watch(
|
||||
() => {
|
||||
if (visible.value) {
|
||||
costumeSelected.value = model.value.costume;
|
||||
fetterSelected.value = model.value.fetter;
|
||||
starSelected.value = model.value.star;
|
||||
levelSelected.value = model.value.level;
|
||||
weaponSelected.value = model.value.weapon;
|
||||
areaSelected.value = model.value.area;
|
||||
elementSelected.value = model.value.element;
|
||||
@@ -127,7 +155,9 @@ function onCancel(): void {
|
||||
function onConfirm(): void {
|
||||
model.value = {
|
||||
costume: costumeSelected.value,
|
||||
fetter: fetterSelected.value,
|
||||
star: starSelected.value,
|
||||
level: levelSelected.value,
|
||||
weapon: weaponSelected.value,
|
||||
element: elementSelected.value,
|
||||
area: areaSelected.value,
|
||||
@@ -162,6 +192,14 @@ function onConfirm(): void {
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.uav-select-props.weapon:deep(img) {
|
||||
filter: invert(0.75);
|
||||
}
|
||||
|
||||
.dark .uav-select-props.weapon:deep(img) {
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
.uav-select-acts {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -58,20 +58,19 @@ const mixData = computed<Array<TGApp.Sqlite.Gacha.Gacha>>(() =>
|
||||
props.modelValue.filter((item) => item.uigfType === "500"),
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
<style lang="scss" scoped>
|
||||
.gro-o-swiper {
|
||||
--swiper-pagination-bottom: 16px;
|
||||
--swiper-pagination-color: var(--tgc-pink-1);
|
||||
--swiper-pagination-bullet-inactive-color: var(--tgc-od-white);
|
||||
--swiper-pagination-bullet-inactive-opacity: 1;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
/* swiper dot */
|
||||
|
||||
:deep(.swiper-pagination-bullet) {
|
||||
background: var(--tgc-od-white);
|
||||
}
|
||||
|
||||
:deep(.swiper-pagination-bullet-active) {
|
||||
background-color: var(--tgc-pink-1);
|
||||
box-shadow: 0 0 4px var(--common-shadow-4);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<div v-if="modelValue.length === 0">暂无数据</div>
|
||||
<div v-else class="tur-hg-box">
|
||||
<TurHomeSub v-for="(home, index) in modelValue" :key="index" :data="home" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TurHomeSub from "./tur-home-sub.vue";
|
||||
|
||||
defineProps<{ modelValue: Array<TGApp.Sqlite.Record.Home> }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tur-hg-box {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
</style>
|
||||
89
src/components/userRecord/tur-home-item.vue
Normal file
89
src/components/userRecord/tur-home-item.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div :title="props.name" class="tur-hi-box">
|
||||
<img ref="TurHiiRef" :src="props.icon" alt="bg" class="tur-hi-bg" @error="handleIconError" />
|
||||
<img v-if="isErr" alt="empty" class="tur-hi-empty" src="/UI/app/empty.webp" />
|
||||
<span class="tur-hi-name">{{ props.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import useAppStore from "@store/app.js";
|
||||
import { str2Color } from "@utils/colorFunc.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, ref, useTemplateRef } from "vue";
|
||||
|
||||
type TurHomeNameProps = { name: string; icon: string };
|
||||
|
||||
const { theme } = storeToRefs(useAppStore());
|
||||
|
||||
const props = defineProps<TurHomeNameProps>();
|
||||
|
||||
const isErr = ref<boolean>(false);
|
||||
const iconEl = useTemplateRef<HTMLImageElement>("TurHiiRef");
|
||||
const isDarkMode = computed<boolean>(() => theme.value === "dark");
|
||||
const color = computed<string>(() =>
|
||||
tag2Color(`${props.name}_${encodeURIComponent(props.icon)}`, isDarkMode.value),
|
||||
);
|
||||
const bg = computed<string>(() => `rgba(${color.value.slice(4, -1)}, 0.5)`);
|
||||
|
||||
function handleIconError(e: Event) {
|
||||
console.debug(e);
|
||||
if (!iconEl.value) return;
|
||||
isErr.value = true;
|
||||
iconEl.value.style.display = "none";
|
||||
}
|
||||
|
||||
function tag2Color(str: string, isDarkMode: boolean = false): string {
|
||||
const adjust = isDarkMode ? 90 : 120;
|
||||
return str2Color(str, adjust);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tur-hi-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
}
|
||||
|
||||
.tur-hi-bg {
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
object-fit: cover;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.dark .tur-hi-bg {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tur-hi-empty {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.tur-hi-name {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 6px;
|
||||
backdrop-filter: blur(10px);
|
||||
background: v-bind(bg);
|
||||
border-bottom-left-radius: 12px;
|
||||
color: v-bind(color);
|
||||
line-height: 24px;
|
||||
text-shadow: 0 0 4px rgb(0 0 0 / 50%);
|
||||
}
|
||||
</style>
|
||||
109
src/components/userRecord/tur-home-overview.vue
Normal file
109
src/components/userRecord/tur-home-overview.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<!-- 尘歌壶数据汇总 -->
|
||||
<template>
|
||||
<div class="tur-ho-container">
|
||||
<div class="tur-hoc-overview">
|
||||
<div v-if="overview" class="tur-hoco-item">
|
||||
<img :src="overview.comfortIcon" alt="icon" />
|
||||
<span>{{ overview.comfortName }}</span>
|
||||
</div>
|
||||
<div class="tur-hoco-item">
|
||||
<span>{{ props.homes.length }}</span>
|
||||
<span>解锁洞天</span>
|
||||
</div>
|
||||
<div class="tur-hoco-item">
|
||||
<span>{{ overview.level ?? 0 }}</span>
|
||||
<span>信任等阶</span>
|
||||
</div>
|
||||
<div class="tur-hoco-item">
|
||||
<span>{{ overview.comfort ?? 0 }}</span>
|
||||
<span>最高洞天仙力</span>
|
||||
</div>
|
||||
<div class="tur-hoco-item">
|
||||
<span>{{ overview.furniture ?? 0 }}</span>
|
||||
<span>获得摆设数</span>
|
||||
</div>
|
||||
<div class="tur-hoco-item">
|
||||
<span>{{ overview.visit ?? 0 }}</span>
|
||||
<span>历史访客数</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.homes.length > 0" class="tur-hoc-list">
|
||||
<TurHomeItem
|
||||
v-for="(item, idx) in props.homes"
|
||||
:key="idx"
|
||||
:icon="item.bg"
|
||||
:name="item.name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
import TurHomeItem from "./tur-home-item.vue";
|
||||
|
||||
type TurHomeOverviewProps = { homes: Array<TGApp.Sqlite.Record.Home> };
|
||||
|
||||
const props = defineProps<TurHomeOverviewProps>();
|
||||
const overview = computed<TGApp.Sqlite.Record.Home>(() => props.homes[0] ?? undefined);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
|
||||
.tur-ho-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.tur-hoc-overview {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
.tur-hoco-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
row-gap: 4px;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
span {
|
||||
&:first-child {
|
||||
color: var(--tgc-yellow-1);
|
||||
font-family: var(--font-text);
|
||||
text-shadow: 0 0 2px #0d1117;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tur-hoc-list {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 0.5fr));
|
||||
}
|
||||
</style>
|
||||
@@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<div class="tur-hs-box">
|
||||
<div class="bg">
|
||||
<img :src="data.bg" alt="bg" />
|
||||
</div>
|
||||
<div class="tur-hs-top">
|
||||
<div class="tur-hs-title">
|
||||
<img :src="data.comfortIcon" alt="icon" />
|
||||
<span>{{ data.comfortName }}</span>
|
||||
</div>
|
||||
<div class="tur-hs-name">{{ data.name }}</div>
|
||||
</div>
|
||||
<div class="tur-hs-text-grid">
|
||||
<div class="tur-hs-text">
|
||||
<div>{{ data.level }}</div>
|
||||
<div>信任等阶</div>
|
||||
</div>
|
||||
<div class="tur-hs-text">
|
||||
<div>{{ data.comfort }}</div>
|
||||
<div>最高洞天仙力</div>
|
||||
</div>
|
||||
<div class="tur-hs-text">
|
||||
<div>{{ data.furniture }}</div>
|
||||
<div>获得摆设数</div>
|
||||
</div>
|
||||
<div class="tur-hs-text">
|
||||
<div>{{ data.visit }}</div>
|
||||
<div>历史访客数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ data: TGApp.Sqlite.Record.Home }>();
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tur-hs-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
right: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tur-hs-top {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.tur-hs-name {
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-text);
|
||||
font-size: 16px;
|
||||
text-shadow: 0 0 4px var(--tgc-yellow-1);
|
||||
}
|
||||
|
||||
.tur-hs-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--tgc-white-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 18px;
|
||||
text-shadow: 0 0 4px var(--tgc-yellow-1);
|
||||
}
|
||||
|
||||
.tur-hs-title img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.tur-hs-text-grid {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
background: #00000066;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
color: var(--tgc-white-1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tur-hs-text :nth-child(1) {
|
||||
color: var(--tgc-yellow-1);
|
||||
font-family: var(--font-text);
|
||||
}
|
||||
|
||||
.tur-hs-text :nth-child(2) {
|
||||
font-family: var(--font-title);
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -36,14 +36,17 @@ const props = defineProps<TAOProps>();
|
||||
|
||||
.tur-os-label {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
right: 2px;
|
||||
bottom: 0;
|
||||
bottom: -2px;
|
||||
color: var(--box-text-4);
|
||||
font-family: var(--font-text);
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.tur-os-text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -226,7 +226,7 @@ async function tryAuto(skip: boolean = false): Promise<void> {
|
||||
}
|
||||
viewCnt++;
|
||||
if (likeCnt < 5) {
|
||||
const isLike = detailResp.self_operation.upvote_type === 1;
|
||||
const isLike = (detailResp.self_operation?.upvote_type ?? 0) > 0;
|
||||
if (isLike) {
|
||||
await TGLogger.Script(`[米游币任务]帖子${post.post.post_id}已点赞,跳过`);
|
||||
continue;
|
||||
|
||||
@@ -271,6 +271,12 @@ async function trySign(
|
||||
const signResp = await lunaReq.sign.oper(item.account, cookie, challenge);
|
||||
console.log("签到信息", item, signResp);
|
||||
if (challenge !== undefined) challenge = undefined;
|
||||
if (typeof signResp !== "object") {
|
||||
await TGLogger.Script(
|
||||
`[签到任务]${item.info.title}-${item.account.regionName}-${item.account.gameUid} ${signResp}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if ("retcode" in signResp) {
|
||||
if (signResp.retcode === 1034) {
|
||||
if (skip) {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div class="tp-avatar-box">
|
||||
<div class="tpa-img">
|
||||
<div class="tpa-icon">
|
||||
<TMiImg :ori="true" :src="getUserAvatar(props.data)" alt="avatar" />
|
||||
<img v-if="avatarUrl.endsWith('.gif')" :src="avatarUrl" alt="avatar" />
|
||||
<TMiImg v-else :ori="true" :src="avatarUrl" alt="avatar" />
|
||||
</div>
|
||||
<div v-if="props.data.pendant !== ''" class="tpa-pendant">
|
||||
<TMiImg :ori="true" :src="props.data.pendant" alt="pendant" />
|
||||
@@ -32,11 +33,11 @@ type TpAvatarProps = { data: TGApp.BBS.Post.User; position: "left" | "right" };
|
||||
|
||||
const props = defineProps<TpAvatarProps>();
|
||||
|
||||
const avatarUrl = computed<string>(() => getUserAvatar(props.data));
|
||||
const authorDesc = computed<string>(() => {
|
||||
if (props.data.certification.label !== "") return props.data.certification.label;
|
||||
return props.data.introduce;
|
||||
});
|
||||
|
||||
const levelColor = computed<string>(() => {
|
||||
if (!props.data.level_exp) return "var(--tgc-od-white)";
|
||||
const level = props.data.level_exp.level;
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
@click="handleEmoticonClick"
|
||||
@error="handleEmoticonError"
|
||||
/>
|
||||
<div v-if="props.data.insert.custom_emoticon.size.width > 100" class="tp-emo-info">
|
||||
自定义表情
|
||||
</div>
|
||||
<div v-if="showLabel" class="tp-emo-info">自定义表情</div>
|
||||
</div>
|
||||
<div v-else :title="props.data.insert.custom_emoticon.url" class="tp-image-load">
|
||||
<v-progress-circular :indeterminate="true" color="blue" size="small" />
|
||||
@@ -86,6 +84,11 @@ const image = computed<TpImage>(() => ({
|
||||
size: props.data.insert.custom_emoticon.size.file_size,
|
||||
},
|
||||
}));
|
||||
const showLabel = computed<boolean>(() => {
|
||||
if (props.data.insert.custom_emoticon.size.width > 100) return true;
|
||||
if (!emoticonEl.value) return false;
|
||||
return emoticonEl.value.clientWidth > 100;
|
||||
});
|
||||
|
||||
console.log("tp-emoticon", props.data.insert.custom_emoticon);
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ async function switchCollect(): Promise<void> {
|
||||
@include github-styles.github-card;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 64px;
|
||||
right: 16px;
|
||||
display: flex;
|
||||
|
||||
@@ -211,6 +211,7 @@ async function handleDebug(): Promise<void> {
|
||||
<style lang="scss" scoped>
|
||||
.tpr-main-box {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
<TOverlay v-model="visible" blur-val="5px">
|
||||
<div class="tpoc-box">
|
||||
<div class="tpoc-top">
|
||||
<span>{{ props.collection.collection_title }}</span>
|
||||
<span @click="toOuterCollect()">{{ props.collection.collection_title }}</span>
|
||||
<span>合集ID:{{ props.collection.collection_id }}</span>
|
||||
</div>
|
||||
<div class="tpoc-list" ref="postListRef">
|
||||
<div class="tpoc-load" v-if="postList.length === 0">
|
||||
<v-progress-circular indeterminate color="blue" size="24" />
|
||||
<div ref="postListRef" class="tpoc-list">
|
||||
<div v-if="postList.length === 0" class="tpoc-load">
|
||||
<v-progress-circular color="blue" indeterminate size="24" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<TPostcard
|
||||
class="tpoc-item"
|
||||
v-for="(item, index) in postList"
|
||||
:key="index"
|
||||
:model-value="item"
|
||||
:class="{ selected: index === props.collection.cur - 1 }"
|
||||
:model-value="item"
|
||||
class="tpoc-item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,6 +26,7 @@ import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import TPostcard from "@comp/app/t-postcard.vue";
|
||||
import bbsReq from "@req/bbsReq.js";
|
||||
import postReq from "@req/postReq.js";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { nextTick, onMounted, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
type TpoCollectionProps = { collection: TGApp.BBS.Post.Collection; gid: number };
|
||||
@@ -64,6 +65,10 @@ async function refreshInfo(): Promise<void> {
|
||||
async function refreshPosts(): Promise<void> {
|
||||
postList.value = await postReq.collection(props.collection.collection_id);
|
||||
}
|
||||
|
||||
async function toOuterCollect(): Promise<void> {
|
||||
await openUrl(`https://www.miyoushe.com/ys/collection/${props.collection.collection_id}`);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tpoc-box {
|
||||
@@ -73,21 +78,23 @@ async function refreshPosts(): Promise<void> {
|
||||
}
|
||||
|
||||
.tpoc-top {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--common-shadow-2);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tpoc-top :nth-child(1) {
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
:first-child {
|
||||
color: var(--common-text-title);
|
||||
cursor: pointer;
|
||||
font-family: var(--font-title);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tpoc-top :nth-child(2) {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
:last-child {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.tpoc-list {
|
||||
|
||||
@@ -8,52 +8,55 @@
|
||||
<div class="tpoi-bottom">
|
||||
<template v-if="typeof props.image.insert.image !== 'string'">
|
||||
<div class="tpoi-info">
|
||||
<p class="tpoi-info-item">
|
||||
<span class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(Number(props.image.insert.image.size) ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
</span>
|
||||
<span class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>
|
||||
{{ props.image.insert.image.width }}x{{ props.image.insert.image.height }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
</span>
|
||||
<span class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="props.image.attributes">
|
||||
<div class="tpoi-info">
|
||||
<p v-if="props.image.attributes.size" class="tpoi-info-item">
|
||||
<span v-if="props.image.attributes.size" class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(props.image.attributes.size ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
</span>
|
||||
<span
|
||||
v-if="props.image.attributes?.width && props.image.attributes?.height"
|
||||
class="tpoi-info-item"
|
||||
>
|
||||
<span>尺寸:</span>
|
||||
<span>{{ props.image.attributes.width }}x{{ props.image.attributes.height }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
</span>
|
||||
<span class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tpoi-tools">
|
||||
<v-icon @click="setBlackBg" title="切换背景色" v-if="showOri">
|
||||
<v-icon v-if="showOri" title="切换背景色" @click="setBlackBg">
|
||||
mdi-format-color-fill
|
||||
</v-icon>
|
||||
<v-icon @click="showOri = true" title="查看原图" v-else>mdi-magnify</v-icon>
|
||||
<v-icon @click="onCopy" title="复制到剪贴板" v-if="showCopy">mdi-content-copy</v-icon>
|
||||
<v-icon @click="onDownload" title="下载到本地">mdi-download</v-icon>
|
||||
<v-icon @click="visible = false" title="关闭浮窗">mdi-close</v-icon>
|
||||
<v-icon v-else title="查看原图" @click="showOri = true">mdi-magnify</v-icon>
|
||||
<v-icon v-if="showCopy" title="复制到剪贴板" @click="onCopy">mdi-content-copy</v-icon>
|
||||
<v-icon title="下载到本地" @click="onDownload">mdi-download</v-icon>
|
||||
<v-icon title="关闭浮窗" @click="visible = false">mdi-close</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
|
||||
@@ -111,7 +111,7 @@ watch(
|
||||
search.value = props.keyword;
|
||||
return;
|
||||
}
|
||||
if (search.value !== props.keyword && props.keyword !== "") {
|
||||
if (search.value !== props.keyword && props.keyword !== "" && props.keyword !== null) {
|
||||
search.value = props.keyword;
|
||||
results.value = [];
|
||||
lastId.value = "";
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<TOverlay v-model="visible">
|
||||
<div class="vp-ou-box">
|
||||
<div class="vp-ou-user" v-if="userInfo">
|
||||
<div v-if="userInfo" class="vp-ou-user" @click="toUserProfile()">
|
||||
<div class="vp-ouu-info">
|
||||
<div class="left">
|
||||
<div class="avatar">
|
||||
<TMiImg :src="getUserAvatar(userInfo)" alt="avatar" :ori="true" />
|
||||
<img v-if="avatarUrl.endsWith('.gif')" :src="avatarUrl" alt="avatar" />
|
||||
<TMiImg v-else :ori="true" :src="avatarUrl" alt="avatar" />
|
||||
</div>
|
||||
<div class="pendant" v-if="userInfo.pendant !== ''">
|
||||
<TMiImg :src="userInfo.pendant" alt="pendant" :ori="true" />
|
||||
<div v-if="userInfo.pendant !== ''" class="pendant">
|
||||
<TMiImg :ori="true" :src="userInfo.pendant" alt="pendant" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
@@ -16,29 +17,29 @@
|
||||
<div class="nickname">{{ userInfo.nickname }}</div>
|
||||
<div class="level">Lv.{{ userInfo.level_exp.level }}</div>
|
||||
</div>
|
||||
<div class="desc" :title="userInfo.introduce">{{ userInfo.introduce }}</div>
|
||||
<div :title="userInfo.introduce" class="desc">{{ userInfo.introduce }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vp-ou-mid">
|
||||
<div class="vp-ouu-extra" v-if="userInfo">
|
||||
<div v-if="userInfo" class="vp-ouu-extra">
|
||||
<span>ID:{{ userInfo.uid }}</span>
|
||||
<span>IP:{{ userInfo.ip_region }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vp-ou-divider" />
|
||||
<div class="vp-ou-list" ref="listRef">
|
||||
<div ref="listRef" class="vp-ou-list">
|
||||
<TPostCard
|
||||
class="vp-ou-item"
|
||||
:model-value="item"
|
||||
v-for="item in results"
|
||||
:key="item.post.post_id"
|
||||
:model-value="item"
|
||||
class="vp-ou-item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import TPostCard from "@comp/app/t-postcard.vue";
|
||||
@@ -46,6 +47,7 @@ import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { useBoxReachBottom } from "@hooks/reachBottom.js";
|
||||
import bbsReq from "@req/bbsReq.js";
|
||||
import postReq from "@req/postReq.js";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { getUserAvatar } from "@utils/toolFunc.js";
|
||||
import { computed, ref, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
@@ -61,6 +63,10 @@ const isLast = ref<boolean>(false);
|
||||
const load = ref<boolean>(false);
|
||||
const userInfo = shallowRef<TGApp.BBS.User.Info>();
|
||||
const results = shallowRef<Array<TGApp.BBS.Post.FullData>>([]);
|
||||
const avatarUrl = computed<string>(() => {
|
||||
if (!userInfo.value) return "";
|
||||
return getUserAvatar(userInfo.value);
|
||||
});
|
||||
const levelColor = computed<string>(() => {
|
||||
if (!userInfo.value) return "var(--tgc-od-white)";
|
||||
const level = userInfo.value.level_exp.level;
|
||||
@@ -107,6 +113,12 @@ async function loadUser(): Promise<void> {
|
||||
userInfo.value = resp;
|
||||
}
|
||||
|
||||
async function toUserProfile(): Promise<void> {
|
||||
// TODO: 专门的个人页面
|
||||
const profileUrl = `https://www.miyoushe.com/ys/accountCenter/postList?id=${props.uid}`;
|
||||
await openUrl(profileUrl);
|
||||
}
|
||||
|
||||
async function loadPosts(): Promise<void> {
|
||||
if (load.value) return;
|
||||
load.value = true;
|
||||
@@ -152,6 +164,7 @@ async function loadPosts(): Promise<void> {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<template>
|
||||
<div class="tpr-reply-box" :id="replyId">
|
||||
<div ref="VpReplyRef" class="tpr-reply-box">
|
||||
<div
|
||||
class="tpr-bubble"
|
||||
v-if="props.modelValue.user.reply_bubble !== null"
|
||||
:title="props.modelValue.user.reply_bubble.name"
|
||||
class="tpr-bubble"
|
||||
>
|
||||
<TMiImg :ori="true" :src="props.modelValue.user.reply_bubble.url" alt="bubble" />
|
||||
</div>
|
||||
<div class="tpr-user" @click="handleUser()">
|
||||
<div class="tpru-left">
|
||||
<div class="avatar">
|
||||
<TMiImg :ori="true" :src="getUserAvatar(props.modelValue.user)" alt="avatar" />
|
||||
<img v-if="avatarUrl.endsWith('.gif')" :src="avatarUrl" alt="avatar" />
|
||||
<TMiImg v-else :ori="true" :src="avatarUrl" alt="avatar" />
|
||||
</div>
|
||||
<div class="pendant" v-if="props.modelValue.user.pendant !== ''">
|
||||
<div v-if="props.modelValue.user.pendant !== ''" class="pendant">
|
||||
<TMiImg :ori="true" :src="props.modelValue.user.pendant" alt="pendant" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tpru-right" :title="props.modelValue.user.nickname">
|
||||
<div :title="props.modelValue.user.nickname" class="tpru-right">
|
||||
<span>{{ props.modelValue.user.nickname }}</span>
|
||||
<span class="level">Lv.{{ props.modelValue.user.level_exp.level }}</span>
|
||||
<span v-if="props.modelValue.is_lz" class="tpru-lz">楼主</span>
|
||||
@@ -33,7 +34,7 @@
|
||||
<span>{{ props.modelValue.user.ip_region }}</span>
|
||||
</div>
|
||||
<div class="tpri-right">
|
||||
<span title="点赞数" class="tpr-like">
|
||||
<span class="tpr-like" title="点赞数">
|
||||
<v-icon size="small">mdi-thumb-up</v-icon>
|
||||
{{ props.modelValue.stat.like_num }}
|
||||
</span>
|
||||
@@ -46,16 +47,16 @@
|
||||
<v-icon size="small">mdi-message-text</v-icon>
|
||||
<span>{{ props.modelValue.sub_reply_count }}</span>
|
||||
<v-menu
|
||||
submenu
|
||||
v-model="showSub"
|
||||
:close-on-content-click="false"
|
||||
activator="parent"
|
||||
location="end"
|
||||
:close-on-content-click="false"
|
||||
v-model="showSub"
|
||||
submenu
|
||||
>
|
||||
<v-list
|
||||
class="tpr-reply-sub"
|
||||
width="300px"
|
||||
max-height="400px"
|
||||
width="300px"
|
||||
@scroll="handleSubScroll"
|
||||
>
|
||||
<VpReplyItem
|
||||
@@ -68,25 +69,25 @@
|
||||
<v-chip color="blue" label>没有更多了</v-chip>
|
||||
</div>
|
||||
<div v-else class="tpr-list-item">
|
||||
<v-btn @click="loadSub()" color="blue" :loading="loading">加载更多</v-btn>
|
||||
<v-btn :loading="loading" color="blue" @click="loadSub()">加载更多</v-btn>
|
||||
</div>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tpr-extra" :title="`ID:${props.modelValue.reply.reply_id}`">
|
||||
<div class="tpr-share" @click="share" data-html2canvas-ignore title="分享">
|
||||
<div :title="`ID:${props.modelValue.reply.reply_id}`" class="tpr-extra">
|
||||
<div class="tpr-share" data-html2canvas-ignore title="分享" @click="share">
|
||||
<v-icon size="small">mdi-share-variant</v-icon>
|
||||
</div>
|
||||
<span
|
||||
class="tpr-pin"
|
||||
v-if="props.mode === 'main' && props.modelValue.reply.reply_id === props.pinId"
|
||||
class="tpr-pin"
|
||||
>
|
||||
<v-icon size="small">mdi-pin</v-icon>
|
||||
<span>置顶评论</span>
|
||||
</span>
|
||||
<span class="tpr-debug" @click="exportData" data-html2canvas-ignore title="导出数据">
|
||||
<span class="tpr-debug" data-html2canvas-ignore title="导出数据" @click="exportData">
|
||||
<v-icon size="small">mdi-file-export</v-icon>
|
||||
</span>
|
||||
<span v-if="props.modelValue.r_user" class="tpr-reply-user">
|
||||
@@ -115,7 +116,16 @@ import { save } from "@tauri-apps/plugin-dialog";
|
||||
import { writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { getNearTime, getUserAvatar, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { computed, onMounted, onUnmounted, ref, shallowRef, toRaw, watch } from "vue";
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
useTemplateRef,
|
||||
watch,
|
||||
} from "vue";
|
||||
|
||||
import TpParser from "./tp-parser.vue";
|
||||
|
||||
@@ -124,18 +134,21 @@ type TprReplyProps =
|
||||
| { mode: "main"; modelValue: TGApp.BBS.Reply.ReplyFull; pinId?: string };
|
||||
|
||||
const props = defineProps<TprReplyProps>();
|
||||
const replyId = `reply_${props.modelValue.reply.post_id}_${props.modelValue.reply.floor_id}_${props.modelValue.reply.reply_id}`;
|
||||
const replyLabel = `reply_${props.modelValue.reply.post_id}_${props.modelValue.reply.floor_id}_${props.modelValue.reply.reply_id}`;
|
||||
let subListener: UnlistenFn | null = null;
|
||||
let closeSubListener: UnlistenFn | null = null;
|
||||
|
||||
console.log("TprReply", toRaw(props.modelValue));
|
||||
|
||||
const existingIds = new Set<string>();
|
||||
|
||||
const showSub = ref<boolean>(false);
|
||||
const lastId = ref<string>();
|
||||
const isLast = ref<boolean>(false);
|
||||
const loading = ref<boolean>(false);
|
||||
const subReplies = shallowRef<Array<TGApp.BBS.Reply.ReplyFull>>([]);
|
||||
const existingIds = new Set<string>();
|
||||
const vpReplyEl = useTemplateRef<HTMLDivElement>("VpReplyRef");
|
||||
const avatarUrl = computed<string>(() => getUserAvatar(props.modelValue.user));
|
||||
const levelColor = computed<string>(() => {
|
||||
const level = props.modelValue.user.level_exp.level;
|
||||
if (level < 5) return "var(--tgc-od-green)";
|
||||
@@ -196,9 +209,11 @@ function handleSubScroll(e: globalThis.Event): void {
|
||||
}
|
||||
|
||||
async function share(): Promise<void> {
|
||||
const replyDom = document.querySelector<HTMLElement>(`#${replyId}`);
|
||||
if (replyDom === null) return;
|
||||
await generateShareImg(replyId, replyDom, 3);
|
||||
if (!vpReplyEl.value) {
|
||||
showSnackbar.warn("未找到分享Dom");
|
||||
return;
|
||||
}
|
||||
await generateShareImg(replyLabel, vpReplyEl.value, 3);
|
||||
}
|
||||
|
||||
async function showReply(): Promise<void> {
|
||||
@@ -252,7 +267,7 @@ async function exportData(): Promise<void> {
|
||||
const savePath = await save({
|
||||
title: "导出回复数据",
|
||||
filters: [{ name: "JSON", extensions: ["json"] }],
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}${replyId}.json`,
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}${replyLabel}.json`,
|
||||
});
|
||||
if (savePath === null) {
|
||||
showSnackbar.cancel("已取消");
|
||||
@@ -280,6 +295,7 @@ async function handleUser(): Promise<void> {
|
||||
border: 1px solid var(--common-shadow-1);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--app-page-content);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
|
||||
@@ -3226,5 +3226,41 @@
|
||||
12402, 12401, 13407, 13401, 14410, 14409, 14403, 14402, 14401, 15412, 15410, 15405, 15403,
|
||||
15402, 15401
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "虚星临渡",
|
||||
"version": "6.4",
|
||||
"order": 2,
|
||||
"banner": "https://sdk-webstatic.mihoyo.com/upload/ann/2026/03/03/4c5b2ae5acc0c4a37e0ac6e5000eda82_7442422502466078931_transformed.jpg",
|
||||
"from": "2026-03-17T18:00:00+08:00",
|
||||
"to": "2026-04-07T14:59:00+08:00",
|
||||
"type": 301,
|
||||
"postId": "73885913",
|
||||
"up5List": [10000114],
|
||||
"up4List": [10000115, 10000072, 10000088]
|
||||
},
|
||||
{
|
||||
"name": "莓色香颂",
|
||||
"version": "6.4",
|
||||
"order": 2,
|
||||
"banner": "https://sdk-webstatic.mihoyo.com/upload/ann/2026/03/03/31193e9b9ece7627bf7accda42b54956_6780122588782148962_transformed.jpg",
|
||||
"from": "2026-03-17T18:00:00+08:00",
|
||||
"to": "2026-04-07T14:59:00+08:00",
|
||||
"type": 400,
|
||||
"postId": "73885912",
|
||||
"up5List": [10000112],
|
||||
"up4List": [10000115, 10000072, 10000088]
|
||||
},
|
||||
{
|
||||
"name": "神铸赋形",
|
||||
"version": "6.4",
|
||||
"order": 2,
|
||||
"banner": "https://sdk-webstatic.mihoyo.com/upload/ann/2026/03/03/0c705f5ed9c06fb91b8f495d9f7f1c0c_1780288503577900005_transformed.jpg",
|
||||
"from": "2026-03-17T18:00:00+08:00",
|
||||
"to": "2026-04-07T14:59:00+08:00",
|
||||
"type": 302,
|
||||
"postId": "73885911",
|
||||
"up5List": [11517, 13514],
|
||||
"up4List": [11402, 12402, 13407, 14401, 15405]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -94,12 +94,7 @@
|
||||
<div class="uaw-o-box">
|
||||
<TuaOverview :val-text="item.totalBattleTimes" title="战斗次数" />
|
||||
<TuaOverview :val-text="item.totalStar" title="获得渊星" />
|
||||
<TuaOverview
|
||||
:val-text="
|
||||
item.skippedFloor !== '' ? `${item.maxFloor}(${item.skippedFloor})` : item.maxFloor
|
||||
"
|
||||
title="最深抵达"
|
||||
/>
|
||||
<TuaOverview :val-text="getMaxFloor(item)" title="最深抵达" />
|
||||
<TuaOverview :val-icons="item.defeatRank" title="最多击破" />
|
||||
<TuaOverview :val-icons="item.takeDamageRank" title="最多承伤" />
|
||||
<TuaOverview :val-icons="item.damageRank" title="最强一击" />
|
||||
@@ -142,6 +137,7 @@ import useUserStore from "@store/user.js";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
@@ -175,6 +171,13 @@ watch(
|
||||
async () => await reloadUid(),
|
||||
);
|
||||
|
||||
function getMaxFloor(abyss: TGApp.Sqlite.Abyss.TableTrans): string {
|
||||
if (abyss.skippedFloor !== null && abyss.skippedFloor !== "") {
|
||||
return `${abyss.maxFloor}(${abyss.skippedFloor})`;
|
||||
}
|
||||
return `${abyss.maxFloor}`;
|
||||
}
|
||||
|
||||
async function reloadUid(uid?: string): Promise<void> {
|
||||
uidList.value = await TSUserAbyss.getAllUid();
|
||||
if (uidList.value.length === 0) uidList.value = [account.value.gameUid];
|
||||
@@ -307,12 +310,21 @@ async function deleteAbyss(): Promise<void> {
|
||||
await loadAbyss();
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试读取胡桃工具箱导出的深渊数据
|
||||
* @since Beta v0.8.6
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
/** 尝试读取胡桃工具箱导出的深渊数据 */
|
||||
async function tryReadAbyss(): Promise<void> {
|
||||
const checkF = await showDialog.checkF({
|
||||
title: "确认导入外部数据?",
|
||||
text: "仅适用于特定工具导出的胡桃深渊数据\n(不适配本应用备份数据)",
|
||||
cancelLabel: "查看详细说明",
|
||||
});
|
||||
if (checkF === undefined) {
|
||||
showSnackbar.cancel("取消导入深渊数据");
|
||||
return;
|
||||
}
|
||||
if (!checkF) {
|
||||
await openUrl("https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html");
|
||||
return;
|
||||
}
|
||||
const file = await open({
|
||||
multiple: false,
|
||||
title: "选择胡桃工具箱导出的深渊数据文件",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="ucp-top-append">
|
||||
<div class="act-list">
|
||||
<v-btn
|
||||
:disabled="localChallenge.length === 0"
|
||||
:disabled="localChallenge.length === 0 || isRefresh"
|
||||
class="ucp-btn"
|
||||
prepend-icon="mdi-share"
|
||||
variant="elevated"
|
||||
@@ -38,6 +38,7 @@
|
||||
分享
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:loading="isRefresh"
|
||||
class="ucp-btn"
|
||||
prepend-icon="mdi-refresh"
|
||||
variant="elevated"
|
||||
@@ -54,6 +55,7 @@
|
||||
导入
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="isRefresh"
|
||||
class="ucp-btn"
|
||||
prepend-icon="mdi-delete"
|
||||
variant="elevated"
|
||||
@@ -153,6 +155,7 @@ import useUserStore from "@store/user.js";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
@@ -171,6 +174,7 @@ const { account, cookie } = storeToRefs(useUserStore());
|
||||
const version = ref<string>();
|
||||
|
||||
const userTab = ref<number>(0);
|
||||
const isRefresh = ref<boolean>(false);
|
||||
const uidCur = ref<string>();
|
||||
const uidList = shallowRef<Array<string>>();
|
||||
const localChallenge = shallowRef<Array<TGApp.Sqlite.Challenge.TableTrans>>([]);
|
||||
@@ -185,6 +189,7 @@ onMounted(async () => {
|
||||
await TGLogger.Info("[UserCombat][onMounted] 打开幽境危战页面");
|
||||
await showLoading.update("正在获取UID列表");
|
||||
await reloadUid();
|
||||
isRefresh.value = false;
|
||||
if (uidCur.value?.startsWith("5")) server.value = gameEnum.server.CN_QD01;
|
||||
await refreshPopList(false);
|
||||
});
|
||||
@@ -282,15 +287,18 @@ async function refreshChallenge(): Promise<void> {
|
||||
}
|
||||
await TGLogger.Info("[Challenge][refreshChallenge] 开始刷新挑战数据");
|
||||
await showLoading.start(`正在获取${rfAccount.gameUid}的幽境危战数据`);
|
||||
isRefresh.value = true;
|
||||
const resp = await recordReq.challenge.detail(rfCk!, rfAccount);
|
||||
console.log(resp);
|
||||
if ("retcode" in resp) {
|
||||
isRefresh.value = false;
|
||||
await showLoading.end();
|
||||
showSnackbar.error(`[${resp.retcode}] ${resp.message}`);
|
||||
await TGLogger.Error(`[Challenge][refreshChallenge] ${resp.retcode} - ${resp.message}`);
|
||||
return;
|
||||
}
|
||||
if (!resp.is_unlock) {
|
||||
isRefresh.value = false;
|
||||
await showLoading.end();
|
||||
showSnackbar.warn("幽境危战未解锁");
|
||||
await TGLogger.Warn("[Challenge][refreshChallenge] 幽境危战未解锁");
|
||||
@@ -304,6 +312,7 @@ async function refreshChallenge(): Promise<void> {
|
||||
}
|
||||
await reloadUid(uidCur.value);
|
||||
await loadChallenge();
|
||||
isRefresh.value = false;
|
||||
await showLoading.end();
|
||||
}
|
||||
|
||||
@@ -353,12 +362,21 @@ async function refreshPopList(hint: boolean = true): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试读取胡桃工具箱导出的危战数据
|
||||
* @since Beta v0.8.6
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
/** 尝试读取胡桃工具箱导出的危战数据 */
|
||||
async function tryReadChallenge(): Promise<void> {
|
||||
const checkF = await showDialog.checkF({
|
||||
title: "确认导入外部数据?",
|
||||
text: "仅适用于特定工具导出的胡桃危战数据\n(不适配本应用备份数据)",
|
||||
cancelLabel: "查看详细说明",
|
||||
});
|
||||
if (checkF === undefined) {
|
||||
showSnackbar.cancel("取消导入危战数据");
|
||||
return;
|
||||
}
|
||||
if (!checkF) {
|
||||
await openUrl("https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html");
|
||||
return;
|
||||
}
|
||||
const file = await open({
|
||||
multiple: false,
|
||||
title: "选择胡桃工具箱导出的危战数据文件",
|
||||
|
||||
@@ -101,6 +101,13 @@
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<div class="uc-box">
|
||||
<div class="uc-box-info">
|
||||
<span>角色详情</span>
|
||||
<span>|</span>
|
||||
<span>TeyvatGuide v{{ version }}</span>
|
||||
<span>|</span>
|
||||
<span>更新于 {{ getUpdateTime() }}</span>
|
||||
</div>
|
||||
<div class="uc-box-top">
|
||||
<div class="uc-box-title">
|
||||
<TurRoleInfo v-if="roleRecord && uidCur" :role="roleRecord" :uid="uidCur" />
|
||||
@@ -119,15 +126,9 @@
|
||||
<span v-else>{{ item.cnt }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="uc-box-info">
|
||||
<span>角色详情</span>
|
||||
<span>|</span>
|
||||
<span>TeyvatGuide v{{ version }}</span>
|
||||
<span>|</span>
|
||||
<span>更新于 {{ getUpdateTime() }}</span>
|
||||
</div>
|
||||
<!-- TODO: 渲染筛选条件 -->
|
||||
<TuaSelectVals :isConstUp :isFetterUp :isLevelUp :isSelected :selectOpts />
|
||||
</div>
|
||||
<div class="uc-divider" />
|
||||
<div v-if="!isEmpty" class="uc-grid">
|
||||
<TuaAvatarBox
|
||||
v-for="(role, index) in selectedList"
|
||||
@@ -158,6 +159,7 @@ import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TuaAvatarBox from "@comp/userAvatar/tua-avatar-box.vue";
|
||||
import TuaDetailOverlay from "@comp/userAvatar/tua-detail-overlay.vue";
|
||||
import TuaSelectVals from "@comp/userAvatar/tua-select-vals.vue";
|
||||
import UavSelect, { type UavSelectModel } from "@comp/userAvatar/uav-select.vue";
|
||||
import TurRoleInfo from "@comp/userRecord/tur-role-info.vue";
|
||||
import recordReq from "@req/recordReq.js";
|
||||
@@ -203,7 +205,9 @@ const isFetterUp = ref<boolean | null>(null);
|
||||
const isConstUp = ref<boolean | null>(null);
|
||||
const selectOpts = ref<UavSelectModel>({
|
||||
costume: [],
|
||||
fetter: [],
|
||||
star: [],
|
||||
level: [],
|
||||
weapon: [],
|
||||
area: [],
|
||||
element: [],
|
||||
@@ -260,12 +264,12 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
function toggleSort(value: boolean | null): boolean {
|
||||
function toggleSort(value: boolean | null): boolean | null {
|
||||
switch (value) {
|
||||
case true:
|
||||
return false;
|
||||
case false:
|
||||
return true;
|
||||
return null;
|
||||
case null:
|
||||
return true;
|
||||
}
|
||||
@@ -297,7 +301,15 @@ function resetList(): void {
|
||||
isLevelUp.value = null;
|
||||
isFetterUp.value = null;
|
||||
isConstUp.value = null;
|
||||
selectOpts.value = { costume: [], star: [], weapon: [], area: [], element: [] };
|
||||
selectOpts.value = {
|
||||
costume: [],
|
||||
fetter: [],
|
||||
star: [],
|
||||
level: [],
|
||||
weapon: [],
|
||||
area: [],
|
||||
element: [],
|
||||
};
|
||||
selectedList.value = getOrderedList(roleList.value);
|
||||
showSnackbar.success("已重置筛选条件");
|
||||
if (!dataVal.value) return;
|
||||
@@ -544,6 +556,14 @@ function handleSelect(val: UavSelectModel): void {
|
||||
const filterC = roleList.value.filter((role) => {
|
||||
const info = AppCharacterData.find((i) => i.id === role.cid);
|
||||
if (val.star.length > 0 && !val.star.includes(role.avatar.rarity.toString())) return false;
|
||||
if (val.level.length > 0) {
|
||||
if (!val.level.includes("true") && role.avatar.level >= 70) return false;
|
||||
if (!val.level.includes("false") && role.avatar.level < 70) return false;
|
||||
}
|
||||
if (val.fetter.length > 0) {
|
||||
if (!val.fetter.includes("true") && role.avatar.fetter === 10) return false;
|
||||
if (!val.fetter.includes("false") && role.avatar.fetter !== 10) return false;
|
||||
}
|
||||
if (val.weapon.length > 0 && !val.weapon.includes(role.weapon.type_name)) return false;
|
||||
if (val.element.length > 0 && !val.element.includes(getZhElement(role.avatar.element)))
|
||||
return false;
|
||||
@@ -645,6 +665,7 @@ function handleSwitch(next: boolean): void {
|
||||
}
|
||||
|
||||
.uc-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
@@ -658,10 +679,18 @@ function handleSwitch(next: boolean): void {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--common-shadow-2);
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.uc-divider {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-radius: 1px;
|
||||
background: var(--common-shadow-2);
|
||||
}
|
||||
|
||||
.uc-box-title {
|
||||
@@ -705,8 +734,8 @@ function handleSwitch(next: boolean): void {
|
||||
.uc-box-info {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 2px;
|
||||
right: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -164,6 +164,7 @@ import useUserStore from "@store/user.js";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
@@ -480,12 +481,21 @@ async function uploadCombat(): Promise<void> {
|
||||
await showLoading.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试读取胡桃工具箱导出的剧诗数据
|
||||
* @since Beta v0.8.6
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
/** 尝试读取胡桃工具箱导出的剧诗数据 */
|
||||
async function tryReadCombat(): Promise<void> {
|
||||
const checkF = await showDialog.checkF({
|
||||
title: "确认导入外部数据?",
|
||||
text: "仅适用于特定工具导出的胡桃剧诗数据\n(不适配本应用备份数据)",
|
||||
cancelLabel: "查看详细说明",
|
||||
});
|
||||
if (checkF === undefined) {
|
||||
showSnackbar.cancel("取消导入剧诗数据");
|
||||
return;
|
||||
}
|
||||
if (!checkF) {
|
||||
await openUrl("https://app.btmuli.ink/docs/TeyvatGuide/import-hutao-db.html");
|
||||
return;
|
||||
}
|
||||
const file = await open({
|
||||
multiple: false,
|
||||
title: "选择胡桃工具箱导出的剧诗数据文件",
|
||||
|
||||
@@ -549,7 +549,7 @@ async function refreshGachaPool(
|
||||
if (force) await showLoading.update(`[${label}] 第${page}页,${gachaRes.length}条`);
|
||||
for (const item of gachaRes) {
|
||||
if (!force) {
|
||||
await showLoading.update(`[${item.item_type}][${item.time}] ${item.name}`);
|
||||
await showLoading.update(`[${item.item_type}][${item.time}] ${item.name}`, { timeout: 0 });
|
||||
}
|
||||
const tempItem: TGApp.Plugins.UIGF.GachaItem = {
|
||||
gacha_type: item.gacha_type,
|
||||
@@ -684,11 +684,14 @@ async function deleteGacha(): Promise<void> {
|
||||
}
|
||||
}
|
||||
await showLoading.start("正在删除祈愿数据", `UID:${uidCur.value}`);
|
||||
const label = `已成功删除 ${uidCur.value} 的祈愿数据,即将刷新页面`;
|
||||
await TSUserGacha.deleteGachaRecords(uidCur.value);
|
||||
await reloadUid();
|
||||
await loadGachaList();
|
||||
await showLoading.end();
|
||||
showSnackbar.success(`已成功删除 ${uidCur.value} 的祈愿数据,即将刷新页面`);
|
||||
showSnackbar.success(label);
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
async function checkData(): Promise<void> {
|
||||
|
||||
@@ -315,14 +315,17 @@ async function deleteGacha(): Promise<void> {
|
||||
}
|
||||
}
|
||||
await showLoading.start("正在删除祈愿数据", `UID:${uidCur.value}`);
|
||||
const label = `已成功删除 ${uidCur.value} 的颂愿数据,即将刷新页面`;
|
||||
await TSUserGachaB.deleteRecords(uidCur.value);
|
||||
await TGLogger.Info(
|
||||
`[UserGachaB][${uidCur.value}][deleteGacha] 成功删除 ${gachaListCur.value.length} 条祈愿数据`,
|
||||
);
|
||||
showSnackbar.success(`已成功删除 ${uidCur.value} 的祈愿数据,即将刷新页面`);
|
||||
await reloadUid();
|
||||
await loadGachaBList();
|
||||
await showLoading.end();
|
||||
showSnackbar.success(label);
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
async function importUigf(): Promise<void> {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<template #append>
|
||||
<div class="ur-top-btns">
|
||||
<v-btn
|
||||
:loading="isRefresh"
|
||||
class="ur-top-btn"
|
||||
prepend-icon="mdi-refresh"
|
||||
variant="elevated"
|
||||
@@ -26,7 +27,7 @@
|
||||
更新
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="recordData === undefined"
|
||||
:disabled="recordData === undefined || isRefresh"
|
||||
class="ur-top-btn"
|
||||
prepend-icon="mdi-share"
|
||||
variant="elevated"
|
||||
@@ -35,7 +36,7 @@
|
||||
分享
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="recordData === undefined"
|
||||
:disabled="recordData === undefined || isRefresh"
|
||||
class="ur-top-btn"
|
||||
prepend-icon="mdi-delete"
|
||||
variant="elevated"
|
||||
@@ -60,9 +61,8 @@
|
||||
<PhCompCard title="世界探索">
|
||||
<TurWorldGrid :worlds="recordData.worldExplore" />
|
||||
</PhCompCard>
|
||||
<!-- TODO: 优化UI -->
|
||||
<PhCompCard title="尘歌壶">
|
||||
<TurHomeGrid :model-value="recordData.homes" />
|
||||
<TurHomeOverview :homes="recordData.homes" />
|
||||
</PhCompCard>
|
||||
</div>
|
||||
<div v-else class="ur-empty">
|
||||
@@ -76,7 +76,7 @@ import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import PhCompCard from "@comp/pageHome/ph-comp-card.vue";
|
||||
import TurAvatarGrid from "@comp/userRecord/tur-avatar-grid.vue";
|
||||
import TurHomeGrid from "@comp/userRecord/tur-home-grid.vue";
|
||||
import TurHomeOverview from "@comp/userRecord/tur-home-overview.vue";
|
||||
import TurOverviewGrid from "@comp/userRecord/tur-overview-grid.vue";
|
||||
import TurRoleInfo from "@comp/userRecord/tur-role-info.vue";
|
||||
import TurWorldGrid from "@comp/userRecord/tur-world-grid.vue";
|
||||
@@ -92,8 +92,10 @@ import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { account, cookie } = storeToRefs(userStore);
|
||||
const uidCur = ref<number>();
|
||||
|
||||
const version = ref<string>();
|
||||
const isRefresh = ref<boolean>(false);
|
||||
const uidCur = ref<number>();
|
||||
const uidList = shallowRef<Array<number>>([]);
|
||||
const recordData = shallowRef<TGApp.Sqlite.Record.TableTrans>();
|
||||
|
||||
@@ -102,6 +104,7 @@ onMounted(async () => {
|
||||
await TGLogger.Info("[UserRecord][onMounted] 打开角色战绩页面");
|
||||
version.value = await getVersion();
|
||||
await loadUid();
|
||||
isRefresh.value = false;
|
||||
await showLoading.end();
|
||||
});
|
||||
|
||||
@@ -165,6 +168,7 @@ async function refreshRecord(): Promise<void> {
|
||||
}
|
||||
await showLoading.start(`正在刷新${rfAccount.gameUid}的战绩数据`);
|
||||
await TGLogger.Info(`[UserRecord][refresh][${rfAccount.gameUid}] 刷新战绩数据`);
|
||||
isRefresh.value = true;
|
||||
const resp = await recordReq.index(rfCk!, rfAccount);
|
||||
console.log(resp);
|
||||
if ("retcode" in resp) {
|
||||
@@ -174,6 +178,7 @@ async function refreshRecord(): Promise<void> {
|
||||
await TGLogger.Error(
|
||||
`[UserRecord][refresh][${rfAccount.gameUid}] ${resp.retcode} ${resp.message}`,
|
||||
);
|
||||
isRefresh.value = false;
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(`[UserRecord][refresh][${rfAccount.gameUid}] 获取战绩数据成功`);
|
||||
@@ -184,6 +189,7 @@ async function refreshRecord(): Promise<void> {
|
||||
await loadUid(rfAccount.gameUid);
|
||||
await loadRecord();
|
||||
await showLoading.end();
|
||||
isRefresh.value = false;
|
||||
showSnackbar.success(`成功刷新${rfAccount.gameUid}的战绩数据`);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<img alt="icon" src="/UI/nav/toolbox.webp" />
|
||||
<span>实用脚本</span>
|
||||
<v-select
|
||||
v-model="curAccount"
|
||||
:disabled="runScript || runAll"
|
||||
:hide-details="true"
|
||||
:items="accounts"
|
||||
:model-value="curAccount"
|
||||
class="us-top-select"
|
||||
density="compact"
|
||||
item-title="uid"
|
||||
@@ -17,27 +17,47 @@
|
||||
variant="outlined"
|
||||
>
|
||||
<template #selection="{ item }">
|
||||
<div class="select-main">
|
||||
<div class="us-select-main">
|
||||
<img :src="item.brief.avatar" alt="icon" />
|
||||
<div class="content">
|
||||
<div class="us-sm-content">
|
||||
<span>{{ item.brief.nickname }}</span>
|
||||
<span>UID:{{ item.uid }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #item="{ props, item }">
|
||||
<div class="select-item" v-bind="props">
|
||||
<div
|
||||
:class="{ selected: item.uid === curAccount?.uid }"
|
||||
class="us-select-item"
|
||||
v-bind="props"
|
||||
@click="() => (curAccount = item)"
|
||||
>
|
||||
<img :src="item.brief.avatar" alt="icon" />
|
||||
<div class="content">
|
||||
<div class="us-si-content">
|
||||
<span>{{ item.brief.nickname }}</span>
|
||||
<span>UID:{{ item.uid }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
<v-btn :loading="runAll" class="run-all-btn" variant="elevated" @click="tryExecAll()">
|
||||
一键执行
|
||||
</v-btn>
|
||||
<template v-if="accounts.length > 1">
|
||||
<v-btn :loading="runAll" class="run-all-btn" variant="elevated" @click="tryExecSingle()">
|
||||
一键执行当前账号
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:loading="runAll"
|
||||
class="run-all-btn"
|
||||
variant="elevated"
|
||||
@click="tryExecAllAccounts()"
|
||||
>
|
||||
一键执行全部账号
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn :loading="runAll" class="run-all-btn" variant="elevated" @click="tryExecSingle()">
|
||||
一键执行
|
||||
</v-btn>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
@@ -134,7 +154,7 @@ async function tryAutoRun(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
curAccount.value = acFind;
|
||||
await tryExecAll();
|
||||
await tryExecSingle();
|
||||
}
|
||||
if (exitAfter.value) {
|
||||
showSnackbar.success("任务执行完成,即将自动退出");
|
||||
@@ -184,7 +204,7 @@ async function tryCkVerify(): Promise<void> {
|
||||
else showSnackbar.success("CK验证成功");
|
||||
}
|
||||
|
||||
async function tryExecAll(): Promise<void> {
|
||||
async function tryExecSingle(): Promise<void> {
|
||||
if (!curAccount.value) {
|
||||
showSnackbar.warn("当前账号未选择,请先选择账号");
|
||||
return;
|
||||
@@ -198,6 +218,35 @@ async function tryExecAll(): Promise<void> {
|
||||
await signEl.value?.tryAuto(skipGeetest.value);
|
||||
runAll.value = false;
|
||||
}
|
||||
|
||||
async function tryExecAllAccounts(): Promise<void> {
|
||||
if (accounts.value.length === 0) {
|
||||
showSnackbar.warn("未检测到可用账号");
|
||||
return;
|
||||
}
|
||||
if (runScript.value || runAll.value) {
|
||||
showSnackbar.warn("脚本正在执行,请稍后");
|
||||
return;
|
||||
}
|
||||
|
||||
runAll.value = true;
|
||||
|
||||
await TGLogger.ScriptSep(`全量执行`);
|
||||
for (const account of accounts.value) {
|
||||
curAccount.value = account;
|
||||
|
||||
await TGLogger.Script(`账号 UID:${account.uid} 执行开始`);
|
||||
|
||||
if (missionEl.value) await missionEl.value.tryAuto(skipGeetest.value);
|
||||
if (signEl.value) await signEl.value.tryAuto(skipGeetest.value);
|
||||
|
||||
await TGLogger.Script(`账号 UID:${account.uid} 执行完毕`);
|
||||
}
|
||||
await TGLogger.ScriptSep(`全量执行`, false);
|
||||
|
||||
runAll.value = false;
|
||||
showSnackbar.success("所有账号均已执行完毕");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.us-top-title {
|
||||
@@ -224,7 +273,7 @@ async function tryExecAll(): Promise<void> {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.select-main {
|
||||
.us-select-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
@@ -236,24 +285,24 @@ async function tryExecAll(): Promise<void> {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.us-sm-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:first-child {
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
}
|
||||
:first-child {
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
font-size: 10px;
|
||||
}
|
||||
:last-child {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-item {
|
||||
.us-select-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@@ -262,35 +311,44 @@ async function tryExecAll(): Promise<void> {
|
||||
justify-content: flex-start;
|
||||
padding: 8px;
|
||||
column-gap: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected:not(:hover) {
|
||||
background: var(--common-shadow-1);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--common-shadow-2);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.us-si-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:first-child {
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
font-size: 10px;
|
||||
}
|
||||
:first-child {
|
||||
font-family: var(--font-title);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.append {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
:last-child {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.us-si-append {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.top-hint {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
@@ -324,6 +382,10 @@ async function tryExecAll(): Promise<void> {
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.run-all-btn + .run-all-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.run-all-btn,
|
||||
.us-test-btn {
|
||||
background: var(--tgc-btn-1);
|
||||
|
||||
@@ -139,7 +139,7 @@ async function toOuter(item?: TGApp.App.Character.WikiBriefInfo): Promise<void>
|
||||
.wc-left {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
flex: 3;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
@@ -169,13 +169,14 @@ async function toOuter(item?: TGApp.App.Character.WikiBriefInfo): Promise<void>
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 160px);
|
||||
grid-template-columns: repeat(auto-fill, minmax(144px, 1fr));
|
||||
}
|
||||
|
||||
.wc-detail {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex: 5;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px var(--common-shadow-2);
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
<div class="twm-top-append">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:clearable="true"
|
||||
:hide-details="true"
|
||||
:single-line="true"
|
||||
append-inner-icon="mdi-magnify"
|
||||
density="compact"
|
||||
label="搜索"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
@keydown.enter="searchMaterial()"
|
||||
@click:prepend-inner="searchMaterial()"
|
||||
@click:append-inner="searchMaterial()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -137,7 +137,7 @@ function switchMaterial(isNext: boolean): void {
|
||||
|
||||
function searchMaterial(): void {
|
||||
let selectData = getSelectMaterials();
|
||||
if (search.value === undefined || search.value === "") {
|
||||
if (search.value === undefined || search.value === "" || search.value === null) {
|
||||
if (sortMaterialsData.value.length === selectData.length) {
|
||||
showSnackbar.warn("请输入搜索内容!");
|
||||
return;
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
</div>
|
||||
<v-select
|
||||
v-model="selectType"
|
||||
:items="namecardTypes"
|
||||
item-title="type"
|
||||
:hide-details="true"
|
||||
:clearable="true"
|
||||
width="250px"
|
||||
label="名片类别"
|
||||
:hide-details="true"
|
||||
:items="namecardTypes"
|
||||
density="compact"
|
||||
item-title="type"
|
||||
label="名片类别"
|
||||
variant="outlined"
|
||||
width="250px"
|
||||
>
|
||||
<template #item="{ props, item }">
|
||||
<v-list-item v-bind="props">
|
||||
@@ -31,33 +31,34 @@
|
||||
<div class="wnc-top-append">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
density="compact"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
:clearable="true"
|
||||
:hide-details="true"
|
||||
append-inner-icon="mdi-magnify"
|
||||
density="compact"
|
||||
label="搜索"
|
||||
variant="outlined"
|
||||
@click:prepend-inner="searchNameCard()"
|
||||
@click:append-inner="searchNameCard()"
|
||||
@keyup.enter="searchNameCard()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<div class="tw-nc-list">
|
||||
<v-virtual-scroll class="v-scroll" :items="sortNameCardsData" :item-height="80" item-key="id">
|
||||
<v-virtual-scroll :item-height="80" :items="sortNameCardsData" class="v-scroll" item-key="id">
|
||||
<template #default="{ item }">
|
||||
<TopNameCard class="item" :data="item" @selected="showNameCard(item)" />
|
||||
<TopNameCard :data="item" class="item" @selected="showNameCard(item)" />
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</div>
|
||||
<ToNameCard v-model="visible" :data="curNameCard">
|
||||
<template #left>
|
||||
<div class="card-arrow left" @click="switchCard(false)">
|
||||
<img src="@/assets/icons/arrow-right.svg" alt="right" />
|
||||
<img alt="right" src="@/assets/icons/arrow-right.svg" />
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="card-arrow" @click="switchCard(true)">
|
||||
<img src="@/assets/icons/arrow-right.svg" alt="right" />
|
||||
<img alt="right" src="@/assets/icons/arrow-right.svg" />
|
||||
</div>
|
||||
</template>
|
||||
</ToNameCard>
|
||||
@@ -135,8 +136,9 @@ function switchCard(isNext: boolean): void {
|
||||
}
|
||||
|
||||
function searchNameCard(): void {
|
||||
if (search.value === undefined) {
|
||||
if (search.value === undefined || search.value === null) {
|
||||
sortData(AppNameCardsData);
|
||||
showSnackbar.success("已重置");
|
||||
return;
|
||||
}
|
||||
if (search.value === "") {
|
||||
|
||||
@@ -117,8 +117,9 @@ async function toOuter(item?: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
|
||||
}
|
||||
|
||||
.ww-left {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
flex: 3;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
@@ -148,13 +149,14 @@ async function toOuter(item?: TGApp.App.Weapon.WikiBriefInfo): Promise<void> {
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(3, 160px);
|
||||
grid-template-columns: repeat(auto-fill, minmax(144px, 1fr));
|
||||
}
|
||||
|
||||
.ww-detail {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex: 5;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px var(--common-shadow-2);
|
||||
|
||||
@@ -22,9 +22,10 @@
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:hide-details="true"
|
||||
:single-line="true"
|
||||
append-inner-icon="mdi-magnify"
|
||||
@click:append-inner="isSearch = true"
|
||||
variant="outlined"
|
||||
:clearable="true"
|
||||
density="compact"
|
||||
label="搜索"
|
||||
@keydown.enter="isSearch = true"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<template #prepend>
|
||||
<div class="pa-prepend">
|
||||
<v-tabs v-model="tab" align-tabs="start" class="pa-tabs">
|
||||
<v-tab v-for="tab in tabList" :key="tab.id" :value="tab.id" :title="tab.name">
|
||||
<v-tab v-for="tab in tabList" :key="tab.id" :title="tab.name" :value="tab.id">
|
||||
{{ tab.mi18n_name }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
@@ -36,23 +36,16 @@
|
||||
</template>
|
||||
<template #append>
|
||||
<div class="anno-top-append">
|
||||
<v-btn
|
||||
class="anno-btn"
|
||||
prepend-icon="mdi-bullhorn"
|
||||
rounded
|
||||
variant="elevated"
|
||||
@click="switchNews"
|
||||
>
|
||||
切换米游社资讯
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="isLogin"
|
||||
class="anno-btn"
|
||||
prepend-icon="mdi-web"
|
||||
rounded
|
||||
size="small"
|
||||
variant="elevated"
|
||||
@click="showIframe()"
|
||||
>
|
||||
<v-icon>mdi-web</v-icon>
|
||||
游戏内公告
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
@@ -82,7 +75,6 @@ import useAppStore from "@store/app.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
type AnnoSelect<T = string> = { text: string; value: T };
|
||||
|
||||
@@ -92,7 +84,6 @@ const langList: ReadonlyArray<AnnoSelect<TGApp.Game.Anno.AnnoLangEnum>> =
|
||||
gameEnum.anno.langList.map((i) => ({ text: gameEnum.anno.langDesc(i), value: i }));
|
||||
|
||||
const { server, lang, isLogin } = storeToRefs(useAppStore());
|
||||
const router = useRouter();
|
||||
|
||||
const tab = ref<number>(0);
|
||||
const tabList = shallowRef<Array<TGApp.Game.Anno.ListType>>([]);
|
||||
@@ -158,11 +149,6 @@ async function loadData(): Promise<void> {
|
||||
await showLoading.end();
|
||||
isReq.value = false;
|
||||
}
|
||||
|
||||
async function switchNews(): Promise<void> {
|
||||
await TGLogger.Info("[Announcements][switchNews] 切换米游社资讯");
|
||||
await router.push("/news/2");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.pa-prepend {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-app-bar>
|
||||
<template #prepend>
|
||||
<div class="pbm-nav-prepend">
|
||||
<img alt="icon" src="/icon/material/121234.webp" />
|
||||
<img alt="icon" src="/UI/nav/userBag.webp" />
|
||||
<span>背包材料</span>
|
||||
<v-select
|
||||
v-model="curUid"
|
||||
@@ -21,14 +21,14 @@
|
||||
<div class="pbm-nav-search">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:clearable="true"
|
||||
:hide-details="true"
|
||||
:single-line="true"
|
||||
append-inner-icon="mdi-magnify"
|
||||
density="compact"
|
||||
label="搜索"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
@keydown.enter="searchMaterial()"
|
||||
@click:prepend-inner="searchMaterial()"
|
||||
@click:append-inner="searchMaterial()"
|
||||
/>
|
||||
</div>
|
||||
<v-btn
|
||||
@@ -299,7 +299,7 @@ function getItemInfo(id: number): TGApp.App.Material.WikiItem | false {
|
||||
|
||||
function searchMaterial(): void {
|
||||
let selectData = getSelectMaterials();
|
||||
if (search.value === undefined || search.value === "") {
|
||||
if (search.value === undefined || search.value === "" || search.value === null) {
|
||||
if (materialShow.value.length === selectData.length) {
|
||||
showSnackbar.warn("请输入搜索内容!");
|
||||
return;
|
||||
|
||||
@@ -607,6 +607,7 @@ async function switchIncognito(): Promise<void> {
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-start;
|
||||
padding-right: 4px;
|
||||
row-gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 首页 -->
|
||||
<template>
|
||||
<v-app-bar>
|
||||
<template #prepend>
|
||||
<div class="home-top-nav">
|
||||
<div v-if="isLogin" class="home-tools">
|
||||
<v-select
|
||||
v-model="curGid"
|
||||
@@ -15,10 +15,9 @@
|
||||
>
|
||||
<template #selection="{ item }">
|
||||
<div class="select-item main">
|
||||
<TMiImg
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:alt="item.title"
|
||||
:ori="true"
|
||||
:src="item.icon"
|
||||
:title="item.title"
|
||||
class="icon"
|
||||
@@ -32,7 +31,7 @@
|
||||
class="select-item sub"
|
||||
v-bind="props"
|
||||
>
|
||||
<TMiImg
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:alt="item.title"
|
||||
:src="item.icon"
|
||||
@@ -43,10 +42,8 @@
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
<TGameNav :model-value="curGid" />
|
||||
<TGameNav :gid="curGid" />
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<div class="home-select">
|
||||
<v-select
|
||||
v-model="oldItems"
|
||||
@@ -63,7 +60,7 @@
|
||||
确定
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</v-app-bar>
|
||||
<div class="home-container">
|
||||
<component :is="item" v-for="item in components" :key="item" @success="loadEnd(item)" />
|
||||
@@ -71,7 +68,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TGameNav from "@comp/app/t-gameNav.vue";
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import PhCompCalendar from "@comp/pageHome/ph-comp-calendar.vue";
|
||||
@@ -82,6 +79,10 @@ import TSUserAccount from "@Sqlm/userAccount.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import useBBSStore from "@store/bbs.js";
|
||||
import useHomeStore from "@store/home.js";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { getLatestReleaseVersion } from "@utils/Github.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { defineComponent, onMounted, ref, shallowRef, watch } from "vue";
|
||||
@@ -105,7 +106,7 @@ type SelectItem = {
|
||||
const homeStore = useHomeStore();
|
||||
const bbsStore = useBBSStore();
|
||||
|
||||
const { devMode, isLogin } = storeToRefs(useAppStore());
|
||||
const { devMode, isLogin, lastUcts } = storeToRefs(useAppStore());
|
||||
const { gameList } = storeToRefs(bbsStore);
|
||||
|
||||
const curGid = ref<number>(2);
|
||||
@@ -134,6 +135,7 @@ onMounted(async () => {
|
||||
}
|
||||
oldItems.value = showItems.value;
|
||||
await loadComp();
|
||||
await checkAppUpdate();
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -209,8 +211,50 @@ async function loadEnd(item: ReturnType<typeof defineComponent>): Promise<void>
|
||||
else showSnackbar.warn(`${compName} 已加载`);
|
||||
if (loadItems.value.length === components.value.length) await showLoading.end();
|
||||
}
|
||||
|
||||
async function checkAppUpdate(): Promise<void> {
|
||||
const nowTs = Math.floor(Date.now() / 1000);
|
||||
const diffTime = nowTs - lastUcts.value;
|
||||
if (diffTime < 60 * 60 * 24) return;
|
||||
await TGLogger.Info("[Home][CheckAppUpdate]检测版本更新");
|
||||
const versionApp = await getVersion();
|
||||
const versionCheck = await getLatestReleaseVersion();
|
||||
if (versionCheck === "0") return;
|
||||
if (versionCheck === versionApp) {
|
||||
await TGLogger.Info(`[Home][CheckAppUpdate]版本号一致:${versionCheck}`);
|
||||
lastUcts.value = nowTs;
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(`[Home][CheckAppUpdate]检测到新版本:${versionCheck}`);
|
||||
const check = await showDialog.checkF({
|
||||
title: "检测到新版本",
|
||||
text: `${versionApp}→${versionCheck}`,
|
||||
otcancel: false,
|
||||
confirmLabel: "前往更新",
|
||||
cancelLabel: "稍后提醒",
|
||||
});
|
||||
lastUcts.value = nowTs;
|
||||
if (!check) return;
|
||||
const isMsix = await invoke<boolean>("is_msix");
|
||||
if (isMsix) {
|
||||
await openUrl("ms-windows-store://pdp/?ProductId=9nlbnnnbnsjn");
|
||||
return;
|
||||
}
|
||||
await openUrl("https://github.com/BTMuli/TeyvatGuide/releases/latest");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.home-top-nav {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.home-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -218,14 +262,6 @@ async function loadEnd(item: ReturnType<typeof defineComponent>): Promise<void>
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.home-top {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.home-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -239,10 +275,12 @@ async function loadEnd(item: ReturnType<typeof defineComponent>): Promise<void>
|
||||
}
|
||||
|
||||
.home-select {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
margin-left: auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,9 @@
|
||||
>
|
||||
<template #selection="{ item }">
|
||||
<div class="select-item main">
|
||||
<TMiImg
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:alt="item.text"
|
||||
:ori="true"
|
||||
:src="item.icon"
|
||||
:title="item.text"
|
||||
class="icon"
|
||||
@@ -33,7 +32,7 @@
|
||||
</template>
|
||||
<template #item="{ props, item }">
|
||||
<div :class="{ selected: item.gid === curGid }" class="select-item sub" v-bind="props">
|
||||
<TMiImg v-if="item.icon" :alt="item.text" :ori="true" :src="item.icon" class="icon" />
|
||||
<img v-if="item.icon" :alt="item.text" :src="item.icon" class="icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -49,7 +48,7 @@
|
||||
>
|
||||
<template #selection="{ item }">
|
||||
<div class="select-item main">
|
||||
<TMiImg :alt="item.text" :ori="true" :src="item.icon" :title="item.text" class="icon" />
|
||||
<img :alt="item.text" :src="item.icon" :title="item.text" class="icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -60,7 +59,7 @@
|
||||
v-bind="props"
|
||||
@click="() => (selectedForum = item)"
|
||||
>
|
||||
<TMiImg :alt="item.text" :ori="true" :src="item.icon" class="icon" />
|
||||
<img :alt="item.text" :src="item.icon" class="icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -78,12 +77,12 @@
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:hide-details="true"
|
||||
:single-line="true"
|
||||
:clearable="true"
|
||||
append-inner-icon="mdi-magnify"
|
||||
class="post-switch-item"
|
||||
label="请输入帖子 ID 或搜索词"
|
||||
variant="outlined"
|
||||
@click:append="searchPost"
|
||||
@click:append-inner="searchPost"
|
||||
@keyup.enter="searchPost"
|
||||
/>
|
||||
<v-btn
|
||||
@@ -98,7 +97,7 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
<template #extension>
|
||||
<TGameNav :model-value="curGid" style="margin-left: 8px" />
|
||||
<TGameNav :mini="false" :gid="curGid" style="margin-left: 8px" />
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<div class="posts-grid">
|
||||
@@ -111,7 +110,6 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TGameNav from "@comp/app/t-gameNav.vue";
|
||||
import TMiImg from "@comp/app/t-mi-img.vue";
|
||||
import TPostCard from "@comp/app/t-postcard.vue";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!-- 咨讯页面 -->
|
||||
<template>
|
||||
<v-app-bar>
|
||||
<template #prepend>
|
||||
<v-tabs v-model="recentNewsType" align-tabs="center" class="news-tab">
|
||||
<div class="pn-nav">
|
||||
<v-tabs v-model="recentNewsType" align-tabs="center" class="pn-nav-tabs">
|
||||
<v-tab
|
||||
v-for="(value, index) in bbsEnum.post.newsTypeList"
|
||||
:key="index"
|
||||
@@ -13,48 +13,37 @@
|
||||
{{ rawData[value].name }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
<template #title>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:hide-details="true"
|
||||
:single-line="true"
|
||||
append-icon="mdi-magnify"
|
||||
class="news-search"
|
||||
append-inner-icon="mdi-magnify"
|
||||
class="pn-nav-search"
|
||||
density="compact"
|
||||
label="请输入帖子 ID 或搜索词"
|
||||
variant="outlined"
|
||||
:clearable="true"
|
||||
@keydown.enter="searchPost()"
|
||||
@click:append="searchPost()"
|
||||
@click:append-inner="searchPost()"
|
||||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
class="post-news-btn"
|
||||
size="small"
|
||||
variant="elevated"
|
||||
@click="firstLoad(true)"
|
||||
>
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
<v-btn class="post-news-btn" size="small" variant="elevated" @click="handleList()">
|
||||
<v-icon>mdi-view-list</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="gid === '2'"
|
||||
class="post-news-btn"
|
||||
prepend-icon="mdi-bullhorn"
|
||||
rounded
|
||||
variant="elevated"
|
||||
@click="switchAnno"
|
||||
>
|
||||
切换游戏内公告
|
||||
</v-btn>
|
||||
</template>
|
||||
<div class="pn-nav-btns">
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
class="pn-nav-btn"
|
||||
size="small"
|
||||
variant="elevated"
|
||||
@click="firstLoad(true)"
|
||||
>
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
<v-btn class="pn-nav-btn" size="small" variant="elevated" @click="handleList()">
|
||||
<v-icon>mdi-view-list</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-app-bar>
|
||||
<v-window v-model="recentNewsType">
|
||||
<v-window-item v-for="(value, index) in bbsEnum.post.newsTypeList" :key="index" :value="value">
|
||||
<div class="news-grid">
|
||||
<div class="pn-grid">
|
||||
<div v-for="item in postData[value]" :key="item.post.post_id">
|
||||
<TPostCard :model-value="item" />
|
||||
</div>
|
||||
@@ -62,7 +51,7 @@
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<ToChannel v-model="showList" :gid="gid" />
|
||||
<VpOverlaySearch v-model="showSearch" :gid="gid" :keyword="search" />
|
||||
<VpOverlaySearch v-model="showSearch" :gid="Number(gid)" :keyword="search" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TPostCard from "@comp/app/t-postcard.vue";
|
||||
@@ -78,14 +67,13 @@ import useBBSStore from "@store/bbs.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { createPost } from "@utils/TGWindow.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, reactive, Ref, ref, shallowRef, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { computed, onMounted, reactive, type Ref, ref, shallowRef, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
type PostData = Record<TGApp.BBS.Post.NewsTypeEnum, Ref<Array<TGApp.BBS.Post.FullData>>>;
|
||||
type RawItem = { isLast: boolean; name: string; lastId: number };
|
||||
type RawData = Record<TGApp.BBS.Post.NewsTypeEnum, Ref<RawItem>>;
|
||||
|
||||
const router = useRouter();
|
||||
const { recentNewsType } = storeToRefs(useAppStore());
|
||||
const { gameList } = storeToRefs(useBBSStore());
|
||||
const { gid } = <{ gid: string }>useRoute().params;
|
||||
@@ -159,11 +147,6 @@ async function firstLoad(refresh: boolean = false): Promise<void> {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
async function switchAnno(): Promise<void> {
|
||||
await TGLogger.Info(`[News][${gid}][switchAnno] 切换公告`);
|
||||
await router.push("/announcements");
|
||||
}
|
||||
|
||||
function handleList(): void {
|
||||
if (showSearch.value === true) showSearch.value = false;
|
||||
showList.value = true;
|
||||
@@ -215,37 +198,47 @@ async function searchPost(): Promise<void> {
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.news-tab {
|
||||
margin-bottom: 8px;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
|
||||
&:first-child {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.pn-nav {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: auto hidden;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
.post-news-btn {
|
||||
.pn-nav-tabs {
|
||||
position: relative;
|
||||
color: var(--common-text-title);
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.pn-nav-search {
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.pn-nav-btns {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
.pn-nav-btn {
|
||||
height: 40px;
|
||||
background: var(--tgc-btn-1);
|
||||
color: var(--btn-text);
|
||||
font-family: var(--font-title);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-news-btn + .post-news-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.news-search {
|
||||
margin: 0 16px;
|
||||
color: var(--box-text-1);
|
||||
}
|
||||
|
||||
.news-grid {
|
||||
.pn-grid {
|
||||
display: grid;
|
||||
font-family: var(--font-title);
|
||||
gap: 8px;
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<TMiImg :ori="true" :src="topicInfo.topic.cover" alt="cover" />
|
||||
<div class="post-topic-info">
|
||||
<span class="post-topic-title">{{ topicInfo.topic.name }}</span>
|
||||
<span :title="topicInfo.topic.desc" class="post-topic-desc">{{
|
||||
topicInfo.topic.desc
|
||||
}}</span>
|
||||
<span :title="topicInfo.topic.desc" class="post-topic-desc">
|
||||
{{ topicInfo.topic.desc }}
|
||||
</span>
|
||||
</div>
|
||||
<div :title="`话题ID:${topicInfo.topic.id}`" class="post-topic-id">
|
||||
{{ topicInfo.topic.id }}
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #extension>
|
||||
<TGameNav v-if="curGid !== 0" :model-value="curGid" style="margin-left: 8px" />
|
||||
<TGameNav v-if="curGid !== 0" :gid="curGid" :mini="false" style="margin-left: 8px" />
|
||||
</template>
|
||||
<div class="post-topic-switch">
|
||||
<v-select
|
||||
@@ -67,12 +67,12 @@
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:hide-details="true"
|
||||
:single-line="true"
|
||||
:clearable="true"
|
||||
append-inner-icon="mdi-magnify"
|
||||
class="post-switch-item"
|
||||
label="请输入帖子 ID 或搜索词"
|
||||
variant="outlined"
|
||||
@click:append="searchPost"
|
||||
@click:append-inner="searchPost"
|
||||
@keyup.enter="searchPost"
|
||||
/>
|
||||
<v-btn
|
||||
|
||||
@@ -1,54 +1,98 @@
|
||||
/**
|
||||
* 用户祈愿模块
|
||||
* @since Beta v0.9.6
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import Database from "@tauri-apps/plugin-sql";
|
||||
import { getUtc8Time, getWikiBrief, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { ref, type Ref } from "vue";
|
||||
|
||||
import TGSqlite from "../index.js";
|
||||
|
||||
/**
|
||||
* 导入物品
|
||||
* @since Beta v0.9.6
|
||||
* @param uid - UID
|
||||
* @param item - UIGF数据
|
||||
* @returns Promise<void>
|
||||
* 批量插入祈愿数据
|
||||
* @since Beta v0.9.8
|
||||
* @param db - 数据库
|
||||
* @param uid - 用户UID
|
||||
* @param data - 祈愿数据
|
||||
* @param size - batchSize
|
||||
* @param cnt - cntRef
|
||||
*/
|
||||
async function insertGachaItem(uid: string, item: TGApp.Plugins.UIGF.GachaItem): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const updateTime = timestampToDate(Date.now());
|
||||
await db.execute(
|
||||
`INSERT INTO GachaRecords(uid, gachaType, itemId, count, time,
|
||||
name, type, rank, id, uigfType, updated)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT(id) DO UPDATE
|
||||
SET uid = $1,
|
||||
gachaType = $2,
|
||||
itemId = $3,
|
||||
count = $4,
|
||||
time = $5,
|
||||
name = $6,
|
||||
type = $7,
|
||||
rank = $8,
|
||||
uigfType = $10,
|
||||
updated = $11;
|
||||
`,
|
||||
[
|
||||
uid,
|
||||
item.gacha_type,
|
||||
item.item_id ?? null,
|
||||
item.count ?? 1,
|
||||
item.time,
|
||||
item.name,
|
||||
item.item_type ?? null,
|
||||
item.rank_type ?? null,
|
||||
item.id,
|
||||
item.uigf_gacha_type,
|
||||
updateTime,
|
||||
],
|
||||
);
|
||||
async function insertGachaList(
|
||||
db: Database,
|
||||
uid: string,
|
||||
data: Array<TGApp.Plugins.UIGF.GachaItem>,
|
||||
size: number,
|
||||
cnt: Ref<number>,
|
||||
): Promise<void> {
|
||||
await db.execute("PRAGMA busy_timeout = 5000;");
|
||||
for (let i = 0; i < data.length; i += size) {
|
||||
await db.execute("BEGIN IMMEDIATE;");
|
||||
try {
|
||||
const batch = data.slice(i, i + size);
|
||||
let batchSql = "";
|
||||
const batchParams = [];
|
||||
for (const item of batch) {
|
||||
const updateTime = timestampToDate(Date.now());
|
||||
batchSql += `
|
||||
INSERT INTO GachaRecords(uid, gachaType, itemId, count, time,
|
||||
name, type, rank, id, uigfType, updated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id)
|
||||
DO UPDATE
|
||||
SET uid=?,
|
||||
gachaType=?,
|
||||
itemId=?,
|
||||
count=?,
|
||||
time=?,
|
||||
name=?,
|
||||
type=?,
|
||||
rank=?,
|
||||
uigfType=?,
|
||||
updated=?;
|
||||
`;
|
||||
batchParams.push(
|
||||
uid,
|
||||
item.gacha_type,
|
||||
item.item_id ?? null,
|
||||
item.count ?? 1,
|
||||
item.time,
|
||||
item.name,
|
||||
item.item_type ?? null,
|
||||
item.rank_type ?? null,
|
||||
item.id,
|
||||
item.uigf_gacha_type,
|
||||
updateTime,
|
||||
// update 部分
|
||||
uid,
|
||||
item.gacha_type,
|
||||
item.item_id ?? null,
|
||||
item.count ?? 1,
|
||||
item.time,
|
||||
item.name,
|
||||
item.item_type ?? null,
|
||||
item.rank_type ?? null,
|
||||
item.uigf_gacha_type,
|
||||
updateTime,
|
||||
);
|
||||
cnt.value++;
|
||||
}
|
||||
await db.execute(batchSql, batchParams);
|
||||
await db.execute("COMMIT;");
|
||||
} catch (e) {
|
||||
const msg = String(e);
|
||||
if (/BUSY|LOCKED|SQLITE_BUSY|SQLITE_LOCKED/i.test(msg)) {
|
||||
await showDialog.check(`数据库锁定`, `请刷新页面(F5)后重试操作`);
|
||||
return;
|
||||
}
|
||||
// 其他错误直接抛出
|
||||
await db.execute("ROLLBACK;");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,14 +279,13 @@ async function cleanGachaRecords(
|
||||
const res = await db.execute(sql);
|
||||
if (res.rowsAffected > 0) {
|
||||
showSnackbar.success(`[${uid}][${pool}][${time}]清理了${res.rowsAffected}条祈愿记录`);
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并祈愿数据
|
||||
* @since Beta v0.9.5
|
||||
* @since Beta v0.9.8
|
||||
* @param uid - UID
|
||||
* @param data - UIGF数据
|
||||
* @param showProgress - 是否显示进度
|
||||
@@ -253,33 +296,32 @@ async function mergeUIGF(
|
||||
data: Array<TGApp.Plugins.UIGF.GachaItem>,
|
||||
showProgress: boolean = false,
|
||||
): Promise<void> {
|
||||
let cnt = 0;
|
||||
const db = await TGSqlite.getDB();
|
||||
const len = data.length;
|
||||
let progress = 0;
|
||||
const cnt = ref<number>(0);
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
if (showProgress) {
|
||||
timer = setInterval(async () => {
|
||||
progress = Math.round((cnt / len) * 100 * 100) / 100;
|
||||
const current = data[cnt]?.time ?? "";
|
||||
const name = data[cnt]?.name ?? "";
|
||||
const rank = data[cnt]?.rank_type ?? "0";
|
||||
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`);
|
||||
const progress = Math.round((cnt.value / len) * 100 * 100) / 100;
|
||||
const current = data[cnt.value]?.time ?? "";
|
||||
const name = data[cnt.value]?.name ?? "";
|
||||
const rank = data[cnt.value]?.rank_type ?? "0";
|
||||
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`, {
|
||||
timeout: 0,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
for (const gacha of data) {
|
||||
await insertGachaItem(uid, transGacha(gacha));
|
||||
cnt++;
|
||||
}
|
||||
const transformed = data.map((g) => transGacha(g));
|
||||
await insertGachaList(db, uid, transformed, 100, cnt);
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
progress = 100;
|
||||
await showLoading.update(`[${progress}%] 完成`);
|
||||
await showLoading.update(`[100%] 完成`, { timeout: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并祈愿数据(v4.x)
|
||||
* @since Beta v0.9.5
|
||||
* @since Beta v0.9.8
|
||||
* @param data - UIGF数据
|
||||
* @param showProgress - 是否显示进度
|
||||
* @returns 无返回值
|
||||
@@ -288,27 +330,27 @@ async function mergeUIGF4(
|
||||
data: TGApp.Plugins.UIGF.GachaHk4e,
|
||||
showProgress: boolean = false,
|
||||
): Promise<void> {
|
||||
let cnt: number = 0;
|
||||
const db = await TGSqlite.getDB();
|
||||
const len = data.list.length;
|
||||
let progress: number = 0;
|
||||
const cnt = ref<number>(0);
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
if (showProgress) {
|
||||
timer = setInterval(async () => {
|
||||
progress = Math.round((cnt / len) * 100 * 100) / 100;
|
||||
const current = data.list[cnt]?.time ?? "";
|
||||
const name = data.list[cnt]?.name ?? data.list[cnt]?.item_id;
|
||||
const rank = data.list[cnt]?.rank_type ?? "0";
|
||||
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`);
|
||||
const progress = Math.round((cnt.value / len) * 100 * 100) / 100;
|
||||
const current = data.list[cnt.value]?.time ?? "";
|
||||
const name = data.list[cnt.value]?.name ?? data.list[cnt.value]?.item_id;
|
||||
const rank = data.list[cnt.value]?.rank_type ?? "0";
|
||||
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`, {
|
||||
timeout: 0,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
for (const gacha of data.list) {
|
||||
await insertGachaItem(data.uid.toString(), transGacha(gacha, data.timezone));
|
||||
cnt++;
|
||||
}
|
||||
const uid = data.uid.toString();
|
||||
const transformed = data.list.map((g) => transGacha(g, data.timezone));
|
||||
await insertGachaList(db, uid, transformed, 100, cnt);
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
progress = 100;
|
||||
await showLoading.update(`[${progress}%] 完成`);
|
||||
await showLoading.update(`[100%] 完成`, { timeout: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +1,99 @@
|
||||
/**
|
||||
* 千星奇域祈愿模块
|
||||
* @since Beta v0.9.5
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import TGSqlite from "@Sql/index.js";
|
||||
import Database from "@tauri-apps/plugin-sql";
|
||||
import { getUtc8Time, timestampToDate } from "@utils/toolFunc.js";
|
||||
import { ref, type Ref } from "vue";
|
||||
|
||||
import { AppGachaBData } from "@/data/index.js";
|
||||
|
||||
/**
|
||||
* 插入颂愿数据
|
||||
* @since Beta v0.9.5
|
||||
* @param uid - UID
|
||||
* @param item - 颂愿数据
|
||||
* @returns 无返回值
|
||||
* 批量插入颂愿数据
|
||||
* @since Beta v0.9.8
|
||||
* @param db - 数据库
|
||||
* @param uid - 用户UID
|
||||
* @param data - 祈愿数据
|
||||
* @param size - batchSize
|
||||
* @param cnt - cntRef
|
||||
*/
|
||||
async function insertGachaItem(uid: string, item: TGApp.Plugins.UIGF.GachaItemB): Promise<void> {
|
||||
const db = await TGSqlite.getDB();
|
||||
const gachaType = item.op_gacha_type === "1000" ? "1000" : "2000";
|
||||
const updateTime = timestampToDate(Date.now());
|
||||
await db.execute(
|
||||
`
|
||||
INSERT INTO GachaBRecords(id, uid, scheduleId, gachaType, opGachaType, time,
|
||||
itemId, name, type, rank, updated)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT (id)
|
||||
DO UPDATE
|
||||
SET uid = $2,
|
||||
scheduleId = $3,
|
||||
gachaType = $4,
|
||||
opGachaType = $5,
|
||||
time = $6,
|
||||
itemId = $7,
|
||||
name = $8,
|
||||
type = $9,
|
||||
rank = $10,
|
||||
updated = $11;
|
||||
`,
|
||||
[
|
||||
item.id,
|
||||
uid,
|
||||
item.schedule_id,
|
||||
gachaType,
|
||||
item.op_gacha_type,
|
||||
item.time,
|
||||
item.item_id,
|
||||
item.item_name,
|
||||
item.item_type,
|
||||
item.rank_type,
|
||||
updateTime,
|
||||
],
|
||||
);
|
||||
async function insertGachaBList(
|
||||
db: Database,
|
||||
uid: string,
|
||||
data: Array<TGApp.Plugins.UIGF.GachaItemB>,
|
||||
size: number,
|
||||
cnt?: Ref<number>,
|
||||
): Promise<void> {
|
||||
await db.execute("PRAGMA busy_timeout = 5000;");
|
||||
for (let i = 0; i < data.length; i += size) {
|
||||
await db.execute("BEGIN IMMEDIATE;");
|
||||
try {
|
||||
const batch = data.slice(i, i + size);
|
||||
let batchSql = "";
|
||||
const batchParams = [];
|
||||
for (const item of batch) {
|
||||
const updateTime = timestampToDate(Date.now());
|
||||
const gachaType = item.op_gacha_type === "1000" ? "1000" : "2000";
|
||||
batchSql += `
|
||||
INSERT INTO GachaBRecords(id, uid, scheduleId, gachaType, opGachaType, time,
|
||||
itemId, name, type, rank, updated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id)
|
||||
DO UPDATE
|
||||
SET uid=?,
|
||||
scheduleId=?,
|
||||
gachaType=?,
|
||||
opGachaType=?,
|
||||
time=?,
|
||||
itemId=?,
|
||||
name=?,
|
||||
type=?,
|
||||
rank=?,
|
||||
updated=?;
|
||||
`;
|
||||
batchParams.push(
|
||||
item.id,
|
||||
uid,
|
||||
item.schedule_id,
|
||||
gachaType,
|
||||
item.op_gacha_type,
|
||||
item.time,
|
||||
item.item_id,
|
||||
item.item_name,
|
||||
item.item_type,
|
||||
item.rank_type,
|
||||
updateTime,
|
||||
// update 部分
|
||||
uid,
|
||||
item.schedule_id,
|
||||
gachaType,
|
||||
item.op_gacha_type,
|
||||
item.time,
|
||||
item.item_id,
|
||||
item.item_name,
|
||||
item.item_type,
|
||||
item.rank_type,
|
||||
updateTime,
|
||||
);
|
||||
if (cnt) cnt.value++;
|
||||
}
|
||||
await db.execute(batchSql, batchParams);
|
||||
await db.execute("COMMIT;");
|
||||
} catch (e) {
|
||||
const msg = String(e);
|
||||
if (/BUSY|LOCKED|SQLITE_BUSY|SQLITE_LOCKED/i.test(msg)) {
|
||||
await showDialog.check(`数据库锁定`, `请刷新页面(F5)后重试操作`);
|
||||
return;
|
||||
}
|
||||
// 其他错误直接抛出
|
||||
await db.execute("ROLLBACK;");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,9 +107,8 @@ async function insertGachaList(
|
||||
uid: string,
|
||||
list: Array<TGApp.Plugins.UIGF.GachaItemB>,
|
||||
): Promise<void> {
|
||||
for (const gacha of list) {
|
||||
await insertGachaItem(uid, gacha);
|
||||
}
|
||||
const db = await TGSqlite.getDB();
|
||||
await insertGachaBList(db, uid, list, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,27 +147,26 @@ async function mergeUIGF4(
|
||||
data: TGApp.Plugins.UIGF.GachaUgc,
|
||||
showProgress: boolean = false,
|
||||
): Promise<void> {
|
||||
let cnt: number = 0;
|
||||
const db = await TGSqlite.getDB();
|
||||
const len = data.list.length;
|
||||
let progress: number = 0;
|
||||
const cnt = ref<number>(0);
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
if (showProgress) {
|
||||
timer = setInterval(async () => {
|
||||
progress = Math.round((cnt / len) * 100 * 100) / 100;
|
||||
const current = data.list[cnt].time ?? "";
|
||||
const name = data.list[cnt].item_name ?? "";
|
||||
const rank = data.list[cnt].rank_type ?? "0";
|
||||
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`);
|
||||
const progress = Math.round((cnt.value / len) * 100 * 100) / 100;
|
||||
const current = data.list[cnt.value].time ?? "";
|
||||
const name = data.list[cnt.value].item_name ?? "";
|
||||
const rank = data.list[cnt.value].rank_type ?? "0";
|
||||
await showLoading.update(`[${progress}%][${current}] ${"⭐".repeat(Number(rank))}-${name}`, {
|
||||
timeout: 0,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
for (const gacha of data.list) {
|
||||
await insertGachaItem(data.uid.toString(), transGacha(gacha, data.timezone));
|
||||
cnt++;
|
||||
}
|
||||
const transformed = data.list.map((g) => transGacha(g));
|
||||
await insertGachaBList(db, data.uid.toString(), transformed, 100, cnt);
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
progress = 100;
|
||||
await showLoading.update(`[${progress}%] 完成`);
|
||||
await showLoading.update(`[100%] 完成`, { timeout: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 应用状态管理
|
||||
* @since Beta v0.9.1
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import bbsEnum from "@enum/bbs.js";
|
||||
@@ -67,6 +67,11 @@ const useAppStore = defineStore(
|
||||
const closeToTray = ref<boolean>(false);
|
||||
/** 展示反馈按钮 */
|
||||
const showFeedback = ref<boolean>(true);
|
||||
/**
|
||||
* 上次检测更新时间
|
||||
* @remarks LastUpdateCheckTimeStamp
|
||||
*/
|
||||
const lastUcts = ref<number>(0);
|
||||
|
||||
/**
|
||||
* 初始化应用状态
|
||||
@@ -91,6 +96,7 @@ const useAppStore = defineStore(
|
||||
cancelLike.value = true;
|
||||
closeToTray.value = false;
|
||||
showFeedback.value = true;
|
||||
lastUcts.value = 0;
|
||||
initDevice();
|
||||
}
|
||||
|
||||
@@ -149,6 +155,7 @@ const useAppStore = defineStore(
|
||||
cancelLike,
|
||||
closeToTray,
|
||||
showFeedback,
|
||||
lastUcts,
|
||||
init,
|
||||
changeTheme,
|
||||
getImageUrl,
|
||||
@@ -178,6 +185,7 @@ const useAppStore = defineStore(
|
||||
"cancelLike",
|
||||
"closeToTray",
|
||||
"showFeedback",
|
||||
"lastUcts",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
6
src/types/BBS/Post.d.ts
vendored
6
src/types/BBS/Post.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 帖子类型定义文件
|
||||
* @since Beta v0.8.6
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
declare namespace TGApp.BBS.Post {
|
||||
@@ -124,7 +124,7 @@ declare namespace TGApp.BBS.Post {
|
||||
|
||||
/**
|
||||
* 帖子数据
|
||||
* @since Beta v0.7.2
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
type FullData = {
|
||||
/** 帖子信息 */
|
||||
@@ -136,7 +136,7 @@ declare namespace TGApp.BBS.Post {
|
||||
/** 发帖人,可能为 null */
|
||||
user: User | null;
|
||||
/** 当前用户操作 */
|
||||
self_operation: TGApp.BBS.User.SelfOperation;
|
||||
self_operation: TGApp.BBS.User.SelfOperation | null;
|
||||
/** 帖子统计,可能为 null */
|
||||
stat: Stat | null;
|
||||
/** 帮助系统,可能为 null */
|
||||
|
||||
64
src/types/Plugins/Github.d.ts
vendored
Normal file
64
src/types/Plugins/Github.d.ts
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Github API 类型
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
declare namespace TGApp.Plugins.Github {
|
||||
/**
|
||||
* LatestReleaseResponse
|
||||
* @since Beta v0.9.8
|
||||
* @see https://api.github.com/repos/BTMuli/TeyvatGuide/releases/latest
|
||||
* @remarks 省略了不需要的子数据
|
||||
*/
|
||||
type LastestReleaseResp = {
|
||||
/** URL */
|
||||
url: string;
|
||||
/** Assets URL */
|
||||
assets_url: string;
|
||||
/** Upload URL */
|
||||
upload_url: string;
|
||||
/** Html URL */
|
||||
html_url: string;
|
||||
/** Release ID */
|
||||
id: number;
|
||||
/** Author */
|
||||
author: unknown;
|
||||
/** Node ID */
|
||||
node_id: string;
|
||||
/** Tag */
|
||||
tag_name: string;
|
||||
/** Commit Hash */
|
||||
target_commitish: string;
|
||||
/** Release Name */
|
||||
name: string;
|
||||
/** Draft */
|
||||
draft: boolean;
|
||||
/** Immutable */
|
||||
immutable: boolean;
|
||||
/** PreRelease */
|
||||
prerelease: boolean;
|
||||
/**
|
||||
* CreateTime
|
||||
* @example 2026-02-26T10:33:04Z
|
||||
*/
|
||||
created_at: string;
|
||||
/**
|
||||
* UpdateTime
|
||||
* @example 2026-02-27T08:53:27Z
|
||||
*/
|
||||
updated_at: string;
|
||||
/**
|
||||
* PublishTime
|
||||
* @example 2026-02-27T08:53:27Z
|
||||
*/
|
||||
published_at: string;
|
||||
/** Assets */
|
||||
assets: Array<unknown>;
|
||||
/** Tarball URL */
|
||||
tarball_url: string;
|
||||
/** Zipball URL */
|
||||
zipball_url: string;
|
||||
/** Release Body */
|
||||
body: string;
|
||||
};
|
||||
}
|
||||
26
src/utils/Github.ts
Normal file
26
src/utils/Github.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Github API
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import TGHttp from "@utils/TGHttp.js";
|
||||
|
||||
/**
|
||||
* 获取最新Release版本
|
||||
* @since Beta v0.9.8
|
||||
* @returns 最新版本
|
||||
*/
|
||||
export async function getLatestReleaseVersion(): Promise<string> {
|
||||
const latestReleaseApi: Readonly<string> =
|
||||
"https://api.github.com/repos/BTMuli/TeyvatGuide/releases/latest";
|
||||
try {
|
||||
const latestReleaseResp = await TGHttp<TGApp.Plugins.Github.LastestReleaseResp>(
|
||||
latestReleaseApi,
|
||||
{ method: "GET" },
|
||||
);
|
||||
return latestReleaseResp.tag_name.replace("v", "");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
/**
|
||||
* 游戏文件相关功能
|
||||
* @since Beta v0.9.6
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { documentDir, resourceDir, sep } from "@tauri-apps/api/path";
|
||||
import { copyFile, exists, mkdir, readTextFile, readTextFileLines } from "@tauri-apps/plugin-fs";
|
||||
import {
|
||||
copyFile,
|
||||
exists,
|
||||
mkdir,
|
||||
readDir,
|
||||
readTextFile,
|
||||
readTextFileLines,
|
||||
} from "@tauri-apps/plugin-fs";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
|
||||
@@ -33,7 +40,10 @@ export async function tryReadGameVer(gameDir: string): Promise<false | string> {
|
||||
const iniRead = await readTextFileLines(iniPath);
|
||||
while (true) {
|
||||
const line = await iniRead.next();
|
||||
if (line.value.startsWith("game_version=")) return line.value.split("=")[1];
|
||||
const lineRead = line.value;
|
||||
if (typeof lineRead === "string" && lineRead.startsWith("game_version=")) {
|
||||
return lineRead.split("=")[1];
|
||||
}
|
||||
if (line.done) break;
|
||||
}
|
||||
}
|
||||
@@ -97,11 +107,13 @@ export async function tryCallYae(gameDir: string, uid?: string): Promise<void> {
|
||||
showSnackbar.warn("请前往设置页面设置游戏安装目录");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${gameDir}${sep()}YuanShen.exe`;
|
||||
if (!(await exists(gamePath))) {
|
||||
const dirRead = await readDir(gameDir);
|
||||
const find = dirRead.find((i) => i.isFile && i.name.toLowerCase() === "yuanshen.exe");
|
||||
if (!find) {
|
||||
showSnackbar.warn("未检测到游戏本体");
|
||||
return;
|
||||
}
|
||||
const gamePath = `${gameDir}${sep()}${find.name}`;
|
||||
const isRun = await invoke<boolean>("is_process_running", { processName: "Yuanshen.exe" });
|
||||
if (isRun) {
|
||||
showSnackbar.warn("检测到已启动的原神进程,请关闭进程(Yuanshen.exe)后重试");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* 窗口创建相关工具函数
|
||||
* @since Beta v0.9.6
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import type { RenderCard } from "@comp/app/t-postcard.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { core, webviewWindow, window as TauriWindow } from "@tauri-apps/api";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { PhysicalSize } from "@tauri-apps/api/dpi";
|
||||
import { PhysicalPosition, PhysicalSize } from "@tauri-apps/api/dpi";
|
||||
import { currentMonitor, WindowOptions } from "@tauri-apps/api/window";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
|
||||
@@ -99,6 +99,45 @@ export function getWindowSize(label: string): PhysicalSize {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断窗口位置,确保窗口不超出屏幕并居中
|
||||
* @since Beta v0.9.8
|
||||
* @remarks 当窗口超出屏幕时回滚到 resizeWindow,此时回正配置默认生效
|
||||
* @returns 无返回值
|
||||
*/
|
||||
export async function setWindowPos(): Promise<void> {
|
||||
const screen = await currentMonitor();
|
||||
const NAV_BAR_HEIGHT = 28;
|
||||
if (screen === null) {
|
||||
showSnackbar.error("获取屏幕信息失败!", 3000);
|
||||
return;
|
||||
}
|
||||
const windowCur = webviewWindow.getCurrentWebviewWindow();
|
||||
if (await windowCur.isMaximized()) return;
|
||||
const designSize = getWindowSize(windowCur.label);
|
||||
const screenScale = screen.scaleFactor;
|
||||
const targetWidth = Math.round(designSize.width * screenScale);
|
||||
const targetHeight = Math.round(designSize.height * screenScale);
|
||||
const cpWidth = screen.size.width - NAV_BAR_HEIGHT * screenScale;
|
||||
const cpHeight = screen.size.height - NAV_BAR_HEIGHT * screenScale;
|
||||
if (targetWidth > cpWidth && targetHeight > cpHeight) {
|
||||
await resizeWindow();
|
||||
await windowCur.center();
|
||||
} else if (targetHeight > cpHeight) {
|
||||
const left = (screen.size.width - targetWidth) / 2;
|
||||
await windowCur.setSize(new PhysicalSize(targetWidth, targetHeight));
|
||||
await windowCur.setPosition(new PhysicalPosition(left, 24));
|
||||
} else if (targetWidth > screen.size.width) {
|
||||
const top = (screen.size.height - targetHeight) / 2;
|
||||
await windowCur.setSize(new PhysicalSize(targetWidth, targetHeight));
|
||||
await windowCur.setPosition(new PhysicalPosition(24, top));
|
||||
} else {
|
||||
await windowCur.setSize(new PhysicalSize(targetWidth, targetHeight));
|
||||
await windowCur.center();
|
||||
}
|
||||
await windowCur.setZoom(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口适配
|
||||
* @since Beta v0.9.6
|
||||
|
||||
@@ -94,6 +94,7 @@ async function createAnnoJson(): Promise<void> {
|
||||
|
||||
.anno-body {
|
||||
width: 800px;
|
||||
max-width: calc(100% - 100px);
|
||||
margin: 0 auto;
|
||||
font-family: var(--font-text);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,15 @@
|
||||
<TMiImg
|
||||
:src="getGameIcon(postData?.forum?.game_id || postData.post.game_id)"
|
||||
alt="gameIcon"
|
||||
title="点击前往资讯页面"
|
||||
@click="toGame(postData?.forum?.game_id || postData.post.game_id)"
|
||||
/>
|
||||
<div v-if="postData.forum" class="mpm-forum" @click="toForum(postData.forum)">
|
||||
<div
|
||||
v-if="postData.forum"
|
||||
class="mpm-forum"
|
||||
title="点击前往版块页面"
|
||||
@click="toForum(postData.forum)"
|
||||
>
|
||||
<TMiImg :ori="true" :src="postData.forum.icon" alt="forumIcon" />
|
||||
<span>{{ postData.forum.name }}</span>
|
||||
</div>
|
||||
@@ -245,7 +251,7 @@ onMounted(async () => {
|
||||
}
|
||||
postData.value = resp;
|
||||
console.log(resp);
|
||||
isLike.value = postData.value.self_operation.upvote_type !== 0;
|
||||
isLike.value = (postData.value.self_operation?.upvote_type ?? 0) > 0;
|
||||
await showLoading.update("正在渲染数据");
|
||||
renderPost.value = await getRenderPost(postData.value);
|
||||
await webviewWindow
|
||||
@@ -279,7 +285,10 @@ onUnmounted(() => {
|
||||
|
||||
async function openJson(): Promise<void> {
|
||||
// @ts-expect-error import.meta
|
||||
if (import.meta.env.MODE === "production") return;
|
||||
if (import.meta.env.MODE === "production") {
|
||||
await toPost();
|
||||
return;
|
||||
}
|
||||
await createPostJson(postId);
|
||||
}
|
||||
|
||||
@@ -452,7 +461,7 @@ async function toTopic(topic: TGApp.BBS.Post.Topic): Promise<void> {
|
||||
}
|
||||
|
||||
async function toGame(gameId: number): Promise<void> {
|
||||
await emit("active_deep_link", `router?path=/posts/forum/${gameId}`);
|
||||
await emit("active_deep_link", `router?path=/news/${gameId}`);
|
||||
}
|
||||
|
||||
async function toForum(forum: TGApp.BBS.Post.Forum): Promise<void> {
|
||||
@@ -486,7 +495,9 @@ function handleUser(user: TGApp.BBS.Post.User): void {
|
||||
@use "@styles/github.styles.scss" as github-styles;
|
||||
|
||||
.tp-post-body {
|
||||
position: relative;
|
||||
width: v-bind(viewWidth); /* stylelint-disable-line value-keyword-case */
|
||||
max-width: calc(100% - 100px);
|
||||
margin: 0 auto;
|
||||
font-family: var(--font-text);
|
||||
transition: width 0.3s ease-in-out;
|
||||
|
||||
Reference in New Issue
Block a user