重构: 审批交互重设计

This commit is contained in:
lxy
2026-10-05 14:59:12 +08:00
parent 24d6803ecc
commit 2eea0118db
7 changed files with 254 additions and 30 deletions
+61 -2
View File
@@ -4,12 +4,19 @@
<div v-if="pendingCount > 0" class="ai-tool-topbar">
<!-- 批量操作栏(存在待审批项时显示) -->
<div class="ai-batch-approve">
<button class="ai-btn ai-btn--sm" @click="$emit('batch-approve', 'approve')">
<!-- 批量批准含 High 风险工具时前置二次确认(与单卡 onApprove 同门槛;
纯低风险批量保持一键直批) -->
<button class="ai-btn ai-btn--sm" :disabled="batchProcessing" @click="onBatchApprove">
<span v-if="batchProcessing" class="ai-btn-spinner" />
{{ $t('aiTool.approveAll', { n: pendingCount }) }}
</button>
<button class="ai-btn ai-btn--sm ai-btn--danger" @click="$emit('batch-approve', 'reject')">
<button class="ai-btn ai-btn--sm ai-btn--danger" :disabled="batchProcessing" @click="$emit('batch-approve', 'reject')">
{{ $t('aiTool.rejectAll') }}
</button>
<!-- High 风险计数徽标:批量批准将二次确认的项数,提示操作后果 -->
<span v-if="highRiskPendingCount > 0" class="ai-batch-high-risk-hint">
{{ $t('aiTool.batchHighRiskHint', { n: highRiskPendingCount }) }}
</span>
</div>
</div>
@@ -34,6 +41,14 @@
:class="{ 'ai-tool-card--group-hidden': isGroupHidden(tc, group, gi) }"
/>
</template>
<!-- 批量批准 High 风险二次确认(本组件自治 useConfirm,不与父级串台) -->
<ConfirmDialog
:visible="confirmState.visible"
:msg="confirmState.msg"
:danger-label="t('common.confirm')"
@result="answerConfirm"
/>
</div>
</template>
@@ -41,8 +56,11 @@
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import ToolCard from './ToolCard.vue'
import ConfirmDialog from './ConfirmDialog.vue'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import type { AiToolCallInfo } from '@/api/types'
import { useConfirm } from '@/composables/useConfirm'
import { toolRiskLevel } from '@/composables/ai/useToolApproval'
// useI18n needed for $t() in template (collapseAll / expandAll) + groupDisplayName(i18n key)
const { t } = useI18n()
@@ -123,6 +141,37 @@ const pendingCount = computed(() =>
props.toolCalls.filter(tc => tc.status === 'pending_approval').length
)
// -- 批量批准 High 风险拦截(治安全缺口:原批量批准绕过单卡二次确认直接放行) --
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
/** 待审批中的 High 风险项数(徽标提示 + 确认门槛判定) */
const highRiskPendingCount = computed(() =>
props.toolCalls.filter(tc => tc.status === 'pending_approval' && toolRiskLevel(tc.name) === 'high').length
)
/** 批量处理中(透传 isBatchProcessing 不便——本组件直接用 pendingCount 收敛到 0 判断,简单可靠) */
const batchProcessing = ref(false)
/**
* 批量批准入口:含 High 风险项 → 先二次确认(文案对齐单卡 highRiskConfirmText 语义,
* 列出将放行的高危工具数);确认后才向父级派发 batch-approve('approve')。
* 拒绝批量无破坏性,不拦。
*/
async function onBatchApprove() {
if (highRiskPendingCount.value > 0) {
const ok = await confirmDialog(
t('aiTool.batchHighRiskConfirm', { n: highRiskPendingCount.value }),
)
if (!ok) return
}
batchProcessing.value = true
emit('batch-approve', 'approve')
// emit 后父级异步批量处理;本组件无完成回执,以 pendingCount 归零兜底复位
const stop = watch(pendingCount, (n, old) => {
if (n === 0 || n < old) { batchProcessing.value = false; stop() }
}, { flush: 'post' })
// 兜底:pendingCount 因新增挂起不清零时 3s 后自动复位
setTimeout(() => { batchProcessing.value = false }, 3000)
}
/**
* 默认收起补齐:达到多卡阈值(>=2)且用户未手动操作过的组自动收起。
* 响应式而非一次性初始化——流式场景第一张卡到达即触发旧一次性逻辑,
@@ -398,6 +447,16 @@ function groupIcon(name: string): string {
border-radius: var(--df-radius-sm);
background: rgba(255,255,255,0.02);
}
/* 批量批准将二次确认的 High 风险项数徽标(橙红警示,不抢按钮焦点) */
.ai-batch-high-risk-hint {
font-size: 10px;
color: var(--df-danger);
background: var(--df-danger-bg);
border: 0.5px solid color-mix(in srgb, var(--df-danger) 40%, transparent);
border-radius: var(--df-radius-sm);
padding: 2px 8px;
white-space: nowrap;
}
/* .ai-btn 基类:批量审批按钮(历史重构遗漏定义,补全治本) */
.ai-btn {
display: inline-flex;