import { Alert, Button, Form, Input, InputNumber, Modal, Select, Space, Switch, Table, Tabs, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; import { Download, Plus, RefreshCw, Search, SlidersHorizontal } from 'lucide-react'; import { useEffect, useState } from 'react'; import { PageHeader, Surface } from '@/components/Page'; import { apiRequest, downloadFile, jsonOptions } from '@/services/api'; import type { ChatSummary, Paged, PointAccount, PointRules, PointTransaction, } from '@/types'; import { pointSourceLabel, pointSourceOptions } from '@/utils/labels'; import { userDisplayName } from '@/utils/users'; export default function PointsPage() { const [chats, setChats] = useState([]); const [chatId, setChatId] = useState(); const [accounts, setAccounts] = useState>({ items: [], total: 0, page: 1, page_size: 20 }); const [transactions, setTransactions] = useState>({ items: [], total: 0, page: 1, page_size: 20 }); const [query, setQuery] = useState(''); const [source, setSource] = useState(); const [loading, setLoading] = useState(false); const [adjustOpen, setAdjustOpen] = useState(false); const [adjustForm] = Form.useForm(); useEffect(() => { apiRequest>('/chats?page_size=100').then((result) => { setChats(result.items); if (result.items.length) setChatId(result.items[0].chat_id); }); }, []); const loadAccounts = async (page = 1) => { if (!chatId) return; setLoading(true); try { const params = new URLSearchParams({ chat_id: chatId, page: String(page), page_size: '20', query }); setAccounts(await apiRequest>(`/points/accounts?${params}`)); } finally { setLoading(false); } }; const loadTransactions = async (page = 1) => { if (!chatId) return; setLoading(true); try { const params = new URLSearchParams({ chat_id: chatId, page: String(page), page_size: '20' }); if (source) params.set('source', source); setTransactions(await apiRequest>(`/points/transactions?${params}`)); } finally { setLoading(false); } }; useEffect(() => { void loadAccounts(1); void loadTransactions(1); }, [chatId, source]); const accountColumns: ColumnsType = [ { title: '成员', key: 'member', minWidth: 190, render: (_, item) => ( {userDisplayName(item)} {item.user_id} ), }, { title: '余额', dataIndex: 'balance', width: 110, sorter: (a, b) => a.balance - b.balance, render: (value) => {value} }, { title: '累计获得', dataIndex: 'lifetime_earned', width: 120, responsive: ['md'], className: 'numeric' }, { title: '累计消耗', dataIndex: 'lifetime_spent', width: 120, responsive: ['md'], className: 'numeric' }, { title: '更新时间', dataIndex: 'updated_at', width: 160, responsive: ['lg'], render: (value) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-') }, { title: '操作', width: 90, fixed: 'right', render: (_, item) => ( ), }, ]; const transactionColumns: ColumnsType = [ { title: '时间', dataIndex: 'created_at', width: 165, render: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss') }, { title: '成员', key: 'member', width: 190, render: (_, item) => ( {userDisplayName(item)} {item.user_id} ), }, { title: '变化', dataIndex: 'delta', width: 90, render: (value: number) => ( {value > 0 ? '+' : ''}{value} ), }, { title: '余额', dataIndex: 'balance_after', width: 90, className: 'numeric' }, { title: '来源', dataIndex: 'source', width: 180, render: (value) => {pointSourceLabel(value)} }, { title: '原因', dataIndex: 'reason', minWidth: 220, ellipsis: true }, { title: '关联对象', dataIndex: 'reference_id', width: 150, responsive: ['lg'], render: (value) => value || '-' }, ]; const selectedChat = chats.find((chat) => chat.chat_id === chatId); return ( <> } />
setQuery(event.target.value)} onPressEnter={() => void loadAccounts(1)} prefix={} placeholder="用户 ID、@username 或名称" style={{ width: 'min(340px, 100%)' }} />
void loadAccounts(pagination.current || 1)} scroll={{ x: 850 }} /> ), }, { key: 'transactions', label: `流水 (${transactions.total})`, children: ( <>
void loadTransactions(pagination.current || 1)} scroll={{ x: 1050 }} /> ), }, { key: 'rules', label: '自动积分规则', children: , }, ]} /> )} setAdjustOpen(false)} onOk={() => adjustForm.submit()} destroyOnHidden >
{ Modal.confirm({ title: '确认写入积分流水?', content: '历史流水不可修改或删除。', okText: '确认写入', okButtonProps: { danger: values.operation === 'deduct' || values.operation === 'set' }, onOk: async () => { await apiRequest( '/points/adjustments', jsonOptions('POST', { ...values, chat_id: chatId, request_id: crypto.randomUUID(), confirm: true, }), ); message.success('积分已调整并写入流水'); setAdjustOpen(false); adjustForm.resetFields(); await loadAccounts(1); await loadTransactions(1); }, }); }} >
); } function RuleGroup({ title, enabled, fields }: { title: string; enabled: string; fields: string[][] }) { return (

{title}

{fields.map(([name, label]) => ( ))}
); }