diff --git a/apps/frontend/src/components/admin/FinanceSubNav.tsx b/apps/frontend/src/components/admin/FinanceSubNav.tsx
new file mode 100644
index 00000000..1a2c96ad
--- /dev/null
+++ b/apps/frontend/src/components/admin/FinanceSubNav.tsx
@@ -0,0 +1,93 @@
+import {
+ AccountBookOutlined,
+ ApartmentOutlined,
+ LineChartOutlined,
+ WalletOutlined,
+} from '@ant-design/icons'
+import { Tabs } from 'antd'
+import type { TabsProps } from 'antd'
+import { useLocation, useNavigate } from 'react-router'
+import { getAdminRole } from '@/utils/admin-auth'
+
+export const FINANCE_NAV_ITEMS = [
+ {
+ key: '/admin/sales-profit',
+ label: '销售毛利',
+ icon: ,
+ },
+ {
+ key: '/admin/cost-center',
+ label: '商品与成本中心',
+ icon: ,
+ },
+ {
+ key: '/admin/fund',
+ label: '钱包管理',
+ icon: ,
+ },
+ {
+ key: '/admin/finance',
+ label: '收支明细',
+ icon: ,
+ },
+] as const
+
+type FinanceNavKey = (typeof FINANCE_NAV_ITEMS)[number]['key']
+
+type FinanceSubNavProps = {
+ current?: FinanceNavKey
+}
+
+export default function FinanceSubNav({ current }: FinanceSubNavProps) {
+ const location = useLocation()
+ const navigate = useNavigate()
+ const role = getAdminRole()
+
+ // 客服角色无完整财务权限,不展示财务联动 Tab
+ if (role === 'support') {
+ return null
+ }
+
+ const activeKey =
+ current ||
+ FINANCE_NAV_ITEMS.find((item) => location.pathname.startsWith(item.key))?.key ||
+ '/admin/sales-profit'
+
+ const tabItems: TabsProps['items'] = FINANCE_NAV_ITEMS.map((item) => ({
+ key: item.key,
+ label: (
+
+ {item.icon}
+ {item.label}
+
+ ),
+ }))
+
+ return (
+
+ navigate(key)}
+ items={tabItems}
+ style={{ marginBottom: -1 }}
+ />
+
+ )
+}
diff --git a/apps/frontend/src/layouts/AdminLayout.tsx b/apps/frontend/src/layouts/AdminLayout.tsx
index 91f0d880..9e567ecd 100644
--- a/apps/frontend/src/layouts/AdminLayout.tsx
+++ b/apps/frontend/src/layouts/AdminLayout.tsx
@@ -168,52 +168,81 @@ export default function AdminLayout() {
},
})
+ const selectedKey = resolveSelectedKey(location.pathname)
+ const isFinanceKey = [
+ '/admin/sales-profit',
+ '/admin/cost-center',
+ '/admin/fund',
+ '/admin/finance',
+ ].includes(selectedKey)
+ const [openKeys, setOpenKeys] = useState(['sub-finance'])
+
+ useEffect(() => {
+ if (isFinanceKey && !openKeys.includes('sub-finance')) {
+ setOpenKeys((prev) => [...prev, 'sub-finance'])
+ }
+ }, [isFinanceKey])
+
const menuItems = useMemo(() => {
+ const financeSubItems: MenuProps['items'] = [
+ { key: '/admin/sales-profit', icon: , label: '销售毛利' },
+ { key: '/admin/cost-center', icon: , label: '商品与成本' },
+ { key: '/admin/fund', icon: , label: '钱包管理' },
+ { key: '/admin/finance', icon: , label: '收支明细' },
+ ]
+
+ const financeSubMenu = {
+ key: 'sub-finance',
+ icon: ,
+ label: '财务',
+ children: financeSubItems,
+ }
+
const operationItems: MenuProps['items'] =
role === 'finance'
? [
- { key: '/admin/finance', icon: , label: '财务' },
- { key: '/admin/sales-profit', icon: , label: '销售毛利' },
- { key: '/admin/cost-center', icon: , label: '商品与成本' },
- { key: '/admin/fund', icon: , label: '钱包管理' },
+ financeSubMenu,
{ key: '/admin/worker-platform', icon: , label: '资金审核' },
]
- : [
- { key: '/admin/dashboard', icon: , label: '概览' },
- { key: '/admin/orders', icon: , label: '订单' },
- {
- key: '/admin/fund',
- icon: ,
- label: role === 'support' ? '我的钱包' : '钱包管理',
- },
- ...(role === 'support'
- ? []
- : [
- { key: '/admin/finance', icon: , label: '财务' },
- {
- key: '/admin/sales-profit',
- icon: ,
- label: '销售毛利',
- },
- {
- key: '/admin/cost-center',
- icon: ,
- label: '商品与成本',
- },
- ]),
- { key: '/admin/tasks', icon: , label: '任务' },
- { key: '/admin/worker-platform', icon: , label: '接单平台' },
- {
- key: '/admin/kuaishou-industry',
- icon: ,
- label: '电子凭证',
- },
- {
- key: '/admin/douyin-mashida-certificates',
- icon: ,
- label: '抖音券核销',
- },
- ]
+ : role === 'support'
+ ? [
+ { key: '/admin/dashboard', icon: , label: '概览' },
+ { key: '/admin/orders', icon: , label: '订单' },
+ {
+ key: '/admin/fund',
+ icon: ,
+ label: '我的钱包',
+ },
+ { key: '/admin/tasks', icon: , label: '任务' },
+ { key: '/admin/worker-platform', icon: , label: '接单平台' },
+ {
+ key: '/admin/kuaishou-industry',
+ icon: ,
+ label: '电子凭证',
+ },
+ {
+ key: '/admin/douyin-mashida-certificates',
+ icon: ,
+ label: '抖音券核销',
+ },
+ ]
+ : [
+ { key: '/admin/dashboard', icon: , label: '概览' },
+ { key: '/admin/orders', icon: , label: '订单' },
+ financeSubMenu,
+ { key: '/admin/tasks', icon: , label: '任务' },
+ { key: '/admin/worker-platform', icon: , label: '接单平台' },
+ {
+ key: '/admin/kuaishou-industry',
+ icon: ,
+ label: '电子凭证',
+ },
+ {
+ key: '/admin/douyin-mashida-certificates',
+ icon: ,
+ label: '抖音券核销',
+ },
+ ]
const items: MenuProps['items'] = [
{
@@ -333,8 +362,6 @@ export default function AdminLayout() {
)
- const selectedKey = resolveSelectedKey(location.pathname)
-
return (
setOpenKeys(keys as string[])}
items={menuItems}
className="admin-menu"
onClick={({ key }) => navigate(String(key))}
@@ -802,8 +831,10 @@ function playNotificationSound(kind: NotificationAlertKind) {
function resolveSelectedKey(pathname: string) {
if (pathname.startsWith('/admin/orders')) return '/admin/orders'
- if (pathname.startsWith('/admin/finance')) return '/admin/finance'
+ if (pathname.startsWith('/admin/sales-profit')) return '/admin/sales-profit'
if (pathname.startsWith('/admin/cost-center')) return '/admin/cost-center'
+ if (pathname.startsWith('/admin/fund')) return '/admin/fund'
+ if (pathname.startsWith('/admin/finance')) return '/admin/finance'
if (pathname.startsWith('/admin/tasks')) return '/admin/tasks'
if (pathname.startsWith('/admin/worker-platform')) return '/admin/worker-platform'
if (pathname.startsWith('/admin/kuaishou-industry')) return '/admin/kuaishou-industry'
diff --git a/apps/frontend/src/lib/http.ts b/apps/frontend/src/lib/http.ts
index 666e0b75..fb8e6802 100644
--- a/apps/frontend/src/lib/http.ts
+++ b/apps/frontend/src/lib/http.ts
@@ -188,7 +188,11 @@ export function apiGet(url: string, params?: Record, options
return request({ method: 'GET', url, params, ...options })
}
-export function apiGetBlob(url: string, params?: Record, options?: RequestOptions) {
+export function apiGetBlob(
+ url: string,
+ params?: Record,
+ options?: RequestOptions,
+) {
return http.request({
method: 'GET',
url,
diff --git a/apps/frontend/src/pages/admin/AdminFinancePage.tsx b/apps/frontend/src/pages/admin/AdminFinancePage.tsx
index 089dd181..3c1ba94a 100644
--- a/apps/frontend/src/pages/admin/AdminFinancePage.tsx
+++ b/apps/frontend/src/pages/admin/AdminFinancePage.tsx
@@ -19,6 +19,7 @@ import { useState } from 'react'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import PageHeader from '@/components/admin/PageHeader'
+import FinanceSubNav from '@/components/admin/FinanceSubNav'
import dayjs from '@/lib/dayjs'
import {
fetchAdminFinanceSummary,
@@ -160,7 +161,8 @@ export default function AdminFinancePage() {
return (