搭建客服端基础框架:路由、侧边栏布局、客服工作台三栏页面
This commit is contained in:
Generated
+58
@@ -14,6 +14,7 @@
|
|||||||
"antd": "^6.5.1",
|
"antd": "^6.5.1",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
|
"react-router-dom": "^7.18.1",
|
||||||
"tailwindcss": "^4.3.2"
|
"tailwindcss": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -2614,6 +2615,19 @@
|
|||||||
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
|
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/css-color-keywords": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
"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==",
|
"integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||||
@@ -3651,6 +3703,12 @@
|
|||||||
"compute-scroll-into-view": "^3.0.2"
|
"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": {
|
"node_modules/simple-swizzle": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"antd": "^6.5.1",
|
"antd": "^6.5.1",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
|
"react-router-dom": "^7.18.1",
|
||||||
"tailwindcss": "^4.3.2"
|
"tailwindcss": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+4
-10
@@ -1,12 +1,6 @@
|
|||||||
const App = () => {
|
import { RouterProvider } from 'react-router-dom'
|
||||||
return (
|
import { router } from './router'
|
||||||
<div className="h-screen flex items-center justify-center bg-neutral-50">
|
|
||||||
<div className="text-center">
|
const App = () => <RouterProvider router={router} />
|
||||||
<h1 className="text-2xl font-bold text-neutral-900 mb-2">客服云</h1>
|
|
||||||
<p className="text-neutral-500">在线客服系统</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Outlet } from 'react-router-dom'
|
||||||
|
import { Layout } from 'antd'
|
||||||
|
import AgentSidebar from './AgentSidebar'
|
||||||
|
|
||||||
|
const { Content } = Layout
|
||||||
|
|
||||||
|
const AgentLayout = () => {
|
||||||
|
return (
|
||||||
|
<Layout className="h-screen">
|
||||||
|
<AgentSidebar />
|
||||||
|
<Layout>
|
||||||
|
<Content className="overflow-auto bg-neutral-50">
|
||||||
|
<Outlet />
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AgentLayout
|
||||||
@@ -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: <AppstoreOutlined />, label: '工作台' },
|
||||||
|
{ key: '/agent/chat-history', icon: <HistoryOutlined />, label: '对话记录' },
|
||||||
|
{ key: '/agent/customers', icon: <TeamOutlined />, label: '客户管理' },
|
||||||
|
{ key: '/agent/knowledge', icon: <FileTextOutlined />, label: '知识库' },
|
||||||
|
{ key: '/agent/statistics', icon: <BarChartOutlined />, label: '数据统计' },
|
||||||
|
{ key: '/agent/settings', icon: <SettingOutlined />, label: '系统设置' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const AgentSidebar = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const selectedKey = menuItems.find(item => location.pathname.startsWith(item.key))?.key || '/agent/dashboard'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sider width={220} className="!bg-white border-r border-neutral-200">
|
||||||
|
<div className="h-14 flex items-center px-5 border-b border-neutral-100">
|
||||||
|
<MessageOutlined className="text-lg text-[#2563eb] mr-2.5" />
|
||||||
|
<span className="text-base font-semibold text-neutral-900">客服云</span>
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
selectedKeys={[selectedKey]}
|
||||||
|
items={menuItems}
|
||||||
|
onClick={({ key }) => navigate(key)}
|
||||||
|
className="border-e-0 mt-2"
|
||||||
|
/>
|
||||||
|
</Sider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AgentSidebar
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const ChatHistory = () => (
|
||||||
|
<div className="h-full flex items-center justify-center text-neutral-400 text-center">
|
||||||
|
<div><div className="text-lg mb-1">对话记录</div><div className="text-sm">开发中...</div></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
export default ChatHistory
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const Customers = () => (
|
||||||
|
<div className="h-full flex items-center justify-center text-neutral-400 text-center">
|
||||||
|
<div><div className="text-lg mb-1">客户管理</div><div className="text-sm">开发中...</div></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
export default Customers
|
||||||
@@ -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<string>('1')
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
|
||||||
|
const selectedSession = mockSessions.find(s => s.id === selectedId)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex">
|
||||||
|
{/* 左侧:会话列表 */}
|
||||||
|
<div className="w-[300px] flex-shrink-0 border-r border-neutral-200 bg-white flex flex-col">
|
||||||
|
<div className="p-3 border-b border-neutral-100">
|
||||||
|
<Input prefix={<SearchOutlined />} placeholder="搜索会话..." variant="borderless" />
|
||||||
|
</div>
|
||||||
|
<div className="flex px-3 py-2 gap-2 border-b border-neutral-100">
|
||||||
|
{(['urgent', 'waiting', 'active'] as const).map(p => (
|
||||||
|
<span key={p} className="text-xs px-2 py-0.5 rounded-full cursor-pointer hover:bg-neutral-100" style={{ color: priorityColors[p] }}>
|
||||||
|
{priorityLabels[p]}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-auto">
|
||||||
|
{mockSessions.map(session => (
|
||||||
|
<div
|
||||||
|
key={session.id}
|
||||||
|
className={`px-3 py-2.5 cursor-pointer border-b border-neutral-50 hover:bg-neutral-50 transition-colors ${selectedId === session.id ? 'bg-blue-50' : ''}`}
|
||||||
|
onClick={() => setSelectedId(session.id)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="inline-block w-2 h-2 rounded-full" style={{ backgroundColor: priorityColors[session.priority] }} />
|
||||||
|
<span className="text-sm font-medium text-neutral-800">{session.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{session.unread > 0 && <Badge count={session.unread} size="small" />}
|
||||||
|
<span className="text-xs text-neutral-400">{session.time}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-neutral-400 mt-1 truncate pl-4">{session.lastMessage}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 中间:聊天区 */}
|
||||||
|
<div className="flex-1 flex flex-col min-w-0 bg-white">
|
||||||
|
{selectedSession ? (
|
||||||
|
<>
|
||||||
|
<div className="h-12 px-4 flex items-center justify-between border-b border-neutral-100 flex-shrink-0">
|
||||||
|
<span className="text-sm font-medium text-neutral-800">{selectedSession.name}</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Tooltip title="转接"><span className="text-neutral-400 cursor-pointer hover:text-neutral-600 text-sm">转接</span></Tooltip>
|
||||||
|
<Tooltip title="结束会话"><span className="text-neutral-400 cursor-pointer hover:text-red-500 text-sm">结束</span></Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-auto p-4 flex flex-col gap-3">
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
<div className="bg-neutral-100 rounded-lg px-3 py-2 text-sm text-neutral-700 max-w-[70%]">
|
||||||
|
你好,我想咨询一下订单物流问题
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-neutral-400 mt-0.5">10:30</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-end">
|
||||||
|
<div className="bg-blue-500 rounded-lg px-3 py-2 text-sm text-white max-w-[70%]">
|
||||||
|
您好,请提供您的订单号,我帮您查询
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-neutral-400 mt-0.5">10:31</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
<div className="bg-neutral-100 rounded-lg px-3 py-2 text-sm text-neutral-700 max-w-[70%]">
|
||||||
|
订单号是:AB20260714001
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-neutral-400 mt-0.5">10:31</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-end">
|
||||||
|
<div className="bg-blue-500 rounded-lg px-3 py-2 text-sm text-white max-w-[70%]">
|
||||||
|
好的,正在为您查询,请稍候...
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-neutral-400 mt-0.5">10:32</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 border-t border-neutral-100 flex-shrink-0">
|
||||||
|
<div className="flex items-center gap-2 bg-neutral-50 rounded-lg px-3 py-2">
|
||||||
|
<input
|
||||||
|
className="flex-1 bg-transparent outline-none text-sm text-neutral-700 placeholder:text-neutral-400"
|
||||||
|
placeholder="输入消息... (Enter 发送)"
|
||||||
|
value={message}
|
||||||
|
onChange={e => setMessage(e.target.value)}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.key === 'Enter' && message.trim()) {
|
||||||
|
setMessage('')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-neutral-300 cursor-pointer hover:text-neutral-500">😊</span>
|
||||||
|
<span className="text-neutral-300 cursor-pointer hover:text-neutral-500">📎</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 flex items-center justify-center text-neutral-400">
|
||||||
|
选择一个会话开始接待
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧:客户信息面板 */}
|
||||||
|
<div className="w-[300px] flex-shrink-0 border-l border-neutral-200 bg-white overflow-auto">
|
||||||
|
<div className="p-4 border-b border-neutral-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
|
||||||
|
<UserOutlined className="text-blue-500" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium text-neutral-800">{selectedSession?.name || '访客'}</div>
|
||||||
|
<div className="text-xs text-neutral-400 mt-0.5">网页渠道</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-neutral-400 mb-1.5">标签</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
<span className="text-xs px-2 py-0.5 rounded bg-orange-50 text-orange-600">VIP客户</span>
|
||||||
|
<span className="text-xs px-2 py-0.5 rounded bg-blue-50 text-blue-600">新客户</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-neutral-400 mb-1.5">联系信息</div>
|
||||||
|
<div className="text-sm text-neutral-700 space-y-1">
|
||||||
|
<div>📱 138****8888</div>
|
||||||
|
<div>📧 zhang***@example.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-neutral-400 mb-1.5">统计数据</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="bg-neutral-50 rounded p-2 text-center">
|
||||||
|
<div className="text-lg font-semibold text-neutral-800">12</div>
|
||||||
|
<div className="text-xs text-neutral-400">对话次数</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-neutral-50 rounded p-2 text-center">
|
||||||
|
<div className="text-lg font-semibold text-green-600 flex items-center justify-center gap-0.5">
|
||||||
|
4.8 <StarFilled className="text-xs" />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-neutral-400">满意度</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-neutral-400 mb-1.5">内部备注</div>
|
||||||
|
<div className="text-sm text-neutral-500 bg-neutral-50 rounded p-2">
|
||||||
|
之前咨询过售后问题...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const Knowledge = () => (
|
||||||
|
<div className="h-full flex items-center justify-center text-neutral-400 text-center">
|
||||||
|
<div><div className="text-lg mb-1">知识库</div><div className="text-sm">开发中...</div></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
export default Knowledge
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const Settings = () => (
|
||||||
|
<div className="h-full flex items-center justify-center text-neutral-400 text-center">
|
||||||
|
<div><div className="text-lg mb-1">系统设置</div><div className="text-sm">开发中...</div></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
export default Settings
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const Statistics = () => (
|
||||||
|
<div className="h-full flex items-center justify-center text-neutral-400 text-center">
|
||||||
|
<div><div className="text-lg mb-1">数据统计</div><div className="text-sm">开发中...</div></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
export default Statistics
|
||||||
@@ -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 = (
|
||||||
|
<div className="h-full flex items-center justify-center">
|
||||||
|
<Spin size="large" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
function Lazy({ children }: { children: React.ReactNode }) {
|
||||||
|
return <Suspense fallback={loading}>{children}</Suspense>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
element: <div>管理端</div>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <Lazy><AgentLayout /></Lazy>,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Navigate to="/agent/dashboard" replace /> },
|
||||||
|
{
|
||||||
|
path: 'agent',
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Navigate to="/agent/dashboard" replace /> },
|
||||||
|
{ path: 'dashboard', element: <Lazy><Dashboard /></Lazy> },
|
||||||
|
{ path: 'chat-history', element: <Lazy><ChatHistory /></Lazy> },
|
||||||
|
{ path: 'customers', element: <Lazy><Customers /></Lazy> },
|
||||||
|
{ path: 'knowledge', element: <Lazy><Knowledge /></Lazy> },
|
||||||
|
{ path: 'statistics', element: <Lazy><Statistics /></Lazy> },
|
||||||
|
{ path: 'settings', element: <Lazy><Settings /></Lazy> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
@@ -20,7 +20,14 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
/* Path alias */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"ignoreDeprecations": "6.0"
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user