更新路由守卫
Change-Id: Iddd1d006202a03e8a97e3a90d64d9a43c5d2cb78
diff --git a/Merge/front/src/components/Admin.js b/Merge/front/src/components/Admin.js
index 2d97495..f73e239 100644
--- a/Merge/front/src/components/Admin.js
+++ b/Merge/front/src/components/Admin.js
@@ -1,21 +1,32 @@
-import 'antd/dist/antd.css';
-import React, { useState, useEffect, useMemo, useCallback } from 'react';
-import { Layout, Tabs, Input, List, Card, Button, Tag, Spin, Typography, Divider } from 'antd';
-import '../style/Admin.css';
-import { fetchPosts, approvePost, rejectPost } from '../api/posts_trm';
+// src/components/Admin.jsx
+import React, { useState, useEffect, useCallback } from 'react'
+import { useParams } from 'react-router-dom'
+import 'antd/dist/antd.css'
+import {
+ Layout,
+ Tabs,
+ Input,
+ List,
+ Card,
+ Button,
+ Tag,
+ Spin,
+ Typography,
+ Divider
+} from 'antd'
+import '../style/Admin.css'
+import { fetchPosts, approvePost, rejectPost } from '../api/posts_trm'
-export default function Admin() {
- const ADMIN_USER_ID = 2;
- const [posts, setPosts] = useState([]);
- const [loading, setLoading] = useState(true);
- const [hasPermission, setHasPermission] = useState(true);
- const [activeTab, setActiveTab] = useState('all');
- const [selectedPost, setSelectedPost] = useState(null);
- const [searchTerm, setSearchTerm] = useState('');
-
- // 新增:拖拽相关状态
- const [leftPanelWidth, setLeftPanelWidth] = useState(300);
- const [isResizing, setIsResizing] = useState(false);
+export default function AdminPage() {
+ const { userId } = useParams() // ← 从路由拿到
+ const [posts, setPosts] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [hasPermission, setHasPermission] = useState(true)
+ const [activeTab, setActiveTab] = useState('all')
+ const [selectedPost, setSelectedPost] = useState(null)
+ const [searchTerm, setSearchTerm] = useState('')
+ const [leftPanelWidth, setLeftPanelWidth] = useState(300)
+ const [isResizing, setIsResizing] = useState(false)
const statusColors = {
draft: 'orange',
@@ -23,12 +34,12 @@
published: 'green',
deleted: 'gray',
rejected: 'red'
- };
+ }
useEffect(() => {
async function load() {
try {
- const list = await fetchPosts(ADMIN_USER_ID)
+ const list = await fetchPosts(userId) // ← 传入 userId
setPosts(list)
} catch (e) {
if (e.message === 'Unauthorized') {
@@ -41,10 +52,9 @@
}
}
load()
- }, [])
+ }, [userId])
- // 过滤并排序
- const sortedPosts = useMemo(() => {
+ const sortedPosts = React.useMemo(() => {
return [...posts].sort((a, b) => {
if (a.status === 'pending' && b.status !== 'pending') return -1
if (b.status === 'pending' && a.status !== 'pending') return 1
@@ -52,18 +62,10 @@
})
}, [posts])
- // 调整:根据 activeTab 及搜索关键词过滤
- const filteredPosts = useMemo(() => {
- let list
- switch (activeTab) {
- case 'pending':
- list = sortedPosts.filter(p => p.status === 'pending'); break
- case 'published':
- list = sortedPosts.filter(p => p.status === 'published'); break
- case 'rejected':
- list = sortedPosts.filter(p => p.status === 'rejected'); break
- default:
- list = sortedPosts
+ const filteredPosts = React.useMemo(() => {
+ let list = sortedPosts
+ if (activeTab !== 'all') {
+ list = sortedPosts.filter(p => p.status === activeTab)
}
return list.filter(p =>
p.title.toLowerCase().includes(searchTerm.toLowerCase())
@@ -71,84 +73,101 @@
}, [sortedPosts, activeTab, searchTerm])
const handleApprove = async id => {
- await approvePost(id, ADMIN_USER_ID)
- setPosts(ps => ps.map(x => x.id === id ? { ...x, status: 'published' } : x))
- // 同步更新选中的帖子状态
+ await approvePost(id, userId) // ← 传入 userId
+ setPosts(ps =>
+ ps.map(x => (x.id === id ? { ...x, status: 'published' } : x))
+ )
if (selectedPost?.id === id) {
- setSelectedPost(prev => ({ ...prev, status: 'published' }));
+ setSelectedPost(prev => ({ ...prev, status: 'published' }))
}
}
- const handleReject = async id => {
- await rejectPost(id, ADMIN_USER_ID)
- setPosts(ps => ps.map(x => x.id === id ? { ...x, status: 'rejected' } : x))
- // 同步更新选中的帖子状态
- if (selectedPost?.id === id) {
- setSelectedPost(prev => ({ ...prev, status: 'rejected' }));
- }
- }
- const handleSelect = post => setSelectedPost(post)
- // 修复:拖拽处理函数
- const handleMouseMove = useCallback((e) => {
- if (!isResizing) return;
-
- const newWidth = e.clientX;
- const minWidth = 200;
- const maxWidth = window.innerWidth - 300;
-
- if (newWidth >= minWidth && newWidth <= maxWidth) {
- setLeftPanelWidth(newWidth);
+ const handleReject = async id => {
+ await rejectPost(id, userId) // ← 传入 userId
+ setPosts(ps =>
+ ps.map(x => (x.id === id ? { ...x, status: 'rejected' } : x))
+ )
+ if (selectedPost?.id === id) {
+ setSelectedPost(prev => ({ ...prev, status: 'rejected' }))
}
- }, [isResizing]);
+ }
+
+ const handleSelect = post => setSelectedPost(post)
+
+ const handleMouseMove = useCallback(
+ e => {
+ if (!isResizing) return
+ const newWidth = e.clientX
+ const minWidth = 200
+ const maxWidth = window.innerWidth - 300
+ if (newWidth >= minWidth && newWidth <= maxWidth) {
+ setLeftPanelWidth(newWidth)
+ }
+ },
+ [isResizing]
+ )
const handleMouseUp = useCallback(() => {
- setIsResizing(false);
- document.removeEventListener('mousemove', handleMouseMove);
- document.removeEventListener('mouseup', handleMouseUp);
- document.body.style.cursor = '';
- document.body.style.userSelect = '';
- }, [handleMouseMove]);
+ setIsResizing(false)
+ document.removeEventListener('mousemove', handleMouseMove)
+ document.removeEventListener('mouseup', handleMouseUp)
+ document.body.style.cursor = ''
+ document.body.style.userSelect = ''
+ }, [handleMouseMove])
- const handleMouseDown = useCallback((e) => {
- e.preventDefault();
- setIsResizing(true);
- document.addEventListener('mousemove', handleMouseMove);
- document.addEventListener('mouseup', handleMouseUp);
- document.body.style.cursor = 'col-resize';
- document.body.style.userSelect = 'none';
- }, [handleMouseMove, handleMouseUp]);
+ const handleMouseDown = useCallback(
+ e => {
+ e.preventDefault()
+ setIsResizing(true)
+ document.addEventListener('mousemove', handleMouseMove)
+ document.addEventListener('mouseup', handleMouseUp)
+ document.body.style.cursor = 'col-resize'
+ document.body.style.userSelect = 'none'
+ },
+ [handleMouseMove, handleMouseUp]
+ )
- // 新增:组件卸载时清理事件监听器
useEffect(() => {
return () => {
- document.removeEventListener('mousemove', handleMouseMove);
- document.removeEventListener('mouseup', handleMouseUp);
- document.body.style.cursor = '';
- document.body.style.userSelect = '';
- };
- }, [handleMouseMove, handleMouseUp]);
+ document.removeEventListener('mousemove', handleMouseMove)
+ document.removeEventListener('mouseup', handleMouseUp)
+ document.body.style.cursor = ''
+ document.body.style.userSelect = ''
+ }
+ }, [handleMouseMove, handleMouseUp])
- if (loading) return <Spin spinning tip="加载中…" style={{ width: '100%', marginTop: 100 }} />;
- if (!hasPermission) return <div style={{ textAlign: 'center', marginTop: 100 }}>权限不足</div>;
+ if (loading)
+ return (
+ <Spin
+ spinning
+ tip="加载中…"
+ style={{ width: '100%', marginTop: 100 }}
+ />
+ )
+ if (!hasPermission)
+ return (
+ <div style={{ textAlign: 'center', marginTop: 100 }}>
+ 权限不足
+ </div>
+ )
- const { Content } = Layout;
- const { TabPane } = Tabs;
- const { Title, Text } = Typography;
+ const { Content } = Layout
+ const { TabPane } = Tabs
+ const { Title, Text } = Typography
return (
<div style={{ height: '100vh', display: 'flex' }}>
- {/* 左侧面板 */}
- <div
- style={{
+ <div
+ style={{
width: leftPanelWidth,
- background: '#fff',
+ background: '#fff',
padding: 16,
borderRight: '1px solid #f0f0f0',
overflow: 'hidden'
}}
>
<div style={{ marginBottom: 24 }}>
- <Title level={3}>小红书</Title>
+ <Title level={3}>小红书 管理</Title>
<Input.Search
placeholder="搜索帖子标题..."
value={searchTerm}
@@ -156,26 +175,38 @@
enterButton
/>
</div>
- <Tabs activeKey={activeTab} onChange={key => { setActiveTab(key); setSelectedPost(null); }}>
+ <Tabs
+ activeKey={activeTab}
+ onChange={key => {
+ setActiveTab(key)
+ setSelectedPost(null)
+ }}
+ >
<TabPane tab="全部" key="all" />
<TabPane tab="待审核" key="pending" />
<TabPane tab="已通过" key="published" />
<TabPane tab="已驳回" key="rejected" />
</Tabs>
- <div style={{ height: 'calc(100vh - 200px)', overflow: 'auto' }}>
+ <div
+ style={{
+ height: 'calc(100vh - 200px)',
+ overflow: 'auto'
+ }}
+ >
<List
dataSource={filteredPosts}
pagination={{
pageSize: 5,
showSizeChanger: true,
- pageSizeOptions: ['5','10','20'],
+ pageSizeOptions: ['5', '10', '20'],
onChange: () => setSelectedPost(null)
}}
renderItem={p => (
<List.Item
key={p.id}
style={{
- background: selectedPost?.id === p.id ? '#e6f7ff' : '',
+ background:
+ selectedPost?.id === p.id ? '#e6f7ff' : '',
cursor: 'pointer',
marginBottom: 8
}}
@@ -187,32 +218,38 @@
<img
src={p.thumbnail}
alt=""
- style={{ width: 64, height: 64, objectFit: 'cover' }}
+ style={{
+ width: 64,
+ height: 64,
+ objectFit: 'cover'
+ }}
/>
)
}
title={p.title}
- description={`${p.createdAt} · ${p.author} · ${p.likes || 0}赞`}
+ description={`${p.createdAt} · ${p.author} · ${
+ p.likes || 0
+ }赞`}
/>
- <Tag color={statusColors[p.status]}>{p.status}</Tag>
+ <Tag color={statusColors[p.status]}>
+ {p.status}
+ </Tag>
</List.Item>
)}
/>
</div>
</div>
- {/* 拖拽分割条 */}
<div
style={{
width: 5,
cursor: 'col-resize',
background: isResizing ? '#1890ff' : '#f0f0f0',
- transition: isResizing ? 'none' : 'background-color 0.2s',
position: 'relative',
flexShrink: 0
}}
onMouseDown={handleMouseDown}
- onSelectStart={(e) => e.preventDefault()}
+ onSelectStart={e => e.preventDefault()}
>
<div
style={{
@@ -228,41 +265,111 @@
/>
</div>
- {/* 右侧内容区域 */}
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
- <Content style={{ padding: 24, background: '#fff', overflow: 'auto' }}>
+ <div
+ style={{
+ flex: 1,
+ display: 'flex',
+ flexDirection: 'column'
+ }}
+ >
+ <Content
+ style={{
+ padding: 24,
+ background: '#fff',
+ overflow: 'auto'
+ }}
+ >
{selectedPost ? (
<Card
- cover={selectedPost.image && <img alt="cover" src={selectedPost.image} />}
+ cover={
+ selectedPost.image && (
+ <img
+ alt="cover"
+ src={selectedPost.image}
+ />
+ )
+ }
title={selectedPost.title}
extra={
<div>
{selectedPost.status === 'pending' && (
<>
- <Button type="primary" onClick={() => handleApprove(selectedPost.id)}>通过</Button>
- <Button danger onClick={() => handleReject(selectedPost.id)}>驳回</Button>
+ <Button
+ type="primary"
+ onClick={() =>
+ handleApprove(selectedPost.id)
+ }
+ >
+ 通过
+ </Button>
+ <Button
+ danger
+ onClick={() =>
+ handleReject(selectedPost.id)
+ }
+ >
+ 驳回
+ </Button>
</>
)}
{selectedPost.status === 'published' && (
- <Button danger onClick={() => handleReject(selectedPost.id)}>驳回</Button>
+ <Button
+ danger
+ onClick={() =>
+ handleReject(selectedPost.id)
+ }
+ >
+ 驳回
+ </Button>
)}
{selectedPost.status === 'rejected' && (
<>
- <Button onClick={() => {
- setPosts(ps => ps.map(x => x.id === selectedPost.id ? { ...x, status: 'pending' } : x));
- setSelectedPost(prev => ({ ...prev, status: 'pending' }));
- }}>恢复待审</Button>
- <Button onClick={() => {
- setPosts(ps => ps.map(x => x.id === selectedPost.id ? { ...x, status: 'published' } : x));
- setSelectedPost(prev => ({ ...prev, status: 'published' }));
- }}>恢复已发</Button>
+ <Button
+ onClick={() => {
+ setPosts(ps =>
+ ps.map(x =>
+ x.id === selectedPost.id
+ ? { ...x, status: 'pending' }
+ : x
+ )
+ )
+ setSelectedPost(prev => ({
+ ...prev,
+ status: 'pending'
+ }))
+ }}
+ >
+ 恢复待审
+ </Button>
+ <Button
+ onClick={() => {
+ setPosts(ps =>
+ ps.map(x =>
+ x.id === selectedPost.id
+ ? {
+ ...x,
+ status: 'published'
+ }
+ : x
+ )
+ )
+ setSelectedPost(prev => ({
+ ...prev,
+ status: 'published'
+ }))
+ }}
+ >
+ 恢复已发
+ </Button>
</>
)}
</div>
}
>
<Text type="secondary">
- {`${selectedPost.createdAt} · ${selectedPost.author} · ${selectedPost.likes || 0}赞`}
+ {`${selectedPost.createdAt} · ${selectedPost.author} · ${
+ selectedPost.likes || 0
+ }赞`}
</Text>
<Divider />
<p>{selectedPost.content}</p>
@@ -274,10 +381,12 @@
</ul>
</Card>
) : (
- <Text type="secondary">请选择左侧列表中的帖子查看详情</Text>
+ <Text type="secondary">
+ 请选择左侧列表中的帖子查看详情
+ </Text>
)}
</Content>
</div>
</div>
- );
+)
}