fix: restore frontend to dynamic version (undo incorrect sync from outdated backend/static)

This commit is contained in:
lt
2026-05-01 07:51:16 +08:00
parent 94295e1f74
commit e1613d2af9
3 changed files with 172 additions and 75 deletions
+105 -28
View File
@@ -19,8 +19,8 @@
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.content-area { flex: 1; padding: 32px; overflow-y: auto; }
.card { background: white; border-radius: 16px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.status-badge { display: inline-flex; align-items: center; gap: 4px; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
.status-dot.pending { background: #E6A23C; }
@@ -36,6 +36,7 @@
.content-area { padding: 16px; padding-bottom: 80px; }
}
.topic-card-list { display: none; }
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
@@ -45,7 +46,7 @@
.topic-card-list { display: block; margin: 0 -16px; }
.topic-card {
background: white;
border-radius: 8px;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
@@ -99,7 +100,7 @@
</aside>
<main class="content-area">
<div class="card">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</h2>
<h2 style="font-size: 28px; font-weight: 700; margin-bottom: 32px; color: #303133;">📋 选题管理</h2>
<div class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
@@ -109,11 +110,11 @@
</div>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
<el-tag size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-tag>
<el-tag size="large" :type="filterStatus === 'pending' ? 'primary' : ''" @click="filterStatus = 'pending'">待处理 ({{ countByStatus('pending') }})</el-tag>
<el-tag size="large" :type="filterStatus === 'review' ? 'primary' : ''" @click="filterStatus = 'review'">待审查 ({{ countByStatus('review') }})</el-tag>
<el-tag size="large" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'">待发布 ({{ countByStatus('ready') }})</el-tag>
<el-tag size="large" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'">已发布 ({{ countByStatus('published') }})</el-tag>
<el-button size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-button>
<el-button size="large" :type="filterStatus === 'pending' ? 'primary' : ''" @click="filterStatus = 'pending'">待处理 ({{ statusStats.pending }})</el-button>
<el-button size="large" :type="filterStatus === 'review' ? 'primary' : ''" @click="filterStatus = 'review'">待审查 ({{ statusStats.review }})</el-button>
<el-button size="large" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'">待发布 ({{ statusStats.ready }})</el-button>
<el-button size="large" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'">已发布 ({{ statusStats.published }})</el-button>
</div>
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
@@ -130,13 +131,13 @@
<el-table-column prop="created_at" label="创建时间" width="140"><template #default="scope">{{ formatDate(scope.row.created_at) }}</template></el-table-column>
<el-table-column prop="generated_at" label="创作时间" width="140"><template #default="scope">{{ scope.row.generated_at ? formatDate(scope.row.generated_at) : '-' }}</template></el-table-column>
<el-table-column prop="published_at" label="发布时间" width="140"><template #default="scope">{{ scope.row.published_at ? formatDate(scope.row.published_at) : '-' }}</template></el-table-column>
<el-table-column label="操作" width="280" fixed="right">
<el-table-column label="操作" width="180" fixed="right">
<template #default="scope">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
<el-button size="small" @click="openPreview(scope.row)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="scope.row.status !== 'pending'" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="scope.row.status !== 'review'" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="scope.row.status === 'ready'" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="success" :disabled="!isStatus(scope.row, 'pending')" @click="createTopic(scope.row)">创作</el-button>
<el-button size="small" type="warning" :disabled="!isStatus(scope.row, 'review')" @click="optimizeTopic(scope.row)">审查</el-button>
<el-button v-if="isStatus(scope.row, 'ready')" size="small" type="primary" @click="handlePublish(scope.row)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
</div>
</template>
@@ -144,9 +145,9 @@
</el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div v-for="(topic, index) in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<div class="topic-card-title">{{ topic.title }}</div>
<div class="topic-card-title">{{ topic.id }}. {{ topic.title }}</div>
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
</div>
<div class="topic-card-tags">
@@ -160,9 +161,9 @@
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== 'pending'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== 'review'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === 'ready'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="success" :disabled="!isStatus(topic, 'pending')" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="!isStatus(topic, 'review')" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="isStatus(topic, 'ready')" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
@@ -178,6 +179,29 @@
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
</nav>
<!-- 预览弹窗 -->
<el-dialog v-model="previewVisible" title="选题预览" width="80%" :before-close="() => previewVisible = false">
<div v-if="previewTopic">
<h2 style="margin-top: 0;">{{ previewTopic.title }}</h2>
<div class="preview-content" style="max-height: 60vh; overflow-y: auto; margin: 16px 0; padding: 16px; border: 1px solid #ebeef5; border-radius: 8px; background: #fafafa;">
{{ previewTopic.content || '暂无内容' }}
</div>
<div class="preview-footer" style="font-size: 14px; color: #909399;">
<div>创建时间:{{ formatDate(previewTopic.created_at) }}</div>
<div>状态:{{ previewTopic.status }}</div>
<div v-if="previewTopic.generated_at">创作时间:{{ formatDate(previewTopic.generated_at) }}</div>
<div v-if="previewTopic.published_at">发布时间:{{ formatDate(previewTopic.published_at) }}</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="previewVisible = false">关闭</el-button>
<el-button type="primary" @click="copyContent('微信公众号')">复制并发布到微信公众号</el-button>
<el-button type="primary" @click="copyContent('知乎')">复制并发布到知乎</el-button>
<el-button type="primary" @click="copyContent('小红书')">复制并发布到小红书</el-button>
</div>
</template>
</el-dialog>
</div>
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
@@ -194,17 +218,36 @@ const TopicsApp = {
selectedTopicIds: [],
filterStatus: '',
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
topics: []
topics: [],
previewVisible: false,
previewTopic: null
}
},
computed: {
filteredTopics() {
filteredTopics() {
if (!this.topics || !this.topics.length) { return []; }
if (!this.filterStatus) { return this.topics; }
return this.topics.filter(t => t.status === this.filterStatus);
const statusMap = {
'pending': ['pending', '待处理'],
'review': ['review', '待审查'],
'ready': ['ready', '待发布'],
'published': ['published', '已发布']
};
const allowed = statusMap[this.filterStatus] || [this.filterStatus];
return this.topics.filter(t => allowed.includes(t.status));
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length;
statusStats() {
const pending = ['pending', '待处理'];
const review = ['review', '待审查'];
const ready = ['ready', '待发布'];
const published = ['published', '已发布'];
return {
total: this.topics.length,
pending: this.topics.filter(t => pending.includes(t.status)).length,
review: this.topics.filter(t => review.includes(t.status)).length,
ready: this.topics.filter(t => ready.includes(t.status)).length,
published: this.topics.filter(t => published.includes(t.status)).length
};
}
},
methods: {
@@ -249,9 +292,25 @@ const TopicsApp = {
this.selectedTopicIds = [];
await this.fetchTopics();
},
openPreview(topic) { this.$message.info('预览:' + topic.title); },
openPreview(topic) {
this.previewTopic = topic;
this.previewVisible = true;
},
copyContent(platform) {
if (!this.previewTopic || !this.previewTopic.content) {
this.$message.warning('暂无内容可复制');
return;
}
const text = `标题:${this.previewTopic.title}\n\n内容:\n${this.previewTopic.content}`;
navigator.clipboard.writeText(text).then(() => {
this.$message.success(`已复制内容,请前往${platform}粘贴发布`);
}).catch(err => {
console.error('复制失败', err);
this.$message.error('复制失败,请手动复制');
});
},
async createTopic(topic) {
if (topic.status !== 'pending') {
if (!this.isStatus(topic, 'pending')) {
this.$message.info('仅待处理选题可创作');
return;
}
@@ -282,7 +341,7 @@ const TopicsApp = {
}
},
async optimizeTopic(topic) {
if (topic.status !== 'review') {
if (!this.isStatus(topic, 'review')) {
this.$message.info('仅待审查选题可优化');
return;
}
@@ -313,7 +372,7 @@ const TopicsApp = {
}
},
async handlePublish(topic) {
if (topic.status !== 'ready') {
if (!this.isStatus(topic, 'ready')) {
this.$message.info('仅待发布选题可发布');
return;
}
@@ -382,8 +441,26 @@ const TopicsApp = {
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { 'pending': 'warning', 'review': 'danger', 'ready': 'success', 'published': 'info' };
const map = {
'pending': 'warning',
'review': 'danger',
'ready': 'success',
'published': 'info',
'待处理': 'warning',
'待审查': 'danger',
'待发布': 'success',
'已发布': 'info'
};
return map[status] || 'primary';
},
isStatus(row, status) {
const map = {
'pending': ['pending', '待处理'],
'review': ['review', '待审查'],
'ready': ['ready', '待发布'],
'published': ['published', '已发布']
};
return map[status] ? map[status].includes(row.status) : row.status === status;
}
},
mounted() {