58a8b47532
Co-authored-by: Cursor <cursoragent@cursor.com>
258 lines
8.8 KiB
TypeScript
258 lines
8.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Button, Descriptions, Drawer, Form, Input, Segmented, Space, Table, Tag, Typography } from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import { useSearchParams } from 'react-router-dom';
|
|
import {
|
|
PARTNER_LOG_CATEGORY_OPTIONS,
|
|
PARTNER_STAFF_ROLE_LABELS,
|
|
type PartnerLogCategory,
|
|
type PartnerStaffRole,
|
|
} from '@dukang/shared-types';
|
|
import { request } from '../lib/api';
|
|
import { AdminCellLine } from '../components/AdminCellLine';
|
|
import { fmtTime } from '../lib/constants';
|
|
import { clientAppLabel, eventNameLabel, formatRef, partnerEventCategoryLabel } from '../lib/display-labels';
|
|
import { useAdminList } from '../lib/useAdminList';
|
|
import { useAdminListColumns } from '../lib/useAdminListColumns';
|
|
import { AdminListHeader } from '../components/AdminListHeader';
|
|
import { AdminPrimaryLink } from '../components/AdminPrimaryLink';
|
|
|
|
|
|
type Row = {
|
|
id: string;
|
|
partnerId: string;
|
|
partnerAccountId: string | null;
|
|
accountName: string | null;
|
|
accountPhone: string | null;
|
|
companyName: string | null;
|
|
isSubAccount?: boolean;
|
|
staffRole?: string | null;
|
|
category: PartnerLogCategory | null;
|
|
eventName: string;
|
|
clientApp: string | null;
|
|
refType: string | null;
|
|
refId: string | null;
|
|
extraJson: Record<string, unknown> | null;
|
|
createdAt: string;
|
|
};
|
|
|
|
function summarizeExtra(json: Record<string, unknown> | null) {
|
|
if (!json) return '—';
|
|
const text = JSON.stringify(json);
|
|
return text.length > 80 ? `${text.slice(0, 80)}…` : text;
|
|
}
|
|
|
|
export default function PartnerLogsPage() {
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const [form] = Form.useForm();
|
|
const [category, setCategory] = useState(searchParams.get('category') ?? '');
|
|
const [filters, setFilters] = useState<Record<string, string>>(() => ({
|
|
partnerId: searchParams.get('partnerId') ?? '',
|
|
partnerAccountId: searchParams.get('partnerAccountId') ?? '',
|
|
phone: searchParams.get('phone') ?? '',
|
|
companyName: searchParams.get('companyName') ?? '',
|
|
eventName: searchParams.get('eventName') ?? '',
|
|
}));
|
|
const { data, loading, page, pageSize, setPage, setPageSize } = useAdminList<Row>(
|
|
'/admin/logs/partners',
|
|
() => {
|
|
const qs = new URLSearchParams();
|
|
if (filters.partnerId) qs.set('partnerId', filters.partnerId);
|
|
if (filters.partnerAccountId) qs.set('partnerAccountId', filters.partnerAccountId);
|
|
if (filters.phone) qs.set('phone', filters.phone);
|
|
if (filters.companyName) qs.set('companyName', filters.companyName);
|
|
if (filters.eventName) qs.set('eventName', filters.eventName);
|
|
if (category) qs.set('category', category);
|
|
return qs;
|
|
},
|
|
[filters, category],
|
|
);
|
|
const [detail, setDetail] = useState<Record<string, unknown> | null>(null);
|
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
form.setFieldsValue(filters);
|
|
}, [form, filters]);
|
|
|
|
const baseColumns: ColumnsType<Row> = [
|
|
{ title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
|
|
{
|
|
title: '合伙人',
|
|
width: 180,
|
|
render: (_, r) => (
|
|
r.isSubAccount ? (
|
|
<Tag color="blue">子账号</Tag>
|
|
) : (
|
|
<AdminCellLine primary={r.companyName} secondary={r.partnerId} />
|
|
)
|
|
),
|
|
},
|
|
{
|
|
title: '账号',
|
|
width: 150,
|
|
render: (_, r) => (
|
|
<AdminCellLine
|
|
primary={r.accountName}
|
|
secondary={
|
|
r.isSubAccount && r.staffRole
|
|
? `${r.accountPhone || ''} · ${PARTNER_STAFF_ROLE_LABELS[r.staffRole as PartnerStaffRole] || r.staffRole}`
|
|
: r.accountPhone || r.partnerAccountId
|
|
}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: '分类',
|
|
dataIndex: 'category',
|
|
width: 100,
|
|
render: (v: PartnerLogCategory | null, r) => (
|
|
<Tag>{partnerEventCategoryLabel(r.eventName, v)}</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '事件',
|
|
dataIndex: 'eventName',
|
|
width: 180,
|
|
render: (v, row) => (
|
|
<AdminPrimaryLink
|
|
onClick={async () => {
|
|
setDetail(await request(`/admin/logs/partners/${row.id}`));
|
|
setDrawerOpen(true);
|
|
}}
|
|
>
|
|
{eventNameLabel(v)}
|
|
</AdminPrimaryLink>
|
|
),
|
|
},
|
|
{
|
|
title: '关联',
|
|
width: 120,
|
|
render: (_, r) => formatRef(r.refType, r.refId),
|
|
},
|
|
{
|
|
title: '摘要',
|
|
render: (_, r) => summarizeExtra(r.extraJson),
|
|
},
|
|
{
|
|
title: '操作',
|
|
width: 80,
|
|
render: (_, row) => (
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
onClick={async () => {
|
|
setDetail(await request(`/admin/logs/partners/${row.id}`));
|
|
setDrawerOpen(true);
|
|
}}
|
|
>
|
|
详情
|
|
</Button>
|
|
),
|
|
},
|
|
];
|
|
|
|
const { columns, settingsButton, settingsModal } = useAdminListColumns('logs-partners', baseColumns, { page, pageSize });
|
|
|
|
return (
|
|
<div>
|
|
{settingsModal}
|
|
<AdminListHeader title="合伙人日志" settings={settingsButton} />
|
|
<Segmented
|
|
options={PARTNER_LOG_CATEGORY_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
|
|
value={category}
|
|
onChange={(v) => {
|
|
setCategory(String(v));
|
|
setPage(1);
|
|
setSearchParams((prev) => {
|
|
const next = new URLSearchParams(prev);
|
|
if (v) next.set('category', String(v));
|
|
else next.delete('category');
|
|
return next;
|
|
});
|
|
}}
|
|
style={{ marginBottom: 16 }}
|
|
/>
|
|
<Form
|
|
form={form}
|
|
layout="inline"
|
|
style={{ marginBottom: 16, flexWrap: 'wrap', gap: 8 }}
|
|
onFinish={(values) => {
|
|
setFilters(values);
|
|
setPage(1);
|
|
}}
|
|
>
|
|
<Form.Item name="phone" label="手机号">
|
|
<Input placeholder="账号手机号" allowClear style={{ width: 140 }} />
|
|
</Form.Item>
|
|
<Form.Item name="companyName" label="公司">
|
|
<Input placeholder="合伙人公司" allowClear style={{ width: 140 }} />
|
|
</Form.Item>
|
|
<Form.Item name="partnerId" label="合伙人 ID">
|
|
<Input placeholder="合伙人 ID" allowClear style={{ width: 120 }} />
|
|
</Form.Item>
|
|
<Form.Item name="eventName" label="事件编码">
|
|
<Input placeholder="事件编码" allowClear style={{ width: 160 }} />
|
|
</Form.Item>
|
|
<Form.Item>
|
|
<Space>
|
|
<Button type="primary" htmlType="submit">
|
|
查询
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
form.resetFields();
|
|
setFilters({
|
|
partnerId: '',
|
|
partnerAccountId: '',
|
|
phone: '',
|
|
companyName: '',
|
|
eventName: '',
|
|
});
|
|
setPage(1);
|
|
}}
|
|
>
|
|
重置
|
|
</Button>
|
|
</Space>
|
|
</Form.Item>
|
|
</Form>
|
|
<Table
|
|
rowKey="id"
|
|
loading={loading}
|
|
columns={columns}
|
|
dataSource={data?.items ?? []}
|
|
pagination={{
|
|
current: page,
|
|
pageSize,
|
|
total: data?.total ?? 0,
|
|
showSizeChanger: true,
|
|
onChange: (p, ps) => {
|
|
setPage(p);
|
|
setPageSize(ps);
|
|
},
|
|
}}
|
|
scroll={{ x: 'max-content' }}
|
|
/>
|
|
<Drawer title="日志详情" open={drawerOpen} onClose={() => setDrawerOpen(false)} width={520}>
|
|
{detail && (
|
|
<Descriptions column={1} bordered size="small">
|
|
<Descriptions.Item label="ID">{String(detail.id ?? '—')}</Descriptions.Item>
|
|
<Descriptions.Item label="合伙人">{String(detail.companyName || detail.partnerId || '—')}</Descriptions.Item>
|
|
<Descriptions.Item label="账号">{String(detail.accountName || detail.accountPhone || '—')}</Descriptions.Item>
|
|
<Descriptions.Item label="分类">{partnerEventCategoryLabel(String(detail.eventName ?? ''), detail.category ? String(detail.category) : null)}</Descriptions.Item>
|
|
<Descriptions.Item label="事件">{eventNameLabel(String(detail.eventName ?? ''))}</Descriptions.Item>
|
|
<Descriptions.Item label="端">{clientAppLabel(detail.clientApp ? String(detail.clientApp) : null)}</Descriptions.Item>
|
|
<Descriptions.Item label="关联">{formatRef(detail.refType ? String(detail.refType) : null, detail.refId ? String(detail.refId) : null)}</Descriptions.Item>
|
|
<Descriptions.Item label="时间">{fmtTime(detail.createdAt ? String(detail.createdAt) : null)}</Descriptions.Item>
|
|
<Descriptions.Item label="参数">
|
|
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
|
{JSON.stringify(detail.extraJson ?? {}, null, 2)}
|
|
</pre>
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
)}
|
|
</Drawer>
|
|
</div>
|
|
);
|
|
}
|