Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a31ae09af | |||
| 6f0713953a | |||
| d719181834 | |||
| 953b74bf52 | |||
| 0a77e67200 | |||
| 03287697c3 | |||
| 6cb273a8a9 | |||
| 4647921e01 | |||
| 54c84b6681 | |||
| 33133430f8 | |||
| d63074be71 | |||
| 75ae5ae01f | |||
| 9ced46c296 |
@@ -3,7 +3,7 @@
|
||||
在 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:
|
||||
lines = f.readlines()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
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:
|
||||
html = f.read()
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
import os
|
||||
from PIL import Image
|
||||
|
||||
IMAGES_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images"
|
||||
OUTPUT_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images_compressed"
|
||||
IMAGES_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images"
|
||||
OUTPUT_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images_compressed"
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ from docx.shared import Inches, Pt, RGBColor
|
||||
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"
|
||||
IMAGES_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images"
|
||||
OUTPUT_DOCX = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/上海阳台种菜一年_最终版.docx"
|
||||
MARKDOWN_FILE = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/final-article.md"
|
||||
IMAGES_DIR = "/root/openclaw-workspace/projects/yu-zhi-ran/content/publishing/images"
|
||||
OUTPUT_DOCX = "/root/openclaw-workspace/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/上海阳台种菜一年_最终版.docx"
|
||||
|
||||
# 读取 Markdown
|
||||
with open(MARKDOWN_FILE, "r", encoding="utf-8") as f:
|
||||
|
||||
@@ -8,7 +8,7 @@ from docx import Document
|
||||
from docx.shared import Inches, Pt
|
||||
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")
|
||||
IMAGES_DIR = os.path.join(BASE_DIR, "images") # 已复制的图片
|
||||
OUTPUT_DOCX = os.path.join(BASE_DIR, "上海阳台种菜一年_最终版.docx")
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import os
|
||||
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")
|
||||
OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_可复制.html")
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
import re
|
||||
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")
|
||||
IMAGES_DIR = os.path.join(BASE_DIR, "images") # 使用发布目录内的 images
|
||||
OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_内联.html")
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import os
|
||||
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")
|
||||
OUT_MD = os.path.join(BASE_DIR, "final-article-optimized.md")
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from docx import Document
|
||||
from docx.shared import Inches, Pt
|
||||
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")
|
||||
IMAGES_DIR = os.path.join(BASE_DIR, "images")
|
||||
OUTPUT_DOCX = os.path.join(BASE_DIR, "上海阳台种菜一年_发布版.docx")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
python3 - << 'PYEOF'
|
||||
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")
|
||||
IMAGES_DIR = os.path.join(BASE_DIR, "images")
|
||||
OUT_HTML = os.path.join(BASE_DIR, "上海阳台种菜一年_发布版.html")
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -e
|
||||
|
||||
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
|
||||
echo "用法: $0 <markdown_file> <images_dir>"
|
||||
|
||||
@@ -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(" 文件路径:", coverPath);
|
||||
|
||||
|
||||
@@ -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(" 1. 点击编辑器工具栏的图片按钮(🖼️)");
|
||||
console.log(" 2. 选择 '上传图片'");
|
||||
|
||||
BIN
Binary file not shown.
@@ -4,9 +4,9 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from ..core.security import get_current_admin_user
|
||||
from ..database import get_db
|
||||
from ..models import User, AuditLog
|
||||
from core.security import get_current_admin_user
|
||||
from app.database import get_db
|
||||
from app.models import User, AuditLog
|
||||
|
||||
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(
|
||||
@@ -69,7 +69,7 @@ async def create_user(
|
||||
db.refresh(new_user)
|
||||
|
||||
# 记录审计日志
|
||||
from ..core.security import create_audit_log
|
||||
from core.security import create_audit_log
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
@@ -129,7 +129,7 @@ async def update_user(
|
||||
db.commit()
|
||||
|
||||
# 记录审计日志
|
||||
from ..core.security import create_audit_log
|
||||
from core.security import create_audit_log
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
@@ -169,7 +169,7 @@ async def delete_user(
|
||||
db.commit()
|
||||
|
||||
# 记录审计日志
|
||||
from ..core.security import create_audit_log
|
||||
from core.security import create_audit_log
|
||||
create_audit_log(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
|
||||
@@ -4,8 +4,8 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import Topic
|
||||
from app.database import get_db
|
||||
from app.models import Topic
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
import secrets
|
||||
|
||||
from ..core.security import (
|
||||
from core.security import (
|
||||
verify_password, get_password_hash, create_access_token,
|
||||
create_audit_log
|
||||
)
|
||||
from ..database import get_db
|
||||
from ..models import User
|
||||
from app.database import get_db
|
||||
from app.models import User
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 宇之然内容创作平台 - 文章生成API
|
||||
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import asyncio
|
||||
|
||||
from ..core.security import get_current_user, create_audit_log
|
||||
from ..database import get_db
|
||||
from ..models import Topic, User, GenerateTask
|
||||
from core.security import get_current_user, create_audit_log
|
||||
from app.database import get_db
|
||||
from app.models import Topic, User, GenerateTask
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import AuditLog
|
||||
from app.database import get_db
|
||||
from app.models import AuditLog
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# 宇之然内容创作平台 - 文章发布API
|
||||
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
import random
|
||||
|
||||
from ..core.security import get_current_user, create_audit_log
|
||||
from ..database import get_db
|
||||
from ..models import Topic, User
|
||||
from core.security import get_current_user, create_audit_log
|
||||
from app.database import get_db
|
||||
from app.models import Topic, User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# 宇之然内容创作平台 - 系统管理API
|
||||
|
||||
from sqlalchemy import func
|
||||
from datetime import datetime, date
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
import os
|
||||
|
||||
from ..database import get_db
|
||||
from app.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 宇之然内容创作平台 - 选题管理API
|
||||
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import json
|
||||
|
||||
from ..core.security import get_current_admin_user, create_audit_log
|
||||
from ..database import get_db
|
||||
from ..models import Topic, User
|
||||
from core.security import get_current_admin_user, create_audit_log
|
||||
from app.database import get_db
|
||||
from app.models import Topic, User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models import User
|
||||
from ..database import get_db
|
||||
from app.models import User
|
||||
from app.database import get_db
|
||||
|
||||
# 密码加密
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
@@ -39,5 +39,5 @@ def get_db():
|
||||
|
||||
def init_db():
|
||||
"""初始化数据库(创建表)"""
|
||||
from .models import Base
|
||||
from app.models import Base
|
||||
Base.metadata.create_all(bind=engine)
|
||||
@@ -1,13 +1,17 @@
|
||||
# 宇之然内容创作平台 - 主应用入口
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
import uvicorn
|
||||
|
||||
from .database import init_db
|
||||
from .core.security import SECRET_KEY
|
||||
from .api import auth, topics, system, generate, publishing, articles, logs, admin
|
||||
from app.database import init_db
|
||||
from core.security import SECRET_KEY
|
||||
from api import auth, topics, system, publishing, articles, logs, admin
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -42,7 +46,6 @@ app.add_middleware(
|
||||
app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
|
||||
app.include_router(topics.router, prefix="/api/topics", 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(articles.router, prefix="/api/articles", tags=["文章预览"])
|
||||
app.include_router(logs.router, prefix="/api/logs", tags=["日志系统"])
|
||||
|
||||
Regular → Executable
+1
-1
@@ -32,6 +32,6 @@ fi
|
||||
|
||||
# 启动服务
|
||||
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 "服务已停止"
|
||||
@@ -81,10 +81,10 @@
|
||||
</nav>
|
||||
<div class="main-content">
|
||||
<aside class="sidebar">
|
||||
<button class="sidebar-btn" :class="{ active: currentPage === 'overview' }" @click="currentPage = 'overview'">📊 系统概览</button>
|
||||
<button class="sidebar-btn" :class="{ active: currentPage === 'topics' }" @click="currentPage = 'topics'">📋 选题管理</button>
|
||||
<button class="sidebar-btn" :class="{ active: currentPage === 'logs' }" @click="currentPage = 'logs'">📄 系统日志</button>
|
||||
<button v-if="isAdmin" class="sidebar-btn" :class="{ active: currentPage === 'users' }" @click="currentPage = 'users'">👥 用户管理</button>
|
||||
<button class="sidebar-btn" :class="{ active: currentPage === 'overview' }" @click="redirectToPage('/')">📊 系统概览</button>
|
||||
<button class="sidebar-btn" :class="{ active: currentPage === 'topics' }" @click="redirectToPage('topics.html')">📋 选题管理</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="redirectToPage('users.html')">👥 用户管理</button>
|
||||
</aside>
|
||||
<main class="content-area">
|
||||
<div id="page-overview" class="page" :class="{ active: currentPage === 'overview' }">
|
||||
@@ -111,10 +111,10 @@
|
||||
</main>
|
||||
</div>
|
||||
<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 === 'topics' }" @click="currentPage = 'topics'">📋 选题</button>
|
||||
<button class="mobile-nav-btn" :class="{ active: currentPage === 'logs' }" @click="currentPage = 'logs'">📄 日志</button>
|
||||
<button v-if="isAdmin" class="mobile-nav-btn" :class="{ active: currentPage === 'users' }" @click="currentPage = 'users'">👥 用户</button>
|
||||
<button class="mobile-nav-btn" :class="{ active: currentPage === 'overview' }" @click="redirectToPage('/')">📊 概览</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="redirectToPage('logs.html')">📄 日志</button>
|
||||
<button v-if="isAdmin" class="mobile-nav-btn" :class="{ active: currentPage === 'users' }" @click="redirectToPage('users.html')">👥 用户</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
.mobile-nav { display: flex; }
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
+132
-16
@@ -35,6 +35,48 @@
|
||||
.mobile-nav { display: flex; }
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
@@ -43,7 +85,7 @@
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,6 +142,32 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</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>
|
||||
</main>
|
||||
@@ -113,12 +181,31 @@
|
||||
</div>
|
||||
<script src="/static/vue/vue.global.js"></script>
|
||||
<script src="/static/element-plus/index.full.min.js"></script>
|
||||
<script>
|
||||
const TopicsApp = {
|
||||
data() { return { isLoggedIn: false, isAdmin: false, currentUser: { username: '' }, topics: [], filterStatus: '', selectedTopicIds: [], loadingTable: false } },
|
||||
|
||||
<script>
|
||||
const TopicsApp = {
|
||||
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: {
|
||||
filteredTopics() { if (!this.topics.length) return []; if (!this.filterStatus) return this.topics; return this.topics.filter(t => t.status === this.filterStatus); },
|
||||
countByStatus() { return (status) => this.topics.filter(t => t.status === status).length; }
|
||||
filteredTopics() {
|
||||
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: {
|
||||
async fetchTopics() {
|
||||
@@ -129,14 +216,17 @@
|
||||
if (!response.ok) throw new Error('获取失败');
|
||||
const data = await response.json();
|
||||
this.topics = data || [];
|
||||
this.loadingTable = false;
|
||||
} catch (error) {
|
||||
console.log('使用模拟数据');
|
||||
console.log('获取选题失败,使用模拟数据');
|
||||
this.$message.error('获取选题失败,使用模拟数据');
|
||||
this.topics = [
|
||||
{ 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: '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('执行批量刷新'); },
|
||||
async triggerGenerateSelected() {
|
||||
@@ -170,21 +260,47 @@
|
||||
.then(async () => { this.$message.success('删除成功'); await this.fetchTopics(); }).catch(() => {});
|
||||
},
|
||||
handleLogout() { localStorage.removeItem('authToken'); window.location.href = '/'; },
|
||||
redirectToPage(page) { window.location.href = '/' + 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' }); }
|
||||
redirectToPage(page) { window.location.href = page.startsWith('/') ? page : '/' + page; },
|
||||
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() {
|
||||
console.log('[DEBUG] TopicsApp mounted');
|
||||
const token = localStorage.getItem('authToken');
|
||||
console.log('[DEBUG] Token exists:', !!token);
|
||||
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 } })
|
||||
.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 = '/'; });
|
||||
}
|
||||
};
|
||||
const app = Vue.createApp(TopicsApp);
|
||||
app.use(ElementPlus);
|
||||
app.mount('#app');
|
||||
</script>
|
||||
};
|
||||
const app = Vue.createApp(TopicsApp);
|
||||
app.use(ElementPlus);
|
||||
app.mount('#app');
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -29,6 +29,41 @@
|
||||
.mobile-nav { display: flex; }
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
@@ -56,7 +91,7 @@
|
||||
<h3 style="font-size: 18px; font-weight: 600;">用户列表</h3>
|
||||
<el-button type="primary" @click="addUser">+ 新建用户</el-button>
|
||||
</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="username" label="用户名"></el-table-column>
|
||||
<el-table-column prop="role" label="角色" width="100">
|
||||
|
||||
@@ -6,7 +6,7 @@ Collector 修复脚本
|
||||
|
||||
import sys
|
||||
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 datetime import datetime
|
||||
|
||||
@@ -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()}
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
print(f"✅ 已创建全新选题库:{len(topics)} 个选题")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
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
|
||||
result = run_creator('A05')
|
||||
print('RESULT:', result)
|
||||
|
||||
Reference in New Issue
Block a user