mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-24 05:19:45 +08:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2807ce48a6 | ||
|
|
f9b64c5c6a | ||
|
|
1038c9cdb7 | ||
|
|
87f9df80a5 | ||
|
|
d8f4a4c2bf | ||
|
|
3537751d65 | ||
|
|
66f77da754 | ||
|
|
7da01c117d | ||
|
|
722b5598fe | ||
|
|
ba962ae4c6 | ||
|
|
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 |
@@ -1,3 +1,3 @@
|
||||
VITE_SENTRY_RELEASE=TeyvatGuide@0.9.7
|
||||
VITE_COMMIT_HASH=fcc5d3db
|
||||
VITE_BUILD_TIME=1772101907
|
||||
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
|
||||
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -2,12 +2,36 @@
|
||||
Author: 目棃
|
||||
Description: CHANGELOG
|
||||
Date: 2025-09-09
|
||||
Update: 2026-02-26
|
||||
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-26 18:31:44`
|
||||
> 更新于 `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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
58
package.json
58
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "teyvatguide",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.8",
|
||||
"description": "Game Tool for GenshinImpact player",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.30.1",
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"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.45.0",
|
||||
"@sentry/vite-plugin": "^5.1.1",
|
||||
"@sentry/vue": "^10.45.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,64 +101,64 @@
|
||||
"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",
|
||||
"vue-router": "^5.0.4",
|
||||
"vuetify": "^4.0.3",
|
||||
"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",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260222.1",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"@types/node": "^25.5.0",
|
||||
"@typescript-eslint/parser": "^8.57.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260321.1",
|
||||
"@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.4.0",
|
||||
"oxlint": "^1.56.0",
|
||||
"postcss-preset-env": "^11.2.0",
|
||||
"prettier": "3.8.1",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint": "^17.5.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.1.1",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"stylelint-scss": "^7.0.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"vite": "npm:rolldown-vite@^7.3.1",
|
||||
"vite-plugin-vue-devtools": "^8.0.6",
|
||||
"typescript-eslint": "^8.57.1",
|
||||
"vite": "^8.0.1",
|
||||
"vite-plugin-vue-devtools": "^8.1.0",
|
||||
"vite-plugin-vuetify": "^2.1.3",
|
||||
"vue-eslint-parser": "^10.4.0",
|
||||
"vue-tsc": "^3.2.5",
|
||||
"vue-tsc": "^3.2.6",
|
||||
"yaml-eslint-parser": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
2937
pnpm-lock.yaml
generated
2937
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
850
src-tauri/Cargo.lock
generated
850
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.7"
|
||||
version = "0.9.8"
|
||||
description = "Game Tool for Genshin Impact player"
|
||||
authors = ["BTMuli <bt-muli@outlook.com>"]
|
||||
license = "MIT"
|
||||
@@ -17,19 +17,19 @@ 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"
|
||||
image = "0.25.9"
|
||||
chrono = "0.4.44"
|
||||
image = "0.25.10"
|
||||
log = "0.4.29"
|
||||
prost = "=0.14.1"
|
||||
prost-types = "=0.14.1"
|
||||
sentry = { version = "0.46.2", features = ["backtrace", "panic"] }
|
||||
sentry = { version = "0.47.0", 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.7",
|
||||
"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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 全局样式文件
|
||||
* @since Beta v0.9.6
|
||||
* @since Beta v0.9.9
|
||||
*/
|
||||
|
||||
@use "fonts/index";
|
||||
@@ -54,7 +54,7 @@
|
||||
--tgi-geetest: 100;
|
||||
--tgi-loading: 100;
|
||||
--tgi-hyperlink: 100;
|
||||
--tgi-snackbar: 999;
|
||||
--tgi-snackbar: 9999;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 :title="props.label" class="tgni-box">
|
||||
<slot name="icon"></slot>
|
||||
<span v-show="!props.mini" 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;
|
||||
@@ -44,8 +45,16 @@ async function switchPin(): Promise<void> {
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
&.active {
|
||||
animation: pin-pulse 0.3s ease;
|
||||
background: var(--tgc-btn-1);
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--btn-text);
|
||||
@@ -53,6 +62,21 @@ async function switchPin(): Promise<void> {
|
||||
|
||||
&:hover:not(.active) {
|
||||
background: var(--common-shadow-1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pin-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ function switchPostWidth(): void {
|
||||
@include github-styles.github-card;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 112px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
@@ -41,8 +42,16 @@ function switchPostWidth(): void {
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
&.active {
|
||||
animation: width-pulse 0.3s ease;
|
||||
background: var(--tgc-btn-1);
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--btn-text);
|
||||
@@ -50,6 +59,21 @@ function switchPostWidth(): void {
|
||||
|
||||
&:hover:not(.active) {
|
||||
background: var(--common-shadow-1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes width-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,23 +19,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="props.modelValue.post.images.length > 1"
|
||||
:title="`图片数:${props.modelValue.post.images.length}`"
|
||||
v-else-if="props.post.post.images.length > 1"
|
||||
:title="`图片数:${props.post.post.images.length}`"
|
||||
class="tpc-image-cnt"
|
||||
>
|
||||
<v-icon size="10">mdi-folder-multiple-image</v-icon>
|
||||
<span>{{ props.modelValue.post.images.length }}</span>
|
||||
<span>{{ props.post.post.images.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :title="card.title" class="tpc-title" @click="shareCard()">{{ card.title }}</div>
|
||||
</div>
|
||||
<div v-if="card.user !== null" class="tpc-mid">
|
||||
<TpAvatar
|
||||
:data="card.user"
|
||||
:style="{ cursor: props.userClick ? 'pointer' : 'default' }"
|
||||
position="left"
|
||||
@click="onUserClick()"
|
||||
/>
|
||||
<TpAvatar :data="card.user" position="left" @click="onUserClick()" />
|
||||
</div>
|
||||
<div class="tpc-bottom">
|
||||
<div class="tpc-tags">
|
||||
@@ -103,10 +98,10 @@
|
||||
data-html2canvas-ignore
|
||||
@click.stop="trySelect()"
|
||||
/>
|
||||
<div v-else class="tpc-info-id">
|
||||
<span>{{ props.modelValue.post.post_id }}</span>
|
||||
<div v-else class="tpc-info-id" @click="shareCard()">
|
||||
<span>{{ props.post.post.post_id }}</span>
|
||||
<template v-if="isDevEnv">
|
||||
<span data-html2canvas-ignore>[{{ props.modelValue.post.view_type }}]</span>
|
||||
<span data-html2canvas-ignore>[{{ props.post.post.view_type }}]</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,10 +121,12 @@ import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
/** 帖子卡片参数 */
|
||||
type TPostCardProps = {
|
||||
modelValue: TGApp.BBS.Post.FullData;
|
||||
/** 帖子数据 */
|
||||
post: TGApp.BBS.Post.FullData;
|
||||
/** 是否开启选择模式 */
|
||||
selectMode?: boolean;
|
||||
userClick?: boolean;
|
||||
};
|
||||
type TPostCardEmits = {
|
||||
(e: "onSelected", v: string): void;
|
||||
@@ -178,17 +175,17 @@ const cardBg = computed<string>(() => {
|
||||
const forumBg = computed<string>(() =>
|
||||
str2Color(`${card.value?.forum?.id}${card.value?.forum?.icon}${card.value?.forum?.name}`, -60),
|
||||
);
|
||||
const idBg = computed<string>(() => str2Color(`${props.modelValue.post.post_id}`, 0));
|
||||
const idBg = computed<string>(() => str2Color(`${props.post.post.post_id}`, 0));
|
||||
|
||||
onMounted(async () => (card.value = getPostCard(props.modelValue)));
|
||||
onMounted(async () => (card.value = getPostCard(props.post)));
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => (card.value = getPostCard(props.modelValue)),
|
||||
() => props.post,
|
||||
async () => (card.value = getPostCard(props.post)),
|
||||
);
|
||||
|
||||
function trySelect(): void {
|
||||
if (props.selectMode) emits("onSelected", props.modelValue.post.post_id);
|
||||
if (props.selectMode) emits("onSelected", props.post.post.post_id);
|
||||
isSelected.value = !isSelected.value;
|
||||
}
|
||||
|
||||
@@ -230,7 +227,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 };
|
||||
@@ -293,20 +290,20 @@ async function shareCard(): Promise<void> {
|
||||
|
||||
async function toTopic(topic: TGApp.BBS.Post.Topic): Promise<void> {
|
||||
if (props.selectMode) return;
|
||||
const gid = props.modelValue.post.game_id;
|
||||
const gid = props.post.post.game_id;
|
||||
await emit("active_deep_link", `router?path=/posts/topic/${gid}/${topic.id}`);
|
||||
}
|
||||
|
||||
async function toForum(forum: RenderForum): Promise<void> {
|
||||
if (props.selectMode) return;
|
||||
const gid = props.modelValue.post.game_id;
|
||||
const gid = props.post.post.game_id;
|
||||
await emit("active_deep_link", `router?path=/posts/forum/${gid}/${forum.id}`);
|
||||
}
|
||||
|
||||
function onUserClick(): void {
|
||||
if (props.selectMode) return;
|
||||
if (!card.value || card.value.user === null) return;
|
||||
emits("onUserClick", card.value.user, props.modelValue.post.game_id);
|
||||
emits("onUserClick", card.value.user, props.post.post.game_id);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -575,6 +572,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;
|
||||
@@ -54,6 +55,16 @@ async function shareContent(): Promise<void> {
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--btn-text);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.share-box:hover {
|
||||
box-shadow: 2px 4px 12px var(--common-shadow-4);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
.share-box:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.dark .share-box {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- 应用侧边栏 -->
|
||||
<template>
|
||||
<v-navigation-drawer :permanent="true" :rail="rail" class="tsb-box">
|
||||
<v-navigation-drawer :permanent="true" :rail="rail" :width="160" class="tsb-box">
|
||||
<v-list :nav="true" class="side-list" density="compact">
|
||||
<v-list-item
|
||||
:append-icon="!rail ? 'mdi-chevron-left' : undefined"
|
||||
@@ -332,7 +332,6 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
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";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, onUnmounted, ref, shallowRef } from "vue";
|
||||
@@ -690,6 +689,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({
|
||||
@@ -749,34 +749,13 @@ async function tryLaunchGame(): Promise<void> {
|
||||
await TGLogger.Error(`[sidebar][tryLaunchGame] resp: ${JSON.stringify(resp)}`);
|
||||
return;
|
||||
}
|
||||
const isInAdmin = await isRunInAdmin();
|
||||
const gameVer = await tryReadGameVer(gameDir.value);
|
||||
if (!isInAdmin || !gameVer || gameVer !== YAE_GAME_VER) {
|
||||
showSnackbar.success(`成功获取ticket:${resp},正在启动应用...`);
|
||||
try {
|
||||
await invoke("launch_game", { path: gamePath, ticket: resp });
|
||||
} catch (error) {
|
||||
showSnackbar.error(`${error}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const isMsix = await invoke<boolean>("is_msix");
|
||||
if (isMsix) {
|
||||
const copy = await tryCopyYae();
|
||||
if (!copy) return;
|
||||
}
|
||||
showSnackbar.success(`成功获取ticket:${resp},正在启动应用...`);
|
||||
try {
|
||||
await invoke("call_yae_dll", {
|
||||
gamePath: gamePath,
|
||||
uid: account.value.gameUid,
|
||||
ticket: resp,
|
||||
isMsix: isMsix,
|
||||
});
|
||||
} catch (err) {
|
||||
showSnackbar.error(`调用Yae DLL失败: ${err}`);
|
||||
await TGLogger.Error(`[pageAchi][toYae]调用Yae DLL失败: ${err}`);
|
||||
return;
|
||||
await invoke("launch_game", { path: gamePath, ticket: resp });
|
||||
} catch (error) {
|
||||
showSnackbar.error(`${error}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -791,9 +770,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);
|
||||
}
|
||||
|
||||
@@ -806,7 +782,7 @@ async function tryLaunchGame(): Promise<void> {
|
||||
|
||||
.bottom-menu {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -818,6 +794,7 @@ async function tryLaunchGame(): Promise<void> {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.paimon {
|
||||
position: relative;
|
||||
@@ -826,6 +803,10 @@ async function tryLaunchGame(): Promise<void> {
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
.side-list-menu {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="switch-box" :title="isDefault ? '切换到深色模式' : '切换到浅色模式'">
|
||||
<div class="switch-btn" @click="switchTheme()">
|
||||
<div class="switch-btn" :class="{ 'is-dark': !isDefault }" @click="switchTheme()">
|
||||
<v-icon size="20">
|
||||
{{ isDefault ? "mdi-weather-night" : "mdi-weather-sunny" }}
|
||||
</v-icon>
|
||||
@@ -43,6 +43,7 @@ onUnmounted(() => {
|
||||
|
||||
.switch-box {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
@@ -56,6 +57,12 @@ onUnmounted(() => {
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--btn-text);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.switch-box:hover {
|
||||
box-shadow: 1px 3px 10px var(--common-shadow-4);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.dark .switch-box {
|
||||
@@ -71,5 +78,10 @@ onUnmounted(() => {
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
.switch-btn.is-dark {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<!-- 兑换码浮窗组件 -->
|
||||
<template>
|
||||
<TOverlay v-model="visible" class="tolc-overlay">
|
||||
<div class="tolc-box">
|
||||
<div ref="TolcRef" class="tolc-box">
|
||||
<div class="tolc-title">
|
||||
<span>{{ gameInfo?.name ?? "" }}兑换码</span>
|
||||
<v-icon
|
||||
data-html2canvas-ignore
|
||||
icon="mdi-share-variant"
|
||||
size="18px"
|
||||
title="share"
|
||||
@click="shareImg()"
|
||||
icon="mdi-share-variant"
|
||||
variant="outlined"
|
||||
data-html2canvas-ignore
|
||||
@click="shareImg()"
|
||||
/>
|
||||
</div>
|
||||
<div class="tolc-info">ActID:{{ props.actId }}</div>
|
||||
<div v-for="(item, index) in props.data" :key="index" class="tolc-list-box">
|
||||
<div class="tolc-list-icon">
|
||||
<img v-if="item.img === ''" src="/UI/app/empty.webp" alt="empty" />
|
||||
<TMiImg :src="item.img" :ori="true" v-else alt="award" />
|
||||
<img v-if="item.img === ''" alt="empty" src="/UI/app/empty.webp" />
|
||||
<TMiImg v-else :ori="true" :src="item.img" alt="award" />
|
||||
</div>
|
||||
<div class="tolc-list-info">
|
||||
<span>{{ item.code === "" ? "暂无兑换码" : item.code }}</span>
|
||||
@@ -26,12 +26,12 @@
|
||||
</div>
|
||||
<div class="tolc-list-btn">
|
||||
<v-btn
|
||||
size="small"
|
||||
:disabled="item.code === ''"
|
||||
@click="copy(item.code)"
|
||||
icon="mdi-content-copy"
|
||||
variant="outlined"
|
||||
data-html2canvas-ignore
|
||||
icon="mdi-content-copy"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
@click="copy(item.code)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,13 +39,13 @@
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import useBBSStore from "@store/bbs.js";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { timestampToDate } from "@utils/toolFunc.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
import { computed, useTemplateRef } from "vue";
|
||||
|
||||
import TMiImg from "./t-mi-img.vue";
|
||||
import TOverlay from "./t-overlay.vue";
|
||||
@@ -66,6 +66,7 @@ const { gameList } = storeToRefs(useBBSStore());
|
||||
|
||||
const props = defineProps<ToLiveCodeProps>();
|
||||
const visible = defineModel<boolean>({ default: false });
|
||||
const tolcEl = useTemplateRef<HTMLDivElement>("TolcRef");
|
||||
const gameInfo = computed<TGApp.BBS.Game.Item | undefined>(() =>
|
||||
gameList.value.find((i) => i.id === props.gid),
|
||||
);
|
||||
@@ -80,23 +81,27 @@ async function copy(code: string): Promise<void> {
|
||||
showSnackbar.success("已复制到剪贴板");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成分享图片
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function shareImg(): Promise<void> {
|
||||
const element = document.querySelector<HTMLElement>(".tolc-box");
|
||||
if (element === null) {
|
||||
if (tolcEl.value === null) {
|
||||
showSnackbar.warn("未获取到分享内容");
|
||||
return;
|
||||
}
|
||||
const fileName = `LiveCode_${props.gid}_${props.actId}_${new Date().getTime()}`;
|
||||
await generateShareImg(fileName, element, 4);
|
||||
await generateShareImg(fileName, tolcEl.value, 4);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
<style lang="scss" scoped>
|
||||
/** 解决默认样式的上下 margin */
|
||||
|
||||
:deep(p) {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
.tolc-overlay {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
height: 100vh;
|
||||
color: var(--app-page-content);
|
||||
}
|
||||
|
||||
.tolc-box {
|
||||
|
||||
@@ -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) {
|
||||
@@ -170,9 +176,9 @@ defineExpose({ displayBox });
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border: #f4d8a8ff 1px solid;
|
||||
border: var(--tgc-yellow-3) 1px solid;
|
||||
border-radius: 5px;
|
||||
color: #f4d8a8ff;
|
||||
color: var(--tgc-yellow-2);
|
||||
}
|
||||
|
||||
.loading-title {
|
||||
|
||||
@@ -76,6 +76,7 @@ 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";
|
||||
|
||||
@@ -109,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;
|
||||
@@ -123,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> {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -70,8 +70,10 @@ import { computed, onMounted, ref, shallowRef, watch } from "vue";
|
||||
type UgoUidProps = {
|
||||
/** 导入/导出 */
|
||||
mode: "import" | "export";
|
||||
/** filePathImport,导出路径 */
|
||||
/** filePathImport,导入路径 */
|
||||
fpi?: string;
|
||||
/** filePathExport,导出路径 */
|
||||
fpe?: string;
|
||||
};
|
||||
/**
|
||||
* UID项
|
||||
@@ -107,7 +109,7 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [visible.value, props.mode, props.fpi],
|
||||
() => [visible.value, props.mode, props.fpi, props.fpe],
|
||||
async () => {
|
||||
if (visible.value) await refreshData();
|
||||
},
|
||||
@@ -127,7 +129,7 @@ async function refreshData(): Promise<void> {
|
||||
fp.value = props.fpi ?? fpEmptyText;
|
||||
await refreshImport();
|
||||
} else {
|
||||
fp.value = await getDefaultSavePath();
|
||||
fp.value = props.fpe ?? (await getDefaultSavePath());
|
||||
await refreshExport();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<!-- 头像组件 -->
|
||||
<template>
|
||||
<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 +34,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;
|
||||
@@ -47,7 +49,7 @@ const levelColor = computed<string>(() => {
|
||||
return "var(--tgc-od-white)";
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
<style lang="scss" scoped>
|
||||
.tp-avatar-box {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
@@ -56,6 +58,7 @@ const levelColor = computed<string>(() => {
|
||||
flex-direction: v-bind("props.position === 'left' ? 'row' : 'row-reverse'");
|
||||
align-items: center;
|
||||
justify-content: v-bind("props.position === 'left' ? 'flex-start' : 'flex-end'");
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tpa-text {
|
||||
|
||||
@@ -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;
|
||||
@@ -97,6 +98,7 @@ async function switchCollect(): Promise<void> {
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&.active {
|
||||
background: var(--tgc-btn-1);
|
||||
@@ -107,6 +109,15 @@ async function switchCollect(): Promise<void> {
|
||||
&:hover:not(.active) {
|
||||
background: var(--common-shadow-1);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 2px 4px 12px var(--common-shadow-4);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tbc-box {
|
||||
|
||||
@@ -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;
|
||||
@@ -224,6 +225,16 @@ async function handleDebug(): Promise<void> {
|
||||
box-shadow: 1px 3px 6px var(--common-shadow-2);
|
||||
color: var(--btn-text);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.tpr-main-box:hover {
|
||||
box-shadow: 2px 4px 12px var(--common-shadow-4);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
.tpr-main-box:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.dark .tpr-main-box {
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
<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"
|
||||
v-for="(post, index) in postList"
|
||||
:key="index"
|
||||
:model-value="item"
|
||||
:class="{ selected: index === props.collection.cur - 1 }"
|
||||
:post
|
||||
@onUserClick="toUserProfile"
|
||||
class="tpoc-item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,6 +27,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 +66,17 @@ 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}`);
|
||||
}
|
||||
|
||||
async function toUserProfile(user: TGApp.BBS.Post.User, gid: number): Promise<void> {
|
||||
// TODO: 专门的个人页面
|
||||
console.log(user, gid);
|
||||
const profileUrl = `https://www.miyoushe.com/ys/accountCenter/postList?id=${user.uid}`;
|
||||
await openUrl(profileUrl);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tpoc-box {
|
||||
@@ -73,21 +86,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 {
|
||||
|
||||
@@ -3,32 +3,34 @@
|
||||
<div class="vp-of-box">
|
||||
<div class="vo-of-top">
|
||||
<div class="vo-oft-left">
|
||||
<img src="/platforms/mhy/mys.webp" alt="icon" />
|
||||
<img alt="icon" src="/platforms/mhy/mys.webp" />
|
||||
<span>关注动态</span>
|
||||
</div>
|
||||
<div class="vo-oft-right">已加载:{{ posts.length }}条</div>
|
||||
</div>
|
||||
<div class="vo-of-actions">
|
||||
<v-btn class="vo-of-btn" @click="loadMore(true)" :loading="loading">刷新</v-btn>
|
||||
<v-btn :loading="loading" class="vo-of-btn" @click="loadMore(true)">刷新</v-btn>
|
||||
</div>
|
||||
<div class="vp-of-list" ref="listRef">
|
||||
<div ref="listRef" class="vp-of-list">
|
||||
<TPostcard
|
||||
v-for="post in posts"
|
||||
:key="post.post.post_id"
|
||||
:post
|
||||
class="vp-of-list-item"
|
||||
v-for="(item, index) in posts"
|
||||
:key="index"
|
||||
:model-value="item"
|
||||
@onUserClick="toUserProfile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TOverlay>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import TOverlay from "@comp/app/t-overlay.vue";
|
||||
import TPostcard from "@comp/app/t-postcard.vue";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { useBoxReachBottom } from "@hooks/reachBottom.js";
|
||||
import painterReq from "@req/painterReq.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
@@ -87,6 +89,13 @@ async function loadMore(refresh: boolean = false): Promise<void> {
|
||||
listEl.value.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
|
||||
async function toUserProfile(user: TGApp.BBS.Post.User, gid: number): Promise<void> {
|
||||
// TODO: 专门的个人页面
|
||||
console.log(user, gid);
|
||||
const profileUrl = `https://www.miyoushe.com/ys/accountCenter/postList?id=${user.uid}`;
|
||||
await openUrl(profileUrl);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.vp-of-box {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<span>{{ timeStatus }}</span>
|
||||
</div>
|
||||
<div class="tpol-info">
|
||||
<TpAvatar :data="card.creator" position="left" />
|
||||
<TpAvatar @click="toUserProfile(card.creator.uid)" :data="card.creator" position="left" />
|
||||
<div>参与方式:{{ upWay }}</div>
|
||||
<div>奖品详情:</div>
|
||||
<div v-for="reward in card.rewards" :key="reward.name" class="tpol-info-reward">
|
||||
@@ -41,6 +41,7 @@ import showSnackbar from "@comp/func/snackbar.js";
|
||||
import TpAvatar from "@comp/viewPost/tp-avatar.vue";
|
||||
import painterReq from "@req/painterReq.js";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { generateShareImg } from "@utils/TGShare.js";
|
||||
import { stamp2LastTime } from "@utils/toolFunc.js";
|
||||
import { onUnmounted, ref, shallowRef, watch } from "vue";
|
||||
@@ -155,6 +156,12 @@ async function shareLottery(): Promise<void> {
|
||||
await generateShareImg(fileName, shareDom, 2, true);
|
||||
}
|
||||
|
||||
async function toUserProfile(uid: string): Promise<void> {
|
||||
// TODO: 专门的个人页面
|
||||
const profileUrl = `https://www.miyoushe.com/ys/accountCenter/postList?id=${uid}`;
|
||||
await openUrl(profileUrl);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer !== undefined) {
|
||||
clearInterval(timer);
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
<div class="tops-divider" />
|
||||
<div ref="listRef" class="tops-list">
|
||||
<TPostCard
|
||||
v-for="item in results"
|
||||
:key="item.post.post_id"
|
||||
:model-value="item"
|
||||
v-for="post in results"
|
||||
:key="post.post.post_id"
|
||||
:post
|
||||
@onUserClick="toUserProfile"
|
||||
class="tops-item"
|
||||
/>
|
||||
</div>
|
||||
@@ -40,6 +41,7 @@ import showSnackbar from "@comp/func/snackbar.js";
|
||||
import { useBoxReachBottom } from "@hooks/reachBottom.js";
|
||||
import postReq from "@req/postReq.js";
|
||||
import useBBSStore from "@store/bbs.js";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref, shallowRef, useTemplateRef, watch } from "vue";
|
||||
|
||||
@@ -111,7 +113,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 = "";
|
||||
@@ -160,6 +162,13 @@ async function searchPosts(): Promise<void> {
|
||||
if (!visible.value) visible.value = true;
|
||||
showSnackbar.success(`成功加载${res.posts.length}条数据`);
|
||||
}
|
||||
|
||||
async function toUserProfile(user: TGApp.BBS.Post.User, gid: number): Promise<void> {
|
||||
// TODO: 专门的个人页面
|
||||
console.log(user, gid);
|
||||
const profileUrl = `https://www.miyoushe.com/ys/accountCenter/postList?id=${user.uid}`;
|
||||
await openUrl(profileUrl);
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.tops-box {
|
||||
|
||||
@@ -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,30 @@
|
||||
<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
|
||||
@onUserClick="toUserProfile()"
|
||||
v-for="post in results"
|
||||
:key="post.post.post_id"
|
||||
:post
|
||||
class="vp-ou-item"
|
||||
:model-value="item"
|
||||
v-for="item in results"
|
||||
:key="item.post.post_id"
|
||||
/>
|
||||
</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 +48,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 +64,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 +114,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 +165,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: "选择胡桃工具箱导出的剧诗数据文件",
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
<UgoUid v-model="ovShow" :fpi="ovFpi" :mode="ovMode" />
|
||||
<UgoUid v-model="ovShow" :fpe="ovFpe" :fpi="ovFpi" :mode="ovMode" />
|
||||
<UgoHutaoDu v-model="hutaoShow" :mode="htMode" @selected="handleHutaoDu" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -181,6 +181,7 @@ const { isLogin: isLoginHutao, accessToken, userName, userInfo } = storeToRefs(h
|
||||
const ovMode = ref<"export" | "import">("import");
|
||||
const ovShow = ref<boolean>(false);
|
||||
const ovFpi = ref<string>();
|
||||
const ovFpe = ref<string>();
|
||||
|
||||
const authkey = ref<string>("");
|
||||
const uidCur = ref<string>();
|
||||
@@ -549,7 +550,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,
|
||||
@@ -654,7 +655,18 @@ async function exportUigf4(): Promise<void> {
|
||||
showSnackbar.error("未获取到 UID");
|
||||
return;
|
||||
}
|
||||
const tsNow = Math.floor(Date.now() / 1000);
|
||||
const file = await save({
|
||||
title: "导出祈愿数据",
|
||||
filters: [{ name: "UIGF JSON", extensions: ["json"] }],
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}UIGFv4.2_${tsNow}.json`,
|
||||
});
|
||||
if (!file) {
|
||||
showSnackbar.cancel("已取消文件保存");
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(`[UserGacha][${uidCur.value}][exportUigf4] 导出祈愿数据(v4)`);
|
||||
ovFpe.value = file;
|
||||
ovMode.value = "export";
|
||||
ovShow.value = true;
|
||||
}
|
||||
@@ -684,11 +696,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> {
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
<UgoUid v-model="ovShow" :fpi="ovFpi" :mode="ovMode" />
|
||||
<UgoUid v-model="ovShow" :fpe="ovFpe" :fpi="ovFpi" :mode="ovMode" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
@@ -99,7 +99,7 @@ import TSUserAccount from "@Sqlm/userAccount.js";
|
||||
import TSUserGachaB from "@Sqlm/userGachaB.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import { path } from "@tauri-apps/api";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { open, save } from "@tauri-apps/plugin-dialog";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, shallowRef, watch } from "vue";
|
||||
@@ -113,6 +113,7 @@ const { account, cookie } = storeToRefs(useUserStore());
|
||||
const ovMode = ref<"export" | "import">("import");
|
||||
const ovShow = ref<boolean>(false);
|
||||
const ovFpi = ref<string>();
|
||||
const ovFpe = ref<string>();
|
||||
|
||||
const authkey = ref<string>("");
|
||||
const uidCur = ref<string>();
|
||||
@@ -315,14 +316,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> {
|
||||
@@ -348,7 +352,18 @@ async function exportUigf(): Promise<void> {
|
||||
showSnackbar.error("未获取到 UID");
|
||||
return;
|
||||
}
|
||||
const tsNow = Math.floor(Date.now() / 1000);
|
||||
const file = await save({
|
||||
title: "导出祈愿数据",
|
||||
filters: [{ name: "UIGF JSON", extensions: ["json"] }],
|
||||
defaultPath: `${await path.downloadDir()}${path.sep()}UIGFv4.2_${tsNow}.json`,
|
||||
});
|
||||
if (!file) {
|
||||
showSnackbar.cancel("已取消文件保存");
|
||||
return;
|
||||
}
|
||||
await TGLogger.Info(`[UserGachaB][${uidCur.value}][exportUigf] 导出祈愿数据`);
|
||||
ovFpe.value = file;
|
||||
ovMode.value = "export";
|
||||
ovShow.value = true;
|
||||
}
|
||||
|
||||
@@ -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}的战绩数据`);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #item="{ props, item }">
|
||||
<div class="us-select-item" v-bind="props" @click="() => (curAccount = item)">
|
||||
<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="us-si-content">
|
||||
<span>{{ item.brief.nickname }}</span>
|
||||
@@ -35,9 +40,24 @@
|
||||
</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 {
|
||||
@@ -264,10 +313,14 @@ async function tryExecAll(): Promise<void> {
|
||||
column-gap: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
&.selected:not(:hover) {
|
||||
background: var(--common-shadow-1);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--common-shadow-2);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
@@ -289,7 +342,7 @@ async function tryExecAll(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
.append {
|
||||
.us-si-append {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -329,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);
|
||||
|
||||
@@ -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 === "") {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" :mini="true" />
|
||||
</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;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,11 +103,10 @@
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<div class="pc-posts">
|
||||
<div v-for="item in curPosts" :key="item.post.post_id">
|
||||
<div v-for="post in curPosts" :key="post.post.post_id">
|
||||
<TPostCard
|
||||
:model-value="item"
|
||||
:post
|
||||
:select-mode="selectedMode"
|
||||
:user-click="true"
|
||||
@onSelected="handleSelected"
|
||||
@onUserClick="handleUserClick"
|
||||
/>
|
||||
|
||||
@@ -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,12 +97,12 @@
|
||||
</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">
|
||||
<div v-for="post in posts" :key="post.post.post_id">
|
||||
<TPostCard :model-value="post" :user-click="true" @onUserClick="handleUserClick" />
|
||||
<TPostCard :post @onUserClick="handleUserClick" />
|
||||
</div>
|
||||
</div>
|
||||
<VpOverlaySearch v-model="showSearch" :gid="curGid" :keyword="search" />
|
||||
@@ -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,40 +13,39 @@
|
||||
{{ 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>
|
||||
</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 v-for="item in postData[value]" :key="item.post.post_id">
|
||||
<TPostCard :model-value="item" />
|
||||
<div class="pn-grid">
|
||||
<div v-for="post in postData[value]" :key="post.post.post_id">
|
||||
<TPostCard :post />
|
||||
</div>
|
||||
</div>
|
||||
</v-window-item>
|
||||
@@ -199,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
|
||||
@@ -89,7 +89,7 @@
|
||||
</v-app-bar>
|
||||
<div class="post-topic-grid">
|
||||
<div v-for="post in posts" :key="post.post.post_id">
|
||||
<TPostCard :model-value="post" :user-click="true" @onUserClick="handleUserClick" />
|
||||
<TPostCard :post @onUserClick="handleUserClick" />
|
||||
</div>
|
||||
</div>
|
||||
<VpOverlaySearch v-model="showSearch" :gid="curGid" :keyword="search" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 角色生日模块
|
||||
* @since Beta v0.9.1
|
||||
* @since Beta v0.9.9
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
|
||||
/**
|
||||
* 判断今天是不是角色生日
|
||||
* @since Beta v0.4.6
|
||||
* @since Beta v0.9.9
|
||||
* @returns 角色生日
|
||||
*/
|
||||
function isAvatarBirth(): Array<TGApp.Archive.Birth.CalendarItem> {
|
||||
@@ -20,19 +20,28 @@ function isAvatarBirth(): Array<TGApp.Archive.Birth.CalendarItem> {
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const days = ArcBirCalendar[month];
|
||||
const find = days.filter((i) => i.role_birthday === `${month}/${day}`);
|
||||
if (find.length > 0) {
|
||||
for (const i of find) i.is_subscribe = true;
|
||||
return find;
|
||||
const resId = new Set<number>();
|
||||
const res: Array<TGApp.Archive.Birth.CalendarItem> = [];
|
||||
const rawFind = days.filter((i) => i.role_birthday === `${month}/${day}`);
|
||||
if (rawFind.length > 0) {
|
||||
res.push(...rawFind);
|
||||
rawFind.map((i) => resId.add(i.role_id));
|
||||
}
|
||||
const find2 = AppCharacterData.filter((i) => i.birthday.toString() === [month, day].toString());
|
||||
return find2.map((i) => ({
|
||||
role_id: i.id,
|
||||
name: i.name,
|
||||
role_birthday: `${month}/${day}`,
|
||||
head_icon: `/WIKI/character/${i.id}.webp`,
|
||||
is_subscribe: false,
|
||||
}));
|
||||
const wikiFind = AppCharacterData.filter(
|
||||
(i) => i.birthday.toString() === [month, day].toString(),
|
||||
);
|
||||
for (const i of wikiFind) {
|
||||
if (resId.has(i.id)) continue;
|
||||
res.push({
|
||||
role_id: i.id,
|
||||
name: i.name,
|
||||
role_birthday: `${month}/${day}`,
|
||||
head_icon: `/WIKI/character/${i.id}.webp`,
|
||||
is_subscribe: false,
|
||||
});
|
||||
resId.add(i.id);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
/**
|
||||
* 游戏文件相关功能
|
||||
* @since Beta v0.9.6
|
||||
* @since Beta v0.9.8
|
||||
*/
|
||||
|
||||
import showDialog from "@comp/func/dialog.js";
|
||||
@@ -40,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* vite 配置文件
|
||||
* @since Beta v0.9.6
|
||||
* @since Beta v0.9.9
|
||||
*/
|
||||
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin";
|
||||
|
||||
Reference in New Issue
Block a user