优化: i18n硬编码清扫(8文件20+处中文字符串替为i18n)

- ToolCard: 已等待 → aiTool.waitTime

- ErrorBoundary: 堆栈详情 → error.stackDetails

- TopBar: 10处硬编码(tooltip+section标签+单位) → aiChat.* keys

- FileExplorer: 4处工程操作title → fileExplorer.*

- Tasks: 快捷菜单4处 → tasks.*

- EnrichmentPanel: 取消关联 → aiChat.enrichmentDismiss

- PlanProgress: 复用已有 aiChat.conflictsPending

- WorkflowDagDisplay: 删除中文fallback(已有i18n key)

- vue-tsc + vite build 通过
This commit is contained in:
2026-07-02 12:19:57 +08:00
parent 34944df764
commit 3f22fd3673
18 changed files with 78 additions and 24 deletions

View File

@@ -9,7 +9,7 @@
<div class="error-boundary-title">{{ $t('error.title') }}</div> <div class="error-boundary-title">{{ $t('error.title') }}</div>
<div class="error-boundary-desc">{{ errorMessage }}</div> <div class="error-boundary-desc">{{ errorMessage }}</div>
<details class="error-boundary-details"> <details class="error-boundary-details">
<summary>堆栈详情</summary> <summary>{{ $t('error.stackDetails') }}</summary>
<pre class="error-boundary-stack">{{ errorStack }}</pre> <pre class="error-boundary-stack">{{ errorStack }}</pre>
</details> </details>
<button class="error-boundary-retry" @click="reset">{{ $t('error.retry') }}</button> <button class="error-boundary-retry" @click="reset">{{ $t('error.retry') }}</button>

View File

@@ -42,7 +42,7 @@
<div v-if="tc.reason" class="ai-tool-approval-reason"> {{ tc.reason }}</div> <div v-if="tc.reason" class="ai-tool-approval-reason"> {{ tc.reason }}</div>
<!-- BUG-260624-02: 审批挂起时长提示(>2分钟橙色,>5分钟红色) --> <!-- BUG-260624-02: 审批挂起时长提示(>2分钟橙色,>5分钟红色) -->
<div v-if="waitSecs > 0" class="ai-tool-wait-time" :class="'is-' + waitLevel"> <div v-if="waitSecs > 0" class="ai-tool-wait-time" :class="'is-' + waitLevel">
已等待 {{ formatWaitTime(waitSecs) }} {{ $t('aiTool.waitTime', { time: formatWaitTime(waitSecs) }) }}
</div> </div>
<!-- AE-2025-03: write_file 审批 diff 预览(红删绿增,行级)旧文件不存在tc.diff 为空,回退显上方 args content --> <!-- AE-2025-03: write_file 审批 diff 预览(红删绿增,行级)旧文件不存在tc.diff 为空,回退显上方 args content -->
<div v-if="tc.diff" class="ai-tool-approval-diff"> <div v-if="tc.diff" class="ai-tool-approval-diff">

View File

@@ -51,7 +51,7 @@ function toggleExpanded(current: boolean) {
</svg> </svg>
<span class="ai-enrichment-badge">{{ enrichmentBadgeText }}</span> <span class="ai-enrichment-badge">{{ enrichmentBadgeText }}</span>
</button> </button>
<button class="ai-enrichment-dismiss" @click="emit('dismiss')" title="取消关联">×</button> <button class="ai-enrichment-dismiss" @click="emit('dismiss')" :title="$t('aiChat.enrichmentDismiss')">×</button>
<div v-if="enrichmentExpanded" class="ai-enrichment-panel"> <div v-if="enrichmentExpanded" class="ai-enrichment-panel">
<div class="ai-enrichment-label">{{ $t('aiChat.enrichmentLabel') }}</div> <div class="ai-enrichment-label">{{ $t('aiChat.enrichmentLabel') }}</div>
<pre class="ai-enrichment-body"><code>{{ enrichmentDetailText }}</code></pre> <pre class="ai-enrichment-body"><code>{{ enrichmentDetailText }}</code></pre>

View File

@@ -26,7 +26,7 @@
</div> </div>
<!-- 冲突徽章 --> <!-- 冲突徽章 -->
<div v-if="conflicts.length > 0" class="plan-conflicts-badge" @click="emit('show-conflicts')"> <div v-if="conflicts.length > 0" class="plan-conflicts-badge" @click="emit('show-conflicts')">
{{ conflicts.length }} 处冲突待处理 {{ t('aiChat.conflictsPending', { n: conflicts.length }) }}
</div> </div>
</div> </div>
</template> </template>

View File

@@ -113,7 +113,7 @@
<div class="ai-bottom-tools-right"> <div class="ai-bottom-tools-right">
<!-- 目标计数 --> <!-- 目标计数 -->
<div v-if="goals.length" class="ai-goals-inline"> <div v-if="goals.length" class="ai-goals-inline">
<span class="ai-goals-inline-badge ai-tool-badge" @click="goalsExpanded = !goalsExpanded" title="查看目标"> <span class="ai-goals-inline-badge ai-tool-badge" @click="goalsExpanded = !goalsExpanded" :title="$t('aiChat.viewGoals')">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg> <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
{{ goals.length }} {{ goals.length }}
</span> </span>
@@ -126,7 +126,7 @@
</div> </div>
<!-- 历史消息 --> <!-- 历史消息 -->
<div v-if="historyMsgs.length" class="ai-history-inline"> <div v-if="historyMsgs.length" class="ai-history-inline">
<span class="ai-history-inline-badge ai-tool-badge" @click="historyExpanded = !historyExpanded" title="查看历史消息"> <span class="ai-history-inline-badge ai-tool-badge" @click="historyExpanded = !historyExpanded" :title="$t('aiChat.viewHistory')">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{{ historyMsgs.length }} {{ historyMsgs.length }}
</span> </span>
@@ -140,44 +140,44 @@
</div> </div>
<!-- 上下文摘要 --> <!-- 上下文摘要 -->
<div v-if="summaryMsgs.length" class="ai-summary-inline"> <div v-if="summaryMsgs.length" class="ai-summary-inline">
<span class="ai-summary-inline-badge ai-tool-badge" @click="summaryExpanded = !summaryExpanded" title="查看上下文摘要"> <span class="ai-summary-inline-badge ai-tool-badge" @click="summaryExpanded = !summaryExpanded" :title="$t('aiChat.viewSummary')">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h12"/></svg> <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h16M4 18h12"/></svg>
{{ summaryMsgs.length }} {{ summaryMsgs.length }}
</span> </span>
<div v-if="summaryExpanded" class="ai-summary-inline-list"> <div v-if="summaryExpanded" class="ai-summary-inline-list">
<div v-for="(s, i) in summaryMsgs" :key="i" class="ai-summary-inline-item" @click="scrollToMessage(s.id)"> <div v-for="(s, i) in summaryMsgs" :key="i" class="ai-summary-inline-item" @click="scrollToMessage(s.id)">
<span class="ai-summary-label">{{ $t('aiChat.compressedSummaryLabel') || '上下文摘要' }} #{{ summaryMsgs.length - i }}</span> <span class="ai-summary-label">{{ $t('aiChat.compressedSummaryLabel') }} #{{ summaryMsgs.length - i }}</span>
<p class="ai-summary-preview">{{ s.text }}</p> <p class="ai-summary-preview">{{ s.text }}</p>
</div> </div>
</div> </div>
</div> </div>
<!-- L3 完整上下文 --> <!-- L3 完整上下文 -->
<div v-if="contextInfo.goals.length || contextInfo.summaryCount > 0" class="ai-context-inline"> <div v-if="contextInfo.goals.length || contextInfo.summaryCount > 0" class="ai-context-inline">
<span class="ai-context-inline-badge ai-tool-badge" @click="contextExpanded = !contextExpanded" title="查看 AI 上下文"> <span class="ai-context-inline-badge ai-tool-badge" @click="contextExpanded = !contextExpanded" :title="$t('aiChat.viewContext')">
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><circle cx="12" cy="8" r="1" fill="currentColor"/></svg> <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><circle cx="12" cy="8" r="1" fill="currentColor"/></svg>
</span> </span>
<div v-if="contextExpanded" class="ai-context-inline-list"> <div v-if="contextExpanded" class="ai-context-inline-list">
<!-- 系统提示 --> <!-- 系统提示 -->
<div class="ai-context-section"> <div class="ai-context-section">
<div class="ai-context-section-title">系统提示</div> <div class="ai-context-section-title">{{ $t('aiChat.contextSystemPrompt') }}</div>
<div class="ai-context-section-body"> <div class="ai-context-section-body">
<div class="ai-context-row"> <div class="ai-context-row">
<span class="ai-context-label">目标({{ contextInfo.goals.length }})</span> <span class="ai-context-label">目标({{ contextInfo.goals.length }})</span>
<span class="ai-context-value">{{ contextInfo.goals.join('; ') || '无' }}</span> <span class="ai-context-value">{{ contextInfo.goals.join('; ') || '无' }}</span>
</div> </div>
<div class="ai-context-row"> <div class="ai-context-row">
<span class="ai-context-label">环境</span> <span class="ai-context-label">{{ $t('aiChat.contextEnvironment') }}</span>
<span class="ai-context-value">{{ contextInfo.osInfo }}</span> <span class="ai-context-value">{{ contextInfo.osInfo }}</span>
</div> </div>
<div class="ai-context-row"> <div class="ai-context-row">
<span class="ai-context-label">压缩摘要</span> <span class="ai-context-label">{{ $t('aiChat.contextCompressedSummary') }}</span>
<span class="ai-context-value">{{ contextInfo.summaryCount }} </span> <span class="ai-context-value">{{ contextInfo.summaryCount }} {{ $t('aiChat.contextCountUnit') }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 增强上下文 --> <!-- 增强上下文 -->
<div class="ai-context-section"> <div class="ai-context-section">
<div class="ai-context-section-title">增强上下文</div> <div class="ai-context-section-title">{{ $t('aiChat.contextEnrichment') }}</div>
<div class="ai-context-section-body"> <div class="ai-context-section-body">
<div class="ai-context-row"> <div class="ai-context-row">
<span class="ai-context-label">@项目</span> <span class="ai-context-label">@项目</span>

View File

@@ -45,20 +45,20 @@
</div> </div>
<div class="toolbar-right"> <div class="toolbar-right">
<button class="btn btn-ghost btn-sm" type="button" title="编辑当前工程" @click="openEditModule"> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.editCurrentModule')" @click="openEditModule">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
</button> </button>
<button class="btn btn-ghost btn-sm" type="button" title="删除当前工程" @click="confirmRemoveModule"> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.deleteCurrentModule')" @click="confirmRemoveModule">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
</button> </button>
<button class="btn btn-ghost btn-sm" type="button" title="添加工程" @click="openAddModule"> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.addModuleTitle')" @click="openAddModule">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button> </button>
<button class="btn btn-ghost btn-sm" type="button" :disabled="refreshing" @click="refresh"> <button class="btn btn-ghost btn-sm" type="button" :disabled="refreshing" @click="refresh">
<span v-if="refreshing" class="spinner"></span> <span v-if="refreshing" class="spinner"></span>
{{ refreshing ? $t('fileExplorer.refreshing') : $t('fileExplorer.refresh') }} {{ refreshing ? $t('fileExplorer.refreshing') : $t('fileExplorer.refresh') }}
</button> </button>
<button class="btn btn-ghost btn-sm" type="button" title="弹出到独立窗口" @click="onDetach"> <button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.detachToWindow')" @click="onDetach">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 3h6v6"/> <path d="M15 3h6v6"/>
<path d="M10 14L21 3"/> <path d="M10 14L21 3"/>

View File

@@ -46,14 +46,14 @@
@keydown.enter="saveCondition(i)" @keydown.enter="saveCondition(i)"
@keydown.escape="cancelEdit" @keydown.escape="cancelEdit"
@blur="saveCondition(i)" @blur="saveCondition(i)"
:placeholder="$t('taskDetail.workflowCondPlaceholder') || '输入条件表达式'" :placeholder="$t('taskDetail.workflowCondPlaceholder')"
/> />
<span <span
v-else v-else
class="wf-dag-cond-label" class="wf-dag-cond-label"
:class="{ 'wf-dag-cond-label--uncond': !edge.condition, 'wf-dag-cond-label--editable': true }" :class="{ 'wf-dag-cond-label--uncond': !edge.condition, 'wf-dag-cond-label--editable': true }"
@click="startEdit(i, edge)" @click="startEdit(i, edge)"
>{{ edge.condition || ($t('taskDetail.workflowUnconditional') || '无条件') }}</span> >{{ edge.condition || $t('taskDetail.workflowUnconditional') }}</span>
</div> </div>
</div> </div>
</details> </details>

View File

@@ -209,6 +209,20 @@ export default {
// Compressed summary block label (before the system summary message when expanded) // Compressed summary block label (before the system summary message when expanded)
compressedSummaryLabel: 'Context summary', compressedSummaryLabel: 'Context summary',
// ── TopBar bottom context badges (tooltip + L3 full context popover labels) ──
viewGoals: 'View goals',
viewHistory: 'View history messages',
viewSummary: 'View context summary',
viewContext: 'View AI context',
contextSystemPrompt: 'System Prompt',
contextEnvironment: 'Environment',
contextCompressedSummary: 'Compressed Summary',
contextEnrichment: 'Enriched Context',
contextCountUnit: 'items',
// ── EnrichmentPanel ──
enrichmentDismiss: 'Unlink',
// ── AE-2025-04 Session Trust ── // ── AE-2025-04 Session Trust ──
// Auto-approval toast (same-session already approved same-kind op: same tool + same dir) // Auto-approval toast (same-session already approved same-kind op: same tool + same dir)
autoApprovedToast: 'Auto-approved: {tool}({dir})', autoApprovedToast: 'Auto-approved: {tool}({dir})',

View File

@@ -8,6 +8,8 @@ export default {
rejectedHint: 'User rejected this action', rejectedHint: 'User rejected this action',
// B-260616-12: tool execution slow hint (frontend fallback, no backend cancel IPC) // B-260616-12: tool execution slow hint (frontend fallback, no backend cancel IPC)
executionSlow: 'Tool "{name}" is taking longer than expected, please wait…', executionSlow: 'Tool "{name}" is taking longer than expected, please wait…',
// BUG-260624-02: approval pending duration hint (>2min orange, >5min red)
waitTime: '⏱ Waiting {time}',
// UX-260616-01: tool execution failure marker (AR-6 keeps status=completed, red to distinguish from success) // UX-260616-01: tool execution failure marker (AR-6 keeps status=completed, red to distinguish from success)
executionFailed: 'Failed', executionFailed: 'Failed',
executionFailedHint: 'Tool execution failed, see details below', executionFailedHint: 'Tool execution failed, see details below',

View File

@@ -3,5 +3,6 @@ export default {
title: 'Render Error', title: 'Render Error',
desc: 'This section failed to render due to an internal error. Try reloading.', desc: 'This section failed to render due to an internal error. Try reloading.',
retry: 'Retry', retry: 'Retry',
stackDetails: 'Stack Details',
}, },
} }

View File

@@ -20,6 +20,11 @@ export default {
// Diff // Diff
showDiff: 'Show Diff', showDiff: 'Show Diff',
showContent: 'Show Content', showContent: 'Show Content',
// Toolbar (current module actions)
editCurrentModule: 'Edit current module',
deleteCurrentModule: 'Delete current module',
addModuleTitle: 'Add module',
detachToWindow: 'Detach to separate window',
// Module management // Module management
addModule: 'Add Module', addModule: 'Add Module',
adding: 'Adding…', adding: 'Adding…',

View File

@@ -39,6 +39,11 @@ export default {
empty: 'No tasks yet', empty: 'No tasks yet',
}, },
confirmDelete: 'Delete task "{title}"? This action cannot be undone.', confirmDelete: 'Delete task "{title}"? This action cannot be undone.',
// Quick action menu (task card quick status/priority/delete)
quickActions: 'Quick Actions',
quickStatus: 'Status',
quickPriority: 'Priority',
quickDelete: '🗑 Delete',
// New task modal // New task modal
modal: { modal: {

View File

@@ -210,6 +210,20 @@ export default {
// 压缩摘要块标题(展开后 system 摘要消息前的标签) // 压缩摘要块标题(展开后 system 摘要消息前的标签)
compressedSummaryLabel: '上下文摘要', compressedSummaryLabel: '上下文摘要',
// ── TopBar 底部上下文徽章(tooltip + L3 完整上下文 popover 标签) ──
viewGoals: '查看目标',
viewHistory: '查看历史消息',
viewSummary: '查看上下文摘要',
viewContext: '查看 AI 上下文',
contextSystemPrompt: '系统提示',
contextEnvironment: '环境',
contextCompressedSummary: '压缩摘要',
contextEnrichment: '增强上下文',
contextCountUnit: '条',
// ── EnrichmentPanel ──
enrichmentDismiss: '取消关联',
// ── AE-2025-04 会话级信任(Session Trust) ── // ── AE-2025-04 会话级信任(Session Trust) ──
// 自动放行 toast(同会话已批准过同类操作:同工具+同目录,轻量 info 非审批气泡) // 自动放行 toast(同会话已批准过同类操作:同工具+同目录,轻量 info 非审批气泡)
autoApprovedToast: '已自动放行: {tool}({dir})', autoApprovedToast: '已自动放行: {tool}({dir})',

View File

@@ -8,6 +8,8 @@ export default {
rejectedHint: '用户拒绝了此操作', rejectedHint: '用户拒绝了此操作',
// B-260616-12:工具执行超时提示(前端降级,无后端取消 IPC) // B-260616-12:工具执行超时提示(前端降级,无后端取消 IPC)
executionSlow: '工具「{name}」执行较久,请稍候…', executionSlow: '工具「{name}」执行较久,请稍候…',
// BUG-260624-02:审批挂起时长提示(>2分钟橙色,>5分钟红色)
waitTime: '⏱ 已等待 {time}',
// UX-260616-01:工具执行失败视觉标识(AR-6 下 status=completed,需红色区分于成功) // UX-260616-01:工具执行失败视觉标识(AR-6 下 status=completed,需红色区分于成功)
executionFailed: '执行失败', executionFailed: '执行失败',
executionFailedHint: '工具执行失败,详情见下方', executionFailedHint: '工具执行失败,详情见下方',

View File

@@ -3,5 +3,6 @@ export default {
title: '页面渲染异常', title: '页面渲染异常',
desc: '当前区域因内部错误无法显示,可尝试重新加载。', desc: '当前区域因内部错误无法显示,可尝试重新加载。',
retry: '重试', retry: '重试',
stackDetails: '堆栈详情',
}, },
} }

View File

@@ -20,6 +20,11 @@ export default {
// Diff // Diff
showDiff: '查看变更', showDiff: '查看变更',
showContent: '查看内容', showContent: '查看内容',
// 工具栏(当前工程操作)
editCurrentModule: '编辑当前工程',
deleteCurrentModule: '删除当前工程',
addModuleTitle: '添加工程',
detachToWindow: '弹出到独立窗口',
// 工程管理 // 工程管理
addModule: '添加工程', addModule: '添加工程',
adding: '添加中…', adding: '添加中…',

View File

@@ -39,6 +39,11 @@ export default {
empty: '暂无任务', empty: '暂无任务',
}, },
confirmDelete: '确定删除任务「{title}」吗?此操作不可撤销。', confirmDelete: '确定删除任务「{title}」吗?此操作不可撤销。',
// 快捷操作菜单(任务卡片快捷改状态/优先级/删除)
quickActions: '快捷操作',
quickStatus: '状态',
quickPriority: '优先级',
quickDelete: '🗑 删除',
// 新建任务模态 // 新建任务模态
modal: { modal: {

View File

@@ -86,23 +86,23 @@
</div> </div>
<div class="task-actions"> <div class="task-actions">
<span class="status-tag" :class="taskStatusClass(task.status)">{{ $t(statusLabel(task.status)) }}</span> <span class="status-tag" :class="taskStatusClass(task.status)">{{ $t(statusLabel(task.status)) }}</span>
<button class="task-quick-btn" title="快捷操作" @click.stop="toggleQuickMenu(task.id)"></button> <button class="task-quick-btn" :title="$t('tasks.quickActions')" @click.stop="toggleQuickMenu(task.id)"></button>
<div v-if="quickMenuId === task.id" class="quick-menu" @click.stop> <div v-if="quickMenuId === task.id" class="quick-menu" @click.stop>
<div class="quick-menu-section"> <div class="quick-menu-section">
<div class="quick-menu-label">状态</div> <div class="quick-menu-label">{{ $t('tasks.quickStatus') }}</div>
<button v-for="s in quickStatuses" :key="s.key" class="quick-menu-item" @click="quickAdvance(task.id, s.key)"> <button v-for="s in quickStatuses" :key="s.key" class="quick-menu-item" @click="quickAdvance(task.id, s.key)">
<span>{{ s.icon }}</span>{{ s.label }} <span>{{ s.icon }}</span>{{ s.label }}
</button> </button>
</div> </div>
<div class="quick-menu-divider"></div> <div class="quick-menu-divider"></div>
<div class="quick-menu-section"> <div class="quick-menu-section">
<div class="quick-menu-label">优先级</div> <div class="quick-menu-label">{{ $t('tasks.quickPriority') }}</div>
<button v-for="p in quickPriorities" :key="p.value" class="quick-menu-item" @click="quickPriority(task.id, p.value)"> <button v-for="p in quickPriorities" :key="p.value" class="quick-menu-item" @click="quickPriority(task.id, p.value)">
<span :class="p.cls"></span>{{ p.label }} <span :class="p.cls"></span>{{ p.label }}
</button> </button>
</div> </div>
<div class="quick-menu-divider"></div> <div class="quick-menu-divider"></div>
<button class="quick-menu-item quick-menu-danger" @click="quickDelete(task)">🗑 删除</button> <button class="quick-menu-item quick-menu-danger" @click="quickDelete(task)">{{ $t('tasks.quickDelete') }}</button>
</div> </div>
</div> </div>
</div> </div>