feat: processing flow enhancement + responsive UI
Phase 2 - Processing flow:
- Multi-task monitoring: store supports concurrent task tracking
- Task retry: POST /api/tasks/{id}/retry re-runs failed tasks
- Dashboard multi-task cards with progress, error details, retry/dismiss
- Log panel expanded from 10 to 50 lines with "view all" link
Phase 3 - UI/UX:
- Mobile sidebar drawer (< 768px) with hamburger menu
- Layout responsive styles (768px, 480px breakpoints)
- Tasks/Logs pages responsive (stat cards, filters, columns)
- File views responsive (header wrap, button sizing)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -35,27 +35,34 @@
|
||||
<div class="main-grid">
|
||||
<!-- Left column: Progress + Logs -->
|
||||
<div class="col-left">
|
||||
<!-- Progress -->
|
||||
<!-- Active tasks list -->
|
||||
<div class="card progress-card animate-in animate-in-delay-1">
|
||||
<div class="card-head">
|
||||
<h3>处理进度</h3>
|
||||
<el-tag v-if="currentTask" :type="statusType" size="small" effect="dark">
|
||||
{{ statusText }}
|
||||
<el-tag v-if="visibleTasks.length > 0" size="small" effect="dark">
|
||||
{{ visibleTasks.length }} 个任务
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTask" class="progress-area">
|
||||
<div class="progress-bar-wrapper">
|
||||
<div class="progress-bar-track">
|
||||
<div
|
||||
class="progress-bar-fill"
|
||||
:style="{ width: currentTask.progress + '%', background: statusColor }"
|
||||
></div>
|
||||
<div v-if="visibleTasks.length > 0" class="task-cards">
|
||||
<div v-for="task in visibleTasks" :key="task.task_id" class="task-card-item">
|
||||
<div class="task-card-header">
|
||||
<span class="task-name">{{ task.name }}</span>
|
||||
<el-tag :type="statusTagType(task.status)" size="small">{{ statusLabel(task.status) }}</el-tag>
|
||||
</div>
|
||||
<el-progress v-if="task.status === 'running' || task.status === 'pending'" :percentage="task.progress" :stroke-width="8" />
|
||||
<div v-if="task.message" class="task-message">{{ task.message }}</div>
|
||||
<!-- Error display -->
|
||||
<el-alert v-if="task.status === 'failed' && task.error" :title="task.error" type="error" show-icon :closable="false" class="task-error" />
|
||||
<!-- Actions -->
|
||||
<div class="task-card-actions">
|
||||
<el-button v-if="task.status === 'failed'" type="warning" size="small" @click="handleRetry(task.task_id)">重试</el-button>
|
||||
<el-button v-if="task.status === 'completed' || task.status === 'failed'" size="small" @click="handleDismiss(task.task_id)">关闭</el-button>
|
||||
</div>
|
||||
<!-- Log lines for this task -->
|
||||
<div v-if="task.log_lines?.length" class="task-logs">
|
||||
<div v-for="(log, i) in task.log_lines.slice(-50)" :key="i" class="log-line">{{ log }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-meta">
|
||||
<span class="progress-pct" :style="{ color: statusColor }">{{ currentTask.progress }}%</span>
|
||||
<span class="progress-msg">{{ currentTask.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
@@ -71,7 +78,10 @@
|
||||
<div class="card log-card animate-in animate-in-delay-2">
|
||||
<div class="card-head">
|
||||
<h3>处理日志</h3>
|
||||
<el-button size="small" link @click="clearLogs">清空</el-button>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<el-button size="small" link @click="$router.push('/tasks')">查看全部日志</el-button>
|
||||
<el-button size="small" link @click="clearLogs">清空</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="logBox" class="log-box">
|
||||
<div v-if="logs.length === 0" class="empty-state small">
|
||||
@@ -207,41 +217,10 @@ const detailedStats = ref({
|
||||
total_processed: 0,
|
||||
})
|
||||
|
||||
const currentTask = computed(() => {
|
||||
if (ps.taskSource !== 'sync') return ps.currentTask
|
||||
return null
|
||||
})
|
||||
const logs = computed(() => ps.logs.slice(0, 10))
|
||||
|
||||
const statusType = computed(() => {
|
||||
const m: Record<string, string> = {
|
||||
pending: 'info',
|
||||
running: 'warning',
|
||||
completed: 'success',
|
||||
failed: 'danger',
|
||||
}
|
||||
return m[currentTask.value?.status || ''] || 'info'
|
||||
})
|
||||
|
||||
const statusColor = computed(() => {
|
||||
const m: Record<string, string> = {
|
||||
pending: '#a1a1aa',
|
||||
running: '#f97316',
|
||||
completed: '#22c55e',
|
||||
failed: '#ef4444',
|
||||
}
|
||||
return m[currentTask.value?.status || ''] || '#a1a1aa'
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
const m: Record<string, string> = {
|
||||
pending: '等待中',
|
||||
running: '运行中',
|
||||
completed: '已完成',
|
||||
failed: '已失败',
|
||||
}
|
||||
return m[currentTask.value?.status || ''] || ''
|
||||
})
|
||||
const visibleTasks = computed(() =>
|
||||
ps.taskSource !== 'sync' ? ps.activeTaskList : []
|
||||
)
|
||||
const logs = computed(() => ps.logs.slice(0, 50))
|
||||
|
||||
const stats = computed(() => [
|
||||
{
|
||||
@@ -290,6 +269,29 @@ function clearLogs(): void {
|
||||
ps.logs.splice(0)
|
||||
}
|
||||
|
||||
function statusTagType(status: string): string {
|
||||
const map: Record<string, string> = { pending: 'info', running: '', completed: 'success', failed: 'danger' }
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
function statusLabel(status: string): string {
|
||||
const map: Record<string, string> = { pending: '等待中', running: '运行中', completed: '已完成', failed: '失败' }
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
async function handleRetry(taskId: string): Promise<void> {
|
||||
try {
|
||||
await ps.retryTask(taskId)
|
||||
ElMessage.success('已重新提交任务')
|
||||
} catch {
|
||||
ElMessage.error('重试失败')
|
||||
}
|
||||
}
|
||||
|
||||
function handleDismiss(taskId: string): void {
|
||||
ps.removeTask(taskId)
|
||||
}
|
||||
|
||||
async function refreshStats(): Promise<void> {
|
||||
statsLoading.value = true
|
||||
try {
|
||||
@@ -400,11 +402,11 @@ const runPipeline = () => doAction('/processing/pipeline')
|
||||
const runOcr = () => doAction('/processing/ocr-batch')
|
||||
const runExcel = () => doAction('/processing/excel')
|
||||
|
||||
// Auto-refresh stats when task completes
|
||||
// Auto-refresh stats when any task completes or fails
|
||||
watch(
|
||||
() => currentTask.value?.status,
|
||||
(status) => {
|
||||
if (status === 'completed' || status === 'failed') {
|
||||
() => visibleTasks.value.map(t => t.status),
|
||||
(statuses) => {
|
||||
if (statuses.some(s => s === 'completed' || s === 'failed')) {
|
||||
refreshStats()
|
||||
}
|
||||
}
|
||||
@@ -693,6 +695,75 @@ onMounted(() => {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ── Task cards ── */
|
||||
.task-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.task-card-item {
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 16px;
|
||||
background: #fafafa;
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.task-card-item:hover {
|
||||
border-color: #d4d4d8;
|
||||
}
|
||||
|
||||
.task-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-message {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.task-error {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.task-card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.task-logs {
|
||||
margin-top: 10px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: #09090b;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.task-logs .log-line {
|
||||
color: #a1a1aa;
|
||||
padding: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* ── Progress area ── */
|
||||
.progress-card {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user