性能优化

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
+32 -34
View File
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { EVENTS, STORAGE_KEYS } from '@/lib/constants'
import { Effect, EffectState } from '@tauri-apps/api/window'
// Rust 端通过 tauri-specta 生成的命令绑定(bindings.ts
import { commands } from '@/lib/bindings'
import {
ClipboardList, Pin, PinOff, Trash2, Search, Image as ImageIcon,
FileText, Files, Loader2,
@@ -14,21 +16,9 @@ import {
Pagination, PaginationContent, PaginationItem, PaginationEllipsis,
} from '@/components/ui/pagination'
// ===== 与 Rust 端对应的数据结构(camelCase =====
type ClipboardKind = 'text' | 'image' | 'files'
interface ClipboardItem {
id: number
kind: ClipboardKind
preview: string
size: number
pinned: boolean
pinnedOrder: number | null
createdAt: number
}
interface HistoryPage {
items: ClipboardItem[]
total: number
}
// ===== 与 Rust 端对应的数据结构(bindings 提供,camelCase =====
// kind 为 bindings 生成的 string,前端按字符串比较即可
import type { ClipboardItem, HistoryPage } from '@/lib/bindings'
// ===== 状态 =====
const items = ref<ClipboardItem[]>([])
@@ -45,27 +35,35 @@ let searchTimer: ReturnType<typeof setTimeout> | null = null
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / PAGE_SIZE)))
// ===== 数据加载 =====
/** 加载请求序号:翻页/搜索快速操作时丢弃过期请求结果,避免旧请求覆盖新结果 */
let loadSeq = 0
async function loadData() {
const seq = ++loadSeq
loading.value = true
try {
const q = searchQuery.value.trim()
const offset = (currentPage.value - 1) * PAGE_SIZE
let res: HistoryPage
if (q) {
res = await invoke<HistoryPage>('clipboard_search', { query: q, limit: PAGE_SIZE, offset })
res = await commands.clipboardSearch(q, PAGE_SIZE, offset)
} else {
res = await invoke<HistoryPage>('clipboard_get_history', { limit: PAGE_SIZE, offset, kind: 'all' })
res = await commands.clipboardGetHistory(PAGE_SIZE, offset, 'all')
}
if (seq !== loadSeq) return // 过期请求丢弃
items.value = res.items
total.value = res.total
selectedIndex.value = 0
} catch (e) {
if (seq !== loadSeq) return
console.error('[clipboard-popup] 加载失败:', e)
} finally {
loading.value = false
// 仅最新请求可结束 loading,避免旧请求提前清除新请求的加载态
if (seq === loadSeq) loading.value = false
}
if (seq === loadSeq) {
await nextTick()
scrollSelectedIntoView()
}
await nextTick()
scrollSelectedIntoView()
}
async function gotoPage(p: number) {
@@ -84,9 +82,9 @@ watch(searchQuery, () => {
/// 选中条目 → 写回剪贴板 → 隐藏窗口 → 模拟 Ctrl+V 粘贴到原窗口
async function selectAndPaste(item: ClipboardItem) {
try {
await invoke('clipboard_copy_back', { id: item.id })
await commands.clipboardCopyBack(item.id)
// paste_to_target 会先隐藏窗口,再延迟模拟 Ctrl+V
await invoke('clipboard_paste_to_target')
await commands.clipboardPasteToTarget()
} catch (e) {
console.error('[clipboard-popup] 粘贴失败:', e)
// 失败时至少隐藏窗口
@@ -97,7 +95,7 @@ async function selectAndPaste(item: ClipboardItem) {
async function togglePin(item: ClipboardItem, ev: Event) {
ev.stopPropagation()
try {
await invoke('clipboard_set_pinned', { id: item.id, pinned: !item.pinned })
await commands.clipboardSetPinned(item.id, !item.pinned)
await loadData()
} catch (e) {
console.error('[clipboard-popup] 固定失败:', e)
@@ -107,7 +105,7 @@ async function togglePin(item: ClipboardItem, ev: Event) {
async function deleteItem(item: ClipboardItem, ev: Event) {
ev.stopPropagation()
try {
await invoke('clipboard_delete', { id: item.id })
await commands.clipboardDelete(item.id)
items.value = items.value.filter((i) => i.id !== item.id)
} catch (e) {
console.error('[clipboard-popup] 删除失败:', e)
@@ -116,7 +114,7 @@ async function deleteItem(item: ClipboardItem, ev: Event) {
async function hideWindow() {
try {
await invoke('clipboard_hide_popup')
await commands.clipboardHidePopup()
} catch {
/* 忽略 */
}
@@ -153,14 +151,14 @@ function scrollSelectedIntoView() {
})
}
// ===== 显示辅助 =====
const kindIcon = (k: ClipboardKind) => {
// ===== 显示辅助kind 为 bindings 生成的 string,按字符串比较) =====
const kindIcon = (k: string) => {
if (k === 'text') return FileText
if (k === 'image') return ImageIcon
return Files
}
const kindLabel = (k: ClipboardKind) => (k === 'text' ? '文本' : k === 'image' ? '图片' : '文件')
const kindBadgeClass = (k: ClipboardKind) =>
const kindLabel = (k: string) => (k === 'text' ? '文本' : k === 'image' ? '图片' : '文件')
const kindBadgeClass = (k: string) =>
k === 'text'
? 'badge-text'
: k === 'image'
@@ -205,7 +203,7 @@ async function onItemHover(idx: number, item: ClipboardItem) {
try {
let src = imageCache.get(item.id)
if (!src) {
const detail = await invoke<{ imageBase64: string | null } | null>('clipboard_get_item', { id: item.id })
const detail = await commands.clipboardGetItem(item.id)
if (detail?.imageBase64) {
src = buildImageDataUrl(detail.imageBase64)
imageCache.set(item.id, src)
@@ -237,7 +235,7 @@ function onItemLeave() {
/** 从 localStorage 读取主应用的主题设置 */
function readMainTheme(): { theme: string; effect: string } {
try {
const raw = localStorage.getItem('thing_app_settings')
const raw = localStorage.getItem(STORAGE_KEYS.appSettings)
if (raw) {
const s = JSON.parse(raw)
return {
@@ -330,7 +328,7 @@ onMounted(async () => {
unlistenFns.push(() => mq.removeEventListener('change', onThemeChange))
// 监听弹窗显示事件:每次显示时重新同步主题 + 刷新数据
unlistenFns.push(await listen('clipboard-popup-show', async () => {
unlistenFns.push(await listen(EVENTS.clipboardPopupShow, async () => {
// 主应用可能切换了主题,每次显示前重新应用
await applyTheme()
searchQuery.value = ''
@@ -351,7 +349,7 @@ onMounted(async () => {
// 主题和数据都就绪后,调用 Rust 端显示窗口
try {
await invoke('clipboard_show_window')
await commands.clipboardShowWindow()
} catch {
/* 忽略 */
}