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

View File

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

View File

@ -95,6 +95,18 @@ export const dialogMethods = {
// ignore // 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) { async submitPlanApproval(payload) {
const approvalId = String(payload?.approval_id || '').trim(); const approvalId = String(payload?.approval_id || '').trim();
if (!approvalId) { if (!approvalId) {

View File

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

View File

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

View File

@ -4,6 +4,17 @@
<section class="plan-approval-card" role="dialog" aria-modal="true" :aria-label="$t('overlay.planApprovalAriaLabel')"> <section class="plan-approval-card" role="dialog" aria-modal="true" :aria-label="$t('overlay.planApprovalAriaLabel')">
<header class="plan-approval-windowbar"> <header class="plan-approval-windowbar">
<div class="plan-approval-window-title">{{ $t('overlay.planApprovalTitle') }}</div> <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>
<header class="plan-approval-header"> <header class="plan-approval-header">
@ -75,6 +86,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(event: 'submit', payload: { approval_id: string; approved: boolean; comment: string }): void; (event: 'submit', payload: { approval_id: string; approved: boolean; comment: string }): void;
(event: 'minimize'): void;
}>(); }>();
// plan // plan
@ -139,6 +151,7 @@ function reject() {
} }
.plan-approval-windowbar { .plan-approval-windowbar {
position: relative;
height: 40px; height: 40px;
flex: 0 0 40px; flex: 0 0 40px;
display: flex; display: flex;
@ -147,6 +160,29 @@ function reject() {
border-bottom: 1px solid var(--border-default); 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 { .plan-approval-window-title {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;

View File

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

View File

@ -88,6 +88,7 @@ export default {
// ── PlanApprovalDialog ── // ── PlanApprovalDialog ──
planApprovalAriaLabel: 'Plan awaiting approval', planApprovalAriaLabel: 'Plan awaiting approval',
planApprovalTitle: '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)', planTruncatedNote: '(Content too long; preview truncated. See the file for the full content)',
planCommentPlaceholder: planCommentPlaceholder:
'Feedback (optional): added as extra requirements when approving, or describe what to adjust when rejecting...', 'Feedback (optional): added as extra requirements when approving, or describe what to adjust when rejecting...',

View File

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

View File

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