blob: 4e2b5dca7d59060726260197291089c9c22e5abd [file] [log] [blame]
meisiyu1d4aade2025-06-02 20:10:36 +08001import React, { useState, useEffect } from 'react';
2import {
3 Card,
4 Tabs,
5 Button,
6 Table,
7 Modal,
8 Form,
9 Input,
10 Select,
11 Upload,
12 message,
13 Tag,
14 Space,
15 Popconfirm,
16 Row,
17 Col,
18 Radio,
19 InputNumber,
20 Image
21} from 'antd';
22import {
23 PlusOutlined,
24 EditOutlined,
25 DeleteOutlined,
26 EyeOutlined,
27 UploadOutlined,
28 HeartOutlined,
29 LoadingOutlined
30} from '@ant-design/icons';
31import { useNavigate } from 'react-router-dom';
32import {
33 getMyPosts,
34 getMyFavorites,
35 publishPost,
36 updatePost,
37 deletePost,
38 getAvailableTags,
39 uploadImage,
40 deleteImage,
41 getPromotionPlans,
42 createPayment,
43 getPromotionStatus,
44 confirmPayment,
45 cancelPayment
46} from '@/services/post';
47import PostCard from '../PostCenter/PostCard';
48import styles from './index.module.css';
49
50const { TabPane } = Tabs;
51const { TextArea } = Input;
52const { Option } = Select;
53
54interface PostFormData {
55 title: string;
56 content: string;
57 summary: string;
58 tags: string[] | string;
59 promotionPlan?: number;
60 coverImage?: string;
61}
62
63interface PromotionPlan {
64 id: number;
65 name: string;
66 description: string;
67 price: number;
68 duration: number;
69}
70
71interface PaymentRecord {
72 paymentId: number;
73 postId: number;
74 planId: number;
75 userId: number;
76 amount: number;
77 paymentStatus: string;
78 paymentTime: string;
79}
80
81const UserCenter: React.FC = () => {
82 const navigate = useNavigate();
83 const [activeTab, setActiveTab] = useState('myPosts');
84 const [publishModalVisible, setPublishModalVisible] = useState(false);
85 const [editModalVisible, setEditModalVisible] = useState(false);
86 const [paymentModalVisible, setPaymentModalVisible] = useState(false);
87 const [myPosts, setMyPosts] = useState<API.Post.PostInfo[]>([]);
88 const [favorites, setFavorites] = useState<API.Post.PostInfo[]>([]);
89 const [loading, setLoading] = useState(false);
90 const [form] = Form.useForm();
91 const [editForm] = Form.useForm();
92 const [selectedPromotion, setSelectedPromotion] = useState<PromotionPlan | null>(null);
93 const [currentEditPost, setCurrentEditPost] = useState<API.Post.PostInfo | null>(null);
94 const [availableTags, setAvailableTags] = useState<API.Post.PostTag[]>([]);
95 const [promotionPlans, setPromotionPlans] = useState<PromotionPlan[]>([]);
96 const [uploadLoading, setUploadLoading] = useState(false);
97 const [editUploadLoading, setEditUploadLoading] = useState(false);
98 const [coverImageUrl, setCoverImageUrl] = useState<string>('');
99 const [editCoverImageUrl, setEditCoverImageUrl] = useState<string>('');
100 const [currentPayment, setCurrentPayment] = useState<PaymentRecord | null>(null);
101 const [isEditingPromotion, setIsEditingPromotion] = useState(false);
meisiyud83081c2025-06-05 22:25:36 +0800102 const [currentPublishingPostId, setCurrentPublishingPostId] = useState<number | null>(null); // 跟踪当前正在发布的帖子ID
meisiyu1d4aade2025-06-02 20:10:36 +0800103
104 useEffect(() => {
105 if (activeTab === 'myPosts') {
106 fetchMyPosts();
107 } else if (activeTab === 'favorites') {
108 fetchFavorites();
109 }
110 }, [activeTab]);
111
112 useEffect(() => {
113 fetchAvailableTags();
114 fetchPromotionPlans();
115 }, []);
116
117 const fetchMyPosts = async () => {
118 setLoading(true);
119 try {
120 const response = await getMyPosts({ pageNum: 1, pageSize: 100 });
121 if (response.code === 200) {
122 setMyPosts(response.rows || []);
123 } else {
124 message.error(response.msg || '获取我的帖子失败');
125 }
126 } catch (error) {
127 message.error('获取我的帖子失败');
128 } finally {
129 setLoading(false);
130 }
131 };
132
133 const fetchFavorites = async () => {
134 setLoading(true);
135 try {
136 const response = await getMyFavorites({ pageNum: 1, pageSize: 100 });
137 if (response.code === 200) {
138 setFavorites(response.rows || []);
139 } else {
140 message.error(response.msg || '获取收藏列表失败');
141 }
142 } catch (error) {
143 message.error('获取收藏列表失败');
144 } finally {
145 setLoading(false);
146 }
147 };
148
149 const fetchAvailableTags = async () => {
150 try {
151 const response = await getAvailableTags();
152 if (response.code === 200) {
153 setAvailableTags(response.data || []);
154 } else {
155 message.error(response.msg || '获取可用标签失败');
156 }
157 } catch (error) {
158 message.error('获取可用标签失败');
159 }
160 };
161
162 const fetchPromotionPlans = async () => {
163 try {
164 const response = await getPromotionPlans();
165 if (response.code === 200) {
166 setPromotionPlans(response.data || []);
167 }
168 } catch (error) {
169 console.error('获取推广计划失败:', error);
170 }
171 };
172
173 const handlePublishPost = async (values: PostFormData) => {
174 try {
meisiyud83081c2025-06-05 22:25:36 +0800175 // 如果选择了推广计划
176 if (values.promotionPlan) {
177 const selectedPlan = promotionPlans.find(p => p.id === values.promotionPlan);
178 if (selectedPlan) {
179 setSelectedPromotion(selectedPlan);
180
181 let postId = currentPublishingPostId;
182
183 // 如果还没有创建帖子,先创建帖子
184 if (!postId) {
185 const postData = {
186 title: values.title,
187 content: values.content,
188 summary: values.summary,
189 tags: Array.isArray(values.tags) ? values.tags.join(',') : values.tags,
190 coverImage: coverImageUrl || undefined
191 // 注意:这里不包含promotionPlan,等支付成功后再更新
192 };
193
194 const publishResponse = await publishPost(postData);
195 if (publishResponse.code === 200) {
196 postId = publishResponse.data?.postId;
197 if (postId) {
198 setCurrentPublishingPostId(postId); // 保存已创建的帖子ID
199 } else {
200 message.error('帖子发布成功但无法获取帖子ID');
201 return;
202 }
203 } else {
204 message.error(publishResponse.msg || '帖子发布失败');
205 return;
206 }
207 }
208
209 // 使用帖子ID创建支付记录
210 if (postId) {
211 const paymentResponse = await createPayment({
212 postId: postId,
213 planId: selectedPlan.id,
214 amount: selectedPlan.price
215 });
216
217 if (paymentResponse.code === 200) {
218 setCurrentPayment(paymentResponse.data);
219 setPaymentModalVisible(true);
220 return;
221 } else {
222 message.error(paymentResponse.msg || '创建支付记录失败');
223 return;
224 }
225 }
meisiyu1d4aade2025-06-02 20:10:36 +0800226 } else {
meisiyud83081c2025-06-05 22:25:36 +0800227 message.error('无效的推广计划');
meisiyu1d4aade2025-06-02 20:10:36 +0800228 return;
229 }
230 }
231
meisiyud83081c2025-06-05 22:25:36 +0800232 // 直接发布帖子(没有选择推广)
meisiyu1d4aade2025-06-02 20:10:36 +0800233 await submitPost(values);
234 } catch (error) {
235 message.error('发布帖子失败');
236 }
237 };
238
239 const submitPost = async (values: PostFormData) => {
240 try {
241 // 处理标签格式
242 const tagsString = Array.isArray(values.tags) ? values.tags.join(',') : values.tags;
243
meisiyud83081c2025-06-05 22:25:36 +0800244 // 如果有选择的推广计划,使用推广计划ID,否则使用表单中的值
245 const promotionPlanId = selectedPromotion?.id || values.promotionPlan;
246
meisiyu1d4aade2025-06-02 20:10:36 +0800247 const postData = {
248 title: values.title,
249 content: values.content,
250 summary: values.summary,
251 tags: tagsString,
meisiyud83081c2025-06-05 22:25:36 +0800252 promotionPlan: promotionPlanId,
meisiyu1d4aade2025-06-02 20:10:36 +0800253 coverImage: coverImageUrl || undefined
254 };
255
256 const response = await publishPost(postData);
257 if (response.code === 200) {
258 message.success('帖子发布成功');
259 setPublishModalVisible(false);
260 form.resetFields();
261 setSelectedPromotion(null);
262 setCoverImageUrl('');
263 fetchMyPosts();
264 } else {
265 message.error(response.msg || '发布帖子失败');
266 }
267 } catch (error) {
268 message.error('发布帖子失败');
269 }
270 };
271
272 const handleEditPost = async (post: API.Post.PostInfo) => {
273 setCurrentEditPost(post);
274 const tagsArray = post.tags ? (typeof post.tags === 'string' ? post.tags.split(',') : post.tags) : [];
275
276 // 检查推广状态
277 try {
278 const promotionResponse = await getPromotionStatus(post.postId || post.id || 0);
279 if (promotionResponse.code === 200) {
280 const { hasPromotion, promotionPlanId } = promotionResponse.data;
281 setIsEditingPromotion(hasPromotion);
282
283 editForm.setFieldsValue({
284 title: post.title,
285 content: post.content,
286 summary: post.summary,
287 tags: tagsArray,
288 promotionPlan: hasPromotion ? promotionPlanId : undefined
289 });
290 }
291 } catch (error) {
292 console.error('获取推广状态失败:', error);
293 editForm.setFieldsValue({
294 title: post.title,
295 content: post.content,
296 summary: post.summary,
297 tags: tagsArray,
298 promotionPlan: post.promotionPlanId
299 });
300 }
301
302 setEditCoverImageUrl(post.coverImage || '');
303 setEditModalVisible(true);
304 };
305
306 const handleUpdatePost = async (values: any) => {
307 if (!currentEditPost) return;
308
309 try {
310 // 处理标签格式
311 const tagsString = Array.isArray(values.tags) ? values.tags.join(',') : values.tags;
312
313 // 检查是否选择了新的推广计划
314 const hasNewPromotion = values.promotionPlan && !isEditingPromotion;
315
316 if (hasNewPromotion) {
317 // 如果选择了新的推广计划,需要先创建支付记录
318 const selectedPlan = promotionPlans.find(p => p.id === values.promotionPlan);
319 if (selectedPlan) {
320 setSelectedPromotion(selectedPlan);
321
322 // 创建支付记录
323 const paymentResponse = await createPayment({
324 postId: currentEditPost.postId || currentEditPost.id || 0,
325 planId: selectedPlan.id,
326 amount: selectedPlan.price
327 });
328
329 if (paymentResponse.code === 200) {
330 setCurrentPayment(paymentResponse.data);
331 setPaymentModalVisible(true);
332 return; // 等待支付完成后再更新帖子
333 } else {
334 message.error(paymentResponse.msg || '创建支付记录失败');
335 return;
336 }
337 }
338 }
339
340 // 直接更新帖子(没有新推广或已有推广)
341 await updatePostDirectly(values, tagsString);
342 } catch (error) {
343 message.error('更新帖子失败');
344 }
345 };
346
347 const updatePostDirectly = async (values: any, tagsString: string) => {
348 if (!currentEditPost) return;
349
350 const updateData = {
351 ...currentEditPost,
352 title: values.title,
353 content: values.content,
354 summary: values.summary,
355 tags: tagsString,
356 coverImage: editCoverImageUrl || currentEditPost.coverImage,
357 promotionPlanId: values.promotionPlan
358 };
359
360 const response = await updatePost(updateData);
361 if (response.code === 200) {
362 message.success('帖子更新成功');
363 setEditModalVisible(false);
364 editForm.resetFields();
365 setCurrentEditPost(null);
366 setEditCoverImageUrl('');
367 setIsEditingPromotion(false);
368 fetchMyPosts();
369 } else {
370 message.error(response.msg || '更新帖子失败');
371 }
372 };
373
374 const handleDeletePost = async (postId: number) => {
375 try {
376 const response = await deletePost(postId);
377 if (response.code === 200) {
378 message.success('帖子删除成功');
379 fetchMyPosts();
380 } else {
381 message.error(response.msg || '删除帖子失败');
382 }
383 } catch (error) {
384 message.error('删除帖子失败');
385 }
386 };
387
388 const handleViewPost = (postId: number) => {
389 navigate(`/post-detail/${postId}`);
390 };
391
392 const handlePaymentConfirm = async () => {
393 if (!currentPayment) return;
394
395 try {
396 const response = await confirmPayment(currentPayment.paymentId);
397 if (response.code === 200) {
398 message.success('支付成功,推广已生效');
meisiyud83081c2025-06-05 22:25:36 +0800399 setPaymentModalVisible(false);
meisiyu1d4aade2025-06-02 20:10:36 +0800400 setCurrentPayment(null);
401
402 // 如果是编辑模式,完成帖子更新
403 if (editModalVisible && currentEditPost) {
404 const values = editForm.getFieldsValue();
405 const tagsString = Array.isArray(values.tags) ? values.tags.join(',') : values.tags;
406 await updatePostDirectly(values, tagsString);
meisiyud83081c2025-06-05 22:25:36 +0800407 } else if (publishModalVisible) {
408 // 如果是发布模式,支付成功后关闭所有弹窗,刷新帖子列表,清空状态
409 setPublishModalVisible(false);
410 form.resetFields();
meisiyu1d4aade2025-06-02 20:10:36 +0800411 setCoverImageUrl('');
meisiyud83081c2025-06-05 22:25:36 +0800412 setCurrentPublishingPostId(null); // 清空已创建的帖子ID
413 fetchMyPosts();
meisiyu1d4aade2025-06-02 20:10:36 +0800414 }
meisiyud83081c2025-06-05 22:25:36 +0800415
416 setSelectedPromotion(null);
meisiyu1d4aade2025-06-02 20:10:36 +0800417 } else {
418 message.error(response.msg || '支付确认失败');
419 }
420 } catch (error) {
421 message.error('支付确认失败');
422 }
423 };
424
425 const handlePaymentCancel = async () => {
426 if (!currentPayment) return;
427
428 try {
429 await cancelPayment(currentPayment.paymentId);
430 message.info('支付已取消');
431 setPaymentModalVisible(false);
432 setCurrentPayment(null);
433 setSelectedPromotion(null);
meisiyud83081c2025-06-05 22:25:36 +0800434
435 // 如果是发布模式,支付取消后返回发布页面(不关闭发布弹窗)
436 // 保持currentPublishingPostId,用户可以重新支付
437 // 如果是编辑模式,关闭编辑弹窗
438 if (editModalVisible) {
439 setEditModalVisible(false);
440 editForm.resetFields();
441 setCurrentEditPost(null);
442 setEditCoverImageUrl('');
443 setIsEditingPromotion(false);
444 }
meisiyu1d4aade2025-06-02 20:10:36 +0800445 } catch (error) {
446 console.error('取消支付失败:', error);
447 setPaymentModalVisible(false);
448 setCurrentPayment(null);
449 setSelectedPromotion(null);
450 }
451 };
452
453 const handleImageUpload = async (file: any) => {
454 setUploadLoading(true);
455 try {
456 const formData = new FormData();
457 formData.append('file', file);
458
459 const response = await uploadImage(formData);
460 if (response.code === 200 && response.data) {
461 setCoverImageUrl(response.data.url);
462 message.success('图片上传成功');
463 return false; // 阻止自动上传
464 } else {
465 message.error(response.msg || '图片上传失败');
466 }
467 } catch (error) {
468 message.error('图片上传失败');
469 } finally {
470 setUploadLoading(false);
471 }
472 return false;
473 };
474
475 const handleDeleteImage = async () => {
476 if (coverImageUrl) {
477 try {
478 const filename = coverImageUrl.split('/').pop();
479 if (filename) {
480 await deleteImage(filename);
481 }
482 setCoverImageUrl('');
483 message.success('图片删除成功');
484 } catch (error) {
485 message.error('图片删除失败');
486 }
487 }
488 };
489
490 const handleCancelPublish = async () => {
meisiyud83081c2025-06-05 22:25:36 +0800491 // 如果有已创建的帖子但还没有支付成功,需要删除这个帖子
492 if (currentPublishingPostId) {
493 try {
494 await deletePost(currentPublishingPostId);
495 message.info('已取消发布并删除草稿帖子');
496 } catch (error) {
497 console.error('删除草稿帖子失败:', error);
498 message.warning('取消发布成功,但删除草稿帖子失败');
499 }
500 }
501
meisiyu1d4aade2025-06-02 20:10:36 +0800502 // 如果有上传的图片但没有发布帖子,删除图片
503 if (coverImageUrl) {
504 try {
505 const filename = coverImageUrl.split('/').pop();
506 if (filename) {
507 await deleteImage(filename);
508 }
509 } catch (error) {
510 console.error('删除图片失败:', error);
511 }
512 }
513
514 setPublishModalVisible(false);
515 form.resetFields();
516 setSelectedPromotion(null);
517 setCoverImageUrl('');
meisiyud83081c2025-06-05 22:25:36 +0800518 setCurrentPublishingPostId(null); // 清空已创建的帖子ID
meisiyu1d4aade2025-06-02 20:10:36 +0800519 };
520
521 const uploadButton = (
522 <div>
523 {uploadLoading ? <LoadingOutlined /> : <PlusOutlined />}
524 <div style={{ marginTop: 8 }}>上传封面</div>
525 </div>
526 );
527
528 const handleEditImageUpload = async (file: any) => {
529 setEditUploadLoading(true);
530 try {
531 const formData = new FormData();
532 formData.append('file', file);
533
534 const response = await uploadImage(formData);
535 if (response.code === 200 && response.data) {
536 // 如果有旧图片,删除它
537 if (editCoverImageUrl) {
538 const oldFilename = editCoverImageUrl.split('/').pop();
539 if (oldFilename) {
540 await deleteImage(oldFilename);
541 }
542 }
543
544 setEditCoverImageUrl(response.data.url);
545 message.success('图片上传成功');
546 return false;
547 } else {
548 message.error(response.msg || '图片上传失败');
549 }
550 } catch (error) {
551 message.error('图片上传失败');
552 } finally {
553 setEditUploadLoading(false);
554 }
555 return false;
556 };
557
558 const handleDeleteEditImage = async () => {
559 if (editCoverImageUrl) {
560 try {
561 const filename = editCoverImageUrl.split('/').pop();
562 if (filename) {
563 await deleteImage(filename);
564 }
565 setEditCoverImageUrl('');
566 message.success('图片删除成功');
567 } catch (error) {
568 message.error('图片删除失败');
569 }
570 }
571 };
572
573 const editUploadButton = (
574 <div>
575 {editUploadLoading ? <LoadingOutlined /> : <PlusOutlined />}
576 <div style={{ marginTop: 8 }}>上传封面</div>
577 </div>
578 );
579
580 const myPostsColumns = [
581 {
582 title: '标题',
583 dataIndex: 'title',
584 key: 'title',
585 render: (text: string, record: API.Post.PostInfo) => (
586 <a onClick={() => handleViewPost(record.postId || record.id || 0)}>{text}</a>
587 ),
588 },
589 {
590 title: '状态',
591 dataIndex: 'status',
592 key: 'status',
593 render: (status: string) => {
594 const statusMap: Record<string, { color: string; text: string }> = {
595 '0': { color: 'orange', text: '待审核' },
596 '1': { color: 'green', text: '已发布' },
597 '2': { color: 'red', text: '已拒绝' },
598 '3': { color: 'gray', text: '已下架' }
599 };
600 const statusInfo = statusMap[status] || { color: 'gray', text: '未知' };
601 return <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
602 },
603 },
604 {
605 title: '浏览量',
606 dataIndex: 'views',
607 key: 'views',
608 },
609 {
610 title: '评论数',
611 dataIndex: 'comments',
612 key: 'comments',
613 },
614 {
615 title: '收藏数',
616 dataIndex: 'favorites',
617 key: 'favorites',
618 },
619 {
620 title: '点赞数',
621 dataIndex: 'likes',
622 key: 'likes',
623 },
624 {
625 title: '发布时间',
626 dataIndex: 'publishTime',
627 key: 'publishTime',
628 },
629 {
630 title: '操作',
631 key: 'action',
632 render: (text: any, record: API.Post.PostInfo) => (
633 <Space size="middle">
634 <Button
635 type="link"
636 icon={<EyeOutlined />}
637 onClick={() => handleViewPost(record.postId || record.id || 0)}
638 >
639 查看
640 </Button>
641 <Button
642 type="link"
643 icon={<EditOutlined />}
644 onClick={() => handleEditPost(record)}
645 >
646 编辑
647 </Button>
648 <Popconfirm
649 title="确定要删除这篇帖子吗?"
650 onConfirm={() => handleDeletePost(record.postId || record.id || 0)}
651 okText="确定"
652 cancelText="取消"
653 >
654 <Button type="link" danger icon={<DeleteOutlined />}>
655 删除
656 </Button>
657 </Popconfirm>
658 </Space>
659 ),
660 },
661 ];
662
663 return (
664 <div className={styles.userCenterContainer}>
665 <Card title="个人中心" className={styles.userCenterCard}>
666 <Tabs activeKey={activeTab} onChange={setActiveTab}>
667 <TabPane tab="我的帖子" key="myPosts">
668 <div className={styles.tabContent}>
669 <div className={styles.tabHeader}>
670 <Button
671 type="primary"
672 icon={<PlusOutlined />}
673 onClick={() => setPublishModalVisible(true)}
674 >
675 发布新帖子
676 </Button>
677 </div>
678 <Table
679 columns={myPostsColumns}
680 dataSource={myPosts}
681 loading={loading}
682 rowKey="id"
683 pagination={{
684 pageSize: 10,
685 showTotal: (total) => `共 ${total} 条记录`,
686 }}
687 />
688 </div>
689 </TabPane>
690
691 <TabPane tab="我的收藏" key="favorites">
692 <div className={styles.tabContent}>
693 <Row gutter={[24, 24]}>
694 {favorites.map((post: any) => {
695 // 确保post对象有正确的id字段
696 const formattedPost = {
697 ...post,
698 id: post.postId || post.id,
699 tags: post.tags ? (Array.isArray(post.tags) ? post.tags : post.tags.split(',')) : []
700 };
701 return (
702 <Col xs={24} sm={12} md={8} key={formattedPost.id}>
703 <PostCard post={formattedPost} />
704 </Col>
705 );
706 })}
707 </Row>
708 {favorites.length === 0 && !loading && (
709 <div className={styles.emptyState}>
710 <HeartOutlined style={{ fontSize: 48, color: '#ccc' }} />
711 <p>暂无收藏的帖子</p>
712 </div>
713 )}
714 </div>
715 </TabPane>
716 </Tabs>
717 </Card>
718
719 {/* 发布帖子弹窗 */}
720 <Modal
721 title="发布新帖子"
722 open={publishModalVisible}
723 onCancel={handleCancelPublish}
724 footer={null}
725 width={800}
726 >
727 <Form
728 form={form}
729 layout="vertical"
730 onFinish={handlePublishPost}
731 >
732 <Form.Item
733 name="title"
734 label="帖子标题"
735 rules={[{ required: true, message: '请输入帖子标题' }]}
736 >
737 <Input placeholder="请输入帖子标题" />
738 </Form.Item>
739
740 <Form.Item
741 name="summary"
742 label="帖子摘要"
743 rules={[{ required: true, message: '请输入帖子摘要' }]}
744 >
745 <TextArea rows={3} placeholder="请输入帖子摘要" />
746 </Form.Item>
747
748 <Form.Item
749 name="content"
750 label="帖子内容"
751 rules={[{ required: true, message: '请输入帖子内容' }]}
752 >
753 <TextArea rows={8} placeholder="请输入帖子内容" />
754 </Form.Item>
755
756 <Form.Item
757 name="coverImage"
758 label="封面图片(可选)"
759 >
760 <Upload
761 listType="picture-card"
762 showUploadList={false}
763 beforeUpload={handleImageUpload}
764 >
765 {coverImageUrl ? (
766 <Image
767 src={coverImageUrl}
768 alt="封面"
769 width="100%"
770 height="100%"
771 style={{ objectFit: 'cover' }}
772 />
773 ) : (
774 uploadButton
775 )}
776 </Upload>
777 {coverImageUrl && (
778 <Button
779 type="link"
780 onClick={handleDeleteImage}
781 style={{ padding: 0, marginTop: 8 }}
782 >
783 删除图片
784 </Button>
785 )}
786 </Form.Item>
787
788 <Form.Item
789 name="tags"
790 label="标签"
791 rules={[{ required: true, message: '请选择标签' }]}
792 >
793 <Select
794 mode="multiple"
795 placeholder="请选择标签"
796 allowClear
797 style={{ width: '100%' }}
798 >
799 {availableTags.map(tag => (
800 <Select.Option key={tag.tagId} value={tag.tagName}>
801 <Tag color={tag.tagColor}>{tag.tagName}</Tag>
802 </Select.Option>
803 ))}
804 </Select>
805 </Form.Item>
806
807 <Form.Item
808 name="promotionPlan"
809 label="推广选项(可选)"
810 >
811 <Radio.Group>
812 <Space direction="vertical">
813 <Radio value={undefined}>不选择推广</Radio>
814 {promotionPlans.map(plan => (
815 <Radio key={plan.id} value={plan.id}>
816 <div>
817 <strong>{plan.name}</strong> - ¥{plan.price} ({plan.duration}天)
818 <br />
819 <span style={{ color: '#666', fontSize: '12px' }}>
820 {plan.description}
821 </span>
822 </div>
823 </Radio>
824 ))}
825 </Space>
826 </Radio.Group>
827 </Form.Item>
828
829 <Form.Item>
830 <Space>
831 <Button type="primary" htmlType="submit">
832 {selectedPromotion ? '选择支付方式' : '发布帖子'}
833 </Button>
834 <Button onClick={handleCancelPublish}>
835 取消
836 </Button>
837 </Space>
838 </Form.Item>
839 </Form>
840 </Modal>
841
842 {/* 编辑帖子弹窗 */}
843 <Modal
844 title="编辑帖子"
845 open={editModalVisible}
846 onCancel={() => {
847 setEditModalVisible(false);
848 editForm.resetFields();
849 setCurrentEditPost(null);
850 setEditCoverImageUrl('');
851 setIsEditingPromotion(false);
852 }}
853 footer={null}
854 width={800}
855 >
856 <Form
857 form={editForm}
858 layout="vertical"
859 onFinish={handleUpdatePost}
860 >
861 <Form.Item
862 name="title"
863 label="帖子标题"
864 rules={[{ required: true, message: '请输入帖子标题' }]}
865 >
866 <Input placeholder="请输入帖子标题" />
867 </Form.Item>
868
869 <Form.Item
870 name="summary"
871 label="帖子摘要"
872 rules={[{ required: true, message: '请输入帖子摘要' }]}
873 >
874 <TextArea rows={3} placeholder="请输入帖子摘要" />
875 </Form.Item>
876
877 <Form.Item
878 name="content"
879 label="帖子内容"
880 rules={[{ required: true, message: '请输入帖子内容' }]}
881 >
882 <TextArea rows={8} placeholder="请输入帖子内容" />
883 </Form.Item>
884
885 <Form.Item
886 name="coverImage"
887 label="封面图片"
888 >
889 <Upload
890 listType="picture-card"
891 showUploadList={false}
892 beforeUpload={handleEditImageUpload}
893 >
894 {editCoverImageUrl ? (
895 <Image
896 src={editCoverImageUrl}
897 alt="封面"
898 width="100%"
899 height="100%"
900 style={{ objectFit: 'cover' }}
901 />
902 ) : (
903 editUploadButton
904 )}
905 </Upload>
906 {editCoverImageUrl && (
907 <Button
908 type="link"
909 onClick={handleDeleteEditImage}
910 style={{ padding: 0, marginTop: 8 }}
911 >
912 删除图片
913 </Button>
914 )}
915 </Form.Item>
916
917 <Form.Item
918 name="tags"
919 label="标签"
920 rules={[{ required: true, message: '请选择标签' }]}
921 >
922 <Select
923 mode="multiple"
924 placeholder="请选择标签"
925 allowClear
926 style={{ width: '100%' }}
927 >
928 {availableTags.map(tag => (
929 <Select.Option key={tag.tagId} value={tag.tagName}>
930 <Tag color={tag.tagColor}>{tag.tagName}</Tag>
931 </Select.Option>
932 ))}
933 </Select>
934 </Form.Item>
935
936 <Form.Item
937 name="promotionPlan"
938 label="推广选项(可选)"
939 >
940 <Radio.Group disabled={isEditingPromotion}>
941 <Space direction="vertical">
942 <Radio value={undefined}>不选择推广</Radio>
943 {isEditingPromotion && (
944 <div style={{ color: '#ff4d4f', fontSize: '12px', marginBottom: 8 }}>
945 该帖子已购买推广,无法更改推广选项
946 </div>
947 )}
948 {promotionPlans.map(plan => (
949 <Radio key={plan.id} value={plan.id} disabled={isEditingPromotion}>
950 <div>
951 <strong>{plan.name}</strong> - ¥{plan.price} ({plan.duration}天)
952 <br />
953 <span style={{ color: '#666', fontSize: '12px' }}>
954 {plan.description}
955 </span>
956 </div>
957 </Radio>
958 ))}
959 </Space>
960 </Radio.Group>
961 </Form.Item>
962
963 <Form.Item>
964 <Space>
965 <Button type="primary" htmlType="submit">
966 更新帖子
967 </Button>
968 <Button onClick={() => {
969 setEditModalVisible(false);
970 editForm.resetFields();
971 setCurrentEditPost(null);
972 setEditCoverImageUrl('');
973 setIsEditingPromotion(false);
974 }}>
975 取消
976 </Button>
977 </Space>
978 </Form.Item>
979 </Form>
980 </Modal>
981
982 {/* 支付弹窗 */}
983 <Modal
984 title="支付推广费用"
985 open={paymentModalVisible}
986 onCancel={handlePaymentCancel}
987 footer={null}
988 width={400}
989 >
990 <div className={styles.paymentModal}>
991 {selectedPromotion && (
992 <>
993 <div className={styles.paymentInfo}>
994 <h3>{selectedPromotion.name}</h3>
995 <p>{selectedPromotion.description}</p>
996 <p>费用: <strong>¥{selectedPromotion.price}</strong></p>
997 <p>时长: {selectedPromotion.duration}天</p>
998 </div>
999
1000 <div className={styles.qrCode}>
1001 <div className={styles.qrCodePlaceholder}>
1002 <p>支付二维码</p>
1003 <p style={{ fontSize: '12px', color: '#666' }}>
1004 请使用支付宝扫描二维码支付
1005 </p>
1006 <div className={styles.mockQrCode}>
1007 <p>模拟二维码</p>
1008 <p>¥{selectedPromotion.price}</p>
1009 </div>
1010 </div>
1011 </div>
1012
1013 <div className={styles.paymentActions}>
1014 <Button
1015 type="primary"
1016 onClick={handlePaymentConfirm}
1017 style={{ width: '100%', marginBottom: 8 }}
1018 >
1019 我已完成支付
1020 </Button>
1021 <Button
1022 onClick={handlePaymentCancel}
1023 style={{ width: '100%' }}
1024 >
1025 取消支付
1026 </Button>
1027 </div>
1028 </>
1029 )}
1030 </div>
1031 </Modal>
1032 </div>
1033 );
1034};
1035
1036export default UserCenter;