用户个人中心、兴趣小组
Change-Id: I0e2f3f4ad586f237505613238cbb7bebb6118b63
diff --git a/src/App.js b/src/App.js
index 4a74de6..a9b0d85 100644
--- a/src/App.js
+++ b/src/App.js
@@ -16,7 +16,7 @@
import CreatePostPage from './pages/Forum/posts-create/CreatePostPage';
import MessagePage from './pages/MessagePage/MessagePage';
import CreateMoment from './pages/FriendMoments/CreateMoment';
-import LevelPage from './pages/LevelPage/LevelPage';
+// import LevelPage from './pages/LevelPage/LevelPage';
import NewbieTasks from './pages/UserCenter/NewbieTasks';
import UserDynamics from './pages/UserCenter/UserDynamics';
import UserFriends from './pages/UserCenter/UserFriends';
@@ -72,7 +72,7 @@
<Route path="/seed/:id" component={({ id }) => <PrivateRoute component={() => <SeedDetail id={id} />} />} />
<Route path="/interest-groups" component={() => <PrivateRoute component={InterestGroup} />} />
<Route path="/messages" component={() => <PrivateRoute component={MessagePage} />} />
- <Route path="/level" component={() => <PrivateRoute component={LevelPage} />} />
+ {/* <Route path="/level" component={() => <PrivateRoute component={LevelPage} />} /> */}
<Route path="/information/:userId" component={({ userId }) => <PrivateRoute component={() => <UserInfo userId={userId} />} />} />
<Route path="/new-user-guide" component={() => <PrivateRoute component={NewUserGuide} />} />
@@ -101,91 +101,4 @@
);
}
-export default App;
-
-
-// // export default App;
-
-// import { Route } from 'wouter';
-// import { Redirect } from 'wouter';
-// import AuthPage from './pages/AuthPage/AuthPage';
-// import HomePage from './pages/HomePage';
-// import FriendMoments from './pages/FriendMoments/FriendMoments';
-// import ForumPage from './pages/Forum/posts-main/ForumPage';
-// import SeedList from './pages/SeedList/SeedList';
-// import PostDetailPage from './pages/Forum/posts-detail/PostDetailPage';
-// import { UserProvider } from './context/UserContext';
-// import { GroupProvider } from './context/useGroupStore';
-// import PublishSeed from './pages/PublishSeed/PublishSeed';
-// import SeedDetail from './pages/SeedList/SeedDetail/SeedDetail';
-// import InterestGroup from './pages/InterestGroup/InterestGroup';
-// import UserProfile from './pages/UserCenter/UserProfile';
-// import CreatePostPage from './pages/Forum/posts-create/CreatePostPage';
-// import MessagePage from './pages/MessagePage/MessagePage';
-// import CreateMoment from './pages/FriendMoments/CreateMoment';
-// // import PromotionsPage from './pages/PromotionsPage/PromotionsPage';
-// import LevelPage from './pages/LevelPage/LevelPage';
-// import NewbieTasks from './pages/UserCenter/NewbieTasks';
-// import UserDynamics from './pages/UserCenter/UserDynamics';
-// import UserFriends from './pages/UserCenter/UserFriends';
-// import UserCollect from './pages/UserCenter/UserCollect';
-// import UserInvite from './pages/UserCenter/UserInvite';
-// import UserInfo from './pages/UserInfo/UserInfo';
-// import UserLayout from './pages/UserCenter/UserLayout';
-
-// function RedirectToAuth() {
-// if (typeof window !== 'undefined') {
-// window.location.replace('/auth');
-// }
-// return null;
-// }
-
-// function App() {
-// return (
-// <UserProvider>
-// <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/:id" component={SeedDetail} />
-// <Route path="/interest-groups" component={InterestGroup} />
-// <Route path="/messages" component={MessagePage} />
-// {/* <Route path="/promotions" component={PromotionsPage} /> */}
-// <Route path="/level" component={LevelPage} />
-// <Route path="/information/:userId" component={UserInfo} />
-
-
-// {/* 用户中心嵌套路由模拟 */}
-// <Route path="/user/profile" component={() => (
-// <UserLayout><UserProfile /></UserLayout>
-// )} />
-// <Route path="/user/newbie-tasks" component={() => (
-// <UserLayout><NewbieTasks /></UserLayout>
-// )} />
-// <Route path="/user/dynamics" component={() => (
-// <UserLayout><UserDynamics /></UserLayout>
-// )} />
-// <Route path="/user/friends" component={() => (
-// <UserLayout><UserFriends /></UserLayout>
-// )} />
-// <Route path="/user/collections" component={() => (
-// <UserLayout><UserCollect /></UserLayout>
-// )} />
-// <Route path="/user/invite" component={() => (
-// <UserLayout><UserInvite /></UserLayout>
-// )} />
-// </>
-// </GroupProvider>
-// </UserProvider>
-// );
-// }
-
-// export default App;
+export default App;
\ No newline at end of file
diff --git a/src/context/useGroupStore.js b/src/context/useGroupStore.js
index 985bc33..d10a69a 100644
--- a/src/context/useGroupStore.js
+++ b/src/context/useGroupStore.js
@@ -98,6 +98,7 @@
sortBy,
setSortBy,
joinStatus,
+ setJoinStatus,
fetchGroupList,
handleJoinGroup,
handleCreatePost
diff --git a/src/pages/Forum/promotion-part/Promotion.jsx b/src/pages/Forum/promotion-part/Promotion.jsx
index 8ca4067..953a37f 100644
--- a/src/pages/Forum/promotion-part/Promotion.jsx
+++ b/src/pages/Forum/promotion-part/Promotion.jsx
@@ -1,3 +1,829 @@
+// import React, { useEffect, useState, useRef } from 'react';
+// import './Promotion.css';
+// import { useUser } from '../../../context/UserContext';
+
+// const Promotion = () => {
+// const { user } = useUser();
+// const [promotions, setPromotions] = useState([]);
+// const [torrents, setTorrents] = useState([]);
+// const [loading, setLoading] = useState(true);
+// const [promoIndex, setPromoIndex] = useState(0);
+// const promoTimerRef = useRef(null);
+
+// // 新增:控制创建对话框显示
+// const [showCreateDialog, setShowCreateDialog] = useState(false);
+
+// // 创建促销活动表单状态
+// const [formData, setFormData] = useState({
+// name: '',
+// startTime: '',
+// endTime: '',
+// discountPercentage: '',
+// uploadCoeff: '',
+// downloadCoeff: '',
+// description: ''
+// });
+
+// useEffect(() => {
+// fetchData();
+// fetchTorrentList();
+// }, []);
+
+// useEffect(() => {
+// if (promotions.length === 0) return;
+// clearInterval(promoTimerRef.current);
+// promoTimerRef.current = setInterval(() => {
+// setPromoIndex(prev => (prev + 1) % promotions.length);
+// }, 5000);
+// return () => clearInterval(promoTimerRef.current);
+// }, [promotions]);
+
+// const fetchData = async () => {
+// try {
+// const response = await fetch('/seeds/promotions');
+// const json = await response.json();
+// const promoData = Array.isArray(json?.data) ? json.data : [];
+// setPromotions(promoData);
+// } catch (error) {
+// console.error('获取促销活动失败:', error);
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// const fetchTorrentList = async () => {
+// try {
+// const response = await fetch('/seeds/list');
+// const json = await response.json();
+// const torrentList = Array.isArray(json?.data) ? json.data : [];
+// setTorrents(torrentList);
+// } catch (error) {
+// console.error('获取种子列表失败:', error);
+// }
+// };
+
+// // 打开创建促销活动弹窗
+// const openCreateDialog = () => {
+// // 重置表单数据
+// setFormData({
+// name: '',
+// startTime: '',
+// endTime: '',
+// discountPercentage: '',
+// uploadCoeff: '',
+// downloadCoeff: '',
+// description: ''
+// });
+// setShowCreateDialog(true);
+// };
+
+// // 关闭弹窗
+// const closeCreateDialog = () => {
+// setShowCreateDialog(false);
+// };
+
+// // 处理表单输入变化
+// const handleInputChange = (e) => {
+// const { name, value } = e.target;
+// setFormData(prev => ({
+// ...prev,
+// [name]: value
+// }));
+// };
+
+// // 提交创建促销活动
+// const handleCreatePromotion = async () => {
+// if (torrents.length === 0) {
+// alert('没有可用的种子,请先上传种子');
+// return;
+// }
+// if (!formData.name.trim()) {
+// alert('促销名称不能为空');
+// return;
+// }
+// if (!formData.startTime || !formData.endTime) {
+// alert('促销开始时间和结束时间不能为空');
+// return;
+// }
+// if (new Date(formData.startTime) >= new Date(formData.endTime)) {
+// alert('促销结束时间必须晚于开始时间');
+// return;
+// }
+// if (!formData.discountPercentage || isNaN(formData.discountPercentage)) {
+// alert('折扣百分比必须是数字');
+// return;
+// }
+
+// const applicableTorrentIds = torrents.map(t => t.id);
+
+// const newPromo = {
+// name: formData.name,
+// startTime: new Date(formData.startTime).toISOString(),
+// endTime: new Date(formData.endTime).toISOString(),
+// discountPercentage: Number(formData.discountPercentage),
+// uploadCoeff: formData.uploadCoeff ? Number(formData.uploadCoeff) : undefined,
+// downloadCoeff: formData.downloadCoeff ? Number(formData.downloadCoeff) : undefined,
+// applicableTorrentIds: JSON.stringify(applicableTorrentIds), // ✅ 关键修改
+// description: formData.description
+// };
+
+
+// try {
+// const res = await fetch('/seeds/promotions', {
+// method: 'POST',
+// headers: { 'Content-Type': 'application/json' },
+// body: JSON.stringify(newPromo)
+// });
+// const json = await res.json();
+// if (json.code === 200) {
+// alert('促销活动创建成功');
+// fetchData();
+// setShowCreateDialog(false);
+// } else {
+// alert('创建失败: ' + (json.msg || '未知错误'));
+// }
+// } catch (err) {
+// console.error('创建促销失败:', err);
+// alert('创建促销失败');
+// }
+// };
+
+// const handleDeletePromotion = async (promotionId) => {
+// if (!window.confirm('确认删除该促销活动吗?')) return;
+
+// try {
+// const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
+// const json = await res.json();
+// if (json.success) {
+// alert('删除成功');
+// fetchData();
+// } else {
+// alert('删除失败: ' + json.message);
+// }
+// } catch (err) {
+// console.error('删除失败:', err);
+// }
+// };
+
+// const isAdmin = user?.role === 'admin';
+// const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
+// const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
+// const currentPromo = promotions[promoIndex];
+
+// if (loading) {
+// return <div className="promotion-container">加载中...</div>;
+// }
+
+// return (
+// <div className="promotion-container carousel-container">
+// <section className="carousel-section">
+// <h2>当前促销活动</h2>
+
+// {isAdmin && (
+// <button className="create-btn" onClick={openCreateDialog}>
+// 创建促销活动
+// </button>
+// )}
+
+// {promotions.length === 0 || !currentPromo ? (
+// <div className="empty-state">暂无促销活动</div>
+// ) : (
+// <div
+// className="carousel"
+// onMouseEnter={() => clearInterval(promoTimerRef.current)}
+// onMouseLeave={() => {
+// promoTimerRef.current = setInterval(() => {
+// setPromoIndex(prev => (prev + 1) % promotions.length);
+// }, 3000);
+// }}
+// >
+// <button className="arrow left" onClick={prevPromo}><</button>
+// <div className="slide">
+// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
+// <div><strong>促销时间:</strong>
+// {currentPromo?.pStartTime && currentPromo?.pEndTime
+// ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
+// : '未知'}
+// </div>
+// <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
+// <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
+// {currentPromo?.description && (
+// <div><strong>描述:</strong>{currentPromo.description}</div>
+// )}
+// {isAdmin && (
+// <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
+// 删除该活动
+// </button>
+// )}
+// </div>
+// <button className="arrow right" onClick={nextPromo}>></button>
+// </div>
+// )}
+// </section>
+
+// {/* 创建促销活动弹窗 */}
+// {showCreateDialog && (
+// <div className="dialog-overlay">
+// <div className="dialog">
+// <h3>创建促销活动</h3>
+// <div className="form-item">
+// <label>促销名称:</label>
+// <input
+// type="text"
+// name="name"
+// value={formData.name}
+// onChange={handleInputChange}
+// placeholder="请输入促销名称"
+// />
+// </div>
+// <div className="form-item">
+// <label>开始时间:</label>
+// <input
+// type="datetime-local"
+// name="startTime"
+// value={formData.startTime}
+// onChange={handleInputChange}
+// />
+// </div>
+// <div className="form-item">
+// <label>结束时间:</label>
+// <input
+// type="datetime-local"
+// name="endTime"
+// value={formData.endTime}
+// onChange={handleInputChange}
+// />
+// </div>
+// <div className="form-item">
+// <label>折扣百分比(数字):</label>
+// <input
+// type="number"
+// name="discountPercentage"
+// value={formData.discountPercentage}
+// onChange={handleInputChange}
+// placeholder="例如:20 表示 20% 折扣"
+// min="0"
+// max="100"
+// />
+// </div>
+// <div className="form-item">
+// <label>上传奖励系数(可选):</label>
+// <input
+// type="number"
+// name="uploadCoeff"
+// value={formData.uploadCoeff}
+// onChange={handleInputChange}
+// placeholder="例如:1.5"
+// step="0.1"
+// />
+// </div>
+// <div className="form-item">
+// <label>下载折扣系数(可选):</label>
+// <input
+// type="number"
+// name="downloadCoeff"
+// value={formData.downloadCoeff}
+// onChange={handleInputChange}
+// placeholder="例如:0.8"
+// step="0.1"
+// />
+// </div>
+// <div className="form-item">
+// <label>描述(可选):</label>
+// <textarea
+// name="description"
+// value={formData.description}
+// onChange={handleInputChange}
+// placeholder="促销活动描述"
+// rows={3}
+// />
+// </div>
+// <div className="dialog-buttons">
+// <button onClick={handleCreatePromotion}>确定</button>
+// <button onClick={closeCreateDialog}>取消</button>
+// </div>
+// </div>
+// </div>
+// )}
+// </div>
+// );
+// };
+
+// export default Promotion;
+
+
+// // import React, { useEffect, useState, useRef } from 'react';
+// // import './Promotion.css';
+// // import { useUser } from '../../../context/UserContext';
+
+// // const Promotion = () => {
+// // const { user } = useUser();
+// // const [promotions, setPromotions] = useState([]);
+// // const [torrents, setTorrents] = useState([]);
+// // const [loading, setLoading] = useState(true);
+// // const [promoIndex, setPromoIndex] = useState(0);
+// // const promoTimerRef = useRef(null);
+
+// // // 新增:控制模态框显示与表单状态
+// // const [showCreateModal, setShowCreateModal] = useState(false);
+// // const [formData, setFormData] = useState({
+// // name: '',
+// // description: '',
+// // discountPercentage: 0,
+// // startTime: '',
+// // endTime: '',
+// // applicableTorrentIds: [],
+// // });
+
+// // useEffect(() => {
+// // fetchData();
+// // fetchTorrentList();
+// // }, []);
+
+// // useEffect(() => {
+// // if (promotions.length === 0) return;
+// // clearInterval(promoTimerRef.current);
+// // promoTimerRef.current = setInterval(() => {
+// // setPromoIndex(prev => (prev + 1) % promotions.length);
+// // }, 5000);
+// // return () => clearInterval(promoTimerRef.current);
+// // }, [promotions]);
+
+// // const fetchData = async () => {
+// // try {
+// // const response = await fetch('/seeds/promotions');
+// // const json = await response.json();
+// // const promoData = Array.isArray(json?.data) ? json.data : [];
+// // setPromotions(promoData);
+// // } catch (error) {
+// // console.error('获取促销活动失败:', error);
+// // } finally {
+// // setLoading(false);
+// // }
+// // };
+
+// // const fetchTorrentList = async () => {
+// // try {
+// // const response = await fetch('/seeds/list');
+// // const json = await response.json();
+// // const torrentList = Array.isArray(json?.data) ? json.data : [];
+// // setTorrents(torrentList);
+// // } catch (error) {
+// // console.error('获取种子列表失败:', error);
+// // }
+// // };
+
+// // // 打开模态框时,重置表单数据,默认设置时间并填入所有种子ID
+// // const openCreateModal = () => {
+// // if (torrents.length === 0) {
+// // alert('没有可用的种子,请先上传种子');
+// // return;
+// // }
+// // setFormData({
+// // name: '',
+// // description: '',
+// // discountPercentage: 20,
+// // startTime: new Date().toISOString().slice(0, 16), // 用于datetime-local输入框,格式 YYYY-MM-DDTHH:mm
+// // endTime: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 16),
+// // applicableTorrentIds: torrents.map(t => t.id),
+// // });
+// // setShowCreateModal(true);
+// // };
+
+// // // 表单输入处理
+// // const handleInputChange = (e) => {
+// // const { name, value } = e.target;
+// // setFormData(prev => ({
+// // ...prev,
+// // [name]: name === 'discountPercentage' ? Number(value) : value,
+// // }));
+// // };
+
+// // // 点击确定提交创建
+// // const handleCreateConfirm = async () => {
+// // if (!formData.name) {
+// // alert('促销名称不能为空');
+// // return;
+// // }
+// // if (!formData.startTime || !formData.endTime) {
+// // alert('请选择开始时间和结束时间');
+// // return;
+// // }
+// // if (formData.discountPercentage <= 0 || formData.discountPercentage >= 100) {
+// // alert('折扣百分比应在1-99之间');
+// // return;
+// // }
+// // if (!formData.applicableTorrentIds.length) {
+// // alert('请选择适用的种子');
+// // return;
+// // }
+
+// // // 准备发送数据,适配后端字段名
+// // const newPromo = {
+// // name: formData.name,
+// // description: formData.description,
+// // discountPercentage: formData.discountPercentage,
+// // startTime: new Date(formData.startTime).toISOString(),
+// // endTime: new Date(formData.endTime).toISOString(),
+// // applicableTorrentIds: formData.applicableTorrentIds,
+// // };
+
+// // try {
+// // const res = await fetch('/seeds/promotions', {
+// // method: 'POST',
+// // headers: { 'Content-Type': 'application/json' },
+// // body: JSON.stringify(newPromo),
+// // });
+// // const json = await res.json();
+// // if (json.code === 200) {
+// // alert('促销活动创建成功');
+// // setShowCreateModal(false);
+// // fetchData();
+// // } else {
+// // alert('创建失败: ' + (json.msg || '未知错误'));
+// // }
+// // } catch (err) {
+// // console.error('创建促销失败:', err);
+// // alert('创建促销失败');
+// // }
+// // };
+
+// // const handleCancel = () => {
+// // setShowCreateModal(false);
+// // };
+
+// // const handleDeletePromotion = async (promotionId) => {
+// // if (!window.confirm('确认删除该促销活动吗?')) return;
+
+// // try {
+// // const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
+// // const json = await res.json();
+// // if (json.success) {
+// // alert('删除成功');
+// // fetchData();
+// // } else {
+// // alert('删除失败: ' + json.message);
+// // }
+// // } catch (err) {
+// // console.error('删除失败:', err);
+// // }
+// // };
+
+// // const isAdmin = user?.role === 'admin';
+// // const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
+// // const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
+// // const currentPromo = promotions[promoIndex];
+
+// // if (loading) {
+// // return <div className="promotion-container">加载中...</div>;
+// // }
+
+// // return (
+// // <div className="promotion-container carousel-container">
+// // <section className="carousel-section">
+// // <h2>当前促销活动</h2>
+
+// // {isAdmin && (
+// // <button className="create-btn" onClick={openCreateModal}>
+// // 创建促销活动
+// // </button>
+// // )}
+
+// // {promotions.length === 0 || !currentPromo ? (
+// // <div className="empty-state">暂无促销活动</div>
+// // ) : (
+// // <div
+// // className="carousel"
+// // onMouseEnter={() => clearInterval(promoTimerRef.current)}
+// // onMouseLeave={() => {
+// // promoTimerRef.current = setInterval(() => {
+// // setPromoIndex(prev => (prev + 1) % promotions.length);
+// // }, 3000);
+// // }}
+// // >
+// // <button className="arrow left" onClick={prevPromo}><</button>
+// // <div className="slide">
+// // <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
+// // <div><strong>促销时间:</strong>
+// // {currentPromo?.startTime && currentPromo?.endTime
+// // ? `${new Date(currentPromo.startTime).toLocaleString()} ~ ${new Date(currentPromo.endTime).toLocaleString()}`
+// // : '未知'}
+// // </div>
+// // <div><strong>折扣百分比:</strong>{currentPromo?.discountPercentage ?? '无'}</div>
+// // {currentPromo?.description && (
+// // <div><strong>描述:</strong>{currentPromo.description}</div>
+// // )}
+// // {isAdmin && (
+// // <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
+// // 删除该活动
+// // </button>
+// // )}
+// // </div>
+// // <button className="arrow right" onClick={nextPromo}>></button>
+// // </div>
+// // )}
+// // </section>
+
+// // {/* 创建促销模态框 */}
+// // {showCreateModal && (
+// // <div className="modal-overlay">
+// // <div className="modal-content">
+// // <h3>创建促销活动</h3>
+// // <label>
+// // 促销名称:
+// // <input
+// // type="text"
+// // name="name"
+// // value={formData.name}
+// // onChange={handleInputChange}
+// // />
+// // </label>
+// // <label>
+// // 描述:
+// // <textarea
+// // name="description"
+// // value={formData.description}
+// // onChange={handleInputChange}
+// // rows={3}
+// // />
+// // </label>
+// // <label>
+// // 折扣百分比:
+// // <input
+// // type="number"
+// // name="discountPercentage"
+// // value={formData.discountPercentage}
+// // min={1}
+// // max={99}
+// // onChange={handleInputChange}
+// // />
+// // </label>
+// // <label>
+// // 开始时间:
+// // <input
+// // type="datetime-local"
+// // name="startTime"
+// // value={formData.startTime}
+// // onChange={handleInputChange}
+// // />
+// // </label>
+// // <label>
+// // 结束时间:
+// // <input
+// // type="datetime-local"
+// // name="endTime"
+// // value={formData.endTime}
+// // onChange={handleInputChange}
+// // />
+// // </label>
+// // <label>
+// // 适用种子ID(逗号分隔,可留空默认所有):
+// // <input
+// // type="text"
+// // name="applicableTorrentIds"
+// // value={formData.applicableTorrentIds.join(',')}
+// // onChange={(e) => {
+// // const ids = e.target.value
+// // .split(',')
+// // .map(id => id.trim())
+// // .filter(id => id !== '')
+// // .map(id => Number(id))
+// // .filter(id => !isNaN(id));
+// // setFormData(prev => ({ ...prev, applicableTorrentIds: ids }));
+// // }}
+// // />
+// // </label>
+
+// // <div className="modal-buttons">
+// // <button onClick={handleCreateConfirm}>确定</button>
+// // <button onClick={handleCancel}>取消</button>
+// // </div>
+// // </div>
+// // </div>
+// // )}
+
+// // {/* 模态框简单样式 */}
+// // <style>{`
+// // .modal-overlay {
+// // position: fixed;
+// // top: 0; left: 0; right: 0; bottom: 0;
+// // background: rgba(0,0,0,0.4);
+// // display: flex;
+// // justify-content: center;
+// // align-items: center;
+// // z-index: 999;
+// // }
+// // .modal-content {
+// // background: white;
+// // padding: 20px;
+// // border-radius: 6px;
+// // width: 320px;
+// // max-width: 90%;
+// // }
+// // .modal-content label {
+// // display: block;
+// // margin-bottom: 10px;
+// // font-size: 14px;
+// // }
+// // .modal-content input[type="text"],
+// // .modal-content input[type="number"],
+// // .modal-content input[type="datetime-local"],
+// // .modal-content textarea {
+// // width: 100%;
+// // box-sizing: border-box;
+// // padding: 5px;
+// // font-size: 14px;
+// // margin-top: 4px;
+// // }
+// // .modal-buttons {
+// // margin-top: 15px;
+// // text-align: right;
+// // }
+// // .modal-buttons button {
+// // margin-left: 10px;
+// // padding: 6px 12px;
+// // font-size: 14px;
+// // }
+// // `}</style>
+// // </div>
+// // );
+// // };
+
+// // export default Promotion;
+
+
+// // import React, { useEffect, useState, useRef } from 'react';
+// // import './Promotion.css';
+// // import { useUser } from '../../../context/UserContext';
+
+// // const Promotion = () => {
+// // const { user } = useUser();
+// // const [promotions, setPromotions] = useState([]);
+// // const [torrents, setTorrents] = useState([]); // 新增,存放种子列表
+// // const [loading, setLoading] = useState(true);
+// // const [promoIndex, setPromoIndex] = useState(0);
+// // const promoTimerRef = useRef(null);
+
+// // useEffect(() => {
+// // fetchData();
+// // fetchTorrentList(); // 新增,获取种子列表
+// // }, []);
+
+// // useEffect(() => {
+// // if (promotions.length === 0) return;
+// // clearInterval(promoTimerRef.current);
+// // promoTimerRef.current = setInterval(() => {
+// // setPromoIndex(prev => (prev + 1) % promotions.length);
+// // }, 5000);
+// // return () => clearInterval(promoTimerRef.current);
+// // }, [promotions]);
+
+// // // 获取促销数据
+// // const fetchData = async () => {
+// // try {
+// // const response = await fetch('/seeds/promotions');
+// // const json = await response.json();
+// // const promoData = Array.isArray(json?.data) ? json.data : [];
+// // setPromotions(promoData);
+// // } catch (error) {
+// // console.error('获取促销活动失败:', error);
+// // } finally {
+// // setLoading(false);
+// // }
+// // };
+
+// // // 获取种子列表,赋值给torrents
+// // const fetchTorrentList = async () => {
+// // try {
+// // const response = await fetch('/seeds/list');
+// // const json = await response.json();
+// // const torrentList = Array.isArray(json?.data) ? json.data : [];
+// // setTorrents(torrentList);
+// // } catch (error) {
+// // console.error('获取种子列表失败:', error);
+// // }
+// // };
+
+// // // 创建促销时,自动使用当前种子的id列表,而不是写死
+// // const handleCreatePromotion = async () => {
+// // if (torrents.length === 0) {
+// // alert('没有可用的种子,请先上传种子');
+// // return;
+// // }
+
+// // const applicableTorrentIds = torrents.map(t => t.id); // 获取所有种子id数组
+
+// // const newPromo = {
+// // name: '测试促销活动',
+// // startTime: new Date().toISOString(),
+// // endTime: new Date(Date.now() + 7 * 86400000).toISOString(),
+// // discountPercentage: 20,
+// // applicableTorrentIds: applicableTorrentIds, // 动态传入种子ID数组
+// // description: '这是一个测试促销活动'
+// // };
+
+// // try {
+// // const res = await fetch('/seeds/promotions', {
+// // method: 'POST',
+// // headers: {
+// // 'Content-Type': 'application/json'
+// // },
+// // body: JSON.stringify(newPromo)
+// // });
+// // const json = await res.json();
+// // if (json.code === 200) {
+// // alert('促销活动创建成功');
+// // fetchData();
+// // } else {
+// // alert('创建失败: ' + (json.msg || '未知错误'));
+// // }
+// // } catch (err) {
+// // console.error('创建促销失败:', err);
+// // alert('创建促销失败');
+// // }
+// // };
+
+// // const handleDeletePromotion = async (promotionId) => {
+// // if (!window.confirm('确认删除该促销活动吗?')) return;
+
+// // try {
+// // const res = await fetch(`/seeds/promotions/${promotionId}`, {
+// // method: 'DELETE'
+// // });
+// // const json = await res.json();
+// // if (json.success) {
+// // alert('删除成功');
+// // fetchData();
+// // } else {
+// // alert('删除失败: ' + json.message);
+// // }
+// // } catch (err) {
+// // console.error('删除失败:', err);
+// // }
+// // };
+
+// // const isAdmin = user?.role === 'admin';
+// // const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
+// // const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
+// // const currentPromo = promotions[promoIndex];
+
+// // if (loading) {
+// // return <div className="promotion-container">加载中...</div>;
+// // }
+
+// // return (
+// // <div className="promotion-container carousel-container">
+// // <section className="carousel-section">
+// // <h2>当前促销活动</h2>
+
+// // {isAdmin && (
+// // <button className="create-btn" onClick={handleCreatePromotion}>
+// // 创建促销活动
+// // </button>
+// // )}
+
+// // {promotions.length === 0 || !currentPromo ? (
+// // <div className="empty-state">暂无促销活动</div>
+// // ) : (
+// // <div
+// // className="carousel"
+// // onMouseEnter={() => clearInterval(promoTimerRef.current)}
+// // onMouseLeave={() => {
+// // promoTimerRef.current = setInterval(() => {
+// // setPromoIndex(prev => (prev + 1) % promotions.length);
+// // }, 3000);
+// // }}
+// // >
+// // <button className="arrow left" onClick={prevPromo}><</button>
+// // <div className="slide">
+// // <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
+// // <div><strong>促销时间:</strong>
+// // {currentPromo?.pStartTime && currentPromo?.pEndTime
+// // ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
+// // : '未知'}
+// // </div>
+// // <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
+// // <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
+// // {currentPromo?.description && (
+// // <div><strong>描述:</strong>{currentPromo.description}</div>
+// // )}
+// // {isAdmin && (
+// // <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
+// // 删除该活动
+// // </button>
+// // )}
+// // </div>
+// // <button className="arrow right" onClick={nextPromo}>></button>
+// // </div>
+// // )}
+// // </section>
+// // </div>
+// // );
+// // };
+
+// // export default Promotion;
+
+
import React, { useEffect, useState, useRef } from 'react';
import './Promotion.css';
import { useUser } from '../../../context/UserContext';
@@ -123,7 +949,7 @@
discountPercentage: Number(formData.discountPercentage),
uploadCoeff: formData.uploadCoeff ? Number(formData.uploadCoeff) : undefined,
downloadCoeff: formData.downloadCoeff ? Number(formData.downloadCoeff) : undefined,
- applicableTorrentIds: JSON.stringify(applicableTorrentIds), // ✅ 关键修改
+ applicableTorrentIds: applicableTorrentIds,
description: formData.description
};
@@ -135,7 +961,7 @@
body: JSON.stringify(newPromo)
});
const json = await res.json();
- if (json.code === 200) {
+ if (json.code === 200 || json.code === 0) {
alert('促销活动创建成功');
fetchData();
setShowCreateDialog(false);
@@ -154,11 +980,11 @@
try {
const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
const json = await res.json();
- if (json.success) {
+ if (json.code === 0 || json.code === 200) {
alert('删除成功');
fetchData();
} else {
- alert('删除失败: ' + json.message);
+ alert('删除失败: ' + (json.msg || '未知错误'));
}
} catch (err) {
console.error('删除失败:', err);
@@ -310,516 +1136,3 @@
};
export default Promotion;
-
-
-// import React, { useEffect, useState, useRef } from 'react';
-// import './Promotion.css';
-// import { useUser } from '../../../context/UserContext';
-
-// const Promotion = () => {
-// const { user } = useUser();
-// const [promotions, setPromotions] = useState([]);
-// const [torrents, setTorrents] = useState([]);
-// const [loading, setLoading] = useState(true);
-// const [promoIndex, setPromoIndex] = useState(0);
-// const promoTimerRef = useRef(null);
-
-// // 新增:控制模态框显示与表单状态
-// const [showCreateModal, setShowCreateModal] = useState(false);
-// const [formData, setFormData] = useState({
-// name: '',
-// description: '',
-// discountPercentage: 0,
-// startTime: '',
-// endTime: '',
-// applicableTorrentIds: [],
-// });
-
-// useEffect(() => {
-// fetchData();
-// fetchTorrentList();
-// }, []);
-
-// useEffect(() => {
-// if (promotions.length === 0) return;
-// clearInterval(promoTimerRef.current);
-// promoTimerRef.current = setInterval(() => {
-// setPromoIndex(prev => (prev + 1) % promotions.length);
-// }, 5000);
-// return () => clearInterval(promoTimerRef.current);
-// }, [promotions]);
-
-// const fetchData = async () => {
-// try {
-// const response = await fetch('/seeds/promotions');
-// const json = await response.json();
-// const promoData = Array.isArray(json?.data) ? json.data : [];
-// setPromotions(promoData);
-// } catch (error) {
-// console.error('获取促销活动失败:', error);
-// } finally {
-// setLoading(false);
-// }
-// };
-
-// const fetchTorrentList = async () => {
-// try {
-// const response = await fetch('/seeds/list');
-// const json = await response.json();
-// const torrentList = Array.isArray(json?.data) ? json.data : [];
-// setTorrents(torrentList);
-// } catch (error) {
-// console.error('获取种子列表失败:', error);
-// }
-// };
-
-// // 打开模态框时,重置表单数据,默认设置时间并填入所有种子ID
-// const openCreateModal = () => {
-// if (torrents.length === 0) {
-// alert('没有可用的种子,请先上传种子');
-// return;
-// }
-// setFormData({
-// name: '',
-// description: '',
-// discountPercentage: 20,
-// startTime: new Date().toISOString().slice(0, 16), // 用于datetime-local输入框,格式 YYYY-MM-DDTHH:mm
-// endTime: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 16),
-// applicableTorrentIds: torrents.map(t => t.id),
-// });
-// setShowCreateModal(true);
-// };
-
-// // 表单输入处理
-// const handleInputChange = (e) => {
-// const { name, value } = e.target;
-// setFormData(prev => ({
-// ...prev,
-// [name]: name === 'discountPercentage' ? Number(value) : value,
-// }));
-// };
-
-// // 点击确定提交创建
-// const handleCreateConfirm = async () => {
-// if (!formData.name) {
-// alert('促销名称不能为空');
-// return;
-// }
-// if (!formData.startTime || !formData.endTime) {
-// alert('请选择开始时间和结束时间');
-// return;
-// }
-// if (formData.discountPercentage <= 0 || formData.discountPercentage >= 100) {
-// alert('折扣百分比应在1-99之间');
-// return;
-// }
-// if (!formData.applicableTorrentIds.length) {
-// alert('请选择适用的种子');
-// return;
-// }
-
-// // 准备发送数据,适配后端字段名
-// const newPromo = {
-// name: formData.name,
-// description: formData.description,
-// discountPercentage: formData.discountPercentage,
-// startTime: new Date(formData.startTime).toISOString(),
-// endTime: new Date(formData.endTime).toISOString(),
-// applicableTorrentIds: formData.applicableTorrentIds,
-// };
-
-// try {
-// const res = await fetch('/seeds/promotions', {
-// method: 'POST',
-// headers: { 'Content-Type': 'application/json' },
-// body: JSON.stringify(newPromo),
-// });
-// const json = await res.json();
-// if (json.code === 200) {
-// alert('促销活动创建成功');
-// setShowCreateModal(false);
-// fetchData();
-// } else {
-// alert('创建失败: ' + (json.msg || '未知错误'));
-// }
-// } catch (err) {
-// console.error('创建促销失败:', err);
-// alert('创建促销失败');
-// }
-// };
-
-// const handleCancel = () => {
-// setShowCreateModal(false);
-// };
-
-// const handleDeletePromotion = async (promotionId) => {
-// if (!window.confirm('确认删除该促销活动吗?')) return;
-
-// try {
-// const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
-// const json = await res.json();
-// if (json.success) {
-// alert('删除成功');
-// fetchData();
-// } else {
-// alert('删除失败: ' + json.message);
-// }
-// } catch (err) {
-// console.error('删除失败:', err);
-// }
-// };
-
-// const isAdmin = user?.role === 'admin';
-// const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
-// const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
-// const currentPromo = promotions[promoIndex];
-
-// if (loading) {
-// return <div className="promotion-container">加载中...</div>;
-// }
-
-// return (
-// <div className="promotion-container carousel-container">
-// <section className="carousel-section">
-// <h2>当前促销活动</h2>
-
-// {isAdmin && (
-// <button className="create-btn" onClick={openCreateModal}>
-// 创建促销活动
-// </button>
-// )}
-
-// {promotions.length === 0 || !currentPromo ? (
-// <div className="empty-state">暂无促销活动</div>
-// ) : (
-// <div
-// className="carousel"
-// onMouseEnter={() => clearInterval(promoTimerRef.current)}
-// onMouseLeave={() => {
-// promoTimerRef.current = setInterval(() => {
-// setPromoIndex(prev => (prev + 1) % promotions.length);
-// }, 3000);
-// }}
-// >
-// <button className="arrow left" onClick={prevPromo}><</button>
-// <div className="slide">
-// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
-// <div><strong>促销时间:</strong>
-// {currentPromo?.startTime && currentPromo?.endTime
-// ? `${new Date(currentPromo.startTime).toLocaleString()} ~ ${new Date(currentPromo.endTime).toLocaleString()}`
-// : '未知'}
-// </div>
-// <div><strong>折扣百分比:</strong>{currentPromo?.discountPercentage ?? '无'}</div>
-// {currentPromo?.description && (
-// <div><strong>描述:</strong>{currentPromo.description}</div>
-// )}
-// {isAdmin && (
-// <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
-// 删除该活动
-// </button>
-// )}
-// </div>
-// <button className="arrow right" onClick={nextPromo}>></button>
-// </div>
-// )}
-// </section>
-
-// {/* 创建促销模态框 */}
-// {showCreateModal && (
-// <div className="modal-overlay">
-// <div className="modal-content">
-// <h3>创建促销活动</h3>
-// <label>
-// 促销名称:
-// <input
-// type="text"
-// name="name"
-// value={formData.name}
-// onChange={handleInputChange}
-// />
-// </label>
-// <label>
-// 描述:
-// <textarea
-// name="description"
-// value={formData.description}
-// onChange={handleInputChange}
-// rows={3}
-// />
-// </label>
-// <label>
-// 折扣百分比:
-// <input
-// type="number"
-// name="discountPercentage"
-// value={formData.discountPercentage}
-// min={1}
-// max={99}
-// onChange={handleInputChange}
-// />
-// </label>
-// <label>
-// 开始时间:
-// <input
-// type="datetime-local"
-// name="startTime"
-// value={formData.startTime}
-// onChange={handleInputChange}
-// />
-// </label>
-// <label>
-// 结束时间:
-// <input
-// type="datetime-local"
-// name="endTime"
-// value={formData.endTime}
-// onChange={handleInputChange}
-// />
-// </label>
-// <label>
-// 适用种子ID(逗号分隔,可留空默认所有):
-// <input
-// type="text"
-// name="applicableTorrentIds"
-// value={formData.applicableTorrentIds.join(',')}
-// onChange={(e) => {
-// const ids = e.target.value
-// .split(',')
-// .map(id => id.trim())
-// .filter(id => id !== '')
-// .map(id => Number(id))
-// .filter(id => !isNaN(id));
-// setFormData(prev => ({ ...prev, applicableTorrentIds: ids }));
-// }}
-// />
-// </label>
-
-// <div className="modal-buttons">
-// <button onClick={handleCreateConfirm}>确定</button>
-// <button onClick={handleCancel}>取消</button>
-// </div>
-// </div>
-// </div>
-// )}
-
-// {/* 模态框简单样式 */}
-// <style>{`
-// .modal-overlay {
-// position: fixed;
-// top: 0; left: 0; right: 0; bottom: 0;
-// background: rgba(0,0,0,0.4);
-// display: flex;
-// justify-content: center;
-// align-items: center;
-// z-index: 999;
-// }
-// .modal-content {
-// background: white;
-// padding: 20px;
-// border-radius: 6px;
-// width: 320px;
-// max-width: 90%;
-// }
-// .modal-content label {
-// display: block;
-// margin-bottom: 10px;
-// font-size: 14px;
-// }
-// .modal-content input[type="text"],
-// .modal-content input[type="number"],
-// .modal-content input[type="datetime-local"],
-// .modal-content textarea {
-// width: 100%;
-// box-sizing: border-box;
-// padding: 5px;
-// font-size: 14px;
-// margin-top: 4px;
-// }
-// .modal-buttons {
-// margin-top: 15px;
-// text-align: right;
-// }
-// .modal-buttons button {
-// margin-left: 10px;
-// padding: 6px 12px;
-// font-size: 14px;
-// }
-// `}</style>
-// </div>
-// );
-// };
-
-// export default Promotion;
-
-
-// import React, { useEffect, useState, useRef } from 'react';
-// import './Promotion.css';
-// import { useUser } from '../../../context/UserContext';
-
-// const Promotion = () => {
-// const { user } = useUser();
-// const [promotions, setPromotions] = useState([]);
-// const [torrents, setTorrents] = useState([]); // 新增,存放种子列表
-// const [loading, setLoading] = useState(true);
-// const [promoIndex, setPromoIndex] = useState(0);
-// const promoTimerRef = useRef(null);
-
-// useEffect(() => {
-// fetchData();
-// fetchTorrentList(); // 新增,获取种子列表
-// }, []);
-
-// useEffect(() => {
-// if (promotions.length === 0) return;
-// clearInterval(promoTimerRef.current);
-// promoTimerRef.current = setInterval(() => {
-// setPromoIndex(prev => (prev + 1) % promotions.length);
-// }, 5000);
-// return () => clearInterval(promoTimerRef.current);
-// }, [promotions]);
-
-// // 获取促销数据
-// const fetchData = async () => {
-// try {
-// const response = await fetch('/seeds/promotions');
-// const json = await response.json();
-// const promoData = Array.isArray(json?.data) ? json.data : [];
-// setPromotions(promoData);
-// } catch (error) {
-// console.error('获取促销活动失败:', error);
-// } finally {
-// setLoading(false);
-// }
-// };
-
-// // 获取种子列表,赋值给torrents
-// const fetchTorrentList = async () => {
-// try {
-// const response = await fetch('/seeds/list');
-// const json = await response.json();
-// const torrentList = Array.isArray(json?.data) ? json.data : [];
-// setTorrents(torrentList);
-// } catch (error) {
-// console.error('获取种子列表失败:', error);
-// }
-// };
-
-// // 创建促销时,自动使用当前种子的id列表,而不是写死
-// const handleCreatePromotion = async () => {
-// if (torrents.length === 0) {
-// alert('没有可用的种子,请先上传种子');
-// return;
-// }
-
-// const applicableTorrentIds = torrents.map(t => t.id); // 获取所有种子id数组
-
-// const newPromo = {
-// name: '测试促销活动',
-// startTime: new Date().toISOString(),
-// endTime: new Date(Date.now() + 7 * 86400000).toISOString(),
-// discountPercentage: 20,
-// applicableTorrentIds: applicableTorrentIds, // 动态传入种子ID数组
-// description: '这是一个测试促销活动'
-// };
-
-// try {
-// const res = await fetch('/seeds/promotions', {
-// method: 'POST',
-// headers: {
-// 'Content-Type': 'application/json'
-// },
-// body: JSON.stringify(newPromo)
-// });
-// const json = await res.json();
-// if (json.code === 200) {
-// alert('促销活动创建成功');
-// fetchData();
-// } else {
-// alert('创建失败: ' + (json.msg || '未知错误'));
-// }
-// } catch (err) {
-// console.error('创建促销失败:', err);
-// alert('创建促销失败');
-// }
-// };
-
-// const handleDeletePromotion = async (promotionId) => {
-// if (!window.confirm('确认删除该促销活动吗?')) return;
-
-// try {
-// const res = await fetch(`/seeds/promotions/${promotionId}`, {
-// method: 'DELETE'
-// });
-// const json = await res.json();
-// if (json.success) {
-// alert('删除成功');
-// fetchData();
-// } else {
-// alert('删除失败: ' + json.message);
-// }
-// } catch (err) {
-// console.error('删除失败:', err);
-// }
-// };
-
-// const isAdmin = user?.role === 'admin';
-// const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
-// const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
-// const currentPromo = promotions[promoIndex];
-
-// if (loading) {
-// return <div className="promotion-container">加载中...</div>;
-// }
-
-// return (
-// <div className="promotion-container carousel-container">
-// <section className="carousel-section">
-// <h2>当前促销活动</h2>
-
-// {isAdmin && (
-// <button className="create-btn" onClick={handleCreatePromotion}>
-// 创建促销活动
-// </button>
-// )}
-
-// {promotions.length === 0 || !currentPromo ? (
-// <div className="empty-state">暂无促销活动</div>
-// ) : (
-// <div
-// className="carousel"
-// onMouseEnter={() => clearInterval(promoTimerRef.current)}
-// onMouseLeave={() => {
-// promoTimerRef.current = setInterval(() => {
-// setPromoIndex(prev => (prev + 1) % promotions.length);
-// }, 3000);
-// }}
-// >
-// <button className="arrow left" onClick={prevPromo}><</button>
-// <div className="slide">
-// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
-// <div><strong>促销时间:</strong>
-// {currentPromo?.pStartTime && currentPromo?.pEndTime
-// ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
-// : '未知'}
-// </div>
-// <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
-// <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
-// {currentPromo?.description && (
-// <div><strong>描述:</strong>{currentPromo.description}</div>
-// )}
-// {isAdmin && (
-// <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
-// 删除该活动
-// </button>
-// )}
-// </div>
-// <button className="arrow right" onClick={nextPromo}>></button>
-// </div>
-// )}
-// </section>
-// </div>
-// );
-// };
-
-// export default Promotion;
-
diff --git a/src/pages/InterestGroup/GroupFilters.jsx b/src/pages/InterestGroup/GroupFilters.jsx
index c93b495..c6c3a52 100644
--- a/src/pages/InterestGroup/GroupFilters.jsx
+++ b/src/pages/InterestGroup/GroupFilters.jsx
@@ -1,58 +1,58 @@
-import React from 'react';
-import { useGroupStore } from '../../context/useGroupStore';
+// import React from 'react';
+// import { useGroupStore } from '../../context/useGroupStore';
-const GroupFilters = () => {
- const {
- category, setCategory,
- name, setName,
- sortBy, setSortBy,
- handleSearch
- } = 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);
- };
+// 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>
+// 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="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>
- );
-};
+// <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
+// export default GroupFilters;
\ No newline at end of file
diff --git a/src/pages/InterestGroup/GroupItem.jsx b/src/pages/InterestGroup/GroupItem.jsx
index 736f88d..ea2253f 100644
--- a/src/pages/InterestGroup/GroupItem.jsx
+++ b/src/pages/InterestGroup/GroupItem.jsx
@@ -1,97 +1,3 @@
-// import React, { useState, useEffect } from 'react';
-// import { useGroupStore } from '../../context/useGroupStore';
-// import { useUser } from '../../context/UserContext';
-// import CreatePostForm from './CreatePostForm';
-// import axios from 'axios'; // 新增
-
-// const GroupItem = ({ group }) => {
-// const { handleJoinGroup, joinStatus, setJoinStatus } = useGroupStore(); // 假设你有 setJoinStatus 方法
-// const { user } = useUser();
-
-// const userId = user?.userId;
-// const groupId = group.groupId;
-
-// const [isMember, setIsMember] = useState(false);
-
-// useEffect(() => {
-// setIsMember(joinStatus[groupId] === '加入成功');
-// }, [joinStatus, groupId]);
-
-// const [showCreatePost, setShowCreatePost] = useState(false);
-
-// // 退出小组函数(新增)
-// const handleLeaveGroup = async () => {
-// try {
-// const res = await axios.post(`/echo/groups/${groupId}/leave`, {
-// user_id: userId,
-// });
-// if (res.data.status === 'success') {
-// setJoinStatus(groupId, '未加入'); // 更新全局状态(需确保 useGroupStore 中有此方法)
-// setIsMember(false); // 本地状态也更新
-// } else {
-// alert(res.data.message || '退出失败');
-// }
-// } catch (error) {
-// console.error('退出小组失败:', error);
-// alert('退出小组失败');
-// }
-// };
-
-// return (
-// <div className="group-item">
-// <div className="group-content">
-// <img
-// style={{ width: '40%', height: '40%' }}
-// src={group.coverImage || 'https://picsum.photos/200/200'}
-// alt={group.groupName}
-// className="group-cover"
-// />
-// <div className="group-info-right">
-// <h3>{group.groupName}</h3>
-// <p style={{ color: '#BA929A' }}>{group.memberCount || 0}人加入了小组</p>
-
-// {/* 加入/退出按钮逻辑 */}
-// {userId && (
-// <button
-// onClick={() => {
-// if (isMember) {
-// handleLeaveGroup(); // 已加入 -> 退出
-// } else {
-// handleJoinGroup(groupId, userId); // 未加入 -> 加入
-// }
-// }}
-// >
-// {isMember ? '退出小组' : '+加入小组'}
-// </button>
-// )}
-// {!userId && <button disabled>请登录</button>}
-
-// {/* 发布帖子按钮 */}
-// {userId && isMember && (
-// <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, useEffect } from 'react';
import { useGroupStore } from '../../context/useGroupStore';
import { useUser } from '../../context/UserContext';
@@ -99,7 +5,7 @@
import axios from 'axios';
const GroupItem = ({ group }) => {
- const { handleJoinGroup, joinStatus, setJoinStatus } = useGroupStore();
+ const { handleJoinGroup, joinStatus, setJoinStatus,fetchGroupList } = useGroupStore();
const { user } = useUser();
const userId = user?.userId;
@@ -126,7 +32,8 @@
try {
const res = await axios.get(`/echo/groups/${groupId}/members`);
const isMember = res.data.members.some(member => member.user_id === userId);
- setJoinStatus(groupId, isMember ? '加入成功' : '未加入');
+ setIsMember(isMember);
+ // setJoinStatus(groupId, isMember ? '加入成功' : '未加入');
} catch (error) {
console.error('检查成员状态失败:', error);
}
@@ -141,7 +48,8 @@
user_id: userId,
});
if (res.data.status === 'success') {
- setJoinStatus(groupId, '未加入');
+ fetchGroupList(); // 刷新小组列表
+ // setJoinStatus(groupId, '未加入');
setIsMember(false);
// 可选:刷新小组成员计数
group.memberCount = (group.memberCount || 0) - 1;
diff --git a/src/pages/InterestGroup/InterestGroup.css b/src/pages/InterestGroup/InterestGroup.css
index 91b21da..f819038 100644
--- a/src/pages/InterestGroup/InterestGroup.css
+++ b/src/pages/InterestGroup/InterestGroup.css
@@ -204,4 +204,82 @@
margin-top: 40px;
/* padding: 24px 32px; */
padding: 3% 3%
-}
\ No newline at end of file
+}
+
+.create-group-btn {
+ background-color: #f2d0c9; /* 浅粉色 */
+ color: #4e342e; /* 深棕色 */
+ border: none;
+ padding: 10px 20px;
+ margin: 20px 0;
+ border-radius: 8px;
+ font-size: 16px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.create-group-btn:hover {
+ background-color: #e4b5ae;
+}
+
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(50, 30, 20, 0.5); /* 米棕半透明 */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.modal-content {
+ background-color: #fffaf5; /* 淡米色 */
+ padding: 30px;
+ border-radius: 12px;
+ width: 400px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
+}
+
+.modal-content h2 {
+ margin-bottom: 15px;
+ color: #4e342e;
+}
+
+.modal-content input,
+.modal-content textarea {
+ width: 100%;
+ padding: 10px;
+ margin: 8px 0;
+ border: 1px solid #d3c0b0;
+ border-radius: 6px;
+ font-size: 14px;
+}
+
+.modal-buttons {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 15px;
+}
+
+.modal-buttons button {
+ padding: 8px 16px;
+ border: none;
+ border-radius: 6px;
+ font-weight: bold;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.modal-buttons button:first-child {
+ background-color: #d7a29e; /* 粉棕 */
+ color: white;
+}
+
+.modal-buttons button:last-child {
+ background-color: #c5b8af; /* 米色灰棕 */
+ color: white;
+}
diff --git a/src/pages/InterestGroup/InterestGroup.jsx b/src/pages/InterestGroup/InterestGroup.jsx
index b89cf57..0b5b002 100644
--- a/src/pages/InterestGroup/InterestGroup.jsx
+++ b/src/pages/InterestGroup/InterestGroup.jsx
@@ -1,23 +1,217 @@
-import React, { useEffect } from 'react';
+// // 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';
+// // const InterestGroup = () => {
+// // const { fetchGroupList, setPage, handleSearch } = useGroupStore();
+
+// // // 初始化加载
+// // useEffect(() => {
+// // fetchGroupList();
+// // }, [fetchGroupList]);
+
+// // return (
+// // <div className="interest-group-container">
+// // <Header />
+// // <div className="interest-group-card">
+// // {/* <GroupFilters /> */}
+// // <GroupList />
+// // <GroupPagination />
+// // </div>
+// // </div>
+// // );
+// // };
+
+// // export default InterestGroup;
+
+// import React, { useEffect, useState } from 'react';
+// import Header from '../../components/Header';
+// import { useGroupStore } from '../../context/useGroupStore';
+// import GroupList from './GroupList';
+// import GroupPagination from './GroupPagination';
+// import './InterestGroup.css';
+// import axios from 'axios';
+
+// const InterestGroup = () => {
+// const { fetchGroupList } = useGroupStore();
+
+// const [showModal, setShowModal] = useState(false);
+// const [groupName, setGroupName] = useState('');
+// const [groupDescription, setGroupDescription] = useState('');
+
+// useEffect(() => {
+// fetchGroupList();
+// }, [fetchGroupList]);
+
+// const handleCreateGroup = async () => {
+// try {
+// const res = await axios.post('http://localhost:3011/echo/groups/createGroup', {
+// user_id: 1,
+// group_name: groupName, // ✅ 改为 snake_case
+// description: groupDescription,
+// time: new Date().toISOString(),
+// category: '默认分类',
+// cover_image: 'https://picsum.photos/300/200',
+// });
+
+// if (res.status === 200 && res.data.status === 'success') {
+// alert('小组创建成功');
+// setShowModal(false);
+// setGroupName('');
+// setGroupDescription('');
+// fetchGroupList();
+// } else {
+// alert('创建失败: ' + res.data.message);
+// }
+// } catch (error) {
+// alert('创建失败,请检查网络或输入');
+// console.error(error);
+// }
+// };
+
+
+// // const handleCreateGroup = async () => {
+// // try {
+// // const res = await axios.post('/createGroup', {
+// // groupName,
+// // description: groupDescription,
+// // });
+// // if (res.status === 200) {
+// // alert('小组创建成功');
+// // setShowModal(false);
+// // setGroupName('');
+// // setGroupDescription('');
+// // fetchGroupList(); // 刷新列表
+// // }
+// // } catch (error) {
+// // alert('创建失败,请重试');
+// // }
+// // };
+
+// return (
+// <div className="interest-group-container">
+// <Header />
+// <div className="interest-group-card">
+// <button className="create-group-btn" onClick={() => setShowModal(true)}>
+// 创建小组
+// </button>
+
+// {showModal && (
+// <div className="modal-overlay">
+// <div className="modal-content">
+// <h2>创建新小组</h2>
+// <input
+// type="text"
+// placeholder="小组名称"
+// value={groupName}
+// onChange={(e) => setGroupName(e.target.value)}
+// />
+// <textarea
+// placeholder="小组简介"
+// value={groupDescription}
+// onChange={(e) => setGroupDescription(e.target.value)}
+// />
+// <div className="modal-buttons">
+// <button onClick={handleCreateGroup}>确定</button>
+// <button onClick={() => setShowModal(false)}>取消</button>
+// </div>
+// </div>
+// </div>
+// )}
+
+// <GroupList />
+// <GroupPagination />
+// </div>
+// </div>
+// );
+// };
+
+// export default InterestGroup;
+
+
+import React, { useEffect, useState } 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';
-const InterestGroup = () => {
- const { fetchGroupList, setPage, handleSearch } = useGroupStore();
+import axios from 'axios';
- // 初始化加载
+const InterestGroup = () => {
+ const { fetchGroupList } = useGroupStore();
+
+ const [showModal, setShowModal] = useState(false);
+ const [groupName, setGroupName] = useState('');
+ const [groupDescription, setGroupDescription] = useState('');
+
useEffect(() => {
fetchGroupList();
}, [fetchGroupList]);
+ const handleCreateGroup = async () => {
+ try {
+ // ✅ 修改请求体字段名,使用驼峰命名法
+ const res = await axios.post('http://localhost:3011/echo/groups/createGroup', {
+ userId: 1, // 改为驼峰命名 userId
+ groupName: groupName, // 改为驼峰命名 groupName
+ description: groupDescription,
+ // 移除time字段,使用后端生成的时间
+ memberCount: 1, // 添加初始成员数
+ category: '默认分类',
+ coverImage: 'https://picsum.photos/300/200', // 改为驼峰命名 coverImage
+ });
+
+ if (res.status === 200 && res.data.status === 'success') {
+ alert('小组创建成功');
+ setShowModal(false);
+ setGroupName('');
+ setGroupDescription('');
+ fetchGroupList(); // 刷新列表
+ } else {
+ // 显示后端返回的详细错误信息
+ alert('创建失败: ' + res.data.message);
+ }
+ } catch (error) {
+ // 处理网络错误或其他异常
+ alert('创建失败,请检查网络连接或输入内容');
+ console.error('创建小组错误:', error);
+ }
+ };
+
return (
<div className="interest-group-container">
<Header />
<div className="interest-group-card">
- <GroupFilters />
+ <button className="create-group-btn" onClick={() => setShowModal(true)}>
+ 创建小组
+ </button>
+
+ {showModal && (
+ <div className="modal-overlay">
+ <div className="modal-content">
+ <h2>创建新小组</h2>
+ <input
+ type="text"
+ placeholder="小组名称"
+ value={groupName}
+ onChange={(e) => setGroupName(e.target.value)}
+ />
+ <textarea
+ placeholder="小组简介"
+ value={groupDescription}
+ onChange={(e) => setGroupDescription(e.target.value)}
+ />
+ <div className="modal-buttons">
+ <button onClick={handleCreateGroup}>确定</button>
+ <button onClick={() => setShowModal(false)}>取消</button>
+ </div>
+ </div>
+ </div>
+ )}
+
<GroupList />
<GroupPagination />
</div>
diff --git a/src/pages/LevelPage/LevelPage.jsx b/src/pages/LevelPage/LevelPage.jsx
index 55ce6b6..a7b8ea3 100644
--- a/src/pages/LevelPage/LevelPage.jsx
+++ b/src/pages/LevelPage/LevelPage.jsx
@@ -1,100 +1,100 @@
-import React, { useEffect, useState } from 'react';
-import { getExperience, updateExperience, checkUpgrade, upgradeUserLevel } from '../../api/level';
-import LevelCard from '../../components/LevelCard';
+// import React, { useEffect, useState } from 'react';
+// import { getExperience, updateExperience, checkUpgrade, upgradeUserLevel } from '../../api/level';
+// import LevelCard from '../../components/LevelCard';
-const user_id = 1; // 实际项目中请从用户上下文获取
+// const user_id = 1; // 实际项目中请从用户上下文获取
-const LevelPage = () => {
- const [levelInfo, setLevelInfo] = useState(null);
- const [upgradeStatus, setUpgradeStatus] = useState(null);
- const [message, setMessage] = useState('');
+// const LevelPage = () => {
+// const [levelInfo, setLevelInfo] = useState(null);
+// const [upgradeStatus, setUpgradeStatus] = useState(null);
+// const [message, setMessage] = useState('');
- const fetchExperience = async () => {
- try {
- const res = await getExperience(user_id);
- setLevelInfo(res.data);
- } catch (error) {
- console.error(error);
- }
- };
+// const fetchExperience = async () => {
+// try {
+// const res = await getExperience(user_id);
+// setLevelInfo(res.data);
+// } catch (error) {
+// console.error(error);
+// }
+// };
- const handleUpdate = async () => {
- try {
- const res = await updateExperience({ user_id, experience: 50, source: '签到' });
- setMessage(res.data.message || '经验更新成功');
- fetchExperience();
- } catch (error) {
- console.error(error);
- }
- };
+// const handleUpdate = async () => {
+// try {
+// const res = await updateExperience({ user_id, experience: 50, source: '签到' });
+// setMessage(res.data.message || '经验更新成功');
+// fetchExperience();
+// } catch (error) {
+// console.error(error);
+// }
+// };
- const handleCheckUpgrade = async () => {
- try {
- const res = await checkUpgrade(user_id);
- setUpgradeStatus(res.data);
- } catch (error) {
- console.error(error);
- }
- };
+// const handleCheckUpgrade = async () => {
+// try {
+// const res = await checkUpgrade(user_id);
+// setUpgradeStatus(res.data);
+// } catch (error) {
+// console.error(error);
+// }
+// };
- const handleUpgrade = async () => {
- try {
- const res = await upgradeUserLevel({ user_id, can_upgrade: true });
- setMessage(res.data.message || '升级成功');
- fetchExperience();
- } catch (error) {
- setMessage(error.response?.data?.message || '升级失败');
- }
- };
+// const handleUpgrade = async () => {
+// try {
+// const res = await upgradeUserLevel({ user_id, can_upgrade: true });
+// setMessage(res.data.message || '升级成功');
+// fetchExperience();
+// } catch (error) {
+// setMessage(error.response?.data?.message || '升级失败');
+// }
+// };
- useEffect(() => {
- fetchExperience();
- }, []);
+// useEffect(() => {
+// fetchExperience();
+// }, []);
- return (
- <div className="max-w-xl mx-auto p-4">
- <h1 className="text-2xl font-bold mb-4">我的等级</h1>
- {levelInfo && (
- <LevelCard
- level={levelInfo.level}
- current={levelInfo.current_experience}
- next={levelInfo.next_level_experience}
- />
- )}
+// return (
+// <div className="max-w-xl mx-auto p-4">
+// <h1 className="text-2xl font-bold mb-4">我的等级</h1>
+// {levelInfo && (
+// <LevelCard
+// level={levelInfo.level}
+// current={levelInfo.current_experience}
+// next={levelInfo.next_level_experience}
+// />
+// )}
- <div className="space-x-2 mb-4">
- <button
- onClick={handleUpdate}
- className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
- >
- 模拟签到加经验
- </button>
- <button
- onClick={handleCheckUpgrade}
- className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700"
- >
- 检查是否可升级
- </button>
- </div>
+// <div className="space-x-2 mb-4">
+// <button
+// onClick={handleUpdate}
+// className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
+// >
+// 模拟签到加经验
+// </button>
+// <button
+// onClick={handleCheckUpgrade}
+// className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700"
+// >
+// 检查是否可升级
+// </button>
+// </div>
- {upgradeStatus && (
- <div className="mb-4">
- {upgradeStatus.can_upgrade ? (
- <button
- onClick={handleUpgrade}
- className="px-4 py-2 bg-purple-600 text-white rounded hover:bg-purple-700"
- >
- 升级到下一级
- </button>
- ) : (
- <p className="text-gray-700">当前还不能升级。</p>
- )}
- </div>
- )}
+// {upgradeStatus && (
+// <div className="mb-4">
+// {upgradeStatus.can_upgrade ? (
+// <button
+// onClick={handleUpgrade}
+// className="px-4 py-2 bg-purple-600 text-white rounded hover:bg-purple-700"
+// >
+// 升级到下一级
+// </button>
+// ) : (
+// <p className="text-gray-700">当前还不能升级。</p>
+// )}
+// </div>
+// )}
- {message && <div className="text-sm text-green-700 mt-2">{message}</div>}
- </div>
- );
-};
+// {message && <div className="text-sm text-green-700 mt-2">{message}</div>}
+// </div>
+// );
+// };
-export default LevelPage;
+// export default LevelPage;
diff --git a/src/pages/PromotionsPage/PromotionsPage.css b/src/pages/PromotionsPage/PromotionsPage.css
index 6752c69..bc5bf8c 100644
--- a/src/pages/PromotionsPage/PromotionsPage.css
+++ b/src/pages/PromotionsPage/PromotionsPage.css
@@ -1,4 +1,4 @@
-.promotions-page {
+/* .promotions-page {
padding: 20px;
font-family: Arial, sans-serif;
}
@@ -203,4 +203,4 @@
.pagination span {
font-size: 14px;
-}
\ No newline at end of file
+} */
\ No newline at end of file
diff --git a/src/pages/UserCenter/UserCollect.css b/src/pages/UserCenter/UserCollect.css
index fe7b3bd..79caaf6 100644
--- a/src/pages/UserCenter/UserCollect.css
+++ b/src/pages/UserCenter/UserCollect.css
@@ -1,11 +1,11 @@
-.user-center {
+/* .user-center {
max-width: 100%;
padding: 3%;
font-family: Arial, sans-serif;
display: flex;
gap: 10%;
background: #333;
-}
+} */
.post-item {
margin-bottom: 20px;
diff --git a/src/pages/UserCenter/UserDynamics.css b/src/pages/UserCenter/UserDynamics.css
index 40a11b4..5380c57 100644
--- a/src/pages/UserCenter/UserDynamics.css
+++ b/src/pages/UserCenter/UserDynamics.css
@@ -17,12 +17,13 @@
}
.dynamic-card {
- background: #fff;
- border: 1px solid #eee;
+ background: #E4D8C9;
+ /* border: 1px solid #eee; */
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
+ width: 100%;
}
.dynamic-header {
diff --git a/src/pages/UserCenter/UserDynamics.jsx b/src/pages/UserCenter/UserDynamics.jsx
index 05d9046..d494c52 100644
--- a/src/pages/UserCenter/UserDynamics.jsx
+++ b/src/pages/UserCenter/UserDynamics.jsx
@@ -29,7 +29,7 @@
return (
<div className="user-dynamics-container">
- <h2 className="user-dynamics-title">我的动态</h2>
+ {/* <h2 className="user-dynamics-title">我的动态</h2> */}
{dynamics.length === 0 ? (
<div className="user-dynamics-empty">暂无动态</div>
) : (
@@ -49,13 +49,6 @@
<div className="dynamic-content">
{item.title && <h4 className="dynamic-title">{item.title}</h4>}
<p>{item.content}</p>
- {/* {item.images && (
- <div className="dynamic-images">
- {JSON.parse(item.images).map((img, index) => (
- <img key={index} src={img} alt={`图${index + 1}`} />
- ))}
- </div>
- )} */}
{item.images && (
<div className="dynamic-images">
{(() => {
diff --git a/src/pages/UserCenter/UserFriends.css b/src/pages/UserCenter/UserFriends.css
index 2533959..37f6c9b 100644
--- a/src/pages/UserCenter/UserFriends.css
+++ b/src/pages/UserCenter/UserFriends.css
@@ -19,6 +19,8 @@
.friend-info {
flex: 1;
+ /* 字体大小 */
+ font-size: 1.3em;
}
.friend-info h4 {
diff --git a/src/pages/UserCenter/UserFriends.jsx b/src/pages/UserCenter/UserFriends.jsx
index 5540911..491c19c 100644
--- a/src/pages/UserCenter/UserFriends.jsx
+++ b/src/pages/UserCenter/UserFriends.jsx
@@ -335,13 +335,25 @@
return (
<div className="user-subpage-card">
- <h2>我的好友</h2>
- <div>
- <input type="text" value={email} placeholder="请输入好友邮箱" onChange={e => setEmail(e.target.value)} />
+
+ <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginLeft: '-12%' }}>
+ <div className="search">
+ <input
+ type="text"
+ value={email}
+ onChange={(e) => setEmail(e.target.value)}
+ placeholder="搜索邮箱添加好友"
+ />
+ </div>
+
<button className="btn submit" onClick={handleSubmit}>
添加
</button>
</div>
+
+ {/* 增加间距 */}
+ <div style={{ marginBottom: '30px' }}></div>
+
<div className="friends-list">
{friends.length === 0 && <p>暂无好友</p>}
{friends.map((friend) => (
@@ -350,7 +362,7 @@
<div className="friend-info">
<p><strong>{friend.nickname}</strong></p>
<p>{friend.email}</p>
- <button className="send-message-btn" onClick={() => openChat(friend)}>
+ <button style={{ padding: '6px 12px', fontSize: '15px', float: 'right' }} onClick={() => openChat(friend)}>
发送私信
</button>
</div>
diff --git a/src/pages/UserCenter/UserLevelExperience.jsx b/src/pages/UserCenter/UserLevelExperience.jsx
new file mode 100644
index 0000000..60dcd55
--- /dev/null
+++ b/src/pages/UserCenter/UserLevelExperience.jsx
@@ -0,0 +1,375 @@
+// import React, { useState, useEffect } from 'react';
+// import axios from 'axios';
+
+// const UserLevelExperience = ({ userId }) => {
+// const [experienceInfo, setExperienceInfo] = useState(null);
+// const [error, setError] = useState(null);
+// const [isLoading, setIsLoading] = useState(false);
+// const [upgradeResult, setUpgradeResult] = useState(null);
+// const [hasCheckedIn, setHasCheckedIn] = useState(false);
+
+// useEffect(() => {
+// if (!userId) return;
+// fetchAllLevelData();
+// }, [userId]);
+
+// useEffect(() => {
+// // 自动触发升级判断
+// if (
+// experienceInfo &&
+// experienceInfo.current_experience >= experienceInfo.next_level_experience
+// ) {
+// checkUpgrade();
+// }
+// }, [experienceInfo]);
+
+// const fetchAllLevelData = async () => {
+// try {
+// setIsLoading(true);
+// setError(null);
+
+// const { data } = await axios.get('/echo/level/getExperience', {
+// params: { user_id: userId },
+// });
+
+// const normalizedData = {
+// ...data,
+// current_level: data.current_level || data.level,
+// };
+
+// setExperienceInfo(normalizedData);
+
+// const today = new Date().toDateString();
+// const lastCheckIn = localStorage.getItem('lastCheckIn');
+// setHasCheckedIn(lastCheckIn === today);
+// } catch (err) {
+// console.error('经验信息获取失败:', err);
+// setError('获取经验信息失败');
+// } finally {
+// setIsLoading(false);
+// }
+// };
+
+// const updateExperience = async (source, amount = 10) => {
+// try {
+// setIsLoading(true);
+// setError(null);
+
+// const { data } = await axios.post('/echo/level/updateExperience', {
+// user_id: userId,
+// experience: amount,
+// source: source,
+// });
+
+// setExperienceInfo((prev) => ({
+// ...prev,
+// current_experience: data.current_experience,
+// }));
+
+// alert(`获得${amount}点经验值!来源:${source}`);
+
+// if (source === 'check-in') {
+// localStorage.setItem('lastCheckIn', new Date().toDateString());
+// setHasCheckedIn(true);
+// }
+// } catch (err) {
+// console.error('更新经验失败:', err);
+// setError(err.response?.data?.message || '更新经验失败');
+// } finally {
+// setIsLoading(false);
+// }
+// };
+
+// const checkUpgrade = async () => {
+// try {
+// setIsLoading(true);
+// setError(null);
+
+// const { data } = await axios.get('/echo/level/upgrade-check', {
+// params: { user_id: userId },
+// });
+
+// if (data.can_upgrade) {
+// await performUpgrade(); // 自动触发
+// }
+// } catch (err) {
+// console.error('检查升级失败:', err);
+// setError(err.response?.data?.message || '检查升级失败');
+// } finally {
+// setIsLoading(false);
+// }
+// };
+
+
+// const performUpgrade = async () => {
+// try {
+// setIsLoading(true);
+// setError(null);
+
+// const { data } = await axios.post('/echo/level/upgrades', {
+// user_id: userId,
+// can_upgrade: true,
+// });
+
+// console.log('升级响应数据:', data); // 保留调试日志
+
+// setExperienceInfo((prev) => ({
+// ...prev,
+// current_level: data.new_level, // 修复:使用正确的字段名
+// current_experience: 0,
+// next_level_experience: prev.next_level_experience * 2,
+// }));
+
+// setUpgradeResult(data);
+// alert(`恭喜!您已升级到等级 ${data.new_level}!`); // 修复:使用正确的字段名
+// } catch (err) {
+// console.error('升级失败:', err);
+// setError(err.response?.data?.message || '升级失败');
+// } finally {
+// setIsLoading(false);
+// }
+// };
+
+
+// if (error) return <p className="error">{error}</p>;
+// if (isLoading) return <p>加载中...</p>;
+// if (!experienceInfo) return <p>加载经验信息中...</p>;
+
+// const { current_experience, next_level_experience, current_level } = experienceInfo;
+// const progressPercent = Math.min(
+// 100,
+// (current_experience / (next_level_experience || 1)) * 100
+// ).toFixed(2);
+
+// const expToNextLevel = Math.max(0, next_level_experience - current_experience); // 防止负数
+
+// return (
+// <div className="level-experience-section">
+// {/* <h3>等级与经验</h3> */}
+// <p><strong>当前等级:</strong>{current_level || '未知'}</p>
+// <p><strong>当前经验:</strong>{current_experience}</p>
+// <p><strong>距离下一等级还需:</strong>{expToNextLevel} 经验值</p>
+
+// <div className="exp-bar-wrapper">
+// <div className="exp-bar" style={{ width: `${progressPercent}%` }} />
+// </div>
+// <p className="exp-progress-text">{progressPercent}%</p>
+
+
+// {upgradeResult && (
+// <div className="upgrade-success">
+// {/* 使用与状态一致的字段名 */}
+// <p>恭喜!您已成功升级到等级 {upgradeResult.new_level}!</p>
+// </div>
+// )}
+
+
+// <div className="level-actions">
+// <button onClick={() => updateExperience('check-in', 15)} disabled={hasCheckedIn}>
+// {hasCheckedIn ? '今日已签到' : '每日签到 (+15经验)'}
+// </button>
+// <button onClick={() => updateExperience('task', 30)}>完成任务 (+30经验)</button>
+// <button onClick={() => updateExperience('upload', 50)}>上传种子 (+50经验)</button>
+// {/* <button onClick={checkUpgrade}>检查升级</button> */}
+// </div>
+// </div>
+// );
+// };
+
+// export default UserLevelExperience;
+
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+
+const UserLevelExperience = ({ userId }) => {
+ const [experienceInfo, setExperienceInfo] = useState(null);
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [upgradeResult, setUpgradeResult] = useState(null);
+ const [hasCheckedIn, setHasCheckedIn] = useState(false);
+
+ useEffect(() => {
+ if (!userId) return;
+ fetchAllLevelData();
+ }, [userId]);
+
+ useEffect(() => {
+ // 自动触发升级判断
+ if (
+ experienceInfo &&
+ experienceInfo.current_experience >= experienceInfo.next_level_experience
+ ) {
+ checkUpgrade();
+ }
+ }, [experienceInfo]);
+
+ const fetchAllLevelData = async () => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ const { data } = await axios.get('/echo/level/getExperience', {
+ params: { user_id: userId },
+ });
+
+ const normalizedData = {
+ ...data,
+ current_level: data.current_level || data.level,
+ };
+
+ setExperienceInfo(normalizedData);
+
+ const today = new Date().toDateString();
+ const lastCheckIn = localStorage.getItem('lastCheckIn');
+ setHasCheckedIn(lastCheckIn === today);
+ } catch (err) {
+ console.error('经验信息获取失败:', err);
+ setError('获取经验信息失败');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const updateExperience = async (source, amount = 10) => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ const { data } = await axios.post('/echo/level/updateExperience', {
+ user_id: userId,
+ experience: amount,
+ source: source,
+ });
+
+ setExperienceInfo((prev) => ({
+ ...prev,
+ current_experience: data.current_experience,
+ }));
+
+ alert(`获得${amount}点经验值!来源:${source}`);
+
+ if (source === 'check-in') {
+ localStorage.setItem('lastCheckIn', new Date().toDateString());
+ setHasCheckedIn(true);
+ }
+ } catch (err) {
+ console.error('更新经验失败:', err);
+ setError(err.response?.data?.message || '更新经验失败');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const checkUpgrade = async () => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ const { data } = await axios.get('/echo/level/upgrade-check', {
+ params: { user_id: userId },
+ });
+
+ if (data.can_upgrade) {
+ if (window.confirm('您已满足升级条件,是否要升级?')) {
+ await performUpgrade();
+ }
+ } else {
+ // 区分是经验不足还是已达最高等级
+ if (data.is_max_level) {
+ alert('您已达到最高等级!');
+ } else {
+ alert(`还不能升级,还需要${data.next_level_experience - data.current_experience}点经验值`);
+ }
+ }
+ } catch (err) {
+ console.error('检查升级失败:', err);
+ setError(err.response?.data?.message || '检查升级失败');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const performUpgrade = async () => {
+ try {
+ setIsLoading(true);
+ setError(null);
+
+ const { data } = await axios.post('/echo/level/upgrades', {
+ user_id: userId,
+ can_upgrade: true,
+ });
+
+ console.log('升级响应数据:', data);
+
+ // 正确处理升级结果
+ if (data.status === 'success') {
+ setExperienceInfo((prev) => ({
+ ...prev,
+ current_level: data.new_level,
+ current_experience: 0,
+ next_level_experience: prev.next_level_experience * 2,
+ }));
+
+ setUpgradeResult(data);
+ alert(`恭喜!您已升级到等级 ${data.new_level}!`);
+ } else {
+ throw new Error(data.message || '升级失败');
+ }
+ } catch (err) {
+ console.error('升级失败:', err);
+ setError(err.message || '升级失败');
+ alert(err.message || '升级失败,请稍后再试');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ if (error) return <p className="error">{error}</p>;
+ if (isLoading) return <p>加载中...</p>;
+ if (!experienceInfo) return <p>加载经验信息中...</p>;
+
+ const { current_experience, next_level_experience, current_level } = experienceInfo;
+ const progressPercent = Math.min(
+ 100,
+ (current_experience / (next_level_experience || 1)) * 100
+ ).toFixed(2);
+
+ const expToNextLevel = Math.max(0, next_level_experience - current_experience);
+
+ return (
+ <div className="level-experience-section">
+ <h3>等级与经验</h3>
+ <p><strong>当前等级:</strong>{current_level || '未知'}</p>
+ <p><strong>当前经验:</strong>{current_experience}</p>
+ <p><strong>距离下一等级还需:</strong>{expToNextLevel} 经验值</p>
+
+ <div className="exp-bar-wrapper">
+ <div className="exp-bar" style={{ width: `${progressPercent}%` }} />
+ </div>
+ <p className="exp-progress-text">{progressPercent}%</p>
+
+ {upgradeResult && (
+ <div className="upgrade-success">
+ <p>恭喜!您已成功升级到等级 {upgradeResult.new_level}!</p>
+ </div>
+ )}
+
+ {error && (
+ <div className="upgrade-error">
+ <p>{error}</p>
+ </div>
+ )}
+
+ <div className="level-actions">
+ <button onClick={() => updateExperience('check-in', 15)} disabled={hasCheckedIn}>
+ {hasCheckedIn ? '今日已签到' : '每日签到 (+15经验)'}
+ </button>
+ <button onClick={() => updateExperience('task', 30)}>完成任务 (+30经验)</button>
+ <button onClick={() => updateExperience('upload', 50)}>上传种子 (+50经验)</button>
+ <button onClick={checkUpgrade}>检查升级</button>
+ </div>
+ </div>
+ );
+};
+
+export default UserLevelExperience;
diff --git a/src/pages/UserCenter/UserProfile.css b/src/pages/UserCenter/UserProfile.css
index b230c1e..cb71be7 100644
--- a/src/pages/UserCenter/UserProfile.css
+++ b/src/pages/UserCenter/UserProfile.css
@@ -54,10 +54,25 @@
margin-top: 40px;
width: 80%;
padding-top: 10%;
- padding-right: 15%;
+ padding-right: 10%;
padding-bottom: 10%;
padding-left: 10%;
+ margin-left: 3%;
+ margin-right: 5%;
+
+ /* padding: 10% 20%; */
}
+
+/* .common-card {
+ background-color: #e9ded2;
+ border-radius: 16px;
+ margin: 0 auto;
+ margin-top: 40px;
+ padding: 10% 20%;
+ margin-left: 5%;
+ margin-right: 5%;
+} */
+
.avatar-wrapper {
position: relative;
display: inline-block;
@@ -119,15 +134,7 @@
text-align: center;
}
-.common-card {
- background-color: #e9ded2;
- border-radius: 16px;
- margin: 0 auto;
- margin-top: 40px;
- padding: 10% 20%;
- margin-left: 5%;
- margin-right: 5%;
-}
+
.avatar-wrapper {
position: relative;
diff --git a/src/pages/UserCenter/UserProfile.jsx b/src/pages/UserCenter/UserProfile.jsx
index c6e80e0..bd87c99 100644
--- a/src/pages/UserCenter/UserProfile.jsx
+++ b/src/pages/UserCenter/UserProfile.jsx
@@ -1,263 +1,283 @@
-import React, { useEffect, useState } from 'react';
-import axios from 'axios';
-import './UserProfile.css';
-import { useUser } from '../../context/UserContext';
-import { useLocation } from 'wouter';
+// import React, { useEffect, useState } from 'react';
+// import axios from 'axios';
+// import './UserProfile.css';
+// import { useUser } from '../../context/UserContext';
+// import { useLocation } from 'wouter';
-const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
+// const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
+
+// const UserProfile = () => {
+// const { user, loading, logout } = useUser();
+// const [userProfile, setUserProfile] = useState(null);
+// const [experienceInfo, setExperienceInfo] = useState(null);
+// const [error, setError] = useState(null);
+
+// // 修改密码状态
+// const [showPwdModal, setShowPwdModal] = useState(false);
+// const [oldPassword, setOldPassword] = useState('');
+// const [newPassword, setNewPassword] = useState('');
+// const [confirmPassword, setConfirmPassword] = useState('');
+
+// // 退出登录
+// const [, setLocation] = useLocation();
+
+// useEffect(() => {
+// if (loading) return;
+// if (!user || !user.userId) {
+// setError('未登录或用户信息缺失');
+// setUserProfile(null);
+// return;
+// }
+
+// const fetchUserProfile = async () => {
+// try {
+// setError(null);
+// const { data: raw } = await axios.get(`/echo/user/${user.userId}/getProfile`);
+// if (!raw) {
+// setError('用户数据为空');
+// setUserProfile(null);
+// return;
+// }
+
+// const profile = {
+// avatarUrl: raw.avatarUrl
+// ? `${process.env.REACT_APP_AVATAR_BASE_URL}${raw.avatarUrl}`
+// : DEFAULT_AVATAR_URL,
+// nickname: raw.username || '未知用户',
+// email: raw.email || '未填写',
+// gender: raw.gender || '保密',
+// bio: raw.description || '无',
+// interests: raw.hobbies ? raw.hobbies.split(',') : [],
+// level: raw.level || '未知',
+// experience: raw.experience ?? 0,
+// uploadAmount: raw.uploadCount ?? 0,
+// downloadAmount: raw.downloadCount ?? 0,
+// shareRate: raw.shareRate ?? 0,
+// joinedDate: raw.registrationTime,
+// };
+
+// setUserProfile(profile);
+// } catch (err) {
+// setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
+// setUserProfile(null);
+// }
+// };
+
+// const fetchExperienceInfo = async () => {
+// try {
+// const { data } = await axios.get('/echo/level/getExperience', {
+// params: { user_id: user.userId },
+// });
+// setExperienceInfo(data);
+// } catch (err) {
+// console.error('经验信息获取失败:', err);
+// }
+// };
+
+// fetchUserProfile();
+// fetchExperienceInfo();
+// }, [user, loading]);
+
+// const handleAvatarUpload = async (e) => {
+// const file = e.target.files[0];
+// if (!file) return;
+
+// const formData = new FormData();
+// formData.append('file', file);
+
+// try {
+// const { data } = await axios.post(
+// `/echo/user/${user.userId}/uploadAvatar`,
+// formData,
+// { headers: { 'Content-Type': 'multipart/form-data' } }
+// );
+
+// if (data?.avatarUrl) {
+// setUserProfile((prev) => ({
+// ...prev,
+// avatarUrl: `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}`,
+// }));
+// alert('头像上传成功');
+// } else {
+// alert('头像上传成功,但未返回新头像地址');
+// }
+// } catch (err) {
+// console.error('上传失败:', err);
+// alert('头像上传失败,请重试');
+// }
+// };
+
+// const handleLogout = () => {
+// logout();
+// setLocation('/auth'); // 退出后跳转登录页
+// // window.location.reload(); // 或跳转登录页
+// };
+
+// const handleChangePassword = async () => {
+// if (!oldPassword || !newPassword || !confirmPassword) {
+// alert('请填写所有字段');
+// return;
+// }
+// if (newPassword !== confirmPassword) {
+// alert('两次输入的新密码不一致');
+// return;
+// }
+
+// try {
+// // await axios.post('/echo/user/password', {
+// // user_id: user.userId,
+// // oldPassword,
+// // newPassword,
+// // });
+// await axios.post('/echo/user/password', {
+// user_id: user.userId,
+// old_password: oldPassword,
+// new_password: newPassword,
+// confirm_password: confirmPassword,
+// });
+// alert('密码修改成功,请重新登录');
+// logout();
+// window.location.reload();
+// } catch (err) {
+// alert(err.response?.data?.message || '密码修改失败,请检查原密码是否正确');
+// }
+// };
+
+// if (loading) return <p>正在加载用户信息...</p>;
+// if (error) return <p className="error">{error}</p>;
+// if (!userProfile) return null;
+
+// const {
+// avatarUrl,
+// nickname,
+// email,
+// gender,
+// bio,
+// interests,
+// level,
+// experience,
+// uploadAmount,
+// downloadAmount,
+// shareRate,
+// joinedDate,
+// } = userProfile;
+
+// const progressPercent = experienceInfo
+// ? Math.min(
+// 100,
+// ((experienceInfo.current_experience || 0) /
+// (experienceInfo.next_level_experience || 1)) *
+// 100
+// ).toFixed(2)
+// : 0;
+
+// const expToNextLevel = experienceInfo
+// ? (experienceInfo.next_level_experience - experienceInfo.current_experience)
+// : null;
+
+// return (
+// <div className="common-card">
+// <div className="right-content">
+// <div className="profile-header">
+// <div className="avatar-wrapper">
+// <img src={avatarUrl} alt={nickname} className="avatar" />
+// <label htmlFor="avatar-upload" className="avatar-upload-label">
+// 上传头像
+// </label>
+// <input
+// type="file"
+// id="avatar-upload"
+// accept="image/*"
+// style={{ display: 'none' }}
+// onChange={handleAvatarUpload}
+// />
+// </div>
+// <h1>{nickname}</h1>
+// </div>
+
+// <div className="profile-details">
+// <p><strong>邮箱:</strong>{email}</p>
+// <p><strong>性别:</strong>{gender}</p>
+// <p><strong>个人简介:</strong>{bio}</p>
+// <p><strong>兴趣:</strong>{interests.length > 0 ? interests.join(', ') : '无'}</p>
+// <p><strong>等级:</strong>{level}</p>
+// <p><strong>经验:</strong>{experience}</p>
+// <p><strong>上传量:</strong>{uploadAmount}</p>
+// <p><strong>下载量:</strong>{downloadAmount}</p>
+// <p><strong>分享率:</strong>{(shareRate * 100).toFixed(2)}%</p>
+// <p><strong>加入时间:</strong>{new Date(joinedDate).toLocaleDateString()}</p>
+
+// {experienceInfo && (
+// <>
+// <p><strong>距离下一等级还需:</strong>{expToNextLevel} 经验值</p>
+// <div className="exp-bar-wrapper">
+// <div className="exp-bar" style={{ width: `${progressPercent}%` }} />
+// </div>
+// <p className="exp-progress-text">{progressPercent}%</p>
+// </>
+// )}
+
+// {/* 修改密码与退出登录按钮 */}
+// <div className="profile-actions">
+// <button onClick={() => setShowPwdModal(true)}>修改密码</button>
+// <button onClick={handleLogout}>退出登录</button>
+// </div>
+
+// {/* 修改密码弹窗 */}
+// {showPwdModal && (
+// <div className="modal">
+// <div className="modal-content">
+// <h3>修改密码</h3>
+// <input
+// type="password"
+// placeholder="原密码"
+// value={oldPassword}
+// onChange={(e) => setOldPassword(e.target.value)}
+// />
+// <input
+// type="password"
+// placeholder="新密码"
+// value={newPassword}
+// onChange={(e) => setNewPassword(e.target.value)}
+// />
+// <input
+// type="password"
+// placeholder="确认新密码"
+// value={confirmPassword}
+// onChange={(e) => setConfirmPassword(e.target.value)}
+// />
+// <div className="modal-buttons">
+// <button onClick={handleChangePassword}>确认修改</button>
+// <button onClick={() => setShowPwdModal(false)}>取消</button>
+// </div>
+// </div>
+// </div>
+// )}
+// </div>
+// </div>
+// </div>
+// );
+// };
+
+// export default UserProfile;
+
+import React from 'react';
+import UserProfileBase from './UserProfileBase';
+import UserLevelExperience from './UserLevelExperience';
const UserProfile = () => {
- const { user, loading, logout } = useUser();
- const [userProfile, setUserProfile] = useState(null);
- const [experienceInfo, setExperienceInfo] = useState(null);
- const [error, setError] = useState(null);
+ const [userId, setUserId] = React.useState(null);
- // 修改密码状态
- const [showPwdModal, setShowPwdModal] = useState(false);
- const [oldPassword, setOldPassword] = useState('');
- const [newPassword, setNewPassword] = useState('');
- const [confirmPassword, setConfirmPassword] = useState('');
-
- // 退出登录
- const [, setLocation] = useLocation();
-
- useEffect(() => {
- if (loading) return;
- if (!user || !user.userId) {
- setError('未登录或用户信息缺失');
- setUserProfile(null);
- return;
- }
-
- const fetchUserProfile = async () => {
- try {
- setError(null);
- const { data: raw } = await axios.get(`/echo/user/${user.userId}/getProfile`);
- if (!raw) {
- setError('用户数据为空');
- setUserProfile(null);
- return;
- }
-
- const profile = {
- avatarUrl: raw.avatarUrl
- ? `${process.env.REACT_APP_AVATAR_BASE_URL}${raw.avatarUrl}`
- : DEFAULT_AVATAR_URL,
- nickname: raw.username || '未知用户',
- email: raw.email || '未填写',
- gender: raw.gender || '保密',
- bio: raw.description || '无',
- interests: raw.hobbies ? raw.hobbies.split(',') : [],
- level: raw.level || '未知',
- experience: raw.experience ?? 0,
- uploadAmount: raw.uploadCount ?? 0,
- downloadAmount: raw.downloadCount ?? 0,
- shareRate: raw.shareRate ?? 0,
- joinedDate: raw.registrationTime,
- };
-
- setUserProfile(profile);
- } catch (err) {
- setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
- setUserProfile(null);
- }
- };
-
- const fetchExperienceInfo = async () => {
- try {
- const { data } = await axios.get('/echo/level/getExperience', {
- params: { user_id: user.userId },
- });
- setExperienceInfo(data);
- } catch (err) {
- console.error('经验信息获取失败:', err);
- }
- };
-
- fetchUserProfile();
- fetchExperienceInfo();
- }, [user, loading]);
-
- const handleAvatarUpload = async (e) => {
- const file = e.target.files[0];
- if (!file) return;
-
- const formData = new FormData();
- formData.append('file', file);
-
- try {
- const { data } = await axios.post(
- `/echo/user/${user.userId}/uploadAvatar`,
- formData,
- { headers: { 'Content-Type': 'multipart/form-data' } }
- );
-
- if (data?.avatarUrl) {
- setUserProfile((prev) => ({
- ...prev,
- avatarUrl: `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}`,
- }));
- alert('头像上传成功');
- } else {
- alert('头像上传成功,但未返回新头像地址');
- }
- } catch (err) {
- console.error('上传失败:', err);
- alert('头像上传失败,请重试');
- }
+ const loadExperienceInfo = (id) => {
+ setUserId(id);
};
- const handleLogout = () => {
- logout();
- setLocation('/auth'); // 退出后跳转登录页
- // window.location.reload(); // 或跳转登录页
- };
-
- const handleChangePassword = async () => {
- if (!oldPassword || !newPassword || !confirmPassword) {
- alert('请填写所有字段');
- return;
- }
- if (newPassword !== confirmPassword) {
- alert('两次输入的新密码不一致');
- return;
- }
-
- try {
- // await axios.post('/echo/user/password', {
- // user_id: user.userId,
- // oldPassword,
- // newPassword,
- // });
- await axios.post('/echo/user/password', {
- user_id: user.userId,
- old_password: oldPassword,
- new_password: newPassword,
- confirm_password: confirmPassword,
- });
- alert('密码修改成功,请重新登录');
- logout();
- window.location.reload();
- } catch (err) {
- alert(err.response?.data?.message || '密码修改失败,请检查原密码是否正确');
- }
- };
-
- if (loading) return <p>正在加载用户信息...</p>;
- if (error) return <p className="error">{error}</p>;
- if (!userProfile) return null;
-
- const {
- avatarUrl,
- nickname,
- email,
- gender,
- bio,
- interests,
- level,
- experience,
- uploadAmount,
- downloadAmount,
- shareRate,
- joinedDate,
- } = userProfile;
-
- const progressPercent = experienceInfo
- ? Math.min(
- 100,
- ((experienceInfo.current_experience || 0) /
- (experienceInfo.next_level_experience || 1)) *
- 100
- ).toFixed(2)
- : 0;
-
- const expToNextLevel = experienceInfo
- ? (experienceInfo.next_level_experience - experienceInfo.current_experience)
- : null;
-
return (
- <div className="common-card">
- <div className="right-content">
- <div className="profile-header">
- <div className="avatar-wrapper">
- <img src={avatarUrl} alt={nickname} className="avatar" />
- <label htmlFor="avatar-upload" className="avatar-upload-label">
- 上传头像
- </label>
- <input
- type="file"
- id="avatar-upload"
- accept="image/*"
- style={{ display: 'none' }}
- onChange={handleAvatarUpload}
- />
- </div>
- <h1>{nickname}</h1>
- </div>
-
- <div className="profile-details">
- <p><strong>邮箱:</strong>{email}</p>
- <p><strong>性别:</strong>{gender}</p>
- <p><strong>个人简介:</strong>{bio}</p>
- <p><strong>兴趣:</strong>{interests.length > 0 ? interests.join(', ') : '无'}</p>
- <p><strong>等级:</strong>{level}</p>
- <p><strong>经验:</strong>{experience}</p>
- <p><strong>上传量:</strong>{uploadAmount}</p>
- <p><strong>下载量:</strong>{downloadAmount}</p>
- <p><strong>分享率:</strong>{(shareRate * 100).toFixed(2)}%</p>
- <p><strong>加入时间:</strong>{new Date(joinedDate).toLocaleDateString()}</p>
-
- {experienceInfo && (
- <>
- <p><strong>距离下一等级还需:</strong>{expToNextLevel} 经验值</p>
- <div className="exp-bar-wrapper">
- <div className="exp-bar" style={{ width: `${progressPercent}%` }} />
- </div>
- <p className="exp-progress-text">{progressPercent}%</p>
- </>
- )}
-
- {/* 修改密码与退出登录按钮 */}
- <div className="profile-actions">
- <button onClick={() => setShowPwdModal(true)}>修改密码</button>
- <button onClick={handleLogout}>退出登录</button>
- </div>
-
- {/* 修改密码弹窗 */}
- {showPwdModal && (
- <div className="modal">
- <div className="modal-content">
- <h3>修改密码</h3>
- <input
- type="password"
- placeholder="原密码"
- value={oldPassword}
- onChange={(e) => setOldPassword(e.target.value)}
- />
- <input
- type="password"
- placeholder="新密码"
- value={newPassword}
- onChange={(e) => setNewPassword(e.target.value)}
- />
- <input
- type="password"
- placeholder="确认新密码"
- value={confirmPassword}
- onChange={(e) => setConfirmPassword(e.target.value)}
- />
- <div className="modal-buttons">
- <button onClick={handleChangePassword}>确认修改</button>
- <button onClick={() => setShowPwdModal(false)}>取消</button>
- </div>
- </div>
- </div>
- )}
- </div>
- </div>
+ <div>
+ <UserProfileBase onLoadExperienceInfo={loadExperienceInfo} />
+ {userId && <UserLevelExperience userId={userId} />}
</div>
);
};
-export default UserProfile;
-
+export default UserProfile;
\ No newline at end of file
diff --git a/src/pages/UserCenter/UserProfileBase.jsx b/src/pages/UserCenter/UserProfileBase.jsx
new file mode 100644
index 0000000..7a1f726
--- /dev/null
+++ b/src/pages/UserCenter/UserProfileBase.jsx
@@ -0,0 +1,221 @@
+import React, { useEffect, useState } from 'react';
+import axios from 'axios';
+import { useUser } from '../../context/UserContext';
+import { useLocation } from 'wouter';
+
+const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
+
+const UserProfileBase = ({ onLoadExperienceInfo }) => {
+ const { user, loading, logout } = useUser();
+ const [userProfile, setUserProfile] = useState(null);
+ const [error, setError] = useState(null);
+
+ // 修改密码状态
+ const [showPwdModal, setShowPwdModal] = useState(false);
+ const [oldPassword, setOldPassword] = useState('');
+ const [newPassword, setNewPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+
+ // 退出登录
+ const [, setLocation] = useLocation();
+
+ useEffect(() => {
+ if (loading) return;
+ if (!user || !user.userId) {
+ setError('未登录或用户信息缺失');
+ setUserProfile(null);
+ return;
+ }
+
+ const fetchUserProfile = async () => {
+ try {
+ setError(null);
+ const { data: raw } = await axios.get(`/echo/user/${user.userId}/getProfile`);
+ if (!raw) {
+ setError('用户数据为空');
+ setUserProfile(null);
+ return;
+ }
+
+ const profile = {
+ avatarUrl: raw.avatarUrl
+ ? `${process.env.REACT_APP_AVATAR_BASE_URL}${raw.avatarUrl}`
+ : DEFAULT_AVATAR_URL,
+ nickname: raw.username || '未知用户',
+ email: raw.email || '未填写',
+ gender: raw.gender || '保密',
+ bio: raw.description || '无',
+ interests: raw.hobbies ? raw.hobbies.split(',') : [],
+ level: raw.level || '未知',
+ experience: raw.experience ?? 0,
+ uploadAmount: raw.uploadCount ?? 0,
+ downloadAmount: raw.downloadCount ?? 0,
+ shareRate: raw.shareRate ?? 0,
+ joinedDate: raw.registrationTime,
+ };
+
+ setUserProfile(profile);
+ // 加载经验信息
+ if (onLoadExperienceInfo) onLoadExperienceInfo(user.userId);
+ } catch (err) {
+ setError(err.response?.status === 404 ? '用户不存在' : '请求失败,请稍后再试');
+ setUserProfile(null);
+ }
+ };
+
+ fetchUserProfile();
+ }, [user, loading, onLoadExperienceInfo]);
+
+ const handleAvatarUpload = async (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ const formData = new FormData();
+ formData.append('file', file);
+
+ try {
+ const { data } = await axios.post(
+ `/echo/user/${user.userId}/uploadAvatar`,
+ formData,
+ { headers: { 'Content-Type': 'multipart/form-data' } }
+ );
+
+ if (data?.avatarUrl) {
+ setUserProfile((prev) => ({
+ ...prev,
+ avatarUrl: `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}`,
+ }));
+ alert('头像上传成功');
+ } else {
+ alert('头像上传成功,但未返回新头像地址');
+ }
+ } catch (err) {
+ console.error('上传失败:', err);
+ alert('头像上传失败,请重试');
+ }
+ };
+
+ const handleLogout = () => {
+ logout();
+ setLocation('/auth'); // 退出后跳转登录页
+ };
+
+ const handleChangePassword = async () => {
+ if (!oldPassword || !newPassword || !confirmPassword) {
+ alert('请填写所有字段');
+ return;
+ }
+ if (newPassword !== confirmPassword) {
+ alert('两次输入的新密码不一致');
+ return;
+ }
+
+ try {
+ await axios.post('/echo/user/password', {
+ user_id: user.userId,
+ old_password: oldPassword,
+ new_password: newPassword,
+ confirm_password: confirmPassword,
+ });
+ alert('密码修改成功,请重新登录');
+ logout();
+ window.location.reload();
+ } catch (err) {
+ alert(err.response?.data?.message || '密码修改失败,请检查原密码是否正确');
+ }
+ };
+
+ if (loading) return <p>正在加载用户信息...</p>;
+ if (error) return <p className="error">{error}</p>;
+ if (!userProfile) return null;
+
+ const {
+ avatarUrl,
+ nickname,
+ email,
+ gender,
+ bio,
+ interests,
+ level,
+ experience,
+ uploadAmount,
+ downloadAmount,
+ shareRate,
+ joinedDate,
+ } = userProfile;
+
+ return (
+ <div className="common-card">
+ <div className="right-content">
+ <div className="profile-header">
+ <div className="avatar-wrapper">
+ <img src={avatarUrl} alt={nickname} className="avatar" />
+ <label htmlFor="avatar-upload" className="avatar-upload-label">
+ 上传头像
+ </label>
+ <input
+ type="file"
+ id="avatar-upload"
+ accept="image/*"
+ style={{ display: 'none' }}
+ onChange={handleAvatarUpload}
+ />
+ </div>
+ <h1>{nickname}</h1>
+ </div>
+
+ <div className="profile-details">
+ <p><strong>邮箱:</strong>{email}</p>
+ <p><strong>性别:</strong>{gender}</p>
+ <p><strong>个人简介:</strong>{bio}</p>
+ <p><strong>兴趣:</strong>{interests.length > 0 ? interests.join(', ') : '无'}</p>
+ {/* <p><strong>等级:</strong>{level}</p>
+ <p><strong>经验:</strong>{experience}</p> */}
+ <p><strong>上传量:</strong>{uploadAmount}</p>
+ <p><strong>下载量:</strong>{downloadAmount}</p>
+ <p><strong>分享率:</strong>{(shareRate * 100).toFixed(2)}%</p>
+ <p><strong>加入时间:</strong>{new Date(joinedDate).toLocaleDateString()}</p>
+
+ {/* 修改密码与退出登录按钮 */}
+ <div className="profile-actions">
+ <button onClick={() => setShowPwdModal(true)}>修改密码</button>
+ <button onClick={handleLogout}>退出登录</button>
+ </div>
+
+ {/* 修改密码弹窗 */}
+ {showPwdModal && (
+ <div className="modal">
+ <div className="modal-content">
+ <h3>修改密码</h3>
+ <input
+ type="password"
+ placeholder="原密码"
+ value={oldPassword}
+ onChange={(e) => setOldPassword(e.target.value)}
+ />
+ <input
+ type="password"
+ placeholder="新密码"
+ value={newPassword}
+ onChange={(e) => setNewPassword(e.target.value)}
+ />
+ <input
+ type="password"
+ placeholder="确认新密码"
+ value={confirmPassword}
+ onChange={(e) => setConfirmPassword(e.target.value)}
+ />
+ <div className="modal-buttons">
+ <button onClick={handleChangePassword}>确认修改</button>
+ <button onClick={() => setShowPwdModal(false)}>取消</button>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default UserProfileBase;
\ No newline at end of file