fix: 修复前端空白页与API调用错误,统一创作流程

- 前端
  - topics.html: 恢复结构并修复Vue初始化问题
  - 调整创作按钮逻辑:仅已发布选题禁用
  - 修正API端点与payload格式(generate/optimizer/publishing使用topic_ids数组)
  - 移除ElementPlus图标模块依赖,使用全局构建
  - admin.html: 回退至Options API版本,解决this上下文错误
- 后端
  - 注册/api/generate/run路由
  - 简化generate逻辑:允许非已发布选题重创作,更新状态为“待审查”
  - 统一logs查询接口支持query参数
  - 修复admin用户管理字段引用
  - 系统概览返回{ stats }结构
- 静态资源整理
  - 删除冗余element-plus-icons、重复CSS/JS、图标文件
  - 正确放置Vue和ElementPlus全局文件
- 数据库与数据
  - 补充30个案例
  - 更新选题状态与初始数据

验证:所有页面可访问,API认证与端点正常工作。
This commit is contained in:
lt
2026-05-06 21:44:56 +08:00
parent bd381ff65a
commit 8920a337e0
29 changed files with 159 additions and 138169 deletions
+29 -6
View File
@@ -221,9 +221,11 @@
</main>
</div>
<script type="module">
import { createApp, ref, reactive, onMounted, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
<script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script>
<script>
const { createApp, ref, reactive, onMounted, watch } = Vue;
const { ElMessage, ElMessageBox } = ElementPlus;
const app = createApp({
setup() {
@@ -236,10 +238,30 @@ const app = createApp({
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(); })
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');
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(data => {
currentUser.value = data.user;
isAdmin.value = data.user.role === 'admin';
isLoggedIn.value = true;
if (!isAdmin.value) {
ElMessage.warning('需要管理员权限');
window.location.href = '/';
}
})
.catch(() => {
localStorage.removeItem('authToken');
window.location.href = 'login.html';
});
// Cases
const cases = ref([]);
@@ -352,13 +374,14 @@ const app = createApp({
taskLogs, loadTaskLogs,
llmConfigs, llmConfigDialogVisible, llmConfigForm, llmConfigDialogTitle, showLLMConfigDialog, saveLLMConfig, deleteLLMConfig,
systemConfigs, systemConfigDialogVisible, systemConfigForm, systemConfigDialogTitle, showSystemConfigDialog, saveSystemConfig, deleteSystemConfig,
logout
logout,
currentUser, isAdmin, isLoggedIn
};
}
});
app.use(ElementPlus);
app.mount('#app');
</script>
</script>
</body>
</html>
-1
View File
@@ -478,7 +478,6 @@
<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() {
-1
View File
@@ -1 +0,0 @@
Redirecting to /axios@1.15.2/dist/axios.min.js
@@ -1 +0,0 @@
Redirecting to /@element-plus/icons-vue@2.3.2/dist/index.full.js
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
<rect width="192" height="192" fill="#409EFF" rx="24"/>
<text x="96" y="120" font-family="Arial, sans-serif" font-size="80" font-weight="bold" fill="white" text-anchor="middle"></text>
</svg>

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 B

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#409EFF" rx="48"/>
<text x="256" y="320" font-family="Arial, sans-serif" font-size="200" font-weight="bold" fill="white" text-anchor="middle"></text>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -136,7 +136,7 @@
<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="success" :disabled="isStatus(scope.row, 'published')" @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="danger" @click="deleteTopic(scope.row.id)">删除</el-button>
@@ -162,7 +162,7 @@
</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="success" :disabled="isStatus(topic, 'published')" @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="danger" @click="deleteTopic(topic.id)">删除</el-button>
@@ -312,8 +312,8 @@ const TopicsApp = {
});
},
async createTopic(topic) {
if (!this.isStatus(topic, 'pending')) {
this.$message.info('仅待处理选题可创作');
if (this.isStatus(topic, 'published')) {
this.$message.info('已发布选题可创作');
return;
}
try {
@@ -322,13 +322,13 @@ const TopicsApp = {
this.$message.error('请先登录');
return;
}
const response = await fetch('/api/system/generate/run', {
const response = await fetch('/api/generate/run', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ topic_id: topic.id })
body: JSON.stringify({ topic_ids: [topic.id] })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
@@ -359,7 +359,7 @@ const TopicsApp = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ topic_id: topic.id })
body: JSON.stringify({ topic_ids: [topic.id] })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
@@ -390,7 +390,7 @@ const TopicsApp = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ topic_id: topic.id })
body: JSON.stringify({ topic_ids: [topic.id] })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));