Add pagination to admin management tabs (cases/categories/sources/orgs)

This commit is contained in:
Yuzhiran Dev
2026-05-18 19:18:09 +08:00
parent 7169ddb678
commit a09e7e6671
14 changed files with 455 additions and 340 deletions
+38 -10
View File
@@ -50,7 +50,7 @@
<el-button size="default" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'; fetchTopics()">待发布 ({{ statusStats.ready }})</el-button>
<el-button size="default" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'; fetchTopics()">已发布 ({{ statusStats.published }})</el-button>
</div>
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event.map(item => item.id)">
<el-table :data="paginatedTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event.map(item => item.id)">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="70" fixed></el-table-column>
<el-table-column prop="title" label="标题" min-width="200"></el-table-column>
@@ -76,7 +76,10 @@
</template>
</el-table-column>
</el-table>
<div v-if="!loadingTable && filteredTopics.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;">
<el-pagination background layout="prev, pager, next" :total="filteredTopics.length" :page-size="pageSize" :current-page="currentPage" @current-change="currentPage = $event"></el-pagination>
</div>
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card" :class="{ 'is-checked': selectedTopicIds.includes(topic.id) }">
<div class="topic-card-header">
@@ -138,19 +141,26 @@
<div style="display:flex; align-items:center; gap:12px; font-size:13px; color:#909399;">
<span>创建:{{ formatDate(previewTopic.created_at) }}</span>
<span>状态:{{ getStatusLabel(previewTopic.status) }}</span>
<el-tag v-if="editing" type="warning" size="small">编辑模式</el-tag>
<el-button size="small" @click="togglePreviewFullscreen">{{ previewFullscreen ? '退出全屏' : '全屏' }}</el-button>
<el-button v-if="previewFullscreen" size="small" type="danger" @click="previewVisible = false">关闭</el-button>
</div>
</div>
<div style="margin: 12px 0 16px; display: flex; justify-content: flex-end; gap: 8px;">
<div style="margin: 12px 0 16px; display: flex; justify-content: space-between; align-items: center; gap: 8px;">
<el-button-group>
<el-button :type="previewPlatform === 'zhihu' ? 'primary' : 'default'" @click="previewPlatform = 'zhihu'">知乎</el-button>
<el-button :type="previewPlatform === 'wechat' ? 'primary' : 'default'" @click="previewPlatform = 'wechat'">微信公众号</el-button>
<el-button :type="previewPlatform === 'xiaohongshu' ? 'primary' : 'default'" @click="previewPlatform = 'xiaohongshu'">小红书</el-button>
<el-button :type="previewPlatform === 'zhihu' ? 'primary' : 'default'" @click="switchPlatform('zhihu')">知乎</el-button>
<el-button :type="previewPlatform === 'wechat' ? 'primary' : 'default'" @click="switchPlatform('wechat')">微信公众号</el-button>
<el-button :type="previewPlatform === 'xiaohongshu' ? 'primary' : 'default'" @click="switchPlatform('xiaohongshu')">小红书</el-button>
</el-button-group>
<div>
<el-button v-if="!editing" size="small" type="primary" @click="startEdit" plain><el-icon style="vertical-align:-2px;"><IconEdit /></el-icon> 编辑</el-button>
<el-button v-if="editing" size="small" @click="cancelEdit">取消</el-button>
<el-button v-if="editing" size="small" type="primary" @click="saveContent" :loading="savingContent">保存</el-button>
</div>
</div>
<div style="max-width: 1000px; margin: 0 auto; width: 100%; height: 100%; display: flex; flex-direction: column;">
<iframe :srcdoc="currentPreviewHtml" class="preview-iframe" style="flex:1; min-height:500px; border:1px solid #ebeef5; border-radius:8px; background:#fff; overflow:auto; padding:0; width:100%;" sandbox></iframe>
<div style="max-width: 1000px; margin: 0 auto; width: 100%; display: flex; flex-direction: column;">
<div v-if="!editing" class="preview-body" style="flex:1; min-height:500px; border:1px solid #ebeef5; border-radius:8px; background:#fff; padding:20px; overflow:auto; width:100%; line-height:1.8;" v-html="currentPreviewBody"></div>
<div v-else ref="editor" contenteditable="true" style="flex:1; min-height:500px; border:1px solid #409eff; border-radius:8px; background:#fff; padding:20px; overflow:auto; width:100%; line-height:1.8; outline:none; box-shadow:0 0 0 2px rgba(64,158,255,0.2);" @input="editContent = $event.target.innerHTML"></div>
</div>
</div>
<template #footer>
@@ -182,10 +192,11 @@ const TopicsApp = {
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
topics: [], allTopics: [], todayTopics: [], todayCount: 0,
previewVisible: false, previewTopic: null, previewFullscreen: false,
previewPlatform: 'zhihu', platformContents: {},
previewPlatform: 'zhihu', platformContents: {}, editing: false, editContent: '',
publishDialogVisible: false, publishTopic: null,
publishPlatforms: { zhihu: true, wechat: true, xiaohongshu: true },
publishing: false
publishing: false,
currentPage: 1, pageSize: 20
}
},
computed: {
@@ -196,6 +207,10 @@ const TopicsApp = {
const allowed = map[this.filterStatus] || [this.filterStatus];
return this.topics.filter(t => allowed.includes(t.status));
},
paginatedTopics() {
const start = (this.currentPage - 1) * this.pageSize;
return this.filteredTopics.slice(start, start + this.pageSize);
},
statusStats() {
const s = { total: this.topics.length, pending: 0, review: 0, ready: 0, published: 0 };
const aliases = { 'pending': ['pending','待处理'], 'review': ['review','待审查'], 'ready': ['ready','待发布'], 'published': ['published','已发布'] };
@@ -217,6 +232,18 @@ const TopicsApp = {
return `<!DOCTYPE html><html><head>${headHtml}</head><body style="margin:0;padding:0;">${bodyHtml}${ending}</body></html>`;
} catch (e) { console.error('生成预览 HTML 失败:', e); return html; }
},
currentPreviewBody() {
const html = this.platformContents[this.previewPlatform];
if (!html) return '';
try {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const body = doc.body;
if (!body) return html;
body.querySelectorAll('script, nav, .header, footer, .interaction').forEach(el => el.remove());
return body.innerHTML;
} catch (e) { console.error('解析 HTML 失败:', e); return html; }
},
selectAllLabel() {
const visible = this.filteredTopics.map(t => t.id);
if (visible.length === 0) return '全选';
@@ -234,6 +261,7 @@ const TopicsApp = {
return res.json();
},
async fetchTopics() {
this.currentPage = 1;
this.loadingTable = true;
try {
const token = this.getToken();