Files
TeyvatGuide/src/utils/TGWindow.ts
Copilot 1497533f14 完善非回正模式下的窗口位置&大小处理 (#226)
* ️大幅提升UIGF导入速度 (#225)

* Initial plan

* perf: optimize gacha import with batch transactions and reduced UI delays

- Wrap DB inserts in transactions (batches of 500) for mergeUIGF/mergeUIGF4
- Pre-transform all data before batch insert loop
- Pass timeout: 0 to showLoading.update in progress callbacks
- Remove 1500ms snackbar delay in cleanGachaRecords
- Reduce per-item loading update delay in refreshGachaPool

Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

* fix: increment progress counter per item instead of per batch for accurate progress display

Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

* Initial plan

* feat: calculate window size based on resolution/scaling with baseline check and centering

- resizeWindow: add baseline check (1920x1080@150%), clamp to screen bounds
- setWindowPos: ensure window fits on screen and always center
- App.vue: use setWindowPos instead of manual positioning, center on deep link show
- tray.rs: center window when showing from system tray

Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

* fix: address code review - add zero guard and use setWindowPos consistently

Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

* refactor: address review feedback - move baseline to setWindowPos, revert tray.rs

- tray.rs: reverted, center() removed as redundant
- TGWindow.ts: baseline check moved to setWindowPos, resizeWindow restored as fallback
- App.vue: needResize=false → setWindowPos, else → center; deep link reverted;
  handleResizeListen true path unchanged, setWindowPos moved inside else

Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

* refactor: use needResize judgment condition (targetZoom/scaleFactor/textScale) in setWindowPos

Replace the simple curSize > screen.size overflow check with the same
condition used by resizeWindow(): targetZoom < 1, which considers
scaleFactor and textScale. Falls back to resizeWindow() when below
baseline or when targetZoom < 1.

Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>

* 🚸 调整尺寸判断

* 🚸 处理溢出

* 🚸 优化处理

* 🚸 移除冗余scale处理

* 🎨 CodeStyle

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Mikachu2333 <63829496+Mikachu2333@users.noreply.github.com>
Co-authored-by: BTMuli <bt-muli@outlook.com>
2026-02-28 17:58:19 +08:00

167 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 窗口创建相关工具函数
* @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 { PhysicalPosition, PhysicalSize } from "@tauri-apps/api/dpi";
import { currentMonitor, WindowOptions } from "@tauri-apps/api/window";
import { openUrl } from "@tauri-apps/plugin-opener";
import TGLogger from "./TGLogger.js";
/**
* 创建TG窗口
* @since Beta v0.5.0
* @param url - 窗口地址
* @param label - 窗口标签
* @param title - 窗口标题
* @param width - 窗口宽度
* @param height - 窗口高度
* @param resizable - 是否可调整大小
* @param visible - 是否可见
* @returns 无返回值
*/
export async function createTGWindow(
url: string,
label: string,
title: string,
width: number,
height: number,
resizable: boolean,
visible: boolean = true,
): Promise<void> {
const windowOpt: WindowOptions = {
title,
width,
height,
resizable,
visible,
};
const window = await TauriWindow.Window.getByLabel(label);
if (window !== null) await window.destroy();
await core.invoke("create_window", { label, url, option: windowOpt });
}
/**
* 打开帖子
* @since Beta v0.4.2
* @param item - 帖子内容或ID
* @param title - 帖子标题
* @returns 无返回值
*/
export async function createPost(
item: RenderCard | string | number,
title?: string,
): Promise<void> {
let postId: string, postTitle: string;
if (typeof item === "string" || typeof item === "number") {
postId = item.toString();
postTitle = title ? `Post_${postId} ${title}` : `Post_${postId}`;
} else {
postId = item.postId.toString();
postTitle = `Post_${postId} ${item.title}`;
}
const postPath = `/post_detail/${postId}`;
await createTGWindow(postPath, "Sub_window", postTitle, 960, 720, false, false);
await TGLogger.Info(`[createPost][${postId}] 打开帖子`);
}
/**
* 打开观测枢
* @since Beta 0.7.6
* @param contentId - 观测枢内容ID
* @returns 无返回值
*/
export async function toObcPage(contentId: number): Promise<void> {
const obcUrl = `https://bbs.mihoyo.com/ys/obc/content/${contentId}/detail?bbs_presentation_style=no_header`;
await openUrl(obcUrl);
}
/**
* 获取不同label下的默认窗口大小
* @since Beta v0.7.2
* @param label - 窗口标签
* @returns 物理大小
*/
export function getWindowSize(label: string): PhysicalSize {
switch (label) {
case "TeyvatGuide":
return new PhysicalSize(1600, 900);
case "Sub_window":
case "Dev_JSON":
return new PhysicalSize(960, 720);
default:
return new PhysicalSize(1280, 720);
}
}
/**
* 判断窗口位置,确保窗口不超出屏幕并居中
* @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
* @returns 无返回值
*/
export async function resizeWindow(): Promise<void> {
const screen = await currentMonitor();
if (screen === null) {
showSnackbar.error("获取屏幕信息失败!", 3000);
return;
}
const windowCur = webviewWindow.getCurrentWebviewWindow();
const textScale = await invoke<number>("read_text_scale");
if (await windowCur.isMaximized()) return;
const designSize = getWindowSize(windowCur.label);
const widthScale = screen.size.width / 1920;
const heightScale = screen.size.height / 1080;
const targetWidth = Math.round(designSize.width * widthScale);
const targetHeight = Math.round(designSize.height * heightScale);
if (await windowCur.isMaximized()) {
await windowCur.unmaximize();
}
await windowCur.setSize(new PhysicalSize(targetWidth, targetHeight));
const targetZoom = Math.min(widthScale, heightScale) / (screen.scaleFactor * textScale);
await windowCur.setZoom(targetZoom);
}