diff --git a/frontend/src/components/NavOverlay.tsx b/frontend/src/components/NavOverlay.tsx
new file mode 100644
index 0000000..2a9d3b0
--- /dev/null
+++ b/frontend/src/components/NavOverlay.tsx
@@ -0,0 +1,86 @@
+import { useNavigate } from 'react-router-dom'
+import { useAppDispatch } from '../store/hooks'
+import { openTab } from '../store/slices/tabsSlice'
+
+const LEFT_MODULES = [
+ '销售管理', 'DCS系统', '产品管理', '生产运营', '生产执行', '模具管理',
+ '采购管理', '材料库存', '成品库存', '外协管理', '物流管理', '质量管理',
+ '财务管理', '成本管理(专)', '成本管理', '设备管理', '人事行政', 'OA系统',
+ '基础设置', '系统设置',
+]
+
+type NavItem = string | { label: string; go?: string; star?: boolean }
+
+const NAV_COLS: { title: string; items: NavItem[] }[] = [
+ { title: '期初设置', items: ['客户期初', '供应商期初', '材料期初', '产品期初', '数据导入', '离线导出下载'] },
+ { title: '用户管理', items: [{ label: '用户列表', go: 'userlist', star: true }, '系统权限', '系统权限稽查表', '权限组'] },
+ { title: '系统参数', items: ['系统参数', '财务结账', '系统常量配置'] },
+ { title: '计算方案', items: ['方案列表', '计算参数'] },
+ { title: '日志', items: ['个性化模块', '操作日志', '异常清除KPI任务表', 'MYSQL监听器'] },
+ { title: '开发平台', items: ['自定义开发范例', { label: '系统功能模块设置', star: true }, 'EBC流程清单', '功能模块界面设置', '增删改存业务处理'] },
+ { title: 'API对接管理', items: ['调用第三方接口(TOKEN配置)', '调用第三方接口(接口定义)', '被第三方调用(生成token)', '数据同步', '被第三方调用(API定义)'] },
+]
+
+interface Props { onClose: () => void }
+
+export default function NavOverlay({ onClose }: Props) {
+ const navigate = useNavigate()
+ const dispatch = useAppDispatch()
+
+ function handleUserList() {
+ dispatch(openTab({ id: 'userlist', title: '用户列表', path: '/usr/users', closable: true }))
+ navigate('/usr/users')
+ onClose()
+ }
+
+ return (
+
+
e.stopPropagation()} style={{ display: 'flex', flex: 1 }}>
+ {/* Left sidebar */}
+
+ {LEFT_MODULES.map(mod => (
+
+ {mod}
+
+ ))}
+
+ {/* Right grid */}
+
+ {NAV_COLS.map(col => (
+
+
+ {col.title}
+
+ {col.items.map(item => {
+ if (typeof item === 'string') {
+ return
{item}
+ }
+ return (
+
+ {item.label}
+ {item.star && ★}
+
+ )
+ })}
+
+ ))}
+
+
+
+ )
+}
diff --git a/frontend/src/test/NavOverlay.test.tsx b/frontend/src/test/NavOverlay.test.tsx
new file mode 100644
index 0000000..25e7093
--- /dev/null
+++ b/frontend/src/test/NavOverlay.test.tsx
@@ -0,0 +1,56 @@
+import { describe, it, expect, vi } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { Provider } from 'react-redux'
+import { MemoryRouter, Routes, Route } from 'react-router-dom'
+import { configureStore } from '@reduxjs/toolkit'
+import authReducer from '../store/slices/authSlice'
+import tabsReducer from '../store/slices/tabsSlice'
+import NavOverlay from '../components/NavOverlay'
+
+function makeStore() {
+ return configureStore({ reducer: { auth: authReducer, tabs: tabsReducer } })
+}
+
+function renderOverlay(onClose = vi.fn()) {
+ return render(
+
+
+
+ } />
+ UserList} />
+
+
+
+ )
+}
+
+describe('NavOverlay', () => {
+ it('renders_systemModules', () => {
+ renderOverlay()
+ expect(screen.getByText('系统设置')).toBeInTheDocument()
+ expect(screen.getByText('用户管理')).toBeInTheDocument()
+ })
+
+ it('clickUserList_navigatesAndClosesOverlay', async () => {
+ const onClose = vi.fn()
+ renderOverlay(onClose)
+ await userEvent.click(screen.getByText('用户列表'))
+ expect(screen.getByText('UserList')).toBeInTheDocument()
+ expect(onClose).toHaveBeenCalledTimes(1)
+ })
+
+ it('clickBackground_callsOnClose', async () => {
+ const onClose = vi.fn()
+ render(
+
+
+
+
+
+ )
+ // Click the outer overlay div (background)
+ await userEvent.click(document.querySelector('[data-testid="nav-bg"]')!)
+ expect(onClose).toHaveBeenCalledTimes(1)
+ })
+})