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 ( + +
+ + 客服云 +
+ navigate(key)} + className="border-e-0 mt-2" + /> + + ) +} + +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 || '访客'}
+
网页渠道
+
+
+
+
+
+
标签
+
+ VIP客户 + 新客户 +
+
+
+
联系信息
+
+
📱 138****8888
+
📧 zhang***@example.com
+
+
+
+
统计数据
+
+
+
12
+
对话次数
+
+
+
+ 4.8 +
+
满意度
+
+
+
+
+
内部备注
+
+ 之前咨询过售后问题... +
+
+
+
+
+ ) +} + +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"] }