import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Form, Input, Modal, Select, Space, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { PROMO_CODE_SCENE_LABELS, PROMO_CODE_STATUS_LABELS, promoConversion, type PromoCodeItem, type PromoCodeScene, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; type Row = PromoCodeItem; type SceneOption = { value: PromoCodeScene; label: string }; async function downloadQrcode(url: string, filename: string) { try { const res = await fetch(url); const blob = await res.blob(); const objectUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = objectUrl; a.download = filename; a.click(); URL.revokeObjectURL(objectUrl); } catch { window.open(url, '_blank'); } } export default function PromoCodesPage() { const navigate = useNavigate(); const [filterForm] = Form.useForm(); const [createForm] = Form.useForm(); const [filters, setFilters] = useState>({}); const [scenes, setScenes] = useState( Object.entries(PROMO_CODE_SCENE_LABELS).map(([value, label]) => ({ value: value as PromoCodeScene, label, })), ); const [createOpen, setCreateOpen] = useState(false); const [creating, setCreating] = useState(false); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/promo-codes', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.code) qs.set('code', filters.code); if (filters.status) qs.set('status', filters.status); if (filters.scene) qs.set('scene', filters.scene); return qs; }, [filters], ); async function loadScenes() { try { const list = await request('/admin/promo-codes/scenes'); if (list.length) setScenes(list); } catch { /* 使用本地默认场景 */ } } useEffect(() => { void loadScenes(); }, []); const columns: ColumnsType = [ { title: '名称', dataIndex: 'name', width: 160, ellipsis: true }, { title: '码值', dataIndex: 'code', width: 110 }, { title: '场景', dataIndex: 'scene', width: 110, render: (s: PromoCodeScene) => PROMO_CODE_SCENE_LABELS[s] || s, }, { title: '状态', dataIndex: 'status', width: 90, render: (s) => ( {PROMO_CODE_STATUS_LABELS[s as keyof typeof PROMO_CODE_STATUS_LABELS] || s} ), }, { title: '扫码', dataIndex: 'scanCount', width: 70 }, { title: '订单', dataIndex: 'orderCount', width: 70 }, { title: '转化率', width: 90, render: (_, row) => promoConversion(row.scanCount, row.orderCount), }, { title: '渠道负责人', width: 120, render: (_, row) => row.ownerUser?.userNo || row.ownerUser?.phone || '—', }, { title: '创建', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 220, fixed: 'right', render: (_, row) => ( ), }, ]; async function handleCreate(values: Record) { setCreating(true); try { const created = await request('/admin/promo-codes', { method: 'POST', body: JSON.stringify({ name: values.name, code: values.code?.trim() || undefined, scene: values.scene, ownerUserId: values.ownerUserId?.trim() || undefined, remark: values.remark?.trim() || undefined, page: values.page?.trim() || undefined, }), }); message.success('推广码已创建'); setCreateOpen(false); createForm.resetFields(); void reload(); navigate(`/promo-codes/${created.id}`); } catch (e) { message.error(e instanceof Error ? e.message : '创建失败'); } finally { setCreating(false); } } return (
推广码管理
{ setFilters(v); setPage(1); }} > ({ value, label }))} />
{ setPage(p); setPageSize(ps); }, }} /> setCreateOpen(false)} footer={null} destroyOnClose >
); }