增加核销接口
增加hq管理端日志
This commit is contained in:
@@ -25,6 +25,7 @@ import StorePayoutsPage from './pages/StorePayoutsPage';
|
||||
import PartnerBillsPage from './pages/PartnerBillsPage';
|
||||
import TicketsPage from './pages/TicketsPage';
|
||||
import UserLogsPage from './pages/UserLogsPage';
|
||||
import HqLogsPage from './pages/HqLogsPage';
|
||||
import ThirdPartyLogsPage from './pages/ThirdPartyLogsPage';
|
||||
|
||||
function RequireAuth({ children }: { children: React.ReactNode }) {
|
||||
@@ -63,6 +64,7 @@ export default function App() {
|
||||
<Route path="/partner-bills" element={<PartnerBillsPage />} />
|
||||
<Route path="/tickets" element={<TicketsPage />} />
|
||||
<Route path="/logs/users" element={<UserLogsPage />} />
|
||||
<Route path="/logs/hq" element={<HqLogsPage />} />
|
||||
<Route path="/logs/third-party" element={<ThirdPartyLogsPage />} />
|
||||
<Route path="/deliveries" element={<DeliveriesPage />} />
|
||||
<Route path="/deliveries/xiaofeixia" element={<XiaofeixiaTestPage />} />
|
||||
|
||||
@@ -73,6 +73,7 @@ const MENU_ITEMS: MenuProps['items'] = [
|
||||
label: '日志',
|
||||
children: [
|
||||
{ key: '/logs/users', label: '用户日志' },
|
||||
{ key: '/logs/hq', label: 'HQ 操作日志' },
|
||||
{ key: '/logs/third-party', label: '第三方日志' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
export const HQ_OPERATION_ACTION_OPTIONS = [
|
||||
{ value: 'CITY_CREATE', label: '新增开城城市' },
|
||||
{ value: 'CITY_UPDATE', label: '编辑开城城市' },
|
||||
{ value: 'PARTNER_CREATE', label: '新增城市合伙人' },
|
||||
{ value: 'PARTNER_UPDATE', label: '编辑城市合伙人' },
|
||||
{ value: 'PARTNER_ACCOUNT_CREATE', label: '新增合伙人账户' },
|
||||
{ value: 'PARTNER_ACCOUNT_UPDATE', label: '编辑合伙人账户' },
|
||||
{ value: 'HQ_ACCOUNT_CREATE', label: '新增 HQ 管理员' },
|
||||
{ value: 'HQ_ACCOUNT_UPDATE', label: '编辑 HQ 管理员/权限' },
|
||||
{ value: 'USER_DELETE', label: '删除用户' },
|
||||
{ value: 'USER_BATCH_DELETE', label: '批量删除用户' },
|
||||
{ value: 'ORDER_SHIP', label: '订单发货' },
|
||||
{ value: 'ORDER_STATUS_DEBUG', label: '订单状态调试' },
|
||||
{ value: 'ORDER_BATCH_DELETE', label: '批量删除订单' },
|
||||
{ value: 'STORE_CREATE', label: '新增门店' },
|
||||
{ value: 'STORE_UPDATE', label: '编辑门店' },
|
||||
{ value: 'STORE_STATUS', label: '变更门店状态' },
|
||||
{ value: 'STORE_AUDIT', label: '门店审核' },
|
||||
{ value: 'STORE_ACCOUNT_CREATE', label: '新增门店账户' },
|
||||
{ value: 'STORE_ACCOUNT_UPDATE', label: '编辑门店账户' },
|
||||
{ value: 'PRODUCT_CREATE', label: '新增商品' },
|
||||
{ value: 'PRODUCT_UPDATE', label: '编辑商品' },
|
||||
{ value: 'PRODUCT_DELETE', label: '删除商品' },
|
||||
{ value: 'BENEFIT_COUPON_VOID', label: '作废权益券' },
|
||||
{ value: 'DELIVERY_UPDATE', label: '编辑配送单' },
|
||||
{ value: 'TICKET_APPROVE', label: '工单通过' },
|
||||
{ value: 'TICKET_REJECT', label: '工单驳回' },
|
||||
{ value: 'STORE_PAYOUT_CONFIRM', label: '门店打款确认' },
|
||||
{ value: 'STORE_PAYOUT_BATCH_CONFIRM', label: '批量门店打款' },
|
||||
{ value: 'PARTNER_BILL_GENERATE', label: '生成合伙人账单' },
|
||||
{ value: 'PARTNER_BILL_CONFIRM', label: '确认合伙人账单' },
|
||||
{ value: 'PARTNER_BILL_MARK_PAID', label: '合伙人账单结算' },
|
||||
] as const;
|
||||
|
||||
export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = Object.fromEntries(
|
||||
HQ_OPERATION_ACTION_OPTIONS.map((o) => [o.value, o.label]),
|
||||
);
|
||||
|
||||
export function resolveHqOperationLabel(action: string | null | undefined): string {
|
||||
if (!action) return '—';
|
||||
return HQ_OPERATION_ACTION_LABELS[action] || action;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button, Descriptions, Drawer, Form, Input, Select, Space, Table, Tag, Typography,
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { request } from '../lib/api';
|
||||
import { fmtTime } from '../lib/constants';
|
||||
import { HQ_OPERATION_ACTION_OPTIONS, resolveHqOperationLabel } from '../lib/hq-log';
|
||||
import { useAdminList } from '../lib/useAdminList';
|
||||
|
||||
type Row = {
|
||||
id: string;
|
||||
hqAccountId: string | null;
|
||||
hqName: string | null;
|
||||
hqPhone: string | null;
|
||||
hqRole: string | null;
|
||||
action: string | null;
|
||||
actionLabel: string;
|
||||
refType: string | null;
|
||||
refId: string | null;
|
||||
status: string | null;
|
||||
remark: string | null;
|
||||
detail: Record<string, unknown> | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export default function HqLogsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [form] = Form.useForm();
|
||||
const [filters, setFilters] = useState<Record<string, string>>(() => ({
|
||||
hqAccountId: searchParams.get('hqAccountId') ?? '',
|
||||
action: searchParams.get('action') ?? '',
|
||||
refType: searchParams.get('refType') ?? '',
|
||||
}));
|
||||
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
|
||||
'/admin/logs/hq',
|
||||
() => {
|
||||
const qs = new URLSearchParams();
|
||||
if (filters.hqAccountId) qs.set('hqAccountId', filters.hqAccountId);
|
||||
if (filters.action) qs.set('action', filters.action);
|
||||
if (filters.refType) qs.set('refType', filters.refType);
|
||||
return qs;
|
||||
},
|
||||
[filters],
|
||||
);
|
||||
const [detail, setDetail] = useState<Row | null>(null);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(filters);
|
||||
}, [form, filters]);
|
||||
|
||||
const columns: ColumnsType<Row> = [
|
||||
{ title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
|
||||
{
|
||||
title: '操作人',
|
||||
width: 160,
|
||||
render: (_, r) => (
|
||||
<div>
|
||||
<div>{r.hqName || '—'}</div>
|
||||
<div style={{ color: '#999', fontSize: 12 }}>
|
||||
{r.hqPhone || ''} {r.hqAccountId ? `(#${r.hqAccountId})` : ''}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '行为',
|
||||
dataIndex: 'actionLabel',
|
||||
width: 160,
|
||||
render: (v, r) => <Tag color="blue">{v || resolveHqOperationLabel(r.action)}</Tag>,
|
||||
},
|
||||
{ title: '对象类型', dataIndex: 'refType', width: 120, render: (v) => v || '—' },
|
||||
{ title: '对象 ID', dataIndex: 'refId', width: 100, render: (v) => v || '—' },
|
||||
{
|
||||
title: '操作',
|
||||
width: 80,
|
||||
render: (_, row) => (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
const res = await request<Row>(`/admin/logs/hq/${row.id}`);
|
||||
setDetail(res);
|
||||
setDrawerOpen(true);
|
||||
}}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography.Title level={4}>HQ 操作日志</Typography.Title>
|
||||
<Typography.Paragraph type="secondary">
|
||||
记录总部后台写操作(开城、订单、用户、权限、合伙人等),仅追加不删除。
|
||||
</Typography.Paragraph>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="inline"
|
||||
style={{ marginBottom: 16 }}
|
||||
onFinish={(values) => {
|
||||
setFilters(values);
|
||||
setPage(1);
|
||||
const qs = new URLSearchParams();
|
||||
if (values.hqAccountId) qs.set('hqAccountId', values.hqAccountId);
|
||||
if (values.action) qs.set('action', values.action);
|
||||
if (values.refType) qs.set('refType', values.refType);
|
||||
setSearchParams(qs);
|
||||
}}
|
||||
>
|
||||
<Form.Item name="hqAccountId" label="HQ 账户 ID">
|
||||
<Input allowClear style={{ width: 140 }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="action" label="行为">
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
style={{ width: 180 }}
|
||||
options={HQ_OPERATION_ACTION_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="refType" label="对象类型">
|
||||
<Input allowClear placeholder="ORDER / USER / CITY..." style={{ width: 140 }} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">查询</Button>
|
||||
<Button onClick={() => {
|
||||
form.resetFields();
|
||||
setFilters({ hqAccountId: '', action: '', refType: '' });
|
||||
setSearchParams({});
|
||||
setPage(1);
|
||||
void reload();
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={data?.items ?? []}
|
||||
scroll={{ x: 900 }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize,
|
||||
total: data?.total ?? 0,
|
||||
showSizeChanger: true,
|
||||
onChange: (p, ps) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Drawer title="操作详情" width={640} open={drawerOpen} onClose={() => setDrawerOpen(false)}>
|
||||
{detail && (
|
||||
<>
|
||||
<Descriptions column={1} bordered size="small">
|
||||
<Descriptions.Item label="日志 ID">{detail.id}</Descriptions.Item>
|
||||
<Descriptions.Item label="时间">{fmtTime(detail.createdAt)}</Descriptions.Item>
|
||||
<Descriptions.Item label="操作人">
|
||||
{detail.hqName} / {detail.hqPhone} (ID: {detail.hqAccountId})
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="角色">{detail.hqRole || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="行为">
|
||||
{detail.actionLabel || resolveHqOperationLabel(detail.action)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="对象">{detail.refType} / {detail.refId}</Descriptions.Item>
|
||||
<Descriptions.Item label="状态">{detail.status || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="备注">{detail.remark || '—'}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Typography.Title level={5} style={{ marginTop: 16 }}>请求/响应快照</Typography.Title>
|
||||
<pre style={{
|
||||
margin: 0, padding: 12, background: '#f5f5f5', borderRadius: 4,
|
||||
maxHeight: 400, overflow: 'auto', fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(detail.detail, null, 2)}
|
||||
</pre>
|
||||
</>
|
||||
)}
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user