修复: 审批浮窗 3 bug(打开面板权限/审批后空转自关/关闭顺序)
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
"core:event:allow-emit",
|
"core:event:allow-emit",
|
||||||
"core:window:allow-create",
|
"core:window:allow-create",
|
||||||
"core:window:allow-close",
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-show",
|
||||||
"core:window:allow-set-always-on-top",
|
"core:window:allow-set-always-on-top",
|
||||||
"core:window:allow-set-focus",
|
"core:window:allow-set-focus",
|
||||||
"core:window:allow-set-position",
|
"core:window:allow-set-position",
|
||||||
|
|||||||
+6
-2
@@ -122,12 +122,16 @@ let _stopApprovalPopup: (() => void) | null = null
|
|||||||
let _unlistenPopupReady: (() => void) | null = null
|
let _unlistenPopupReady: (() => void) | null = null
|
||||||
async function activateForPopup() {
|
async function activateForPopup() {
|
||||||
// 浮窗「打开审批面板」按钮 → 聚焦主窗口 + 打开 AI 面板/激活分离窗口
|
// 浮窗「打开审批面板」按钮 → 聚焦主窗口 + 打开 AI 面板/激活分离窗口
|
||||||
|
// 修复 BUG1:setFocus 先行(已授权,失焦场景有效),show 兜底(需 core:window:allow-show,
|
||||||
|
// capabilities/default.json 已补)。原顺序 show→setFocus,show reject 直接吞掉 setFocus,
|
||||||
|
// 主窗口最小化/隐藏时 show 失败 → 用户看到「点了没反应」。
|
||||||
try {
|
try {
|
||||||
const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
|
const { WebviewWindow } = await import('@tauri-apps/api/webviewWindow')
|
||||||
const mainWin = await WebviewWindow.getByLabel('main')
|
const mainWin = await WebviewWindow.getByLabel('main')
|
||||||
if (mainWin) {
|
if (mainWin) {
|
||||||
await mainWin.show()
|
// setFocus 先行(对未聚焦但可见的窗口即可恢复焦点);show 兜底取消最小化/隐藏
|
||||||
await mainWin.setFocus()
|
try { await mainWin.setFocus() } catch { /* setFocus 失败不阻塞 show */ }
|
||||||
|
try { await mainWin.show() } catch { /* show 未授权或不可用,忽略(setFocus 已尽力) */ }
|
||||||
}
|
}
|
||||||
} catch { /* 忽略 */ }
|
} catch { /* 忽略 */ }
|
||||||
if (!aiStore.state.detached && !aiStore.state.panelOpen) {
|
if (!aiStore.state.detached && !aiStore.state.panelOpen) {
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export default {
|
|||||||
popupTagPath: 'Path auth',
|
popupTagPath: 'Path auth',
|
||||||
popupTagRisk: 'Risk action',
|
popupTagRisk: 'Risk action',
|
||||||
popupOpenPanel: 'Open approval panel',
|
popupOpenPanel: 'Open approval panel',
|
||||||
// 根因1:首屏空态友好文案(数据未到达时,替代裸 spinner)
|
// 空态文案:正常空态会被 maybeAutoClose 即时关窗,此 hint 仅 close 失败时兜底
|
||||||
popupEmptyHint: 'Waiting for approval data…',
|
popupEmptyHint: 'Closing…',
|
||||||
|
|
||||||
// ── Provider status ──
|
// ── Provider status ──
|
||||||
clickToSwitchProvider: 'Click to switch provider',
|
clickToSwitchProvider: 'Click to switch provider',
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export default {
|
|||||||
popupTagPath: '路径授权',
|
popupTagPath: '路径授权',
|
||||||
popupTagRisk: '风险操作',
|
popupTagRisk: '风险操作',
|
||||||
popupOpenPanel: '打开审批面板',
|
popupOpenPanel: '打开审批面板',
|
||||||
// 根因1:首屏空态友好文案(数据未到达时,替代裸 spinner)
|
// 空态文案:正常空态会被 maybeAutoClose 即时关窗,此 hint 仅 close 失败时兜底
|
||||||
popupEmptyHint: '等待审批数据…',
|
popupEmptyHint: '正在关闭…',
|
||||||
|
|
||||||
// ── Provider 状态 ──
|
// ── Provider 状态 ──
|
||||||
clickToSwitchProvider: '点击切换 Provider',
|
clickToSwitchProvider: '点击切换 Provider',
|
||||||
|
|||||||
+28
-13
@@ -30,7 +30,8 @@
|
|||||||
<!-- 待审批列表 -->
|
<!-- 待审批列表 -->
|
||||||
<div class="popup-body">
|
<div class="popup-body">
|
||||||
<div v-if="approvals.length === 0" class="popup-empty">
|
<div v-if="approvals.length === 0" class="popup-empty">
|
||||||
<span class="popup-empty-spinner"></span>
|
<!-- BUG2 修复:空态不再永久转圈(原 popup-empty-spinner 致「处理完仍转圈」误判卡死)。
|
||||||
|
空态正常会被 maybeAutoClose 即时关窗;此 hint 仅作防御性兜底(close 失败时给可见反馈)。 -->
|
||||||
<span class="popup-empty-hint">{{ $t('aiChat.popupEmptyHint') }}</span>
|
<span class="popup-empty-hint">{{ $t('aiChat.popupEmptyHint') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -144,6 +145,8 @@ onMounted(async () => {
|
|||||||
ApprovalPopupEvents.EVT_UPDATE,
|
ApprovalPopupEvents.EVT_UPDATE,
|
||||||
(e) => {
|
(e) => {
|
||||||
approvals.value = e.payload?.approvals || []
|
approvals.value = e.payload?.approvals || []
|
||||||
|
// BUG2 修复:主窗口推空快照(所有 pending 已清)→ 立即关浮窗,避免停留在空态转圈
|
||||||
|
void maybeAutoClose()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -175,6 +178,8 @@ async function onApprove(tc: AiToolCallInfo) {
|
|||||||
}
|
}
|
||||||
// 乐观移除(主窗口 listener 收后端 emit 后会推新快照覆盖,此乐观仅缩短视觉延迟)
|
// 乐观移除(主窗口 listener 收后端 emit 后会推新快照覆盖,此乐观仅缩短视觉延迟)
|
||||||
approvals.value = approvals.value.filter(a => a.id !== tc.id)
|
approvals.value = approvals.value.filter(a => a.id !== tc.id)
|
||||||
|
// BUG2 修复:乐观移除后若列表空,立即关浮窗(避免空列表停留在 popup-empty 永久转圈态)
|
||||||
|
await maybeAutoClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[ApprovalPopup] 批准失败:', e)
|
console.error('[ApprovalPopup] 批准失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -193,6 +198,7 @@ async function onReject(tc: AiToolCallInfo) {
|
|||||||
await aiApi.approve(tc.id, false)
|
await aiApi.approve(tc.id, false)
|
||||||
}
|
}
|
||||||
approvals.value = approvals.value.filter(a => a.id !== tc.id)
|
approvals.value = approvals.value.filter(a => a.id !== tc.id)
|
||||||
|
await maybeAutoClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[ApprovalPopup] 拒绝失败:', e)
|
console.error('[ApprovalPopup] 拒绝失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -200,6 +206,20 @@ async function onReject(tc: AiToolCallInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 列表清空时自动关浮窗。
|
||||||
|
* 场景:用户处理完最后一条审批 / 主窗口推空快照(EVT_UPDATE approvals:[])。
|
||||||
|
* 修复 BUG2:原实现空列表渲染 popup-empty + 永久 popup-empty-spinner,用户看到「列表空 + 一直转」
|
||||||
|
* 误以为卡死。空 = 完成,应即时关浮窗(不等待主窗口 watch closePopup 的跨窗口往返)。 */
|
||||||
|
async function maybeAutoClose() {
|
||||||
|
if (approvals.value.length > 0) return
|
||||||
|
try {
|
||||||
|
const { emit } = await import('@tauri-apps/api/event')
|
||||||
|
// 先关窗(确保用户立刻看到反馈),再 emit 清主窗口引用(顺序与 onClose 一致)
|
||||||
|
await getCurrentWebviewWindow().close()
|
||||||
|
await emit(ApprovalPopupEvents.EVT_CLOSED, {})
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
/** 点击「打开审批面板」:emit 主窗口聚焦 + togglePanel */
|
/** 点击「打开审批面板」:emit 主窗口聚焦 + togglePanel */
|
||||||
async function onActivate() {
|
async function onActivate() {
|
||||||
try {
|
try {
|
||||||
@@ -208,12 +228,17 @@ async function onActivate() {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭浮窗(用户主动关 → emit closed 让主窗口清引用,允许下次有新审批再弹) */
|
/** 关闭浮窗(用户主动关 → emit closed 让主窗口清引用,允许下次有新审批再弹)
|
||||||
|
* 修复 BUG3:原顺序 emit→close,若 emit 跨窗口分发阻塞/异常(close 永不执行)→ 关闭按钮「无效」。
|
||||||
|
* 改为先 close(即时反馈,core:window:allow-close 已授权),再 emit 清主窗口引用;
|
||||||
|
* 即便 emit 失败,主窗口 win.once('tauri://destroyed') 也会清 _popupWin(双保险)。 */
|
||||||
async function onClose() {
|
async function onClose() {
|
||||||
|
try {
|
||||||
|
await getCurrentWebviewWindow().close()
|
||||||
|
} catch { /* ignore */ }
|
||||||
try {
|
try {
|
||||||
const { emit } = await import('@tauri-apps/api/event')
|
const { emit } = await import('@tauri-apps/api/event')
|
||||||
await emit(ApprovalPopupEvents.EVT_CLOSED, {})
|
await emit(ApprovalPopupEvents.EVT_CLOSED, {})
|
||||||
await getCurrentWebviewWindow().close()
|
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,16 +348,6 @@ function onDragEnd() { isDragging.value = false }
|
|||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
.popup-empty-spinner {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border: 2px solid var(--df-border, #444);
|
|
||||||
border-top-color: var(--df-accent);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: df-spin 0.8s linear infinite;
|
|
||||||
display: inline-block;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.popup-empty-hint {
|
.popup-empty-hint {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--df-text-dim);
|
color: var(--df-text-dim);
|
||||||
|
|||||||
Reference in New Issue
Block a user