feat: 添加队列和议题删除功能,支持归档议题

This commit is contained in:
catlog22
2026-01-15 19:58:54 +08:00
parent 7db659f0e1
commit af4ddb1280
4 changed files with 237 additions and 0 deletions

View File

@@ -3258,6 +3258,34 @@
border-color: hsl(38 92% 50%);
}
.btn-danger,
.btn-secondary.btn-danger,
.btn-sm.btn-danger {
color: hsl(var(--destructive));
border-color: hsl(var(--destructive) / 0.5);
background: hsl(var(--destructive) / 0.08);
}
.btn-danger:hover,
.btn-secondary.btn-danger:hover,
.btn-sm.btn-danger:hover {
background: hsl(var(--destructive) / 0.15);
border-color: hsl(var(--destructive));
}
/* Issue Detail Actions */
.issue-detail-actions {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--border));
}
.issue-detail-actions .flex {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
/* Active queue badge enhancement */
.queue-active-badge {
display: inline-flex;

View File

@@ -2273,6 +2273,16 @@ const i18n = {
'issues.deactivate': 'Deactivate',
'issues.queueActivated': 'Queue activated',
'issues.queueDeactivated': 'Queue deactivated',
'issues.deleteQueue': 'Delete queue',
'issues.confirmDeleteQueue': 'Are you sure you want to delete this queue? This action cannot be undone.',
'issues.queueDeleted': 'Queue deleted successfully',
'issues.actions': 'Actions',
'issues.archive': 'Archive',
'issues.delete': 'Delete',
'issues.confirmDeleteIssue': 'Are you sure you want to delete this issue? This action cannot be undone.',
'issues.confirmArchiveIssue': 'Archive this issue? It will be moved to history.',
'issues.issueDeleted': 'Issue deleted successfully',
'issues.issueArchived': 'Issue archived successfully',
'issues.executionQueues': 'Execution Queues',
'issues.queues': 'queues',
'issues.noQueues': 'No queues found',
@@ -4605,6 +4615,16 @@ const i18n = {
'issues.deactivate': '取消激活',
'issues.queueActivated': '队列已激活',
'issues.queueDeactivated': '队列已取消激活',
'issues.deleteQueue': '删除队列',
'issues.confirmDeleteQueue': '确定要删除此队列吗?此操作无法撤销。',
'issues.queueDeleted': '队列删除成功',
'issues.actions': '操作',
'issues.archive': '归档',
'issues.delete': '删除',
'issues.confirmDeleteIssue': '确定要删除此议题吗?此操作无法撤销。',
'issues.confirmArchiveIssue': '归档此议题?它将被移动到历史记录中。',
'issues.issueDeleted': '议题删除成功',
'issues.issueArchived': '议题归档成功',
'issues.executionQueues': '执行队列',
'issues.queues': '个队列',
'issues.noQueues': '暂无队列',

View File

@@ -562,6 +562,9 @@ function renderQueueCard(queue, isActive) {
<i data-lucide="git-merge" class="w-3 h-3"></i>
</button>
` : ''}
<button class="btn-sm btn-danger" onclick="confirmDeleteQueue('${safeQueueId}')" title="${t('issues.deleteQueue') || 'Delete queue'}">
<i data-lucide="trash-2" class="w-3 h-3"></i>
</button>
</div>
</div>
`;
@@ -619,6 +622,33 @@ async function deactivateQueue(queueId) {
}
}
function confirmDeleteQueue(queueId) {
const msg = t('issues.confirmDeleteQueue') || 'Are you sure you want to delete this queue? This action cannot be undone.';
if (confirm(msg)) {
deleteQueue(queueId);
}
}
async function deleteQueue(queueId) {
try {
const response = await fetch('/api/queue/' + encodeURIComponent(queueId) + '?path=' + encodeURIComponent(projectPath), {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
showNotification(t('issues.queueDeleted') || 'Queue deleted successfully', 'success');
queueData.expandedQueueId = null;
await Promise.all([loadQueueData(), loadAllQueues()]);
renderIssueView();
} else {
showNotification(result.error || 'Failed to delete queue', 'error');
}
} catch (err) {
console.error('Failed to delete queue:', err);
showNotification('Failed to delete queue', 'error');
}
}
async function renderExpandedQueueView(queueId) {
const safeQueueId = escapeHtml(queueId || '');
// Fetch queue detail
@@ -1405,6 +1435,23 @@ function renderIssueDetailPanel(issue) {
`).join('') : '<p class="text-sm text-muted-foreground">' + (t('issues.noTasks') || 'No tasks') + '</p>'}
</div>
</div>
<!-- Actions -->
<div class="detail-section issue-detail-actions">
<label class="detail-label">${t('issues.actions') || 'Actions'}</label>
<div class="flex gap-2 flex-wrap">
${!issue._isArchived ? `
<button class="btn-secondary btn-sm" onclick="confirmArchiveIssue('${issue.id}')">
<i data-lucide="archive" class="w-4 h-4"></i>
${t('issues.archive') || 'Archive'}
</button>
` : ''}
<button class="btn-secondary btn-sm btn-danger" onclick="confirmDeleteIssue('${issue.id}', ${issue._isArchived || false})">
<i data-lucide="trash-2" class="w-4 h-4"></i>
${t('issues.delete') || 'Delete'}
</button>
</div>
</div>
</div>
`;
@@ -1419,6 +1466,67 @@ function closeIssueDetail() {
issueData.selectedIssue = null;
}
// ========== Issue Delete & Archive ==========
function confirmDeleteIssue(issueId, isArchived) {
const msg = t('issues.confirmDeleteIssue') || 'Are you sure you want to delete this issue? This action cannot be undone.';
if (confirm(msg)) {
deleteIssue(issueId, isArchived);
}
}
async function deleteIssue(issueId, isArchived) {
try {
const response = await fetch('/api/issues/' + encodeURIComponent(issueId) + '?path=' + encodeURIComponent(projectPath), {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
showNotification(t('issues.issueDeleted') || 'Issue deleted successfully', 'success');
closeIssueDetail();
if (isArchived) {
issueData.historyIssues = issueData.historyIssues.filter(i => i.id !== issueId);
} else {
issueData.issues = issueData.issues.filter(i => i.id !== issueId);
}
renderIssueView();
updateIssueBadge();
} else {
showNotification(result.error || 'Failed to delete issue', 'error');
}
} catch (err) {
console.error('Failed to delete issue:', err);
showNotification('Failed to delete issue', 'error');
}
}
function confirmArchiveIssue(issueId) {
const msg = t('issues.confirmArchiveIssue') || 'Archive this issue? It will be moved to history.';
if (confirm(msg)) {
archiveIssue(issueId);
}
}
async function archiveIssue(issueId) {
try {
const response = await fetch('/api/issues/' + encodeURIComponent(issueId) + '/archive?path=' + encodeURIComponent(projectPath), {
method: 'POST'
});
const result = await response.json();
if (result.success) {
showNotification(t('issues.issueArchived') || 'Issue archived successfully', 'success');
closeIssueDetail();
await loadIssueData();
renderIssueView();
updateIssueBadge();
} else {
showNotification(result.error || 'Failed to archive issue', 'error');
}
} catch (err) {
console.error('Failed to archive issue:', err);
showNotification('Failed to archive issue', 'error');
}
}
function toggleSolutionExpand(solId) {
const el = document.getElementById('solution-' + solId);
if (el) {