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
+86 -4
View File
@@ -24,6 +24,13 @@
.day-holiday { font-size: 8px; padding: 0 2px; }
.day-entry { font-size: 10px; padding: 2px 3px; white-space: normal; line-height: 1.2; }
.stats-bar { gap: 12px; font-size: 12px; }
.calendar-guide { flex-direction: column; padding: 12px; }
.calendar-guide-icon { font-size: 22px; }
.calendar-guide-content h3 { font-size: 15px; }
.calendar-guide-content li, .calendar-guide-content p { font-size: 12px; }
.upcoming-section h3 { font-size: 14px; }
.upcoming-item { width: 140px; padding: 10px; }
.calendar-hint { font-size: 11px; }
}
.calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 12px; }
@@ -51,6 +58,24 @@
.entry-topic-status.published { background:#f0f9eb; color:#67c23a; }
.entry-topic-status.pending { background:#f4f4f5; color:#909399; }
.calendar-guide { background: linear-gradient(135deg, #eef2ff, #e0e7ff); border: 1px solid #c7d2fe; border-radius: 12px; padding: 16px 20px; margin-bottom: 20px; display: flex; align-items: flex-start; gap: 12px; position: relative; }
.calendar-guide-close { position: absolute; top: 8px; right: 8px; background: none; border: none; color: #6366f1; cursor: pointer; font-size: 18px; padding: 4px; border-radius: 4px; }
.calendar-guide-close:hover { background: rgba(99,102,241,0.1); }
.calendar-guide-icon { font-size: 28px; flex-shrink: 0; margin-top: 2px; }
.calendar-guide-content { flex: 1; }
.calendar-guide-content h3 { font-size: 16px; font-weight: 600; color: #4338ca; margin: 0 0 6px 0; }
.calendar-guide-content p { font-size: 13px; color: #4f46e5; margin: 0; line-height: 1.6; }
.calendar-guide-content ol { margin: 6px 0 0 0; padding-left: 20px; }
.calendar-guide-content li { font-size: 13px; color: #4f46e5; line-height: 1.8; }
.upcoming-section { margin-bottom: 20px; }
.upcoming-section h3 { font-size: 16px; font-weight: 600; color: #303133; margin: 0 0 10px 0; display: flex; align-items: center; gap: 8px; }
.upcoming-list { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.upcoming-item { flex-shrink: 0; width: 180px; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 12px; cursor: pointer; transition: all 0.2s; }
.upcoming-item:hover { border-color: #6366f1; box-shadow: 0 2px 8px rgba(99,102,241,0.15); }
.upcoming-date { font-size: 12px; color: #6366f1; font-weight: 600; }
.upcoming-title { font-size: 14px; color: #1f2937; font-weight: 500; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upcoming-meta { font-size: 12px; color: #9ca3af; margin-top: 6px; display: flex; gap: 8px; }
.calendar-hint { text-align: center; font-size: 12px; color: #c0c4cc; margin-top: 8px; padding: 4px 0; }
.stats-bar { display: flex; gap: 24px; margin-bottom: 24px; flex-wrap: wrap; }
.stat-item { display: flex; align-items: center; gap: 8px; }
.stat-dot { width: 10px; height: 10px; border-radius: 50%; }
@@ -78,8 +103,42 @@
</div>
</div>
<!-- 使用指南 -->
<div v-if="showGuide" class="calendar-guide">
<div class="calendar-guide-icon">📅</div>
<div class="calendar-guide-content">
<h3>内容日历使用说明</h3>
<p>规划和管理各平台的内容发布排期,一目了然看到每月发布计划。</p>
<ol>
<li><strong>点击日期</strong> — 查看该日所有日程,或点击"添加日程"新建</li>
<li><strong>点击日程卡片</strong> — 编辑或删除已有日程</li>
<li><strong>关联选题</strong> — 创建日程时可关联已有选题,跟踪从创作到发布的全流程</li>
</ol>
</div>
<button class="calendar-guide-close" @click="showGuide = false; localStorage.setItem('calendarGuideDismissed', 'true')" title="不再显示"></button>
</div>
<!-- 即将到来 -->
<div v-if="upcomingEntries.length > 0" class="upcoming-section">
<h3><el-icon><IconClock /></el-icon> 即将到来</h3>
<div class="upcoming-list">
<div v-for="entry in upcomingEntries" :key="entry.id" class="upcoming-item" @click="openEntryDialog(entry)">
<div class="upcoming-date">{{ formatDate(entry.planned_date) }}</div>
<div class="upcoming-title">{{ entry.title || '未命名' }}</div>
<div class="upcoming-meta">
<span>{{ platformName(entry.platform) }}</span>
<el-tag :type="statusType(entry.status)" size="small">{{ statusLabel(entry.status) }}</el-tag>
</div>
</div>
</div>
</div>
<div class="stats-bar">
<div v-if="loadingCalendar" style="color:#909399;font-size:14px;">加载中...</div>
<div v-else-if="errorMsg" class="empty-state" style="color:#F56C6C;">
<el-icon style="font-size:36px;color:#F56C6C;"><IconClose /></el-icon>
<div class="empty-text">{{ errorMsg }}</div>
</div>
<div v-else-if="!calendarDays.length" class="empty-state">
<el-icon style="font-size:48px;color:#c0c4cc;"><IconCalendar /></el-icon>
<div class="empty-text">暂无日历数据</div>
@@ -113,15 +172,21 @@
</div>
</div>
</div>
<div class="calendar-hint">💡 点击日期查看或添加日程 · 点击日程卡片编辑详情</div>
</div>
</div>
</main>
</div>
<el-dialog v-model="dayDialogVisible" :title="selectedDay ? `${selectedDay.year}-${selectedDay.month}-${selectedDay.day} 日程` : ''" width="500px">
<el-dialog v-model="dayDialogVisible" :title="selectedDay ? `${selectedDay.year}${selectedDay.month}${selectedDay.day} 日程` : ''" width="500px">
<div v-if="selectedDay">
<el-button type="primary" @click="openCreateDialog" style="margin-bottom: 16px;">+ 添加日程</el-button>
<el-table :data="selectedDayEntries" stripe>
<div v-if="selectedDayEntries.length === 0" class="empty-state" style="padding:20px 0;">
<el-icon style="font-size:36px;color:#c0c4cc;"><IconCalendar /></el-icon>
<div class="empty-text">该日暂无日程</div>
<p style="font-size:13px;color:#909399;margin:4px 0 0 0;">点击上方"添加日程"创建发布计划</p>
</div>
<el-table v-else :data="selectedDayEntries" stripe>
<el-table-column prop="title" label="标题" min-width="120"></el-table-column>
<el-table-column prop="platform" label="平台" width="100">
<template #default="scope">{{ platformName(scope.row.platform) }}</template>
@@ -321,11 +386,28 @@ function getDayMeta(year, month, day) {
const loadingCalendar = ref(false);
const calendarError = ref('');
const showGuide = ref(localStorage.getItem('calendarGuideDismissed') !== 'true');
const errorMsg = ref('');
const upcomingEntries = computed(() => {
const today = new Date(); today.setHours(0, 0, 0, 0);
const end = new Date(today); end.setDate(end.getDate() + 7);
return entries.value
.filter(e => { const d = new Date(e.planned_date); return d >= today && d < end; })
.sort((a, b) => new Date(a.planned_date) - new Date(b.planned_date))
.slice(0, 10);
});
const formatDate = (dateStr) => {
if (!dateStr) return '';
const d = new Date(dateStr);
return `${d.getMonth() + 1}${d.getDate()}`;
};
const fetchEntries = async () => {
loadingCalendar.value = true;
calendarError.value = '';
try { entries.value = await api(`/api/calendar?year=${currentYear.value}&month=${currentMonth.value}`); } catch (e) { console.error(e); calendarError.value = e.message; ElMessage.error('加载日历失败: ' + e.message); }
errorMsg.value = '';
try { entries.value = await api(`/api/calendar?year=${currentYear.value}&month=${currentMonth.value}`); } catch (e) { console.error(e); calendarError.value = e.message; errorMsg.value = '加载失败: ' + e.message; }
finally { loadingCalendar.value = false; }
};
const fetchStats = async () => {
@@ -398,7 +480,7 @@ function getDayMeta(year, month, day) {
checkAuth();
});
return { currentUser, isAdmin, currentYear, currentMonth, weekDays, calendarDays, entries, topics, stats, dayDialogVisible, entryDialogVisible, selectedDay, selectedDayEntries, isEdit, saving, entryForm, prevMonth, nextMonth, goToday, openDayDialog, openCreateDialog, openEntryDialog, saveEntry, deleteEntry, platformName, statusLabel, statusType, topicStatusLabel, handleLogout, redirectToPage };
return { currentUser, isAdmin, currentYear, currentMonth, weekDays, calendarDays, entries, topics, stats, dayDialogVisible, entryDialogVisible, selectedDay, selectedDayEntries, isEdit, saving, entryForm, prevMonth, nextMonth, goToday, openDayDialog, openCreateDialog, openEntryDialog, saveEntry, deleteEntry, platformName, statusLabel, statusType, topicStatusLabel, handleLogout, redirectToPage, showGuide, errorMsg, upcomingEntries, formatDate };
}
};