feat(admin): 对齐旧站后台布局与导航

This commit is contained in:
yml2213
2026-10-31 18:09:34 +08:00
parent 14003a9764
commit 13bda13633
21 changed files with 546 additions and 188 deletions
+6
View File
@@ -563,6 +563,7 @@ export function AppRouter() {
<Route path="withdrawals" element={<AdminWithdrawalsPage />} />
<Route path="disputes" element={<AdminDisputesPage />} />
<Route path="finance" element={<AdminFinancePage />} />
<Route path="finance/dashboard" element={<AdminFinancePage />} />
<Route path="finance/disbursements" element={<AdminFinanceDisbursementsPage />} />
<Route path="finance/operating-expenses" element={<AdminOperatingExpensesPage />} />
<Route path="announcements" element={<AdminAnnouncementsPage />} />
@@ -591,6 +592,7 @@ export function AppRouter() {
<Route path="chat-quick-replies" element={<AdminQuickRepliesPage />} />
<Route path="tracking-links" element={<AdminTrackingLinksPage />} />
<Route path="chat-support-groups" element={<AdminSupportGroupsPage />} />
<Route path="support-groups" element={<AdminSupportGroupsPage />} />
<Route path="distribution/sites" element={<AdminDistributionSitesPage />} />
<Route
path="distribution/dashboard"
@@ -621,6 +623,10 @@ export function AppRouter() {
element={<AdminDistributionInvitationPage />}
/>
<Route path="distribution/qrcodes" element={<AdminDistributionQrCodesPage />} />
<Route
path="distribution/qrcode-pool"
element={<AdminDistributionQrCodesPage />}
/>
<Route path="distribution/rules" element={<AdminDistributionRulesPage />} />
<Route
path="*"
@@ -61,7 +61,7 @@ export function AdminBackupsPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 系统" title="备份管理" />
<AdminPageHeader eyebrow="JIEYOU OPS · 系统" title="数据备份" />
<Alert type="warning" showIcon message="当前账号没有备份查看权限(backup:view)" />
</div>
)
@@ -78,7 +78,7 @@ export function AdminBackupsPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 系统"
title="备份管理"
title="数据备份"
description="查看数据库全量/增量备份状态,并维护定时备份计划。"
meta={
<Space size={8} wrap>
@@ -215,7 +215,7 @@ export function AdminChatsPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 客服" title="客服会话" />
<AdminPageHeader eyebrow="JIEYOU OPS · 客服" title="客服群聊" />
<Alert type="warning" showIcon message="当前账号没有聊天查看权限(chat:view)" />
</div>
)
@@ -225,7 +225,7 @@ export function AdminChatsPage() {
<div className="admin-page admin-chat-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 客服"
title="客服会话"
title="客服群聊"
description="按阶段筛选会话、查看用户消息并直接回复,支持转接与备注。"
meta={
<Space size={8} wrap>
@@ -172,7 +172,7 @@ export function AdminCrashCategoriesPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="摸红分类" />
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="撞车分类" />
<Alert type="warning" showIcon message="当前账号没有摸红商品权限" />
</div>
)
@@ -182,7 +182,7 @@ export function AdminCrashCategoriesPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 摸红"
title="摸红分类"
title="撞车分类"
description="维护撞车商城的商品分类与排序。"
meta={<Tag color="geekblue">共 {categoriesQuery.data?.length ?? 0} 个分类</Tag>}
extra={
@@ -35,8 +35,8 @@ export function AdminCrashConfigPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="摸红配置" />
<Alert type="warning" showIcon message="当前账号没有摸红配置权限" />
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="撞车配置" />
<Alert type="warning" showIcon message="当前账号没有撞车配置权限" />
</div>
)
}
@@ -45,7 +45,7 @@ export function AdminCrashConfigPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 摸红"
title="摸红配置"
title="撞车配置"
description="维护撞车订单默认固定二维码与订单一键复制模板。"
extra={
<Button
@@ -210,8 +210,8 @@ export function AdminCrashOrdersPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="摸红订单" />
<Alert type="warning" showIcon message="当前账号没有摸红订单权限" />
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="撞车订单" />
<Alert type="warning" showIcon message="当前账号没有撞车订单权限" />
</div>
)
}
@@ -220,7 +220,7 @@ export function AdminCrashOrdersPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 摸红"
title="摸红订单"
title="撞车订单"
description="处理撞车商城订单:接单、完成、取消与退款。"
meta={<Tag color="geekblue">共 {ordersQuery.data?.total ?? 0} 单</Tag>}
extra={
@@ -245,8 +245,8 @@ export function AdminCrashProductsPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="摸红商品" />
<Alert type="warning" showIcon message="当前账号没有摸红商品权限" />
<AdminPageHeader eyebrow="JIEYOU OPS · 摸红" title="撞车商品" />
<Alert type="warning" showIcon message="当前账号没有撞车商品权限" />
</div>
)
}
@@ -255,7 +255,7 @@ export function AdminCrashProductsPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 摸红"
title="摸红商品"
title="撞车商品"
description="维护撞车商城商品、价格、库存与固定二维码。"
meta={<Tag color="geekblue">共 {productsQuery.data?.total ?? 0} 个商品</Tag>}
extra={
@@ -173,7 +173,7 @@ export function AdminDisputesPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 交易"
title="申诉仲裁"
title="仲裁中心"
description="核对申诉描述、证据与结账快照,给出可追溯的仲裁结果。"
meta={
<Space size={8} wrap>
@@ -504,7 +504,7 @@ export function AdminDistributionQrCodesPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 分销"
title="二维码池"
title="分站二维码池"
description="每个分站只从自己的码池发码;池空等待补码,不会混发主站或其他分站二维码。"
meta={
<Space size={8} wrap>
@@ -165,7 +165,7 @@ export function AdminFinanceDetailsPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader title="资金明细" eyebrow="JIEYOU OPS · 财务" />
<AdminPageHeader title="财务明细" eyebrow="JIEYOU OPS · 财务" />
<Alert type="warning" showIcon message="当前账号没有资金查看权限" />
</div>
)
@@ -175,7 +175,7 @@ export function AdminFinanceDetailsPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 财务"
title="资金明细"
title="财务明细"
description="按订单维度核对实付、退款、号主结算与平台净额。"
meta={
<Space size={8} wrap>
@@ -256,7 +256,7 @@ export function AdminFinanceDetailsPage() {
type="error"
showIcon
className="admin-table-alert"
message="资金明细加载失败"
message="财务明细加载失败"
description={errorText(detailsQuery.error)}
/>
) : null}
@@ -273,7 +273,7 @@ export function AdminFinanceDetailsPage() {
setPage(nextPage)
setPageSize(nextSize)
}}
emptyDescription="所选区间还没有资金明细"
emptyDescription="所选区间还没有财务明细"
/>
</div>
)
@@ -265,7 +265,7 @@ export function AdminFinancePage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 财务"
title="财务看板"
title="财务仪表盘"
description="按时间区间核对流水、退款、平台收入、号主结算与出款压力。"
meta={
<Space size={8} wrap>
@@ -240,7 +240,7 @@ export function AdminInvitationsPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 运营" title="邀请管理" />
<AdminPageHeader eyebrow="JIEYOU OPS · 运营" title="邀请返利管理" />
<Alert type="warning" showIcon message="当前账号没有邀请数据查看权限" />
</div>
)
@@ -252,7 +252,7 @@ export function AdminInvitationsPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 运营"
title="邀请管理"
title="邀请返利管理"
description="维护邀请返利比例,查看邀请关系与返利流水;比例按百分比录入,提交时转 0–1 小数。"
meta={
<Space size={8} wrap>
@@ -366,8 +366,8 @@ export function AdminManualEntriesPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 客服" title="手工录单" />
<Alert type="warning" showIcon message="当前账号没有手工录单查看权限" />
<AdminPageHeader eyebrow="JIEYOU OPS · 客服" title="手动录单" />
<Alert type="warning" showIcon message="当前账号没有手动录单查看权限" />
</div>
)
}
@@ -376,7 +376,7 @@ export function AdminManualEntriesPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 客服"
title="手工录单"
title="手动录单"
description="客服在系统外成单后录入编号,用于对账与客服绩效统计。"
meta={
<Space size={8} wrap>
@@ -270,7 +270,7 @@ export function AdminRolesPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 权限"
title="角色权限"
title="角色管理"
description="维护后台角色与权限矩阵;系统内置角色不可删除。"
meta={
<Space size={8} wrap>
@@ -328,7 +328,7 @@ export function AdminStaffAccountsPage() {
if (!canManage) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 系统" title="管理员账号" />
<AdminPageHeader eyebrow="JIEYOU OPS · 系统" title="管理员管理" />
<Alert type="warning" showIcon message="当前账号没有管理员账号管理权限" />
</div>
)
@@ -338,7 +338,7 @@ export function AdminStaffAccountsPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 系统"
title="管理员账号"
title="管理员管理"
description="维护后台账号、启用状态、角色绑定与密码重置。"
meta={
<Space size={8} wrap>
@@ -238,7 +238,7 @@ export function AdminSupportGroupsPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 客服"
title="客服群组"
title="客服分组"
description="维护客服分组、在线成员与排序,用于会话分配与订单客服群。"
meta={
<Space size={8} wrap>
@@ -322,8 +322,8 @@ export function AdminTrackingLinksPage() {
if (!canManage) {
return (
<div className="admin-page">
<AdminPageHeader eyebrow="JIEYOU OPS · 运营" title="推广追踪" />
<Alert type="warning" showIcon message="当前账号没有推广追踪管理权限" />
<AdminPageHeader eyebrow="JIEYOU OPS · 运营" title="推广链接管理" />
<Alert type="warning" showIcon message="当前账号没有推广链接管理管理权限" />
</div>
)
}
@@ -332,7 +332,7 @@ export function AdminTrackingLinksPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 运营"
title="推广追踪"
title="推广链接管理"
description="管理推广短链、渠道成本与注册下单转化数据。"
meta={
<Space size={8} wrap>
@@ -125,7 +125,7 @@ export function AdminWalletLedgerPage() {
if (!canView) {
return (
<div className="admin-page">
<AdminPageHeader title="钱包流水" eyebrow="JIEYOU OPS · 财务" />
<AdminPageHeader title="资金流水" eyebrow="JIEYOU OPS · 财务" />
<Alert type="warning" showIcon message="当前账号没有资金查看权限" />
</div>
)
@@ -135,7 +135,7 @@ export function AdminWalletLedgerPage() {
<div className="admin-page">
<AdminPageHeader
eyebrow="JIEYOU OPS · 财务"
title="钱包流水"
title="资金流水"
description="按用户、订单与业务类型核对每一笔余额变动。"
meta={
<Space size={8} wrap>
@@ -197,7 +197,7 @@ export function AdminWalletLedgerPage() {
type="error"
showIcon
className="admin-table-alert"
message="钱包流水加载失败"
message="资金流水加载失败"
description={errorText(ledgerQuery.error)}
/>
) : null}
@@ -214,7 +214,7 @@ export function AdminWalletLedgerPage() {
setPage(nextPage)
setPageSize(nextSize)
}}
emptyDescription="还没有钱包流水"
emptyDescription="还没有资金流水"
/>
</div>
)
+7
View File
@@ -169,6 +169,13 @@ export async function fetchAdminMe() {
return data.data
}
export async function updateAdminSupportStatus(status: 'online' | 'offline' | 'busy') {
const { data } = await adminClient.put<
ApiResponse<{ updated: boolean; support_status: string }>
>('/admin/me/support-status', { status })
return data.data
}
export async function logoutAdmin() {
await adminClient.post('/admin/auth/logout')
}
+404 -149
View File
@@ -1,35 +1,58 @@
import { useState, type ReactNode } from 'react'
import { useEffect, useState, type ReactNode } from 'react'
import { useQuery } from '@tanstack/react-query'
import {
App,
Avatar,
Breadcrumb,
Button,
Dropdown,
Layout,
Menu,
Space,
Tag,
Typography,
type MenuProps,
} from 'antd'
import {
AccountBookOutlined,
ApartmentOutlined,
AppstoreOutlined,
BankOutlined,
BellOutlined,
CloudServerOutlined,
ControlOutlined,
CreditCardOutlined,
CustomerServiceOutlined,
DashboardOutlined,
FileDoneOutlined,
FileSearchOutlined,
FileTextOutlined,
FormOutlined,
GiftOutlined,
LineChartOutlined,
LinkOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
MessageOutlined,
MoneyCollectOutlined,
NotificationOutlined,
ProfileOutlined,
QrcodeOutlined,
RollbackOutlined,
SafetyCertificateOutlined,
SafetyOutlined,
SettingOutlined,
ShareAltOutlined,
ShopOutlined,
ShoppingCartOutlined,
SoundOutlined,
SwapOutlined,
TeamOutlined,
UserSwitchOutlined,
} from '@ant-design/icons'
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'
import { BrandLogo } from '@/components/BrandLogo'
import { fetchAdminNotificationUnreadCount, updateAdminSupportStatus } from '@/features/admin/api'
import { getAdminBasePath, getAdminLoginPath } from '@/shared/api/adminClient'
import { useAdminSessionStore } from '@/stores/adminSession'
@@ -38,6 +61,7 @@ const { Sider, Header, Content } = Layout
interface NavItem {
key: string
label: string
icon?: ReactNode
permission?: string
}
@@ -53,131 +77,270 @@ const allNavGroups: NavGroup[] = [
key: 'workspace',
label: '工作台',
icon: <DashboardOutlined />,
children: [{ key: '/', label: '仪表盘', permission: 'dashboard:view' }],
},
{
key: 'trade',
label: '交易',
icon: <ShoppingCartOutlined />,
children: [
{ key: '/orders', label: '订单管理', permission: 'order:list' },
{ key: '/orders/refund-review', label: '退款审核', permission: 'order:close' },
{ key: '/pickup', label: '线下提号', permission: 'order:pickup' },
{ key: '/disputes', label: '申诉仲裁', permission: 'dispute:view' },
{ key: '/', label: '仪表盘', icon: <DashboardOutlined />, permission: 'dashboard:view' },
],
},
{
key: 'listings',
label: '商品',
icon: <ShopOutlined />,
key: 'business',
label: '业务管理',
icon: <AppstoreOutlined />,
children: [
{ key: '/listings', label: '商品管理', permission: 'listing:view' },
{ key: '/listings/review', label: '商品审核', permission: 'listing:approve' },
],
},
{
key: 'users',
label: '用户',
icon: <TeamOutlined />,
children: [{ key: '/users', label: '用户管理', permission: 'user:view' }],
},
{
key: 'finance',
label: '财务',
icon: <AccountBookOutlined />,
children: [
{ key: '/finance', label: '财务看板', permission: 'wallet:view' },
{ key: '/finance/details', label: '资金明细', permission: 'wallet:view' },
{ key: '/finance/disbursements', label: '资金出款', permission: 'wallet:view' },
{ key: '/payments', label: '支付流水', permission: 'wallet:view' },
{ key: '/wallet-ledger', label: '钱包流水', permission: 'wallet:view' },
{ key: '/users', label: '用户管理', icon: <TeamOutlined />, permission: 'user:view' },
{
key: '/finance/operating-expenses',
label: '运营开支',
permission: 'finance:operating_expense',
key: '/orders',
label: '订单管理',
icon: <FileTextOutlined />,
permission: 'order:list',
},
{
key: '/orders/refund-review',
label: '退款审核',
icon: <RollbackOutlined />,
permission: 'order:close',
},
{ key: '/pickup', label: '线下提号', icon: <SwapOutlined />, permission: 'order:pickup' },
{
key: '/listings',
label: '商品管理',
icon: <ShopOutlined />,
permission: 'listing:view',
},
{
key: '/listings/review',
label: '商品审核',
icon: <FileDoneOutlined />,
permission: 'listing:approve',
},
{
key: '/crash/categories',
label: '撞车分类',
icon: <GiftOutlined />,
permission: 'mohong:product_view',
},
{
key: '/crash/products',
label: '撞车商品',
icon: <GiftOutlined />,
permission: 'mohong:product_view',
},
{
key: '/crash/orders',
label: '撞车订单',
icon: <ProfileOutlined />,
permission: 'mohong:order_view',
},
{
key: '/crash/config',
label: '撞车配置',
icon: <ControlOutlined />,
permission: 'mohong:config',
},
{ key: '/withdrawals', label: '提现审核', permission: 'withdrawal:list' },
],
},
{
key: 'distribution',
label: '分销',
label: '分销管理',
icon: <ShareAltOutlined />,
children: [
{ key: '/distribution/dashboard', label: '分销看板', permission: 'distribution:site_view' },
{ key: '/distribution/sites', label: '站点管理', permission: 'distribution:site_view' },
{ key: '/distribution/orders', label: '分销订单', permission: 'distribution:order_view' },
{
key: '/distribution/sites',
label: '分销站点管理',
icon: <ApartmentOutlined />,
permission: 'distribution:site_view',
},
{
key: '/distribution/qrcode-pool',
label: '分站二维码池',
icon: <QrcodeOutlined />,
permission: 'qrcode:view',
},
{
key: '/distribution/rules',
label: '分销规则',
icon: <SettingOutlined />,
permission: 'distribution:rule_view',
},
{
key: '/distribution/orders',
label: '分销订单',
icon: <ProfileOutlined />,
permission: 'distribution:order_view',
},
{
key: '/distribution/settlements',
label: '结算批次',
label: '结算对账',
icon: <AccountBookOutlined />,
permission: 'distribution:settlement_view',
},
{ key: '/distribution/users', label: '分销用户', permission: 'distribution:site_view' },
{
key: '/distribution/announcements',
label: '站点公告',
permission: 'distribution:announcement_view',
key: '/invitations',
label: '邀请返利管理',
icon: <GiftOutlined />,
permission: 'distribution:site_view',
},
{ key: '/distribution/listings', label: '分销商品', permission: 'distribution:account_view' },
],
},
{
key: 'ad-tracking',
label: '推广追踪',
icon: <LineChartOutlined />,
children: [
{
key: '/distribution/payment-channels',
label: '支付通道',
permission: 'distribution:payment_view',
key: '/tracking-links',
label: '推广链接管理',
icon: <LinkOutlined />,
permission: 'ad_tracking:manage',
},
{ key: '/distribution/wallet', label: '站点钱包', permission: 'distribution:settlement_view' },
{ key: '/distribution/rules', label: '规则版本', permission: 'distribution:rule_view' },
{ key: '/distribution/qrcodes', label: '二维码池', permission: 'qrcode:view' },
{ key: '/distribution/sms', label: '站点短信', permission: 'distribution:site_view' },
{ key: '/distribution/invitation', label: '邀请返利', permission: 'distribution:site_view' },
],
},
{
key: 'crash',
label: '摸红',
icon: <GiftOutlined />,
children: [
{ key: '/crash/categories', label: '分类管理', permission: 'mohong:product_view' },
{ key: '/crash/products', label: '商品管理', permission: 'mohong:product_view' },
{ key: '/crash/orders', label: '订单管理', permission: 'mohong:order_view' },
{ key: '/crash/config', label: '摸红配置', permission: 'mohong:config' },
],
},
{
key: 'operations',
label: '运营',
icon: <BellOutlined />,
children: [
{ key: '/announcements', label: '公告管理', permission: 'announcement:view' },
{ key: '/tracking-links', label: '推广追踪', permission: 'ad_tracking:manage' },
{ key: '/notifications', label: '通知中心', permission: 'notification:view' },
{ key: '/push', label: '推送通知', permission: 'system_config:view' },
{ key: '/invitations', label: '邀请管理', permission: 'distribution:site_view' },
],
},
{
key: 'support',
label: '客服',
key: 'support-risk',
label: '客服与风控',
icon: <CustomerServiceOutlined />,
children: [
{ key: '/chat-support-groups', label: '客服群组', permission: 'chat:manage' },
{ key: '/chats', label: '客服会话', permission: 'chat:view' },
{ key: '/qrcode-pool', label: '二维码池', permission: 'qrcode:view' },
{ key: '/manual-entries', label: '手工录单', permission: 'cs_manual_entry:view' },
{ key: '/chat-quick-replies', label: '快捷回复', permission: 'chat:view' },
{ key: '/chat-welcome', label: '欢迎语配置', permission: 'system_config:view' },
{
key: '/disputes',
label: '仲裁中心',
icon: <SafetyOutlined />,
permission: 'dispute:view',
},
{ key: '/chats', label: '客服群聊', icon: <MessageOutlined />, permission: 'chat:view' },
{
key: '/qrcode-pool',
label: '二维码池',
icon: <QrcodeOutlined />,
permission: 'qrcode:view',
},
{
key: '/support-groups',
label: '客服分组',
icon: <TeamOutlined />,
permission: 'chat:manage',
},
{
key: '/manual-entries',
label: '手动录单',
icon: <FormOutlined />,
permission: 'cs_manual_entry:view',
},
],
},
{
key: 'system',
label: '系统',
key: 'finance',
label: '财务管理',
icon: <AccountBookOutlined />,
children: [
{
key: '/finance/dashboard',
label: '财务仪表盘',
icon: <DashboardOutlined />,
permission: 'wallet:view',
},
{
key: '/finance/details',
label: '财务明细',
icon: <FileTextOutlined />,
permission: 'wallet:view',
},
{
key: '/finance/disbursements',
label: '资金出款',
icon: <BankOutlined />,
permission: 'wallet:view',
},
{
key: '/finance/operating-expenses',
label: '运营开支',
icon: <MoneyCollectOutlined />,
permission: 'finance:operating_expense',
},
{
key: '/wallet-ledger',
label: '资金流水',
icon: <AccountBookOutlined />,
permission: 'wallet:view',
},
{
key: '/payments',
label: '支付流水',
icon: <CreditCardOutlined />,
permission: 'wallet:view',
},
{
key: '/withdrawals',
label: '提现审核',
icon: <MoneyCollectOutlined />,
permission: 'withdrawal:list',
},
{
key: '/payment-configs',
label: '支付配置',
icon: <CreditCardOutlined />,
permission: 'payment_config:list',
},
],
},
{
key: 'content-config',
label: '内容与配置',
icon: <SettingOutlined />,
children: [
{ key: '/system-configs', label: '系统配置', permission: 'system_config:view' },
{ key: '/roles', label: '角色权限', permission: 'role:manage' },
{ key: '/admin-users', label: '管理员账号', permission: 'admin_user:manage' },
{ key: '/payment-configs', label: '支付配置', permission: 'payment_config:list' },
{ key: '/backups', label: '备份管理', permission: 'backup:view' },
{ key: '/audit-logs', label: '审计日志', permission: 'audit_log:view' },
{
key: '/announcements',
label: '公告管理',
icon: <SoundOutlined />,
permission: 'announcement:view',
},
{
key: '/notifications',
label: '通知中心',
icon: <BellOutlined />,
permission: 'notification:view',
},
{
key: '/push',
label: '推送通知',
icon: <NotificationOutlined />,
permission: 'system_config:view',
},
{
key: '/system-configs',
label: '系统配置',
icon: <ControlOutlined />,
permission: 'system_config:view',
},
{
key: '/backups',
label: '数据备份',
icon: <CloudServerOutlined />,
permission: 'backup:view',
},
],
},
{
key: 'system-auth',
label: '系统权限',
icon: <SafetyCertificateOutlined />,
children: [
{
key: '/audit-logs',
label: '审计日志',
icon: <FileSearchOutlined />,
permission: 'audit_log:view',
},
{
key: '/admin-users',
label: '管理员管理',
icon: <UserSwitchOutlined />,
permission: 'admin_user:manage',
},
{
key: '/roles',
label: '角色管理',
icon: <SafetyCertificateOutlined />,
permission: 'role:manage',
},
],
},
]
@@ -186,35 +349,37 @@ const sectionMeta: Record<string, { title: string; description: string }> = {
'/orders': { title: '订单管理', description: '查看全量订单、交接记录与结账状态。' },
'/orders/refund-review': { title: '退款审核', description: '审核退款申请并核对资金影响。' },
'/pickup': { title: '线下提号', description: '管理员线下提号,站内结算或线下打款确认。' },
'/disputes': { title: '申诉仲裁', description: '处理订单争议、证据与仲裁结果。' },
'/disputes': { title: '仲裁中心', description: '处理订单争议、证据与仲裁结果。' },
'/listings': { title: '商品管理', description: '查看商品、账号资产与上下架状态。' },
'/listings/review': { title: '商品审核', description: '审核待上架商品与账号截图。' },
'/users': { title: '用户管理', description: '查看用户、实名状态与交易记录。' },
'/finance': { title: '财务看板', description: '查看收入、退款、结算与资金趋势。' },
'/finance/details': { title: '资金明细', description: '按订单核对实付、退款、号主结算与平台净额。' },
'/finance': { title: '财务仪表盘', description: '查看收入、退款、结算与资金趋势。' },
'/finance/dashboard': { title: '财务仪表盘', description: '查看收入、退款、结算与资金趋势。' },
'/finance/details': { title: '财务明细', description: '按订单核对实付、退款、号主结算与平台净额。' },
'/payments': { title: '支付流水', description: '核对渠道支付单、回调状态与失败原因。' },
'/wallet-ledger': { title: '钱包流水', description: '按用户、订单与业务类型核对余额变动。' },
'/wallet-ledger': { title: '资金流水', description: '按用户、订单与业务类型核对余额变动。' },
'/finance/disbursements': { title: '资金出款', description: '查看代管出款、提现与线下出款明细。' },
'/finance/operating-expenses': { title: '运营开支', description: '登记与核对日常运营支出。' },
'/withdrawals': { title: '提现审核', description: '审核提现申请与打款确认。' },
'/distribution/sites': { title: '分销站点', description: '管理分站、规则、结算与站点钱包。' },
'/distribution/sites': { title: '分销站点管理', description: '管理分站、规则、结算与站点钱包。' },
'/distribution/dashboard': { title: '分销看板', description: '查看站点收号、订单、利润与近 7 天趋势。' },
'/distribution/orders': { title: '分销订单', description: '核对分销订单快照与冷却期释放状态。' },
'/distribution/settlements': { title: '结算批次', description: '生成人工批次并核对、释放站内份额。' },
'/distribution/settlements': { title: '结算对账', description: '生成人工批次并核对、释放站内份额。' },
'/distribution/users': { title: '分销用户', description: '查看站点注册客户、下单客户与消费贡献。' },
'/distribution/announcements': { title: '站点公告', description: '维护分站自己的公告中心与弹窗公告。' },
'/distribution/listings': { title: '分销商品', description: '查看收货商品并控制账号展示与加价。' },
'/distribution/payment-channels': { title: '支付通道', description: '为分站绑定主站支付商户配置。' },
'/distribution/wallet': { title: '站点钱包', description: '查看分站余额、结算进度与提现记录。' },
'/distribution/rules': { title: '规则版本', description: '维护全局与站点级分账规则版本。' },
'/distribution/qrcodes': { title: '二维码池', description: '按站点维护发布群二维码池与发放状态。' },
'/distribution/rules': { title: '分销规则', description: '维护全局与站点级分账规则版本。' },
'/distribution/qrcodes': { title: '分站二维码池', description: '按站点维护发布群二维码池与发放状态。' },
'/distribution/qrcode-pool': { title: '分站二维码池', description: '按站点维护发布群二维码池与发放状态。' },
'/distribution/sms': { title: '站点短信', description: '为分站配置独立短信签名、模板与密钥。' },
'/distribution/invitation': { title: '邀请返利', description: '配置分站邀请好友的返利比例。' },
'/announcements': { title: '公告管理', description: '维护主站与分站公告。' },
'/roles': { title: '角色权限', description: '维护后台角色并分配资源级操作权限。' },
'/admin-users': { title: '管理员账号', description: '维护后台账号、启用状态与角色绑定。' },
'/roles': { title: '角色管理', description: '维护后台角色并分配资源级操作权限。' },
'/admin-users': { title: '管理员管理', description: '维护后台账号、启用状态与角色绑定。' },
'/manual-entries': {
title: '手工录单',
title: '手动录单',
description: '录入系统外成单编号,用于对账与客服绩效统计。',
},
'/chat-quick-replies': {
@@ -222,25 +387,29 @@ const sectionMeta: Record<string, { title: string; description: string }> = {
description: '维护客服会话常用话术与全局可见范围。',
},
'/tracking-links': {
title: '推广追踪',
title: '推广链接管理',
description: '管理推广短链、渠道成本与注册下单转化。',
},
'/crash/categories': { title: '摸红分类', description: '维护撞车商城商品分类与排序。' },
'/crash/products': { title: '摸红商品', description: '维护撞车商城商品、价格与固定二维码。' },
'/crash/orders': { title: '摸红订单', description: '处理撞车订单的接单、完成、取消与退款。' },
'/crash/config': { title: '摸红配置', description: '维护默认二维码与订单一键复制模板。' },
'/crash/categories': { title: '撞车分类', description: '维护撞车商城商品分类与排序。' },
'/crash/products': { title: '撞车商品', description: '维护撞车商城商品、价格与固定二维码。' },
'/crash/orders': { title: '撞车订单', description: '处理撞车订单的接单、完成、取消与退款。' },
'/crash/config': { title: '撞车配置', description: '维护默认二维码与订单一键复制模板。' },
'/notifications': { title: '通知中心', description: '查看后台系统通知并批量标记已读。' },
'/push': { title: '推送通知', description: '维护推送渠道与通知规则。' },
'/invitations': {
title: '邀请管理',
title: '邀请返利管理',
description: '维护邀请返利比例,查看邀请关系与返利流水。',
},
'/chat-support-groups': {
title: '客服群组',
title: '客服分组',
description: '维护客服群与坐席分配,支撑会话分流。',
},
'/support-groups': {
title: '客服分组',
description: '维护客服群与坐席分配,支撑会话分流。',
},
'/chats': {
title: '客服会话',
title: '客服群聊',
description: '按阶段筛选会话、回复用户并转接客服。',
},
'/chat-welcome': {
@@ -253,22 +422,55 @@ const sectionMeta: Record<string, { title: string; description: string }> = {
},
'/system-configs': { title: '系统配置', description: '维护首页、发布、订单与支付配置。' },
'/payment-configs': { title: '支付配置', description: '维护主站支付商户、渠道与回调参数。' },
'/backups': { title: '备份管理', description: '查看备份健康状态并维护定时备份计划。' },
'/backups': { title: '数据备份', description: '查看备份健康状态并维护定时备份计划。' },
'/audit-logs': { title: '审计日志', description: '查看高风险操作与变更记录。' },
}
const MENU_ALIASES: Record<string, string> = {
'/finance': '/finance/dashboard',
'/chat-support-groups': '/support-groups',
'/distribution/qrcodes': '/distribution/qrcode-pool',
}
const SUPPORT_STATUS_LABELS: Record<string, string> = {
online: '在线',
busy: '忙碌',
offline: '离线',
}
export function AdminLayout() {
const navigate = useNavigate()
const location = useLocation()
const admin = useAdminSessionStore(state => state.admin)
const logout = useAdminSessionStore(state => state.logout)
const { message } = App.useApp()
const [collapsed, setCollapsed] = useState(false)
const [openKeys, setOpenKeys] = useState<string[]>([])
const [supportStatus, setSupportStatus] = useState<'online' | 'offline' | 'busy'>(
admin?.support_status ?? 'offline'
)
useEffect(() => {
if (admin?.support_status) setSupportStatus(admin.support_status)
}, [admin?.support_status])
const canViewNotifications = Boolean(
admin?.permissions?.includes('*') || admin?.permissions?.includes('notification:view')
)
const { data: unreadCount = 0 } = useQuery({
queryKey: ['admin-notification-unread'],
queryFn: fetchAdminNotificationUnreadCount,
enabled: canViewNotifications,
refetchInterval: 60_000,
})
const basePath = getAdminBasePath()
const relativePath =
location.pathname.startsWith(basePath) && location.pathname !== basePath
? location.pathname.slice(basePath.length)
: '/'
const selectedKey = MENU_ALIASES[relativePath] ?? relativePath
const can = (permission?: string) => {
if (!permission) return true
@@ -290,19 +492,42 @@ export function AdminLayout() {
children: group.children.map(item => ({
key: item.key,
label: item.label,
icon: item.icon,
})),
}))
const activeGroupKey = visibleGroups.find(group =>
group.children.some(item => item.key === selectedKey)
)?.key
useEffect(() => {
if (activeGroupKey) setOpenKeys([activeGroupKey])
}, [activeGroupKey])
const currentLabel =
visibleGroups.flatMap(group => group.children).find(item => item.key === relativePath)?.label ||
sectionMeta[relativePath]?.title ||
(relativePath === '/' ? '仪表盘' : '功能建设中')
visibleGroups.flatMap(group => group.children).find(item => item.key === selectedKey)?.label ||
sectionMeta[selectedKey]?.title ||
(selectedKey === '/' ? '仪表盘' : '功能建设中')
const onLogout = async () => {
await logout()
navigate(getAdminLoginPath(), { replace: true })
}
const adminName = admin?.nickname || admin?.username || '管理员'
const adminRoleText = admin?.roles?.[0]?.name || '管理员'
const handleStatusChange = async (status: 'online' | 'offline' | 'busy') => {
if (status === supportStatus) return
try {
await updateAdminSupportStatus(status)
setSupportStatus(status)
message.success(`已切换为${SUPPORT_STATUS_LABELS[status]}`)
} catch {
message.error('状态更新失败')
}
}
return (
<Layout className="admin-shell">
<Sider
@@ -324,8 +549,12 @@ export function AdminLayout() {
theme="light"
inlineCollapsed={collapsed}
items={menuItems}
selectedKeys={[relativePath]}
defaultOpenKeys={visibleGroups.map(group => group.key)}
selectedKeys={[selectedKey]}
openKeys={openKeys}
onOpenChange={keys => {
const opened = keys.find(key => !openKeys.includes(key))
setOpenKeys(opened ? [opened] : [])
}}
onClick={({ key }) => {
if (key.startsWith('/')) {
navigate(key === '/' ? basePath : `${basePath}${key}`)
@@ -335,8 +564,59 @@ export function AdminLayout() {
{!collapsed ? (
<div className="admin-sider__foot">
<Typography.Text type="secondary">解忧商行 · 运营后台</Typography.Text>
<Typography.Text type="secondary">Phase 2 · 交易与商品</Typography.Text>
{canViewNotifications ? (
<Link className="admin-sider__notice" to={`${basePath}/notifications`}>
<BellOutlined />
<span>通知中心</span>
{unreadCount > 0 ? (
<Tag color="red">{unreadCount > 99 ? '99+' : unreadCount}</Tag>
) : null}
</Link>
) : null}
<Dropdown
placement="topLeft"
menu={{
items: [
{ key: 'online', label: '在线' },
{ key: 'busy', label: '忙碌' },
{ key: 'offline', label: '离线' },
],
selectedKeys: [supportStatus],
onClick: ({ key }) =>
void handleStatusChange(key as 'online' | 'offline' | 'busy'),
}}
>
<button type="button" className="admin-sider__status">
<span className={`admin-status-dot is-${supportStatus}`} />
{SUPPORT_STATUS_LABELS[supportStatus]}
</button>
</Dropdown>
<Dropdown
placement="topLeft"
menu={{
items: [
{
key: 'account',
label: `${adminName} · ${admin?.username || ''}`,
disabled: true,
},
{ type: 'divider' },
{ key: 'logout', label: '退出后台', icon: <LogoutOutlined />, onClick: onLogout },
],
}}
>
<button type="button" className="admin-sider__account">
<Avatar size={28} className="admin-avatar">
{adminName.slice(0, 1)}
</Avatar>
<span className="admin-sider__account-text">
<strong>{adminName}</strong>
<small>{adminRoleText}</small>
</span>
</button>
</Dropdown>
</div>
) : null}
</Sider>
@@ -358,31 +638,6 @@ export function AdminLayout() {
]}
/>
</div>
<Space size={16}>
{admin?.roles?.[0] ? <Tag className="admin-role-tag">{admin.roles[0].name}</Tag> : null}
<Dropdown
menu={{
items: [
{
key: 'account',
label: `${admin?.nickname || admin?.username || '管理员'} · ${admin?.username || ''}`,
disabled: true,
},
{ type: 'divider' },
{ key: 'logout', label: '退出后台', icon: <LogoutOutlined />, onClick: onLogout },
],
}}
placement="bottomRight"
>
<Button type="text" className="admin-account">
<Avatar size={30} className="admin-avatar">
{(admin?.nickname || admin?.username || '管').slice(0, 1)}
</Avatar>
<span className="admin-account__name">{admin?.nickname || admin?.username}</span>
</Button>
</Dropdown>
</Space>
</Header>
<Content className="admin-content">
+91 -1
View File
@@ -66,11 +66,101 @@
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px 20px;
padding: 14px 16px 16px;
border-top: 1px solid var(--line);
font-size: 11px;
}
.admin-sider__notice {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 8px;
border-radius: 6px;
color: var(--ink-secondary);
font-size: 13px;
}
.admin-sider__notice:hover {
background: #f1ece0;
color: var(--brand-ink);
}
.admin-sider__notice .ant-tag {
margin-inline-end: 0;
margin-left: auto;
}
.admin-sider__status {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 7px 8px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--ink-secondary);
font-size: 13px;
text-align: left;
cursor: pointer;
}
.admin-sider__status:hover {
background: #f1ece0;
}
.admin-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #9ca3af;
}
.admin-status-dot.is-online {
background: #10b981;
}
.admin-status-dot.is-busy {
background: #f59e0b;
}
.admin-status-dot.is-offline {
background: #9ca3af;
}
.admin-sider__account {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
margin-top: 4px;
padding: 10px 8px 2px;
border: 0;
border-top: 1px solid var(--line);
background: transparent;
text-align: left;
cursor: pointer;
}
.admin-sider__account-text {
display: flex;
min-width: 0;
flex-direction: column;
gap: 2px;
}
.admin-sider__account-text strong {
color: var(--brand-ink);
font-size: 13px;
font-weight: 600;
}
.admin-sider__account-text small {
color: var(--ink-muted);
font-size: 11px;
}
.admin-main {
min-width: 0;
background: var(--paper);