diff --git a/frontend/src/layouts/AppLayout/AppFooter.module.css b/frontend/src/layouts/AppLayout/AppFooter.module.css
new file mode 100644
index 0000000..d1c4650
--- /dev/null
+++ b/frontend/src/layouts/AppLayout/AppFooter.module.css
@@ -0,0 +1,19 @@
+/* REQ-USR-003: 页脚 scoped 样式(语义色用 var(--color-*))。 */
+.foot {
+ background: var(--color-bg-base);
+ border-top: 1px solid var(--color-border);
+ padding: 10px 14px;
+ text-align: center;
+ color: var(--color-text-secondary);
+ font-size: 12px;
+}
+.pipe {
+ margin: 0 8px;
+ color: var(--color-border);
+}
+.police {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ margin-left: 6px;
+}
diff --git a/frontend/src/layouts/AppLayout/AppFooter.tsx b/frontend/src/layouts/AppLayout/AppFooter.tsx
new file mode 100644
index 0000000..840f025
--- /dev/null
+++ b/frontend/src/layouts/AppLayout/AppFooter.tsx
@@ -0,0 +1,23 @@
+// REQ-USR-003: 页脚(复刻原型 footer.foot 版权/经营范围/备案号)。
+import styles from './AppFooter.module.css';
+
+export default function AppFooter() {
+ return (
+
+ );
+}
diff --git a/frontend/src/pages/home/HomePage/CommonOps.tsx b/frontend/src/pages/home/HomePage/CommonOps.tsx
new file mode 100644
index 0000000..2b40796
--- /dev/null
+++ b/frontend/src/pages/home/HomePage/CommonOps.tsx
@@ -0,0 +1,27 @@
+// REQ-USR-003: 常用操作卡(用户列表 → 路由;系统功能模块设置 → 占位,复刻原型 .common-ops)。
+import { App as AntdApp } from 'antd';
+import styles from './HomePage.module.css';
+
+interface CommonOpsProps {
+ onOpenUserList: () => void;
+}
+
+export default function CommonOps({ onOpenUserList }: CommonOpsProps) {
+ const { message } = AntdApp.useApp();
+
+ return (
+
+
常用操作
+
+
+
+ );
+}
diff --git a/frontend/src/pages/home/HomePage/HomePage.tsx b/frontend/src/pages/home/HomePage/HomePage.tsx
new file mode 100644
index 0000000..d4387af
--- /dev/null
+++ b/frontend/src/pages/home/HomePage/HomePage.tsx
@@ -0,0 +1,33 @@
+// REQ-USR-003: 主页落地页根(复刻原型 #screen-main:KPI 头条 + 角色树 + KPI 网格 + 常用操作 + 页脚)。
+import { useNavigate } from 'react-router-dom';
+import KpiHeadBar from './KpiHeadBar';
+import RoleProcessTree from './RoleProcessTree';
+import KpiBoard from './KpiBoard';
+import CommonOps from './CommonOps';
+import AppFooter from '../../../layouts/AppLayout/AppFooter';
+import { KPI_STATS, KPI_ROWS, ROLE_GROUPS, PROCESS_GROUPS } from './dashboardData';
+import styles from './HomePage.module.css';
+
+export default function HomePage() {
+ const navigate = useNavigate();
+
+ return (
+ <>
+
+
+
navigate('/usr/users')} />
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/home/HomePage/KpiHeadBar.tsx b/frontend/src/pages/home/HomePage/KpiHeadBar.tsx
new file mode 100644
index 0000000..e6217db
--- /dev/null
+++ b/frontend/src/pages/home/HomePage/KpiHeadBar.tsx
@@ -0,0 +1,29 @@
+// REQ-USR-003: KPI 头条(标题 + 今日未处理/未清总数统计 + AI 占位按钮,复刻原型 .kpi-head)。
+import { Button } from 'antd';
+import { ThunderboltOutlined } from '@ant-design/icons';
+import type { KPI_STATS } from './dashboardData';
+import styles from './HomePage.module.css';
+
+interface KpiHeadBarProps {
+ stats: typeof KPI_STATS;
+}
+
+export default function KpiHeadBar({ stats }: KpiHeadBarProps) {
+ return (
+
+ KPI监控
+
+ 今日未处理:
+ {stats.todayPending}
+
+ |
+
+ 未清总数:
+ {stats.openTotal}
+
+ }>
+ 小ai同学,请帮我安排今日工作
+
+
+ );
+}
diff --git a/frontend/src/pages/home/HomePage/RoleProcessTree.tsx b/frontend/src/pages/home/HomePage/RoleProcessTree.tsx
new file mode 100644
index 0000000..8cc578b
--- /dev/null
+++ b/frontend/src/pages/home/HomePage/RoleProcessTree.tsx
@@ -0,0 +1,45 @@
+// REQ-USR-003: 左侧角色/流程树(按角色/按流程分组 + 计数;点击高亮,不取数,BR11)。
+import { useState } from 'react';
+import type { GroupItem } from './dashboardData';
+import styles from './HomePage.module.css';
+
+interface RoleProcessTreeProps {
+ roleGroups: GroupItem[];
+ processGroups: GroupItem[];
+ onSelect?: (label: string) => void;
+}
+
+export default function RoleProcessTree({
+ roleGroups,
+ processGroups,
+ onSelect,
+}: RoleProcessTreeProps) {
+ // 本地高亮态:默认高亮「所有部门」(复刻原型首项 active)
+ const [activeLabel, setActiveLabel] = useState('所有部门');
+
+ const handleClick = (label: string) => {
+ setActiveLabel(label);
+ onSelect?.(label);
+ };
+
+ const renderItem = (g: GroupItem, keyPrefix: string) => (
+
+ );
+
+ return (
+
+
按角色
+ {roleGroups.map((g) => renderItem(g, 'role'))}
+
按流程
+ {processGroups.map((g) => renderItem(g, 'proc'))}
+
+ );
+}
diff --git a/frontend/tests/unit/HomePage.test.tsx b/frontend/tests/unit/HomePage.test.tsx
new file mode 100644
index 0000000..e2d35c5
--- /dev/null
+++ b/frontend/tests/unit/HomePage.test.tsx
@@ -0,0 +1,76 @@
+// REQ-USR-003: HomePage 落地页区域组合(KPI 头条 / 角色树 / 常用操作 / 页脚,BR8/BR11)
+import { describe, it, expect } from 'vitest';
+import { screen, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { Routes, Route, useLocation } from 'react-router-dom';
+import { renderShell } from './renderShell';
+import HomePage from '../../src/pages/home/HomePage/HomePage';
+
+function LocationProbe() {
+ const loc = useLocation();
+ return {loc.pathname}
;
+}
+
+function renderHome() {
+ return renderShell(
+ <>
+
+
+ } />
+ users} />
+
+ >,
+ {
+ initialEntries: ['/'],
+ preloadedAuth: {
+ token: 't',
+ user: { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' },
+ },
+ },
+ );
+}
+
+describe('HomePage', () => {
+ it('renders KPI head with title and stats', () => {
+ renderHome();
+ expect(screen.getByText('KPI监控')).toBeInTheDocument();
+ expect(screen.getByText('今日未处理:')).toBeInTheDocument();
+ expect(screen.getByText('37428')).toBeInTheDocument();
+ expect(screen.getByText('未清总数:')).toBeInTheDocument();
+ expect(screen.getByText('56433')).toBeInTheDocument();
+ expect(screen.getByText('小ai同学,请帮我安排今日工作')).toBeInTheDocument();
+ });
+
+ it('renders role/process tree groups', () => {
+ renderHome();
+ const tree = within(screen.getByTestId('role-tree'));
+ expect(tree.getByText('按角色')).toBeInTheDocument();
+ expect(tree.getByText('按流程')).toBeInTheDocument();
+ expect(tree.getByText(/所有部门/)).toBeInTheDocument();
+ expect(tree.getByText(/客服部/)).toBeInTheDocument();
+ });
+
+ it('tree item click highlights without navigation', async () => {
+ renderHome();
+ const tree = within(screen.getByTestId('role-tree'));
+ const item = tree.getByRole('button', { name: /核价人员/ });
+ await userEvent.click(item);
+ // 高亮(aria-pressed),不触发路由跳转
+ expect(item).toHaveAttribute('aria-pressed', 'true');
+ expect(screen.getByTestId('loc').textContent).toBe('/');
+ });
+
+ it('common ops user-list click navigates to /usr/users', async () => {
+ renderHome();
+ // 常用操作卡内「用户列表」
+ const opsUserList = screen.getByRole('button', { name: '用户列表' });
+ await userEvent.click(opsUserList);
+ expect(screen.getByTestId('loc').textContent).toBe('/usr/users');
+ });
+
+ it('renders footer copyright text', () => {
+ renderHome();
+ expect(screen.getByText(/©Copyright Antler Software/)).toBeInTheDocument();
+ expect(screen.getByText(/沪ICP备14034791号-1/)).toBeInTheDocument();
+ });
+});