178 lines
5.4 KiB
TypeScript
178 lines
5.4 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Table, Tag, Empty, Spin, Typography, Button, Pagination } from 'antd';
|
|
import { BellOutlined, SettingOutlined, WalletOutlined, GiftOutlined } from '@ant-design/icons';
|
|
import { getNotifications, markNotificationRead, getUnreadCount } from '../api';
|
|
|
|
const MessagesPage: React.FC = () => {
|
|
const [loading, setLoading] = useState(true);
|
|
const [notifications, setNotifications] = useState<any[]>([]);
|
|
const [unreadCount, setUnreadCount] = useState(0);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(1);
|
|
const [pageSize] = useState(10);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
loadUnreadCount();
|
|
}, [page]);
|
|
|
|
const loadData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await getNotifications(page, pageSize);
|
|
setNotifications(data.items || []);
|
|
setTotal(data.total || 0);
|
|
} catch {
|
|
setNotifications([]);
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const loadUnreadCount = async () => {
|
|
try {
|
|
const count = await getUnreadCount();
|
|
setUnreadCount(count);
|
|
} catch {
|
|
setUnreadCount(0);
|
|
}
|
|
};
|
|
|
|
const handleMarkRead = async (id: string) => {
|
|
try {
|
|
await markNotificationRead(id);
|
|
loadData();
|
|
loadUnreadCount();
|
|
} catch { /* ignore */ }
|
|
};
|
|
|
|
const handlePageChange = (newPage: number) => {
|
|
setPage(newPage);
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: '类型',
|
|
dataIndex: 'type',
|
|
key: 'type',
|
|
width: 100,
|
|
render: (text: string) => {
|
|
const typeConfig: Record<string, { color: string; label: string; bg: string; icon: React.ReactNode }> = {
|
|
system: { color: '#6366f1', label: '系统', bg: 'rgba(99,102,241,0.1)', icon: <SettingOutlined style={{ fontSize: 14 }} /> },
|
|
credit: { color: '#f59e0b', label: '积分', bg: 'rgba(245,158,11,0.1)', icon: <WalletOutlined style={{ fontSize: 14 }} /> },
|
|
promo: { color: '#8b5cf6', label: '活动', bg: 'rgba(139,92,246,0.1)', icon: <GiftOutlined style={{ fontSize: 14 }} /> },
|
|
};
|
|
const config = typeConfig[text] || typeConfig.system;
|
|
return (
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
<span style={{ color: config.color }}>{config.icon}</span>
|
|
<span style={{ fontSize: 13, color: config.color, fontWeight: 500 }}>{config.label}</span>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: '标题',
|
|
dataIndex: 'title',
|
|
key: 'title',
|
|
ellipsis: true,
|
|
},
|
|
{
|
|
title: '内容',
|
|
dataIndex: 'content',
|
|
key: 'content',
|
|
ellipsis: true,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'isRead',
|
|
key: 'isRead',
|
|
width: 80,
|
|
render: (text: boolean, record: any) => {
|
|
const isRead = text ?? record.is_read;
|
|
return isRead ? (
|
|
<Tag color="gray" style={{ background: 'rgba(148,163,184,0.1)' }}>已读</Tag>
|
|
) : (
|
|
<Tag color="purple" style={{ background: 'rgba(139,92,246,0.1)' }}>未读</Tag>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
dataIndex: 'createdAt',
|
|
key: 'createdAt',
|
|
width: 160,
|
|
render: (text: string) => {
|
|
if (!text) return '-';
|
|
const date = new Date(text);
|
|
const now = new Date();
|
|
const diff = now.getTime() - date.getTime();
|
|
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
if (hours < 1) return '刚刚';
|
|
if (hours < 24) return `${hours}小时前`;
|
|
const days = Math.floor(hours / 24);
|
|
if (days < 7) return `${days}天前`;
|
|
return date.toLocaleDateString('zh-CN');
|
|
},
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: 100,
|
|
render: (_, record: any) => {
|
|
const isRead = record.isRead ?? record.is_read;
|
|
return !isRead ? (
|
|
<Button
|
|
type="text"
|
|
size="small"
|
|
onClick={() => handleMarkRead(record.id)}
|
|
style={{ color: '#6366f1' }}
|
|
>
|
|
标记已读
|
|
</Button>
|
|
) : null;
|
|
},
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<div>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
|
<BellOutlined style={{ color: '#6366f1', fontSize: 16 }} />
|
|
<Typography.Text strong style={{ fontSize: 16 }}>消息中心</Typography.Text>
|
|
<span style={{ color: '#94a3b8', fontSize: 13 }}>未读 {unreadCount} 条</span>
|
|
</div>
|
|
<div style={{ borderRadius: 16, background: '#fff', border: '1px solid #f0f0f5', overflow: 'hidden' }}>
|
|
<Spin spinning={loading}>
|
|
{notifications.length === 0 ? (
|
|
<Empty
|
|
description={<span style={{ color: '#94a3b8' }}>暂无消息</span>}
|
|
/>
|
|
) : (
|
|
<>
|
|
<Table
|
|
dataSource={notifications}
|
|
columns={columns}
|
|
rowKey="id"
|
|
pagination={false}
|
|
bordered={false}
|
|
/>
|
|
<div style={{ padding: '16px', textAlign: 'right' }}>
|
|
<Pagination
|
|
current={page}
|
|
pageSize={pageSize}
|
|
total={total}
|
|
onChange={handlePageChange}
|
|
size="small"
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
</Spin>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MessagesPage; |