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:
@@ -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' }); };
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user