Fix pagination: all pages default to 10 per page, add total counts, fix tasks pagination position and use client-side slicing

This commit is contained in:
Yuzhiran Dev
2026-05-18 22:59:03 +08:00
parent 894e53caef
commit 7b4e52743b
3 changed files with 30 additions and 19 deletions
+7 -5
View File
@@ -37,6 +37,7 @@
<div v-if="activeTab === 'cases'"> <div v-if="activeTab === 'cases'">
<div class="toolbar"> <div class="toolbar">
<el-button type="primary" size="small" @click="showCaseDialog()">新增案例</el-button> <el-button type="primary" size="small" @click="showCaseDialog()">新增案例</el-button>
<span style="font-size:13px;color:#909399;margin-left:8px;">共 {{ cases.length }} 个</span>
</div> </div>
<div v-if="casesLoading" class="card-loading">加载中...</div> <div v-if="casesLoading" class="card-loading">加载中...</div>
<template v-else-if="cases.length === 0"> <template v-else-if="cases.length === 0">
@@ -272,6 +273,7 @@
<div class="toolbar"> <div class="toolbar">
<el-button type="primary" size="small" @click="showCategoryDialog()">新增类别</el-button> <el-button type="primary" size="small" @click="showCategoryDialog()">新增类别</el-button>
<el-button size="small" @click="loadCategories">刷新</el-button> <el-button size="small" @click="loadCategories">刷新</el-button>
<span style="font-size:13px;color:#909399;margin-left:8px;">共 {{ categories.length }} 个</span>
</div> </div>
<div v-if="categoriesLoading" class="card-loading">加载中...</div> <div v-if="categoriesLoading" class="card-loading">加载中...</div>
<template v-else-if="categories.length === 0"> <template v-else-if="categories.length === 0">
@@ -649,7 +651,7 @@
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); } catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
}; };
const casePage = ref(1); const casePage = ref(1);
const casePageSize = 20; const casePageSize = 10;
const paginatedCases = computed(() => { const s = (casePage.value - 1) * casePageSize; return cases.value.slice(s, s + casePageSize); }); const paginatedCases = computed(() => { const s = (casePage.value - 1) * casePageSize; return cases.value.slice(s, s + casePageSize); });
const taskLogs = ref([]); const taskLogs = ref([]);
@@ -751,7 +753,7 @@
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); } catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
}; };
const orgPage = ref(1); const orgPage = ref(1);
const orgPageSize = 20; const orgPageSize = 10;
const paginatedOrgs = computed(() => { const s = (orgPage.value - 1) * orgPageSize; return orgs.value.slice(s, s + orgPageSize); }); const paginatedOrgs = computed(() => { const s = (orgPage.value - 1) * orgPageSize; return orgs.value.slice(s, s + orgPageSize); });
const users = ref([]); const users = ref([]);
@@ -760,7 +762,7 @@
const userSubmitting = ref(false); const userSubmitting = ref(false);
const userForm = reactive({ username: '', password: '', role: 'editor' }); const userForm = reactive({ username: '', password: '', role: 'editor' });
const userPage = ref(1); const userPage = ref(1);
const userPageSize = 20; const userPageSize = 10;
const paginatedUsers = computed(() => { const paginatedUsers = computed(() => {
const start = (userPage.value - 1) * userPageSize; const start = (userPage.value - 1) * userPageSize;
return users.value.slice(start, start + userPageSize); return users.value.slice(start, start + userPageSize);
@@ -861,7 +863,7 @@
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); } catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
}; };
const categoryPage = ref(1); const categoryPage = ref(1);
const categoryPageSize = 20; const categoryPageSize = 10;
const paginatedCategories = computed(() => { const s = (categoryPage.value - 1) * categoryPageSize; return categories.value.slice(s, s + categoryPageSize); }); const paginatedCategories = computed(() => { const s = (categoryPage.value - 1) * categoryPageSize; return categories.value.slice(s, s + categoryPageSize); });
const loadSources = async () => { const loadSources = async () => {
@@ -886,7 +888,7 @@
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); } catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
}; };
const sourcePage = ref(1); const sourcePage = ref(1);
const sourcePageSize = 20; const sourcePageSize = 10;
const paginatedSources = computed(() => { const s = (sourcePage.value - 1) * sourcePageSize; return sources.value.slice(s, s + sourcePageSize); }); const paginatedSources = computed(() => { const s = (sourcePage.value - 1) * sourcePageSize; return sources.value.slice(s, s + sourcePageSize); });
const formatDate = (dateStr) => { if (!dateStr) return '-'; return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }; const formatDate = (dateStr) => { if (!dateStr) return '-'; return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); };
+20 -12
View File
@@ -82,9 +82,6 @@
<span>{{ job.active ? '运行中' : '等待中' }}</span> <span>{{ job.active ? '运行中' : '等待中' }}</span>
<span v-if="job.next_run" style="margin-left: 8px; color: #909399;">下次: {{ job.next_run }}</span> <span v-if="job.next_run" style="margin-left: 8px; color: #909399;">下次: {{ job.next_run }}</span>
</div> </div>
<div v-if="totalTasks > pageSize" style="display:flex;justify-content:center;margin-top:16px;">
<el-pagination background layout="prev, pager, next" :total="totalTasks" :page-size="pageSize" :current-page="currentPage" @current-change="p => { currentPage = p; loadTasks(); }"></el-pagination>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -104,13 +101,13 @@
</div> </div>
</div> </div>
<div v-if="loading" style="text-align: center; padding: 40px; color: #909399;">加载中...</div> <div v-if="loading" style="text-align: center; padding: 40px; color: #909399;">加载中...</div>
<div v-else-if="tasks.length === 0" style="text-align: center; padding: 60px 20px; color: #909399;"> <div v-else-if="allTasks.length === 0" style="text-align: center; padding: 60px 20px; color: #909399;">
<el-icon style="font-size:56px;margin-bottom:16px;color:#c0c4cc;"><IconTopic /></el-icon> <el-icon style="font-size:56px;margin-bottom:16px;color:#c0c4cc;"><IconTopic /></el-icon>
<div style="font-size: 16px;">暂无任务</div> <div style="font-size: 16px;">暂无任务</div>
<div style="margin-top: 12px; font-size: 14px;">前往 <a href="/topics.html" style="color: #409eff;">选题管理</a> 创建新任务</div> <div style="margin-top: 12px; font-size: 14px;">前往 <a href="/topics.html" style="color: #409eff;">选题管理</a> 创建新任务</div>
</div> </div>
<div v-else> <div v-else>
<div v-for="task in tasks" :key="task.task_id" class="task-card"> <div v-for="task in paginatedTasks" :key="task.task_id" class="task-card">
<div class="task-header"> <div class="task-header">
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;"> <div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
<span class="task-id" title="任务ID">{{ task.task_id.slice(0,16) }}</span> <span class="task-id" title="任务ID">{{ task.task_id.slice(0,16) }}</span>
@@ -140,6 +137,10 @@
</div> </div>
<div v-if="task.error_msg" class="error-box">错误: {{ task.error_msg }}</div> <div v-if="task.error_msg" class="error-box">错误: {{ task.error_msg }}</div>
</div> </div>
<div v-if="paginatedTasks.length > 0 && allTasks.length > pageSize" style="display:flex;justify-content:center;align-items:center;margin-top:16px;gap:16px;">
<span style="font-size:13px;color:#909399;">共 {{ allTasks.length }} 条</span>
<el-pagination background layout="prev, pager, next" :total="allTasks.length" :page-size="pageSize" :current-page="currentPage" @current-change="p => { currentPage = p; }"></el-pagination>
</div>
</div> </div>
</div> </div>
</main> </main>
@@ -183,14 +184,20 @@ const TasksApp = {
const STATUS_NAMES = { 'pending': '等待中', 'running': '进行中', 'completed': '已完成', 'failed': '失败', 'cancelled': '已取消' }; const STATUS_NAMES = { 'pending': '等待中', 'running': '进行中', 'completed': '已完成', 'failed': '失败', 'cancelled': '已取消' };
return { return {
currentUser: { username: '' }, isAdmin: false, isLoggedIn: false, currentUser: { username: '' }, isAdmin: false, isLoggedIn: false,
tasks: [], loading: false, filterStatus: '', allTasks: [], tasks: [], loading: false, filterStatus: '',
showDetailDialog: false, detailTask: null, showDetailDialog: false, detailTask: null,
schedulerJobs: [], schedulerLoading: false, schedulerJobs: [], schedulerLoading: false,
totalTasks: 0, currentPage: 1, pageSize: 20, currentPage: 1, pageSize: 10,
pollTimer: null, pollTimer: null,
SCHEDULER_JOBS, SCHEDULER_JOBS,
} }
}, },
computed: {
paginatedTasks() {
const start = (this.currentPage - 1) * this.pageSize;
return this.allTasks.slice(start, start + this.pageSize);
}
},
methods: { methods: {
getToken() { return localStorage.getItem('authToken'); }, getToken() { return localStorage.getItem('authToken'); },
async api(url, opts = {}) { async api(url, opts = {}) {
@@ -239,19 +246,20 @@ const TasksApp = {
}, },
startPolling() { startPolling() {
this.stopPolling(); this.stopPolling();
this.pollTimer = setInterval(() => this.loadTasks(), 5000); this.pollTimer = setInterval(() => this.loadTasks(false), 5000);
}, },
stopPolling() { stopPolling() {
if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
}, },
async loadTasks() { async loadTasks(resetPage = true) {
this.loading = true; this.loading = true;
try { try {
let url = `/api/tasks?limit=${this.pageSize}&offset=${(this.currentPage - 1) * this.pageSize}`; let url = '/api/tasks?limit=200';
if (this.filterStatus) url += '&status=' + this.filterStatus; if (this.filterStatus) url += '&status=' + this.filterStatus;
const data = await this.api(url) || []; const data = await this.api(url) || [];
this.tasks = data; const oldId = this.allTasks.length > 0 && this.allTasks[0] ? this.allTasks[0].task_id : null;
this.totalTasks = data.length < this.pageSize ? (this.currentPage - 1) * this.pageSize + data.length : this.currentPage * this.pageSize + 1; this.allTasks = data;
if (resetPage) this.currentPage = 1;
const hasRunning = data.some(t => t.status === 'running'); const hasRunning = data.some(t => t.status === 'running');
if (hasRunning) { this.startPolling(); } else { this.stopPolling(); } if (hasRunning) { this.startPolling(); } else { this.stopPolling(); }
} catch (e) { console.error(e); this.$message.error('加载任务列表失败: ' + e.message); } } catch (e) { console.error(e); this.$message.error('加载任务列表失败: ' + e.message); }
+3 -2
View File
@@ -77,7 +77,8 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<div v-if="!loadingTable && paginatedTopics.length === 0" class="empty-state" style="margin-top:20px;"><el-icon style="font-size:48px;color:#c0c4cc;"><IconTopic /></el-icon><div class="empty-text">暂无选题数据</div></div> <div v-if="!loadingTable && paginatedTopics.length === 0" class="empty-state" style="margin-top:20px;"><el-icon style="font-size:48px;color:#c0c4cc;"><IconTopic /></el-icon><div class="empty-text">暂无选题数据</div></div>
<div v-if="filteredTopics.length > pageSize" style="display:flex;justify-content:center;margin:12px 0;"> <div v-if="filteredTopics.length > pageSize" style="display:flex;justify-content:center;align-items:center;margin:12px 0;gap:16px;">
<span style="font-size:13px;color:#909399;">共 {{ filteredTopics.length }} 条</span>
<el-pagination background layout="prev, pager, next" :total="filteredTopics.length" :page-size="pageSize" :current-page="currentPage" @current-change="currentPage = $event"></el-pagination> <el-pagination background layout="prev, pager, next" :total="filteredTopics.length" :page-size="pageSize" :current-page="currentPage" @current-change="currentPage = $event"></el-pagination>
</div> </div>
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0"> <div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
@@ -197,7 +198,7 @@ const TopicsApp = {
publishPlatforms: { zhihu: true, wechat: true, xiaohongshu: true }, publishPlatforms: { zhihu: true, wechat: true, xiaohongshu: true },
publishing: false, publishing: false,
savingContent: false, savingContent: false,
currentPage: 1, pageSize: 20 currentPage: 1, pageSize: 10
} }
}, },
computed: { computed: {