Files
yu-zhi-ran/platform/frontend/index.html
T

193 lines
8.7 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>宇之然内容创作平台</title>
<link rel="stylesheet" href="/static/element-plus.css?v=20260427" />
<script src="/static/element-plus.full.js?v=20260427"></script>
<style>
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
.sidebar { width: 160px; position: fixed; height: 100vh; left: 0; top: 0; background: #f5f5f5; border-right: 1px solid #e0e0e0; }
.main-content { margin-left: 160px; width: calc(100vw - 160px); min-height: 100vh; overflow-x: auto; }
@media (max-width: 768px) { .sidebar { display: none; } .main-content { margin-left: 0; width: 100vw; } }
</style>
</head>
<body>
<div id="app">
<nav class="bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg">
<div class="container mx-auto px-6 py-4 flex justify-between items-center">
<h1 class="text-2xl font-bold nav-title">宇之然内容创作平台</h1>
<div class="flex items-center gap-3">
<span class="text-sm">{{ currentUser?.username || '管理员' }}</span>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div>
</div>
</nav>
<div class="page flex gap-6">
<aside class="w-40 flex-shrink-0 hidden md:block sidebar">
<button @click="currentPage = 'overview'" :class="['px-4 py-2 rounded-lg', currentPage === 'overview' ? 'bg-blue-50 text-blue-600 font-semibold' : 'text-gray-600']">📊 系统概览</button>
<button @click="currentPage = 'topics'" :class="['px-4 py-2 rounded-lg', currentPage === 'topics' ? 'bg-blue-50 text-blue-600 font-semibold' : 'text-gray-600']">📋 选题管理</button>
<button @click="currentPage = 'logs'" :class="['px-4 py-2 rounded-lg', currentPage === 'logs' ? 'bg-blue-50 text-blue-600 font-semibold' : 'text-gray-600']">📄 系统日志</button>
<button v-if="isAdmin" @click="currentPage = 'users'" :class="['px-4 py-2 rounded-lg', currentPage === 'users' ? 'bg-blue-50 text-blue-600 font-semibold' : 'text-gray-600']">👥 用户管理</button>
</aside>
<main class="flex-1 main-content">
<!-- 页面内容将通过条件渲染切换 -->
<div v-if="!isLoggedIn" class="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100">
<div class="bg-white p-8 rounded-xl shadow-lg w-full max-w-md">
<h2 class="text-2xl font-bold text-center mb-6 text-blue-600">宇之然内容创作平台</h2>
<div v-if="loginError" class="mb-4 p-3 bg-red-50 text-red-600 rounded text-sm">{{ loginError }}</div>
<el-form @submit.prevent="handleLogin">
<el-form-item label="用户名"><el-input v-model="loginForm.username" placeholder="请输入用户名" prefix-icon="User"></el-input></el-form-item>
<el-form-item label="密码"><el-input v-model="loginForm.password" type="password" placeholder="请输入密码" prefix-icon="Lock" @keyup.enter="handleLogin"></el-input></el-form-item>
<el-button type="primary" class="w-full" @click="handleLogin" :loading="loadingLogin">登 录</el-button>
</el-form>
</div>
</div>
<div v-if="isLoggedIn && currentPage === 'overview'">
<h2 class="text-2xl font-bold mb-6 text-gray-800">📊 系统概览</h2>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
<div class="stat-card card" @click="goToTopicsWithFilter('total')"><div class="stat-value">{{ status.total_topics || 0 }}</div><div class="stat-label">选题总数</div></div>
<div class="stat-card card" @click="goToTopicsWithFilter('待处理')"><div class="stat-value">{{ countByStatus('待处理') }}</div><div class="stat-label">待处理</div></div>
<div class="stat-card card" @click="goToTopicsWithFilter('待审查')"><div class="stat-value">{{ countByStatus('待审查') }}</div><div class="stat-label">待审查</div></div>
<div class="stat-card card" @click="goToTopicsWithFilter('待发布')"><div class="stat-value">{{ countByStatus('待发布') }}</div><div class="stat-label">待发布</div></div>
<div class="stat-card card" @click="goToTopicsWithFilter('已发布')"><div class="stat-value">{{ countByStatus('已发布') }}</div><div class="stat-label">已发布</div></div>
<div class="stat-card card" @click="goToTopicsWithFilter('today')"><div class="stat-value">{{ status.today_articles || 0 }}</div><div class="stat-label">今日新选题</div></div>
</div>
<div class="card">
<h3 class="text-lg font-bold mb-4">🔄 流水线状态</h3>
<div v-if="pipelineLoading" class="text-center py-4">加载中...</div>
<div v-else class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div v-for="mod in pipelineModules" :key="mod.module" class="p-4 bg-gray-50 rounded-lg">
<div class="flex items-center gap-2 mb-2">
<el-tag :type="mod.status_ok ? 'success' : 'danger'" size="small">{{ mod.status_text }}</el-tag>
<span class="font-semibold">{{ mod.module }}</span>
</div>
<div class="text-sm text-gray-500">最后运行:{{ mod.last_run }}</div>
</div>
</div>
</div>
</div>
<div v-if="isLoggedIn && currentPage === 'topics'">
<!-- 选题管理页面内容将在这里 -->
</div>
<div v-if="isLoggedIn && currentPage === 'logs'">
<!-- 系统日志页面内容将在这里 -->
</div>
<div v-if="isLoggedIn && currentPage === 'users' && isAdmin">
<!-- 用户管理页面内容将在这里 -->
</div>
<div v-if="loadingOverlay" class="loading-overlay">
<el-spinner type="spinning" :size="50"></el-spinner>
<p class="ml-4 text-lg">{{ loadingText }}</p>
</div>
</main>
</div>
</div>
<script src="/static/vue.global.prod.js?v=20260427"></script>
<script>
const App = {
data() {
return {
isLoggedIn: true,
currentUser: { username: 'admin' },
isAdmin: true,
currentPage: 'overview',
loginForm: { username: '', password: '' },
loginError: '',
loadingLogin: false,
// 其他数据...
status: {},
topics: [],
filterStatus: '',
selectedTopicIds: [],
generating: false,
optimizing: false,
loadingAll: false,
loadingTable: false,
loadingLogs: false,
loadingUsers: false,
loadingOverlay: false,
loadingText: '',
pipeline: {},
pipelineLoading: false,
pipelineModules: [],
previewVisible: false,
previewTopic: null,
previewPlatform: '',
previewHtml: '',
fullScreenPreview: false,
showLogs: false,
logType: 'creator',
logDate: '',
logContent: '',
users: [],
createTopicModalVisible: false,
newUserForm: {},
publishModalVisible: false,
publishForm: {},
newTopicForm: {}
}
},
computed: {
filteredTopics() {
if (!this.topics.length) return []
if (!this.filterStatus) return this.topics
return this.topics.filter(t => t.status === this.filterStatus)
},
countByStatus() {
return (status) => this.topics.filter(t => t.status === status).length
}
},
methods: {
handleLogout() {
location.reload()
},
goToTopicsWithFilter(status) {
this.currentPage = 'topics'
this.filterStatus = status
},
// 批量操作方法...
refreshAll() {},
triggerGenerateSelected() {},
triggerOptimizeSelected() {},
openPreview(topic) {},
loadPreview() {},
copyPreviewHtml() {},
expandPreview() {},
fetchLogs() {},
createTopic() {},
optimizeTopic() {},
handlePublish() {},
confirmPublish() {},
openCreateTopic() {},
confirmCreateTopic() {},
openCreateUserModal() {},
confirmCreateUser() {},
deleteUser() {},
loadUsers() {},
deleteTopic() {},
// 工具方法...
getPriorityType(score) {},
getStatusClass(status) {},
formatDate(date) {},
refresh() {},
refreshPipeline() {}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>