89fd333702
Add client-logging SDK, expanded event taxonomy, API observability, admin domain events UI, and move SENTRY_DSN to HQ system settings with @sentry/node bootstrap after config preload. Co-authored-by: Cursor <cursoragent@cursor.com>
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
import { useSearchParams } from 'react-router-dom';
|
|
import { Card, Descriptions, Drawer, Select, Space, Table, Tag } from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import { request } from '../lib/api';
|
|
import { fmtTime } from '../lib/constants';
|
|
|
|
type Row = {
|
|
id: string;
|
|
eventType: string;
|
|
refType: string;
|
|
refId: string;
|
|
status?: string | null;
|
|
param1?: string | null;
|
|
param2?: string | null;
|
|
param3?: string | null;
|
|
remark?: string | null;
|
|
createdAt: string;
|
|
};
|
|
|
|
const EVENT_OPTIONS = [
|
|
{ value: '', label: '全部领域事件' },
|
|
{ value: 'ORDER_STATUS', label: '订单状态' },
|
|
{ value: 'BENEFIT_LEDGER', label: '权益流水' },
|
|
{ value: 'STORE_AUDIT', label: '门店审核' },
|
|
{ value: 'TICKET_COLLAB', label: '工单协作' },
|
|
{ value: 'PROMO_TOUCH', label: '推广触达' },
|
|
];
|
|
|
|
export default function DomainEventsPage() {
|
|
const [params, setParams] = useSearchParams();
|
|
const eventType = params.get('eventType') ?? '';
|
|
const refType = params.get('refType') ?? '';
|
|
const refId = params.get('refId') ?? '';
|
|
const [data, setData] = useState<{ items: Row[]; total: number; page: number; pageSize: number } | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [detail, setDetail] = useState<Row | null>(null);
|
|
|
|
const load = useCallback(() => {
|
|
setLoading(true);
|
|
const q = new URLSearchParams();
|
|
if (eventType) q.set('eventType', eventType);
|
|
if (refType) q.set('refType', refType);
|
|
if (refId) q.set('refId', refId);
|
|
q.set('page', params.get('page') ?? '1');
|
|
q.set('pageSize', '20');
|
|
request<{ items: Row[]; total: number; page: number; pageSize: number }>(
|
|
`/admin/logs/domain-events?${q.toString()}`,
|
|
)
|
|
.then(setData)
|
|
.finally(() => setLoading(false));
|
|
}, [eventType, refType, refId, params]);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
}, [load]);
|
|
|
|
const columns: ColumnsType<Row> = [
|
|
{ title: '时间', dataIndex: 'createdAt', width: 170, render: (v) => fmtTime(v) },
|
|
{ title: '类型', dataIndex: 'eventType', width: 130, render: (v) => <Tag>{v}</Tag> },
|
|
{ title: '关联', render: (_, r) => `${r.refType} #${r.refId}` },
|
|
{ title: '状态', dataIndex: 'status', width: 100 },
|
|
{ title: '摘要', render: (_, r) => r.param1 || r.remark || '—' },
|
|
{
|
|
title: '操作',
|
|
width: 80,
|
|
render: (_, r) => (
|
|
<a
|
|
onClick={async () => {
|
|
setDetail(await request<Row>(`/admin/logs/domain-events/${r.id}`));
|
|
}}
|
|
>
|
|
详情
|
|
</a>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<Card title="领域事件">
|
|
<Space wrap style={{ marginBottom: 16 }}>
|
|
<Select
|
|
style={{ width: 180 }}
|
|
value={eventType}
|
|
options={EVENT_OPTIONS}
|
|
onChange={(v) => {
|
|
const next = new URLSearchParams(params);
|
|
if (v) next.set('eventType', v);
|
|
else next.delete('eventType');
|
|
next.set('page', '1');
|
|
setParams(next);
|
|
}}
|
|
/>
|
|
</Space>
|
|
<Table
|
|
rowKey="id"
|
|
loading={loading}
|
|
columns={columns}
|
|
dataSource={data?.items ?? []}
|
|
pagination={{
|
|
current: data?.page ?? 1,
|
|
pageSize: data?.pageSize ?? 20,
|
|
total: data?.total ?? 0,
|
|
onChange: (p) => {
|
|
const next = new URLSearchParams(params);
|
|
next.set('page', String(p));
|
|
setParams(next);
|
|
},
|
|
}}
|
|
/>
|
|
<Drawer open={!!detail} title="事件详情" width={520} onClose={() => setDetail(null)}>
|
|
{detail && (
|
|
<Descriptions column={1} size="small">
|
|
<Descriptions.Item label="ID">{detail.id}</Descriptions.Item>
|
|
<Descriptions.Item label="类型">{detail.eventType}</Descriptions.Item>
|
|
<Descriptions.Item label="关联">{`${detail.refType} #${detail.refId}`}</Descriptions.Item>
|
|
<Descriptions.Item label="param1">{detail.param1}</Descriptions.Item>
|
|
<Descriptions.Item label="param2">{detail.param2}</Descriptions.Item>
|
|
<Descriptions.Item label="param3">{detail.param3}</Descriptions.Item>
|
|
<Descriptions.Item label="备注">{detail.remark}</Descriptions.Item>
|
|
<Descriptions.Item label="时间">{fmtTime(detail.createdAt)}</Descriptions.Item>
|
|
</Descriptions>
|
|
)}
|
|
</Drawer>
|
|
</Card>
|
|
);
|
|
}
|