Files
video-gen/video-gen-app/src/pages/MessagesPage.tsx
T
2026-06-15 17:50:47 +08:00

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;