364 lines
20 KiB
HTML
364 lines
20 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/index.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; }
|
|
.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; width: 100%; }
|
|
.sidebar { width: 180px; background: white; padding: 16px; box-shadow: 2px 0 8px rgba(0,0,0,0.05); }
|
|
.sidebar-btn { width: 100%; text-align: left; padding: 12px 16px; border: none; background: transparent; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: all 0.3s; color: #606266; font-size: 14px; }
|
|
.sidebar-btn:hover { background: #f5f7fa; color: #409eff; }
|
|
.sidebar-btn.active { background: #ecf5ff; color: #409eff; font-weight: 600; }
|
|
.content-area { flex: 1; padding: 24px; overflow-y: auto; }
|
|
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
|
|
.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; border: none; background: transparent; padding: 12px; text-align: center; font-size: 12px; color: #606266; cursor: pointer; }
|
|
.mobile-nav-btn.active { color: #409eff; font-weight: 600; }
|
|
@media (max-width: 768px) {
|
|
.sidebar { display: none; }
|
|
.mobile-nav { display: flex; }
|
|
.content-area { padding: 16px; padding-bottom: 80px; }
|
|
}
|
|
|
|
.user-card-list { display: none; }
|
|
/* users.html 移动端优化 */
|
|
@media (max-width: 768px) {
|
|
.user-table { display: none; }
|
|
.user-card-list { display: block; margin: 0 -16px; }
|
|
.user-card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin-bottom: 12px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
}
|
|
.user-card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
.user-card-name { font-size: 16px; font-weight: 600; }
|
|
.user-card-meta {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
color: #606266;
|
|
margin-bottom: 12px;
|
|
}
|
|
.user-card-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: flex-end;
|
|
padding-top: 8px;
|
|
border-top: 1px solid #ebeef5;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<nav class="navbar">
|
|
<div class="navbar-content">
|
|
<h1 class="navbar-title">宇之然内容创作平台 - 用户管理</h1>
|
|
<div class="navbar-user">
|
|
<div class="user-info"><div class="avatar">{{ currentUser.username.charAt(0).toUpperCase() }}</div><span>{{ currentUser.username }}</span></div>
|
|
<el-button type="danger" size="small" @click="handleLogout">退出</el-button>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<main class="main-content">
|
|
<el-tabs v-model="activeTab" type="border-card">
|
|
<el-tab-pane label="案例管理" name="cases"></el-tab-pane>
|
|
<el-tab-pane label="任务日志" name="tasklogs"></el-tab-pane>
|
|
<el-tab-pane label="LLM配置" name="llmconfigs"></el-tab-pane>
|
|
<el-tab-pane label="系统配置" name="systemconfigs"></el-tab-pane>
|
|
</el-tabs>
|
|
|
|
<!-- Cases Tab -->
|
|
<div v-show="activeTab === 'cases'">
|
|
<div class="toolbar">
|
|
<el-button type="primary" @click="showCaseDialog()">新增案例</el-button>
|
|
</div>
|
|
<el-table :data="cases" border stripe>
|
|
<el-table-column prop="id" label="ID" width="80"/>
|
|
<el-table-column prop="title" label="标题"/>
|
|
<el-table-column prop="field" label="领域"/>
|
|
<el-table-column prop="summary" label="概述" :show-overflow-tooltip="true"/>
|
|
<el-table-column prop="source" label="来源"/>
|
|
<el-table-column label="操作" width="150">
|
|
<template #default="scope">
|
|
<el-button size="small" @click="showCaseDialog(scope.row)">编辑</el-button>
|
|
<el-button size="small" type="danger" @click="deleteCase(scope.row.id)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
|
|
<!-- TaskLogs Tab -->
|
|
<div v-show="activeTab === 'tasklogs'">
|
|
<div class="toolbar">
|
|
<el-button @click="loadTaskLogs()">刷新</el-button>
|
|
</div>
|
|
<el-table :data="taskLogs" border stripe>
|
|
<el-table-column prop="id" label="ID" width="80"/>
|
|
<el-table-column prop="task_name" label="任务名称"/>
|
|
<el-table-column prop="topic_id" label="选题ID"/>
|
|
<el-table-column prop="status" label="状态"/>
|
|
<el-table-column prop="message" label="消息" :show-overflow-tooltip="true"/>
|
|
<el-table-column prop="started_at" label="开始时间"/>
|
|
<el-table-column prop="finished_at" label="结束时间"/>
|
|
<el-table-column prop="duration_seconds" label="耗时(秒)"/>
|
|
</el-table>
|
|
</div>
|
|
|
|
<!-- LLMConfigs Tab -->
|
|
<div v-show="activeTab === 'llmconfigs'">
|
|
<div class="toolbar">
|
|
<el-button type="primary" @click="showLLMConfigDialog()">新增配置</el-button>
|
|
</div>
|
|
<el-table :data="llmConfigs" border stripe>
|
|
<el-table-column prop="id" label="ID" width="80"/>
|
|
<el-table-column prop="name" label="名称"/>
|
|
<el-table-column prop="model" label="模型"/>
|
|
<el-table-column prop="temperature" label="温度"/>
|
|
<el-table-column prop="max_tokens" label="最大Token"/>
|
|
<el-table-column prop="is_active" label="激活">
|
|
<template #default="scope">{ scope.row.is_active ? '是' : '否' }</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="150">
|
|
<template #default="scope">
|
|
<el-button size="small" @click="showLLMConfigDialog(scope.row)">编辑</el-button>
|
|
<el-button size="small" type="danger" @click="deleteLLMConfig(scope.row.id)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
|
|
<!-- SystemConfigs Tab -->
|
|
<div v-show="activeTab === 'systemconfigs'">
|
|
<div class="toolbar">
|
|
<el-button type="primary" @click="showSystemConfigDialog()">新增配置</el-button>
|
|
</div>
|
|
<el-table :data="systemConfigs" border stripe>
|
|
<el-table-column prop="key" label="键"/>
|
|
<el-table-column prop="value" label="值">
|
|
<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="描述"/>
|
|
<el-table-column label="操作" width="150">
|
|
<template #default="scope">
|
|
<el-button size="small" @click="showSystemConfigDialog(scope.row)">编辑</el-button>
|
|
<el-button size="small" type="danger" @click="deleteSystemConfig(scope.row.key)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
|
|
<!-- Cases Dialog -->
|
|
<el-dialog v-model="caseDialogVisible" :title="caseDialogTitle" width="600px">
|
|
<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-form>
|
|
<template #footer>
|
|
<el-button @click="caseDialogVisible=false">取消</el-button>
|
|
<el-button type="primary" @click="saveCase">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- LLMConfig 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-form>
|
|
<template #footer>
|
|
<el-button @click="llmConfigDialogVisible=false">取消</el-button>
|
|
<el-button type="primary" @click="saveLLMConfig">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- SystemConfig 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-form>
|
|
<template #footer>
|
|
<el-button @click="systemConfigDialogVisible=false">取消</el-button>
|
|
<el-button type="primary" @click="saveSystemConfig">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
</main>
|
|
</div>
|
|
<script type="module">
|
|
import { createApp, ref, reactive, onMounted, watch } from 'vue';
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
const app = createApp({
|
|
setup() {
|
|
const token = localStorage.getItem('authToken');
|
|
if (!token) {
|
|
window.location.href = 'login.html';
|
|
return {};
|
|
}
|
|
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(); })
|
|
};
|
|
|
|
const activeTab = ref('cases');
|
|
|
|
// Cases
|
|
const cases = ref([]);
|
|
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 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] = ''; }); }
|
|
caseDialogVisible.value = true;
|
|
};
|
|
const saveCase = async () => {
|
|
try {
|
|
if (editingCaseId.value) { await api.put(`/api/admin/cases/${editingCaseId.value}`, caseForm); ElMessage.success('更新成功'); }
|
|
else { await api.post('/api/admin/cases', caseForm); ElMessage.success('创建成功'); }
|
|
caseDialogVisible.value = false; await loadCases();
|
|
} catch (e) { ElMessage.error('保存失败: ' + e.message); }
|
|
};
|
|
const deleteCase = async (id) => {
|
|
try { await ElMessageBox.confirm('确定删除该案例吗?', '提示', { type: 'warning' }); await api.delete(`/api/admin/cases/${id}`); ElMessage.success('删除成功'); await loadCases(); }
|
|
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
|
|
};
|
|
|
|
// TaskLogs
|
|
const taskLogs = ref([]);
|
|
const loadTaskLogs = async () => {
|
|
try { taskLogs.value = await api.get('/api/admin/tasklogs'); } catch (e) { ElMessage.error('加载任务日志失败: ' + e.message); }
|
|
};
|
|
|
|
// LLMConfigs
|
|
const llmConfigs = ref([]);
|
|
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 showLLMConfigDialog = (row = null) => {
|
|
if (row) { llmConfigDialogTitle.value = '编辑配置'; editingLLMConfigId.value = row.id; Object.assign(llmConfigForm, row); }
|
|
else { llmConfigDialogTitle.value = '新增配置'; editingLLMConfigId.value = null; Object.keys(llmConfigForm).forEach(k => { if (k === 'id') llmConfigForm.id = null; else if (k === 'temperature') llmConfigForm.temperature = 0.7; else if (k === 'max_tokens') llmConfigForm.max_tokens = 2000; else if (k === 'is_active') llmConfigForm.is_active = true; else llmConfigForm[k] = ''; }); }
|
|
llmConfigDialogVisible.value = true;
|
|
};
|
|
const saveLLMConfig = async () => {
|
|
try {
|
|
if (editingLLMConfigId.value) { await api.put(`/api/admin/llmconfigs/${editingLLMConfigId.value}`, llmConfigForm); ElMessage.success('更新成功'); }
|
|
else { await api.post('/api/admin/llmconfigs', llmConfigForm); ElMessage.success('创建成功'); }
|
|
llmConfigDialogVisible.value = false; await loadLLMConfigs();
|
|
} catch (e) { ElMessage.error('保存失败: ' + e.message); }
|
|
};
|
|
const deleteLLMConfig = async (id) => {
|
|
try { await ElMessageBox.confirm('确定删除该配置吗?', '提示', { type: 'warning' }); await api.delete(`/api/admin/llmconfigs/${id}`); ElMessage.success('删除成功'); await loadLLMConfigs(); }
|
|
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
|
|
};
|
|
|
|
// SystemConfigs
|
|
const systemConfigs = ref([]);
|
|
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 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 = ''; }
|
|
systemConfigDialogVisible.value = true;
|
|
};
|
|
const saveSystemConfig = async () => {
|
|
try {
|
|
if (editingSystemConfigKey.value) { await api.put(`/api/admin/systemconfigs/${editingSystemConfigKey.value}`, systemConfigForm); ElMessage.success('更新成功'); }
|
|
else { await api.post('/api/admin/systemconfigs', systemConfigForm); ElMessage.success('创建成功'); }
|
|
systemConfigDialogVisible.value = false; await loadSystemConfigs();
|
|
} catch (e) { ElMessage.error('保存失败: ' + e.message); }
|
|
};
|
|
const deleteSystemConfig = async (key) => {
|
|
try { await ElMessageBox.confirm('确定删除该配置吗?', '提示', { type: 'warning' }); await api.delete(`/api/admin/systemconfigs/${key}`); ElMessage.success('删除成功'); await loadSystemConfigs(); }
|
|
catch (e) { if (e !== 'cancel') ElMessage.error('删除失败: ' + e.message); }
|
|
};
|
|
|
|
const logout = () => {
|
|
localStorage.removeItem('authToken');
|
|
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();
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (activeTab.value === 'cases') loadCases();
|
|
else if (activeTab.value === 'tasklogs') loadTaskLogs();
|
|
else if (activeTab.value === 'llmconfigs') loadLLMConfigs();
|
|
else if (activeTab.value === 'systemconfigs') loadSystemConfigs();
|
|
});
|
|
|
|
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
|
|
};
|
|
}
|
|
});
|
|
|
|
app.use(ElementPlus);
|
|
app.mount('#app');
|
|
</script>
|
|
</body>
|
|
</html> |