修改好友动态、发布动态、促销模块、创建帖子,Resolve review.
Change-Id: I84a2460dd1208bc703b0527d98225204d03e5efc
diff --git a/src/pages/FriendMoments/CreateMoment.css b/src/pages/FriendMoments/CreateMoment.css
new file mode 100644
index 0000000..ddbaf7b
--- /dev/null
+++ b/src/pages/FriendMoments/CreateMoment.css
@@ -0,0 +1,63 @@
+ /* .create-feed-page {
+ margin: 0 auto;
+ padding-bottom: 40px;
+ }
+
+ .cf-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-top: 20px;
+
+ }
+
+ .cf-header h3 {
+ margin: 0;
+ }
+
+ .cf-form {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .cf-form input[type="text"],
+ .cf-form textarea {
+ width: 100%;
+ padding: 8px;
+ font-size: 14px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ }
+
+ .cf-form textarea {
+ min-height: 100px;
+ resize: vertical;
+ }
+
+ .cf-form input[type="file"] {
+ font-size: 14px;
+ }
+
+ .cf-preview {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+
+ .cf-preview img {
+ width: 80px;
+ height: 80px;
+ object-fit: cover;
+ border-radius: 4px;
+ border: 1px solid #bbb;
+ }
+
+ .cf-submit-btn {
+ align-self: flex-end;
+ padding: 8px 16px;
+ font-size: 14px;
+ cursor: pointer;
+ background: #BA929A;
+ }
+ */
\ No newline at end of file
diff --git a/src/pages/FriendMoments/CreateMoment.jsx b/src/pages/FriendMoments/CreateMoment.jsx
new file mode 100644
index 0000000..4aca455
--- /dev/null
+++ b/src/pages/FriendMoments/CreateMoment.jsx
@@ -0,0 +1,206 @@
+// // import React, { useState } from 'react';
+// // import axios from 'axios';
+// // import { useNavigate } from 'react-router-dom';
+// // import './CreateMoment.css';
+// // import Header from '../../components/Header';
+
+// // const API_BASE = process.env.REACT_APP_API_BASE;
+// // const USER_ID = 456;
+
+// // const CreateMoment = () => {
+// // const [title, setTitle] = useState('');
+// // const [content, setContent] = useState('');
+// // const [previewUrls, setPreviewUrls] = useState([]);
+// // const [images, setImages] = useState([]);
+// // const navigate = useNavigate();
+
+// // const handleImageChange = async (e) => {
+// // const files = Array.from(e.target.files);
+// // if (!files.length) return;
+
+// // setPreviewUrls(files.map(f => URL.createObjectURL(f)));
+
+// // try {
+// // const uploaded = await Promise.all(
+// // files.map(file => uploadImageToServer(file))
+// // );
+// // setImages(uploaded);
+// // } catch (err) {
+// // console.error('图片上传失败:', err);
+// // alert('有图片上传失败,请重试');
+// // }
+// // };
+
+// // const uploadImageToServer = async (file) => {
+// // const formData = new FormData();
+// // formData.append('file', file);
+// // const res = await axios.post(`${API_BASE}/upload`, formData, {
+// // headers: {'Content-Type': 'multipart/form-data'}
+// // });
+// // return res.data.url;
+// // };
+
+// // const handleSubmit = async () => {
+// // if (!content.trim()) {
+// // alert('内容不能为空');
+// // return;
+// // }
+// // try {
+// // await axios.post(
+// // `${API_BASE}/echo/users/${USER_ID}/CreateMoment`,
+// // {
+// // title: title.trim() || undefined,
+// // friend_content: content.trim(),
+// // images
+// // }
+// // );
+// // navigate('/friend-moments');
+// // } catch (err) {
+// // console.error('发布失败:', err);
+// // alert('发布失败,请稍后重试');
+// // }
+// // };
+
+// // return (
+// // <div className="create-feed-page">
+// // <Header/>
+// // <div className="cf-header">
+// // <button onClick={() => navigate(-1)} style={{padding : '5px 10px', backgroundColor: '#BA929A', color: 'white'}}>返回</button>
+// // <h3>发布新动态</h3>
+// // </div>
+
+// // <div className="cf-form">
+// // <input
+// // type="text"
+// // placeholder="标题"
+// // value={title}
+// // onChange={e => setTitle(e.target.value)}
+// // />
+// // <textarea
+// // placeholder="写下你的内容..."
+// // value={content}
+// // onChange={e => setContent(e.target.value)}
+// // />
+// // <input
+// // type="file"
+// // accept="image/*"
+// // multiple
+// // onChange={handleImageChange}
+// // />
+// // <div className="cf-preview">
+// // {previewUrls.map((url, i) => (
+// // <img key={i} src={url} alt={`预览 ${i}`} />
+// // ))}
+// // </div>
+// // <button className="cf-submit-btn" onClick={handleSubmit}>
+// // 发布
+// // </button>
+// // </div>
+// // </div>
+// // );
+// // };
+
+// // export default CreateMoment;
+// import React, { useState } from 'react';
+// import axios from 'axios';
+// import { useNavigate } from 'react-router-dom';
+// import './CreateMoment.css';
+// import Header from '../../components/Header';
+
+// const API_BASE = process.env.REACT_APP_API_BASE;
+// const USER_ID = 456;
+
+// const CreateMoment = ({ onClose, fetchFeeds }) => {
+// const [title, setTitle] = useState('');
+// const [content, setContent] = useState('');
+// const [previewUrls, setPreviewUrls] = useState([]);
+// const [images, setImages] = useState([]);
+// const navigate = useNavigate();
+
+// const handleImageChange = async (e) => {
+// const files = Array.from(e.target.files);
+// if (!files.length) return;
+// setPreviewUrls(files.map(f => URL.createObjectURL(f)));
+// try {
+// const uploaded = await Promise.all(
+// files.map(file => uploadImageToServer(file))
+// );
+// setImages(uploaded);
+// } catch (err) {
+// console.error('图片上传失败:', err);
+// alert('有图片上传失败,请重试');
+// }
+// };
+
+// const uploadImageToServer = async (file) => {
+// const formData = new FormData();
+// formData.append('file', file);
+// const res = await axios.post(`${API_BASE}/upload`, formData, {
+// headers: { 'Content-Type': 'multipart/form-data' }
+// });
+// return res.data.url;
+// };
+
+// const handleSubmit = async () => {
+// if (!content.trim()) {
+// alert('内容不能为空');
+// return;
+// }
+// try {
+// await axios.post(
+// `${API_BASE}/echo/users/${USER_ID}/CreateMoment`,
+// {
+// title: title.trim() || undefined,
+// friend_content: content.trim(),
+// images
+// }
+// );
+// onClose();
+// fetchFeeds();
+// } catch (err) {
+// console.error('发布失败:', err);
+// alert('发布失败,请稍后重试');
+// }
+// };
+
+// return (
+// <div className="create-feed-page">
+// <div className="cf-header">
+// <button onClick={onClose} style={{ padding: '5px 10px', backgroundColor: '#BA929A', color: 'white' }}>
+// 返回
+// </button>
+// <h3>发布新动态</h3>
+// </div>
+// <div className="cf-form">
+// <input
+// type="text"
+// placeholder="标题"
+// value={title}
+// onChange={e => setTitle(e.target.value)}
+// />
+// <textarea
+// placeholder="写下你的内容..."
+// value={content}
+// onChange={e => setContent(e.target.value)}
+// />
+// <input
+// type="file"
+// accept="image/*"
+// multiple
+// onChange={handleImageChange}
+// />
+// <div className="cf-preview">
+// {previewUrls.map((url, i) => (
+// <img key={i} src={url} alt={`预览 ${i}`} />
+// ))}
+// </div>
+// <button className="cf-submit-btn" onClick={handleSubmit}>
+// 发布
+// </button>
+// </div>
+// </div>
+// );
+// };
+
+// export default CreateMoment;
+
diff --git a/src/pages/FriendMoments/FriendMoments.css b/src/pages/FriendMoments/FriendMoments.css
index c95bdcf..a69d919 100644
--- a/src/pages/FriendMoments/FriendMoments.css
+++ b/src/pages/FriendMoments/FriendMoments.css
@@ -1,88 +1,171 @@
-/* .friend-moments {
- background-color: #5F4437;
- color: white;
- }
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px;
- }
- .logo-and-name {
- display: flex;
- align-items: center;
- }
- .logo {
- height: 30px;
- margin-right: 10px;
- }
- .site-name {
- font-size: 24px;
- }
- .user-and-message {
- display: flex;
- align-items: center;
- }
- .user-avatar {
- height: 40px;
- margin-right: 10px;
- }
- .message-center {
- font-size: 16px;
- }
- .nav {
- background-color: #dab8c2;
- display: flex;
- justify-content: center;
- }
- .nav-item {
- color: white;
- text-decoration: none;
- padding: 10px 20px;
- }
- .active {
- background-color: #996633;
- }
- .content {
- padding: 20px;
- }
- .user-post {
- border-bottom: 1px solid white;
- margin-bottom: 20px;
- padding-bottom: 20px;
- }
- .user-info {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
- .user-avatar-small {
- height: 30px;
- margin-right: 10px;
- }
- .username {
- font-size: 16px;
- }
- .post-content {
- margin-bottom: 10px;
- }
- .post-actions {
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- }
- .like-icon,
- .comment-icon {
- margin-right: 5px;
- }
- .like-count,
- .comment-count {
- margin-left: 5px;
- }
- .post-image {
- float: right;
- width: 150px;
- height: 150px;
- background-color: #ddd;
- margin-left: 10px;
- } */
\ No newline at end of file
+.friend-moments-container {
+ margin: 0 auto;
+ background: linear-gradient(180deg, #5F4437, #823c3c);
+ padding-bottom: 40px;
+}
+
+.fm-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 2%;
+}
+
+
+.f-search-bar {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.search-input {
+ padding: 6px 8px;
+ font-size: 14px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+.search-btn {
+ padding: 6px 12px;
+ font-size: 14px;
+ cursor: pointer;
+ background: #fff;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+.feed-list .feed-item {
+ padding: 1% 1.5%;
+ margin: 1% 2%;
+ border-radius: 6px;
+ /*设置item之间的间隔*/
+ margin-bottom: 2%;
+ background-color: #e9ded2;
+}
+
+.feed-item h4 {
+ margin: 0 0 5px;
+}
+
+.feed-item p {
+ margin: 0 0 10px;
+ line-height: 1.4;
+}
+
+.feed-images {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-bottom: 10px;
+}
+
+.feed-images img {
+ width: 100px;
+ height: 100px;
+ object-fit: cover;
+ border-radius: 4px;
+ border: 1px solid #ddd;
+}
+
+.feed-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 12px;
+ color: #666;
+}
+
+.delete-btn {
+ background: none;
+ border: none;
+ color: #f44;
+ cursor: pointer;
+ font-size: 12px;
+}
+
+/* Modal 样式 */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+}
+
+.modal-dialog {
+ background: #e9ded2;
+ padding: 20px;
+ width: 35%;
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+/* 标题 */
+.modal-dialog h3 {
+ margin: 0;
+ color : #4A3B34;
+}
+
+.modal-dialog input[type="text"],
+.modal-dialog textarea {
+ width: 97%;
+ padding: 8px;
+ font-size: 14px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+.modal-dialog input[type="file"] {
+ width: 97%;
+ font-size: 14px;
+ border-radius: 4px;
+}
+
+.modal-dialog textarea {
+ resize: vertical;
+ min-height: 80px;
+}
+
+.cf-preview {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.cf-preview img {
+ width: 80px;
+ height: 80px;
+ object-fit: cover;
+ border-radius: 4px;
+ border: 1px solid #bbb;
+}
+
+.modal-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 10px;
+}
+
+.modal-actions .btn {
+ padding: 6px 12px;
+ font-size: 14px;
+ cursor: pointer;
+ border: none;
+ border-radius: 4px;
+}
+
+.modal-actions .btn.cancel {
+ background: #5F4437;
+}
+
+.modal-actions .btn.submit {
+ background: #BA929A;
+ color: #fff;
+}
diff --git a/src/pages/FriendMoments/FriendMoments.jsx b/src/pages/FriendMoments/FriendMoments.jsx
index 4e1a704..fdf101c 100644
--- a/src/pages/FriendMoments/FriendMoments.jsx
+++ b/src/pages/FriendMoments/FriendMoments.jsx
@@ -1,55 +1,210 @@
-// import React from 'react';
-// import './FriendMoments.css';
-// import { Link } from 'wouter';
-// import logo from '../../assets/logo.png';
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+import './FriendMoments.css';
+import Header from '../../components/Header';
+import { Edit } from '@icon-park/react';
-// const FriendMoments = () => {
-// return (
-// <div className="friend-moments">
-// {/* 顶部栏 */}
-// <header className="header">
-// {/* 左侧 logo 和网站名称 */}
-// <div className="logo-and-name">
-// {/* 确保此处没有语法错误 */}
-// <img src={logo} alt="网站 logo" className="logo" />
-// <span className="site-name">Echo</span>
-// </div>
-// {/* 右侧用户头像和消息中心 */}
-// <div className="user-and-message">
-// <img src="user-avatar.png" alt="用户头像" className="user-avatar" />
-// <span className="message-center">消息</span>
-// </div>
-// </header>
-// {/* 导航栏 */}
-// <nav className="nav">
-// <Link to="/friend-moments" className="nav-item active">好友动态</Link>
-// <Link to="/forum" className="nav-item">论坛</Link>
-// <Link to="/interest-groups" className="nav-item">兴趣小组</Link>
-// <Link to="/seed-list" className="nav-item">种子列表</Link>
-// <Link to="/publish-seed" className="nav-item">发布种子</Link>
-// </nav>
-// {/* 好友动态内容区域 */}
-// <div className="content">
-// {/* 用户动态示例,可从后端获取数据循环展示 */}
-// <div className="user-post">
-// <div className="user-info">
-// <img src="user1-avatar.png" alt="用户头像" className="user-avatar-small" />
-// <span className="username">user1</span>
-// </div>
-// <div className="post-content">
-// <p>动态内容...</p>
-// </div>
-// <div className="post-actions">
-// {/* <GoodTwo theme="outline" size="24" fill="#fff" /> */}
-// <span className="like-count">21</span>
-// {/* <Comment theme="outline" size="24" fill="#fff"/> */}
-// <span className="comment-count">2</span>
-// </div>
-// <img src="image1.png" alt="动态图片" className="post-image" />
-// </div>
-// </div>
-// </div>
-// );
-// };
+const API_BASE = process.env.REACT_APP_API_BASE;
+const USER_ID = 456;
-// export default FriendMoments;
\ No newline at end of file
+const FriendMoments = () => {
+ const [feeds, setFeeds] = useState([]);
+ const [filteredFeeds, setFilteredFeeds] = useState([]);
+ const [query, setQuery] = useState('');
+
+ // Modal state & form fields
+ const [showModal, setShowModal] = useState(false);
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const [previewUrls, setPreviewUrls] = useState([]);
+ const [images, setImages] = useState([]);
+
+ // 拉取好友动态列表
+ const fetchFeeds = async () => {
+ try {
+ const res = await axios.get(`${API_BASE}/echo/users/${USER_ID}/feeds`);
+ setFeeds(res.data.feeds);
+ setFilteredFeeds(res.data.feeds);
+ } catch (err) {
+ console.error('获取动态列表失败:', err);
+ }
+ };
+
+ useEffect(() => {
+ fetchFeeds();
+ }, []);
+
+ // 搜索处理
+ const handleSearch = () => {
+ const q = query.trim().toLowerCase();
+ if (!q) return;
+ setFilteredFeeds(
+ feeds.filter(f => (f.title || '').toLowerCase().includes(q))
+ );
+ };
+ const handleReset = () => {
+ setQuery('');
+ setFilteredFeeds(feeds);
+ };
+
+ // 对话框内:本地预览 & 上传
+ const handleImageChange = async (e) => {
+ const files = Array.from(e.target.files);
+ if (!files.length) return;
+ setPreviewUrls(files.map(f => URL.createObjectURL(f)));
+ try {
+ const uploaded = await Promise.all(files.map(f => uploadImageToServer(f)));
+ setImages(uploaded);
+ } catch (err) {
+ console.error('图片上传失败', err);
+ alert('图片上传失败,请重试');
+ }
+ };
+ const uploadImageToServer = async (file) => {
+ const fd = new FormData();
+ fd.append('file', file);
+ const res = await axios.post(`${API_BASE}/upload`, fd, {
+ headers: {'Content-Type':'multipart/form-data'}
+ });
+ return res.data.url;
+ };
+
+ // 对话框内:提交新动态
+ const handleSubmit = async () => {
+ if (!content.trim()) {
+ alert('内容不能为空');
+ return;
+ }
+ try {
+ await axios.post(
+ `${API_BASE}/echo/users/${USER_ID}/createFeed`,
+ { title: title.trim() || undefined, friend_content: content.trim(), images }
+ );
+ // 重置表单
+ setTitle('');
+ setContent('');
+ setImages([]);
+ setPreviewUrls([]);
+ setShowModal(false);
+ fetchFeeds();
+ } catch (err) {
+ console.error('发布失败', err);
+ alert('发布失败,请稍后重试');
+ }
+ };
+
+ // 删除动态
+ const handleDelete = async (feedId) => {
+ if (!window.confirm('确定要删除这条动态吗?')) return;
+ try {
+ await axios.delete(`${API_BASE}/echo/users/me/feed/${feedId}`);
+ fetchFeeds();
+ } catch (err) {
+ console.error('删除失败', err);
+ alert('删除失败');
+ }
+ };
+
+ return (
+ <div className="friend-moments-container">
+ <Header />
+ <div className="fm-header">
+ <button className="create-btn" onClick={() => setShowModal(true)}>
+ <Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
+ 创建动态
+ </button>
+ <div className="f-search-bar">
+ <input
+ className="search-input"
+ type="text"
+ value={query}
+ onChange={e => setQuery(e.target.value)}
+ placeholder="输入要搜索的动态"
+ />
+ <button className="search-btn" onClick={handleSearch}>搜索</button>
+ <button className="search-btn" onClick={handleReset}>重置</button>
+ </div>
+ </div>
+
+ <div className="feed-list">
+ {filteredFeeds.map(feed => (
+ <div className="feed-item" key={feed.feed_id}>
+ {feed.title && <h4>{feed.title}</h4>}
+ <p>{feed.friend_content}</p>
+
+ {feed.images?.length > 0 && (
+ <div className="feed-images">
+ {feed.images.map((url, i) => (
+ <img key={i} src={url} alt={`动态图${i}`} />
+ ))}
+ </div>
+ )}
+
+ <div className="feed-footer">
+ <span className="feed-date">
+ {new Date(feed.created_at).toLocaleString()}
+ </span>
+ {feed.is_mine && (
+ <button className="delete-btn" onClick={() => handleDelete(feed.feed_id)}>
+ 删除
+ </button>
+ )}
+ </div>
+ </div>
+ ))}
+ </div>
+
+ {/* Modal 对话框 */}
+ {showModal && (
+ <div className="modal-overlay" onClick={() => setShowModal(false)}>
+ <div className="modal-dialog" onClick={e => e.stopPropagation()}>
+ <h3>发布新动态</h3>
+ <input
+ type="text"
+ placeholder="标题"
+ value={title}
+ onChange={e => setTitle(e.target.value)}
+ />
+ <textarea
+ placeholder="写下你的内容..."
+ value={content}
+ onChange={e => setContent(e.target.value)}
+ />
+ {/* <input
+ type="file"
+ accept="image/*"
+ multiple
+ onChange={handleImageChange}
+ /> */}
+ <label className="file-label">
+ 选择图片
+ <input
+ type="file"
+ accept="image/*"
+ multiple
+ onChange={handleImageChange}
+ style={{ display: 'none' }}
+ />
+ </label>
+ <div className="cf-preview">
+ {previewUrls.map((url, i) => (
+ <img key={i} src={url} alt={`预览${i}`} />
+ ))}
+ </div>
+ <div className="modal-actions">
+ <button className="btn cancel" onClick={() => setShowModal(false)}>
+ 取消
+ </button>
+ <button className="btn submit" onClick={handleSubmit}>
+ 发布
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+ );
+};
+
+export default FriendMoments;
+