blob: f73e239f98e9dd35d536cc03cda65d544c2554e7 [file] [log] [blame]
// 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 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',
pending: 'blue',
published: 'green',
deleted: 'gray',
rejected: 'red'
}
useEffect(() => {
async function load() {
try {
const list = await fetchPosts(userId) // ← 传入 userId
setPosts(list)
} catch (e) {
if (e.message === 'Unauthorized') {
setHasPermission(false)
} else {
console.error(e)
}
} finally {
setLoading(false)
}
}
load()
}, [userId])
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
return 0
})
}, [posts])
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())
)
}, [sortedPosts, activeTab, searchTerm])
const handleApprove = async id => {
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' }))
}
}
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' }))
}
}
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])
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])
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
return (
<div style={{ height: '100vh', display: 'flex' }}>
<div
style={{
width: leftPanelWidth,
background: '#fff',
padding: 16,
borderRight: '1px solid #f0f0f0',
overflow: 'hidden'
}}
>
<div style={{ marginBottom: 24 }}>
<Title level={3}>小红书 管理</Title>
<Input.Search
placeholder="搜索帖子标题..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
enterButton
/>
</div>
<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'
}}
>
<List
dataSource={filteredPosts}
pagination={{
pageSize: 5,
showSizeChanger: true,
pageSizeOptions: ['5', '10', '20'],
onChange: () => setSelectedPost(null)
}}
renderItem={p => (
<List.Item
key={p.id}
style={{
background:
selectedPost?.id === p.id ? '#e6f7ff' : '',
cursor: 'pointer',
marginBottom: 8
}}
onClick={() => handleSelect(p)}
>
<List.Item.Meta
avatar={
p.thumbnail && (
<img
src={p.thumbnail}
alt=""
style={{
width: 64,
height: 64,
objectFit: 'cover'
}}
/>
)
}
title={p.title}
description={`${p.createdAt} · ${p.author} · ${
p.likes || 0
}赞`}
/>
<Tag color={statusColors[p.status]}>
{p.status}
</Tag>
</List.Item>
)}
/>
</div>
</div>
<div
style={{
width: 5,
cursor: 'col-resize',
background: isResizing ? '#1890ff' : '#f0f0f0',
position: 'relative',
flexShrink: 0
}}
onMouseDown={handleMouseDown}
onSelectStart={e => e.preventDefault()}
>
<div
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 2,
height: 20,
background: '#999',
borderRadius: 1
}}
/>
</div>
<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}
/>
)
}
title={selectedPost.title}
extra={
<div>
{selectedPost.status === 'pending' && (
<>
<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>
)}
{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>
</>
)}
</div>
}
>
<Text type="secondary">
{`${selectedPost.createdAt} · ${selectedPost.author} · ${
selectedPost.likes || 0
}赞`}
</Text>
<Divider />
<p>{selectedPost.content}</p>
<Divider />
<Title level={4}>合规性指引</Title>
<ul>
<li>不含违法违规内容</li>
<li>不侵害他人合法权益</li>
</ul>
</Card>
) : (
<Text type="secondary">
请选择左侧列表中的帖子查看详情
</Text>
)}
</Content>
</div>
</div>
)
}