diff --git a/apps/admin-web/src/App.tsx b/apps/admin-web/src/App.tsx index 49051bd..da8497b 100644 --- a/apps/admin-web/src/App.tsx +++ b/apps/admin-web/src/App.tsx @@ -38,6 +38,7 @@ import TicketsPage from './pages/TicketsPage'; import SupportTicketsPage from './pages/SupportTicketsPage'; import InvoicesPage from './pages/InvoicesPage'; import UserLogsPage from './pages/UserLogsPage'; +import DomainEventsPage from './pages/DomainEventsPage'; import HqLogsPage from './pages/HqLogsPage'; import ThirdPartyLogsPage from './pages/ThirdPartyLogsPage'; import StoreLogsPage from './pages/StoreLogsPage'; @@ -120,6 +121,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/apps/admin-web/src/layouts/AdminLayout.tsx b/apps/admin-web/src/layouts/AdminLayout.tsx index b9a8d12..c7d0fac 100644 --- a/apps/admin-web/src/layouts/AdminLayout.tsx +++ b/apps/admin-web/src/layouts/AdminLayout.tsx @@ -122,6 +122,7 @@ const MENU_ITEMS: MenuProps['items'] = [ { key: '/logs/partners', label: '合伙人日志' }, { key: '/logs/hq', label: 'HQ 操作日志' }, { key: '/logs/third-party', label: '第三方日志' }, + { key: '/logs/domain-events', label: '领域事件' }, ], }, { key: '/hq-permissions', icon: , label: '权限分配' }, @@ -182,6 +183,7 @@ function menuAllowed(key: string, permissionKeys: string[]): boolean { '/logs/partners': 'logs', '/logs/hq': 'logs', '/logs/third-party': 'logs', + '/logs/domain-events': 'logs', '/hq-permissions': 'hq_permissions', '/system-settings': 'system_settings_any', '/hq-accounts': 'hq_accounts', diff --git a/apps/admin-web/src/lib/user-log.ts b/apps/admin-web/src/lib/user-log.ts index 9114728..3a8c6e9 100644 --- a/apps/admin-web/src/lib/user-log.ts +++ b/apps/admin-web/src/lib/user-log.ts @@ -1,45 +1,12 @@ -export type UserLogCategory = - | 'login' - | 'browse_product' - | 'order' - | 'pay' - | 'wechat_auth' - | 'browse_store' - | 'redeem' - | 'profile'; +export { + USER_LOG_CATEGORY_OPTIONS, + resolveUserLogCategory, + eventNamesForUserLogCategory, + type UserLogCategory, +} from '@dukang/shared-types'; -const USER_LOG_EVENT_CATEGORIES: Record = { - login: ['login_success', 'sms_login', 'sms_send', 'sms_verify_fail'], - browse_product: ['home_view', 'product_click', 'product_detail_view'], - order: ['order_confirm_view', 'order_submit'], - pay: ['pay_success', 'pay_fail'], - wechat_auth: ['wechat_login', 'wechat_phone', 'wechat_location', 'wechat_album'], - browse_store: ['store_list_view', 'store_detail_view'], - redeem: ['benefit_redeem_start', 'benefit_redeem_success'], - profile: ['profile_update', 'bind_phone'], -}; +import { USER_LOG_CATEGORY_OPTIONS } from '@dukang/shared-types'; -export const USER_LOG_CATEGORY_OPTIONS: Array<{ value: UserLogCategory | ''; label: string }> = [ - { value: '', label: '全部' }, - { value: 'login', label: '登录' }, - { value: 'browse_product', label: '浏览商品' }, - { value: 'order', label: '下单' }, - { value: 'pay', label: '支付' }, - { value: 'wechat_auth', label: '微信授权' }, - { value: 'browse_store', label: '浏览门店' }, - { value: 'redeem', label: '核销' }, - { value: 'profile', label: '信息修改' }, -]; - -export function resolveUserLogCategory(eventName: string): UserLogCategory | null { - for (const [category, events] of Object.entries(USER_LOG_EVENT_CATEGORIES) as Array< - [UserLogCategory, readonly string[]] - >) { - if (events.includes(eventName)) return category; - } - return null; -} - -export const USER_LOG_CATEGORY_LABELS = Object.fromEntries( +export const USER_LOG_CATEGORY_LABELS: Record = Object.fromEntries( USER_LOG_CATEGORY_OPTIONS.filter((o) => o.value).map((o) => [o.value, o.label]), -) as Record; +); diff --git a/apps/admin-web/src/pages/DomainEventsPage.tsx b/apps/admin-web/src/pages/DomainEventsPage.tsx new file mode 100644 index 0000000..4e663cf --- /dev/null +++ b/apps/admin-web/src/pages/DomainEventsPage.tsx @@ -0,0 +1,127 @@ +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(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 = [ + { title: '时间', dataIndex: 'createdAt', width: 170, render: (v) => fmtTime(v) }, + { title: '类型', dataIndex: 'eventType', width: 130, render: (v) => {v} }, + { title: '关联', render: (_, r) => `${r.refType} #${r.refId}` }, + { title: '状态', dataIndex: 'status', width: 100 }, + { title: '摘要', render: (_, r) => r.param1 || r.remark || '—' }, + { + title: '操作', + width: 80, + render: (_, r) => ( + { + setDetail(await request(`/admin/logs/domain-events/${r.id}`)); + }} + > + 详情 + + ), + }, + ]; + + return ( + + +