blob: 9817ac0fd13c40cc7d430050589595192a3680d2 [file] [log] [blame]
TRM-codingd1cbf672025-06-18 15:15:08 +08001// src/components/CreatePost.jsx
2
wu90da17b2025-06-19 12:45:29 +08003import React, { useState, useEffect } from 'react'
4import { useNavigate, useParams } from 'react-router-dom'
TRM-codingd1cbf672025-06-18 15:15:08 +08005import UploadPage from './UploadPage'
wu90da17b2025-06-19 12:45:29 +08006import {
7 createPost,
8 updatePost,
9 fetchPost as fetchPostDetail
10} from '../api/posts_wzy'
TRM-codingd1cbf672025-06-18 15:15:08 +080011import '../style/CreatePost.css'
12
13export default function CreatePost() {
14 const navigate = useNavigate()
wu90da17b2025-06-19 12:45:29 +080015 const { postId } = useParams()
16 const isEdit = Boolean(postId)
TRM-codingd1cbf672025-06-18 15:15:08 +080017
wu90da17b2025-06-19 12:45:29 +080018 // 步骤:新帖先上传,编辑则直接到 detail
19 const [step, setStep] = useState(isEdit ? 'detail' : 'upload')
20 const [files, setFiles] = useState([])
21 const [mediaUrls, setMediaUrls] = useState([])
TRM-codingd1cbf672025-06-18 15:15:08 +080022
wu90da17b2025-06-19 12:45:29 +080023 // 表单字段
TRM-codingd1cbf672025-06-18 15:15:08 +080024 const [title, setTitle] = useState('')
25 const [content, setContent] = useState('')
26 const [topicId, setTopicId] = useState('')
27 const [status, setStatus] = useState('published')
28
29 const [error, setError] = useState(null)
wu90da17b2025-06-19 12:45:29 +080030 const [loading, setLoading] = useState(isEdit)
TRM-codingd1cbf672025-06-18 15:15:08 +080031
wu90da17b2025-06-19 12:45:29 +080032 // 静态话题
TRM-codingd1cbf672025-06-18 15:15:08 +080033 const TOPICS = [
34 { id: 1, name: '世俱杯环球评大会' },
35 { id: 2, name: '我的REDmentor' },
36 { id: 3, name: '我染上了拼豆' },
TRM-codingd1cbf672025-06-18 15:15:08 +080037 ]
38
wu90da17b2025-06-19 12:45:29 +080039 // 编辑模式:拉取原帖数据填入
40 useEffect(() => {
41 if (!isEdit) return
42 fetchPostDetail(postId)
43 .then(data => {
44 setTitle(data.title)
45 setContent(data.content)
46 setTopicId(data.topic_id || '')
47 setStatus(data.status)
48 setMediaUrls(data.media_urls || [])
49 })
50 .catch(err => setError(err.message))
51 .finally(() => setLoading(false))
52 }, [isEdit, postId])
53
54 // 上传回调
TRM-codingd1cbf672025-06-18 15:15:08 +080055 const handleUploadComplete = async uploadedFiles => {
56 setFiles(uploadedFiles)
wu90da17b2025-06-19 12:45:29 +080057 // TODO: 真正上传到服务器后替换为服务端 URL
TRM-codingd1cbf672025-06-18 15:15:08 +080058 const urls = await Promise.all(
59 uploadedFiles.map(f => URL.createObjectURL(f))
60 )
61 setMediaUrls(urls)
TRM-codingd1cbf672025-06-18 15:15:08 +080062 setStep('detail')
63 }
64
wu90da17b2025-06-19 12:45:29 +080065 // 提交(创建/更新)
TRM-codingd1cbf672025-06-18 15:15:08 +080066 const handleSubmit = async () => {
67 if (!title.trim() || !content.trim()) {
68 setError('标题和正文必填')
69 return
70 }
71 setError(null)
72 try {
wu90da17b2025-06-19 12:45:29 +080073 if (isEdit) {
74 await updatePost(postId, {
75 title: title.trim(),
76 content: content.trim(),
77 topic_id: topicId || undefined,
78 media_urls: mediaUrls,
79 status
80 })
81 alert('更新成功!')
82 } else {
83 await createPost({
84 user_id: 1,
85 topic_id: topicId || undefined,
86 title: title.trim(),
87 content: content.trim(),
88 media_urls: mediaUrls,
89 status
90 })
91 alert('发布成功!')
92 }
93 navigate('/notebooks', { replace: true })
TRM-codingd1cbf672025-06-18 15:15:08 +080094 } catch (e) {
95 setError(e.message)
96 }
97 }
98
wu90da17b2025-06-19 12:45:29 +080099 if (loading) return <p>加载中…</p>
100 if (step === 'upload' && !isEdit) {
TRM-codingd1cbf672025-06-18 15:15:08 +0800101 return <UploadPage onComplete={handleUploadComplete} />
102 }
103
TRM-codingd1cbf672025-06-18 15:15:08 +0800104 return (
105 <div className="create-post">
wu90da17b2025-06-19 12:45:29 +0800106 <h2>{isEdit ? '编辑帖子' : '填写帖子内容'}</h2>
TRM-codingd1cbf672025-06-18 15:15:08 +0800107 {error && <div className="error">{error}</div>}
108
wu90da17b2025-06-19 12:45:29 +0800109 {/* 媒体预览 */}
TRM-codingd1cbf672025-06-18 15:15:08 +0800110 <div className="preview-media">
111 {mediaUrls.map((url, i) => (
112 <div key={i} className="preview-item">
wu90da17b2025-06-19 12:45:29 +0800113 {url.match(/\.(mp4|mov|avi)$/) ? (
TRM-codingd1cbf672025-06-18 15:15:08 +0800114 <video src={url} controls />
wu90da17b2025-06-19 12:45:29 +0800115 ) : (
116 <img src={url} alt={`预览 ${i}`} />
TRM-codingd1cbf672025-06-18 15:15:08 +0800117 )}
118 </div>
119 ))}
120 </div>
121
122 {/* 标题 */}
123 <label className="form-label">
124 标题(最多20字)
125 <input
126 type="text"
127 maxLength={20}
128 value={title}
129 onChange={e => setTitle(e.target.value)}
TRM-codingd1cbf672025-06-18 15:15:08 +0800130 />
131 <span className="char-count">{title.length}/20</span>
132 </label>
133
134 {/* 正文 */}
135 <label className="form-label">
136 正文(最多1000字)
137 <textarea
138 maxLength={1000}
139 value={content}
140 onChange={e => setContent(e.target.value)}
TRM-codingd1cbf672025-06-18 15:15:08 +0800141 />
142 <span className="char-count">{content.length}/1000</span>
143 </label>
144
145 {/* 话题选择 */}
146 <label className="form-label">
147 选择话题(可选)
148 <select
149 value={topicId}
150 onChange={e => setTopicId(e.target.value)}
151 >
152 <option value="">不添加话题</option>
153 {TOPICS.map(t => (
154 <option key={t.id} value={t.id}>
155 #{t.name}
156 </option>
157 ))}
158 </select>
159 </label>
160
161 {/* 发布状态 */}
162 <div className="status-group">
163 <label>
164 <input
165 type="radio"
166 name="status"
167 value="published"
168 checked={status === 'published'}
169 onChange={() => setStatus('published')}
170 />
171 立即发布
172 </label>
173 <label>
174 <input
175 type="radio"
176 name="status"
177 value="draft"
178 checked={status === 'draft'}
179 onChange={() => setStatus('draft')}
180 />
181 存为草稿
182 </label>
183 </div>
184
wu90da17b2025-06-19 12:45:29 +0800185 {/* 按钮 */}
TRM-codingd1cbf672025-06-18 15:15:08 +0800186 <div className="btn-group">
187 <button className="btn btn-primary" onClick={handleSubmit}>
wu90da17b2025-06-19 12:45:29 +0800188 {isEdit ? '更新' : '发布'}
TRM-codingd1cbf672025-06-18 15:15:08 +0800189 </button>
wu90da17b2025-06-19 12:45:29 +0800190 {!isEdit && (
191 <button className="btn btn-secondary" onClick={() => setStep('upload')}>
192 上一步
193 </button>
194 )}
TRM-codingd1cbf672025-06-18 15:15:08 +0800195 </div>
196 </div>
197 )
198}