feat: 内容数据迁移至数据库,合规审查全链路打通
- 文章 HTML 存储从文件系统迁移至 articles 表,删除 releases 目录 - 合规审查从 DB 读取 HTML,审查结果写回 DB,通过后自动推进至待发布 - 新增 todayCount 筛选按钮,与系统概览统计数据一致 - 全屏预览修复:提升 z-index 超过侧边栏,添加退出全屏/关闭按钮 - 统一 '优化' → '审查' 命名,消除前后端术语不一致 - 调度器创作完成后自动触发审查(生成 → 审查 → 待发布) - 清理旧备份/调试文件、过期大纲和研究笔记
This commit is contained in:
+249
-144
@@ -5,29 +5,31 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宇之然内容创作平台 - 系统管理</title>
|
||||
<link rel="stylesheet" href="element-plus.css">
|
||||
<link rel="stylesheet" href="theme-modern.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; color: #303133; }
|
||||
.navbar { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: white; padding: 16px 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
||||
.navbar-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
|
||||
.navbar-title { font-size: 20px; font-weight: 600; }
|
||||
.navbar-user { display: flex; align-items: center; gap: 16px; }
|
||||
.user-info { display: flex; align-items: center; gap: 8px; }
|
||||
.avatar { width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
|
||||
.main-content { display: flex; flex: 1; max-width: 1400px; margin: 0 auto; min-height: calc(100vh - 60px); }
|
||||
.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); overflow-x: auto; }
|
||||
.mobile-nav { display: none; position: fixed; bottom: 0; left: 0; right: 0; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.1); padding: 8px 0; z-index: 1000; }
|
||||
.mobile-nav-btn { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; background: none; border: none; font-size: 10px; color: #909399; cursor: pointer; padding: 4px; }
|
||||
.mobile-nav-btn.active { color: #409eff; }
|
||||
.card { background: white; border-radius: 16px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0,0,0,0.06); overflow-x: auto; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
|
||||
.card-loading { display: flex; justify-content: center; align-items: center; min-height: 200px; color: #909399; font-size: 14px; }
|
||||
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #909399; }
|
||||
.empty-state .empty-icon { font-size: 48px; margin-bottom: 12px; opacity: 0.4; }
|
||||
.empty-state .empty-text { font-size: 14px; margin-bottom: 16px; }
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 12px; }
|
||||
.page-title { font-size: 24px; font-weight: 700; color: #303133; display: flex; align-items: center; gap: 8px; }
|
||||
.filter-bar { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; }
|
||||
.toolbar { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||
.el-table { width: 100%; }
|
||||
.el-table .el-table__cell { word-break: break-word; }
|
||||
.stat-summary { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
|
||||
.stat-item { background: #f0f5ff; border-radius: 8px; padding: 12px 20px; display: flex; flex-direction: column; align-items: center; min-width: 100px; }
|
||||
.stat-item .num { font-size: 24px; font-weight: 600; color: #409eff; }
|
||||
.stat-item .label { font-size: 12px; color: #909399; margin-top: 4px; }
|
||||
|
||||
.mobile-card-list { display: none; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.mobile-nav { display: flex; }
|
||||
.content-area { padding: 16px; padding-bottom: 80px; }
|
||||
.card { padding: 16px; }
|
||||
.data-table { display: none; }
|
||||
@@ -46,6 +48,9 @@
|
||||
.mobile-card-label { color: #909399; flex-shrink: 0; margin-right: 8px; }
|
||||
.mobile-card-value { color: #303133; text-align: right; word-break: break-word; }
|
||||
.mobile-card-actions { display: flex; gap: 8px; justify-content: flex-end; padding-top: 10px; margin-top: 6px; border-top: 1px solid #ebeef5; }
|
||||
.stat-summary { gap: 8px; }
|
||||
.stat-item { min-width: 70px; padding: 8px 12px; }
|
||||
.stat-item .num { font-size: 18px; }
|
||||
}
|
||||
</style>
|
||||
<script src="navigation-component.js"></script>
|
||||
@@ -57,18 +62,39 @@
|
||||
<navigation-component current-page="admin" :is-admin="isAdmin" @navigate="redirectToPage"></navigation-component>
|
||||
<div class="main-content">
|
||||
<main class="content-area">
|
||||
<div class="card">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="案例管理" name="cases">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="showCaseDialog()">新增案例</el-button>
|
||||
<div class="card page-fade">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">⚙️ 系统管理</h2>
|
||||
<div class="filter-bar">
|
||||
<el-button size="default" :type="activeTab === 'cases' ? 'primary' : ''" @click="switchTab('cases')">案例管理</el-button>
|
||||
<el-button size="default" :type="activeTab === 'tasklogs' ? 'primary' : ''" @click="switchTab('tasklogs')">任务日志</el-button>
|
||||
<el-button size="default" :type="activeTab === 'llmconfigs' ? 'primary' : ''" @click="switchTab('llmconfigs')">LLM配置</el-button>
|
||||
<el-button size="default" :type="activeTab === 'systemconfigs' ? 'primary' : ''" @click="switchTab('systemconfigs')">系统配置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'cases'">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" size="small" @click="showCaseDialog()">新增案例</el-button>
|
||||
</div>
|
||||
<div v-if="casesLoading" class="card-loading">加载中...</div>
|
||||
<template v-else-if="cases.length === 0">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">📋</div>
|
||||
<div class="empty-text">暂无案例数据</div>
|
||||
<el-button type="primary" size="small" @click="showCaseDialog()">新增第一个案例</el-button>
|
||||
</div>
|
||||
<el-table :data="cases" border stripe class="data-table">
|
||||
<el-table-column prop="id" label="ID" width="70"/>
|
||||
<el-table-column prop="title" label="标题" min-width="150"/>
|
||||
<el-table-column prop="field" label="领域" width="100"/>
|
||||
<el-table-column prop="summary" label="概述" min-width="200" :show-overflow-tooltip="true"/>
|
||||
<el-table-column prop="source" label="来源" width="100"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="stat-summary">
|
||||
<div class="stat-item"><span class="num">{{ cases.length }}</span><span class="label">总案例</span></div>
|
||||
</div>
|
||||
<el-table :data="cases" border stripe class="data-table" style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="70"></el-table-column>
|
||||
<el-table-column prop="title" label="标题" min-width="120" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="field" label="领域" width="100"></el-table-column>
|
||||
<el-table-column prop="summary" label="概述" min-width="200" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="source" label="来源" width="100"></el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="showCaseDialog(scope.row)">编辑</el-button>
|
||||
@@ -76,52 +102,73 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in cases" :key="item.id" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">标题</span><span class="mobile-card-value">{{ item.title }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">领域</span><span class="mobile-card-value">{{ item.field }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">来源</span><span class="mobile-card-value">{{ item.source }}</span></div>
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" @click="showCaseDialog(item)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCase(item.id)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in cases" :key="item.id" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">标题</span><span class="mobile-card-value">{{ item.title }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">领域</span><span class="mobile-card-value">{{ item.field }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">来源</span><span class="mobile-card-value">{{ item.source }}</span></div>
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" @click="showCaseDialog(item)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCase(item.id)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="任务日志" name="tasklogs">
|
||||
<div class="toolbar">
|
||||
<el-button @click="loadTaskLogs()">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'tasklogs'">
|
||||
<div class="toolbar">
|
||||
<el-button size="small" @click="loadTaskLogs()">刷新</el-button>
|
||||
</div>
|
||||
<div v-if="taskLogsLoading" class="card-loading">加载中...</div>
|
||||
<template v-else-if="taskLogs.length === 0">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">📝</div>
|
||||
<div class="empty-text">暂无任务日志</div>
|
||||
</div>
|
||||
<el-table :data="taskLogs" border stripe class="data-table">
|
||||
<el-table-column prop="id" label="ID" width="70"/>
|
||||
<el-table-column prop="task_name" label="任务名称" min-width="120"/>
|
||||
<el-table-column prop="topic_id" label="选题" width="100"/>
|
||||
<el-table-column prop="status" label="状态" width="80"/>
|
||||
<el-table-column prop="message" label="消息" min-width="150" :show-overflow-tooltip="true"/>
|
||||
<el-table-column prop="started_at" label="开始" width="150"/>
|
||||
<el-table-column prop="finished_at" label="结束" width="150"/>
|
||||
<el-table-column prop="duration" label="耗时" width="80"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table :data="taskLogs" border stripe class="data-table" style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="70"></el-table-column>
|
||||
<el-table-column prop="task_name" label="任务名称" min-width="120"></el-table-column>
|
||||
<el-table-column prop="topic_id" label="选题" width="100"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80"></el-table-column>
|
||||
<el-table-column prop="message" label="消息" min-width="150" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="started_at" label="开始" width="150"></el-table-column>
|
||||
<el-table-column prop="finished_at" label="结束" width="150"></el-table-column>
|
||||
<el-table-column prop="duration" label="耗时" width="80"></el-table-column>
|
||||
</el-table>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in taskLogs" :key="item.id" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">任务</span><span class="mobile-card-value">{{ item.task_name }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">选题</span><span class="mobile-card-value">{{ item.topic_id }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">状态</span><span class="mobile-card-value">{{ item.status }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">消息</span><span class="mobile-card-value">{{ item.message }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">耗时</span><span class="mobile-card-value">{{ item.duration }}s</span></div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in taskLogs" :key="item.id" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">任务</span><span class="mobile-card-value">{{ item.task_name }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">选题</span><span class="mobile-card-value">{{ item.topic_id }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">状态</span><span class="mobile-card-value">{{ item.status }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">消息</span><span class="mobile-card-value">{{ item.message }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">耗时</span><span class="mobile-card-value">{{ item.duration }}s</span></div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="LLM配置" name="llmconfigs">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="showLLMConfigDialog()">新增配置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'llmconfigs'">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" size="small" @click="showLLMConfigDialog()">新增配置</el-button>
|
||||
</div>
|
||||
<div v-if="llmConfigsLoading" class="card-loading">加载中...</div>
|
||||
<template v-else-if="llmConfigs.length === 0">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">🤖</div>
|
||||
<div class="empty-text">暂无 LLM 配置</div>
|
||||
<el-button type="primary" size="small" @click="showLLMConfigDialog()">新增配置</el-button>
|
||||
</div>
|
||||
<el-table :data="llmConfigs" border stripe class="data-table">
|
||||
<el-table-column prop="id" label="ID" width="70"/>
|
||||
<el-table-column prop="name" label="名称" min-width="120"/>
|
||||
<el-table-column prop="model" label="模型" min-width="180"/>
|
||||
<el-table-column prop="temperature" label="温度" width="80"/>
|
||||
<el-table-column prop="max_tokens" label="最大Token" width="110"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table :data="llmConfigs" border stripe class="data-table" style="width:100%">
|
||||
<el-table-column prop="id" label="ID" width="70"></el-table-column>
|
||||
<el-table-column prop="name" label="名称" min-width="120"></el-table-column>
|
||||
<el-table-column prop="model" label="模型" min-width="180"></el-table-column>
|
||||
<el-table-column prop="temperature" label="温度" width="80"></el-table-column>
|
||||
<el-table-column prop="max_tokens" label="最大Token" width="110"></el-table-column>
|
||||
<el-table-column prop="is_active" label="激活" width="70">
|
||||
<template #default="scope">{{ scope.row.is_active ? '是' : '否' }}</template>
|
||||
</el-table-column>
|
||||
@@ -132,31 +179,42 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in llmConfigs" :key="item.id" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">名称</span><span class="mobile-card-value">{{ item.name }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">模型</span><span class="mobile-card-value">{{ item.model }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">温度</span><span class="mobile-card-value">{{ item.temperature }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">激活</span><span class="mobile-card-value">{{ item.is_active ? '是' : '否' }}</span></div>
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" @click="showLLMConfigDialog(item)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteLLMConfig(item.id)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in llmConfigs" :key="item.id" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">名称</span><span class="mobile-card-value">{{ item.name }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">模型</span><span class="mobile-card-value">{{ item.model }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">温度</span><span class="mobile-card-value">{{ item.temperature }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">激活</span><span class="mobile-card-value">{{ item.is_active ? '是' : '否' }}</span></div>
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" @click="showLLMConfigDialog(item)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteLLMConfig(item.id)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="系统配置" name="systemconfigs">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" @click="showSystemConfigDialog()">新增配置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'systemconfigs'">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" size="small" @click="showSystemConfigDialog()">新增配置</el-button>
|
||||
</div>
|
||||
<div v-if="systemConfigsLoading" class="card-loading">加载中...</div>
|
||||
<template v-else-if="systemConfigs.length === 0">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">⚙️</div>
|
||||
<div class="empty-text">暂无系统配置</div>
|
||||
<el-button type="primary" size="small" @click="showSystemConfigDialog()">新增配置</el-button>
|
||||
</div>
|
||||
<el-table :data="systemConfigs" border stripe class="data-table">
|
||||
<el-table-column prop="key" label="键" min-width="180"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table :data="systemConfigs" border stripe class="data-table" style="width:100%">
|
||||
<el-table-column prop="key" label="键" min-width="180"></el-table-column>
|
||||
<el-table-column prop="value" label="值" min-width="250">
|
||||
<template #default="scope">
|
||||
{{ typeof scope.row.value === 'object' ? JSON.stringify(scope.row.value) : scope.row.value }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="描述" min-width="200"/>
|
||||
<el-table-column prop="description" label="描述" min-width="200"></el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="showSystemConfigDialog(scope.row)">编辑</el-button>
|
||||
@@ -164,34 +222,44 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in systemConfigs" :key="item.key" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">键</span><span class="mobile-card-value">{{ item.key }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">值</span><span class="mobile-card-value">{{ typeof item.value === 'object' ? JSON.stringify(item.value) : item.value }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">描述</span><span class="mobile-card-value">{{ item.description }}</span></div>
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" @click="showSystemConfigDialog(item)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteSystemConfig(item.key)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mobile-card-list">
|
||||
<div v-for="item in systemConfigs" :key="item.key" class="mobile-card">
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">键</span><span class="mobile-card-value">{{ item.key }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">值</span><span class="mobile-card-value">{{ typeof item.value === 'object' ? JSON.stringify(item.value) : item.value }}</span></div>
|
||||
<div class="mobile-card-row"><span class="mobile-card-label">描述</span><span class="mobile-card-value">{{ item.description }}</span></div>
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" @click="showSystemConfigDialog(item)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteSystemConfig(item.key)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="caseDialogVisible" :title="caseDialogTitle" width="600px">
|
||||
<el-dialog v-model="caseDialogVisible" :title="caseDialogTitle" width="700px" :close-on-click-modal="false">
|
||||
<el-form :model="caseForm" label-width="80px">
|
||||
<el-form-item label="标题"><el-input v-model="caseForm.title"/></el-form-item>
|
||||
<el-form-item label="领域"><el-input v-model="caseForm.field"/></el-form-item>
|
||||
<el-form-item label="概述"><el-input type="textarea" v-model="caseForm.summary"/></el-form-item>
|
||||
<el-form-item label="关键指标"><el-input v-model="caseForm.key_metrics"/></el-form-item>
|
||||
<el-form-item label="日期"><el-input v-model="caseForm.date"/></el-form-item>
|
||||
<el-form-item label="来源"><el-input v-model="caseForm.source"/></el-form-item>
|
||||
<el-form-item label="来源URL"><el-input v-model="caseForm.source_url"/></el-form-item>
|
||||
<el-form-item label="可信度"><el-input v-model="caseForm.credibility_rating"/></el-form-item>
|
||||
<el-form-item label="国内适用性"><el-input v-model="caseForm.china_applicability"/></el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="标题"><el-input v-model="caseForm.title"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="领域"><el-input v-model="caseForm.field"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24"><el-form-item label="概述"><el-input type="textarea" v-model="caseForm.summary" :rows="3"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="关键指标"><el-input v-model="caseForm.key_metrics"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="日期"><el-input v-model="caseForm.date"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="来源"><el-input v-model="caseForm.source"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="来源URL"><el-input v-model="caseForm.source_url"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="可信度"><el-input v-model="caseForm.credibility_rating"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="国内适用性"><el-input v-model="caseForm.china_applicability"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="caseDialogVisible=false">取消</el-button>
|
||||
@@ -199,17 +267,25 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="llmConfigDialogVisible" :title="llmConfigDialogTitle" width="600px">
|
||||
<el-form :model="llmConfigForm" label-width="120px">
|
||||
<el-form-item label="名称"><el-input v-model="llmConfigForm.name"/></el-form-item>
|
||||
<el-form-item label="系统提示词"><el-input type="textarea" v-model="llmConfigForm.system_prompt"/></el-form-item>
|
||||
<el-form-item label="用户提示模板"><el-input type="textarea" v-model="llmConfigForm.user_prompt_template"/></el-form-item>
|
||||
<el-form-item label="温度"><el-input-number v-model="llmConfigForm.temperature" :min="0" :max="2" :step="0.1"/></el-form-item>
|
||||
<el-form-item label="最大Token"><el-input-number v-model="llmConfigForm.max_tokens" :min="1" :max="10000"/></el-form-item>
|
||||
<el-form-item label="模型"><el-input v-model="llmConfigForm.model"/></el-form-item>
|
||||
<el-form-item label="激活">
|
||||
<el-switch v-model="llmConfigForm.is_active"/>
|
||||
</el-form-item>
|
||||
<el-dialog v-model="llmConfigDialogVisible" :title="llmConfigDialogTitle" width="700px" :close-on-click-modal="false">
|
||||
<el-form :model="llmConfigForm" label-width="100px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="名称"><el-input v-model="llmConfigForm.name"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="模型"><el-input v-model="llmConfigForm.model"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="温度"><el-input-number v-model="llmConfigForm.temperature" :min="0" :max="2" :step="0.1"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="最大Token"><el-input-number v-model="llmConfigForm.max_tokens" :min="1" :max="10000"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24"><el-form-item label="系统提示词"><el-input type="textarea" v-model="llmConfigForm.system_prompt" :rows="4"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24"><el-form-item label="提示模板"><el-input type="textarea" v-model="llmConfigForm.user_prompt_template" :rows="4"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24"><el-form-item label="激活"><el-switch v-model="llmConfigForm.is_active"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="llmConfigDialogVisible=false">取消</el-button>
|
||||
@@ -217,11 +293,15 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="systemConfigDialogVisible" :title="systemConfigDialogTitle" width="500px">
|
||||
<el-form :model="systemConfigForm" label-width="100px">
|
||||
<el-form-item label="键"><el-input v-model="systemConfigForm.key" :disabled="!!editingSystemConfigKey"/></el-form-item>
|
||||
<el-form-item label="值"><el-input v-model="systemConfigForm.value" type="textarea"/></el-form-item>
|
||||
<el-form-item label="描述"><el-input v-model="systemConfigForm.description"/></el-form-item>
|
||||
<el-dialog v-model="systemConfigDialogVisible" :title="systemConfigDialogTitle" width="600px" :close-on-click-modal="false">
|
||||
<el-form :model="systemConfigForm" label-width="80px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="键"><el-input v-model="systemConfigForm.key" :disabled="!!editingSystemConfigKey"/></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="描述"><el-input v-model="systemConfigForm.description"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24"><el-form-item label="值"><el-input v-model="systemConfigForm.value" type="textarea" :rows="4"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="systemConfigDialogVisible=false">取消</el-button>
|
||||
@@ -232,7 +312,7 @@
|
||||
<script src="vue.global.prod.js"></script>
|
||||
<script src="element-plus.full.js"></script>
|
||||
<script>
|
||||
const { createApp, ref, reactive, onMounted, watch } = Vue;
|
||||
const { createApp, ref, reactive, onMounted } = Vue;
|
||||
const { ElMessage, ElMessageBox } = ElementPlus;
|
||||
|
||||
const app = createApp({
|
||||
@@ -240,38 +320,42 @@
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) { window.location.href = 'login.html'; return {}; }
|
||||
|
||||
const apiBase = '';
|
||||
const api = {
|
||||
get: (url) => fetch(url, { headers: { 'Authorization': `Bearer ${token}` } }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
post: (url, body) => fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(body) }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
put: (url, body) => fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(body) }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
delete: (url) => fetch(url, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
get: (url) => fetch(apiBase + url, { headers: { 'Authorization': `Bearer ${token}` } }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
post: (url, body) => fetch(apiBase + url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(body) }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
put: (url, body) => fetch(apiBase + url, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(body) }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
delete: (url) => fetch(apiBase + url, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }).then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); }),
|
||||
};
|
||||
|
||||
const activeTab = ref('cases');
|
||||
const currentUser = ref({ username: '' });
|
||||
const isAdmin = ref(false);
|
||||
const isLoggedIn = ref(false);
|
||||
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
|
||||
.then(response => response.ok ? response.json() : Promise.reject())
|
||||
.then(r => r.ok ? r.json() : Promise.reject())
|
||||
.then(data => {
|
||||
const user = data.user || { username: '', role: 'user' };
|
||||
currentUser.value = user;
|
||||
isAdmin.value = user.role === 'admin';
|
||||
isLoggedIn.value = true;
|
||||
if (!isAdmin.value) { ElMessage.warning('需要管理员权限'); window.location.href = '/login.html'; }
|
||||
})
|
||||
.catch(() => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; });
|
||||
.catch(() => { localStorage.removeItem('authToken'); window.location.href = '/login.html'; });
|
||||
|
||||
const redirectToPage = (page) => { window.location.href = '/' + page; };
|
||||
|
||||
const cases = ref([]);
|
||||
const casesLoading = ref(false);
|
||||
const caseDialogVisible = ref(false);
|
||||
const caseDialogTitle = ref('新增案例');
|
||||
const caseForm = reactive({ id: null, title: '', field: '', summary: '', key_metrics: '', date: '', source: '', source_url: '', credibility_rating: '', china_applicability: '' });
|
||||
const editingCaseId = ref(null);
|
||||
|
||||
const loadCases = async () => { try { cases.value = await api.get('/api/admin/cases'); } catch (e) { ElMessage.error('加载案例失败: ' + e.message); } };
|
||||
const loadCases = async () => {
|
||||
casesLoading.value = true;
|
||||
try { cases.value = await api.get('/api/admin/cases'); } catch (e) { ElMessage.error('加载案例失败: ' + e.message); }
|
||||
finally { casesLoading.value = false; }
|
||||
};
|
||||
const showCaseDialog = (row = null) => {
|
||||
if (row) { caseDialogTitle.value = '编辑案例'; editingCaseId.value = row.id; Object.assign(caseForm, row); }
|
||||
else { caseDialogTitle.value = '新增案例'; editingCaseId.value = null; Object.keys(caseForm).forEach(k => { if (k === 'id') caseForm.id = null; else caseForm[k] = ''; }); }
|
||||
@@ -290,15 +374,25 @@
|
||||
};
|
||||
|
||||
const taskLogs = ref([]);
|
||||
const loadTaskLogs = async () => { try { taskLogs.value = await api.get('/api/admin/tasklogs'); } catch (e) { ElMessage.error('加载任务日志失败: ' + e.message); } };
|
||||
const taskLogsLoading = ref(false);
|
||||
const loadTaskLogs = async () => {
|
||||
taskLogsLoading.value = true;
|
||||
try { taskLogs.value = await api.get('/api/admin/tasklogs'); } catch (e) { ElMessage.error('加载任务日志失败: ' + e.message); }
|
||||
finally { taskLogsLoading.value = false; }
|
||||
};
|
||||
|
||||
const llmConfigs = ref([]);
|
||||
const llmConfigsLoading = ref(false);
|
||||
const llmConfigDialogVisible = ref(false);
|
||||
const llmConfigDialogTitle = ref('新增配置');
|
||||
const llmConfigForm = reactive({ id: null, name: '', system_prompt: '', user_prompt_template: '', temperature: 0.7, max_tokens: 2000, model: '', is_active: true });
|
||||
const editingLLMConfigId = ref(null);
|
||||
|
||||
const loadLLMConfigs = async () => { try { llmConfigs.value = await api.get('/api/admin/llmconfigs'); } catch (e) { ElMessage.error('加载LLM配置失败: ' + e.message); } };
|
||||
const loadLLMConfigs = async () => {
|
||||
llmConfigsLoading.value = true;
|
||||
try { llmConfigs.value = await api.get('/api/admin/llmconfigs'); } catch (e) { ElMessage.error('加载LLM配置失败: ' + e.message); }
|
||||
finally { llmConfigsLoading.value = false; }
|
||||
};
|
||||
const showLLMConfigDialog = (row = null) => {
|
||||
if (row) { llmConfigDialogTitle.value = '编辑配置'; editingLLMConfigId.value = row.id; Object.assign(llmConfigForm, row); }
|
||||
else {
|
||||
@@ -320,12 +414,17 @@
|
||||
};
|
||||
|
||||
const systemConfigs = ref([]);
|
||||
const systemConfigsLoading = ref(false);
|
||||
const systemConfigDialogVisible = ref(false);
|
||||
const systemConfigDialogTitle = ref('新增配置');
|
||||
const systemConfigForm = reactive({ key: '', value: '', description: '' });
|
||||
const editingSystemConfigKey = ref(null);
|
||||
|
||||
const loadSystemConfigs = async () => { try { systemConfigs.value = await api.get('/api/admin/systemconfigs'); } catch (e) { ElMessage.error('加载系统配置失败: ' + e.message); } };
|
||||
const loadSystemConfigs = async () => {
|
||||
systemConfigsLoading.value = true;
|
||||
try { systemConfigs.value = await api.get('/api/admin/systemconfigs'); } catch (e) { ElMessage.error('加载系统配置失败: ' + e.message); }
|
||||
finally { systemConfigsLoading.value = false; }
|
||||
};
|
||||
const showSystemConfigDialog = (row = null) => {
|
||||
if (row) { systemConfigDialogTitle.value = '编辑配置'; editingSystemConfigKey.value = row.key; systemConfigForm.key = row.key; systemConfigForm.value = (typeof row.value === 'object') ? JSON.stringify(row.value) : (row.value || ''); systemConfigForm.description = row.description || ''; }
|
||||
else { systemConfigDialogTitle.value = '新增配置'; editingSystemConfigKey.value = null; systemConfigForm.key = ''; systemConfigForm.value = ''; systemConfigForm.description = ''; }
|
||||
@@ -345,26 +444,32 @@
|
||||
|
||||
const logout = () => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; };
|
||||
|
||||
watch(activeTab, (t) => {
|
||||
if (t === 'cases' && !cases.value.length) loadCases();
|
||||
if (t === 'tasklogs' && !taskLogs.value.length) loadTaskLogs();
|
||||
if (t === 'llmconfigs' && !llmConfigs.value.length) loadLLMConfigs();
|
||||
if (t === 'systemconfigs' && !systemConfigs.value.length) loadSystemConfigs();
|
||||
});
|
||||
const tabLoaders = {
|
||||
cases: loadCases, tasklogs: loadTaskLogs,
|
||||
llmconfigs: loadLLMConfigs, systemconfigs: loadSystemConfigs,
|
||||
};
|
||||
const loadedTabs = new Set();
|
||||
|
||||
const switchTab = (name) => {
|
||||
activeTab.value = name;
|
||||
if (!loadedTabs.has(name)) {
|
||||
loadedTabs.add(name);
|
||||
tabLoaders[name]();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (activeTab.value === 'cases') loadCases();
|
||||
else if (activeTab.value === 'tasklogs') loadTaskLogs();
|
||||
else if (activeTab.value === 'llmconfigs') loadLLMConfigs();
|
||||
else if (activeTab.value === 'systemconfigs') loadSystemConfigs();
|
||||
loadedTabs.add('cases');
|
||||
loadCases();
|
||||
});
|
||||
|
||||
return {
|
||||
activeTab, cases, caseDialogVisible, caseForm, caseDialogTitle, showCaseDialog, saveCase, deleteCase,
|
||||
taskLogs, loadTaskLogs,
|
||||
llmConfigs, llmConfigDialogVisible, llmConfigForm, llmConfigDialogTitle, showLLMConfigDialog, saveLLMConfig, deleteLLMConfig,
|
||||
systemConfigs, systemConfigDialogVisible, systemConfigForm, systemConfigDialogTitle, showSystemConfigDialog, saveSystemConfig, deleteSystemConfig,
|
||||
logout, currentUser, isAdmin, isLoggedIn, redirectToPage
|
||||
activeTab, switchTab,
|
||||
cases, casesLoading, caseDialogVisible, caseForm, caseDialogTitle, showCaseDialog, saveCase, deleteCase,
|
||||
taskLogs, taskLogsLoading, loadTaskLogs,
|
||||
llmConfigs, llmConfigsLoading, llmConfigDialogVisible, llmConfigForm, llmConfigDialogTitle, showLLMConfigDialog, saveLLMConfig, deleteLLMConfig,
|
||||
systemConfigs, systemConfigsLoading, systemConfigDialogVisible, systemConfigForm, systemConfigDialogTitle, showSystemConfigDialog, saveSystemConfig, deleteSystemConfig,
|
||||
logout, currentUser, isAdmin, redirectToPage
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user