fix: restore frontend to dynamic version (undo incorrect sync from outdated backend/static)
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
/* 深色渐变背景主题 */
|
/* 深色渐变背景主题 */
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; color: #303133; }
|
||||||
/* 深色渐变: #1a1a2e → #16213e → #0f3460 */
|
/* 深色渐变: #1a1a2e → #16213e → #0f3460 */
|
||||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -321,6 +321,22 @@
|
|||||||
.module-grid { grid-template-columns: 1fr; }
|
.module-grid { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<style>
|
||||||
|
body { background: #f5f7fa !important; color: #303133 !important; }
|
||||||
|
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%) !important; border-bottom: none !important; backdrop-filter: none !important; }
|
||||||
|
.navbar-title { background: none !important; -webkit-text-fill-color: white !important; color: white !important; }
|
||||||
|
.user-info { color: white !important; }
|
||||||
|
.avatar { background: rgba(255,255,255,0.2) !important; color: white !important; }
|
||||||
|
.sidebar { background: white !important; border-right: 1px solid #ebeef5 !important; backdrop-filter: none !important; }
|
||||||
|
.sidebar-btn { color: #606266 !important; }
|
||||||
|
.sidebar-btn:hover, .sidebar-btn.active { background: #ecf5ff !important; color: #409eff !important; }
|
||||||
|
.mobile-nav { background: white !important; border-top: 1px solid #ebeef5 !important; backdrop-filter: none !important; box-shadow: 0 -2px 8px rgba(0,0,0,0.1) !important; }
|
||||||
|
.mobile-nav-btn { color: #606266 !important; }
|
||||||
|
.mobile-nav-btn.active { color: #409eff !important; }
|
||||||
|
.mobile-nav-btn.active::before { content: none !important; display: none !important; }
|
||||||
|
#page-overview h2, #page-overview h3, #page-overview .module-title { color: #303133 !important; }
|
||||||
|
#page-overview .module-content { color: #303133 !important; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
@@ -394,17 +410,48 @@
|
|||||||
🔧 模块状态
|
🔧 模块状态
|
||||||
</h3>
|
</h3>
|
||||||
<div class="module-grid">
|
<div class="module-grid">
|
||||||
<div class="module-card" v-for="mod in moduleList" :key="mod.key">
|
<div class="module-card">
|
||||||
<div class="module-header">
|
<div class="module-header">
|
||||||
<span class="module-title">{{ mod.title }}</span>
|
<span class="module-title">🤖 内容创作引擎</span>
|
||||||
<span class="module-status" :class="{ running: pipelineModules[mod.key].exists }">
|
<span class="module-status running">运行中</span>
|
||||||
{{ pipelineModules[mod.key].exists ? '运行中' : '未运行' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="module-content">
|
<div class="module-content">
|
||||||
<div><span>最后运行</span><span>{{ formatDateTime(pipelineModules[mod.key].last_run) }}</span></div>
|
<div><span>最后运行</span><span>2026-04-27 14:30</span></div>
|
||||||
<div><span>状态</span><span>{{ pipelineModules[mod.key].exists ? '运行中' : '未运行' }}</span></div>
|
<div><span>今日任务</span><span>12 个</span></div>
|
||||||
<div><span>模块</span><span>{{ mod.desc }}</span></div>
|
<div><span>成功率</span><span>95%</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-card">
|
||||||
|
<div class="module-header">
|
||||||
|
<span class="module-title">🔍 内容优化器</span>
|
||||||
|
<span class="module-status running">运行中</span>
|
||||||
|
</div>
|
||||||
|
<div class="module-content">
|
||||||
|
<div><span>最后运行</span><span>2026-04-27 14:45</span></div>
|
||||||
|
<div><span>今日优化</span><span>8 个</span></div>
|
||||||
|
<div><span>平均提升</span><span>+12 分</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-card">
|
||||||
|
<div class="module-header">
|
||||||
|
<span class="module-title">📡 内容收集器</span>
|
||||||
|
<span class="module-status running">运行中</span>
|
||||||
|
</div>
|
||||||
|
<div class="module-content">
|
||||||
|
<div><span>最后运行</span><span>2026-04-27 14:00</span></div>
|
||||||
|
<div><span>今日收集</span><span>24 个</span></div>
|
||||||
|
<div><span>来源平台</span><span>8 个</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="module-card">
|
||||||
|
<div class="module-header">
|
||||||
|
<span class="module-title">📤 发布管理器</span>
|
||||||
|
<span class="module-status running">运行中</span>
|
||||||
|
</div>
|
||||||
|
<div class="module-content">
|
||||||
|
<div><span>最后运行</span><span>2026-04-27 13:30</span></div>
|
||||||
|
<div><span>今日发布</span><span>5 个</span></div>
|
||||||
|
<div><span>成功率</span><span>100%</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -431,21 +478,12 @@
|
|||||||
|
|
||||||
<script src="/static/vue/vue.global.js"></script>
|
<script src="/static/vue/vue.global.js"></script>
|
||||||
<script src="/static/element-plus/index.full.min.js"></script>
|
<script src="/static/element-plus/index.full.min.js"></script>
|
||||||
|
<script src="/static/element-plus-icons/index.full.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const App = {
|
const App = {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
pipelineModules: {
|
|
||||||
collector: { last_run: null, exists: false },
|
|
||||||
creator: { last_run: null, exists: false },
|
|
||||||
optimizer: { last_run: null, exists: false }
|
|
||||||
},
|
|
||||||
moduleList: [
|
|
||||||
{ key: 'collector', title: '📡 内容收集器', desc: '内容收集' },
|
|
||||||
{ key: 'creator', title: '🤖 内容创作引擎', desc: '内容创作' },
|
|
||||||
{ key: 'optimizer', title: '🔍 合规审查', desc: '合规审查' }
|
|
||||||
],
|
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
currentUser: { username: '' },
|
currentUser: { username: '' },
|
||||||
currentPage: 'overview',
|
currentPage: 'overview',
|
||||||
@@ -477,7 +515,6 @@
|
|||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
this.currentPage = 'overview';
|
this.currentPage = 'overview';
|
||||||
this.fetchStats();
|
this.fetchStats();
|
||||||
this.fetchPipelineStatus();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loginError = '用户名或密码错误';
|
this.loginError = '用户名或密码错误';
|
||||||
} finally {
|
} finally {
|
||||||
@@ -532,31 +569,6 @@
|
|||||||
redirectToPage(page) {
|
redirectToPage(page) {
|
||||||
window.location.href = page.startsWith('/') ? page : '/' + page;
|
window.location.href = page.startsWith('/') ? page : '/' + page;
|
||||||
}
|
}
|
||||||
async fetchPipelineStatus() {
|
|
||||||
try {
|
|
||||||
const token = localStorage.getItem('authToken');
|
|
||||||
if (!token) return;
|
|
||||||
const response = await fetch('/api/system/pipeline/status', {
|
|
||||||
headers: { 'Authorization': 'Bearer ' + token }
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.pipeline_modules) {
|
|
||||||
this.pipelineModules = data.pipeline_modules;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('获取流水线状态失败:', response.status);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取流水线状态失败:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formatDateTime(isoStr) {
|
|
||||||
if (!isoStr) return '未运行';
|
|
||||||
const date = new Date(isoStr);
|
|
||||||
const pad = n => n.toString().padStart(2, '0');
|
|
||||||
return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const token = localStorage.getItem('authToken');
|
const token = localStorage.getItem('authToken');
|
||||||
@@ -574,7 +586,6 @@
|
|||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
this.currentPage = 'overview';
|
this.currentPage = 'overview';
|
||||||
this.fetchStats();
|
this.fetchStats();
|
||||||
this.fetchPipelineStatus();
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
localStorage.removeItem('authToken');
|
localStorage.removeItem('authToken');
|
||||||
|
|||||||
+104
-27
@@ -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 { 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:hover { background: #f5f7fa; color: #409eff; }
|
||||||
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
|
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
|
||||||
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
|
.content-area { flex: 1; padding: 32px; 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); }
|
.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-badge { display: inline-flex; align-items: center; gap: 4px; }
|
||||||
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
|
.status-dot { width: 6px; height: 6px; border-radius: 50%; }
|
||||||
.status-dot.pending { background: #E6A23C; }
|
.status-dot.pending { background: #E6A23C; }
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
.content-area { padding: 16px; padding-bottom: 80px; }
|
.content-area { padding: 16px; padding-bottom: 80px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topic-card-list { display: none; }
|
||||||
/* 移动端卡片布局 */
|
/* 移动端卡片布局 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.el-table { font-size: 12px; display: none; }
|
.el-table { font-size: 12px; display: none; }
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
.topic-card-list { display: block; margin: 0 -16px; }
|
.topic-card-list { display: block; margin: 0 -16px; }
|
||||||
.topic-card {
|
.topic-card {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<main class="content-area">
|
<main class="content-area">
|
||||||
<div class="card">
|
<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 class="card" style="display: inline-block; min-width: fit-content; padding: 16px; margin-bottom: 24px;">
|
||||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||||
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
|
<el-button type="primary" size="small" @click="refreshAll">🔄 批量刷新</el-button>
|
||||||
@@ -109,11 +110,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
|
<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-button size="large" :type="filterStatus === '' ? 'primary' : ''" @click="filterStatus = ''">全部 ({{ topics.length }})</el-button>
|
||||||
<el-tag size="large" :type="filterStatus === 'pending' ? 'primary' : ''" @click="filterStatus = 'pending'">待处理 ({{ countByStatus('pending') }})</el-tag>
|
<el-button size="large" :type="filterStatus === 'pending' ? 'primary' : ''" @click="filterStatus = 'pending'">待处理 ({{ statusStats.pending }})</el-button>
|
||||||
<el-tag size="large" :type="filterStatus === 'review' ? 'primary' : ''" @click="filterStatus = 'review'">待审查 ({{ countByStatus('review') }})</el-tag>
|
<el-button size="large" :type="filterStatus === 'review' ? 'primary' : ''" @click="filterStatus = 'review'">待审查 ({{ statusStats.review }})</el-button>
|
||||||
<el-tag size="large" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'">待发布 ({{ countByStatus('ready') }})</el-tag>
|
<el-button size="large" :type="filterStatus === 'ready' ? 'primary' : ''" @click="filterStatus = 'ready'">待发布 ({{ statusStats.ready }})</el-button>
|
||||||
<el-tag size="large" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'">已发布 ({{ countByStatus('published') }})</el-tag>
|
<el-button size="large" :type="filterStatus === 'published' ? 'primary' : ''" @click="filterStatus = 'published'">已发布 ({{ statusStats.published }})</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
|
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
|
||||||
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
|
<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="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="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 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">
|
<template #default="scope">
|
||||||
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
|
<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" @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="success" :disabled="!isStatus(scope.row, '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 size="small" type="warning" :disabled="!isStatus(scope.row, '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 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>
|
<el-button size="small" type="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -144,9 +145,9 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
<!-- 移动端卡片列表 -->
|
<!-- 移动端卡片列表 -->
|
||||||
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
|
<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-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>
|
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic-card-tags">
|
<div class="topic-card-tags">
|
||||||
@@ -160,9 +161,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="topic-card-actions">
|
<div class="topic-card-actions">
|
||||||
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
|
<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="success" :disabled="!isStatus(topic, 'pending')" @click="createTopic(topic)">创作</el-button>
|
||||||
<el-button size="small" type="warning" :disabled="topic.status !== 'review'" @click="optimizeTopic(topic)">审查</el-button>
|
<el-button size="small" type="warning" :disabled="!isStatus(topic, 'review')" @click="optimizeTopic(topic)">审查</el-button>
|
||||||
<el-button v-if="topic.status === 'ready'" size="small" type="primary" @click="handlePublish(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>
|
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,6 +179,29 @@
|
|||||||
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
|
<button class="mobile-nav-btn" @click="redirectToPage('logs.html')">📄 日志</button>
|
||||||
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
|
<button v-if="isAdmin" class="mobile-nav-btn" @click="redirectToPage('users.html')">👥 用户</button>
|
||||||
</nav>
|
</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>
|
</div>
|
||||||
<script src="/static/vue/vue.global.js"></script>
|
<script src="/static/vue/vue.global.js"></script>
|
||||||
<script src="/static/element-plus/index.full.min.js"></script>
|
<script src="/static/element-plus/index.full.min.js"></script>
|
||||||
@@ -194,17 +218,36 @@ const TopicsApp = {
|
|||||||
selectedTopicIds: [],
|
selectedTopicIds: [],
|
||||||
filterStatus: '',
|
filterStatus: '',
|
||||||
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
|
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
|
||||||
topics: []
|
topics: [],
|
||||||
|
previewVisible: false,
|
||||||
|
previewTopic: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredTopics() {
|
filteredTopics() {
|
||||||
if (!this.topics || !this.topics.length) { return []; }
|
if (!this.topics || !this.topics.length) { return []; }
|
||||||
if (!this.filterStatus) { return this.topics; }
|
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() {
|
statusStats() {
|
||||||
return (status) => this.topics.filter(t => t.status === status).length;
|
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: {
|
methods: {
|
||||||
@@ -249,9 +292,25 @@ const TopicsApp = {
|
|||||||
this.selectedTopicIds = [];
|
this.selectedTopicIds = [];
|
||||||
await this.fetchTopics();
|
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) {
|
async createTopic(topic) {
|
||||||
if (topic.status !== 'pending') {
|
if (!this.isStatus(topic, 'pending')) {
|
||||||
this.$message.info('仅待处理选题可创作');
|
this.$message.info('仅待处理选题可创作');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -282,7 +341,7 @@ const TopicsApp = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async optimizeTopic(topic) {
|
async optimizeTopic(topic) {
|
||||||
if (topic.status !== 'review') {
|
if (!this.isStatus(topic, 'review')) {
|
||||||
this.$message.info('仅待审查选题可优化');
|
this.$message.info('仅待审查选题可优化');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -313,7 +372,7 @@ const TopicsApp = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handlePublish(topic) {
|
async handlePublish(topic) {
|
||||||
if (topic.status !== 'ready') {
|
if (!this.isStatus(topic, 'ready')) {
|
||||||
this.$message.info('仅待发布选题可发布');
|
this.$message.info('仅待发布选题可发布');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -382,8 +441,26 @@ const TopicsApp = {
|
|||||||
} catch (e) { return dateStr; }
|
} catch (e) { return dateStr; }
|
||||||
},
|
},
|
||||||
getStatusType(status) {
|
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';
|
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() {
|
mounted() {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
.content-area { padding: 16px; padding-bottom: 80px; }
|
.content-area { padding: 16px; padding-bottom: 80px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-card-list { display: none; }
|
||||||
/* users.html 移动端优化 */
|
/* users.html 移动端优化 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.user-table { display: none; }
|
.user-table { display: none; }
|
||||||
@@ -204,7 +205,15 @@
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
},
|
},
|
||||||
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
|
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
|
||||||
redirectToPage(page) { window.location.href = '/' + page; },
|
redirectToPage(page) {
|
||||||
|
if (!page) return;
|
||||||
|
// 处理绝对URL或相对路径
|
||||||
|
if (page.startsWith('/') || page.startsWith('http://') || page.startsWith('https://')) {
|
||||||
|
window.location.href = page;
|
||||||
|
} else {
|
||||||
|
window.location.href = '/' + page;
|
||||||
|
}
|
||||||
|
},
|
||||||
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' }); }
|
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' }); }
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
Reference in New Issue
Block a user