refactor: unify frontend source; sync latest UI changes and update project completion report
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
| users.html | 删除用户 | DELETE /api/admin/users/{id} | ✅ | 已实现 |
|
||||
| users.html | 新增用户 | POST /api/admin/users | ✅ | 已实现 |
|
||||
|
||||
*系统概览模块状态已改为动态展示,显示内容收集、内容创作、合规审查三模块(原发布管理器已移除)。*
|
||||
|
||||
## 🔧 后端API状态
|
||||
|
||||
### 路由注册 (app/main.py)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/* 深色渐变背景主题 */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; color: #303133; }
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
/* 深色渐变: #1a1a2e → #16213e → #0f3460 */
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
min-height: 100vh;
|
||||
@@ -321,22 +321,6 @@
|
||||
.module-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</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>
|
||||
<body>
|
||||
<div id="app">
|
||||
@@ -410,48 +394,17 @@
|
||||
🔧 模块状态
|
||||
</h3>
|
||||
<div class="module-grid">
|
||||
<div class="module-card">
|
||||
<div class="module-card" v-for="mod in moduleList" :key="mod.key">
|
||||
<div class="module-header">
|
||||
<span class="module-title">🤖 内容创作引擎</span>
|
||||
<span class="module-status running">运行中</span>
|
||||
<span class="module-title">{{ mod.title }}</span>
|
||||
<span class="module-status" :class="{ running: pipelineModules[mod.key].exists }">
|
||||
{{ pipelineModules[mod.key].exists ? '运行中' : '未运行' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="module-content">
|
||||
<div><span>最后运行</span><span>2026-04-27 14:30</span></div>
|
||||
<div><span>今日任务</span><span>12 个</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><span>最后运行</span><span>{{ formatDateTime(pipelineModules[mod.key].last_run) }}</span></div>
|
||||
<div><span>状态</span><span>{{ pipelineModules[mod.key].exists ? '运行中' : '未运行' }}</span></div>
|
||||
<div><span>模块</span><span>{{ mod.desc }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -478,12 +431,21 @@
|
||||
|
||||
<script src="/static/vue/vue.global.js"></script>
|
||||
<script src="/static/element-plus/index.full.min.js"></script>
|
||||
<script src="/static/element-plus-icons/index.full.js"></script>
|
||||
<script>
|
||||
const App = {
|
||||
data() {
|
||||
return {
|
||||
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,
|
||||
currentUser: { username: '' },
|
||||
currentPage: 'overview',
|
||||
@@ -515,6 +477,7 @@
|
||||
this.isLoggedIn = true;
|
||||
this.currentPage = 'overview';
|
||||
this.fetchStats();
|
||||
this.fetchPipelineStatus();
|
||||
} catch (error) {
|
||||
this.loginError = '用户名或密码错误';
|
||||
} finally {
|
||||
@@ -569,6 +532,31 @@
|
||||
redirectToPage(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() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
@@ -586,6 +574,7 @@
|
||||
this.isLoggedIn = true;
|
||||
this.currentPage = 'overview';
|
||||
this.fetchStats();
|
||||
this.fetchPipelineStatus();
|
||||
})
|
||||
.catch(() => {
|
||||
localStorage.removeItem('authToken');
|
||||
|
||||
+28
-105
@@ -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: 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); }
|
||||
.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); }
|
||||
.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,7 +36,6 @@
|
||||
.content-area { padding: 16px; padding-bottom: 80px; }
|
||||
}
|
||||
|
||||
.topic-card-list { display: none; }
|
||||
/* 移动端卡片布局 */
|
||||
@media (max-width: 768px) {
|
||||
.el-table { font-size: 12px; display: none; }
|
||||
@@ -46,7 +45,7 @@
|
||||
.topic-card-list { display: block; margin: 0 -16px; }
|
||||
.topic-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
@@ -100,7 +99,7 @@
|
||||
</aside>
|
||||
<main class="content-area">
|
||||
<div class="card">
|
||||
<h2 style="font-size: 28px; font-weight: 700; margin-bottom: 32px; color: #303133;">📋 选题管理</h2>
|
||||
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 24px;">📋 选题管理</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>
|
||||
@@ -110,11 +109,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap;">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<div class="card" style="width: 100%; overflow-x: auto; padding: 16px;">
|
||||
<el-table :data="filteredTopics" stripe v-loading="loadingTable" @selection-change="selectedTopicIds = $event">
|
||||
@@ -131,13 +130,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="180" fixed="right">
|
||||
<el-table-column label="操作" width="280" 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="!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="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="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -145,9 +144,9 @@
|
||||
</el-table>
|
||||
<!-- 移动端卡片列表 -->
|
||||
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
|
||||
<div v-for="(topic, index) in filteredTopics" :key="topic.id" class="topic-card">
|
||||
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
|
||||
<div class="topic-card-header">
|
||||
<div class="topic-card-title">{{ topic.id }}. {{ topic.title }}</div>
|
||||
<div class="topic-card-title">{{ topic.title }}</div>
|
||||
<el-tag :type="getStatusType(topic.status)" size="small">{{ topic.status }}</el-tag>
|
||||
</div>
|
||||
<div class="topic-card-tags">
|
||||
@@ -161,9 +160,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="!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="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="danger" @click="deleteTopic(topic.id)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,29 +178,6 @@
|
||||
<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>
|
||||
@@ -218,36 +194,17 @@ const TopicsApp = {
|
||||
selectedTopicIds: [],
|
||||
filterStatus: '',
|
||||
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
|
||||
topics: [],
|
||||
previewVisible: false,
|
||||
previewTopic: null
|
||||
topics: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredTopics() {
|
||||
filteredTopics() {
|
||||
if (!this.topics || !this.topics.length) { return []; }
|
||||
if (!this.filterStatus) { return this.topics; }
|
||||
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));
|
||||
return this.topics.filter(t => t.status === this.filterStatus);
|
||||
},
|
||||
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
|
||||
};
|
||||
countByStatus() {
|
||||
return (status) => this.topics.filter(t => t.status === status).length;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -292,25 +249,9 @@ const TopicsApp = {
|
||||
this.selectedTopicIds = [];
|
||||
await this.fetchTopics();
|
||||
},
|
||||
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('复制失败,请手动复制');
|
||||
});
|
||||
},
|
||||
openPreview(topic) { this.$message.info('预览:' + topic.title); },
|
||||
async createTopic(topic) {
|
||||
if (!this.isStatus(topic, 'pending')) {
|
||||
if (topic.status !== 'pending') {
|
||||
this.$message.info('仅待处理选题可创作');
|
||||
return;
|
||||
}
|
||||
@@ -341,7 +282,7 @@ const TopicsApp = {
|
||||
}
|
||||
},
|
||||
async optimizeTopic(topic) {
|
||||
if (!this.isStatus(topic, 'review')) {
|
||||
if (topic.status !== 'review') {
|
||||
this.$message.info('仅待审查选题可优化');
|
||||
return;
|
||||
}
|
||||
@@ -372,7 +313,7 @@ const TopicsApp = {
|
||||
}
|
||||
},
|
||||
async handlePublish(topic) {
|
||||
if (!this.isStatus(topic, 'ready')) {
|
||||
if (topic.status !== 'ready') {
|
||||
this.$message.info('仅待发布选题可发布');
|
||||
return;
|
||||
}
|
||||
@@ -441,26 +382,8 @@ const TopicsApp = {
|
||||
} catch (e) { return dateStr; }
|
||||
},
|
||||
getStatusType(status) {
|
||||
const map = {
|
||||
'pending': 'warning',
|
||||
'review': 'danger',
|
||||
'ready': 'success',
|
||||
'published': 'info',
|
||||
'待处理': 'warning',
|
||||
'待审查': 'danger',
|
||||
'待发布': 'success',
|
||||
'已发布': 'info'
|
||||
};
|
||||
const map = { 'pending': 'warning', 'review': 'danger', 'ready': 'success', 'published': '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() {
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
.content-area { padding: 16px; padding-bottom: 80px; }
|
||||
}
|
||||
|
||||
.user-card-list { display: none; }
|
||||
/* users.html 移动端优化 */
|
||||
@media (max-width: 768px) {
|
||||
.user-table { display: none; }
|
||||
@@ -205,15 +204,7 @@
|
||||
.catch(() => {});
|
||||
},
|
||||
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
|
||||
redirectToPage(page) {
|
||||
if (!page) return;
|
||||
// 处理绝对URL或相对路径
|
||||
if (page.startsWith('/') || page.startsWith('http://') || page.startsWith('https://')) {
|
||||
window.location.href = page;
|
||||
} else {
|
||||
window.location.href = '/' + page;
|
||||
}
|
||||
},
|
||||
redirectToPage(page) { 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' }); }
|
||||
},
|
||||
mounted() {
|
||||
|
||||
Reference in New Issue
Block a user