完成用户等级权限设置、修复下载次数不增加
Change-Id: Ia8ab0e643c86e236a5c25ac77b081cd1f3ba5976
diff --git a/src/components/AuthButton.jsx b/src/components/AuthButton.jsx
index b5cc431..246555a 100644
--- a/src/components/AuthButton.jsx
+++ b/src/components/AuthButton.jsx
@@ -4,11 +4,13 @@
const AuthButton = ({children, roles,onClick, ...rest }) => {
const {user} = useContext(UserContext);
const {levelRole} = user;
+
let clickFunc = onClick
if(!roles || roles.length === 0 || roles.includes(levelRole)){
clickFunc = onClick;
}else{
- clickFunc = () => {
+ clickFunc = (e) => {
+ e.preventDefault();
toast.error("权限不足");
}
}
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
index e0a8495..fd2a381 100644
--- a/src/components/Header.jsx
+++ b/src/components/Header.jsx
@@ -1,12 +1,13 @@
-import React, { useState, useEffect } from 'react';
+import React, { useEffect } from 'react';
import './Header.css';
import logo from '../assets/logo.png';
import { useUser } from '../context/UserContext';
-import { BookOpen } from '@icon-park/react'; // 导入图标组件
+import { BookOpen } from '@icon-park/react';
+import { useLocation } from 'wouter';
const Header = () => {
- const [currentPath, setCurrentPath] = useState(window.location.pathname);
const { user } = useUser();
+ const [location, setLocation] = useLocation();
const navLinks = [
{ to: '/friend-moments', label: '好友动态' },
@@ -16,22 +17,8 @@
{ to: '/publish-seed', label: '发布种子' },
];
- useEffect(() => {
- const handleLocationChange = () => {
- setCurrentPath(window.location.pathname);
- };
- window.addEventListener('popstate', handleLocationChange);
- return () => {
- window.removeEventListener('popstate', handleLocationChange);
- };
- }, []);
-
- const isActive = (path) => currentPath.startsWith(path);
-
- const handleLinkClick = (to) => {
- window.history.pushState({}, '', to);
- setCurrentPath(to);
- };
+ // 头像变化时刷新组件(这段其实 React 会自动做,但你手动 history 管理就错过了)
+ useEffect(() => {}, [user?.avatarUrl]);
return (
<div className="main-page">
@@ -41,16 +28,14 @@
<span className="site-name">Echo</span>
</div>
<div className="user-and-message" style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
- {/* 新手指南图标,放在头像左边 */}
- <button
- className="guide-button"
- onClick={() => handleLinkClick('/new-user-guide')}
- title="新手指南"
- >
- <BookOpen theme="outline" size="20" fill="#fff" />
- <span style={{ marginLeft: '6px' }}>新手指南</span>
- </button>
-
+ <button
+ className="guide-button"
+ onClick={() => setLocation('/new-user-guide')}
+ title="新手指南"
+ >
+ <BookOpen theme="outline" size="20" fill="#fff" />
+ <span style={{ marginLeft: '6px' }}>新手指南</span>
+ </button>
<a href="/user/profile">
<img
@@ -59,13 +44,14 @@
className="user-avatar"
/>
</a>
- <span
+
+ {/* <span
className="message-center"
- onClick={() => handleLinkClick('/messages')}
+ onClick={() => setLocation('/messages')}
style={{ cursor: 'pointer' }}
>
消息
- </span>
+ </span> */}
</div>
</header>
@@ -76,9 +62,9 @@
href={to}
onClick={(e) => {
e.preventDefault();
- handleLinkClick(to);
+ setLocation(to);
}}
- className={`nav-item ${isActive(to) ? 'active' : ''}`}
+ className={`nav-item ${location.startsWith(to) ? 'active' : ''}`}
>
{label}
</a>
diff --git a/src/context/UserContext.js b/src/context/UserContext.js
index c8c65e8..493eede 100644
--- a/src/context/UserContext.js
+++ b/src/context/UserContext.js
@@ -1,4 +1,70 @@
+// import React, { createContext, useContext, useState, useEffect } from 'react';
+
+// const avatarBaseUrl = process.env.REACT_APP_AVATAR_BASE_URL || '';
+
+// export const UserContext = createContext();
+
+// export const UserProvider = ({ children }) => {
+// const [user, setUser] = useState(null);
+// const [loading, setLoading] = useState(true);
+
+// const formatUser = (raw) => {
+// if (!raw) return null;
+// return {
+// userId: raw.userId || raw.id || null,
+// username: raw.username || '匿名用户',
+// avatarUrl: raw.avatarUrl ? `${avatarBaseUrl}${raw.avatarUrl}` : null,
+// ...raw,
+// };
+// };
+
+// useEffect(() => {
+// const storedUser = localStorage.getItem('user');
+// if (storedUser) {
+// try {
+// const parsed = JSON.parse(storedUser);
+// setUser(formatUser(parsed));
+// } catch (err) {
+// console.error('本地用户信息解析失败:', err);
+// localStorage.removeItem('user');
+// setUser(null);
+// }
+// } else {
+// // 可以默认不自动登录,退出就是空
+// setUser(null);
+// }
+// setLoading(false);
+// }, []);
+
+// const saveUser = (userData) => {
+// if (userData && (userData.userId || userData.id)) {
+// localStorage.setItem('user', JSON.stringify(userData));
+// setUser(formatUser(userData));
+// } else {
+// console.error('无效的用户数据:', userData);
+// }
+// };
+
+// const logout = () => {
+// localStorage.removeItem('user');
+// setUser(null);
+// };
+
+// return (
+// <UserContext.Provider value={{ user, saveUser, logout, loading }}>
+// {children}
+// </UserContext.Provider>
+// );
+// };
+
+// export const useUser = () => {
+// const context = useContext(UserContext);
+// if (!context) {
+// throw new Error('useUser 必须在 UserProvider 内使用');
+// }
+// return context;
+// };
import React, { createContext, useContext, useState, useEffect } from 'react';
const avatarBaseUrl = process.env.REACT_APP_AVATAR_BASE_URL || '';
@@ -14,7 +80,11 @@
return {
userId: raw.userId || raw.id || null,
username: raw.username || '匿名用户',
- avatarUrl: raw.avatarUrl ? `${avatarBaseUrl}${raw.avatarUrl}` : null,
+ avatarUrl: raw.avatarUrl
+ ? raw.avatarUrl.startsWith('http')
+ ? raw.avatarUrl
+ : `${avatarBaseUrl}${raw.avatarUrl}`
+ : null,
...raw,
};
};
@@ -31,7 +101,6 @@
setUser(null);
}
} else {
- // 可以默认不自动登录,退出就是空
setUser(null);
}
setLoading(false);
@@ -39,8 +108,9 @@
const saveUser = (userData) => {
if (userData && (userData.userId || userData.id)) {
- localStorage.setItem('user', JSON.stringify(userData));
- setUser(formatUser(userData));
+ const formatted = formatUser(userData);
+ localStorage.setItem('user', JSON.stringify(formatted));
+ setUser(formatted);
} else {
console.error('无效的用户数据:', userData);
}
diff --git a/src/pages/Forum/posts-detail/PostDetailPage.jsx b/src/pages/Forum/posts-detail/PostDetailPage.jsx
index 2e4874a..bf0f062 100644
--- a/src/pages/Forum/posts-detail/PostDetailPage.jsx
+++ b/src/pages/Forum/posts-detail/PostDetailPage.jsx
@@ -5,6 +5,7 @@
import './PostDetailPage.css';
import { UserContext } from '../../../context/UserContext'; // 用户上下文
import Header from '../../../components/Header';
+import AuthButton from '../../../components/AuthButton';
const formatImageUrl = (url) => {
if (!url) return '';
@@ -259,7 +260,10 @@
onChange={(e) => setNewComment(e.target.value)}
/>
<div className="comment-options">
- <button onClick={handleAddComment}>发布回复</button>
+ <AuthButton roles={['cookie', 'chocolate', 'ice-cream']} onClick={handleAddComment}>
+ 发布回复
+ </AuthButton>
+
<button
onClick={() => {
setReplyToCommentId(null);
@@ -284,7 +288,10 @@
onChange={(e) => setNewComment(e.target.value)}
/>
<div className="comment-options">
- <button onClick={handleAddComment}>发布评论</button>
+ <AuthButton roles={['cookie', 'chocolate', 'ice-cream']} onClick={handleAddComment}>
+ 发布评论
+ </AuthButton>
+
</div>
</div>
)}
diff --git a/src/pages/InterestGroup/GroupItem.jsx b/src/pages/InterestGroup/GroupItem.jsx
index 8e6068b..1718111 100644
--- a/src/pages/InterestGroup/GroupItem.jsx
+++ b/src/pages/InterestGroup/GroupItem.jsx
@@ -3,6 +3,7 @@
import { useUser } from '../../context/UserContext';
import { useLocation } from 'wouter';
import './GroupDetail.css';
+import AuthButton from '../../components/AuthButton';
const GroupItem = ({ group }) => {
const [, setLocation] = useLocation();
@@ -97,7 +98,8 @@
<p style={{ color: '#BA929A' }}>{group.memberCount || 0}人加入了小组</p>
{userId && (
- <button
+ <AuthButton
+ roles={["chocolate", "ice-cream"]}
style={{ color: '#2167c9', background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontSize: '16px' }}
onClick={(e) => {
e.stopPropagation();
@@ -106,7 +108,7 @@
disabled={loading}
>
{loading ? '处理中...' : isMember ? '退出小组' : '+加入小组'}
- </button>
+ </AuthButton>
)}
{!userId && <button disabled>请登录</button>}
diff --git a/src/pages/InterestGroup/GroupPosts.jsx b/src/pages/InterestGroup/GroupPosts.jsx
index 7bd16ba..d7893c7 100644
--- a/src/pages/InterestGroup/GroupPosts.jsx
+++ b/src/pages/InterestGroup/GroupPosts.jsx
@@ -2,6 +2,7 @@
import CommentForm from './CommentForm';
import { GoodTwo, Comment } from '@icon-park/react';
import './GroupDetail.css';
+import AuthButton from '../../components/AuthButton';
const GroupPosts = ({
posts,
@@ -21,12 +22,13 @@
<div className="post-list-header">
{userId && isMember && (
- <button
+ <AuthButton
+ roles={[ "chocolate", "ice-cream"]}
className="create-post-btn"
onClick={onShowCreatePost}
>
+ 发布帖子
- </button>
+ </AuthButton>
)}
{(!userId || !isMember) && <p className="login-hint">{loginHint}</p>}
</div>
diff --git a/src/pages/PublishSeed/PublishSeed.jsx b/src/pages/PublishSeed/PublishSeed.jsx
index 27717ca..6528b0d 100644
--- a/src/pages/PublishSeed/PublishSeed.jsx
+++ b/src/pages/PublishSeed/PublishSeed.jsx
@@ -5,6 +5,7 @@
import './PublishSeed.css';
import { useUser } from '../../context/UserContext';
import { uploadFile } from '../../api/file';
+import AuthButton from '../../components/AuthButton';
const PublishSeed = () => {
const [title, setTitle] = useState('');
@@ -99,7 +100,7 @@
});
console.log('[DEBUG] 请求成功,响应:', response.data);
- if (response.data.status === 'success') {
+ if (response.data.code === 0) {
setMessage('种子上传成功');
} else {
setMessage(response.data.message || '上传失败,请稍后再试');
@@ -238,13 +239,14 @@
</div>
<div className="ps-upload-button">
- <button
+ <AuthButton
+ roles={["cookie", "chocolate", "ice-cream"]}
type="submit"
disabled={isLoading}
onClick={() => console.log('[DEBUG] 上传按钮 onClick 触发')}
>
{isLoading ? '正在上传...' : '上传种子'}
- </button>
+ </AuthButton>
</div>
</form>
</div>
diff --git a/src/pages/SeedList/Recommend/Recommend.jsx b/src/pages/SeedList/Recommend/Recommend.jsx
index f58c73d..2229bb1 100644
--- a/src/pages/SeedList/Recommend/Recommend.jsx
+++ b/src/pages/SeedList/Recommend/Recommend.jsx
@@ -7,6 +7,7 @@
import CreatePlaylistModal from './CreatePlaylistModal';
import { confirmAlert } from 'react-confirm-alert';
import 'react-confirm-alert/src/react-confirm-alert.css';
+import AuthButton from '../../../components/AuthButton';
const Recommend = () => {
const { user } = useUser();
@@ -160,15 +161,18 @@
/>
<div className="paid-title">{list.title}</div>
- {user && user.role === 'admin' ? (
- <div className="admin-actions">
- <button onClick={() => handleDelete(list.id)}>删除</button>
- </div>
- ) : list.isPaid ? (
- <button onClick={() => navigate(`/playlist/${list.id}`)}>详情</button>
- ) : (
- <button onClick={() => handlePurchase(list.id)}>购买</button>
- )}
+ {user && user.role === 'admin' ? (
+ <div className="admin-actions">
+ <button onClick={() => handleDelete(list.id)}>删除</button>
+ </div>
+ ) : list.isPaid ? (
+ <button onClick={() => navigate(`/playlist/${list.id}`)}>详情</button>
+ ) : (
+ <AuthButton roles={['chocolate', 'ice-cream']} onClick={() => handlePurchase(list.id)}>
+ 购买
+ </AuthButton>
+ )}
+
</div>
))}
</div>
diff --git a/src/pages/SeedList/SeedDetail/SeedDetail.jsx b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
index 4ab9274..705368a 100644
--- a/src/pages/SeedList/SeedDetail/SeedDetail.jsx
+++ b/src/pages/SeedList/SeedDetail/SeedDetail.jsx
@@ -5,6 +5,7 @@
import './SeedDetail.css';
import { useUser } from '../../../context/UserContext';
import SeedRating from './SeedRating';
+import AuthButton from '../../../components/AuthButton';
const SeedDetail = () => {
const params = useParams();
@@ -213,12 +214,16 @@
</div>
<div className="action-buttons">
- <button className="btn" onClick={() => handleDownload(seed.id)}>下载</button>
- <button className="btn-outline" onClick={handleCollect}>收藏</button>
- {/* <button className="btn" onClick={handleCollect}>收藏</button> */}
+ <AuthButton roles={["cookie", "chocolate", "ice-cream"]} onClick={() => handleDownload(seed.id)}>
+ 下载
+ </AuthButton>
+ <AuthButton roles={["cookie", "chocolate", "ice-cream"]} onClick={handleCollect}>
+ 收藏
+ </AuthButton>
<SeedRating seedId={seed.id} />
</div>
+
<hr className="divider" />
<h3>评论区</h3>
<div className="comments-section">
diff --git a/src/pages/SeedList/SeedDetail/SeedRating.css b/src/pages/SeedList/SeedDetail/SeedRating.css
index e40c055..f62131c 100644
--- a/src/pages/SeedList/SeedDetail/SeedRating.css
+++ b/src/pages/SeedList/SeedDetail/SeedRating.css
@@ -20,4 +20,8 @@
margin-left: 10px;
font-size: 14px;
color: rgba(222, 91, 111, 0.982);
+}
+.star.hover {
+ color: rgba(240, 184, 62, 0.916); /* 亮黄色 */
}
+
diff --git a/src/pages/SeedList/SeedDetail/SeedRating.jsx b/src/pages/SeedList/SeedDetail/SeedRating.jsx
index 1841443..8e2d3f0 100644
--- a/src/pages/SeedList/SeedDetail/SeedRating.jsx
+++ b/src/pages/SeedList/SeedDetail/SeedRating.jsx
@@ -63,6 +63,7 @@
import axios from 'axios';
import './SeedRating.css';
import { useUser } from '../../../context/UserContext';
+import AuthButton from '../../../components/AuthButton';
const SeedRating = ({ seedId }) => {
const { user } = useUser();
@@ -105,17 +106,28 @@
return (
<div className="seed-rating">
- <span>评分:</span>
+ <span style={{color: '#333'}}>评分:</span>
{[1, 2, 3, 4, 5].map((star) => (
- <span
- key={star}
- className={`star ${star <= (hoverScore || score) ? 'active' : ''}`}
- onMouseEnter={() => !submitted && setHoverScore(star)}
- onMouseLeave={() => !submitted && setHoverScore(0)}
- onClick={() => handleRating(star)}
- >
- ★
- </span>
+ <AuthButton
+ key={star}
+ roles={["cookie", "chocolate", "ice-cream"]}
+ style={{
+ backgroundColor: 'inherit',
+ margin: 0,
+ // color: '#ccc',
+ padding: 0,
+ outline: 'none',
+ border: 'none',
+ cursor: 'pointer'
+ }}
+ className={`star ${(star <= (hoverScore || score) ? 'active' : '')} ${hoverScore >= star ? 'hover' : ''}`}
+ onMouseEnter={() => !submitted && setHoverScore(star)}
+ onMouseLeave={() => !submitted && setHoverScore(0)}
+ onClick={() => handleRating(star)}
+ >
+ ★
+ </AuthButton>
+
))}
{submitted && <span className="thank-you">感谢您的评分!</span>}
</div>
diff --git a/src/pages/SeedList/SeedList.jsx b/src/pages/SeedList/SeedList.jsx
index 263038b..45b9f8c 100644
--- a/src/pages/SeedList/SeedList.jsx
+++ b/src/pages/SeedList/SeedList.jsx
@@ -169,8 +169,8 @@
<div className="tag-filters">
{TAGS.map(tag => (
- <AuthButton
- roles={["test"]}
+ <button
+ // roles={["test"]}
key={tag}
className={`tag-button ${activeTab === tag ? 'active-tag' : ''}`}
onClick={() => {
@@ -180,7 +180,7 @@
}}
>
{tag}
- </AuthButton>
+ </button>
))}
</div>
@@ -257,9 +257,10 @@
</div>
<div className="seed-item-size">{seed.size || '未知'}</div>
<div className="seed-item-upload-time">{seed.upload_time?.split('T')[0] || '未知'}</div>
- <div className="seed-item-downloads">{seed.downloads ?? 0} 次下载</div>
+ <div className="seed-item-downloads">{seed.leechers ?? 0} 次下载</div>
<div className="seed-item-actions" onClick={e => e.stopPropagation()}>
- <button
+ <AuthButton
+ roles={["cookie", "chocolate", "ice-cream"]}
className="btn-primary"
onClick={e => {
e.preventDefault();
@@ -285,8 +286,9 @@
}}
>
下载
- </button>
- <button
+ </AuthButton>
+ <AuthButton
+ roles={["cookie", "chocolate", "ice-cream"]}
className="btn-outline"
onClick={async (e) => {
e.preventDefault();
@@ -314,7 +316,7 @@
}}
>
收藏
- </button>
+ </AuthButton>
</div>
</div>
</Link>
diff --git a/src/pages/UserCenter/UserProfileBase.jsx b/src/pages/UserCenter/UserProfileBase.jsx
index 791baca..c90cb63 100644
--- a/src/pages/UserCenter/UserProfileBase.jsx
+++ b/src/pages/UserCenter/UserProfileBase.jsx
@@ -9,7 +9,7 @@
const DEFAULT_AVATAR_URL = `${process.env.PUBLIC_URL}/default-avatar.png`;
const UserProfileBase = ({ onLoadExperienceInfo }) => {
- const { user, loading, logout } = useUser();
+ const { user, loading, logout ,saveUser} = useUser();
const [userProfile, setUserProfile] = useState(null);
const [error, setError] = useState(null);
@@ -66,34 +66,44 @@
fetchUserProfile();
}, [user, loading, onLoadExperienceInfo]);
- const handleAvatarUpload = async (e) => {
- const file = e.target.files[0];
- if (!file) return;
+const handleAvatarUpload = async (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
- const formData = new FormData();
- formData.append('file', file);
+ 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' } }
- );
+ 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}`,
- }));
- toast.success('头像上传成功');
- } else {
- toast.success('头像上传成功,但未返回新头像地址');
- }
- } catch (err) {
- console.error('上传失败:', err);
- toast.error('头像上传失败,请重试');
+ if (data?.avatarUrl) {
+ // 加时间戳避免缓存
+ const newAvatarUrl = `${process.env.REACT_APP_AVATAR_BASE_URL}${data.avatarUrl}?t=${Date.now()}`;
+
+ setUserProfile((prev) => ({
+ ...prev,
+ avatarUrl: newAvatarUrl,
+ }));
+
+ saveUser({
+ ...user,
+ avatarUrl: newAvatarUrl,
+ });
+
+ toast.success('头像上传成功');
+ } else {
+ toast.success('头像上传成功,但未返回新头像地址');
}
- };
+ } catch (err) {
+ console.error('上传失败:', err);
+ toast.error('头像上传失败,请重试');
+ }
+};
+
const handleLogout = () => {
logout();