新增: Tauri IPC 命令 + useStore 统一接口(B4)
- commands.rs: exec_op/undo/redo/can_undo/can_redo/get_deck/set_navigation/save_file Rust AppState 持有 deck + 历史栈,所有写操作通过 Op Log - bridge.ts: 安全 Tauri IPC 调用层(动态 import,Web 模式 mock) - useStore.ts: 统一 store 接口,检测 __TAURI__ 环境 Web 模式直接调 TS store;Tauri 模式走 invoke - 50 个测试全部通过,前后端编译零错误
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/* =====================================================================
|
||||
* useStore.ts — 统一 Store 接口(兼容 Tauri 和 Web 双模式)
|
||||
*
|
||||
* 设计:
|
||||
* - 检测 __TAURI__ 环境,决定走 invoke 还是直接调 TS store
|
||||
* - 暴露与现有 store 相同的方法签名(88 处组件调用零改动)
|
||||
* - 返回 reactive ref(与 Vue 响应式兼容)
|
||||
*
|
||||
* Web 模式:直接 return store(现有行为,零改动)
|
||||
* Tauri 模式:返回封装对象,内部 invoke Rust 命令
|
||||
* ===================================================================== */
|
||||
import type { Deck, Slide, SlideElement, LibItem, PageTemplate, ChatMessage, AiCfg } from '../core/types'
|
||||
import { store as tsStore } from '../core/store'
|
||||
import { invoke } from '../core/bridge'
|
||||
|
||||
/** 是否运行在 Tauri 桌面环境中 */
|
||||
export const isTauri = typeof window !== 'undefined' && '__TAURI__' in window
|
||||
|
||||
/** 根据环境创建 store 接口 */
|
||||
function createStore() {
|
||||
// Web 模式:直接返回现有 TS store
|
||||
if (!isTauri) return tsStore
|
||||
|
||||
// Tauri 模式:封装 invoke 调用
|
||||
// 注意:所有写操作通过 exec_op 统一走 Op Log
|
||||
const CLIENT_ID = 'client-' + Math.random().toString(36).slice(2, 10)
|
||||
|
||||
/** 生成一个 Op 并发送到 Rust 后端 */
|
||||
async function execOp(type: string, payload: Record<string, unknown>) {
|
||||
try {
|
||||
const result = await invoke<{ success: boolean; deck: Deck; error?: string }>('exec_op', {
|
||||
opJson: { type, clientId: CLIENT_ID, timestamp: Date.now(), ...payload }
|
||||
})
|
||||
if (result?.deck) {
|
||||
// 更新本地 ref 状态(由 TauriBridge watcher 处理)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('exec_op 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 返回与 TS store 类型兼容的对象
|
||||
// 关键属性通过 computed ref 暴露
|
||||
return {
|
||||
...tsStore,
|
||||
// 读操作暂时委托给 TS store(未来Rust端会emit状态同步)
|
||||
// 写操作改为 invoke
|
||||
setTheme: async (name: string) => {
|
||||
await execOp('set_theme', { theme: name })
|
||||
},
|
||||
addSlide: async (atIndex?: number) => {
|
||||
const slide: Slide = { id: 's-' + Date.now(), background: 'bg', elements: [] }
|
||||
const idx = atIndex != null ? atIndex + 1 : tsStore.getCount()
|
||||
await execOp('add_slide', { atIndex: idx, slide })
|
||||
tsStore.addSlide(atIndex)
|
||||
},
|
||||
updateElement: async (id: string, patch: Record<string, unknown>) => {
|
||||
await execOp('update_element', { slideIdx: tsStore.getCurrentIndex(), elementId: id, patch })
|
||||
},
|
||||
delElement: async (id: string) => {
|
||||
const el = tsStore.findElement(id)
|
||||
await execOp('del_element', { slideIdx: tsStore.getCurrentIndex(), elementId: id, deletedElement: el ? { ...el } : undefined })
|
||||
},
|
||||
undo: async () => {
|
||||
await invoke('undo')
|
||||
},
|
||||
redo: async () => {
|
||||
await invoke('redo')
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const store = createStore()
|
||||
@@ -0,0 +1,44 @@
|
||||
/* =====================================================================
|
||||
* bridge.ts — Tauri IPC 安全调用桥接
|
||||
*
|
||||
* 安全检测 __TAURI__ 环境,动态 import @tauri-apps/api/core。
|
||||
* Web 模式下返回 mock,组件无需关心环境。
|
||||
* ===================================================================== */
|
||||
|
||||
/** 动态获取 invoke 函数 */
|
||||
let _invoke: ((cmd: string, args?: Record<string, unknown>) => Promise<unknown>) | null = null
|
||||
|
||||
async function ensureInvoke() {
|
||||
if (_invoke) return true
|
||||
if (typeof window === 'undefined' || !('__TAURI__' in window)) return false
|
||||
try {
|
||||
const mod = await import('@tauri-apps/api/core')
|
||||
_invoke = mod.invoke
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全调用 Tauri IPC 命令。
|
||||
* Web 模式下返回 undefined(调用方需处理)。
|
||||
*/
|
||||
export async function invoke<T = unknown>(cmd: string, args?: Record<string, unknown>): Promise<T | undefined> {
|
||||
const ok = await ensureInvoke()
|
||||
if (!ok || !_invoke) return undefined
|
||||
return _invoke(cmd, args) as Promise<T>
|
||||
}
|
||||
|
||||
/** 注册 Tauri 事件监听(用于 Rust → 前端推送) */
|
||||
export async function listen<T>(event: string, handler: (payload: T) => void): Promise<() => void> {
|
||||
if (typeof window === 'undefined' || !('__TAURI__' in window)) {
|
||||
return () => {} // 空注销函数
|
||||
}
|
||||
try {
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
return listen(event, (e: { payload: T }) => handler(e.payload))
|
||||
} catch {
|
||||
return () => {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user