109 lines
3.3 KiB
TypeScript
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>
|
|
);
|
|
}
|