|
|
@@ -1,9 +1,16 @@
|
|
|
<template>
|
|
|
<div class="task-page">
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
+ <div class="top-bar">
|
|
|
+ <div class="left">
|
|
|
+ <el-button type="info" plain @click="goBack">🏠 返回首页</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="title">📋 任务列表</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<el-card class="task-card">
|
|
|
<!-- 工具栏 -->
|
|
|
<div class="toolbar" ref="toolbarRef">
|
|
|
- <!-- 你的筛选表单(保持不变) -->
|
|
|
<el-form :inline="true" :model="filters" label-width="90px" @keyup.enter.native="onSearch">
|
|
|
<el-form-item label="ID">
|
|
|
<el-input v-model.number="filters.id" placeholder="精确 ID" clearable />
|
|
|
@@ -47,7 +54,9 @@
|
|
|
<el-table-column prop="task_name" label="任务名称" min-width="200" :show-overflow-tooltip="true" />
|
|
|
<el-table-column prop="task_status" label="状态" sortable="custom" width="140">
|
|
|
<template #default="{ row }">
|
|
|
- <el-tag :type="statusType(row.task_status)">{{ row.status_text || row.task_status }}</el-tag>
|
|
|
+ <el-tag :type="statusType(row.task_status)">
|
|
|
+ {{ row.status_text || row.task_status }}
|
|
|
+ </el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="start_timestamp" label="开始时间" sortable="custom" width="180">
|
|
|
@@ -104,9 +113,16 @@
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { onMounted, onBeforeUnmount, reactive, ref, nextTick } from 'vue'
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import { fetchTasks, fetchTaskDetail, retryTask, cancelTask, type TaskItem } from '../api/task'
|
|
|
|
|
|
+const router = useRouter()
|
|
|
+
|
|
|
+const goBack = () => {
|
|
|
+ router.push('/welcome')
|
|
|
+}
|
|
|
+
|
|
|
const statusOptions = [
|
|
|
{ label: '初始化(0)', value: 0 },
|
|
|
{ label: '处理中(1)', value: 1 },
|
|
|
@@ -146,16 +162,13 @@ const calcTableHeight = () => {
|
|
|
const winH = window.innerHeight
|
|
|
const toolbarH = toolbarRef.value?.offsetHeight ?? 0
|
|
|
const pagerH = pagerRef.value?.offsetHeight ?? 0
|
|
|
- const outerPadding = 16 * 2 // .task-page 上下 padding
|
|
|
- const cardPadding = 20 * 2 // el-card 内部上下 padding(大约值)
|
|
|
- const gap = 12 + 12 // toolbar 下方间距 + pager 上方 padding
|
|
|
- tableHeight.value = Math.max(
|
|
|
- 260,
|
|
|
- winH - (outerPadding + cardPadding + toolbarH + pagerH + gap)
|
|
|
- )
|
|
|
+ const outerPadding = 16 * 2
|
|
|
+ const cardPadding = 20 * 2
|
|
|
+ const gap = 12 + 12
|
|
|
+ tableHeight.value = Math.max(260, winH - (outerPadding + cardPadding + toolbarH + pagerH + gap))
|
|
|
}
|
|
|
|
|
|
-/* 加载列表(POST) */
|
|
|
+/* 加载列表 */
|
|
|
const load = async () => {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
@@ -188,7 +201,7 @@ const onSortChange = (e: any) => {
|
|
|
load()
|
|
|
}
|
|
|
|
|
|
-/* 工具 */
|
|
|
+/* 工具函数 */
|
|
|
const statusType = (s: number) => (s === 0 ? 'info' : s === 1 ? 'warning' : s === 2 ? 'success' : s === 99 ? 'danger' : '')
|
|
|
const formatTs = (ts?: number | null) => (!ts ? '-' : new Date(ts * 1000).toLocaleString())
|
|
|
const pretty = (v: any) => { try { return typeof v === 'string' ? v : JSON.stringify(v, null, 2) } catch { return String(v) } }
|
|
|
@@ -229,34 +242,75 @@ onBeforeUnmount(() => {
|
|
|
|
|
|
<style scoped>
|
|
|
.task-page {
|
|
|
- padding: 16px;
|
|
|
- background: #f5f6fa;
|
|
|
- height: 100vh; /* 让页面可用高度=视口高度 */
|
|
|
+ padding: 0;
|
|
|
+ background: linear-gradient(180deg, #f7f9fc, #edf1f6);
|
|
|
+ height: 100vh;
|
|
|
box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+/* 顶部导航栏 */
|
|
|
+.top-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ background: #fff;
|
|
|
+ padding: 12px 24px;
|
|
|
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
|
|
+ border-bottom: 1px solid #e6e8ef;
|
|
|
+}
|
|
|
+.top-bar .title {
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #333;
|
|
|
}
|
|
|
+
|
|
|
+/* 主体卡片 */
|
|
|
.task-card {
|
|
|
+ flex: 1;
|
|
|
+ margin: 20px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- height: 100%;
|
|
|
- box-shadow: 0 6px 18px rgba(0,0,0,0.06);
|
|
|
- border-radius: 10px;
|
|
|
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.05);
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+.table-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.custom-table {
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
-.toolbar { margin-bottom: 12px; }
|
|
|
-.table-wrapper { flex: 1; overflow: hidden; }
|
|
|
-.custom-table { border-radius: 8px; overflow: hidden; }
|
|
|
.pager {
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
|
- padding: 12px 0 0 0; /* 顶部 12px,底部 0,避免留白 */
|
|
|
+ padding: 12px 0 0 0;
|
|
|
background: #fff;
|
|
|
border-top: 1px solid #f0f2f5;
|
|
|
- margin: 0; /* 清除可能的外边距 */
|
|
|
}
|
|
|
-.detail { padding: 8px 0; }
|
|
|
-.block-title { margin: 14px 0 8px; font-weight: 600; }
|
|
|
+.detail {
|
|
|
+ padding: 8px 0;
|
|
|
+}
|
|
|
+.block-title {
|
|
|
+ margin: 14px 0 8px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
.code-block {
|
|
|
- background: #0b1021; color: #d1e7ff; padding: 12px;
|
|
|
- border-radius: 8px; overflow: auto; line-height: 1.5;
|
|
|
+ background: #0b1021;
|
|
|
+ color: #d1e7ff;
|
|
|
+ padding: 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: auto;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+.ml-8 {
|
|
|
+ margin-left: 8px;
|
|
|
}
|
|
|
-.ml-8 { margin-left: 8px; }
|
|
|
-</style>
|
|
|
+</style>
|