import { useState, useCallback, useMemo } from 'react'; import { Table, Button, Space, Modal, Form, Input, InputNumber, Select, Tag, Badge, message, Switch, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, } from '@ant-design/icons'; import { pointsApi, type PointsRule, type CreatePointsRuleReq, } from '../../api/health/points'; import { AuthButton } from '../../components/AuthButton'; import { PageContainer } from '../../components/PageContainer'; import { formatDateTime } from '../../utils/format'; /** 事件类型映射 */ const EVENT_TYPES: Record = { checkin: '每日打卡', data_report: '数据上报', lab_upload: '化验上传', event_checkin: '活动签到', consultation_complete: '咨询完成', followup_complete: '随访完成', }; /** 事件类型选项 */ const EVENT_TYPE_OPTIONS = Object.entries(EVENT_TYPES).map(([value, label]) => ({ value, label, })); interface RuleFilters { event_type: string | undefined; is_active: string | undefined; } export default function PointsRuleList() { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [filters, setFilters] = useState({ event_type: undefined, is_active: undefined, }); const [form] = Form.useForm(); // ---- 数据获取 ---- const fetchData = useCallback(async () => { setLoading(true); try { const result = await pointsApi.listRules(); let filtered = result; if (filters.event_type) { filtered = filtered.filter((r) => r.event_type === filters.event_type); } if (filters.is_active !== undefined) { const isActive = filters.is_active === 'true'; filtered = filtered.filter((r) => r.is_active === isActive); } setData(filtered); } catch { message.error('加载积分规则失败'); } finally { setLoading(false); } }, [filters]); // Initial fetch const [hasFetched, setHasFetched] = useState(false); if (!hasFetched) { setHasFetched(true); fetchData(); } // ---- 新建 / 编辑 ---- const openCreate = () => { setEditing(null); form.resetFields(); setModalOpen(true); }; const openEdit = (record: PointsRule) => { setEditing(record); form.setFieldsValue({ event_type: record.event_type, name: record.name, description: record.description, points_value: record.points_value, daily_cap: record.daily_cap, streak_7d_bonus: record.streak_7d_bonus, streak_14d_bonus: record.streak_14d_bonus, streak_30d_bonus: record.streak_30d_bonus, }); setModalOpen(true); }; const handleSubmit = async (values: { event_type: string; name: string; description?: string; points_value: number; daily_cap?: number; streak_7d_bonus?: number; streak_14d_bonus?: number; streak_30d_bonus?: number; }) => { try { if (editing) { await pointsApi.updateRule(editing.id, { ...values, version: editing.version, }); } else { const req: CreatePointsRuleReq = { event_type: values.event_type, name: values.name, description: values.description, points_value: values.points_value, daily_cap: values.daily_cap, streak_7d_bonus: values.streak_7d_bonus, streak_14d_bonus: values.streak_14d_bonus, streak_30d_bonus: values.streak_30d_bonus, }; await pointsApi.createRule(req); } message.success(editing ? '更新成功' : '创建成功'); setModalOpen(false); form.resetFields(); fetchData(); } catch { message.error(editing ? '更新失败' : '创建失败'); } }; // ---- 切换启用状态 ---- const handleToggleActive = async (record: PointsRule) => { try { await pointsApi.updateRule(record.id, { is_active: !record.is_active, version: record.version, }); message.success(record.is_active ? '已停用' : '已启用'); fetchData(); } catch { message.error('操作失败'); } }; // ---- 删除 ---- const handleDelete = (record: PointsRule) => { Modal.confirm({ title: '确认删除', content: `确定要删除规则「${record.name}」吗?`, okType: 'danger', onOk: async () => { try { await pointsApi.deleteRule(record.id, record.version); message.success('删除成功'); fetchData(); } catch { message.error('删除失败'); } }, }); }; const resetFilters = () => { setFilters({ event_type: undefined, is_active: undefined }); }; // ---- 列定义 ---- const columns = useMemo(() => [ { title: '规则名称', dataIndex: 'name', key: 'name', width: 140, }, { title: '事件类型', dataIndex: 'event_type', key: 'event_type', width: 120, render: (val: string) => ( {EVENT_TYPES[val] || val} ), }, { title: '积分值', dataIndex: 'points_value', key: 'points_value', width: 80, render: (val: number) => +{val}, }, { title: '每日上限', dataIndex: 'daily_cap', key: 'daily_cap', width: 90, render: (val: number) => (val === -1 ? '无限' : val), }, { title: '7日奖励', dataIndex: 'streak_7d_bonus', key: 'streak_7d_bonus', width: 90, render: (val: number) => (val > 0 ? +{val} : '-'), }, { title: '14日奖励', dataIndex: 'streak_14d_bonus', key: 'streak_14d_bonus', width: 90, render: (val: number) => (val > 0 ? +{val} : '-'), }, { title: '30日奖励', dataIndex: 'streak_30d_bonus', key: 'streak_30d_bonus', width: 90, render: (val: number) => (val > 0 ? +{val} : '-'), }, { title: '状态', dataIndex: 'is_active', key: 'is_active', width: 80, render: (val: boolean) => ( ), }, { title: '更新时间', dataIndex: 'updated_at', key: 'updated_at', width: 170, render: (val: string) => formatDateTime(val), }, { title: '操作', key: 'action', width: 200, render: (_: unknown, record: PointsRule) => ( handleToggleActive(record)} /> ), }, ], [openEdit, handleToggleActive, handleDelete]); return ( setFilters((f) => ({ ...f, is_active: val }))} options={[ { value: 'true', label: '启用' }, { value: 'false', label: '停用' }, ]} allowClear style={{ width: 120 }} /> } onResetFilters={resetFilters} actions={ } > {/* 新建 / 编辑弹窗 */} { setModalOpen(false); form.resetFields(); }} onOk={() => form.submit()} destroyOnHidden width={560} >