diff --git a/apps/admin-web/src/admin.css b/apps/admin-web/src/admin.css index f312640..0b66a03 100644 --- a/apps/admin-web/src/admin.css +++ b/apps/admin-web/src/admin.css @@ -308,6 +308,40 @@ body:has(.big-screen-page), gap: 12px; } +.big-screen-bgm { + display: inline-flex; + align-items: center; + gap: 8px; + margin-right: 8px; + padding: 6px 14px 6px 12px; + border: 1px solid rgba(105, 192, 255, 0.45); + border-radius: 6px; + background: rgba(8, 28, 56, 0.7); + color: rgba(230, 244, 255, 0.92); + font-size: 14px; + font-weight: 700; + letter-spacing: 1px; + cursor: pointer; + line-height: 1; +} + +.big-screen-bgm:hover { + border-color: rgba(105, 192, 255, 0.85); + color: #fff; +} + +.big-screen-bgm.is-on { + border-color: rgba(64, 169, 255, 0.85); + box-shadow: 0 0 12px rgba(64, 169, 255, 0.28); + color: #fff; +} + +.big-screen-bgm-icon { + display: inline-flex; + width: 16px; + height: 16px; +} + .big-screen-live-label { font-size: 18px; color: rgba(230, 244, 255, 0.88); diff --git a/apps/admin-web/src/assets/big-screen/dukang-bgm.mp3 b/apps/admin-web/src/assets/big-screen/dukang-bgm.mp3 new file mode 100644 index 0000000..bd77b4c Binary files /dev/null and b/apps/admin-web/src/assets/big-screen/dukang-bgm.mp3 differ diff --git a/apps/admin-web/src/layouts/AdminLayout.tsx b/apps/admin-web/src/layouts/AdminLayout.tsx index b901a9c..f610a89 100644 --- a/apps/admin-web/src/layouts/AdminLayout.tsx +++ b/apps/admin-web/src/layouts/AdminLayout.tsx @@ -41,7 +41,6 @@ type MenuItem = NonNullable[number]; const MENU_ITEMS: MenuProps['items'] = [ { key: '/', icon: , label: '概览' }, { key: '/users', icon: , label: '用户' }, - { key: '/wechat-bindings', icon: , label: '微信绑定' }, { key: 'products-group', icon: , @@ -52,18 +51,17 @@ const MENU_ITEMS: MenuProps['items'] = [ ], }, { key: '/orders', icon: , label: '订单' }, - { key: '/promo-codes', icon: , label: '推广码' }, { - key: 'wecom-group', + key: 'partners-group', icon: , - label: '企微机器人', + label: '开城', children: [ - { key: '/wecom/bots', label: '智能机器人' }, - { key: '/wecom/pushes', label: '消息推送' }, + { key: '/cities', label: '城市' }, + { key: '/city-partners', label: '城市合伙人' }, + { key: '/city-warehouses', label: '仓库' }, + { key: '/fulfillment-providers', label: '仓配管理' }, ], }, - { key: '/llm-configs', icon: , label: '语言模型' }, - { key: '/knowledge-bases', icon: , label: '知识库' }, { key: 'stores-group', icon: , @@ -77,17 +75,6 @@ const MENU_ITEMS: MenuProps['items'] = [ { key: '/store-media', label: '门店资源' }, ], }, - { - key: 'partners-group', - icon: , - label: '开城', - children: [ - { key: '/cities', label: '城市' }, - { key: '/city-partners', label: '城市合伙人' }, - { key: '/city-warehouses', label: '仓库' }, - { key: '/fulfillment-providers', label: '仓配管理' }, - ], - }, { key: 'finance-group', icon: , @@ -129,6 +116,20 @@ const MENU_ITEMS: MenuProps['items'] = [ { key: '/tickets/support', label: '技术支持' }, ], }, + { key: '/invoices', icon: , label: '发票' }, + { key: '/promo-codes', icon: , label: '推广码' }, + { key: '/wechat-bindings', icon: , label: '微信绑定' }, + { + key: 'wecom-group', + icon: , + label: '企微机器人', + children: [ + { key: '/wecom/bots', label: '智能机器人' }, + { key: '/wecom/pushes', label: '消息推送' }, + ], + }, + { key: '/llm-configs', icon: , label: '语言模型' }, + { key: '/knowledge-bases', icon: , label: '知识库' }, { key: 'dev-plan-group', icon: , @@ -139,7 +140,6 @@ const MENU_ITEMS: MenuProps['items'] = [ { key: '/dev-plan/settings', label: '开发设置' }, ], }, - { key: '/invoices', icon: , label: '发票管理' }, { key: '/resources', icon: , label: 'OSS 资源库' }, { key: 'logs-group', @@ -156,9 +156,9 @@ const MENU_ITEMS: MenuProps['items'] = [ ], }, { key: '/hq-permissions', icon: , label: '权限分配' }, + { key: '/hq-accounts', icon: , label: 'HQ账户' }, { key: '/test-whitelist', icon: , label: '白名单管理' }, { key: '/system-settings', icon: , label: '系统设置' }, - { key: '/hq-accounts', icon: , label: 'HQ账户' }, ]; function menuAllowed(key: string, permissionKeys: string[]): boolean { diff --git a/apps/admin-web/src/lib/constants.ts b/apps/admin-web/src/lib/constants.ts index 95fc60b..490bd3c 100644 --- a/apps/admin-web/src/lib/constants.ts +++ b/apps/admin-web/src/lib/constants.ts @@ -31,9 +31,18 @@ export const ORDER_STATUS_OPERATOR_LABELS: Record = { USER_ON_SITE: '用户·现场取货', USER_WECHAT_CONFIRM: '用户·微信确认收货', WECHAT_TRADE_MANAGE: '微信结算同步', + WECHAT_PAY: '微信支付', MOCK: 'Mock', MOCK_PAY: 'Mock 支付', SYSTEM: '系统', + SYSTEM_AUTO_EXPIRE: '系统·超时取消', + PARTNER_PROXY: '合伙人代下单', + HQ_PROXY: 'HQ 代操作', + HQ_LOGISTICS: 'HQ 物流', + WAREHOUSE_MANUAL: '仓库手工', + HQ: '总部', + STORE: '门店', + PARTNER: '合伙人', }; /** 订单状态 Tag 颜色(Ant Design preset) */ diff --git a/apps/admin-web/src/lib/display-labels.ts b/apps/admin-web/src/lib/display-labels.ts new file mode 100644 index 0000000..d238c1e --- /dev/null +++ b/apps/admin-web/src/lib/display-labels.ts @@ -0,0 +1,358 @@ +import { + LEDGER_TYPE_LABELS, + ORDER_STATUS_LABELS, + ORDER_STATUS_OPERATOR_LABELS, +} from './constants'; +import { HQ_OPERATION_ACTION_LABELS } from './hq-log'; +import { USER_LOG_CATEGORY_LABELS } from './user-log'; +import { + PARTNER_LOG_CATEGORY_LABELS, + HQ_ADMIN_ROLES, + resolvePartnerLogCategory, + resolveStoreLogCategory, + resolveUserLogCategory, + STORE_LOG_CATEGORY_LABELS, + WECOM_BOT_PERMISSION_LABELS, +} from '@dukang/shared-types'; + +export function zhLabel(dict: Record, value?: string | null, empty = '—'): string { + if (value == null || value === '') return empty; + return dict[value] ?? value; +} + +export const CLIENT_APP_LABELS: Record = { + USER_MINI: 'C 端小程序', + USER_H5: 'C 端 H5', + PARTNER_MINI: '合伙人小程序', + PARTNER_H5: '合伙人 H5', + HQ_MINI: 'HQ 小程序', + HQ_WEB: 'HQ 后台', + SHOP_H5: '门店 H5', +}; + +export const REF_TYPE_LABELS: Record = { + ORDER: '订单', + USER: '用户', + STORE: '门店', + PARTNER: '合伙人', + CITY: '城市', + PRODUCT: '商品', + COUPON: '权益券', + TICKET: '工单', + SUPPORT_TICKET: '技术支持工单', + INVOICE: '发票', + PROMO: '推广码', + PROMO_CODE: '推广码', + HQ: '总部', + HQ_ACCOUNT: 'HQ 账号', + STORE_ACCOUNT: '门店账号', + PARTNER_ACCOUNT: '合伙人账号', + PAYOUT: '打款', + WITHDRAWAL: '提现', + BILL: '账单', + DELIVERY: '配送单', + REDEEM: '核销', + WAREHOUSE: '仓库', + PACKAGE: '套餐', +}; + +export const DOMAIN_EVENT_TYPE_LABELS: Record = { + ORDER_STATUS: '订单状态', + BENEFIT_LEDGER: '权益流水', + STORE_AUDIT: '门店审核', + TICKET_COLLAB: '工单协作', + PROMO_TOUCH: '推广触达', + HQ_OPERATION: 'HQ 操作', +}; + +export const PARAM_DESC_LABELS: Record = { + from_status: '原状态', + to_status: '目标状态', + operator: '操作人', + ledger_type: '流水类型', + coupon_id: '权益券 ID', + action: '行为', + target_type: '对象类型', + target_id: '对象 ID', +}; + +export const USER_LOG_EVENT_LABELS: Record = { + login_success: '登录成功', + sms_login: '短信登录', + sms_send: '发送验证码', + sms_verify_fail: '验证码校验失败', + session_start: '会话开始', + home_view: '浏览首页', + product_list_view: '浏览商品列表', + product_click: '点击商品', + product_detail_view: '浏览商品详情', + order_confirm_view: '浏览确认订单', + order_submit: '提交订单', + order_list_view: '浏览订单列表', + order_detail_view: '浏览订单详情', + order_cancel: '取消订单', + pay_page_view: '进入支付页', + pay_success: '支付成功', + pay_fail: '支付失败', + wechat_login: '微信登录', + wechat_phone: '微信授权手机号', + wechat_phone_login: '微信手机号登录', + wechat_location: '微信定位', + wechat_album: '微信选图', + wechat_bind: '绑定微信', + store_list_view: '浏览门店列表', + store_detail_view: '浏览门店详情', + benefit_page_view: '浏览权益页', + benefit_detail_view: '浏览权益详情', + benefit_redeem_start: '开始核销', + benefit_redeem_success: '核销成功', + redeem_code_view: '查看核销码', + redeem_success_view: '核销成功页', + profile_update: '修改资料', + bind_phone: '绑定手机号', + address_list_view: '浏览地址列表', + address_edit: '编辑地址', + promo_touch: '推广触达', + promo_share: '分享推广', + cs_contact: '联系客服', + after_sale_list_view: '浏览售后列表', + after_sale_apply: '申请售后', + client_error: '前端异常', +}; + +export const STORE_LOG_EVENT_LABELS: Record = { + store_sms_send: '发送验证码', + store_sms_login: '短信登录', + store_sms_verify_fail: '验证码校验失败', + store_login_success: '登录成功', + store_select: '选择门店', + store_wechat_login: '微信登录', + store_wechat_bind: '绑定微信', + store_home_view: '浏览首页', + store_redeem_entry_view: '进入核销页', + store_redeem_confirm_view: '核销确认页', + store_phone_redeem_view: '手机号核销页', + store_records_view: '浏览核销记录', + store_withdraw_view: '浏览提现页', + store_packages_view: '浏览套餐页', + store_mine_view: '浏览我的', + store_staff_view: '浏览子账号', + store_redeem_preview: '核销预览', + store_redeem_confirm: '确认核销', + store_redeem_confirm_fail: '核销失败', + store_redeem_weaknet_threshold: '弱网阈值', + store_redeem_pending_submit: '提交挂起核销', + store_redeem_pending_complete: '完成挂起核销', + store_redeem_pending_reject: '驳回挂起核销', + store_redeem_phone_lookup_sms: '手机号核销发码', + store_redeem_phone_balance: '查询用户权益余额', + store_redeem_phone_prepare: '手机号核销准备', + store_redeem_scan_start: '扫码核销开始', + store_payout_created: '生成打款单', + store_payout_paid: '打款完成', + store_withdraw_applied: '申请提现', + store_withdraw_paid: '提现已打款', + store_status_change: '变更营业状态', + store_staff_create: '新增子账号', + store_staff_update: '编辑子账号', + store_staff_delete: '删除子账号', + store_staff_permission_update: '更新子账号权限', + store_package_apply: '申请套餐', + store_package_audit_view: '查看套餐审核', +}; + +export const PARTNER_LOG_EVENT_LABELS: Record = { + partner_sms_send: '发送验证码', + partner_sms_login: '短信登录', + partner_sms_verify_fail: '验证码校验失败', + partner_login_success: '登录成功', + partner_wechat_login: '微信登录', + partner_wechat_bind: '绑定微信', + partner_home_view: '浏览首页', + partner_store_list_view: '浏览门店列表', + partner_store_detail_view: '浏览门店详情', + partner_store_create_view: '进入录店页', + partner_order_list_view: '浏览订单列表', + partner_order_detail_view: '浏览订单详情', + partner_proxy_order_view: '进入代下单页', + partner_bills_view: '浏览账单列表', + partner_bill_detail_view: '浏览账单详情', + partner_staff_list_view: '浏览子账号', + partner_leaderboard_view: '浏览排行榜', + partner_weekly_report_view: '浏览周报', + partner_staff_create: '新增子账号', + partner_staff_sms_send: '子账号发验证码', + partner_staff_sms_verify_fail: '子账号验证码失败', + partner_staff_update: '编辑子账号', + partner_staff_delete: '删除子账号', + partner_staff_permission_update: '更新子账号权限', + partner_store_create: '录入门店', + partner_store_status_change: '变更门店状态', + partner_store_audit_approved: '门店审核通过', + partner_store_audit_rejected: '门店审核驳回', + partner_order_ship: '订单发货', + partner_delivery_advance: '推进配送', + partner_proxy_order_create: '代下单', + partner_bill_view: '查看账单', + partner_bill_confirm: '确认账单', + partner_warehouse_view: '浏览仓库', + partner_warehouse_update: '更新仓库', +}; + +export const EVENT_NAME_LABELS: Record = { + ...USER_LOG_EVENT_LABELS, + ...STORE_LOG_EVENT_LABELS, + ...PARTNER_LOG_EVENT_LABELS, +}; + +export const THIRD_PARTY_PROVIDER_LABELS: Record = { + WECHAT_AUTH: '微信授权', + WECHAT_PAY: '微信支付', + WECHAT_REFUND: '微信退款', + WECHAT_MAP: '微信/腾讯地图', + ALIYUN_OSS: '阿里云 OSS', + ALIYUN_SMS: '阿里云短信', + MOCK_SMS: 'Mock 短信', + SMS: '短信', + XFX: '小飞侠', + LOGISTICS: '物流快递', + MANUAL: '手动', +}; + +export const THIRD_PARTY_STATUS_LABELS: Record = { + SUCCESS: '成功', + FAILED: '失败', + PENDING: '处理中', +}; + +export const THIRD_PARTY_SCENE_LABELS: Record = { + LOGIN: '登录', + USERINFO: '用户信息', + JSSDK_CONFIG: 'JSSDK 配置', + BIND_PHONE: '绑定手机号', + GET_LOCATION: '获取定位', + CHOOSE_IMAGE: '选择图片', + ORDER_PAY: '订单支付', + ORDER_REFUND: '订单退款', + UPLOAD_SHIPPING: '上传发货信息', + TRADE_MANAGE_PUSH: '交易结算推送', + ORDER_DISPATCH: '配送下单', + ORDER_DISPATCH_HOLD: '配送暂不下发', + TRACK_CALLBACK: '物流回调', + UPLOAD_TOKEN: '上传凭证', + UPLOAD_PUT_OBJECT: '上传文件', + GEOCODE: '地理编码', + REVERSE_GEOCODE: '逆地理编码', +}; + +export const WECOM_BOT_ACTION_LABELS: Record = { + ...WECOM_BOT_PERMISSION_LABELS, + 'user.read_sms.start': '查询用户·发送验证码', + 'user.read_sms.verify': '查询用户·校验验证码', +}; + +export const GENERIC_STATUS_LABELS: Record = { + ...ORDER_STATUS_LABELS, + ...LEDGER_TYPE_LABELS, + ...THIRD_PARTY_STATUS_LABELS, + PENDING: '待处理', + APPROVED: '已通过', + REJECTED: '已驳回', + ACTIVE: '正常', + DISABLED: '停用', + SUCCESS: '成功', + FAILED: '失败', + OPEN: '营业中', + PAUSED: '已暂停', + CLOSED: '已关闭', +}; + +export const HQ_ROLE_LABELS: Record = Object.fromEntries( + HQ_ADMIN_ROLES.map((r) => [r.value, r.label]), +); + +const CODE_ZH: Record = { + ...CLIENT_APP_LABELS, + ...REF_TYPE_LABELS, + ...DOMAIN_EVENT_TYPE_LABELS, + ...EVENT_NAME_LABELS, + ...ORDER_STATUS_LABELS, + ...ORDER_STATUS_OPERATOR_LABELS, + ...LEDGER_TYPE_LABELS, + ...HQ_OPERATION_ACTION_LABELS, + ...HQ_ROLE_LABELS, + ...THIRD_PARTY_PROVIDER_LABELS, + ...THIRD_PARTY_STATUS_LABELS, + ...THIRD_PARTY_SCENE_LABELS, + ...WECOM_BOT_ACTION_LABELS, + ...GENERIC_STATUS_LABELS, + ...PARAM_DESC_LABELS, +}; + +export function zhCode(value?: string | null, empty = '—'): string { + if (value == null || value === '') return empty; + return CODE_ZH[value] ?? value; +} + +export function eventNameLabel(value?: string | null): string { + return zhLabel(EVENT_NAME_LABELS, value); +} + +export function clientAppLabel(value?: string | null): string { + return zhLabel(CLIENT_APP_LABELS, value); +} + +export function formatRef(refType?: string | null, refId?: string | null): string { + if (!refType && !refId) return '—'; + const type = zhLabel(REF_TYPE_LABELS, refType, refType || ''); + return refId ? `${type} #${refId}` : type; +} + +export function userEventCategoryLabel(eventName: string, category?: string | null): string { + const key = category || resolveUserLogCategory(eventName); + if (key && USER_LOG_CATEGORY_LABELS[key]) return USER_LOG_CATEGORY_LABELS[key]; + return '其他'; +} + +export function storeEventCategoryLabel(eventName: string, category?: string | null): string { + const key = category || resolveStoreLogCategory(eventName); + const labels = STORE_LOG_CATEGORY_LABELS as Record; + if (key && labels[key]) return labels[key]; + return '其他'; +} + +export function partnerEventCategoryLabel(eventName: string, category?: string | null): string { + const key = category || resolvePartnerLogCategory(eventName); + const labels = PARTNER_LOG_CATEGORY_LABELS as Record; + if (key && labels[key]) return labels[key]; + return '其他'; +} + +export function domainParamFieldLabel(desc?: string | null, fallbackIndex?: 1 | 2 | 3): string { + if (desc && PARAM_DESC_LABELS[desc]) return PARAM_DESC_LABELS[desc]; + if (fallbackIndex) return `参数${fallbackIndex}`; + return '参数'; +} + +export function zhRemark(value?: string | null): string { + if (!value) return '—'; + return value + .replace(/partnerAccountId=/g, '合伙人账号=') + .replace(/hqAccountId=/g, 'HQ账号=') + .replace(/deliveryMode=/g, '履约方式=') + .replace(/\bmode=/g, '履约方式=') + .replace(/customer=/g, '客户=') + .replace(/\bON_SITE_PICKUP\b/g, '现场提货') + .replace(/\bCROSS_CITY\b/g, '跨城') + .replace(/\bLOCAL\b/g, '同城'); +} + +export function domainParamValue(desc?: string | null, value?: string | null): string { + if (value == null || value === '') return '—'; + if (desc === 'from_status' || desc === 'to_status') return zhLabel(ORDER_STATUS_LABELS, value); + if (desc === 'operator') return zhLabel(ORDER_STATUS_OPERATOR_LABELS, value); + if (desc === 'ledger_type') return zhLabel(LEDGER_TYPE_LABELS, value); + if (desc === 'action') return zhLabel(HQ_OPERATION_ACTION_LABELS, value); + if (desc === 'target_type') return zhLabel(REF_TYPE_LABELS, value); + return zhCode(value); +} diff --git a/apps/admin-web/src/pages/BigScreenPage.tsx b/apps/admin-web/src/pages/BigScreenPage.tsx index bb4214c..76e7f54 100644 --- a/apps/admin-web/src/pages/BigScreenPage.tsx +++ b/apps/admin-web/src/pages/BigScreenPage.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'; import { request } from '../lib/api'; +import bgmSrc from '../assets/big-screen/dukang-bgm.mp3'; import sfxTier1 from '../assets/big-screen/celebrate-t1.mp3'; import sfxTier2 from '../assets/big-screen/celebrate-t2.mp3'; import sfxTier3 from '../assets/big-screen/celebrate-t3.mp3'; @@ -33,6 +34,50 @@ const CELEBRATE_SFX: Record = { /** 浏览器自动播放策略:需用户手势后才能出声 */ let audioUnlocked = false; const sharedAudio = typeof Audio !== 'undefined' ? new Audio() : null; +const BGM_VOLUME = 0.32; +const BGM_DUCK_VOLUME = 0.08; +let bgmAudio: HTMLAudioElement | null = null; +let bgmDucked = false; + +function getBgmAudio(): HTMLAudioElement | null { + if (typeof Audio === 'undefined') return null; + if (!bgmAudio) { + bgmAudio = new Audio(bgmSrc); + bgmAudio.loop = true; + bgmAudio.preload = 'auto'; + bgmAudio.volume = BGM_VOLUME; + } + return bgmAudio; +} + +function applyBgmVolume() { + if (!bgmAudio) return; + bgmAudio.volume = bgmDucked ? BGM_DUCK_VOLUME : BGM_VOLUME; +} + +function duckBgm() { + bgmDucked = true; + applyBgmVolume(); +} + +function unduckBgm() { + bgmDucked = false; + applyBgmVolume(); +} + +function setBgmPlaying(on: boolean): Promise { + const audio = getBgmAudio(); + if (!audio) return Promise.resolve(false); + applyBgmVolume(); + if (!on) { + audio.pause(); + return Promise.resolve(false); + } + return audio + .play() + .then(() => true) + .catch(() => false); +} function unlockCelebrateAudio() { if (audioUnlocked || !sharedAudio) return; @@ -53,6 +98,7 @@ function unlockCelebrateAudio() { function playCelebrateSfx(tier: AmountTier): () => void { if (!sharedAudio) return () => undefined; + duckBgm(); try { sharedAudio.pause(); sharedAudio.currentTime = 0; @@ -72,6 +118,7 @@ function playCelebrateSfx(tier: AmountTier): () => void { } catch { /* ignore */ } + unduckBgm(); }; } @@ -341,10 +388,40 @@ function CelebrateFx({ order, onDone }: { order: BigScreenOrder; onDone: () => v ); } +function BgmToggleButton({ on, onToggle }: { on: boolean; onToggle: () => void }) { + return ( + + ); +} + export default function BigScreenPage() { const [items, setItems] = useState([]); const [loadError, setLoadError] = useState(''); const [paused, setPaused] = useState(false); + const [bgmOn, setBgmOn] = useState(false); + const bgmWantedRef = useRef(false); const [celebrate, setCelebrate] = useState(null); const seenIdsRef = useRef | null>(null); const celebratedIdsRef = useRef>(new Set()); @@ -416,6 +493,22 @@ export default function BigScreenPage() { return () => clearInterval(id); }, [fetchData]); + const toggleBgm = useCallback(() => { + unlockCelebrateAudio(); + const next = !bgmWantedRef.current; + bgmWantedRef.current = next; + if (!next) { + void setBgmPlaying(false); + setBgmOn(false); + return; + } + setBgmOn(true); + void setBgmPlaying(true).then((playing) => { + if (!bgmWantedRef.current) return; + setBgmOn(playing); + }); + }, []); + useEffect(() => { unlockCelebrateAudio(); const unlock = () => unlockCelebrateAudio(); @@ -424,6 +517,8 @@ export default function BigScreenPage() { return () => { window.removeEventListener('pointerdown', unlock); window.removeEventListener('keydown', unlock); + bgmWantedRef.current = false; + void setBgmPlaying(false); }; }, []); @@ -480,6 +575,7 @@ export default function BigScreenPage() { · 发布会现场
+ 实时成交 diff --git a/apps/admin-web/src/pages/DomainEventsPage.tsx b/apps/admin-web/src/pages/DomainEventsPage.tsx index baa5a37..034e38e 100644 --- a/apps/admin-web/src/pages/DomainEventsPage.tsx +++ b/apps/admin-web/src/pages/DomainEventsPage.tsx @@ -4,6 +4,14 @@ 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'; +import { + DOMAIN_EVENT_TYPE_LABELS, + domainParamFieldLabel, + domainParamValue, + formatRef, + zhCode, + zhLabel, +} from '../lib/display-labels'; import { useAdminListColumns } from '../lib/useAdminListColumns'; import { AdminPrimaryLink } from '../components/AdminPrimaryLink'; @@ -15,8 +23,11 @@ type Row = { refId: string; status?: string | null; param1?: string | null; + param1Desc?: string | null; param2?: string | null; + param2Desc?: string | null; param3?: string | null; + param3Desc?: string | null; remark?: string | null; createdAt: string; }; @@ -70,13 +81,18 @@ export default function DomainEventsPage() { setDetail(await request(`/admin/logs/domain-events/${r.id}`)); }} > - {v} + {zhLabel(DOMAIN_EVENT_TYPE_LABELS, v)} ), }, - { title: '关联', render: (_, r) => `${r.refType} #${r.refId}` }, - { title: '状态', dataIndex: 'status', width: 100 }, - { title: '摘要', render: (_, r) => r.param1 || r.remark || '—' }, + { title: '关联', render: (_, r) => formatRef(r.refType, r.refId) }, + { title: '状态', dataIndex: 'status', width: 100, render: (v) => zhCode(v) }, + { + title: '摘要', + render: (_, r) => domainParamValue(r.param1Desc, r.param1) !== '—' + ? domainParamValue(r.param1Desc, r.param1) + : (r.remark || '—'), + }, { title: '操作', width: 80, @@ -133,11 +149,18 @@ export default function DomainEventsPage() { {detail && ( {detail.id} - {detail.eventType} - {`${detail.refType} #${detail.refId}`} - {detail.param1} - {detail.param2} - {detail.param3} + {zhLabel(DOMAIN_EVENT_TYPE_LABELS, detail.eventType)} + {formatRef(detail.refType, detail.refId)} + {zhCode(detail.status)} + + {domainParamValue(detail.param1Desc, detail.param1)} + + + {domainParamValue(detail.param2Desc, detail.param2)} + + + {domainParamValue(detail.param3Desc, detail.param3)} + {detail.remark} {fmtTime(detail.createdAt)} diff --git a/apps/admin-web/src/pages/HqLogsPage.tsx b/apps/admin-web/src/pages/HqLogsPage.tsx index eb7110b..1ac2e9e 100644 --- a/apps/admin-web/src/pages/HqLogsPage.tsx +++ b/apps/admin-web/src/pages/HqLogsPage.tsx @@ -7,6 +7,7 @@ import { useSearchParams } from 'react-router-dom'; import { request } from '../lib/api'; import { AdminCellLine } from '../components/AdminCellLine'; import { fmtTime } from '../lib/constants'; +import { formatRef, zhCode } from '../lib/display-labels'; import { HQ_OPERATION_ACTION_OPTIONS, resolveHqOperationLabel } from '../lib/hq-log'; import { useAdminList } from '../lib/useAdminList'; import { useAdminListColumns } from '../lib/useAdminListColumns'; @@ -84,7 +85,7 @@ export default function HqLogsPage() { ), }, - { title: '对象类型', dataIndex: 'refType', width: 120, render: (v) => v || '—' }, + { title: '对象类型', dataIndex: 'refType', width: 120, render: (v) => zhCode(v) }, { title: '对象 ID', dataIndex: 'refId', width: 100, render: (v) => v || '—' }, { title: '操作', @@ -143,7 +144,7 @@ export default function HqLogsPage() { /> - + @@ -192,12 +193,12 @@ export default function HqLogsPage() { secondary={[detail.hqPhone, detail.hqAccountId ? `ID:${detail.hqAccountId}` : ''].filter(Boolean).join(' ') || null} /> - {detail.hqRole || '—'} + {zhCode(detail.hqRole)} {detail.actionLabel || resolveHqOperationLabel(detail.action)} - {detail.refType} / {detail.refId} - {detail.status || '—'} + {formatRef(detail.refType, detail.refId)} + {zhCode(detail.status)} {detail.remark || '—'} 请求/响应快照 diff --git a/apps/admin-web/src/pages/OrdersPage.tsx b/apps/admin-web/src/pages/OrdersPage.tsx index 8949c01..5771ea4 100644 --- a/apps/admin-web/src/pages/OrdersPage.tsx +++ b/apps/admin-web/src/pages/OrdersPage.tsx @@ -39,6 +39,7 @@ import { ORDER_STATUS_OPERATOR_LABELS, fmtTime, } from '../lib/constants'; +import { zhRemark } from '../lib/display-labels'; import { FULFILLMENT_HOLD_REASON_LABELS } from '@dukang/shared-types'; import ProxyOrderModal from '../components/ProxyOrderModal'; import OrderTrackDrawer from '../components/OrderTrackDrawer'; @@ -1210,7 +1211,7 @@ export default function OrdersPage() { { title: '备注', dataIndex: 'remark', - render: (v) => v || '—', + render: (v) => zhRemark(v), }, { title: '时间', diff --git a/apps/admin-web/src/pages/PartnerLogsPage.tsx b/apps/admin-web/src/pages/PartnerLogsPage.tsx index dde320f..b9f8a53 100644 --- a/apps/admin-web/src/pages/PartnerLogsPage.tsx +++ b/apps/admin-web/src/pages/PartnerLogsPage.tsx @@ -4,15 +4,14 @@ import type { ColumnsType } from 'antd/es/table'; import { useSearchParams } from 'react-router-dom'; import { PARTNER_LOG_CATEGORY_OPTIONS, - PARTNER_LOG_CATEGORY_LABELS, PARTNER_STAFF_ROLE_LABELS, - resolvePartnerLogCategory, 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'; @@ -107,7 +106,7 @@ export default function PartnerLogsPage() { dataIndex: 'category', width: 100, render: (v: PartnerLogCategory | null, r) => ( - {PARTNER_LOG_CATEGORY_LABELS[v ?? ''] || resolvePartnerLogCategory(r.eventName) || '其他'} + {partnerEventCategoryLabel(r.eventName, v)} ), }, { @@ -121,14 +120,14 @@ export default function PartnerLogsPage() { setDrawerOpen(true); }} > - {v} + {eventNameLabel(v)} ), }, { title: '关联', width: 120, - render: (_, r) => (r.refType && r.refId ? `${r.refType}#${r.refId}` : '—'), + render: (_, r) => formatRef(r.refType, r.refId), }, { title: '摘要', @@ -188,11 +187,11 @@ export default function PartnerLogsPage() { - - + + - - + + @@ -237,11 +236,19 @@ export default function PartnerLogsPage() { setDrawerOpen(false)} width={520}> {detail && ( - {Object.entries(detail).map(([k, v]) => ( - - {typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v ?? '—')} - - ))} + {String(detail.id ?? '—')} + {String(detail.companyName || detail.partnerId || '—')} + {String(detail.accountName || detail.accountPhone || '—')} + {partnerEventCategoryLabel(String(detail.eventName ?? ''), detail.category ? String(detail.category) : null)} + {eventNameLabel(String(detail.eventName ?? ''))} + {clientAppLabel(detail.clientApp ? String(detail.clientApp) : null)} + {formatRef(detail.refType ? String(detail.refType) : null, detail.refId ? String(detail.refId) : null)} + {fmtTime(detail.createdAt ? String(detail.createdAt) : null)} + +
+                {JSON.stringify(detail.extraJson ?? {}, null, 2)}
+              
+
)}
diff --git a/apps/admin-web/src/pages/StoreCategoriesPage.tsx b/apps/admin-web/src/pages/StoreCategoriesPage.tsx index b037bec..5d34513 100644 --- a/apps/admin-web/src/pages/StoreCategoriesPage.tsx +++ b/apps/admin-web/src/pages/StoreCategoriesPage.tsx @@ -159,9 +159,20 @@ export default function StoreCategoriesPage() { { - await request(`/admin/store-categories/${row.id}`, { method: 'DELETE' }); - message.success('已处理'); - void reload(); + try { + const res = await request<{ deleted?: boolean; softDisabled?: boolean }>( + `/admin/store-categories/${row.id}`, + { method: 'DELETE' }, + ); + if (res.softDisabled) { + message.success('该分类已有门店占用,已改为停用'); + } else { + message.success('已删除'); + } + void reload(); + } catch (e) { + message.error(e instanceof Error ? e.message : '删除失败'); + } }} > diff --git a/apps/admin-web/src/pages/StoreLogsPage.tsx b/apps/admin-web/src/pages/StoreLogsPage.tsx index a63395d..7ea0646 100644 --- a/apps/admin-web/src/pages/StoreLogsPage.tsx +++ b/apps/admin-web/src/pages/StoreLogsPage.tsx @@ -4,13 +4,12 @@ import type { ColumnsType } from 'antd/es/table'; import { useSearchParams } from 'react-router-dom'; import { STORE_LOG_CATEGORY_OPTIONS, - STORE_LOG_CATEGORY_LABELS, - resolveStoreLogCategory, type StoreLogCategory, } from '../lib/store-log'; import { request } from '../lib/api'; import { AdminCellLine } from '../components/AdminCellLine'; import { fmtTime } from '../lib/constants'; +import { clientAppLabel, eventNameLabel, formatRef, storeEventCategoryLabel } from '../lib/display-labels'; import { useAdminList } from '../lib/useAdminList'; import { useAdminListColumns } from '../lib/useAdminListColumns'; import { AdminListHeader } from '../components/AdminListHeader'; @@ -102,7 +101,7 @@ export default function StoreLogsPage() { { title: '分类', dataIndex: 'category', width: 100, render: (v: StoreLogCategory | null, r) => ( - {STORE_LOG_CATEGORY_LABELS[v ?? ''] || resolveStoreLogCategory(r.eventName) || '其他'} + {storeEventCategoryLabel(r.eventName, v)} ), }, { @@ -118,7 +117,7 @@ export default function StoreLogsPage() { setDrawerOpen(true); }} > - {v} + {eventNameLabel(v)} ), }, @@ -128,7 +127,7 @@ export default function StoreLogsPage() { }, { title: '关联', width: 120, - render: (_, r) => (r.refType && r.refId ? `${r.refType}#${r.refId}` : '—'), + render: (_, r) => formatRef(r.refType, r.refId), }, { title: '摘要', @@ -184,7 +183,7 @@ export default function StoreLogsPage() { - + @@ -198,12 +197,12 @@ export default function StoreLogsPage() { {String(detail.accountName || detail.storeAccountId || '系统/HQ')} {String(detail.accountPhone || '—')} - {(STORE_LOG_CATEGORY_LABELS as Record)[String(detail.category ?? '')] || String(detail.category || '—')} + {storeEventCategoryLabel(String(detail.eventName ?? ''), detail.category ? String(detail.category) : null)} - {String(detail.eventName)} + {eventNameLabel(String(detail.eventName ?? ''))} {SOURCE_LABELS[String(detail.source) as Row['source']] || String(detail.source || '—')} - {String(detail.clientApp || '—')} - {detail.refType ? `${String(detail.refType)}#${String(detail.refId)}` : '—'} + {clientAppLabel(detail.clientApp ? String(detail.clientApp) : null)} + {formatRef(detail.refType ? String(detail.refType) : null, detail.refId ? String(detail.refId) : null)} {fmtTime(String(detail.createdAt))}
diff --git a/apps/admin-web/src/pages/ThirdPartyLogsPage.tsx b/apps/admin-web/src/pages/ThirdPartyLogsPage.tsx
index b3c82be..2257954 100644
--- a/apps/admin-web/src/pages/ThirdPartyLogsPage.tsx
+++ b/apps/admin-web/src/pages/ThirdPartyLogsPage.tsx
@@ -3,6 +3,13 @@ import { Button, Descriptions, Drawer, Form, Input, Select, Table, Tag, Typograp
 import type { ColumnsType } from 'antd/es/table';
 import { request } from '../lib/api';
 import { fmtTime } from '../lib/constants';
+import {
+  THIRD_PARTY_PROVIDER_LABELS,
+  THIRD_PARTY_SCENE_LABELS,
+  THIRD_PARTY_STATUS_LABELS,
+  formatRef,
+  zhLabel,
+} from '../lib/display-labels';
 import { useAdminList } from '../lib/useAdminList';
 import { useAdminListColumns } from '../lib/useAdminListColumns';
 import { AdminListHeader } from '../components/AdminListHeader';
@@ -26,14 +33,14 @@ type Row = {
 };
 
 const PROVIDER_OPTIONS = [
-  { value: 'WECHAT_AUTH', label: 'WECHAT_AUTH' },
-  { value: 'WECHAT_PAY', label: 'WECHAT_PAY' },
-  { value: 'WECHAT_MAP', label: 'WECHAT_MAP' },
-  { value: 'ALIYUN_OSS', label: 'ALIYUN_OSS' },
-  { value: 'ALIYUN_SMS', label: 'ALIYUN_SMS' },
-  { value: 'MOCK_SMS', label: 'MOCK_SMS' },
-  { value: 'XFX', label: '小飞侠 (XFX)' },
-  { value: 'LOGISTICS', label: 'LOGISTICS' },
+  { value: 'WECHAT_AUTH', label: '微信授权' },
+  { value: 'WECHAT_PAY', label: '微信支付' },
+  { value: 'WECHAT_MAP', label: '微信/腾讯地图' },
+  { value: 'ALIYUN_OSS', label: '阿里云 OSS' },
+  { value: 'ALIYUN_SMS', label: '阿里云短信' },
+  { value: 'MOCK_SMS', label: 'Mock 短信' },
+  { value: 'XFX', label: '小飞侠' },
+  { value: 'LOGISTICS', label: '物流快递' },
 ];
 
 const STATUS_COLOR: Record = {
@@ -75,18 +82,18 @@ export default function ThirdPartyLogsPage() {
 
   const baseColumns: ColumnsType = [
     { title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
-    { title: 'Provider', dataIndex: 'provider', width: 120 },
-    { title: '场景', dataIndex: 'scene', width: 120 },
+    { title: '渠道', dataIndex: 'provider', width: 120, render: (v: string) => zhLabel(THIRD_PARTY_PROVIDER_LABELS, v) },
+    { title: '场景', dataIndex: 'scene', width: 140, render: (v: string) => zhLabel(THIRD_PARTY_SCENE_LABELS, v) },
     {
       title: '关联',
       width: 120,
-      render: (_, r) => (r.refType && r.refId ? `${r.refType}#${r.refId}` : '—'),
+      render: (_, r) => formatRef(r.refType, r.refId),
     },
     {
       title: '状态',
       dataIndex: 'status',
       width: 90,
-      render: (v: string) => {v},
+      render: (v: string) => {zhLabel(THIRD_PARTY_STATUS_LABELS, v)},
     },
     {
       title: '错误信息',
@@ -147,11 +154,11 @@ export default function ThirdPartyLogsPage() {
           setPage(1);
         }}
       >
-        
+        
           
+          
         
         
           
@@ -181,13 +188,15 @@ export default function ThirdPartyLogsPage() {
         {detail && (
           
             {String(detail.id)}
-            {String(detail.provider)}
-            {String(detail.scene)}
+            {zhLabel(THIRD_PARTY_PROVIDER_LABELS, String(detail.provider))}
+            {zhLabel(THIRD_PARTY_SCENE_LABELS, String(detail.scene))}
             
-              {String(detail.status)}
+              
+                {zhLabel(THIRD_PARTY_STATUS_LABELS, String(detail.status))}
+              
             
             
-              {detail.refType ? `${String(detail.refType)}#${String(detail.refId)}` : '—'}
+              {formatRef(detail.refType ? String(detail.refType) : null, detail.refId ? String(detail.refId) : null)}
             
             {String(detail.externalNo ?? '—')}
             {detail.amount != null ? String(detail.amount) : '—'}
diff --git a/apps/admin-web/src/pages/UserLogsPage.tsx b/apps/admin-web/src/pages/UserLogsPage.tsx
index b9a5d91..d262461 100644
--- a/apps/admin-web/src/pages/UserLogsPage.tsx
+++ b/apps/admin-web/src/pages/UserLogsPage.tsx
@@ -1,11 +1,12 @@
 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 { USER_LOG_CATEGORY_OPTIONS, resolveUserLogCategory, USER_LOG_CATEGORY_LABELS, type UserLogCategory } from '../lib/user-log';
+import { USER_LOG_CATEGORY_OPTIONS, type UserLogCategory } from '../lib/user-log';
 import { useSearchParams } from 'react-router-dom';
 import { request } from '../lib/api';
 import { AdminCellLine } from '../components/AdminCellLine';
 import { fmtTime } from '../lib/constants';
+import { clientAppLabel, eventNameLabel, formatRef, userEventCategoryLabel } from '../lib/display-labels';
 import { useAdminList } from '../lib/useAdminList';
 import { useAdminListColumns } from '../lib/useAdminListColumns';
 import { AdminListHeader } from '../components/AdminListHeader';
@@ -27,8 +28,6 @@ type Row = {
   createdAt: string;
 };
 
-const CATEGORY_LABELS = USER_LOG_CATEGORY_LABELS;
-
 function summarizeExtra(json: Record | null) {
   if (!json) return '—';
   const text = JSON.stringify(json);
@@ -79,7 +78,7 @@ export default function UserLogsPage() {
     {
       title: '分类', dataIndex: 'category', width: 100,
       render: (v: UserLogCategory | null, r) => (
-        {CATEGORY_LABELS[v ?? ''] || resolveUserLogCategory(r.eventName) || '其他'}
+        {userEventCategoryLabel(r.eventName, v)}
       ),
     },
     {
@@ -93,13 +92,16 @@ export default function UserLogsPage() {
             setDrawerOpen(true);
           }}
         >
-          {v}
+          {eventNameLabel(v)}
         
       ),
     },
+    {
+      title: '端', dataIndex: 'clientApp', width: 110, render: (v) => clientAppLabel(v),
+    },
     {
       title: '关联', width: 120,
-      render: (_, r) => (r.refType && r.refId ? `${r.refType}#${r.refId}` : '—'),
+      render: (_, r) => formatRef(r.refType, r.refId),
     },
     {
       title: '摘要',
@@ -148,7 +150,7 @@ export default function UserLogsPage() {
         
         
         
-        
+        
         
         
       
@@ -160,10 +162,10 @@ export default function UserLogsPage() {
             {String(detail.id)}
             {String(detail.nickname || detail.userNo || detail.userId || '—')}
             {String(detail.phone || '—')}
-            {CATEGORY_LABELS[String(detail.category ?? '')] || String(detail.category || '—')}
-            {String(detail.eventName)}
-            {String(detail.clientApp || '—')}
-            {detail.refType ? `${String(detail.refType)}#${String(detail.refId)}` : '—'}
+            {userEventCategoryLabel(String(detail.eventName ?? ''), detail.category ? String(detail.category) : null)}
+            {eventNameLabel(String(detail.eventName ?? ''))}
+            {clientAppLabel(detail.clientApp ? String(detail.clientApp) : null)}
+            {formatRef(detail.refType ? String(detail.refType) : null, detail.refId ? String(detail.refId) : null)}
             {fmtTime(String(detail.createdAt))}
             
               
diff --git a/apps/admin-web/src/pages/UsersPage.tsx b/apps/admin-web/src/pages/UsersPage.tsx
index 26741ca..214ca09 100644
--- a/apps/admin-web/src/pages/UsersPage.tsx
+++ b/apps/admin-web/src/pages/UsersPage.tsx
@@ -17,12 +17,13 @@ import {
   message,
 } from 'antd';
 import type { ColumnsType } from 'antd/es/table';
-import { USER_SOURCE_TYPE_LABELS, resolveUserLogCategory, type UserSourceType } from '@dukang/shared-types';
+import { USER_SOURCE_TYPE_LABELS, type UserSourceType } from '@dukang/shared-types';
 import { request, type AdminUserRow, type HqProfile, type Paginated } from '../lib/api';
 import { useAdminListColumns } from '../lib/useAdminListColumns';
 import { AdminListHeader } from '../components/AdminListHeader';
 import { AdminPrimaryLink } from '../components/AdminPrimaryLink';
 import { ORDER_STATUS_LABELS, fmtTime, maskPhone } from '../lib/constants';
+import { clientAppLabel, eventNameLabel, userEventCategoryLabel } from '../lib/display-labels';
 
 type UserOrderRow = {
   id: string;
@@ -640,10 +641,10 @@ export default function UsersPage() {
                 {
                   title: '分类',
                   width: 100,
-                  render: (_, r) => resolveUserLogCategory(r.eventName) ?? '—',
+                  render: (_, r) => userEventCategoryLabel(r.eventName),
                 },
-                { title: '事件', dataIndex: 'eventName' },
-                { title: '端', dataIndex: 'clientApp', width: 90 },
+                { title: '事件', dataIndex: 'eventName', render: (v: string) => eventNameLabel(v) },
+                { title: '端', dataIndex: 'clientApp', width: 110, render: (v: string | null) => clientAppLabel(v) },
               ]}
             />
 
diff --git a/apps/admin-web/src/pages/WecomBotLogsPage.tsx b/apps/admin-web/src/pages/WecomBotLogsPage.tsx
index ee329be..f703815 100644
--- a/apps/admin-web/src/pages/WecomBotLogsPage.tsx
+++ b/apps/admin-web/src/pages/WecomBotLogsPage.tsx
@@ -4,6 +4,7 @@ import type { ColumnsType } from 'antd/es/table';
 import { useAdminList } from '../lib/useAdminList';
 import { request } from '../lib/api';
 import { fmtTime } from '../lib/constants';
+import { WECOM_BOT_ACTION_LABELS, zhLabel } from '../lib/display-labels';
 import type { WecomBotLogDto } from '@dukang/shared-types';
 import { useAdminListColumns } from '../lib/useAdminListColumns';
 import { AdminListHeader } from '../components/AdminListHeader';
@@ -39,8 +40,8 @@ export default function WecomBotLogsPage() {
       ),
     },
     { title: '企微用户', dataIndex: 'wecomUserId', width: 120 },
-    { title: '动作', dataIndex: 'action', width: 160 },
-    { title: '权限', dataIndex: 'permission', width: 140, render: (v) => v || '—' },
+    { title: '动作', dataIndex: 'action', width: 180, render: (v: string) => zhLabel(WECOM_BOT_ACTION_LABELS, v) },
+    { title: '权限', dataIndex: 'permission', width: 160, render: (v) => zhLabel(WECOM_BOT_ACTION_LABELS, v) },
     {
       title: '结果',
       dataIndex: 'success',
@@ -81,13 +82,13 @@ export default function WecomBotLogsPage() {
         }}
       >
         
-          
+          
         
-        
-          
+        
+          
         
         
-          
+