新增管理帖子的组件
Change-Id: I7299ad6f735064dd112f92bab7c87daa952e6dc2
diff --git a/Merge/back_wzy/routes/posts.py b/Merge/back_wzy/routes/posts.py
index e01bdd8..14ff64c 100644
--- a/Merge/back_wzy/routes/posts.py
+++ b/Merge/back_wzy/routes/posts.py
@@ -1,4 +1,5 @@
# routes/posts.py
+
from flask import Blueprint, request, jsonify, abort
from extensions import db
from models.post import Post
@@ -16,10 +17,24 @@
@posts_bp.route('', methods=['GET'])
def list_posts():
- posts = Post.query.filter_by(status='published').all()
+ """
+ 获取帖子列表,支持:
+ - GET /posts 返回所有已发布帖子
+ - GET /posts?user_id=123 返回指定用户 user_id 的所有帖子
+ """
+ user_id = request.args.get('user_id', type=int)
+ query = Post.query
+ if user_id is not None:
+ query = query.filter_by(user_id=user_id)
+ else:
+ query = query.filter_by(status='published')
+
+ posts = query.all()
+
return jsonify([{
'id': p.id,
'title': p.title,
+ 'status': p.status, # 新增 status 字段
'heat': p.heat,
'created_at': p.created_at.isoformat()
} for p in posts])
@@ -30,6 +45,7 @@
return jsonify({
'id': post.id,
'user_id': post.user_id,
+ 'topic_id': post.topic_id,
'title': post.title,
'content': post.content,
'media_urls': post.media_urls,
@@ -42,20 +58,11 @@
@posts_bp.route('/<int:post_id>', methods=['PUT'])
def update_post(post_id):
"""
- 修改帖子
- URL 参数:
- post_id - 要修改的帖子 ID
- JSON Body 可选字段:
- title (string)
- content (string)
- topic_id (int) — 必须是 topics 表中已有的 ID
- media_urls (list) — 字符串数组
- status (string) — 'draft','pending','published','deleted','rejected'
+ 修改帖子字段(可选字段:title, content, topic_id, media_urls, status)
"""
post = Post.query.get_or_404(post_id)
data = request.get_json() or {}
- # 只更新客户端传来的字段
- for key in ('title','content','topic_id','media_urls','status'):
+ for key in ('title', 'content', 'topic_id', 'media_urls', 'status'):
if key in data:
setattr(post, key, data[key])
db.session.commit()
@@ -68,7 +75,6 @@
db.session.commit()
return '', 204
-
@posts_bp.route('/<int:post_id>/<action>', methods=['POST'])
def post_action(post_id, action):
"""
@@ -104,8 +110,6 @@
db.session.commit()
return '', 201
-
-# 取消点赞
@posts_bp.route('/<int:post_id>/like', methods=['DELETE'])
def unlike(post_id):
user_id = request.get_json(silent=True) and request.get_json().get('user_id')
@@ -127,7 +131,6 @@
db.session.commit()
return '', 204
-# 取消收藏
@posts_bp.route('/<int:post_id>/favorite', methods=['DELETE'])
def unfavorite(post_id):
user_id = request.get_json(silent=True) and request.get_json().get('user_id')
diff --git a/Merge/front/src/api/posts_wzy.js b/Merge/front/src/api/posts_wzy.js
index ae65756..b449c43 100644
--- a/Merge/front/src/api/posts_wzy.js
+++ b/Merge/front/src/api/posts_wzy.js
@@ -1,5 +1,5 @@
// src/api/posts.js
-const BASE = 'http://127.0.0.1:5714/' // 如果有代理可以留空,否则填完整域名,如 'http://localhost:3000'
+const BASE = 'http://10.126.59.25:5714/' // 如果有代理可以留空,否则填完整域名,如 'http://localhost:3000'
/**
* 获取所有已发布的帖子列表
diff --git a/Merge/front/src/components/CreatePost.jsx b/Merge/front/src/components/CreatePost.jsx
index 7519d5b..9817ac0 100644
--- a/Merge/front/src/components/CreatePost.jsx
+++ b/Merge/front/src/components/CreatePost.jsx
@@ -1,48 +1,68 @@
// src/components/CreatePost.jsx
-import React, { useState } from 'react'
-import { useNavigate } from 'react-router-dom'
+import React, { useState, useEffect } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
import UploadPage from './UploadPage'
-import { createPost } from '../api/posts_wzy'
+import {
+ createPost,
+ updatePost,
+ fetchPost as fetchPostDetail
+} from '../api/posts_wzy'
import '../style/CreatePost.css'
export default function CreatePost() {
const navigate = useNavigate()
+ const { postId } = useParams()
+ const isEdit = Boolean(postId)
- const [step, setStep] = useState('upload') // 'upload' | 'detail'
- const [files, setFiles] = useState([]) // 本地 File 对象列表
- const [mediaUrls, setMediaUrls] = useState([]) // 上传后得到的 URL 列表
+ // 步骤:新帖先上传,编辑则直接到 detail
+ const [step, setStep] = useState(isEdit ? 'detail' : 'upload')
+ const [files, setFiles] = useState([])
+ const [mediaUrls, setMediaUrls] = useState([])
- // 详情表单字段
+ // 表单字段
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [topicId, setTopicId] = useState('')
const [status, setStatus] = useState('published')
const [error, setError] = useState(null)
+ const [loading, setLoading] = useState(isEdit)
- // 静态话题数据
+ // 静态话题
const TOPICS = [
{ id: 1, name: '世俱杯环球评大会' },
{ id: 2, name: '我的REDmentor' },
{ id: 3, name: '我染上了拼豆' },
- // …更多静态话题…
]
- // 上传页面回调 —— 上传完成后切换到“填写详情”步骤
+ // 编辑模式:拉取原帖数据填入
+ useEffect(() => {
+ if (!isEdit) return
+ fetchPostDetail(postId)
+ .then(data => {
+ setTitle(data.title)
+ setContent(data.content)
+ setTopicId(data.topic_id || '')
+ setStatus(data.status)
+ setMediaUrls(data.media_urls || [])
+ })
+ .catch(err => setError(err.message))
+ .finally(() => setLoading(false))
+ }, [isEdit, postId])
+
+ // 上传回调
const handleUploadComplete = async uploadedFiles => {
setFiles(uploadedFiles)
-
- // TODO: 改成真实上传逻辑,拿到真正的 media_urls
+ // TODO: 真正上传到服务器后替换为服务端 URL
const urls = await Promise.all(
uploadedFiles.map(f => URL.createObjectURL(f))
)
setMediaUrls(urls)
-
setStep('detail')
}
- // 发布按钮
+ // 提交(创建/更新)
const handleSubmit = async () => {
if (!title.trim() || !content.trim()) {
setError('标题和正文必填')
@@ -50,40 +70,50 @@
}
setError(null)
try {
- await createPost({
- user_id: 1,
- topic_id: topicId || undefined,
- title: title.trim(),
- content: content.trim(),
- media_urls: mediaUrls,
- status
- })
- // 发布成功后跳转回首页
- navigate('/home', { replace: true })
+ if (isEdit) {
+ await updatePost(postId, {
+ title: title.trim(),
+ content: content.trim(),
+ topic_id: topicId || undefined,
+ media_urls: mediaUrls,
+ status
+ })
+ alert('更新成功!')
+ } else {
+ await createPost({
+ user_id: 1,
+ topic_id: topicId || undefined,
+ title: title.trim(),
+ content: content.trim(),
+ media_urls: mediaUrls,
+ status
+ })
+ alert('发布成功!')
+ }
+ navigate('/notebooks', { replace: true })
} catch (e) {
setError(e.message)
}
}
- // 渲染上传页
- if (step === 'upload') {
+ if (loading) return <p>加载中…</p>
+ if (step === 'upload' && !isEdit) {
return <UploadPage onComplete={handleUploadComplete} />
}
- // 渲染详情页
return (
<div className="create-post">
- <h2>填写帖子内容</h2>
+ <h2>{isEdit ? '编辑帖子' : '填写帖子内容'}</h2>
{error && <div className="error">{error}</div>}
- {/* 已上传媒体预览 */}
+ {/* 媒体预览 */}
<div className="preview-media">
{mediaUrls.map((url, i) => (
<div key={i} className="preview-item">
- {files[i].type.startsWith('image/') ? (
- <img src={url} alt={`预览 ${i}`} />
- ) : (
+ {url.match(/\.(mp4|mov|avi)$/) ? (
<video src={url} controls />
+ ) : (
+ <img src={url} alt={`预览 ${i}`} />
)}
</div>
))}
@@ -97,7 +127,6 @@
maxLength={20}
value={title}
onChange={e => setTitle(e.target.value)}
- placeholder="填写标题会有更多赞哦~"
/>
<span className="char-count">{title.length}/20</span>
</label>
@@ -109,7 +138,6 @@
maxLength={1000}
value={content}
onChange={e => setContent(e.target.value)}
- placeholder="输入正文描述,真诚有价值的分享予人温暖"
/>
<span className="char-count">{content.length}/1000</span>
</label>
@@ -154,14 +182,16 @@
</label>
</div>
- {/* 操作按钮 */}
+ {/* 按钮 */}
<div className="btn-group">
<button className="btn btn-primary" onClick={handleSubmit}>
- 发布
+ {isEdit ? '更新' : '发布'}
</button>
- <button className="btn btn-secondary" onClick={() => setStep('upload')}>
- 上一步
- </button>
+ {!isEdit && (
+ <button className="btn btn-secondary" onClick={() => setStep('upload')}>
+ 上一步
+ </button>
+ )}
</div>
</div>
)
diff --git a/Merge/front/src/components/NotebookPage.jsx b/Merge/front/src/components/NotebookPage.jsx
new file mode 100644
index 0000000..25264ec
--- /dev/null
+++ b/Merge/front/src/components/NotebookPage.jsx
@@ -0,0 +1,85 @@
+// src/components/NotebookPage.jsx
+
+import React, { useState, useEffect } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { fetchPosts, deletePost } from '../api/posts_wzy'
+import '../style/NotebookPage.css'
+
+export default function NotebookPage() {
+ const navigate = useNavigate()
+ const [posts, setPosts] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ // TODO: 替换成真实用户 ID
+ const currentUserId = 2
+
+ useEffect(() => {
+ async function load() {
+ try {
+ // GET /posts?user_id=1
+ const list = await fetchPosts(currentUserId)
+ setPosts(list)
+ } catch (e) {
+ setError(e.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+ load()
+ }, [])
+
+ async function handleDelete(id) {
+ if (!window.confirm('确定要删除该帖子吗?')) return
+ try {
+ await deletePost(id)
+ setPosts(posts.filter(p => p.id !== id))
+ alert('删除成功')
+ } catch (e) {
+ alert('删除失败:' + e.message)
+ }
+ }
+
+ function handleEdit(id) {
+ // 假设你在路由里挂载了 /posts/edit/:postId
+ navigate(`/posts/edit/${id}`)
+ }
+
+ if (loading) return <p>加载中…</p>
+ if (error) return <p className="error">加载失败:{error}</p>
+
+ return (
+ <div className="notebook-page">
+ <h2>我的帖子管理</h2>
+ {posts.length === 0 ? (
+ <p>暂无帖子</p>
+ ) : (
+ <table className="post-table">
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>标题</th>
+ <th>状态</th>
+ <th>创建时间</th>
+ <th>操作</th>
+ </tr>
+ </thead>
+ <tbody>
+ {posts.map(p => (
+ <tr key={p.id}>
+ <td>{p.id}</td>
+ <td>{p.title}</td>
+ <td>{p.status}</td>
+ <td>{new Date(p.created_at).toLocaleString()}</td>
+ <td>
+ <button onClick={() => handleEdit(p.id)}>编辑</button>
+ <button onClick={() => handleDelete(p.id)}>删除</button>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ )}
+ </div>
+ )
+}
diff --git a/Merge/front/src/router/App.js b/Merge/front/src/router/App.js
index 3137fd8..d91b3b7 100644
--- a/Merge/front/src/router/App.js
+++ b/Merge/front/src/router/App.js
@@ -23,6 +23,7 @@
import TransactionLogs from '../components/TransactionLogs';
import PerformanceLogs from '../components/PerformanceLogs';
+import NotebookPage from '../components/NotebookPage'
export default function AppRoutes() {
return (
@@ -31,7 +32,7 @@
<Route path="/home" element={<HomeFeed />} />
- <Route path="/notebooks" element={<PlaceholderPage pageId="notebooks" />} />
+ <Route path="/notebooks" element={<NotebookPage />} />
<Route path="/activity" element={<PlaceholderPage pageId="activity" />} />
<Route path="/notes" element={<PlaceholderPage pageId="notes" />} />
<Route path="/creator" element={<PlaceholderPage pageId="creator" />} />
diff --git a/Merge/front/src/style/NotebookPage.css b/Merge/front/src/style/NotebookPage.css
new file mode 100644
index 0000000..c47501a
--- /dev/null
+++ b/Merge/front/src/style/NotebookPage.css
@@ -0,0 +1,33 @@
+.notebook-page {
+ padding: 20px;
+}
+
+.post-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.post-table th,
+.post-table td {
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: left;
+}
+
+.post-table th {
+ background: #f5f5f5;
+}
+
+.post-table button {
+ margin-right: 8px;
+ padding: 4px 8px;
+ border: none;
+ background: #ff4757;
+ color: white;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.post-table button:hover {
+ background: #ff3742;
+}
diff --git a/WZY/xhs_front/src/components/CreatePost.jsx b/WZY/xhs_front/src/components/CreatePost.jsx
index 51635c2..49e75d2 100644
--- a/WZY/xhs_front/src/components/CreatePost.jsx
+++ b/WZY/xhs_front/src/components/CreatePost.jsx
@@ -1,48 +1,68 @@
// src/components/CreatePost.jsx
-import React, { useState } from 'react'
-import { useNavigate } from 'react-router-dom'
+import React, { useState, useEffect } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
import UploadPage from './UploadPage'
-import { createPost } from '../api/posts'
+import {
+ createPost,
+ updatePost,
+ fetchPost as fetchPostDetail
+} from '../api/posts'
import '../style/CreatePost.css'
export default function CreatePost() {
const navigate = useNavigate()
+ const { postId } = useParams()
+ const isEdit = Boolean(postId)
- const [step, setStep] = useState('upload') // 'upload' | 'detail'
- const [files, setFiles] = useState([]) // 本地 File 对象列表
- const [mediaUrls, setMediaUrls] = useState([]) // 上传后得到的 URL 列表
+ // 步骤:新帖先上传,编辑则直接到 detail
+ const [step, setStep] = useState(isEdit ? 'detail' : 'upload')
+ const [files, setFiles] = useState([])
+ const [mediaUrls, setMediaUrls] = useState([])
- // 详情表单字段
+ // 表单字段
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [topicId, setTopicId] = useState('')
const [status, setStatus] = useState('published')
const [error, setError] = useState(null)
+ const [loading, setLoading] = useState(isEdit)
- // 静态话题数据
+ // 静态话题
const TOPICS = [
{ id: 1, name: '世俱杯环球评大会' },
{ id: 2, name: '我的REDmentor' },
{ id: 3, name: '我染上了拼豆' },
- // …更多静态话题…
]
- // 上传页面回调 —— 上传完成后切换到“填写详情”步骤
+ // 编辑模式:拉取原帖数据填入
+ useEffect(() => {
+ if (!isEdit) return
+ fetchPostDetail(postId)
+ .then(data => {
+ setTitle(data.title)
+ setContent(data.content)
+ setTopicId(data.topic_id || '')
+ setStatus(data.status)
+ setMediaUrls(data.media_urls || [])
+ })
+ .catch(err => setError(err.message))
+ .finally(() => setLoading(false))
+ }, [isEdit, postId])
+
+ // 上传回调
const handleUploadComplete = async uploadedFiles => {
setFiles(uploadedFiles)
-
- // TODO: 改成真实上传逻辑,拿到真正的 media_urls
+ // TODO: 真正上传到服务器后替换为服务端 URL
const urls = await Promise.all(
uploadedFiles.map(f => URL.createObjectURL(f))
)
setMediaUrls(urls)
-
setStep('detail')
}
- // 发布按钮
+ // 提交(创建/更新)
const handleSubmit = async () => {
if (!title.trim() || !content.trim()) {
setError('标题和正文必填')
@@ -50,40 +70,50 @@
}
setError(null)
try {
- await createPost({
- user_id: 1,
- topic_id: topicId || undefined,
- title: title.trim(),
- content: content.trim(),
- media_urls: mediaUrls,
- status
- })
- // 发布成功后跳转回首页
- navigate('/home', { replace: true })
+ if (isEdit) {
+ await updatePost(postId, {
+ title: title.trim(),
+ content: content.trim(),
+ topic_id: topicId || undefined,
+ media_urls: mediaUrls,
+ status
+ })
+ alert('更新成功!')
+ } else {
+ await createPost({
+ user_id: 1,
+ topic_id: topicId || undefined,
+ title: title.trim(),
+ content: content.trim(),
+ media_urls: mediaUrls,
+ status
+ })
+ alert('发布成功!')
+ }
+ navigate('/notebooks', { replace: true })
} catch (e) {
setError(e.message)
}
}
- // 渲染上传页
- if (step === 'upload') {
+ if (loading) return <p>加载中…</p>
+ if (step === 'upload' && !isEdit) {
return <UploadPage onComplete={handleUploadComplete} />
}
- // 渲染详情页
return (
<div className="create-post">
- <h2>填写帖子内容</h2>
+ <h2>{isEdit ? '编辑帖子' : '填写帖子内容'}</h2>
{error && <div className="error">{error}</div>}
- {/* 已上传媒体预览 */}
+ {/* 媒体预览 */}
<div className="preview-media">
{mediaUrls.map((url, i) => (
<div key={i} className="preview-item">
- {files[i].type.startsWith('image/') ? (
- <img src={url} alt={`预览 ${i}`} />
- ) : (
+ {url.match(/\.(mp4|mov|avi)$/) ? (
<video src={url} controls />
+ ) : (
+ <img src={url} alt={`预览 ${i}`} />
)}
</div>
))}
@@ -97,7 +127,6 @@
maxLength={20}
value={title}
onChange={e => setTitle(e.target.value)}
- placeholder="填写标题会有更多赞哦~"
/>
<span className="char-count">{title.length}/20</span>
</label>
@@ -109,7 +138,6 @@
maxLength={1000}
value={content}
onChange={e => setContent(e.target.value)}
- placeholder="输入正文描述,真诚有价值的分享予人温暖"
/>
<span className="char-count">{content.length}/1000</span>
</label>
@@ -154,14 +182,16 @@
</label>
</div>
- {/* 操作按钮 */}
+ {/* 按钮 */}
<div className="btn-group">
<button className="btn btn-primary" onClick={handleSubmit}>
- 发布
+ {isEdit ? '更新' : '发布'}
</button>
- <button className="btn btn-secondary" onClick={() => setStep('upload')}>
- 上一步
- </button>
+ {!isEdit && (
+ <button className="btn btn-secondary" onClick={() => setStep('upload')}>
+ 上一步
+ </button>
+ )}
</div>
</div>
)
diff --git a/WZY/xhs_front/src/components/NotebookPage.jsx b/WZY/xhs_front/src/components/NotebookPage.jsx
new file mode 100644
index 0000000..80abc3f
--- /dev/null
+++ b/WZY/xhs_front/src/components/NotebookPage.jsx
@@ -0,0 +1,85 @@
+// src/components/NotebookPage.jsx
+
+import React, { useState, useEffect } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { fetchPosts, deletePost } from '../api/posts'
+import '../style/NotebookPage.css'
+
+export default function NotebookPage() {
+ const navigate = useNavigate()
+ const [posts, setPosts] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ // TODO: 替换成真实用户 ID
+ const currentUserId = 1
+
+ useEffect(() => {
+ async function load() {
+ try {
+ // GET /posts?user_id=1
+ const list = await fetchPosts(currentUserId)
+ setPosts(list)
+ } catch (e) {
+ setError(e.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+ load()
+ }, [])
+
+ async function handleDelete(id) {
+ if (!window.confirm('确定要删除该帖子吗?')) return
+ try {
+ await deletePost(id)
+ setPosts(posts.filter(p => p.id !== id))
+ alert('删除成功')
+ } catch (e) {
+ alert('删除失败:' + e.message)
+ }
+ }
+
+ function handleEdit(id) {
+ // 假设你在路由里挂载了 /posts/edit/:postId
+ navigate(`/posts/edit/${id}`)
+ }
+
+ if (loading) return <p>加载中…</p>
+ if (error) return <p className="error">加载失败:{error}</p>
+
+ return (
+ <div className="notebook-page">
+ <h2>我的帖子管理</h2>
+ {posts.length === 0 ? (
+ <p>暂无帖子</p>
+ ) : (
+ <table className="post-table">
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>标题</th>
+ <th>状态</th>
+ <th>创建时间</th>
+ <th>操作</th>
+ </tr>
+ </thead>
+ <tbody>
+ {posts.map(p => (
+ <tr key={p.id}>
+ <td>{p.id}</td>
+ <td>{p.title}</td>
+ <td>{p.status}</td>
+ <td>{new Date(p.created_at).toLocaleString()}</td>
+ <td>
+ <button onClick={() => handleEdit(p.id)}>编辑</button>
+ <button onClick={() => handleDelete(p.id)}>删除</button>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ )}
+ </div>
+ )
+}
diff --git a/WZY/xhs_front/src/router/index.jsx b/WZY/xhs_front/src/router/index.jsx
index 077f8ed..fd959b9 100644
--- a/WZY/xhs_front/src/router/index.jsx
+++ b/WZY/xhs_front/src/router/index.jsx
@@ -1,33 +1,30 @@
// src/router/index.jsx
+
import React from 'react'
import { Routes, Route, Navigate } from 'react-router-dom'
-
-// 确认这些路径和文件名与你项目里是一一对应的
-import CreatePost from '../components/CreatePost' // src/components/CreatePost.jsx
-import HomeFeed from '../components/HomeFeed' // src/components/HomeFeed.jsx
-import PlaceholderPage from '../components/PlaceholderPage'// src/components/PlaceholderPage.jsx
-import UploadPage from '../components/UploadPage' // src/components/UploadPage.jsx
+import CreatePost from '../components/CreatePost'
+import HomeFeed from '../components/HomeFeed'
+import NotebookPage from '../components/NotebookPage'
+import UploadPage from '../components/UploadPage'
+import PlaceholderPage from '../components/PlaceholderPage'
export default function AppRouter() {
return (
<Routes>
- {/* 一定要放在最前面,防止被 /* 等 catch-all 覆盖 */}
- <Route path="/posts/new" element={<CreatePost />} />
+ {/* 新帖 & 编辑帖(/:postId 可选) */}
+ <Route path="/posts/new" element={<CreatePost />} />
+ <Route path="/posts/edit/:postId" element={<CreatePost />} />
<Route path="/home" element={<HomeFeed />} />
+ <Route path="/notebooks" element={<NotebookPage />} />
+ <Route path="/activity" element={<PlaceholderPage pageId="activity" />} />
+ <Route path="/notes" element={<PlaceholderPage pageId="notes" />} />
+ <Route path="/creator" element={<PlaceholderPage pageId="creator" />} />
+ <Route path="/journal" element={<PlaceholderPage pageId="journal" />} />
- <Route path="/notebooks" element={<PlaceholderPage pageId="notebooks" />} />
- <Route path="/activity" element={<PlaceholderPage pageId="activity" />} />
- <Route path="/notes" element={<PlaceholderPage pageId="notes" />} />
- <Route path="/creator" element={<PlaceholderPage pageId="creator" />} />
- <Route path="/journal" element={<PlaceholderPage pageId="journal" />} />
+ <Route path="/dashboard/*" element={<PlaceholderPage />} />
- <Route path="/dashboard/*" element={<UploadPage />} />
-
- {/* 根路径重定向到 dashboard */}
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
-
- {/* 最后一个兜底 */}
<Route path="*" element={<PlaceholderPage pageId="home" />} />
</Routes>
)
diff --git a/WZY/xhs_front/src/style/NotebookPage.css b/WZY/xhs_front/src/style/NotebookPage.css
new file mode 100644
index 0000000..c47501a
--- /dev/null
+++ b/WZY/xhs_front/src/style/NotebookPage.css
@@ -0,0 +1,33 @@
+.notebook-page {
+ padding: 20px;
+}
+
+.post-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.post-table th,
+.post-table td {
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: left;
+}
+
+.post-table th {
+ background: #f5f5f5;
+}
+
+.post-table button {
+ margin-right: 8px;
+ padding: 4px 8px;
+ border: none;
+ background: #ff4757;
+ color: white;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.post-table button:hover {
+ background: #ff3742;
+}
diff --git a/WZY/xhs_server/__pycache__/config.cpython-312.pyc b/WZY/xhs_server/__pycache__/config.cpython-312.pyc
index 4e1a3be..262103a 100644
--- a/WZY/xhs_server/__pycache__/config.cpython-312.pyc
+++ b/WZY/xhs_server/__pycache__/config.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/__pycache__/extensions.cpython-312.pyc b/WZY/xhs_server/__pycache__/extensions.cpython-312.pyc
index 27ba8c2..0743449 100644
--- a/WZY/xhs_server/__pycache__/extensions.cpython-312.pyc
+++ b/WZY/xhs_server/__pycache__/extensions.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/__init__.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/__init__.cpython-312.pyc
index fe53b2a..6b72b82 100644
--- a/WZY/xhs_server/models/__pycache__/__init__.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/behavior.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/behavior.cpython-312.pyc
index 71f3757..d8b7d89 100644
--- a/WZY/xhs_server/models/__pycache__/behavior.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/behavior.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/comment.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/comment.cpython-312.pyc
index 393c6d3..c67a633 100644
--- a/WZY/xhs_server/models/__pycache__/comment.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/comment.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/post.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/post.cpython-312.pyc
index 1d64737..ce4a560 100644
--- a/WZY/xhs_server/models/__pycache__/post.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/post.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/tag.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/tag.cpython-312.pyc
index d76c0e0..2c109f9 100644
--- a/WZY/xhs_server/models/__pycache__/tag.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/tag.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/topic.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/topic.cpython-312.pyc
index a779595..053f4bd 100644
--- a/WZY/xhs_server/models/__pycache__/topic.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/topic.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/models/__pycache__/user.cpython-312.pyc b/WZY/xhs_server/models/__pycache__/user.cpython-312.pyc
index e3841c7..1d18d94 100644
--- a/WZY/xhs_server/models/__pycache__/user.cpython-312.pyc
+++ b/WZY/xhs_server/models/__pycache__/user.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/routes/__pycache__/__init__.cpython-312.pyc b/WZY/xhs_server/routes/__pycache__/__init__.cpython-312.pyc
index acde7ed..e463ed3 100644
--- a/WZY/xhs_server/routes/__pycache__/__init__.cpython-312.pyc
+++ b/WZY/xhs_server/routes/__pycache__/__init__.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/routes/__pycache__/comments.cpython-312.pyc b/WZY/xhs_server/routes/__pycache__/comments.cpython-312.pyc
index 4bd83ee..94555ac 100644
--- a/WZY/xhs_server/routes/__pycache__/comments.cpython-312.pyc
+++ b/WZY/xhs_server/routes/__pycache__/comments.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/routes/__pycache__/posts.cpython-312.pyc b/WZY/xhs_server/routes/__pycache__/posts.cpython-312.pyc
index a957747..ac244d0 100644
--- a/WZY/xhs_server/routes/__pycache__/posts.cpython-312.pyc
+++ b/WZY/xhs_server/routes/__pycache__/posts.cpython-312.pyc
Binary files differ
diff --git a/WZY/xhs_server/routes/posts.py b/WZY/xhs_server/routes/posts.py
index e01bdd8..11c0b9e 100644
--- a/WZY/xhs_server/routes/posts.py
+++ b/WZY/xhs_server/routes/posts.py
@@ -1,4 +1,5 @@
# routes/posts.py
+
from flask import Blueprint, request, jsonify, abort
from extensions import db
from models.post import Post
@@ -16,11 +17,24 @@
@posts_bp.route('', methods=['GET'])
def list_posts():
- posts = Post.query.filter_by(status='published').all()
+ """
+ 获取帖子列表,支持:
+ - GET /posts 返回所有已发布帖子
+ - GET /posts?user_id=123 返回指定用户 user_id 的所有帖子
+ """
+ user_id = request.args.get('user_id', type=int)
+ query = Post.query
+ if user_id is not None:
+ query = query.filter_by(user_id=user_id)
+ else:
+ query = query.filter_by(status='published')
+ posts = query.all()
+
return jsonify([{
'id': p.id,
'title': p.title,
'heat': p.heat,
+ 'status': p.status,
'created_at': p.created_at.isoformat()
} for p in posts])
@@ -30,6 +44,7 @@
return jsonify({
'id': post.id,
'user_id': post.user_id,
+ 'topic_id': post.topic_id,
'title': post.title,
'content': post.content,
'media_urls': post.media_urls,
@@ -42,20 +57,11 @@
@posts_bp.route('/<int:post_id>', methods=['PUT'])
def update_post(post_id):
"""
- 修改帖子
- URL 参数:
- post_id - 要修改的帖子 ID
- JSON Body 可选字段:
- title (string)
- content (string)
- topic_id (int) — 必须是 topics 表中已有的 ID
- media_urls (list) — 字符串数组
- status (string) — 'draft','pending','published','deleted','rejected'
+ 修改帖子字段(可选字段:title, content, topic_id, media_urls, status)
"""
post = Post.query.get_or_404(post_id)
data = request.get_json() or {}
- # 只更新客户端传来的字段
- for key in ('title','content','topic_id','media_urls','status'):
+ for key in ('title', 'content', 'topic_id', 'media_urls', 'status'):
if key in data:
setattr(post, key, data[key])
db.session.commit()
@@ -68,7 +74,6 @@
db.session.commit()
return '', 204
-
@posts_bp.route('/<int:post_id>/<action>', methods=['POST'])
def post_action(post_id, action):
"""
@@ -104,8 +109,6 @@
db.session.commit()
return '', 201
-
-# 取消点赞
@posts_bp.route('/<int:post_id>/like', methods=['DELETE'])
def unlike(post_id):
user_id = request.get_json(silent=True) and request.get_json().get('user_id')
@@ -127,7 +130,6 @@
db.session.commit()
return '', 204
-# 取消收藏
@posts_bp.route('/<int:post_id>/favorite', methods=['DELETE'])
def unfavorite(post_id):
user_id = request.get_json(silent=True) and request.get_json().get('user_id')