新增: 审批系统级浮窗(WebviewWindow 自定义样式 + 内容/直接操作 + 点击回审批)

This commit is contained in:
lxy
2026-08-01 15:30:03 +08:00
parent 7d33442199
commit 1ddf48d40f
7 changed files with 703 additions and 6 deletions
+54 -5
View File
@@ -34,8 +34,9 @@
<div v-if="errorMsg" class="df-toast"> {{ errorMsg }}</div>
</transition>
<!-- 审批待办浮层(屏幕右上角,窗口置顶时浮于所有桌面窗口上面)
仅在主窗口与 ai-detached 窗口挂载(file-explorer 等其他分离窗口不挂) -->
<!-- 审批待办浮层(应用内右上角,窗口聚焦/可见时作快速提示)
仅在主窗口与 ai-detached 窗口挂载(file-explorer / approval-popup 自身不挂)
应用失焦/最小化时的系统级提示由 useApprovalPopup 启动的独立浮窗(approval-popup)负责 -->
<ApprovalOverlay v-if="showApprovalOverlay" @activate="activateAiPanel" />
</template>
@@ -51,15 +52,19 @@ import AiChat from './components/AiChat.vue'
import ApprovalOverlay from './components/ai/ApprovalOverlay.vue'
import ErrorBoundary from './components/ErrorBoundary.vue'
import AppLayout from './components/layout/AppLayout.vue'
import { startApprovalPopup } from '@/composables/ai/useApprovalPopup'
import { aiApi } from '@/api'
const route = useRoute()
const isDetached = computed(() => route.path === '/ai-detached' || route.path === '/file-explorer-detached')
const isDetached = computed(() => route.path === '/ai-detached' || route.path === '/file-explorer-detached' || route.path === '/approval-popup')
const aiStore = useAiStore()
const appSettings = useAppSettingsStore()
// 审批浮层仅在主窗口与 ai-detached 窗口挂载(不在 file-explorer 等其他分离窗口弹)
const showApprovalOverlay = computed(() => route.path !== '/file-explorer-detached')
// 审批浮层仅在主窗口与 ai-detached 窗口挂载(不在 file-explorer / approval-popup 等其他分离窗口弹)
// approval-popup 是系统级浮窗本身,内部已显式渲染 pending 列表,再挂 ApprovalOverlay 会重复
const showApprovalOverlay = computed(() =>
route.path !== '/file-explorer-detached' && route.path !== '/approval-popup'
)
/** 用户点击审批浮层 → 激活 AI 面板/分离窗口。
* 主窗口:打开 AI 面板;分离窗口:提升焦点(窗口本身 alwaysOnTop 已置顶)。 */
@@ -112,6 +117,24 @@ function handleKeydown(e: KeyboardEvent) {
}
}
// 系统级审批浮窗(独立窗口):仅主窗口启动(ai-detached/approval-popup 自身不启动,避免重复 watch)
let _stopApprovalPopup: (() => void) | null = null
let _unlistenPopupReady: (() => void) | null = null
async function activateForPopup() {
// 浮窗「打开审批面板」按钮 → 聚焦主窗口 + 打开 AI 面板/激活分离窗口
try {
const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
const mainWin = await WebviewWindow.getByLabel('main')
if (mainWin) {
await mainWin.show()
await mainWin.setFocus()
}
} catch { /* 忽略 */ }
if (!aiStore.state.detached && !aiStore.state.panelOpen) {
aiStore.togglePanel()
}
}
onMounted(async () => {
// 1. 先把 SQLite 全表 KV 填进 appSettings 缓存(后续 get/useSetting 才能读到真实值)
await appSettings.loadAll()
@@ -153,6 +176,28 @@ onMounted(async () => {
} catch (e) {
console.error('启动数据变更监听失败:', e)
}
// 系统级审批浮窗:仅主窗口启动(approval-popup 自身路由不启动,避免重复 watch)。
// 设计见 useApprovalPopup.ts:主窗口 watch pendingApprovals → 弹/更新/关浮窗(单一真相源)。
if (route.path !== '/approval-popup') {
_stopApprovalPopup = startApprovalPopup(
() => aiStore.state.pendingApprovals,
activateForPopup,
)
// 浮窗 webview 挂载后会 emit 'approval-popup-ready' 请求初始快照(防主窗口在浮窗 listen 前
// emit 丢失)。此处 listen 后立即重推当前 pendingApprovals。
try {
const { listen } = await import('@tauri-apps/api/event')
const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
_unlistenPopupReady = await listen('approval-popup-ready', async () => {
const popup = await WebviewWindow.getByLabel('approval-popup')
if (popup) {
await popup.emit('approval-popup-update', { approvals: aiStore.state.pendingApprovals })
}
})
} catch (e) {
console.error('[ApprovalPopup] ready 监听失败:', e)
}
}
})
/// 一次性迁移:遍历遗留的 localStorage key,若有值则导入 appSettings(SQLite),
@@ -193,6 +238,10 @@ const knowledgeStore = useKnowledgeStore()
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown)
projectStore.stopDataChangedListener?.()
_stopApprovalPopup?.()
_unlistenPopupReady?.()
_stopApprovalPopup = null
_unlistenPopupReady = null
})
</script>