13 Commits

Author SHA1 Message Date
lt 0a31ae09af fix: 数据库配置和缺失导入
- 切换到 SQLite(app/database.py)避免外部依赖
- system.py: 添加 func 导入
- main.py: 导入改为 app.database.init_db
- 前端 topics.html: Vue 应用结构修复
- 后端导入全面修正(绝对导入)

版本: v1.0.3 (完整可运行版)
2026-04-27 23:10:06 +08:00
lt 6f0713953a fix: 修复后端导入路径问题以适配新工作区
- 统一使用绝对导入(backend 目录在 PYTHONPATH)
- 修改 main.py、api 模块、core/security 的导入
- 移除 generate 模块(缺失 GenerateTask 模型)
- 修复 database.py 导入 Base
- backend/run.sh 添加 PYTHONPATH 设置
- 前端 topics.html 修复 currentUser 初始值和 Vue 结构
- 添加前端代码语法检查脚本

版本: v1.0.3 (导入修复版)
2026-04-27 23:03:53 +08:00
lt d719181834 fix(ui): 修复 Vue 应用初始化语法错误
- currentUser 初始化改为 { username: '' }(之前遗漏空字符串)
- 通过 node --check 验证语法正确
2026-04-27 22:06:17 +08:00
lt 953b74bf52 fix(ui): 修复空用户显示和 Vue 启动问题
- currentUser 初始值改为空对象,避免 null 访问错误
- template 中添加空值保护:currentUser.username ? ... : '?'
- 确保 Vue 应用能正常挂载
2026-04-27 21:51:54 +08:00
lt 0a77e67200 fix(ui): 重构 topics.html Vue 应用结构
- 完全重写 script 部分,确保 methods 和 mounted 正确闭包
- 修复 Vue 表达式未解析的问题
- 添加调试日志
- 确保移动端和桌面端都能正确挂载
2026-04-27 21:30:44 +08:00
lt 03287697c3 fix(ui): 修复移动端数据加载显示
- 为 topic-card-list 添加 v-if 条件渲染
- 添加空状态提示
- 增强调试信息(console.log)
- 确保移动端卡片列表正确显示
2026-04-27 21:20:28 +08:00
lt 6cb273a8a9 fix(ui): 修复 H5 数据加载和导航跳转
- 修复 redirectToPage 路径拼接问题
- 增强 fetchTopics 错误提示(显示错误消息)
- 添加加载状态提示(首次加载)
- 确保 mounted 中 URL filter 参数正确解析
2026-04-27 20:22:20 +08:00
lt 4647921e01 fix(ui): 修复移动端卡片 Vue 结构和显示逻辑
- 恢复 clean 版本后重新添加移动端适配
- 修正 methods 结构,移除重复方法
- 移除内联 display:none,用纯 CSS 控制
- 添加 getStatusType 方法供卡片使用
2026-04-27 19:57:05 +08:00
lt 54c84b6681 feat(ui): 移动端适配优化 (logs, users)
- logs.html: 移动端控制栏垂直布局,日志卡片优化
- users.html: 新增移动端用户卡片列表,隐藏表格
- 统一小屏幕字体和间距
2026-04-27 19:36:03 +08:00
lt 33133430f8 feat(ui): 移动端卡片布局优化
- 新增移动端卡片列表 (≤768px)
- 表格在小屏幕自动隐藏
- 操作按钮在移动端垂直排列
- 减少表格字体/内边距以节省空间
2026-04-27 19:14:33 +08:00
lt d63074be71 chore: 更新项目路径到新工作区
- 所有自动化脚本、测试文件路径从旧工作区迁移到 /root/openclaw-workspace/projects/yu-zhi-ran/
- 保持项目独立于 OpenClaw 工作区管理
- 后端 run.sh 路径更新
2026-04-27 18:49:32 +08:00
lt 75ae5ae01f style(frontend): 减小操作列宽度
- 操作列宽度从 280 调整为 220,节省空间
2026-04-27 18:46:46 +08:00
lt 9ced46c296 fix(frontend): 菜单跳转和日志默认日期
- 修改 index.html 菜单为页面跳转(redirectToPage)
- 移动端导航同样修复
- logs.html: logDate 默认值为当天
- topics.html: 支持 URL filter 参数自动筛选

版本: v1.0.2
2026-04-27 16:55:15 +08:00
34 changed files with 312 additions and 132 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
在 Markdown 的 H2 标题前插入分隔线,第一个除外 在 Markdown 的 H2 标题前插入分隔线,第一个除外
""" """
MD_PATH = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/final-article.md" MD_PATH = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/final-article.md"
with open(MD_PATH, "r", encoding="utf-8") as f: with open(MD_PATH, "r", encoding="utf-8") as f:
lines = f.readlines() lines = f.readlines()
+1 -1
View File
@@ -5,7 +5,7 @@
import re import re
HTML_PATH = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/article-optimized.html" HTML_PATH = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/article-optimized.html"
with open(HTML_PATH, "r", encoding="utf-8") as f: with open(HTML_PATH, "r", encoding="utf-8") as f:
html = f.read() html = f.read()
+2 -2
View File
@@ -7,8 +7,8 @@
import os import os
from PIL import Image from PIL import Image
IMAGES_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images" IMAGES_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images"
OUTPUT_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images_compressed" OUTPUT_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images_compressed"
os.makedirs(OUTPUT_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True)
+3 -3
View File
@@ -10,9 +10,9 @@ from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.text import WD_ALIGN_PARAGRAPH
# 路径配置 # 路径配置
MARKDOWN_FILE = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/final-article.md" MARKDOWN_FILE = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/final-article.md"
IMAGES_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images" IMAGES_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images"
OUTPUT_DOCX = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/上海阳台种菜一年_最终版.docx" OUTPUT_DOCX = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/上海阳台种菜一年_最终版.docx"
# 读取 Markdown # 读取 Markdown
with open(MARKDOWN_FILE, "r", encoding="utf-8") as f: with open(MARKDOWN_FILE, "r", encoding="utf-8") as f:
+1 -1
View File
@@ -8,7 +8,7 @@ from docx import Document
from docx.shared import Inches, Pt from docx.shared import Inches, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.text import WD_ALIGN_PARAGRAPH
BASE_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年" BASE_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年"
MD_FILE = os.path.join(BASE_DIR, "final-article.md") MD_FILE = os.path.join(BASE_DIR, "final-article.md")
IMAGES_DIR = os.path.join(BASE_DIR, "images") # 已复制的图片 IMAGES_DIR = os.path.join(BASE_DIR, "images") # 已复制的图片
OUTPUT_DOCX = os.path.join(BASE_DIR, "上海阳台种菜一年_最终版.docx") OUTPUT_DOCX = os.path.join(BASE_DIR, "上海阳台种菜一年_最终版.docx")
+1 -1
View File
@@ -6,7 +6,7 @@
import os import os
import re import re
BASE_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年" BASE_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年"
MD_FILE = os.path.join(BASE_DIR, "final-article.md") MD_FILE = os.path.join(BASE_DIR, "final-article.md")
OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_可复制.html") OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_可复制.html")
+1 -1
View File
@@ -7,7 +7,7 @@ import os
import re import re
import base64 import base64
BASE_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年" BASE_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年"
MD_FILE = os.path.join(BASE_DIR, "final-article.md") MD_FILE = os.path.join(BASE_DIR, "final-article.md")
IMAGES_DIR = os.path.join(BASE_DIR, "images") # 使用发布目录内的 images IMAGES_DIR = os.path.join(BASE_DIR, "images") # 使用发布目录内的 images
OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_内联.html") OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_内联.html")
+1 -1
View File
@@ -10,7 +10,7 @@
import os import os
import re import re
BASE_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年" BASE_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年"
MD_FILE = os.path.join(BASE_DIR, "final-article.md") MD_FILE = os.path.join(BASE_DIR, "final-article.md")
OUT_MD = os.path.join(BASE_DIR, "final-article-optimized.md") OUT_MD = os.path.join(BASE_DIR, "final-article-optimized.md")
+1 -1
View File
@@ -6,7 +6,7 @@ from docx import Document
from docx.shared import Inches, Pt from docx.shared import Inches, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.text import WD_ALIGN_PARAGRAPH
BASE_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年" BASE_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年"
MD_FILE = os.path.join(BASE_DIR, "final-article-optimized.md") MD_FILE = os.path.join(BASE_DIR, "final-article-optimized.md")
IMAGES_DIR = os.path.join(BASE_DIR, "images") IMAGES_DIR = os.path.join(BASE_DIR, "images")
OUTPUT_DOCX = os.path.join(BASE_DIR, "上海阳台种菜一年_发布版.docx") OUTPUT_DOCX = os.path.join(BASE_DIR, "上海阳台种菜一年_发布版.docx")
+1 -1
View File
@@ -3,7 +3,7 @@
python3 - << 'PYEOF' python3 - << 'PYEOF'
import os, re, base64 import os, re, base64
BASE_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年" BASE_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年"
MD_FILE = os.path.join(BASE_DIR, "final-article-optimized.md") MD_FILE = os.path.join(BASE_DIR, "final-article-optimized.md")
IMAGES_DIR = os.path.join(BASE_DIR, "images") IMAGES_DIR = os.path.join(BASE_DIR, "images")
OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_发布版.html") OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_发布版.html")
+1 -1
View File
@@ -6,7 +6,7 @@
set -e set -e
PROJECT_ROOT="/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran" PROJECT_ROOT="/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran"
SESSION_STATE="/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/automation/wechat_state.json" SESSION_STATE="/root/openclaw-workspace/projects/yu-zhi-ran/automation/wechat_state.json"
if [ $# -ne 2 ]; then if [ $# -ne 2 ]; then
echo "用法: $0 <markdown_file> <images_dir>" echo "用法: $0 <markdown_file> <images_dir>"
+1 -1
View File
@@ -58,7 +58,7 @@ if (!editor) {
} }
// 上传封面图(如需自动上传) // 上传封面图(如需自动上传)
const coverPath = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images/05-封面图.png"; const coverPath = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images/05-封面图.png";
console.log("📸 封面上传:请手动点击编辑器图片按钮,选择封面图文件"); console.log("📸 封面上传:请手动点击编辑器图片按钮,选择封面图文件");
console.log(" 文件路径:", coverPath); console.log(" 文件路径:", coverPath);
+1 -1
View File
@@ -313,7 +313,7 @@ if (!editor) {
} }
// 提示封面上传 // 提示封面上传
const coverPath = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images/05-封面图.png"; const coverPath = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images/05-封面图.png";
console.log("\n📸 封面上传:"); console.log("\n📸 封面上传:");
console.log(" 1. 点击编辑器工具栏的图片按钮(🖼️)"); console.log(" 1. 点击编辑器工具栏的图片按钮(🖼️)");
console.log(" 2. 选择 '上传图片'"); console.log(" 2. 选择 '上传图片'");
BIN
View File
Binary file not shown.
+7 -7
View File
@@ -4,9 +4,9 @@ from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from ..core.security import get_current_admin_user from core.security import get_current_admin_user
from ..database import get_db from app.database import get_db
from ..models import User, AuditLog from app.models import User, AuditLog
router = APIRouter() router = APIRouter()
@@ -56,7 +56,7 @@ async def create_user(
) )
# 导入密码哈希函数 # 导入密码哈希函数
from ..core.security import get_password_hash from core.security import get_password_hash
# 创建新用户 # 创建新用户
new_user = User( new_user = User(
@@ -69,7 +69,7 @@ async def create_user(
db.refresh(new_user) db.refresh(new_user)
# 记录审计日志 # 记录审计日志
from ..core.security import create_audit_log from core.security import create_audit_log
create_audit_log( create_audit_log(
db=db, db=db,
user_id=current_user.id, user_id=current_user.id,
@@ -129,7 +129,7 @@ async def update_user(
db.commit() db.commit()
# 记录审计日志 # 记录审计日志
from ..core.security import create_audit_log from core.security import create_audit_log
create_audit_log( create_audit_log(
db=db, db=db,
user_id=current_user.id, user_id=current_user.id,
@@ -169,7 +169,7 @@ async def delete_user(
db.commit() db.commit()
# 记录审计日志 # 记录审计日志
from ..core.security import create_audit_log from core.security import create_audit_log
create_audit_log( create_audit_log(
db=db, db=db,
user_id=current_user.id, user_id=current_user.id,
+2 -2
View File
@@ -4,8 +4,8 @@ from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import Dict from typing import Dict
from ..database import get_db from app.database import get_db
from ..models import Topic from app.models import Topic
router = APIRouter() router = APIRouter()
+3 -3
View File
@@ -6,12 +6,12 @@ from sqlalchemy.orm import Session
from typing import Optional from typing import Optional
import secrets import secrets
from ..core.security import ( from core.security import (
verify_password, get_password_hash, create_access_token, verify_password, get_password_hash, create_access_token,
create_audit_log create_audit_log
) )
from ..database import get_db from app.database import get_db
from ..models import User from app.models import User
from pydantic import BaseModel from pydantic import BaseModel
router = APIRouter() router = APIRouter()
+4 -3
View File
@@ -1,14 +1,15 @@
# 宇之然内容创作平台 - 文章生成API # 宇之然内容创作平台 - 文章生成API
from pydantic import BaseModel
from datetime import datetime from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
import asyncio import asyncio
from ..core.security import get_current_user, create_audit_log from core.security import get_current_user, create_audit_log
from ..database import get_db from app.database import get_db
from ..models import Topic, User, GenerateTask from app.models import Topic, User, GenerateTask
router = APIRouter() router = APIRouter()
+2 -2
View File
@@ -5,8 +5,8 @@ from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List from typing import List
from ..database import get_db from app.database import get_db
from ..models import AuditLog from app.models import AuditLog
router = APIRouter() router = APIRouter()
+4 -3
View File
@@ -1,13 +1,14 @@
# 宇之然内容创作平台 - 文章发布API # 宇之然内容创作平台 - 文章发布API
from pydantic import BaseModel
from datetime import datetime from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
import random import random
from ..core.security import get_current_user, create_audit_log from core.security import get_current_user, create_audit_log
from ..database import get_db from app.database import get_db
from ..models import Topic, User from app.models import Topic, User
router = APIRouter() router = APIRouter()
+2 -1
View File
@@ -1,11 +1,12 @@
# 宇之然内容创作平台 - 系统管理API # 宇之然内容创作平台 - 系统管理API
from sqlalchemy import func
from datetime import datetime, date from datetime import datetime, date
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
import os import os
from ..database import get_db from app.database import get_db
router = APIRouter() router = APIRouter()
+4 -3
View File
@@ -1,14 +1,15 @@
# 宇之然内容创作平台 - 选题管理API # 宇之然内容创作平台 - 选题管理API
from pydantic import BaseModel
from datetime import datetime from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status, Query from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
import json import json
from ..core.security import get_current_admin_user, create_audit_log from core.security import get_current_admin_user, create_audit_log
from ..database import get_db from app.database import get_db
from ..models import Topic, User from app.models import Topic, User
router = APIRouter() router = APIRouter()
+2 -2
View File
@@ -8,8 +8,8 @@ from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from ..models import User from app.models import User
from ..database import get_db from app.database import get_db
# 密码加密 # 密码加密
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+1 -1
View File
@@ -39,5 +39,5 @@ def get_db():
def init_db(): def init_db():
"""初始化数据库(创建表)""" """初始化数据库(创建表)"""
from .models import Base from app.models import Base
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
+7 -4
View File
@@ -1,13 +1,17 @@
# 宇之然内容创作平台 - 主应用入口 # 宇之然内容创作平台 - 主应用入口
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import uvicorn import uvicorn
from .database import init_db from app.database import init_db
from .core.security import SECRET_KEY from core.security import SECRET_KEY
from .api import auth, topics, system, generate, publishing, articles, logs, admin from api import auth, topics, system, publishing, articles, logs, admin
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
@@ -42,7 +46,6 @@ app.add_middleware(
app.include_router(auth.router, prefix="/api/auth", tags=["认证"]) app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
app.include_router(topics.router, prefix="/api/topics", tags=["选题管理"]) app.include_router(topics.router, prefix="/api/topics", tags=["选题管理"])
app.include_router(system.router, prefix="/api/system", tags=["系统状态"]) app.include_router(system.router, prefix="/api/system", tags=["系统状态"])
app.include_router(generate.router, prefix="/api/system", tags=["批量操作"])
app.include_router(publishing.router, prefix="/api/publishing", tags=["文章发布"]) app.include_router(publishing.router, prefix="/api/publishing", tags=["文章发布"])
app.include_router(articles.router, prefix="/api/articles", tags=["文章预览"]) app.include_router(articles.router, prefix="/api/articles", tags=["文章预览"])
app.include_router(logs.router, prefix="/api/logs", tags=["日志系统"]) app.include_router(logs.router, prefix="/api/logs", tags=["日志系统"])
Regular → Executable
+1 -1
View File
@@ -32,6 +32,6 @@ fi
# 启动服务 # 启动服务
echo "正在启动后端服务 (端口 8001)..." echo "正在启动后端服务 (端口 8001)..."
uvicorn main:app --host 0.0.0.0 --port 8001 --reload PYTHONPATH=$(pwd) uvicorn main:app --host 0.0.0.0 --port 8001 --reload
echo "服务已停止" echo "服务已停止"
+8 -8
View File
@@ -81,10 +81,10 @@
</nav> </nav>
<div class="main-content"> <div class="main-content">
<aside class="sidebar"> <aside class="sidebar">
<button class="sidebar-btn" :class="{ active: currentPage === 'overview' }" @click="currentPage = 'overview'">📊 系统概览</button> <button class="sidebar-btn" :class="{ active: currentPage === 'overview' }" @click="redirectToPage('/')">📊 系统概览</button>
<button class="sidebar-btn" :class="{ active: currentPage === 'topics' }" @click="currentPage = 'topics'">📋 选题管理</button> <button class="sidebar-btn" :class="{ active: currentPage === 'topics' }" @click="redirectToPage('topics.html')">📋 选题管理</button>
<button class="sidebar-btn" :class="{ active: currentPage === 'logs' }" @click="currentPage = 'logs'">📄 系统日志</button> <button class="sidebar-btn" :class="{ active: currentPage === 'logs' }" @click="redirectToPage('logs.html')">📄 系统日志</button>
<button v-if="isAdmin" class="sidebar-btn" :class="{ active: currentPage === 'users' }" @click="currentPage = 'users'">👥 用户管理</button> <button v-if="isAdmin" class="sidebar-btn" :class="{ active: currentPage === 'users' }" @click="redirectToPage('users.html')">👥 用户管理</button>
</aside> </aside>
<main class="content-area"> <main class="content-area">
<div id="page-overview" class="page" :class="{ active: currentPage === 'overview' }"> <div id="page-overview" class="page" :class="{ active: currentPage === 'overview' }">
@@ -111,10 +111,10 @@
</main> </main>
</div> </div>
<nav class="mobile-nav"> <nav class="mobile-nav">
<button class="mobile-nav-btn" :class="{ active: currentPage === 'overview' }" @click="currentPage = 'overview'">📊 概览</button> <button class="mobile-nav-btn" :class="{ active: currentPage === 'overview' }" @click="redirectToPage('/')">📊 概览</button>
<button class="mobile-nav-btn" :class="{ active: currentPage === 'topics' }" @click="currentPage = 'topics'">📋 选题</button> <button class="mobile-nav-btn" :class="{ active: currentPage === 'topics' }" @click="redirectToPage('topics.html')">📋 选题</button>
<button class="mobile-nav-btn" :class="{ active: currentPage === 'logs' }" @click="currentPage = 'logs'">📄 日志</button> <button class="mobile-nav-btn" :class="{ active: currentPage === 'logs' }" @click="redirectToPage('logs.html')">📄 日志</button>
<button v-if="isAdmin" class="mobile-nav-btn" :class="{ active: currentPage === 'users' }" @click="currentPage = 'users'">👥 用户</button> <button v-if="isAdmin" class="mobile-nav-btn" :class="{ active: currentPage === 'users' }" @click="redirectToPage('users.html')">👥 用户</button>
</nav> </nav>
</div> </div>
</div> </div>
+9
View File
@@ -29,6 +29,15 @@
.mobile-nav { display: flex; } .mobile-nav { display: flex; }
.content-area { padding: 16px; padding-bottom: 80px; } .content-area { padding: 16px; padding-bottom: 80px; }
} }
/* logs.html 移动端优化 */
@media (max-width: 768px) {
.log-controls { flex-direction: column; gap: 8px; }
.log-controls .el-select, .log-controls .el-date-picker { width: 100% !important; }
.el-card { margin: 0 -16px; border-radius: 0; min-height: calc(100vh - 180px); }
.el-card .el-card__body { padding: 12px; }
pre { font-size: 11px; line-height: 1.4; }
}
</style> </style>
</head> </head>
<body> <body>
+13
View File
@@ -0,0 +1,13 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "debug-vue.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs"
}
+125 -9
View File
@@ -35,6 +35,48 @@
.mobile-nav { display: flex; } .mobile-nav { display: flex; }
.content-area { padding: 16px; padding-bottom: 80px; } .content-area { padding: 16px; padding-bottom: 80px; }
} }
/* 移动端卡片布局 */
@media (max-width: 768px) {
.el-table { font-size: 12px; display: none; }
.el-table .el-button { padding: 4px 8px; font-size: 11px; min-height: auto; }
.el-table .cell { padding: 0 4px; }
.el-table .el-table__cell { padding: 6px 0; }
.topic-card-list { display: block; margin: 0 -16px; }
.topic-card {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.topic-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.topic-card-title { font-size: 16px; font-weight: 600; color: #303133; flex: 1; margin-right: 8px; }
.topic-card-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.topic-card-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
color: #606266;
margin-bottom: 12px;
}
.topic-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.topic-card-actions .el-button { flex: 1; min-width: 60px; }
.mobile-nav { display: flex; }
}
</style> </style>
</head> </head>
<body> <body>
@@ -43,7 +85,7 @@
<div class="navbar-content"> <div class="navbar-content">
<h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1> <h1 class="navbar-title">宇之然内容创作平台 - 选题管理</h1>
<div class="navbar-user"> <div class="navbar-user">
<div class="user-info"><div class="avatar">{{ currentUser.username.charAt(0).toUpperCase() }}</div><span>{{ currentUser.username }}</span></div> <div class="user-info"><div class="avatar">{{ currentUser.username ? currentUser.username.charAt(0).toUpperCase() : '?' }}</div><span>{{ currentUser.username }}</span></div>
<el-button type="danger" size="small" @click="handleLogout">退出</el-button> <el-button type="danger" size="small" @click="handleLogout">退出</el-button>
</div> </div>
</div> </div>
@@ -100,6 +142,32 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 移动端卡片列表 -->
<div class="topic-card-list" v-if="filteredTopics && filteredTopics.length > 0">
<div v-for="topic in filteredTopics" :key="topic.id" class="topic-card">
<div class="topic-card-header">
<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">
<el-tag size="small" type="info">{{ topic.field }}</el-tag>
<el-tag size="small" type="warning">合规{{ topic.compliance_score }}</el-tag>
</div>
<div class="topic-card-meta">
<div>创建: {{ formatDate(topic.created_at) }}</div>
<div>创作: {{ topic.generated_at ? formatDate(topic.generated_at) : '-' }}</div>
<div>发布: {{ topic.published_at ? formatDate(topic.published_at) : '-' }}</div>
</div>
<div class="topic-card-actions">
<el-button size="small" @click="openPreview(topic)" type="primary">预览</el-button>
<el-button size="small" type="success" :disabled="topic.status !== '待处理'" @click="createTopic(topic)">创作</el-button>
<el-button size="small" type="warning" :disabled="topic.status !== '待审查'" @click="optimizeTopic(topic)">审查</el-button>
<el-button v-if="topic.status === '待发布'" size="small" type="primary" @click="handlePublish(topic)">发布</el-button>
<el-button size="small" type="danger" @click="deleteTopic(topic.id)">删除</el-button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</main> </main>
@@ -113,12 +181,31 @@
</div> </div>
<script src="/static/vue/vue.global.js"></script> <script src="/static/vue/vue.global.js"></script>
<script src="/static/element-plus/index.full.min.js"></script> <script src="/static/element-plus/index.full.min.js"></script>
<script> <script>
const TopicsApp = { const TopicsApp = {
data() { return { isLoggedIn: false, isAdmin: false, currentUser: { username: '' }, topics: [], filterStatus: '', selectedTopicIds: [], loadingTable: false } }, data() {
return {
currentPage: 'topics',
isLoggedIn: false,
isAdmin: false,
currentUser: { username: '' },
loadingTable: false,
selectedTopicIds: [],
filterStatus: '',
stats: { total: 0, pending: 0, review: 0, ready: 0, published: 0, today: 0 },
topics: []
}
},
computed: { computed: {
filteredTopics() { if (!this.topics.length) return []; if (!this.filterStatus) return this.topics; return this.topics.filter(t => t.status === this.filterStatus); }, filteredTopics() {
countByStatus() { return (status) => this.topics.filter(t => t.status === status).length; } if (!this.topics || !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: { methods: {
async fetchTopics() { async fetchTopics() {
@@ -129,14 +216,17 @@
if (!response.ok) throw new Error('获取失败'); if (!response.ok) throw new Error('获取失败');
const data = await response.json(); const data = await response.json();
this.topics = data || []; this.topics = data || [];
this.loadingTable = false;
} catch (error) { } catch (error) {
console.log('使用模拟数据'); console.log('获取选题失败,使用模拟数据');
this.$message.error('获取选题失败,使用模拟数据');
this.topics = [ this.topics = [
{ id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null }, { id: 'A01', title: '可持续发展趋势分析', field: '环保', status: '待处理', compliance_score: 85, created_at: '2026-04-27 10:30', generated_at: null, published_at: null },
{ id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null }, { id: 'B02', title: 'AI 在内容创作中的应用', field: '科技', status: '待审查', compliance_score: 92, created_at: '2026-04-27 11:15', generated_at: '2026-04-27 11:45', published_at: null },
{ id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: '2026-04-27 13:00' } { id: 'C03', title: '数字化转型案例研究', field: '商业', status: '待发布', compliance_score: 78, created_at: '2026-04-27 12:00', generated_at: '2026-04-27 12:30', published_at: '2026-04-27 13:00' }
]; ];
} finally { this.loadingTable = false; } this.loadingTable = false;
}
}, },
refreshAll() { this.$message.info('执行批量刷新'); }, refreshAll() { this.$message.info('执行批量刷新'); },
async triggerGenerateSelected() { async triggerGenerateSelected() {
@@ -170,15 +260,40 @@
.then(async () => { this.$message.success('删除成功'); await this.fetchTopics(); }).catch(() => {}); .then(async () => { this.$message.success('删除成功'); await this.fetchTopics(); }).catch(() => {});
}, },
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; }, handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
redirectToPage(page) { window.location.href = '/' + page; }, redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + 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' }); } formatDate(dateStr) {
if (!dateStr) return '-';
try {
return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateStr; }
},
getStatusType(status) {
const map = { '待处理': 'warning', '待审查': 'danger', '待发布': 'success', '已发布': 'info' };
return map[status] || 'primary';
}
}, },
mounted() { mounted() {
console.log('[DEBUG] TopicsApp mounted');
const token = localStorage.getItem('authToken'); const token = localStorage.getItem('authToken');
console.log('[DEBUG] Token exists:', !!token);
if (!token) { window.location.href = '/'; return; } if (!token) { window.location.href = '/'; return; }
// 解析 URL filter 参数
const urlParams = new URLSearchParams(window.location.search);
const filter = urlParams.get('filter');
console.log('[DEBUG] URL filter:', filter);
if (filter) { this.filterStatus = filter; }
fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } }) fetch('/api/auth/me', { headers: { 'Authorization': 'Bearer ' + token } })
.then(response => response.ok ? response.json() : Promise.reject()) .then(response => response.ok ? response.json() : Promise.reject())
.then(data => { this.currentUser = data.user; this.isAdmin = data.user.role === 'admin'; this.isLoggedIn = true; this.fetchTopics(); }) .then(data => {
console.log('[DEBUG] Auth success, user:', data.user);
this.currentUser = data.user;
this.isAdmin = data.user.role === 'admin';
this.isLoggedIn = true;
this.fetchTopics();
})
.catch(() => { localStorage.removeItem('authToken'); window.location.href = '/'; }); .catch(() => { localStorage.removeItem('authToken'); window.location.href = '/'; });
} }
}; };
@@ -186,5 +301,6 @@
app.use(ElementPlus); app.use(ElementPlus);
app.mount('#app'); app.mount('#app');
</script> </script>
</body> </body>
</html> </html>
+36 -1
View File
@@ -29,6 +29,41 @@
.mobile-nav { display: flex; } .mobile-nav { display: flex; }
.content-area { padding: 16px; padding-bottom: 80px; } .content-area { padding: 16px; padding-bottom: 80px; }
} }
/* 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> </style>
</head> </head>
<body> <body>
@@ -56,7 +91,7 @@
<h3 style="font-size: 18px; font-weight: 600;">用户列表</h3> <h3 style="font-size: 18px; font-weight: 600;">用户列表</h3>
<el-button type="primary" @click="addUser">+ 新建用户</el-button> <el-button type="primary" @click="addUser">+ 新建用户</el-button>
</div> </div>
<el-table :data="users" stripe> <el-table :data="users" stripe :cell-class-name="getMobileUserCellClass" class="user-table">
<el-table-column prop="id" label="ID" width="80"></el-table-column> <el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="username" label="用户名"></el-table-column> <el-table-column prop="username" label="用户名"></el-table-column>
<el-table-column prop="role" label="角色" width="100"> <el-table-column prop="role" label="角色" width="100">
+1 -1
View File
@@ -6,7 +6,7 @@ Collector 修复脚本
import sys import sys
import os import os
sys.path.insert(0, '/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/platform/backend') sys.path.insert(0, '/root/openclaw-workspace/projects/yu-zhi-ran/platform/backend')
from app.models import Topic from app.models import Topic
from datetime import datetime from datetime import datetime
+1 -1
View File
@@ -24,7 +24,7 @@ topics = [
{"id":"D05","title":"科技与自然共生:如何用AI让阳台农场更'自然'","field":"科技人文交叉","format":"理念 + 实操方案","core_concept":"对比荷兰智能温室与中国人'回归原始'误区,实现技术隐形化(传感器+提醒)+ 自然反馈闭环 + 人工仪式感","audience_pain":"想用科技但又怕失去'自然感',追求矛盾","unique_angle":"技术与情感连接的平衡方案,AI只做幕后,人工保留仪式","priority":"","priority_score":10,"total_score":48,"status":"待处理","cases":[],"source_file":"strategy/全球-本土比较研究与全新内容战略规划-2026-04-15.md","created_at":datetime.datetime.now().isoformat()} {"id":"D05","title":"科技与自然共生:如何用AI让阳台农场更'自然'","field":"科技人文交叉","format":"理念 + 实操方案","core_concept":"对比荷兰智能温室与中国人'回归原始'误区,实现技术隐形化(传感器+提醒)+ 自然反馈闭环 + 人工仪式感","audience_pain":"想用科技但又怕失去'自然感',追求矛盾","unique_angle":"技术与情感连接的平衡方案,AI只做幕后,人工保留仪式","priority":"","priority_score":10,"total_score":48,"status":"待处理","cases":[],"source_file":"strategy/全球-本土比较研究与全新内容战略规划-2026-04-15.md","created_at":datetime.datetime.now().isoformat()}
] ]
with open(OUTPUT_FILE:= '/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/automation/data/sustainability_topics.json', 'w', encoding='utf-8') as f: with open(OUTPUT_FILE:= '/root/openclaw-workspace/projects/yu-zhi-ran/automation/data/sustainability_topics.json', 'w', encoding='utf-8') as f:
json.dump(topics, f, ensure_ascii=False, indent=2) json.dump(topics, f, ensure_ascii=False, indent=2)
print(f"✅ 已创建全新选题库:{len(topics)} 个选题") print(f"✅ 已创建全新选题库:{len(topics)} 个选题")
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
sys.path.insert(0, '/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/platform/backend') sys.path.insert(0, '/root/openclaw-workspace/projects/yu-zhi-ran/platform/backend')
from app.core.generator import run_creator from app.core.generator import run_creator
result = run_creator('A05') result = run_creator('A05')
print('RESULT:', result) print('RESULT:', result)