This commit is contained in:
zhongluofeng
2026-07-30 09:09:29 +08:00
parent 74902c4cec
commit f452322aad
32 changed files with 4586 additions and 115 deletions
+293
View File
@@ -0,0 +1,293 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { createLogger } from '@/lib/logger'
const logger = createLogger('clipboard')
// ===== 与 Rust 端对应的数据结构(camelCase =====
export type ClipboardKind = 'text' | 'image' | 'files'
export interface ClipboardItem {
id: number
kind: ClipboardKind
preview: string
size: number
pinned: boolean
pinnedOrder: number | null
createdAt: number
}
/** 历史分页结果(与 Rust 端 HistoryPage 对应) */
export interface HistoryPage {
items: ClipboardItem[]
total: number
}
export interface ClipboardItemDetail extends ClipboardItem {
content: string | null
imageBase64: string | null
}
export interface ClipboardSettings {
enabled: boolean
maxItems: number
maxImageKb: number
recordText: boolean
recordImage: boolean
recordFiles: boolean
dedup: boolean
shortcut: string
}
export interface ClipboardStatus {
running: boolean
count: number
}
const DEFAULT_SETTINGS: ClipboardSettings = {
enabled: true,
maxItems: 500,
maxImageKb: 5120,
recordText: true,
recordImage: true,
recordFiles: true,
dedup: true,
shortcut: 'Alt+V',
}
export const useClipboardStore = defineStore('clipboard', () => {
const history = ref<ClipboardItem[]>([])
const historyTotal = ref(0)
const pinned = ref<ClipboardItem[]>([])
const settings = ref<ClipboardSettings>({ ...DEFAULT_SETTINGS })
const status = ref<ClipboardStatus>({ running: false, count: 0 })
const loading = ref(false)
// 事件监听(应用级单例,只注册一次)
let changedUnlisten: UnlistenFn | null = null
let debounceTimer: ReturnType<typeof setTimeout> | null = null
/** 初始化:加载状态/设置,注册事件监听 */
const init = async () => {
try {
const [s, st] = await Promise.all([
invoke<ClipboardSettings>('clipboard_get_settings'),
invoke<ClipboardStatus>('clipboard_status'),
])
settings.value = { ...DEFAULT_SETTINGS, ...s }
status.value = st
} catch (e) {
logger.error('初始化失败: ' + e)
}
if (!changedUnlisten) {
changedUnlisten = await listen('clipboard-changed', () => {
// 防抖:短时间内多次复制只刷新一次
if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
refreshHistory()
refreshStatus()
}, 250)
})
}
}
const dispose = () => {
if (changedUnlisten) {
changedUnlisten()
changedUnlisten = null
}
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
}
// ===== 查询 =====
/** 拉取指定页的历史数据。pageSize 默认 50。 */
const fetchHistoryPage = async (opts: {
kind?: string
page?: number
pageSize?: number
} = {}) => {
const kind = opts.kind ?? 'all'
const pageSize = opts.pageSize ?? 50
const page = Math.max(1, opts.page ?? 1)
const offset = (page - 1) * pageSize
try {
const res = await invoke<HistoryPage>('clipboard_get_history', {
limit: pageSize,
offset,
kind,
})
history.value = res.items
historyTotal.value = res.total
} catch (e) {
logger.error('获取历史失败: ' + e)
}
return history.value
}
/** 兼容旧调用:拉取第一页 */
const refreshHistory = async (kind: string = 'all') => fetchHistoryPage({ kind, page: 1 })
const refreshPinned = async () => {
try {
pinned.value = await invoke<ClipboardItem[]>('clipboard_get_pinned')
} catch (e) {
logger.error('获取固定条目失败: ' + e)
}
return pinned.value
}
/** 搜索(分页)。pageSize 默认 50。 */
const searchPage = async (query: string, page: number = 1, pageSize: number = 50) => {
if (!query.trim()) {
return fetchHistoryPage({ page, pageSize })
}
try {
const res = await invoke<HistoryPage>('clipboard_search', {
query,
limit: pageSize,
offset: (page - 1) * pageSize,
})
history.value = res.items
historyTotal.value = res.total
} catch (e) {
logger.error('搜索失败: ' + e)
}
return history.value
}
/** 兼容旧调用:搜索第一页 */
const search = async (query: string) => searchPage(query, 1)
const getItem = async (id: number) => {
try {
return await invoke<ClipboardItemDetail | null>('clipboard_get_item', { id })
} catch (e) {
logger.error('获取详情失败: ' + e)
return null
}
}
const refreshStatus = async () => {
try {
status.value = await invoke<ClipboardStatus>('clipboard_status')
} catch (e) {
logger.error('获取状态失败: ' + e)
}
}
// ===== 操作 =====
const setPinned = async (id: number, pinned: boolean) => {
try {
await invoke('clipboard_set_pinned', { id, pinned })
// 固定/取消后刷新两个列表
await Promise.all([refreshHistory(), refreshPinned()])
} catch (e) {
logger.error('切换固定失败: ' + e)
}
}
const remove = async (id: number) => {
try {
await invoke('clipboard_delete', { id })
history.value = history.value.filter((i) => i.id !== id)
pinned.value = pinned.value.filter((i) => i.id !== id)
status.value.count = Math.max(0, status.value.count - 1)
} catch (e) {
logger.error('删除失败: ' + e)
}
}
const clear = async () => {
try {
await invoke('clipboard_clear')
history.value = []
await refreshStatus()
} catch (e) {
logger.error('清空失败: ' + e)
}
}
const copyBack = async (id: number) => {
await invoke('clipboard_copy_back', { id })
// copy_back 会触发 suppress,不会产生 clipboard-changed 事件
}
const saveSettings = async (s: ClipboardSettings) => {
try {
await invoke('clipboard_save_settings', { settings: s })
settings.value = { ...s }
await refreshStatus()
} catch (e) {
logger.error('保存设置失败: ' + e)
throw e
}
}
const start = async () => {
await invoke('clipboard_start')
await refreshStatus()
}
const stop = async () => {
await invoke('clipboard_stop')
await refreshStatus()
}
// ===== 快捷弹窗 =====
const showPopup = async () => {
await invoke('clipboard_show_popup')
}
const hidePopup = async () => {
await invoke('clipboard_hide_popup')
}
/// 隐藏弹窗并模拟 Ctrl+V 粘贴到原窗口
const pasteToTarget = async () => {
await invoke('clipboard_paste_to_target')
}
const registerShortcut = async (shortcut: string) => {
await invoke('clipboard_register_shortcut', { shortcut })
}
const unregisterShortcut = async () => {
await invoke('clipboard_unregister_shortcut')
}
return {
history,
historyTotal,
pinned,
settings,
status,
loading,
init,
dispose,
fetchHistoryPage,
refreshHistory,
refreshPinned,
searchPage,
search,
getItem,
refreshStatus,
setPinned,
remove,
clear,
copyBack,
saveSettings,
start,
stop,
showPopup,
hidePopup,
pasteToTarget,
registerShortcut,
unregisterShortcut,
}
})