性能优化
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user