性能优化

This commit is contained in:
zhongluofeng
2026-08-06 10:33:16 +08:00
parent c7578a2e6b
commit e66c53e66d
105 changed files with 7273 additions and 5002 deletions
+21 -65
View File
@@ -1,67 +1,23 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import type { Component } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { emit, listen, type UnlistenFn } from '@tauri-apps/api/event'
import { EVENTS, STORAGE_KEYS, WINDOWS } from '@/lib/constants'
// Rust 端通过 tauri-specta 生成的命令绑定(bindings.ts
import { commands } from '@/lib/bindings'
import {
Square, Circle, MoveUpRight, Pencil, Type, Grid3x3, Highlighter, ListOrdered,
Undo2, Redo2, Eraser, Copy, Save,
} from '@lucide/vue'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Slider } from '@/components/ui/slider'
// ===== 类型 =====
type Phase = 'pick' | 'drawing' | 'selected' | 'editing'
type ToolType = 'rect' | 'ellipse' | 'arrow' | 'pen' | 'text' | 'mosaic' | 'highlight' | 'number'
interface Point { x: number; y: number }
interface Sel { x: number; y: number; w: number; h: number }
interface RectAnno { type: 'rect'; x1: number; y1: number; x2: number; y2: number; color: string; lineWidth: number }
interface EllipseAnno { type: 'ellipse'; x1: number; y1: number; x2: number; y2: number; color: string; lineWidth: number }
interface ArrowAnno { type: 'arrow'; x1: number; y1: number; x2: number; y2: number; color: string; lineWidth: number }
interface PenAnno { type: 'pen'; points: Point[]; color: string; lineWidth: number }
interface TextAnno { type: 'text'; x: number; y: number; text: string; color: string; fontSize: number }
interface MosaicAnno { type: 'mosaic'; x1: number; y1: number; x2: number; y2: number; blockSize: number }
interface HighlightAnno { type: 'highlight'; x1: number; y1: number; x2: number; y2: number; color: string; alpha: number }
interface NumberAnno { type: 'number'; x: number; y: number; n: number; color: string; fontSize: number }
type Annotation = RectAnno | EllipseAnno | ArrowAnno | PenAnno | TextAnno | MosaicAnno | HighlightAnno | NumberAnno
type DrawableAnnotation = RectAnno | EllipseAnno | ArrowAnno | PenAnno | MosaicAnno | HighlightAnno
interface CaptureData {
pngBase64: string
width: number
height: number
}
interface WindowInfo {
hwnd: number
title: string
rect: { x: number; y: number; width: number; height: number }
/** DWM 视觉边界(去掉最大化窗口隐形缩放边框),优先用于高亮框 */
visualRect: { x: number; y: number; width: number; height: number } | null
}
// ===== 工具与选项 =====
const TOOLS: { value: ToolType; icon: Component; label: string }[] = [
{ value: 'rect', icon: Square, label: '矩形' },
{ value: 'ellipse', icon: Circle, label: '椭圆' },
{ value: 'arrow', icon: MoveUpRight, label: '箭头' },
{ value: 'number', icon: ListOrdered, label: '序号' },
{ value: 'pen', icon: Pencil, label: '画笔' },
{ value: 'text', icon: Type, label: '文字' },
{ value: 'mosaic', icon: Grid3x3, label: '马赛克' },
{ value: 'highlight', icon: Highlighter, label: '高亮' },
]
const COLORS = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#a855f7', '#000000', '#ffffff']
const BLOCK_SIZES = [8, 10, 14]
const ALPHAS = [0.2, 0.4, 0.6]
const HANDLES = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'] as const
type HandleDir = (typeof HANDLES)[number]
const HANDLE_HIT = 10
const DRAG_THRESHOLD = 4
import {
TOOLS, COLORS, BLOCK_SIZES, ALPHAS, HANDLES, HANDLE_HIT, DRAG_THRESHOLD,
type Phase, type ToolType, type Annotation, type DrawableAnnotation,
type Point, type Sel, type CaptureData, type WindowInfo, type HandleDir,
type RectAnno, type EllipseAnno, type ArrowAnno, type PenAnno, type TextAnno,
type MosaicAnno, type HighlightAnno, type NumberAnno,
} from './types'
// ===== 窗口 / 底图 =====
const win = getCurrentWindow()
@@ -1786,7 +1742,7 @@ async function finish() {
if (!out) {
out = await cropFromStored()
if (out) {
await invoke('screenshot_copy_image', { pngBase64: out.b64 }).catch((e) =>
await commands.screenshotCopyImage(out.b64).catch((e) =>
console.error('[screenshot] 复制失败', e)
)
}
@@ -1814,7 +1770,7 @@ async function finish() {
console.error('[screenshot] raw 复制失败,回退 base64 路径', e)
out = await exportBase64()
if (out) {
await invoke('screenshot_copy_image', { pngBase64: out.b64 }).catch((e2) =>
await commands.screenshotCopyImage(out.b64).catch((e2) =>
console.error('[screenshot] 复制失败', e2)
)
}
@@ -1823,7 +1779,7 @@ async function finish() {
}
}
if (!out) return
await emit('screenshot-exported', { pngBase64: out.b64, width: out.w, height: out.h })
await emit(EVENTS.screenshotExported, { pngBase64: out.b64, width: out.w, height: out.h })
} catch (e) {
console.error('[screenshot] 完成失败', e)
} finally {
@@ -1844,8 +1800,8 @@ async function doSave() {
filters: [{ name: 'PNG', extensions: ['png'] }],
})
if (!path) return
await invoke('screenshot_save_png', { pngBase64: out.b64, path })
await emit('screenshot-exported', { pngBase64: out.b64, width: out.w, height: out.h })
await commands.screenshotSavePng(out.b64, path)
await emit(EVENTS.screenshotExported, { pngBase64: out.b64, width: out.w, height: out.h })
await win.hide().catch(() => {})
} catch (e) {
console.error('[screenshot] 保存失败', e)
@@ -1878,7 +1834,7 @@ function applyTheme() {
const root = document.documentElement
let theme = 'system'
try {
const raw = localStorage.getItem('thing_app_settings')
const raw = localStorage.getItem(STORAGE_KEYS.appSettings)
if (raw) {
const s = JSON.parse(raw)
theme = s.theme ?? 'system'
@@ -1895,7 +1851,7 @@ function applyTheme() {
/** 主应用 localStorage 变化(主题切换)时同步主题 */
function onStorageChange(e: StorageEvent) {
if (e.key === 'thing_app_settings') {
if (e.key === STORAGE_KEYS.appSettings) {
applyTheme()
}
}
@@ -1926,12 +1882,12 @@ onMounted(async () => {
// 首次同步窗口尺寸
await refreshWinSize()
// 禁用窗口显示/隐藏过渡动画(消除进入/关闭时的缩放动画),失败静默
invoke('screenshot_disable_transitions', { label: 'screenshot-overlay' }).catch(() => {})
commands.screenshotDisableTransitions(WINDOWS.screenshotOverlay).catch(() => {})
// 先注册 begin 监听再通知 store 就绪,避免首轮事件丢失
beginUnlisten = await listen('screenshot-begin', () => {
beginUnlisten = await listen(EVENTS.screenshotBegin, () => {
void beginCapture()
})
await emit('screenshot-overlay-ready')
await emit(EVENTS.screenshotOverlayReady)
})
/** 响应 store 的 'screenshot-begin':先装载底图(隐藏中),解码完成后再一次性显示窗口 */
@@ -2017,7 +1973,7 @@ onUnmounted(() => {
magGridCanvas = null
if (objectUrl) URL.revokeObjectURL(objectUrl)
// 覆盖层窗口真正销毁(应用退出)时释放 Rust 静态中的全屏原始像素
void invoke('screenshot_clear_fullscreen').catch(() => {})
void commands.screenshotClearFullscreen().catch(() => {})
})
</script>