diff --git a/web/src/components/layout/AdminLayout.tsx b/web/src/components/layout/AdminLayout.tsx
new file mode 100644
index 0000000..48fd118
--- /dev/null
+++ b/web/src/components/layout/AdminLayout.tsx
@@ -0,0 +1,20 @@
+import { Outlet } from 'react-router-dom'
+import { Layout } from 'antd'
+import AdminSidebar from './AdminSidebar'
+
+const { Content } = Layout
+
+const AdminLayout = () => {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AdminLayout
diff --git a/web/src/components/layout/AdminSidebar.tsx b/web/src/components/layout/AdminSidebar.tsx
new file mode 100644
index 0000000..1c80f41
--- /dev/null
+++ b/web/src/components/layout/AdminSidebar.tsx
@@ -0,0 +1,42 @@
+import { useLocation, useNavigate } from 'react-router-dom'
+import { Layout, Menu } from 'antd'
+import {
+ DashboardOutlined,
+ TeamOutlined,
+ GiftOutlined,
+ ToolOutlined,
+} from '@ant-design/icons'
+
+const { Sider } = Layout
+
+const menuItems = [
+ { key: '/admin/dashboard', icon: , label: '运营概览' },
+ { key: '/admin/tenants', icon: , label: '租户管理' },
+ { key: '/admin/plans', icon: , label: '套餐管理' },
+ { key: '/admin/ops', icon: , label: '系统运维' },
+]
+
+const AdminSidebar = () => {
+ const location = useLocation()
+ const navigate = useNavigate()
+
+ const selectedKey = menuItems.find(item => location.pathname.startsWith(item.key))?.key || '/admin/dashboard'
+
+ return (
+
+
+
+ 客服云管理
+
+
+ )
+}
+
+export default AdminSidebar
diff --git a/web/src/pages/WidgetPreview.tsx b/web/src/pages/WidgetPreview.tsx
new file mode 100644
index 0000000..dee7808
--- /dev/null
+++ b/web/src/pages/WidgetPreview.tsx
@@ -0,0 +1,17 @@
+import VisitorChat from '@/widgets/VisitorChat'
+
+const WidgetPreview = () => {
+ return (
+
@@ -21,9 +27,20 @@ function Lazy({ children }: { children: React.ReactNode }) {
}
export const router = createBrowserRouter([
+ {
+ path: '/widget/preview',
+ element:
,
+ },
{
path: '/admin',
- element:
管理端
,
+ element:
,
+ children: [
+ { index: true, element:
},
+ { path: 'dashboard', element:
},
+ { path: 'tenants', element:
},
+ { path: 'plans', element:
},
+ { path: 'ops', element:
},
+ ],
},
{
path: '/',
diff --git a/web/src/widgets/VisitorChat.tsx b/web/src/widgets/VisitorChat.tsx
new file mode 100644
index 0000000..a15f60e
--- /dev/null
+++ b/web/src/widgets/VisitorChat.tsx
@@ -0,0 +1,151 @@
+interface Message {
+ id: string
+ sender: 'visitor' | 'agent'
+ content: string
+ time: string
+}
+
+const initialMessages: Message[] = [
+ { id: '1', sender: 'agent', content: '您好!欢迎咨询客服云,请问有什么可以帮您的?', time: '10:30' },
+]
+
+const quickQuestions = ['产品功能介绍', '价格咨询', '售后服务', '合作咨询']
+
+import { useState } from 'react'
+import { CloseOutlined, MessageOutlined, SmileOutlined, PaperClipOutlined, SendOutlined, StarFilled } from '@ant-design/icons'
+
+const VisitorChat = () => {
+ const [open, setOpen] = useState(false)
+ const [messages, setMessages] = useState
(initialMessages)
+ const [input, setInput] = useState('')
+ const [typing, setTyping] = useState(false)
+ const [showRating, setShowRating] = useState(false)
+ const [rated, setRated] = useState(false)
+
+ const sendMessage = (text: string) => {
+ if (!text.trim()) return
+ const msg: Message = { id: Date.now().toString(), sender: 'visitor', content: text, time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) }
+ setMessages(prev => [...prev, msg])
+ setInput('')
+ setTyping(true)
+ setTimeout(() => {
+ setTyping(false)
+ const reply: Message = { id: (Date.now() + 1).toString(), sender: 'agent', content: '感谢您的咨询,客服正在为您处理中...', time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) }
+ setMessages(prev => [...prev, reply])
+ }, 1500)
+ }
+
+ const handleEnd = () => {
+ if (!rated) {
+ setShowRating(true)
+ }
+ setOpen(false)
+ }
+
+ return (
+ <>
+ {/* 悬浮按钮 */}
+ {!open && (
+
+ )}
+
+ {/* 聊天窗口 */}
+ {open && (
+
+ {/* 顶部 */}
+
+
+
+ 云
+
+
+
客服云
+
{typing ? '正在输入...' : '在线'}
+
+
+
+
+
+ {/* 消息区域 */}
+
+ {messages.map(msg => (
+
+
+ {msg.content}
+
{msg.time}
+
+
+ ))}
+ {typing && (
+
+ )}
+
+
+ {/* 快捷问题 */}
+ {messages.length <= 1 && (
+
+
+ {quickQuestions.map((q, i) => (
+
+ ))}
+
+
+ )}
+
+ {/* 输入框 */}
+
+
+
+
+
setInput(e.target.value)}
+ onKeyDown={e => { if (e.key === 'Enter') { sendMessage(input) } }}
+ />
+
sendMessage(input)} />
+
+
+
+ {/* 满意度弹窗 */}
+ {showRating && (
+
+
+
本次服务如何?
+
请对我们的服务进行评价
+
+ {[1, 2, 3, 4, 5].map(star => (
+ { setRated(true); setShowRating(false) }}
+ />
+ ))}
+
+
+
+
+ )}
+
+ )}
+ >
+ )
+}
+
+export default VisitorChat