新增管理帖子的组件

Change-Id: I7299ad6f735064dd112f92bab7c87daa952e6dc2
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;
+}