修改好友动态、兴趣小组
Change-Id: I8dc8f304f9ac9c968e316bc997b2aeb58b26fe48
diff --git a/src/App.js b/src/App.js
index 1209f65..67df715 100644
--- a/src/App.js
+++ b/src/App.js
@@ -7,6 +7,7 @@
import SeedList from './pages/SeedList/SeedList';
import PostDetailPage from './pages/Forum/posts-detail/PostDetailPage';
import { UserProvider } from './context/UserContext';
+import { GroupProvider } from './context/useGroupStore'; // 导入 GroupProvider
import PublishSeed from './pages/PublishSeed/PublishSeed';
import SeedDetail from './pages/SeedList/SeedDetail/SeedDetail';
import InterestGroup from './pages/InterestGroup/InterestGroup';
@@ -32,30 +33,31 @@
function App() {
return (
<UserProvider>
- <>
- <Route path="/" component={RedirectToAuth} />
- <Route path="/auth" component={AuthPage} />
- <Route path="/friend-moments" component={FriendMoments} />
- <Route path="/friend-moments/create" component={CreateMoment} />
- <Route path="/forum" component={ForumPage} />
- <Route path="/forum/post/:postId" component={PostDetailPage} />
- <Route path="/forum/create-post" component={CreatePostPage} />
- <Route path="/seed-list" component={SeedList} />
- {/* <Route path="/publish-seed" component={PublishSeed} /> */}
- <Route path="/publish-seed" component={SimpleUploader} />
- <Route path="/seed/:seed_id" component={SeedDetail} />
- <Route path="/interest-groups" component={InterestGroup} />
- <Route path="/user/profile" component={UserProfile} />
- <Route path="/messages" component={MessagePage} />
- <Route path="/promotions" component={PromotionsPage} />
- <Route path="/level" component={LevelPage} />
- <Route path="/user/newbie-tasks" component={NewbieTasks} />
- {/* <Route path="/user/dynamics" component={UserDynamics} /> */}
- <Route path="/user/friends" component={UserFriends} />
- <Route path="/user/collections" component={UserCollect} />
- </>
+ <GroupProvider> {/* 添加 GroupProvider */}
+ <>
+ <Route path="/" component={RedirectToAuth} />
+ <Route path="/auth" component={AuthPage} />
+ <Route path="/friend-moments" component={FriendMoments} />
+ <Route path="/friend-moments/create" component={CreateMoment} />
+ <Route path="/forum" component={ForumPage} />
+ <Route path="/forum/post/:postId" component={PostDetailPage} />
+ <Route path="/forum/create-post" component={CreatePostPage} />
+ <Route path="/seed-list" component={SeedList} />
+ {/* <Route path="/publish-seed" component={PublishSeed} /> */}
+ <Route path="/publish-seed" component={SimpleUploader} />
+ <Route path="/seed/:seed_id" component={SeedDetail} />
+ <Route path="/interest-groups" component={InterestGroup} />
+ <Route path="/user/profile" component={UserProfile} />
+ <Route path="/messages" component={MessagePage} />
+ <Route path="/promotions" component={PromotionsPage} />
+ <Route path="/level" component={LevelPage} />
+ <Route path="/user/newbie-tasks" component={NewbieTasks} />
+ <Route path="/user/friends" component={UserFriends} />
+ <Route path="/user/collections" component={UserCollect} />
+ </>
+ </GroupProvider>
</UserProvider>
);
}
-export default App;
+export default App;
\ No newline at end of file
diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx
deleted file mode 100644
index 19d161d..0000000
--- a/src/components/Auth/Login.jsx
+++ /dev/null
@@ -1,213 +0,0 @@
-// import React, { useState } from 'react';
-// import '../../pages/AuthPage/AuthPage.css';
-// import image from './logo.svg'; // 引入图片
-// const Login = ({ onRegisterClick }) => {
-// const [formData, setFormData] = useState({
-// username: '',
-// password: ''
-// });
-
-// const handleChange = (e) => {
-// const { name, value } = e.target;
-// setFormData(prev => ({ ...prev, [name]: value }));
-// };
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-// try {
-// const response = await fetch('http://localhost:8080/user/login', {
-// method: 'POST',
-// headers: {
-// 'Content-Type': 'application/json',
-// },
-// body: JSON.stringify(formData),
-// });
-
-// const result = await response.json();
-// if (response.ok && result.code === "0") {
-// console.log('登录成功:', result);
-// // 处理成功逻辑
-// } else {
-// console.error('登录失败:', result);
-// // 处理失败逻辑
-// }
-// } catch (error) {
-// console.error('请求错误:', error);
-// // 处理请求错误
-// }
-// };
-
-// return (
-// <div className="auth-container">
-// <img
-// src={image}
-// alt="描述"
-// style={{ width: '30%', height: 'auto'}}
-// />
-// <div className="auth-form-section">
-// {/* <h3>欢迎来到EchoTorent !</h3> */}
-// <h3>用户登录</h3>
-// <form onSubmit={handleSubmit}>
-// <div className="form-group">
-// <label>用户名</label>
-// <input
-// type="text"
-// name="username"
-// placeholder="请输入用户名"
-// value={formData.username}
-// onChange={handleChange}
-// className="form-input"
-// />
-// </div>
-// <div className="form-group">
-// <label>密码</label>
-// <input
-// type="password"
-// name="password"
-// placeholder="请输入密码"
-// value={formData.password}
-// onChange={handleChange}
-// className="form-input"
-// />
-// <button
-// type="button"
-// className="link-button forgot-password"
-// onClick={() => console.log('跳转到忘记密码页面')}
-// >
-// 忘记密码?
-// </button>
-// </div>
-// <button type="submit" className="auth-button">
-// 登录
-// </button>
-// <p className="register-link">
-// 没有账号?
-// <button onClick={onRegisterClick} className="link-button">
-// 点击注册
-// </button>
-// </p>
-// </form>
-// </div>
-// </div>
-// );
-// };
-
-// export default Login;
-
-
-// import React, { useState } from 'react';
-// import '../../pages/AuthPage/AuthPage.css';
-// import image from './logo.svg';
-
-//
-
-// const Login = ({ onRegisterClick }) => {
-// const [formData, setFormData] = useState({
-// username: '',
-// password: ''
-// });
-// const [error, setError] = useState('');
-// const [isSubmitting, setIsSubmitting] = useState(false);
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-// setIsSubmitting(true);
-// setError('');
-
-// try {
-// const response = await fetch(`/user/login`, {
-// method: 'POST',
-// headers: {
-// 'Content-Type': 'application/json',
-// },
-// body: JSON.stringify(formData),
-// });
-
-// const result = await response.json();
-
-// if (!response.ok) {
-// throw new Error(result.message || '登录失败');
-// }
-
-// console.log('登录成功:', result);
-// // 这里可以添加登录成功后的跳转逻辑
-// alert('登录成功!');
-// } catch (error) {
-// console.error('登录错误:', error);
-// setError(error.message || '登录过程中出现错误');
-// } finally {
-// setIsSubmitting(false);
-// }
-// };
-
-// const handleChange = (e) => {
-// const { name, value } = e.target;
-// setFormData(prev => ({ ...prev, [name]: value }));
-// };
-
-// return (
-// <div className="auth-container">
-// <img
-// src={image}
-// alt="网站Logo"
-// style={{ width: '30%', height: 'auto' }}
-// />
-// <div className="auth-form-section">
-// <h3>用户登录</h3>
-// {error && <div className="error-message">{error}</div>}
-// <form onSubmit={handleSubmit}>
-// <div className="form-group">
-// <label>用户名</label>
-// <input
-// type="text"
-// name="username"
-// placeholder="请输入用户名"
-// value={formData.username}
-// onChange={handleChange}
-// className="form-input"
-// required
-// />
-// </div>
-// <div className="form-group">
-// <label>密码</label>
-// <input
-// type="password"
-// name="password"
-// placeholder="请输入密码"
-// value={formData.password}
-// onChange={handleChange}
-// className="form-input"
-// required
-// />
-// <button
-// type="button"
-// className="link-button forgot-password"
-// onClick={() => console.log('跳转到忘记密码页面')}
-// >
-// 忘记密码?
-// </button>
-// </div>
-// <button
-// type="submit"
-// className="auth-button"
-// disabled={isSubmitting}
-// >
-// {isSubmitting ? '登录中...' : '登录'}
-// </button>
-// <p className="register-link">
-// 没有账号?{' '}
-// <button
-// type="button"
-// onClick={onRegisterClick}
-// className="link-button"
-// >
-// 点击注册
-// </button>
-// </p>
-// </form>
-// </div>
-// </div>
-// );
-// };
-
-// export default Login;
\ No newline at end of file
diff --git a/src/components/Auth/Login.test.jsx.txt b/src/components/Auth/Login.test.jsx.txt
deleted file mode 100644
index 4631a5f..0000000
--- a/src/components/Auth/Login.test.jsx.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-// import React from 'react';
-// import { render, screen, fireEvent } from '@testing-library/react';
-// import Login from './Login';
-
-// describe('Login component', () => {
-// test('renders login form', () => {
-// const onRegisterClick = jest.fn();
-// render(<Login onRegisterClick={onRegisterClick} />);
-
-// const usernameInput = screen.getByPlaceholderText('请输入用户名');
-// const passwordInput = screen.getByPlaceholderText('请输入密码');
-// const loginButton = screen.getByText('登录');
-
-// expect(usernameInput).toBeInTheDocument();
-// expect(passwordInput).toBeInTheDocument();
-// expect(loginButton).toBeInTheDocument();
-// });
-
-// test('calls onRegisterClick when "点击注册" is clicked', () => {
-// const onRegisterClick = jest.fn();
-// render(<Login onRegisterClick={onRegisterClick} />);
-
-// const registerButton = screen.getByText('点击注册');
-// fireEvent.click(registerButton);
-
-// expect(onRegisterClick).toHaveBeenCalled();
-// });
-// });
\ No newline at end of file
diff --git a/src/components/Auth/Register.jsx b/src/components/Auth/Register.jsx
deleted file mode 100644
index d98a449..0000000
--- a/src/components/Auth/Register.jsx
+++ /dev/null
@@ -1,261 +0,0 @@
-// import React, { useState } from 'react';
-// import '../../pages/AuthPage/AuthPage.css';
-// import image from './logo.svg'; // 引入图片
-
-// const Register = ({ onLoginClick }) => {
-// const [formData, setFormData] = useState({
-// username: '',
-// password: '',
-// email: ''
-// });
-// const [verificationCode, setVerificationCode] = useState('');
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-// // 注册逻辑
-// };
-
-// const verifyEmailCode = async () => {
-// try {
-// const response = await fetch('http://localhost:8080/user/verify-code', {
-// method: 'POST',
-// headers: {
-// 'Content-Type': 'application/json',
-// },
-// body: JSON.stringify({
-// email: formData.email,
-// code: verificationCode,
-// }),
-// });
-
-// const result = await response.json();
-// if (response.ok && result.code === "0") {
-// console.log('邮箱验证成功:', result.msg);
-// // 处理成功逻辑
-// } else {
-// console.error('邮箱验证失败:', result.msg);
-// // 处理失败逻辑
-// }
-// } catch (error) {
-// console.error('请求错误:', error);
-// // 处理请求错误
-// }
-// };
-
-
-// return (
-// <div className="auth-container">
-// <img
-// src={image}
-// alt="描述"
-// style={{ width: '30%', height: 'auto'}}
-// />
-// <div className="auth-form-section">
-// <h3>用户注册</h3>
-// <form onSubmit={handleSubmit}>
-// <div className="form-group">
-// <label>用户名</label>
-// <input
-// type="text"
-// className="form-input"
-// placeholder="请输入用户名"
-// value={formData.username}
-// onChange={(e) => setFormData({ ...formData, username: e.target.value })}
-// required
-// />
-// </div>
-// <div className="form-group">
-// <label>密码</label>
-// <input
-// type="password"
-// className="form-input"
-// placeholder="请输入密码"
-// value={formData.password}
-// onChange={(e) => setFormData({ ...formData, password: e.target.value })}
-// required
-// />
-// </div>
-// <div className="form-group">
-// <label>邮箱</label>
-// <div style={{ display: 'flex', alignItems: 'center' }}>
-// <input
-// type="email"
-// className="form-input"
-// placeholder="请输入邮箱"
-// value={formData.email}
-// onChange={(e) => setFormData({ ...formData, email: e.target.value })}
-// required
-// style={{ flex: 1, marginRight: '10px' }}
-// />
-// <button type="button" onClick={verifyEmailCode} className="verify-button">
-// 验证邮箱
-// </button>
-// </div>
-// </div>
-// <div className="form-group">
-// <label>验证码</label>
-// <input
-// type="text"
-// className="form-input"
-// placeholder="请输入邮箱验证码"
-// value={verificationCode}
-// onChange={(e) => setVerificationCode(e.target.value)}
-// required
-// />
-// </div>
-// <button type="submit" className="auth-button">
-// 注册
-// </button>
-// <p className="login-link">
-// 已有账号?
-// <button onClick={onLoginClick} className="link-button">
-// 点击登录
-// </button>
-// </p>
-// </form>
-// </div>
-// </div>
-// );
-// };
-
-// export default Register;
-
-// import React, { useState } from 'react';
-// import '../../pages/AuthPage/AuthPage.css';
-// import image from './logo.svg';
-
-// const API_BASE = process.env.REACT_APP_API_BASE || '/echo';
-
-// const Register = ({ onLoginClick }) => {
-// const [formData, setFormData] = useState({
-// username: '',
-// email: '',
-// password: '',
-// role: 'user',
-// inviteCode: ''
-// });
-// const [error, setError] = useState('');
-// const [isSubmitting, setIsSubmitting] = useState(false);
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-// setIsSubmitting(true);
-// setError('');
-
-// try {
-// const response = await fetch(`/user/register`, {
-// method: 'POST',
-// headers: {
-// 'Content-Type': 'application/json',
-// },
-// body: JSON.stringify(formData),
-// });
-
-// const result = await response.json();
-
-// if (!response.ok) {
-// throw new Error(result.message || '注册失败');
-// }
-
-// console.log('注册成功:', result);
-// // 这里可以添加注册成功后的跳转逻辑
-// alert('注册成功!');
-// } catch (error) {
-// console.error('注册错误:', error);
-// setError(error.message || '注册过程中出现错误');
-// } finally {
-// setIsSubmitting(false);
-// }
-// };
-
-// const handleChange = (e) => {
-// const { name, value } = e.target;
-// setFormData(prev => ({ ...prev, [name]: value }));
-// };
-
-// return (
-// <div className="auth-container">
-// <img
-// src={image}
-// alt="网站Logo"
-// style={{ width: '30%', height: 'auto' }}
-// />
-// <div className="auth-form-section">
-// <h3>用户注册</h3>
-// {error && <div className="error-message">{error}</div>}
-// <form onSubmit={handleSubmit}>
-// <div className="form-group">
-// <label>用户名</label>
-// <input
-// type="text"
-// name="username"
-// className="form-input"
-// placeholder="请输入用户名"
-// value={formData.username}
-// onChange={handleChange}
-// required
-// minLength="3"
-// maxLength="20"
-// />
-// </div>
-// <div className="form-group">
-// <label>邮箱</label>
-// <input
-// type="email"
-// name="email"
-// className="form-input"
-// placeholder="请输入邮箱"
-// value={formData.email}
-// onChange={handleChange}
-// required
-// />
-// </div>
-// <div className="form-group">
-// <label>密码</label>
-// <input
-// type="password"
-// name="password"
-// className="form-input"
-// placeholder="请输入密码"
-// value={formData.password}
-// onChange={handleChange}
-// required
-// minLength="6"
-// />
-// </div>
-// <div className="form-group">
-// <label>邀请码</label>
-// <input
-// type="text"
-// name="inviteCode"
-// className="form-input"
-// placeholder="请输入邀请码"
-// value={formData.inviteCode}
-// onChange={handleChange}
-// required
-// />
-// </div>
-// <button
-// type="submit"
-// className="auth-button"
-// disabled={isSubmitting}
-// >
-// {isSubmitting ? '注册中...' : '注册'}
-// </button>
-// <p className="login-link">
-// 已有账号?{' '}
-// <button
-// type="button"
-// onClick={onLoginClick}
-// className="link-button"
-// >
-// 点击登录
-// </button>
-// </p>
-// </form>
-// </div>
-// </div>
-// );
-// };
-
-// export default Register;
\ No newline at end of file
diff --git a/src/components/Auth/Register.test.jsx.txt b/src/components/Auth/Register.test.jsx.txt
deleted file mode 100644
index 356fadd..0000000
--- a/src/components/Auth/Register.test.jsx.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-// import React from 'react';
-// import { render, screen, fireEvent } from '@testing-library/react';
-// import Register from './Register';
-
-// describe('Register component', () => {
-// test('renders register form', () => {
-// const onLoginClick = jest.fn();
-// render(<Register onLoginClick={onLoginClick} />);
-
-// const usernameInput = screen.getByPlaceholderText('请输入用户名');
-// const passwordInput = screen.getByPlaceholderText('请输入密码');
-// const emailInput = screen.getByPlaceholderText('请输入邮箱');
-// const verifyButton = screen.getByText('验证邮箱');
-// const registerButton = screen.getByText('注册');
-
-// expect(usernameInput).toBeInTheDocument();
-// expect(passwordInput).toBeInTheDocument();
-// expect(emailInput).toBeInTheDocument();
-// expect(verifyButton).toBeInTheDocument();
-// expect(registerButton).toBeInTheDocument();
-// });
-
-// test('calls onLoginClick when "点击登录" is clicked', () => {
-// const onLoginClick = jest.fn();
-// render(<Register onLoginClick={onLoginClick} />);
-
-// const loginButton = screen.getByText('点击登录');
-// fireEvent.click(loginButton);
-
-// expect(onLoginClick).toHaveBeenCalled();
-// });
-// });
\ No newline at end of file
diff --git a/src/context/useGroupStore.js b/src/context/useGroupStore.js
new file mode 100644
index 0000000..985bc33
--- /dev/null
+++ b/src/context/useGroupStore.js
@@ -0,0 +1,113 @@
+import { createContext, useContext, useState, useCallback } from 'react';
+import { fetchGroups, joinGroup, createPost } from '../services/groupService';
+
+const GroupContext = createContext();
+
+export const GroupProvider = ({ children }) => {
+ const [groups, setGroups] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [category, setCategory] = useState('');
+ const [name, setName] = useState('');
+ const [page, setPage] = useState(1);
+ const [size, setSize] = useState(10);
+ const [totalPages, setTotalPages] = useState(1);
+ const [sortBy, setSortBy] = useState('member_count');
+ const [joinStatus, setJoinStatus] = useState({});
+
+ // 获取小组列表
+ const fetchGroupList = useCallback(async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const requestData = {
+ page,
+ size,
+ sort_by: sortBy
+ };
+
+ if (category) requestData.category = category;
+ if (name) requestData.name = name;
+
+ const response = await fetchGroups(requestData);
+
+ if (response.status === 'success') {
+ setGroups(response.items || []);
+ setTotalPages(response.total_pages || 1);
+ } else {
+ setError('获取兴趣小组列表失败');
+ }
+ } catch (err) {
+ setError('请求失败,请稍后再试');
+ } finally {
+ setLoading(false);
+ }
+ }, [category, name, page, size, sortBy]);
+
+ // 加入小组
+ const handleJoinGroup = useCallback(async (groupId, userId) => {
+ if (!userId) return;
+
+ try {
+ setJoinStatus(prev => ({ ...prev, [groupId]: '请求中' }));
+ const response = await joinGroup(groupId, userId);
+
+ if (response.status === 'success') {
+ setJoinStatus(prev => ({ ...prev, [groupId]: '加入成功' }));
+ fetchGroupList(); // 刷新列表
+ } else {
+ setJoinStatus(prev => ({ ...prev, [groupId]: '加入失败' }));
+ }
+ } catch (error) {
+ setJoinStatus(prev => ({ ...prev, [groupId]: '请求失败' }));
+ }
+ }, [fetchGroupList]);
+
+ // 创建帖子
+ const handleCreatePost = useCallback(async (groupId, userId, content, title, images) => {
+ if (!userId || !content || !title) return;
+
+ try {
+ const response = await createPost(groupId, userId, content, title, images);
+
+ if (response.post_id) {
+ return true; // 成功
+ } else {
+ throw new Error('创建帖子失败');
+ }
+ } catch (error) {
+ console.error('创建帖子错误:', error);
+ return false;
+ }
+ }, []);
+
+ const value = {
+ groups,
+ loading,
+ error,
+ category,
+ setCategory,
+ name,
+ setName,
+ page,
+ setPage,
+ size,
+ setSize,
+ totalPages,
+ sortBy,
+ setSortBy,
+ joinStatus,
+ fetchGroupList,
+ handleJoinGroup,
+ handleCreatePost
+ };
+
+ return (
+ <GroupContext.Provider value={value}>
+ {children}
+ </GroupContext.Provider>
+ );
+};
+
+export const useGroupStore = () => useContext(GroupContext);
\ No newline at end of file
diff --git a/src/pages/Forum/posts-main/components/CreatePostButton.jsx b/src/pages/Forum/posts-main/components/CreatePostButton.jsx
index e324056..0aa24fb 100644
--- a/src/pages/Forum/posts-main/components/CreatePostButton.jsx
+++ b/src/pages/Forum/posts-main/components/CreatePostButton.jsx
@@ -4,6 +4,10 @@
import './CreatePostButton.css';
import { useUser } from '../../../../context/UserContext';
+const user = JSON.parse(localStorage.getItem('user')); // user = { user_id: 123, ... }
+const userId = user?.userId;
+
+
const CreatePostButton = () => {
const { user } = useUser();
const userId = user?.userId; // 这里改为 userId,跟 UserContext 统一
diff --git a/src/pages/FriendMoments/FriendMoments.css b/src/pages/FriendMoments/FriendMoments.css
index 1059f10..087abcb 100644
--- a/src/pages/FriendMoments/FriendMoments.css
+++ b/src/pages/FriendMoments/FriendMoments.css
@@ -11,7 +11,11 @@
padding: 2%;
}
-
+.like-container{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
.f-search-bar {
display: flex;
align-items: center;
diff --git a/src/pages/FriendMoments/FriendMoments.jsx b/src/pages/FriendMoments/FriendMoments.jsx
index fd017b2..526c06d 100644
--- a/src/pages/FriendMoments/FriendMoments.jsx
+++ b/src/pages/FriendMoments/FriendMoments.jsx
@@ -1,32 +1,76 @@
-import React, { useState, useEffect } from 'react';
+import React, { useContext, useState, useEffect } from 'react';
import axios from 'axios';
import './FriendMoments.css';
import Header from '../../components/Header';
-import { Edit } from '@icon-park/react';
+import { Edit, GoodTwo, Comment } from '@icon-park/react';
+import { UserContext } from '../../context/UserContext'; // 引入用户上下文
const FriendMoments = () => {
const [feeds, setFeeds] = useState([]);
const [filteredFeeds, setFilteredFeeds] = useState([]);
const [query, setQuery] = useState('');
- const [userId, setUserId] = useState(456); // 从状态管理或登录信息获取
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [commentBoxVisibleId, setCommentBoxVisibleId] = useState(null); // 当前显示评论框的动态ID
+ const [commentInput, setCommentInput] = useState(''); // 当前输入的评论内容
+
+ // 从上下文中获取用户信息
+ const { user } = useContext(UserContext);
+ const userId = user?.userId || null; // 从用户上下文中获取userId
+ const username = user?.username || '未知用户'; // 获取用户名
// Modal state & form fields
const [showModal, setShowModal] = useState(false);
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [selectedImages, setSelectedImages] = useState([]);
- const [previewUrls, setPreviewUrls] = useState([]); // 新增:图片预览URLs
+ const [previewUrls, setPreviewUrls] = useState([]);
+
+ // 检查用户是否已登录
+ const isLoggedIn = !!userId;
// 拉取好友动态列表
const fetchFeeds = async () => {
+ if (!isLoggedIn) {
+ setLoading(false);
+ setError('请先登录');
+ return;
+ }
+
+ setLoading(true);
+ setError(null);
try {
- // 修改为新的API路径
+ // 注意这里修改了API路径,使用getAllDynamics接口
const res = await axios.get(`/echo/dynamic/${userId}/getAllDynamics`);
- setFeeds(res.data.posts || []);
- setFilteredFeeds(res.data.posts || []);
+
+ // 检查API返回的数据结构
+ console.log('API响应数据:', res.data);
+
+ // 从响应中提取dynamic数组
+ const dynamicList = res.data.dynamic || [];
+
+ // 将API返回的数据结构转换为前端期望的格式
+ const formattedFeeds = dynamicList.map(item => ({
+ postNo: item.dynamic_id, // 使用API返回的dynamic_id作为帖子ID
+ title: item.title,
+ postContent: item.content,
+ imageUrl: item.images, // 使用API返回的images字段
+ postTime: item.time, // 使用API返回的time字段
+ postLikeNum: item.likes?.length || 0, // 点赞数
+ liked: item.likes?.some(like => like.user_id === userId), // 当前用户是否已点赞
+ user_id: item.user_id, // 发布者ID
+ username: item.username, // 发布者昵称
+ avatar_url: item.avatar_url, // 发布者头像
+ comments: item.comments || [] // 评论列表
+ }));
+
+ setFeeds(formattedFeeds);
+ setFilteredFeeds(formattedFeeds);
} catch (err) {
console.error('获取动态列表失败:', err);
- alert('获取动态列表失败,请稍后重试');
+ setError('获取动态列表失败,请稍后重试');
+ } finally {
+ setLoading(false);
}
};
@@ -59,23 +103,27 @@
const files = Array.from(e.target.files);
if (!files.length) return;
- // 生成预览URLs
const previewUrls = files.map(file => URL.createObjectURL(file));
setSelectedImages(files);
- setPreviewUrls(previewUrls); // 更新预览URLs
+ setPreviewUrls(previewUrls);
};
// 对话框内:提交新动态
const handleSubmit = async () => {
+ if (!isLoggedIn) {
+ alert('请先登录');
+ return;
+ }
+
if (!content.trim()) {
alert('内容不能为空');
return;
}
try {
+ // 使用formData格式提交
const formData = new FormData();
- formData.append('user_id', userId);
formData.append('title', title.trim() || '');
formData.append('content', content.trim());
@@ -84,7 +132,7 @@
formData.append('image_url', file);
});
- // 修改为新的API路径
+ // 调用创建动态API
await axios.post(`/echo/dynamic/${userId}/createDynamic`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
@@ -95,7 +143,7 @@
setTitle('');
setContent('');
setSelectedImages([]);
- setPreviewUrls([]); // 重置预览URLs
+ setPreviewUrls([]);
setShowModal(false);
fetchFeeds();
alert('发布成功');
@@ -105,11 +153,17 @@
}
};
- // 删除动态
+ // 删除动态 - 注意:API文档中未提供删除接口,这里保留原代码
const handleDelete = async (dynamicId) => {
+
+ if (!isLoggedIn) {
+ alert('请先登录');
+ return;
+ }
+
if (!window.confirm('确定要删除这条动态吗?')) return;
try {
- // 修改为新的API路径
+ // 注意:API文档中未提供删除接口,这里使用原代码中的路径
await axios.delete(`/echo/dynamic/me/deleteDynamic/${dynamicId}`);
fetchFeeds();
alert('删除成功');
@@ -120,64 +174,224 @@
};
// 点赞动态
- const handleLike = async (dynamicId) => {
+ const handleLike = async (dynamicId,islike) => {
+ if (islike) {
+ handleUnlike(dynamicId);
+ return
+ }
+ if (!isLoggedIn) {
+ alert('请先登录');
+ return;
+ }
+
+ // 验证dynamicId是否有效
+ if (!dynamicId) {
+ console.error('无效的dynamicId:', dynamicId);
+ alert('点赞失败:动态ID无效');
+ return;
+ }
+
+ // 检查是否已经点赞,防止重复请求
+ // const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
+ // if (currentFeed && currentFeed.liked) {
+ // console.warn('尝试重复点赞,已忽略');
+ // return;
+ // }
+
+ console.log('当前用户ID:', userId);
+ console.log('即将点赞的动态ID:', dynamicId);
+
try {
- // 调用新的点赞API
- const res = await axios.post(`/echo/dynamic/like`, {
- userId,
- dynamicId
+ // 确保参数是整数类型
+ const requestData = {
+ userId: parseInt(userId),
+ dynamicId: parseInt(dynamicId)
+ };
+
+ // 验证参数是否为有效数字
+ if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
+ console.error('无效的参数:', requestData);
+ alert('点赞失败:参数格式错误');
+ return;
+ }
+
+ console.log('点赞请求数据:', requestData);
+
+ const res = await axios.post(`/echo/dynamic/like`, requestData, {
+ headers: {
+ 'Content-Type': 'application/json' // 明确指定JSON格式
+ }
});
+ console.log('点赞API响应:', res.data);
+
if (res.status === 200) {
// 更新本地状态
- setFeeds(feeds.map(feed => {
+ feeds.forEach(feed => {
if (feed.postNo === dynamicId) {
- return {
- ...feed,
- postLikeNum: (feed.postLikeNum || 0) + 1,
- liked: true
- };
+ feed.postLikeNum = (feed.postLikeNum || 0) + 1;
+ feed.liked = true;
}
- return feed;
- }));
+ });
+ setFeeds([...feeds]); // 更新状态以触发重新渲染
} else {
alert(res.data.message || '点赞失败');
}
} catch (err) {
console.error('点赞失败', err);
+
+ // 检查错误响应,获取更详细的错误信息
+ if (err.response) {
+ console.error('错误响应数据:', err.response.data);
+ console.error('错误响应状态:', err.response.status);
+ console.error('错误响应头:', err.response.headers);
+ }
+
alert('点赞失败,请稍后重试');
}
};
// 取消点赞
const handleUnlike = async (dynamicId) => {
+ if (!isLoggedIn) {
+ alert('请先登录');
+ return;
+ }
+
+ // 验证dynamicId是否有效
+ if (!dynamicId) {
+ console.error('无效的dynamicId:', dynamicId);
+ alert('取消点赞失败:动态ID无效');
+ return;
+ }
+
+ // 检查是否已经取消点赞,防止重复请求
+ const currentFeed = feeds.find(feed => feed.postNo === dynamicId);
+ if (currentFeed && !currentFeed.liked) {
+ console.warn('尝试重复取消点赞,已忽略');
+ return;
+ }
+
try {
- // 调用新的取消点赞API
+ // 确保参数是整数类型
+ const requestData = {
+ userId: parseInt(userId),
+ dynamicId: parseInt(dynamicId)
+ };
+
+ // 验证参数是否为有效数字
+ if (isNaN(requestData.userId) || isNaN(requestData.dynamicId)) {
+ console.error('无效的参数:', requestData);
+ alert('取消点赞失败:参数格式错误');
+ return;
+ }
+
+ console.log('取消点赞请求数据:', requestData);
+
const res = await axios.delete(`/echo/dynamic/unlike`, {
- data: { userId, dynamicId }
+ headers: {
+ 'Content-Type': 'application/json' // 明确指定JSON格式
+ },
+ data: requestData // 将参数放在data属性中
});
+ console.log('取消点赞API响应:', res.data);
+
if (res.status === 200) {
// 更新本地状态
- setFeeds(feeds.map(feed => {
+ feeds.forEach(feed => {
if (feed.postNo === dynamicId) {
- return {
- ...feed,
- postLikeNum: Math.max(0, (feed.postLikeNum || 0) - 1),
- liked: false
- };
+ feed.postLikeNum = Math.max(0, (feed.postLikeNum || 0) - 1);
+ feed.liked = false;
}
- return feed;
- }));
+ });
+ setFeeds([...feeds]); // 更新状态以触发重新渲染
} else {
alert(res.data.message || '取消点赞失败');
}
} catch (err) {
console.error('取消点赞失败', err);
+
+ // 检查错误响应,获取更详细的错误信息
+ if (err.response) {
+ console.error('错误响应数据:', err.response.data);
+ console.error('错误响应状态:', err.response.status);
+ console.error('错误响应头:', err.response.headers);
+ }
+
alert('取消点赞失败,请稍后重试');
}
};
+ // 评论好友动态
+ // 评论好友动态
+const handleComment = async (dynamicId) => {
+ if (!isLoggedIn) {
+ alert('请先登录');
+ return;
+ }
+
+ if (!commentInput.trim()) {
+ alert('评论内容不能为空');
+ return;
+ }
+
+ try {
+ const res = await axios.post(`/echo/dynamic/${userId}/feeds/${dynamicId}/comments`, {
+ content: commentInput.trim()
+ });
+
+ if (res.status === 200 || res.status === 201) {
+ // 成功获取评论数据
+ const newComment = {
+ user_id: userId,
+ username: username,
+ content: commentInput.trim(),
+ time: new Date().toISOString() // 使用当前时间作为评论时间
+ };
+
+ // 更新本地状态,添加新评论
+ setFeeds(prevFeeds => {
+ return prevFeeds.map(feed => {
+ if (feed.postNo === dynamicId) {
+ // 确保comments是数组,并且正确合并新评论
+ const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
+ return {
+ ...feed,
+ comments: [...currentComments, newComment]
+ };
+ }
+ return feed;
+ });
+ });
+
+ // 更新过滤后的动态列表
+ setFilteredFeeds(prevFeeds => {
+ return prevFeeds.map(feed => {
+ if (feed.postNo === dynamicId) {
+ // 确保comments是数组,并且正确合并新评论
+ const currentComments = Array.isArray(feed.comments) ? feed.comments : [];
+ return {
+ ...feed,
+ comments: [...currentComments, newComment]
+ };
+ }
+ return feed;
+ });
+ });
+
+ // alert('评论成功');
+ setCommentInput('');
+ setCommentBoxVisibleId(null); // 关闭评论框
+ } else {
+ alert(res.data.error || '评论失败');
+ }
+ } catch (err) {
+ console.error('评论失败', err);
+ alert('评论失败,请稍后重试');
+ }
+};
+
return (
<div className="friend-moments-container">
<Header />
@@ -186,7 +400,7 @@
<Edit theme="outline" size="18" style={{ marginRight: '6px' }} />
创建动态
</button>
- <div className="f-search-bar">
+ {/* <div className="f-search-bar">
<input
className="search-input"
type="text"
@@ -196,46 +410,115 @@
/>
<button className="search-btn" onClick={handleSearch}>搜索</button>
<button className="search-btn" onClick={handleReset}>重置</button>
- </div>
+ </div> */}
</div>
<div className="feed-list">
- {filteredFeeds.map(feed => (
- <div className="feed-item" key={feed.postNo}>
- {feed.title && <h4>{feed.title}</h4>}
- <p>{feed.postContent}</p>
-
- {feed.imageUrl && feed.imageUrl.split(',').length > 0 && (
- <div className="feed-images">
- {feed.imageUrl.split(',').map((url, i) => (
- <img key={i} src={url} alt={`动态图${i}`} />
- ))}
- </div>
- )}
-
- <div className="feed-footer">
- <span className="feed-date">
- {new Date(feed.postTime).toLocaleString()}
- </span>
- <div className="like-container">
- {feed.liked ? (
- <button className="unlike-btn" onClick={() => handleUnlike(feed.postNo)}>
- 已点赞 ({feed.postLikeNum || 0})
- </button>
- ) : (
- <button className="like-btn" onClick={() => handleLike(feed.postNo)}>
- 点赞 ({feed.postLikeNum || 0})
- </button>
- )}
- </div>
- {feed.user_id === userId && (
- <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
- 删除
- </button>
- )}
- </div>
+ {loading ? (
+ <div className="loading-message">加载中...</div>
+ ) : error ? (
+ <div className="error-message">{error}</div>
+ ) : !isLoggedIn ? (
+ <div className="login-prompt">
+ <p>请先登录查看好友动态</p>
</div>
- ))}
+ ) : filteredFeeds.length === 0 ? (
+ <div className="empty-message">暂无动态</div>
+ ) : (
+ filteredFeeds.map(feed => (
+ <div className="feed-item" key={feed.postNo || `feed-${Math.random()}`}>
+ {/* 显示发布者信息 */}
+ <div className="feed-author">
+ <img src={feed.avatar_url || 'https://example.com/default-avatar.jpg'} alt={feed.username || '用户头像'} />
+ <div>
+ <h4>{feed.username || '未知用户'}</h4>
+ <span className="feed-date">{new Date(feed.postTime || Date.now()).toLocaleString()}</span>
+ </div>
+ </div>
+
+ {feed.title && <h4 className="feed-title">{feed.title}</h4>}
+ <p className="feed-content">{feed.postContent || '无内容'}</p>
+
+ {feed.imageUrl && (
+ <div className="feed-images">
+ {/* 处理可能是单张图片或多张图片的情况 */}
+ {typeof feed.imageUrl === 'string' ? (
+ <img src={feed.imageUrl} alt="动态图片" />
+ ) : (
+ feed.imageUrl.map((url, i) => (
+ <img key={i} src={url} alt={`动态图${i}`} />
+ ))
+ )}
+ </div>
+ )}
+
+ <div className="feed-footer">
+ <div className="like-container">
+ <button className="icon-btn" onClick={() => handleLike(feed.postNo, feed.liked, feed.user_id)}>
+ <GoodTwo theme="outline" size="24" fill={feed.liked ? '#f00' : '#fff'} />
+ <span>{feed.postLikeNum || 0}</span>
+
+ </button>
+
+ <button
+ className="icon-btn"
+ onClick={() => {
+ setCommentBoxVisibleId(feed.postNo);
+ setCommentInput('');
+ }}
+ >
+ <Comment theme="outline" size="24" fill="#333" />
+ <span>评论</span>
+ </button>
+
+ {commentBoxVisibleId === feed.postNo && (
+ <div className="comment-box">
+ <textarea
+ className="comment-input"
+ placeholder="请输入评论内容..."
+ value={commentInput}
+ onChange={(e) => setCommentInput(e.target.value)}
+ />
+ <button
+ className="submit-comment-btn"
+ onClick={() => handleComment(feed.postNo)}
+ >
+ 发布评论
+ </button>
+ </div>
+ )}
+ </div>
+ {feed.user_id === userId && (
+ <button className="delete-btn" onClick={() => handleDelete(feed.postNo)}>
+ 删除
+ </button>
+ )}
+</div>
+
+ {/* 评论列表 */}
+ {Array.isArray(feed.comments) && feed.comments.length > 0 && (
+ <div className="comments-container">
+ <h5>评论 ({feed.comments.length})</h5>
+ <div className="comments-list">
+ {feed.comments.map((comment, index) => (
+ <div className="comment-item" key={index}>
+ <div className="comment-header">
+ <span className="comment-user">{comment.username || '用户'}</span>
+ {/* <span className="comment-user-id">ID: {comment.user_id}</span> */}
+ <span className="comment-time">
+ {new Date(comment.time || Date.now()).toLocaleString()}
+ </span>
+ </div>
+ <p className="comment-content">{comment.content}</p>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+
+ </div>
+ ))
+ )}
</div>
{/* Modal 对话框 */}
@@ -265,7 +548,7 @@
/>
</label>
<div className="cf-preview">
- {previewUrls.map((url, i) => ( // 使用定义的previewUrls
+ {previewUrls.map((url, i) => (
<img key={i} src={url} alt={`预览${i}`} />
))}
</div>
@@ -284,4 +567,4 @@
);
};
-export default FriendMoments;
\ No newline at end of file
+export default FriendMoments;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/CreatePostForm.jsx b/src/pages/InterestGroup/CreatePostForm.jsx
new file mode 100644
index 0000000..6848b54
--- /dev/null
+++ b/src/pages/InterestGroup/CreatePostForm.jsx
@@ -0,0 +1,69 @@
+import React, { useState } from 'react';
+import { useGroupStore } from '../../context/useGroupStore';
+
+const CreatePostForm = ({ groupId, onClose }) => {
+ const { userId, handleCreatePost } = useGroupStore();
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const [images, setImages] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ try {
+ const success = await handleCreatePost(groupId, userId, content, title, images);
+
+ if (success) {
+ alert('帖子发布成功');
+ onClose();
+ } else {
+ setError('帖子发布失败');
+ }
+ } catch (error) {
+ setError(error.message || '帖子发布失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <div className="create-post-form">
+ <h4>发布新帖子</h4>
+ <input
+ type="text"
+ placeholder="帖子标题"
+ value={title}
+ onChange={(e) => setTitle(e.target.value)}
+ required
+ />
+ <textarea
+ placeholder="帖子内容"
+ value={content}
+ onChange={(e) => setContent(e.target.value)}
+ required
+ />
+ <input
+ type="file"
+ multiple
+ onChange={(e) => setImages(e.target.files)}
+ />
+
+ {error && <p className="error">{error}</p>}
+
+ <div className="button-group">
+ <button onClick={handleSubmit} disabled={loading}>
+ {loading ? '发布中...' : '发布'}
+ </button>
+ <button onClick={onClose} disabled={loading}>
+ 取消
+ </button>
+ </div>
+ </div>
+ );
+};
+
+export default CreatePostForm;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/GroupFilters.jsx b/src/pages/InterestGroup/GroupFilters.jsx
new file mode 100644
index 0000000..c93b495
--- /dev/null
+++ b/src/pages/InterestGroup/GroupFilters.jsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { useGroupStore } from '../../context/useGroupStore';
+
+const GroupFilters = () => {
+ const {
+ category, setCategory,
+ name, setName,
+ sortBy, setSortBy,
+ handleSearch
+ } = useGroupStore();
+
+ const handleSortChange = (e) => {
+ const sortValueMap = {
+ 'member_count': 'member_count',
+ 'name': 'groupName',
+ 'category': 'category'
+ };
+ const backendValue = sortValueMap[e.target.value] || 'member_count';
+ setSortBy(backendValue);
+ };
+
+ return (
+ <div className="filter-search-sort-container">
+ <div className="filter">
+ <label>分类:</label>
+ <select onChange={(e) => setCategory(e.target.value)} value={category}>
+ <option value="">全部</option>
+ <option value="影视">影视</option>
+ <option value="游戏">游戏</option>
+ <option value="学习">学习</option>
+ <option value="体育">体育</option>
+ <option value="其他">其他</option>
+ </select>
+ </div>
+
+ <div className="sort">
+ <label>排序:</label>
+ <select onChange={handleSortChange} value={sortBy}>
+ <option value="member_count">按成员数排序</option>
+ <option value="name">按名称排序</option>
+ <option value="category">按分类排序</option>
+ </select>
+ </div>
+
+ <div className="search">
+ <input
+ type="text"
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ placeholder="输入小组名称搜索"
+ />
+ <button onClick={handleSearch}>搜索</button>
+ </div>
+ </div>
+ );
+};
+
+export default GroupFilters;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/GroupItem.jsx b/src/pages/InterestGroup/GroupItem.jsx
new file mode 100644
index 0000000..4f76bac
--- /dev/null
+++ b/src/pages/InterestGroup/GroupItem.jsx
@@ -0,0 +1,178 @@
+// import React, { useState } from 'react';
+// import { useGroupStore } from '../../context/useGroupStore';
+// import { useUser } from '../../context/UserContext'; // ✅ 新增:引入 UserContext
+// import CreatePostForm from './CreatePostForm';
+
+// const GroupItem = ({ group }) => {
+// const { handleJoinGroup, joinStatus } = useGroupStore();
+// const { user } = useUser(); // ✅ 获取 user
+// const userId = user?.userId; // ✅ 安全获取 userId
+// const [showCreatePost, setShowCreatePost] = useState(false);
+
+// return (
+// <div className="group-item">
+// <div className="group-content">
+// <img
+// style={{ width: '40%', height: '40%' }}
+// src={group.cover_image || 'https://picsum.photos/200/200'}
+// alt={group.groupName || group.name}
+// className="group-cover"
+// />
+// <div className="group-info-right">
+// <h3>{group.groupName || group.name}</h3>
+// <p style={{ color: '#BA929A' }}>{group.memberCount || 0}人加入了小组</p>
+// <button
+// onClick={() => handleJoinGroup(group.group_id, userId)}
+// disabled={joinStatus[group.group_id] === '加入成功' || !userId}
+// >
+// {joinStatus[group.group_id] === '加入成功' ? '已加入' : userId ? '+加入小组' : '请登录'}
+// </button>
+
+// {userId && joinStatus[group.group_id] === '加入成功' && (
+// <button onClick={() => setShowCreatePost(!showCreatePost)}>
+// +发布帖子
+// </button>
+// )}
+// </div>
+// </div>
+
+// <div className="group-description">
+// <p>{group.description}</p>
+// </div>
+// <p>分类:{group.category}</p>
+
+// {showCreatePost && (
+// <CreatePostForm
+// groupId={group.group_id}
+// onClose={() => setShowCreatePost(false)}
+// />
+// )}
+// </div>
+// );
+// };
+
+// export default GroupItem;
+
+
+import React, { useState } from 'react';
+import { useGroupStore } from '../../context/useGroupStore';
+import { useUser } from '../../context/UserContext';
+import CreatePostForm from './CreatePostForm';
+
+const GroupItem = ({ group }) => {
+
+ console.log('group:', group);
+ const { handleJoinGroup, joinStatus } = useGroupStore();
+ const { user } = useUser();
+
+ const userId = user?.userId;
+ const groupId = group.groupId; // ✅ 使用正确字段
+console.log('加入小组请求 - groupId:', group.group_id, 'userId:', userId);
+
+ const [showCreatePost, setShowCreatePost] = useState(false);
+
+ return (
+ <div className="group-item">
+ <div className="group-content">
+ <img
+ style={{ width: '40%', height: '40%' }}
+ src={group.cover_image || 'https://picsum.photos/200/200'}
+ alt={group.groupName}
+ className="group-cover"
+ />
+ <div className="group-info-right">
+ <h3>{group.groupName}</h3> {/* ✅ 使用 groupName */}
+ <p style={{ color: '#BA929A' }}>{group.memberCount || 0}人加入了小组</p>
+
+ <button
+ onClick={() => handleJoinGroup(groupId, userId)}
+ disabled={joinStatus[groupId] === '加入成功' || !userId}
+ >
+
+ {joinStatus[groupId] === '加入成功' ? '已加入' : userId ? '+加入小组' : '请登录'}
+ </button>
+
+ {userId && joinStatus[groupId] === '加入成功' && (
+ <button onClick={() => setShowCreatePost(!showCreatePost)}>
+ +发布帖子
+ </button>
+ )}
+ </div>
+ </div>
+
+ <div className="group-description">
+ <p>{group.description}</p>
+ </div>
+ <p>分类:{group.category}</p>
+
+ {showCreatePost && (
+ <CreatePostForm
+ groupId={groupId}
+ onClose={() => setShowCreatePost(false)}
+ />
+ )}
+ </div>
+ );
+};
+
+export default GroupItem;
+
+
+// import React, { useState } from 'react';
+// import { useGroupStore } from '../../context/useGroupStore';
+// import { useUser } from '../../context/UserContext';
+// import CreatePostForm from './CreatePostForm';
+
+// const GroupItem = ({ group }) => {
+// console.log('group:', group);
+
+// const { handleJoinGroup, joinStatus } = useGroupStore();
+// const { user } = useUser();
+// const userId = user?.userId; // 确保使用正确的字段名(取自 localStorage 的结构)
+// const [showCreatePost, setShowCreatePost] = useState(false);
+
+// return (
+// <div className="group-item">
+// <div className="group-content">
+// <img
+// style={{ width: '40%', height: '40%' }}
+// src={group.cover_image || 'https://picsum.photos/200/200'}
+// alt={group.name}
+// className="group-cover"
+// />
+// <div className="group-info-right">
+// <h3>{group.name}</h3>
+// <p style={{ color: '#BA929A' }}>{group.member_count || 0}人加入了小组</p>
+
+// <button
+// onClick={() => handleJoinGroup(group.group_id, userId)}
+// disabled={joinStatus[group.group_id] === '加入成功' || !userId}
+// >
+// {joinStatus[group.group_id] === '加入成功' ? '已加入' : userId ? '+加入小组' : '请登录'}
+// </button>
+
+// {userId && joinStatus[group.group_id] === '加入成功' && (
+// <button onClick={() => setShowCreatePost(!showCreatePost)}>
+// +发布帖子
+// </button>
+// )}
+// </div>
+// </div>
+
+// <div className="group-description">
+// <p>{group.description}</p>
+// </div>
+// <p>分类:{group.category}</p>
+
+// {showCreatePost && (
+// <CreatePostForm
+// groupId={group.group_id}
+// onClose={() => setShowCreatePost(false)}
+// />
+// )}
+// </div>
+// );
+// };
+
+// export default GroupItem;
+
diff --git a/src/pages/InterestGroup/GroupList.jsx b/src/pages/InterestGroup/GroupList.jsx
new file mode 100644
index 0000000..6970f1d
--- /dev/null
+++ b/src/pages/InterestGroup/GroupList.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import GroupItem from './GroupItem';
+import { useGroupStore } from '../../context/useGroupStore';
+
+const GroupList = () => {
+ const { groups, loading, error } = useGroupStore();
+
+ if (loading) return <p>加载中...</p>;
+ if (error) return <p className="error">{error}</p>;
+ if (!groups || groups.length === 0) return (
+ <div className="empty-state">
+ <p>没有找到匹配的兴趣小组</p>
+ <p>请尝试调整筛选条件或搜索关键词</p>
+ </div>
+ );
+
+ return (
+ <div className="group-list">
+ {groups.map(group => (
+ <GroupItem key={group.group_id} group={group} />
+ ))}
+ </div>
+ );
+};
+
+export default GroupList;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/GroupPagination.jsx b/src/pages/InterestGroup/GroupPagination.jsx
new file mode 100644
index 0000000..14b093b
--- /dev/null
+++ b/src/pages/InterestGroup/GroupPagination.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { useGroupStore } from '../../context/useGroupStore';
+
+const GroupPagination = () => {
+ const { page, totalPages, setPage } = useGroupStore();
+
+ const handlePrevPage = () => {
+ if (page > 1) setPage(page - 1);
+ };
+
+ const handleNextPage = () => {
+ if (page < totalPages) setPage(page + 1);
+ };
+
+ return (
+ <div className="pagination">
+ <button onClick={handlePrevPage} disabled={page <= 1}>
+ 上一页
+ </button>
+ <span>第 {page} 页 / 共 {totalPages} 页</span>
+ <button onClick={handleNextPage} disabled={page >= totalPages}>
+ 下一页
+ </button>
+ </div>
+ );
+};
+
+export default GroupPagination;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/InterestGroup.jsx b/src/pages/InterestGroup/InterestGroup.jsx
index a68f20c..b89cf57 100644
--- a/src/pages/InterestGroup/InterestGroup.jsx
+++ b/src/pages/InterestGroup/InterestGroup.jsx
@@ -1,221 +1,25 @@
-import React, { useEffect, useState } from 'react';
-import axios from 'axios';
+import React, { useEffect } from 'react';
+import Header from '../../components/Header';
+import { useGroupStore } from '../../context/useGroupStore';
+import GroupFilters from './GroupFilters';
+import GroupList from './GroupList';
+import GroupPagination from './GroupPagination';
import './InterestGroup.css';
-import Header from '../../components/Header'; // 导入 Header 组件
-
-
-
const InterestGroup = () => {
- const [groups, setGroups] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [category, setCategory] = useState('');
- const [name, setName] = useState('');
- const [page, setPage] = useState(1);
- const [size, setSize] = useState(10);
- const [totalPages, setTotalPages] = useState(1);
- const [sortBy, setSortBy] = useState('member_count'); // 默认按照成员数排序
- const [joinStatus, setJoinStatus] = useState({}); // 存储每个小组的加入状态
+ const { fetchGroupList, setPage, handleSearch } = useGroupStore();
+ // 初始化加载
useEffect(() => {
- // 请求兴趣小组列表
- const fetchGroups = async () => {
- try {
- setLoading(true);
- setError(null);
- const response = await axios.get(`/echo/groups`, {
- params: {
- category,
- name,
- page,
- size,
- sort_by: sortBy
- }
- });
-
- if (response.data.status === 'success') {
- setGroups(response.data.items);
- setTotalPages(response.data.total_pages); // 更新总页数
- } else {
- setError('获取兴趣小组列表失败');
- }
- } catch (err) {
- setError('请求失败,请稍后再试');
- } finally {
- setLoading(false);
- }
- };
-
- fetchGroups();
- }, [category, name, page, size, sortBy]);
-
- const handleCategoryChange = (e) => {
- setCategory(e.target.value);
- setPage(1); // 重置为第一页
- };
-
- const handleSearchChange = (e) => {
- setName(e.target.value);
- setPage(1); // 重置为第一页
- };
-
- const handleSortChange = (e) => {
- setSortBy(e.target.value);
- };
-
- const handlePageChange = (newPage) => {
- if (newPage > 0 && newPage <= totalPages) {
- setPage(newPage);
- }
- };
-
- // 加入兴趣小组
- const handleJoinGroup = async (groupId) => {
- const userId = 1; // 假设用户ID为1,可以根据实际情况获取
-
- try {
- const response = await axios.post(`/echo/groups/${groupId}/join`, {
- user_id: userId
- });
-
- if (response.data.status === 'success') {
- setJoinStatus(prevState => ({
- ...prevState,
- [groupId]: '加入成功'
- }));
- } else {
- setJoinStatus(prevState => ({
- ...prevState,
- [groupId]: '加入失败'
- }));
- }
- } catch (error) {
- setJoinStatus(prevState => ({
- ...prevState,
- [groupId]: '请求失败,请稍后再试'
- }));
- }
- };
-
- const handleSearch = () => {
- // 触发搜索逻辑,通过修改 name 状态重新请求数据
- setPage(1);
- };
+ fetchGroupList();
+ }, [fetchGroupList]);
return (
<div className="interest-group-container">
- {/* Header 组件放在页面最上方 */}
<Header />
<div className="interest-group-card">
- {/* <h1>兴趣小组列表</h1> */}
- {/* 水平排列的筛选、搜索和排序容器 */}
- <div className="filter-search-sort-container">
- {/* 分类筛选 */}
- <div className="filter">
- <label>分类:</label>
- <select onChange={handleCategoryChange} value={category} style={{ width: '150px' }}>
- <option value="">全部</option>
- <option value="影视">影视</option>
- <option value="游戏">游戏</option>
- <option value="学习">学习</option>
- <option value="体育">体育</option>
- <option value="其他">其他</option>
- </select>
- </div>
-
- {/* 排序 */}
- <div className="sort">
- <label>排序:</label>
- <select onChange={handleSortChange} value={sortBy} style={{ width: '150px' }}>
- <option value="member_count">按成员数排序</option>
- <option value="name">按名称排序</option>
- <option value="category">按分类排序</option>
- </select>
- </div>
-
- {/* 搜索框 */}
- <div className="search">
- <input
- type="text"
- value={name}
- onChange={handleSearchChange}
- placeholder="输入小组名称搜索"
- />
- <button onClick={handleSearch} style={{ backgroundColor: '#BA929A', color: 'white' , padding: '5px 10px'}}>搜索</button>
- </div>
- </div>
-
- {/* 加载中提示 */}
- {loading && <p>加载中...</p>}
-
- {/* 错误提示 */}
- {error && <p className="error">{error}</p>}
-
- {/* 小组列表 */}
- {!loading && !error && (
- <div className="group-list">
- {groups.map((group) => (
- <div className="group-item" key={group.group_id}>
- <div className="group-content">
- <img
- style={{ width: '40%', height: '40%'}}
- src={group.cover_image}
- alt={group.name}
- className="group-cover"
- />
- <div
- className="group-info-right"
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- gap: '4px', // 控制元素之间的垂直间距
- }}
- >
- <h3 style={{ margin: 0 }}>{group.name}</h3>
- <p style={{ color: '#BA929A', margin: 0 }}>{group.member_count}人加入了小组</p>
- <button
- onClick={() => handleJoinGroup(group.group_id)}
- disabled={joinStatus[group.group_id] === '加入成功'}
- style={{
- background: 'none',
- color: '#007bff',
- padding: 0,
- marginTop: '4px',
- /*左对齐*/
- textAlign: 'left',
- marginLeft: '0',
- cursor: joinStatus[group.group_id] === '加入成功' ? 'default' : 'pointer',
- }}
- >
- {joinStatus[group.group_id] === '加入成功' ? '已加入' : '+加入小组'}
- </button>
- </div>
-
- </div>
- <div className="group-description">
- <p>{group.description}</p>
- </div>
- <p>分类:{group.category}</p>
-
- </div>
- ))}
- </div>
- )}
-
- {/* 分页 */}
- <div className="pagination">
- <button onClick={() => handlePageChange(page - 1)} disabled={page <= 1}>
- 上一页
- </button>
- <span>第 {page} 页 / 共 {totalPages} 页</span>
- <button
- onClick={() => handlePageChange(page + 1)}
- disabled={page >= totalPages}
- >
- 下一页
- </button>
- </div>
+ <GroupFilters />
+ <GroupList />
+ <GroupPagination />
</div>
</div>
);
diff --git a/src/services/groupService.js b/src/services/groupService.js
new file mode 100644
index 0000000..29e99af
--- /dev/null
+++ b/src/services/groupService.js
@@ -0,0 +1,91 @@
+// import axios from 'axios';
+
+// // 获取所有小组
+// export const fetchGroups = async (params) => {
+// const response = await axios.post('/echo/groups/getAllGroups', params);
+// return response.data;
+// };
+
+// // 加入小组
+// export const joinGroup = async (groupId, userId) => {
+// const response = await axios.post(`/echo/groups/${groupId}/join`, { user_id: userId });
+// return response.data;
+// };
+
+// // 创建帖子
+// export const createPost = async (groupId, userId, content, title, images) => {
+// const formData = new FormData();
+// formData.append('user_id', userId);
+// formData.append('content', content);
+// formData.append('title', title);
+
+// if (images && images.length > 0) {
+// for (let i = 0; i < images.length; i++) {
+// formData.append('images', images[i]);
+// }
+// }
+
+// const response = await axios.post(`/echo/groups/${groupId}/createPost`, formData, {
+// headers: { 'Content-Type': 'multipart/form-data' }
+// });
+
+// return response.data;
+// };
+
+import axios from 'axios';
+
+// 获取所有小组
+export const fetchGroups = async (params) => {
+ try {
+ const response = await axios.post('/echo/groups/getAllGroups', params);
+ return response.data;
+ } catch (error) {
+ console.error('获取小组列表失败:', error.response?.data || error.message);
+ return { status: 'failure', message: '获取小组失败' };
+ }
+};
+
+// 加入小组
+export const joinGroup = async (groupId, userId) => {
+ try {
+ const response = await axios.post(
+ `/echo/groups/${groupId}/join`,
+ { user_id: userId },
+ {
+ headers: {
+ 'Content-Type': 'application/json' // ✅ 明确设置为 JSON
+ }
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error('加入小组失败:', error.response?.data || error.message);
+ return { status: 'failure', message: '加入小组失败' };
+ }
+};
+
+
+// 创建帖子
+export const createPost = async (groupId, userId, content, title, images) => {
+ try {
+ const formData = new FormData();
+ formData.append('user_id', userId);
+ formData.append('content', content);
+ formData.append('title', title);
+
+ if (images && images.length > 0) {
+ for (let i = 0; i < images.length; i++) {
+ formData.append('images', images[i]);
+ }
+ }
+
+ const response = await axios.post(`/echo/groups/${groupId}/createPost`, formData, {
+ headers: { 'Content-Type': 'multipart/form-data' }
+ });
+
+ return response.data;
+ } catch (error) {
+ console.error('创建帖子失败:', error.response?.data || error.message);
+ return { status: 'failure', message: '创建帖子失败' };
+ }
+};