From 280dd48f3f15583198a29e8c14000213807da5f2 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 14 Jul 2026 11:34:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E5=AF=B9=E8=AF=9D=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=92=8C=E8=BF=90=E8=90=A5=E6=A6=82=E8=A7=88API?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E6=88=90=E5=85=A8=E9=83=A8=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=89=8D=E5=90=8E=E7=AB=AF=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/admin/Dashboard.tsx | 109 ++++++------------ web/src/pages/agent/ChatHistory.tsx | 172 ++++++---------------------- 2 files changed, 70 insertions(+), 211 deletions(-) diff --git a/web/src/pages/admin/Dashboard.tsx b/web/src/pages/admin/Dashboard.tsx index 438b467..c64f227 100644 --- a/web/src/pages/admin/Dashboard.tsx +++ b/web/src/pages/admin/Dashboard.tsx @@ -1,46 +1,32 @@ -import { Card, Table, Tag } from 'antd' -import { ArrowUpOutlined, TeamOutlined, UserOutlined, DollarOutlined, SafetyCertificateOutlined } from '@ant-design/icons' -import { Line, Pie } from '@ant-design/charts' - -const kpiData = [ - { label: '租户总数', value: '156', change: 8.3, icon: , color: '#2563eb' }, - { label: '活跃租户', value: '138', change: 5.1, icon: , color: '#16a34a' }, - { label: '平台月收入', value: '¥89,400', change: 12.7, icon: , color: '#d97706' }, - { label: '系统可用率', value: '99.95%', change: 0.02, icon: , color: '#0891b2' }, -] - -const trendData = [ - { date: '07-01', count: 142 }, { date: '07-03', count: 145 }, { date: '07-05', count: 148 }, - { date: '07-07', count: 150 }, { date: '07-09', count: 152 }, { date: '07-11', count: 154 }, - { date: '07-13', count: 156 }, -] - -const planDistribution = [ - { type: '基础版', value: 68 }, { type: '专业版', value: 52 }, { type: '企业版', value: 36 }, -] - -const operations = [ - { time: '2026-07-14 10:30', action: '开通新租户', target: '赵六科技', operator: '管理员A' }, - { time: '2026-07-14 09:15', action: '租户续费', target: '李四商贸', operator: '管理员B' }, - { time: '2026-07-13 16:20', action: '套餐升级', target: '王五集团', operator: '管理员A' }, - { time: '2026-07-13 14:00', action: '租户暂停', target: '孙八信息', operator: '管理员B' }, - { time: '2026-07-13 10:45', action: '开通新租户', target: '周九科技', operator: '管理员A' }, -] - -const actionColors: Record = { - '开通新租户': 'green', - '租户续费': 'blue', - '套餐升级': 'orange', - '租户暂停': 'red', -} +import { useState, useEffect } from 'react' +import { Card, Table, Tag, Spin } from 'antd' +import { ArrowUpOutlined, TeamOutlined, UserOutlined, SafetyCertificateOutlined } from '@ant-design/icons' +import { getTenants, getAdminStats } from '@/services/api' const AdminDashboard = () => { + const [stats, setStats] = useState(null) + const [tenants, setTenants] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + Promise.all([getAdminStats(), getTenants({ page: 1 })]).then(([statsRes, tenantsRes]) => { + setStats(statsRes.data) + setTenants(tenantsRes.list) + }).catch(() => {}).finally(() => setLoading(false)) + }, []) + + if (loading) return
+ + const kpiData = [ + { label: '租户总数', value: stats?.tenant_total || 0, icon: , color: '#2563eb' }, + { label: '活跃租户', value: stats?.active_tenant || 0, icon: , color: '#16a34a' }, + { label: '系统可用率', value: stats?.system_uptime || '99.95%', icon: , color: '#0891b2' }, + ] + return (

运营概览

- - {/* KPI */} -
+
{kpiData.map((k, i) => (
@@ -48,53 +34,22 @@ const AdminDashboard = () => { {k.icon}
{k.value}
-
- {k.change}% 较上月 -
+
运行正常
))}
- - {/* 图表 */} -
- - - - - - -
- - {/* 最近操作 */} {t} }, - { title: '操作', dataIndex: 'action', key: 'action', render: (a: string) => {a} }, - { title: '目标', dataIndex: 'target', key: 'target' }, - { title: '操作人', dataIndex: 'operator', key: 'operator', render: (o: string) => {o} }, + { title: '租户', dataIndex: 'name', key: 'name' }, + { title: '状态', dataIndex: 'status', key: 'status', render: (s: string) => ( + {s} + )}, + { title: '到期日期', dataIndex: 'expire_at', key: 'expire_at', render: (t: string) => t ? new Date(t).toLocaleDateString() : '-' }, ]} /> diff --git a/web/src/pages/agent/ChatHistory.tsx b/web/src/pages/agent/ChatHistory.tsx index 80c3c3f..0a43d06 100644 --- a/web/src/pages/agent/ChatHistory.tsx +++ b/web/src/pages/agent/ChatHistory.tsx @@ -1,179 +1,83 @@ -import { useState } from 'react' -import { Input, Select, DatePicker, Tag, Empty } from 'antd' +import { useState, useEffect } from 'react' +import { Input, Select, Tag, Empty, Spin } from 'antd' import { SearchOutlined, UserOutlined } from '@ant-design/icons' -import type { Dayjs } from 'dayjs' - -interface ChatRecord { - id: string - visitorName: string - avatar?: string - lastMessage: string - time: string - status: 'active' | 'ended' | 'archived' - channel: string - agent: string - satisfaction?: number - messages: { sender: string; content: string; time: string; type: 'message' | 'system' | 'action' }[] -} - -const mockRecords: ChatRecord[] = [ - { - id: '1', visitorName: '张三', lastMessage: '好的谢谢,问题已解决', time: '10:45', status: 'ended', channel: '网页', agent: '客服小王', - satisfaction: 5, - messages: [ - { sender: '张三', content: '你好,我的订单怎么还没发货?', time: '10:30', type: 'message' }, - { sender: '客服小王', content: '您好,请提供一下您的订单号,我帮您查看', time: '10:31', type: 'message' }, - { sender: '张三', content: '订单号 AB20260714001', time: '10:32', type: 'message' }, - { sender: '系统', content: '客服小王查看了客户资料', time: '10:32', type: 'system' }, - { sender: '客服小王', content: '您好,您的订单已经在配送中,预计明天到达', time: '10:35', type: 'message' }, - { sender: '张三', content: '好的谢谢,问题已解决', time: '10:45', type: 'message' }, - { sender: '系统', content: '客服小王结束会话,原因:已解决', time: '10:45', type: 'action' }, - ], - }, - { - id: '2', visitorName: '李四', lastMessage: '这个功能怎么用?', time: '09:20', status: 'ended', channel: '微信', agent: '客服小李', - satisfaction: 4, - messages: [ - { sender: '李四', content: '这个功能怎么用?', time: '09:15', type: 'message' }, - { sender: '客服小李', content: '您好,请问您咨询的是哪个功能?', time: '09:16', type: 'message' }, - { sender: '李四', content: '就是那个批量导入', time: '09:17', type: 'message' }, - { sender: '系统', content: '客服小李使用了知识库条目"批量导入指南"', time: '09:18', type: 'action' }, - { sender: '客服小李', content: '批量导入功能在设置→数据管理中,支持 CSV 和 Excel 格式,点击即可上传', time: '09:19', type: 'message' }, - { sender: '李四', content: '明白了,谢谢', time: '09:20', type: 'message' }, - { sender: '系统', content: '客服小李结束会话,原因:已解决', time: '09:20', type: 'action' }, - ], - }, - { - id: '3', visitorName: '王五', lastMessage: '你们的价格比其他家贵很多', time: '08:50', status: 'active', channel: 'APP', agent: '客服小张', - messages: [ - { sender: '王五', content: '你们的价格比其他家贵很多', time: '08:45', type: 'message' }, - { sender: '客服小张', content: '您好,我们的产品在功能完整性和服务支持上有明显优势', time: '08:46', type: 'message' }, - { sender: '王五', content: '具体说说看', time: '08:47', type: 'message' }, - { sender: '客服小张', content: '我们有7×24小时在线客服、免费知识库搭建、数据报表分析等增值服务', time: '08:48', type: 'message' }, - { sender: '系统', content: '客服小张将会话转接给客服主管', time: '08:49', type: 'action' }, - { sender: '王五', content: '那还可以', time: '08:50', type: 'message' }, - ], - }, - { - id: '4', visitorName: '匿名访客342', lastMessage: '请问可以试用吗', time: '07-11 16:30', status: 'archived', channel: '网页', agent: '客服小王', - satisfaction: 3, - messages: [ - { sender: '匿名访客342', content: '请问可以试用吗', time: '16:25', type: 'message' }, - { sender: '客服小王', content: '可以的,我们提供7天免费试用', time: '16:26', type: 'message' }, - { sender: '系统', content: '客服小王结束会话,原因:已解决', time: '16:30', type: 'action' }, - ], - }, -] +import { getSessions, type Session as SessionType } from '@/services/api' const statusColors: Record = { active: 'blue', ended: 'green', archived: 'default' } -const statusLabels: Record = { active: '进行中', ended: '已结束', archived: '已归档' } +const statusLabels: Record = { active: '进行中', ended: '已结束', waiting: '等待中' } const ChatHistory = () => { + const [sessions, setSessions] = useState([]) + const [loading, setLoading] = useState(true) const [search, setSearch] = useState('') - const [channelFilter, setChannelFilter] = useState() - const [agentFilter, setAgentFilter] = useState() const [statusFilter, setStatusFilter] = useState() - const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null]>([null, null]) - const [selectedId, setSelectedId] = useState('1') + const [selectedId, setSelectedId] = useState(null) - const filtered = mockRecords.filter(r => { - if (search && !r.visitorName.includes(search)) return false - if (channelFilter && r.channel !== channelFilter) return false - if (agentFilter && r.agent !== agentFilter) return false - if (statusFilter && r.status !== statusFilter) return false - return true - }) + useEffect(() => { loadSessions() }, [statusFilter]) - const selected = mockRecords.find(r => r.id === selectedId) + const loadSessions = async () => { + setLoading(true) + try { + const res = await getSessions({ status: statusFilter }) + setSessions(res.list) + if (res.list.length > 0 && !selectedId) setSelectedId(res.list[0].id) + } catch { setSessions([]) } finally { setLoading(false) } + } + + const selected = sessions.find(s => s.id === selectedId) return (
- {/* 左侧列表 */}
- } placeholder="搜索访客名称..." value={search} onChange={e => setSearch(e.target.value)} allowClear size="small" /> -
- ({ value: v, label: statusLabels[v] }))} /> -
-
- } placeholder="搜索访客..." value={search} onChange={e => setSearch(e.target.value)} allowClear size="small" /> +