From cc2020c64e13243b08b851f11f84147a17037dae Mon Sep 17 00:00:00 2001 From: BTMuli Date: Tue, 9 Dec 2025 16:33:35 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Yae=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E7=A7=BB=E8=87=B3App=E5=B1=82=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=91=BD=E4=BB=A4=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/yae/mod.rs | 9 +- src/App.vue | 199 ++++++++++++++++++++------- src/pages/common/PageAchi.vue | 45 ++---- src/pages/common/PageBagMaterial.vue | 55 ++------ src/pages/common/PageTest.vue | 1 + src/types/Plugins/Yae.d.ts | 29 +++- 6 files changed, 201 insertions(+), 137 deletions(-) diff --git a/src-tauri/src/yae/mod.rs b/src-tauri/src/yae/mod.rs index cc2dcfe7..f98a552d 100644 --- a/src-tauri/src/yae/mod.rs +++ b/src-tauri/src/yae/mod.rs @@ -67,7 +67,7 @@ fn read_exact_vec(r: &mut R, len: usize) -> io::Result> { /// 调用 dll #[tauri::command] -pub fn call_yae_dll(app_handle: AppHandle, game_path: String) -> Result<(), String> { +pub fn call_yae_dll(app_handle: AppHandle, game_path: String, uid: String) -> Result<(), String> { let dll_path = app_handle.path().resource_dir().unwrap().join("resources/YaeAchievementLib.dll"); dbg!(&dll_path); // 0. 创建 YaeAchievementPipe 的 命名管道,获取句柄 @@ -130,7 +130,9 @@ pub fn call_yae_dll(app_handle: AppHandle, game_path: String) -> Result<(), Stri Ok(list) => { println!("解码成功,成就列表长度: {}", list.len()); let json = serde_json::to_string_pretty(&list).unwrap(); - let _ = app_handle.emit("yae_achi_list", json); + let payload = + serde_json::json!({"type":"achievement","data":json,"uid":&uid}); + let _ = app_handle.emit("yae_read", payload); } Err(e) => println!("解析失败: {:?}", e), } @@ -152,7 +154,8 @@ pub fn call_yae_dll(app_handle: AppHandle, game_path: String) -> Result<(), Stri Ok(list) => { println!("解码成功,物品列表长度: {}", list.len()); let json = serde_json::to_string_pretty(&list).unwrap(); - let _ = app_handle.emit("yae_store_list", json); + let payload = serde_json::json!({"type":"store","data":json,"uid":&uid}); + let _ = app_handle.emit("yae_read", payload); } Err(e) => println!("解析失败: {:?}", e), } diff --git a/src/App.vue b/src/App.vue index c9f27e23..7f357eee 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,10 +14,13 @@ import TBackTop from "@comp/app/t-backTop.vue"; import TSidebar from "@comp/app/t-sidebar.vue"; import showDialog from "@comp/func/dialog.js"; +import showLoading from "@comp/func/loading.js"; import showSnackbar from "@comp/func/snackbar.js"; import OtherApi from "@req/otherReq.js"; import TGSqlite from "@Sql/index.js"; import TSUserAccount from "@Sqlm/userAccount.js"; +import TSUserAchi from "@Sqlm/userAchi.js"; +import TSUserBagMaterial from "@Sqlm/userBagMaterial.js"; import useAppStore from "@store/app.js"; import useUserStore from "@store/user.js"; import { app, core, event, webviewWindow } from "@tauri-apps/api"; @@ -35,40 +38,31 @@ import { useRouter } from "vue-router"; const router = useRouter(); const { theme, needResize, deviceInfo, isLogin, userDir, buildTime } = storeToRefs(useAppStore()); const { uid, briefInfo, account, cookie } = storeToRefs(useUserStore()); + const isMain = ref(false); const vuetifyTheme = computed(() => (theme.value === "dark" ? "dark" : "light")); let themeListener: UnlistenFn | null = null; -let urlListener: UnlistenFn | null = null; +let dpListener: UnlistenFn | null = null; let resizeListener: UnlistenFn | null = null; +let yaeListener: UnlistenFn | null = null; +let yaeFlag: Array = []; onMounted(async () => { const win = getCurrentWindow(); - const webview = webviewWindow.getCurrentWebviewWindow(); isMain.value = win.label === "TeyvatGuide"; if (isMain.value) { const title = "Teyvat Guide v" + (await app.getVersion()) + " Beta"; await win.setTitle(title); await listenOnInit(); await core.invoke("init_app"); - urlListener = await getDeepLink(); + dpListener = await event.listen("active_deep_link", handleDpListen); + yaeListener = await event.listen("yae_read", handleYaeListen); } if (needResize.value !== "false") await resizeWindow(); document.documentElement.className = theme.value; - themeListener = await event.listen("readTheme", (e: Event) => { - theme.value = e.payload; - document.documentElement.className = theme.value; - }); - resizeListener = await event.listen("needResize", async (e: Event) => { - if (e.payload !== "false") { - await resizeWindow(); - } else { - const size = getWindowSize(webview.label); - await win.setSize(new LogicalSize(size.width, size.height)); - await webview.setZoom(1); - } - await win.center(); - }); + themeListener = await event.listen("readTheme", handleThemeListen); + resizeListener = await event.listen("needResize", handleResizeListen); const isShow = await win.isVisible(); if (!isShow) { await win.center(); @@ -76,6 +70,146 @@ onMounted(async () => { } }); +onUnmounted(() => { + if (dpListener !== null) { + dpListener(); + dpListener = null; + } + if (yaeListener !== null) { + yaeListener(); + yaeListener = null; + } + if (themeListener !== null) { + themeListener(); + themeListener = null; + } + if (resizeListener !== null) { + resizeListener(); + resizeListener = null; + } +}); + +/** + * 自定义URL协议监听处理 + * @param {Event} event - 事件 + * @returns {Promise} + */ +async function handleDpListen(event: Event): Promise { + const windowGet = new webviewWindow.WebviewWindow("TeyvatGuide"); + if (await windowGet.isMinimized()) await windowGet.unminimize(); + await windowGet.setFocus(); + const payload = await parseDeepLink(event.payload); + if (payload === false) { + showSnackbar.error("无效的 deep link!", 3000); + await TGLogger.Error(`[App][getDeepLink] 无效的 deep link! ${JSON.stringify(event.payload)}`); + return; + } + await TGLogger.Info(`[App][getDeepLink] ${event.payload}`); + await handleDeepLink(payload); +} + +/** + * Yae监听处理 + * @param {Event} event + * @returns {Promise} + */ +async function handleYaeListen(event: Event): Promise { + if (event.payload.type === "achievement") { + await loadYaeAchi(event.payload.uid, JSON.parse(event.payload.data)); + if (!yaeFlag.includes("achievement")) yaeFlag.push("achievement"); + } else if (event.payload.type === "store") { + await loadYaeBag(event.payload.uid, JSON.parse(event.payload.data)); + if (!yaeFlag.includes("store")) yaeFlag.push("store"); + } + if (yaeFlag.length === 2) { + yaeFlag = []; + showSnackbar.success(`导入Yae数据完成,即将刷新页面`); + await showLoading.end(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + window.location.reload(); + } +} + +/** + * 导入成就 + * @param {string} uid - 存档UID + * @param {TGApp.Plugins.Yae.AchiListRes} data - 成就数据 + * @returns {Promise} + */ +async function loadYaeAchi(uid: string, data: TGApp.Plugins.Yae.AchiListRes): Promise { + await showLoading.start("正在导入成就数据", `UID:${uid},数量:${data.length}`); + await TGLogger.Info(`[App][loadYaeAchi] 开始处理 ${uid} 的 ${data.length} 条成就数据`); + try { + await TSUserAchi.mergeUiaf(data, Number(uid)); + showSnackbar.success(`成功导入 ${uid} 的 ${data.length}条成就数据`); + await TGLogger.Info(`[App][loadYaeAchi] 成功导入 ${uid} 的 ${data.length} 条成就数据`); + } catch (e) { + console.error(e); + await TGLogger.Error(`[App][loadYaeAchi] 成就导入失败:${e}`); + } +} + +/** + * 导入材料 + * @param {string} uid + * @param {TGApp.Plugins.Yae.BagListRes} data - 背包数据 + * @returns {Promise} + */ +async function loadYaeBag(uid: string, data: TGApp.Plugins.Yae.BagListRes): Promise { + const listM = data.filter((i) => i.kind === "material"); + const listW = data.filter((i) => i.kind === "weapon"); + const listR = data.filter((i) => i.kind === "reliquary"); + await TGLogger.Info(`[App][loadYaeBag] 接收到 ${uid} 的背包数据`); + await TGLogger.Info( + `[App][loadYaeBag] 材料:${listM.length},武器:${listW.length},圣遗物:${listR.length}`, + ); + await showLoading.start("正在导入材料数据", `UID:${uid},数量:${listM.length}`); + try { + const now = new Date(); + const skip = await TSUserBagMaterial.saveYaeData(Number(uid), listM); + const cost = new Date().getTime() - now.getTime(); + await TGLogger.Info(`[App][loadYaeBag] Skip: ${skip}`); + if (skip === 0) { + showSnackbar.success(`成功导入 ${listM.length} 条数据,耗时 ${Math.floor(cost / 1000)}s`); + } else if (skip === listM.length) { + showSnackbar.success(`未检测到数据更新,耗时 ${Math.floor(cost / 1000)}s`); + } else { + showSnackbar.success(`成功更新 ${listM.length - skip} 条数据`); + } + } catch (e) { + console.error(e); + await TGLogger.Error(`[App][loadYaeBag] 导入材料失败:${e}`); + } +} + +/** + * 主题监听处理 + * @param {Event} event - 事件 + * @returns {void} + */ +function handleThemeListen(event: Event): void { + theme.value = event.payload; + document.documentElement.className = theme.value; +} + +/** + * 窗口适配监听处理 + * @param {Event} event 事件 + * @returns {Promise} + */ +async function handleResizeListen(event: Event): Promise { + const win = getCurrentWindow(); + const webview = webviewWindow.getCurrentWebviewWindow(); + if (event.payload !== "false") { + await resizeWindow(); + } else { + const size = getWindowSize(webview.label); + await win.setSize(new LogicalSize(size.width, size.height)); + await webview.setZoom(1); + } + await win.center(); +} + // 启动后只执行一次的监听 async function listenOnInit(): Promise { console.info("[App][listenOnInit] 监听初始化事件!"); @@ -160,22 +294,6 @@ async function checkUserLoad(): Promise { await new Promise((resolve) => setTimeout(resolve, 1000)); } -async function getDeepLink(): Promise { - return await event.listen("active_deep_link", async (e: Event) => { - const windowGet = new webviewWindow.WebviewWindow("TeyvatGuide"); - if (await windowGet.isMinimized()) await windowGet.unminimize(); - await windowGet.setFocus(); - const payload = await parseDeepLink(e.payload); - if (payload === false) { - showSnackbar.error("无效的 deep link!", 3000); - await TGLogger.Error(`[App][getDeepLink] 无效的 deep link! ${JSON.stringify(e.payload)}`); - return; - } - await TGLogger.Info(`[App][getDeepLink] ${e.payload}`); - await handleDeepLink(payload); - }); -} - async function parseDeepLink(payload: string | string[]): Promise { try { if (typeof payload === "string") return payload; @@ -239,21 +357,6 @@ async function checkUpdate(): Promise { await openUrl("https://app.btmuli.ink/docs/TeyvatGuide/changelogs.html"); } } - -onUnmounted(() => { - if (themeListener !== null) { - themeListener(); - themeListener = null; - } - if (urlListener !== null) { - urlListener(); - urlListener = null; - } - if (resizeListener !== null) { - resizeListener(); - resizeListener = null; - } -});