性能优化
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { PhysicalPosition, PhysicalSize } from '@tauri-apps/api/dpi'
|
||||
import { availableMonitors } from '@tauri-apps/api/window'
|
||||
import { emit, listen, type UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { EVENTS, WINDOWS } from '@/lib/constants'
|
||||
// Rust 端通过 tauri-specta 生成的命令绑定(bindings.ts)
|
||||
import { commands } from '@/lib/bindings'
|
||||
|
||||
export interface RecentCapture {
|
||||
id: string
|
||||
pngBase64: string
|
||||
/** 缩略图 data URL(JPEG,~256px 宽,常驻内存的只有它,完整图已落盘缓存) */
|
||||
thumb: string
|
||||
/** 完整 PNG 缓存文件路径(按需通过 screenshot_load_cache 加载) */
|
||||
filePath: string
|
||||
width: number
|
||||
height: number
|
||||
time: number
|
||||
@@ -47,7 +52,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
let exportUnlisten: UnlistenFn | null = null
|
||||
let shortcutUnlisten: UnlistenFn | null = null
|
||||
/** 常驻截图覆盖层窗口(启动时创建,之后每次截图复用,避免重复 WebView 初始化) */
|
||||
const OVERLAY_LABEL = 'screenshot-overlay'
|
||||
const OVERLAY_LABEL = WINDOWS.screenshotOverlay
|
||||
let overlayWin: WebviewWindow | null = null
|
||||
let overlayReadyResolve: (() => void) | null = null
|
||||
let overlayReadyPromise: Promise<void> | null = null
|
||||
@@ -122,17 +127,17 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
}
|
||||
}
|
||||
// 捕获虚拟屏(覆盖层隐藏 → 不会出现在截图中),仅存原始像素,不做 PNG 编码
|
||||
await invoke('screenshot_capture_fullscreen')
|
||||
await commands.screenshotCaptureFullscreen()
|
||||
// 用物理像素把覆盖层对齐到虚拟屏(多显示器/混合 DPI 下保证底图 1:1 与坐标一致)
|
||||
const rect = await computeVirtualPhysicalRect()
|
||||
await overlayWin?.setPosition(new PhysicalPosition(rect.x, rect.y))
|
||||
await overlayWin?.setSize(new PhysicalSize(rect.width, rect.height))
|
||||
await waitOverlayReady()
|
||||
await emit('screenshot-begin')
|
||||
await emit(EVENTS.screenshotBegin)
|
||||
} catch (e) {
|
||||
console.error('[screenshot] 捕获失败', e)
|
||||
toast.error('截图启动失败:' + (e as Error).message)
|
||||
await invoke('screenshot_clear_fullscreen').catch(() => {})
|
||||
await commands.screenshotClearFullscreen().catch(() => {})
|
||||
} finally {
|
||||
capturing.value = false
|
||||
}
|
||||
@@ -188,7 +193,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
if (readyListenerInit) return
|
||||
readyListenerInit = true
|
||||
resetOverlayReady()
|
||||
await listen('screenshot-overlay-ready', () => {
|
||||
await listen(EVENTS.screenshotOverlayReady, () => {
|
||||
overlayReadyResolve?.()
|
||||
})
|
||||
await ensureOverlay()
|
||||
@@ -214,25 +219,71 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
}
|
||||
|
||||
// ===== 历史 / 导出 =====
|
||||
function addRecent(pngBase64: string, width: number, height: number, mode: string) {
|
||||
recent.value.unshift({
|
||||
id: crypto.randomUUID(),
|
||||
pngBase64,
|
||||
width,
|
||||
height,
|
||||
time: Date.now(),
|
||||
mode,
|
||||
/** 由完整 PNG base64 生成缩略图 data URL(canvas 缩放至 ~256px 宽,JPEG 压缩) */
|
||||
function makeThumb(pngBase64: string, width: number, height: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
try {
|
||||
const targetW = 256
|
||||
const scale = targetW / width
|
||||
const targetH = Math.max(1, Math.round(height * scale))
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = targetW
|
||||
canvas.height = targetH
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
reject(new Error('无法创建画布上下文'))
|
||||
return
|
||||
}
|
||||
ctx.drawImage(img, 0, 0, targetW, targetH)
|
||||
resolve(canvas.toDataURL('image/jpeg', 0.7))
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
}
|
||||
img.onerror = () => reject(new Error('缩略图解码失败'))
|
||||
img.src = `data:image/png;base64,${pngBase64}`
|
||||
})
|
||||
const limit = Math.max(1, settings.value.historyLimit)
|
||||
if (recent.value.length > limit) recent.value.length = limit
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
/** 追加历史项:删除超出上限的最旧缓存文件 */
|
||||
function addRecent(thumb: string, filePath: string, width: number, height: number, mode: string) {
|
||||
recent.value.unshift({ id: crypto.randomUUID(), thumb, filePath, width, height, time: Date.now(), mode })
|
||||
const limit = Math.max(1, settings.value.historyLimit)
|
||||
if (recent.value.length > limit) {
|
||||
const removed = recent.value.splice(limit)
|
||||
for (const item of removed) {
|
||||
void commands.screenshotDeleteCache(item.filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除单个历史项(同时删除缓存文件) */
|
||||
async function removeRecent(item: RecentCapture) {
|
||||
const idx = recent.value.findIndex((r) => r.id === item.id)
|
||||
if (idx >= 0) recent.value.splice(idx, 1)
|
||||
try {
|
||||
await commands.screenshotDeleteCache(item.filePath)
|
||||
} catch (e) {
|
||||
console.error('[screenshot] 删除缓存失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function clearHistory() {
|
||||
for (const item of recent.value) {
|
||||
void commands.screenshotDeleteCache(item.filePath)
|
||||
}
|
||||
recent.value = []
|
||||
}
|
||||
|
||||
/** 从历史缓存加载完整 PNG base64(一次性,不常驻) */
|
||||
async function loadFullImage(item: RecentCapture): Promise<string> {
|
||||
return await commands.screenshotLoadCache(item.filePath)
|
||||
}
|
||||
|
||||
async function copyImage(pngBase64: string) {
|
||||
await invoke('screenshot_copy_image', { pngBase64 })
|
||||
await commands.screenshotCopyImage(pngBase64)
|
||||
toast.success('已复制到剪贴板')
|
||||
}
|
||||
|
||||
@@ -247,14 +298,21 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
filters: [{ name: 'PNG', extensions: ['png'] }],
|
||||
})
|
||||
if (!path) return
|
||||
await invoke('screenshot_save_png', { pngBase64, path })
|
||||
await commands.screenshotSavePng(pngBase64, path)
|
||||
toast.success('已保存到文件')
|
||||
}
|
||||
|
||||
/** 覆盖层/编辑器导出处理:记录历史 + 按设置自动保存 */
|
||||
/** 覆盖层/编辑器导出处理:记录历史(缩略图 + 缓存落盘)+ 按设置自动保存 */
|
||||
async function handleExport(payload: { pngBase64: string; width: number; height: number }) {
|
||||
const { pngBase64, width, height } = payload
|
||||
addRecent(pngBase64, width, height, 'capture')
|
||||
try {
|
||||
// 完整图落盘缓存目录,历史只保留缩略图,避免完整 base64 常驻内存
|
||||
const filePath = await commands.screenshotSaveCache(pngBase64)
|
||||
const thumb = await makeThumb(pngBase64, width, height)
|
||||
addRecent(thumb, filePath, width, height, 'capture')
|
||||
} catch (e) {
|
||||
console.error('[screenshot] 历史缓存失败', e)
|
||||
}
|
||||
// 自动保存到指定目录
|
||||
if (settings.value.autoSave && settings.value.saveDir) {
|
||||
const ts = new Date()
|
||||
@@ -263,7 +321,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
.slice(0, 19)
|
||||
const path = `${settings.value.saveDir.replace(/\\$/, '')}\\screenshot_${ts}.png`
|
||||
try {
|
||||
await invoke('screenshot_save_png', { pngBase64, path })
|
||||
await commands.screenshotSavePng(pngBase64, path)
|
||||
} catch (e) {
|
||||
console.error('[screenshot] 自动保存失败', e)
|
||||
}
|
||||
@@ -286,7 +344,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
/** 监听 Rust 侧 emit 的 'screenshot-shortcut' 事件(快捷键按下时触发) */
|
||||
async function initShortcutListener() {
|
||||
if (shortcutUnlisten) return
|
||||
shortcutUnlisten = await listen('screenshot-shortcut', () => {
|
||||
shortcutUnlisten = await listen(EVENTS.screenshotShortcut, () => {
|
||||
void startCapture()
|
||||
})
|
||||
}
|
||||
@@ -294,7 +352,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
/** 应用启动时按已保存的快捷键注册全局热键(支持自定义,默认 Ctrl+Alt+A) */
|
||||
async function initShortcutRegistration() {
|
||||
try {
|
||||
await invoke('screenshot_register_shortcut', { shortcut: settings.value.shortcut })
|
||||
await commands.screenshotRegisterShortcut(settings.value.shortcut)
|
||||
} catch (e) {
|
||||
console.error('[screenshot] 快捷键注册失败', e)
|
||||
}
|
||||
@@ -305,7 +363,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
const next = shortcut.trim()
|
||||
setSettings({ shortcut: next })
|
||||
try {
|
||||
await invoke('screenshot_register_shortcut', { shortcut: next })
|
||||
await commands.screenshotRegisterShortcut(next)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('[screenshot] 快捷键注册失败', e)
|
||||
@@ -339,6 +397,8 @@ export const useScreenshotStore = defineStore('screenshot', () => {
|
||||
copyImage,
|
||||
saveImage,
|
||||
handleExport,
|
||||
removeRecent,
|
||||
loadFullImage,
|
||||
loadSettings,
|
||||
setSettings,
|
||||
setShortcut,
|
||||
|
||||
Reference in New Issue
Block a user