Files
dukang/apps/admin-web/src/pages/promo/PromoCodeDetailLayout.tsx
T
2026-07-12 11:40:04 +08:00

109 lines
3.3 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
import { Breadcrumb, Button, Space, Spin, Tabs, Tag, Typography } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import {
PROMO_CODE_STATUS_LABELS,
type PromoCodeItem,
type PromoCodeStats,
} from '@dukang/shared-types';
import { request } from '../../lib/api';
export type PromoCodeDetailContext = {
detail: PromoCodeItem & { stats?: PromoCodeStats };
reload: () => Promise<void>;
};
export default function PromoCodeDetailLayout() {
const { id = '' } = useParams();
const navigate = useNavigate();
const location = useLocation();
const [detail, setDetail] = useState<(PromoCodeItem & { stats?: PromoCodeStats }) | null>(null);
const [loading, setLoading] = useState(true);
async function loadDetail() {
setLoading(true);
try {
const res = await request<PromoCodeItem & { stats?: PromoCodeStats }>(`/admin/promo-codes/${id}`);
setDetail(res);
} catch {
setDetail(null);
} finally {
setLoading(false);
}
}
useEffect(() => {
void loadDetail();
}, [id]);
const tabKey = location.pathname.endsWith('/users') ? 'users' : 'overview';
if (loading) {
return <Spin style={{ display: 'block', margin: '80px auto' }} />;
}
if (!detail) {
return (
<div>
<Typography.Text type="danger">推广码不存在或加载失败</Typography.Text>
<div style={{ marginTop: 16 }}>
<Button onClick={() => navigate('/promo-codes')}>返回列表</Button>
</div>
</div>
);
}
return (
<div>
<Breadcrumb
style={{ marginBottom: 12 }}
items={[
{ title: <Link to="/promo-codes">推广码管理</Link> },
{ title: detail.name },
]}
/>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 }}>
<Space direction="vertical" size={4}>
<Space align="center">
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/promo-codes')}
style={{ marginLeft: -8 }}
/>
<Typography.Title level={4} style={{ margin: 0 }}>
{detail.name}
</Typography.Title>
<Tag color={detail.status === 'ACTIVE' ? 'green' : 'default'}>
{PROMO_CODE_STATUS_LABELS[detail.status] || detail.status}
</Tag>
</Space>
<Typography.Text type="secondary" copyable={{ text: detail.code }}>
码值:{detail.code}
</Typography.Text>
</Space>
</div>
<Tabs
activeKey={tabKey}
onChange={(key) => {
if (key === 'users') navigate(`/promo-codes/${id}/users`);
else navigate(`/promo-codes/${id}`);
}}
items={[
{ key: 'overview', label: '概览' },
{
key: 'users',
label: `关联用户${detail.stats?.sourceMarkedCount != null ? ` (${detail.stats.sourceMarkedCount})` : ''}`,
},
]}
style={{ marginBottom: 16 }}
/>
<Outlet context={{ detail, reload: loadDetail } satisfies PromoCodeDetailContext} />
</div>
);
}