diff --git a/web/package-lock.json b/web/package-lock.json
index e0b7f65..abd86e6 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -14,6 +14,7 @@
"antd": "^6.5.1",
"react": "^19.2.7",
"react-dom": "^19.2.7",
+ "react-router-dom": "^7.18.1",
"tailwindcss": "^4.3.2"
},
"devDependencies": {
@@ -2614,6 +2615,19 @@
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
@@ -3591,6 +3605,44 @@
"integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==",
"license": "MIT"
},
+ "node_modules/react-router": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz",
+ "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz",
+ "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/rolldown": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
@@ -3651,6 +3703,12 @@
"compute-scroll-into-view": "^3.0.2"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
diff --git a/web/package.json b/web/package.json
index 0be7467..350ebe1 100644
--- a/web/package.json
+++ b/web/package.json
@@ -16,6 +16,7 @@
"antd": "^6.5.1",
"react": "^19.2.7",
"react-dom": "^19.2.7",
+ "react-router-dom": "^7.18.1",
"tailwindcss": "^4.3.2"
},
"devDependencies": {
diff --git a/web/src/App.tsx b/web/src/App.tsx
index aca3bca..0b3a815 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -1,12 +1,6 @@
-const App = () => {
- return (
-
- )
-}
+import { RouterProvider } from 'react-router-dom'
+import { router } from './router'
+
+const App = () =>
export default App
diff --git a/web/src/components/layout/AgentLayout.tsx b/web/src/components/layout/AgentLayout.tsx
new file mode 100644
index 0000000..eb4c168
--- /dev/null
+++ b/web/src/components/layout/AgentLayout.tsx
@@ -0,0 +1,20 @@
+import { Outlet } from 'react-router-dom'
+import { Layout } from 'antd'
+import AgentSidebar from './AgentSidebar'
+
+const { Content } = Layout
+
+const AgentLayout = () => {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AgentLayout
diff --git a/web/src/components/layout/AgentSidebar.tsx b/web/src/components/layout/AgentSidebar.tsx
new file mode 100644
index 0000000..31ea694
--- /dev/null
+++ b/web/src/components/layout/AgentSidebar.tsx
@@ -0,0 +1,47 @@
+import { useLocation, useNavigate } from 'react-router-dom'
+import { Layout, Menu } from 'antd'
+import {
+ AppstoreOutlined,
+ MessageOutlined,
+ HistoryOutlined,
+ TeamOutlined,
+ FileTextOutlined,
+ BarChartOutlined,
+ SettingOutlined,
+} from '@ant-design/icons'
+
+const { Sider } = Layout
+
+const menuItems = [
+ { key: '/agent/dashboard', icon: , label: '工作台' },
+ { key: '/agent/chat-history', icon: , label: '对话记录' },
+ { key: '/agent/customers', icon: , label: '客户管理' },
+ { key: '/agent/knowledge', icon: , label: '知识库' },
+ { key: '/agent/statistics', icon: , label: '数据统计' },
+ { key: '/agent/settings', icon: , label: '系统设置' },
+]
+
+const AgentSidebar = () => {
+ const location = useLocation()
+ const navigate = useNavigate()
+
+ const selectedKey = menuItems.find(item => location.pathname.startsWith(item.key))?.key || '/agent/dashboard'
+
+ return (
+
+
+
+ 客服云
+
+
+ )
+}
+
+export default AgentSidebar
diff --git a/web/src/pages/agent/ChatHistory.tsx b/web/src/pages/agent/ChatHistory.tsx
new file mode 100644
index 0000000..4e8e249
--- /dev/null
+++ b/web/src/pages/agent/ChatHistory.tsx
@@ -0,0 +1,6 @@
+const ChatHistory = () => (
+
+)
+export default ChatHistory
diff --git a/web/src/pages/agent/Customers.tsx b/web/src/pages/agent/Customers.tsx
new file mode 100644
index 0000000..a2a9ea3
--- /dev/null
+++ b/web/src/pages/agent/Customers.tsx
@@ -0,0 +1,6 @@
+const Customers = () => (
+
+)
+export default Customers
diff --git a/web/src/pages/agent/Dashboard.tsx b/web/src/pages/agent/Dashboard.tsx
new file mode 100644
index 0000000..5684b05
--- /dev/null
+++ b/web/src/pages/agent/Dashboard.tsx
@@ -0,0 +1,186 @@
+import { useState } from 'react'
+import { Badge, Input, Tooltip } from 'antd'
+import { SearchOutlined, UserOutlined, StarFilled } from '@ant-design/icons'
+
+interface Session {
+ id: string
+ name: string
+ avatar?: string
+ lastMessage: string
+ time: string
+ priority: 'urgent' | 'waiting' | 'active'
+ unread: number
+}
+
+const mockSessions: Session[] = [
+ { id: '1', name: '张三', lastMessage: '我的订单什么时候发货?', time: '10:32', priority: 'urgent', unread: 3 },
+ { id: '2', name: '李四', lastMessage: '怎么退货啊', time: '10:28', priority: 'active', unread: 0 },
+ { id: '3', name: '王五', lastMessage: '你们这个产品好用吗', time: '10:15', priority: 'active', unread: 1 },
+ { id: '4', name: '赵六科技', lastMessage: '企业版价格能否优惠', time: '09:58', priority: 'waiting', unread: 0 },
+ { id: '5', name: '钱七', lastMessage: '谢谢,问题已解决', time: '09:45', priority: 'active', unread: 0 },
+]
+
+const priorityColors = { urgent: '#dc2626', waiting: '#d97706', active: '#2563eb' }
+const priorityLabels = { urgent: '紧急', waiting: '等待中', active: '进行中' }
+
+const Dashboard = () => {
+ const [selectedId, setSelectedId] = useState('1')
+ const [message, setMessage] = useState('')
+
+ const selectedSession = mockSessions.find(s => s.id === selectedId)
+
+ return (
+
+ {/* 左侧:会话列表 */}
+
+
+ } placeholder="搜索会话..." variant="borderless" />
+
+
+ {(['urgent', 'waiting', 'active'] as const).map(p => (
+
+ {priorityLabels[p]}
+
+ ))}
+
+
+ {mockSessions.map(session => (
+
setSelectedId(session.id)}
+ >
+
+
+
+ {session.name}
+
+
+ {session.unread > 0 && }
+ {session.time}
+
+
+
{session.lastMessage}
+
+ ))}
+
+
+
+ {/* 中间:聊天区 */}
+
+ {selectedSession ? (
+ <>
+
+
{selectedSession.name}
+
+ 转接
+ 结束
+
+
+
+
+
+ 你好,我想咨询一下订单物流问题
+
+
10:30
+
+
+
+ 您好,请提供您的订单号,我帮您查询
+
+
10:31
+
+
+
+ 订单号是:AB20260714001
+
+
10:31
+
+
+
+ 好的,正在为您查询,请稍候...
+
+
10:32
+
+
+
+
+ setMessage(e.target.value)}
+ onKeyDown={e => {
+ if (e.key === 'Enter' && message.trim()) {
+ setMessage('')
+ }
+ }}
+ />
+ 😊
+ 📎
+
+
+ >
+ ) : (
+
+ 选择一个会话开始接待
+
+ )}
+
+
+ {/* 右侧:客户信息面板 */}
+
+
+
+
+
+
+
+
{selectedSession?.name || '访客'}
+
网页渠道
+
+
+
+
+
+
+
联系信息
+
+
📱 138****8888
+
📧 zhang***@example.com
+
+
+
+
+
内部备注
+
+ 之前咨询过售后问题...
+
+
+
+
+
+ )
+}
+
+export default Dashboard
diff --git a/web/src/pages/agent/Knowledge.tsx b/web/src/pages/agent/Knowledge.tsx
new file mode 100644
index 0000000..f095628
--- /dev/null
+++ b/web/src/pages/agent/Knowledge.tsx
@@ -0,0 +1,6 @@
+const Knowledge = () => (
+
+)
+export default Knowledge
diff --git a/web/src/pages/agent/Settings.tsx b/web/src/pages/agent/Settings.tsx
new file mode 100644
index 0000000..2e9eeb7
--- /dev/null
+++ b/web/src/pages/agent/Settings.tsx
@@ -0,0 +1,6 @@
+const Settings = () => (
+
+)
+export default Settings
diff --git a/web/src/pages/agent/Statistics.tsx b/web/src/pages/agent/Statistics.tsx
new file mode 100644
index 0000000..ec9fa7f
--- /dev/null
+++ b/web/src/pages/agent/Statistics.tsx
@@ -0,0 +1,6 @@
+const Statistics = () => (
+
+)
+export default Statistics
diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx
new file mode 100644
index 0000000..200cf49
--- /dev/null
+++ b/web/src/router/index.tsx
@@ -0,0 +1,47 @@
+import { lazy, Suspense } from 'react'
+import { Navigate, createBrowserRouter } from 'react-router-dom'
+import { Spin } from 'antd'
+
+const AgentLayout = lazy(() => import('@/components/layout/AgentLayout'))
+const Dashboard = lazy(() => import('@/pages/agent/Dashboard'))
+const ChatHistory = lazy(() => import('@/pages/agent/ChatHistory'))
+const Customers = lazy(() => import('@/pages/agent/Customers'))
+const Knowledge = lazy(() => import('@/pages/agent/Knowledge'))
+const Statistics = lazy(() => import('@/pages/agent/Statistics'))
+const Settings = lazy(() => import('@/pages/agent/Settings'))
+
+const loading = (
+
+
+
+)
+
+function Lazy({ children }: { children: React.ReactNode }) {
+ return {children}
+}
+
+export const router = createBrowserRouter([
+ {
+ path: '/admin',
+ element: 管理端
,
+ },
+ {
+ path: '/',
+ element: ,
+ children: [
+ { index: true, element: },
+ {
+ path: 'agent',
+ children: [
+ { index: true, element: },
+ { path: 'dashboard', element: },
+ { path: 'chat-history', element: },
+ { path: 'customers', element: },
+ { path: 'knowledge', element: },
+ { path: 'statistics', element: },
+ { path: 'settings', element: },
+ ],
+ },
+ ],
+ },
+])
diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json
index 6830b6f..5e91216 100644
--- a/web/tsconfig.app.json
+++ b/web/tsconfig.app.json
@@ -20,7 +20,14 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+
+ /* Path alias */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ },
+ "ignoreDeprecations": "6.0"
},
"include": ["src"]
}