feat(ui): 计划批准弹窗支持最小化关闭,状态栏可恢复处理

- PlanApprovalDialog windowbar 新增关闭(X)按钮,点击后弹窗收起不打扰
- InputComposer 底部状态栏新增「有待批准的计划」按钮(对齐等待回答问题交互),点击恢复弹窗
- floatingStatusVisible 可见性判断纳入计划批准最小化状态,修复弹窗收起后浮栏不渲染的问题
- 批准解决后自动复位最小化状态,避免按钮残留
This commit is contained in:
JOJO 2026-08-30 00:33:34 +08:00
parent 89bcca744d
commit 4c3d8cedc7
10 changed files with 74 additions and 1 deletions

View File

@ -302,6 +302,8 @@
:project-git-summary="projectGitSummary"
:user-question-minimized="userQuestionMinimized"
:pending-user-question-count="pendingUserQuestions.length"
:plan-approval-minimized="planApprovalMinimized"
:pending-plan-approval-count="pendingPlanApprovals.length"
:goal-mode-armed="goalModeArmed"
:goal-running="goalRunning"
:goal-progress="goalProgress"
@ -319,6 +321,7 @@
:active-sub-agent-count="activeSubAgentCount"
:avatar-status="showStatusAvatar && messages.length > 0 ? avatarStatus : null"
@restore-user-question="restoreUserQuestionDialog"
@restore-plan-approval="restorePlanApprovalDialog"
@stop-all-sub-agents="handleStopAllSubAgents"
@update:input-message="inputSetMessage"
@input-change="handleInputChange"
@ -544,10 +547,11 @@
@dismiss="dismissUserQuestions"
/>
<PlanApprovalDialog
:visible="pendingPlanApprovals.length > 0"
:visible="pendingPlanApprovals.length > 0 && !planApprovalMinimized"
:approvals="pendingPlanApprovals"
:submitting-ids="answeringPlanApprovalIds"
@submit="submitPlanApproval"
@minimize="minimizePlanApprovalDialog"
/>
<transition name="overlay-fade">
<VersioningDialog

View File

@ -446,6 +446,10 @@ export const toolMethods = {
this.pendingPlanApprovals = this.pendingPlanApprovals.filter(
(item: any) => item && String(item.approval_id || '') !== id
);
// 弹窗被关闭(最小化)期间批准已解决:无待批事项时复位,避免状态栏按钮残留
if (!this.pendingPlanApprovals.length) {
this.planApprovalMinimized = false;
}
// 批准后后端已切换运行模式/恢复权限与执行环境,刷新显示(多标签页同步场景)
if (String(data?.decision || '') === 'approved') {
this.fetchWorkMode();

View File

@ -95,6 +95,18 @@ export const dialogMethods = {
// ignore
}
},
minimizePlanApprovalDialog() {
if (!Array.isArray(this.pendingPlanApprovals) || !this.pendingPlanApprovals.length) {
return;
}
this.planApprovalMinimized = true;
},
restorePlanApprovalDialog() {
if (!Array.isArray(this.pendingPlanApprovals) || !this.pendingPlanApprovals.length) {
return;
}
this.planApprovalMinimized = false;
},
async submitPlanApproval(payload) {
const approvalId = String(payload?.approval_id || '').trim();
if (!approvalId) {

View File

@ -292,6 +292,7 @@ export function dataState() {
pendingUserQuestions: [],
pendingPlanApprovals: [],
answeringPlanApprovalIds: [],
planApprovalMinimized: false,
userQuestionDialogVisible: false,
userQuestionMinimized: false,
userQuestionActiveIndex: 0,

View File

@ -179,6 +179,14 @@
>
{{ $t('input.waitingForQuestion') }}
</button>
<button
v-if="planApprovalMinimized && (pendingPlanApprovalCount || 0) > 0"
type="button"
class="floating-project-status__notice floating-project-status__notice--button"
@click.stop="$emit('restore-plan-approval')"
>
{{ $t('input.planApprovalPending') }}
</button>
<button
v-if="(activeSubAgentCount || 0) > 0"
type="button"
@ -698,6 +706,7 @@ const emit = defineEmits([
'delete-runtime-message',
'composer-height-change',
'restore-user-question',
'restore-plan-approval',
'toggle-goal-mode',
'open-goal-dialog',
'workflow-activated',
@ -776,6 +785,8 @@ const props = defineProps<{
} | null;
userQuestionMinimized?: boolean;
pendingUserQuestionCount?: number;
planApprovalMinimized?: boolean;
pendingPlanApprovalCount?: number;
goalModeArmed?: boolean;
goalRunning?: boolean;
goalProgress?: Record<string, any> | null;
@ -1066,6 +1077,7 @@ const floatingStatusVisible = computed(() => {
!!props.goalRunning ||
!!props.goalModeArmed ||
(Number(props.pendingUserQuestionCount || 0) > 0) ||
(!!props.planApprovalMinimized && Number(props.pendingPlanApprovalCount || 0) > 0) ||
(Number(props.activeSubAgentCount || 0) > 0);
return visible;
});

View File

@ -4,6 +4,17 @@
<section class="plan-approval-card" role="dialog" aria-modal="true" :aria-label="$t('overlay.planApprovalAriaLabel')">
<header class="plan-approval-windowbar">
<div class="plan-approval-window-title">{{ $t('overlay.planApprovalTitle') }}</div>
<button
type="button"
class="plan-approval-close"
:title="$t('overlay.planApprovalMinimize')"
:aria-label="$t('overlay.planApprovalMinimize')"
@click="emit('minimize')"
>
<svg viewBox="0 0 20 20" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 5l10 10M15 5L5 15" />
</svg>
</button>
</header>
<header class="plan-approval-header">
@ -75,6 +86,7 @@ const props = defineProps<{
const emit = defineEmits<{
(event: 'submit', payload: { approval_id: string; approved: boolean; comment: string }): void;
(event: 'minimize'): void;
}>();
// plan
@ -139,6 +151,7 @@ function reject() {
}
.plan-approval-windowbar {
position: relative;
height: 40px;
flex: 0 0 40px;
display: flex;
@ -147,6 +160,29 @@ function reject() {
border-bottom: 1px solid var(--border-default);
}
.plan-approval-close {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
border-radius: 7px;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
}
.plan-approval-close:hover {
background: var(--hover-bg);
color: var(--text-primary);
}
.plan-approval-window-title {
font-size: 13px;
font-weight: 600;

View File

@ -9,6 +9,7 @@ export default {
goalModeRunning: 'Goal mode running',
goalModeReady: 'Goal mode ready',
waitingForQuestion: 'Waiting for your answer',
planApprovalPending: 'Plan awaiting approval',
subAgentsRunning: '{n} sub-agents running',
terminalsRunning: '{n} terminals',
quickMenuAriaLabel: 'Quick menu',

View File

@ -88,6 +88,7 @@ export default {
// ── PlanApprovalDialog ──
planApprovalAriaLabel: 'Plan awaiting approval',
planApprovalTitle: 'Plan awaiting approval',
planApprovalMinimize: 'Close the dialog; restore it later from the status bar',
planTruncatedNote: '(Content too long; preview truncated. See the file for the full content)',
planCommentPlaceholder:
'Feedback (optional): added as extra requirements when approving, or describe what to adjust when rejecting...',

View File

@ -9,6 +9,7 @@ export default {
goalModeRunning: '目标模式运行中',
goalModeReady: '目标模式就绪',
waitingForQuestion: '等待回答问题',
planApprovalPending: '有待批准的计划',
subAgentsRunning: '{n} 个子智能体运行中',
terminalsRunning: '{n} 个终端',
quickMenuAriaLabel: '快捷菜单',

View File

@ -87,6 +87,7 @@ export default {
// ── PlanApprovalDialog计划待批准 ──
planApprovalAriaLabel: '计划待批准',
planApprovalTitle: '计划待批准',
planApprovalMinimize: '关闭弹窗,稍后从状态栏恢复',
planTruncatedNote: '(内容过长,已截断预览,完整内容见文件)',
planCommentPlaceholder: '意见(可选):批准时作为补充要求,拒绝时说明需要调整的方向…',
planRejectTitle: '拒绝这份计划AI 将根据你的意见修订后重新提交',