2025-10-04 22:21:30 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="dashboard-container">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<!-- 顶部状态条 -->
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 现代化顶部统计栏 -->
|
|
|
|
|
|
<div class="modern-stats-header">
|
|
|
|
|
|
<StatusBar />
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<StatsOverviewComponent :basic-stats="basicStats" />
|
2025-10-04 22:21:30 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<!-- Grid布局的主要内容区域 -->
|
|
|
|
|
|
<div class="dashboard-grid">
|
2025-10-07 00:15:14 +08:00
|
|
|
|
<!-- 调用统计模块 - 占据2x1网格 -->
|
|
|
|
|
|
<CallStatsComponent :loading="loading" ref="callStatsRef" />
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 用户活跃度分析 - 占据1x1网格 -->
|
|
|
|
|
|
<div class="grid-item user-stats">
|
|
|
|
|
|
<UserStatsComponent
|
|
|
|
|
|
:user-stats="allStatsData?.users"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
ref="userStatsRef"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- AI智能体分析 - 占据1x1网格 -->
|
|
|
|
|
|
<div class="grid-item agent-stats">
|
|
|
|
|
|
<AgentStatsComponent
|
|
|
|
|
|
:agent-stats="allStatsData?.agents"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
ref="agentStatsRef"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 工具调用监控 - 占据1x1网格 -->
|
|
|
|
|
|
<div class="grid-item tool-stats">
|
|
|
|
|
|
<ToolStatsComponent
|
|
|
|
|
|
:tool-stats="allStatsData?.tools"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
ref="toolStatsRef"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 知识库使用情况 - 占据1x1网格 -->
|
|
|
|
|
|
<div class="grid-item knowledge-stats">
|
|
|
|
|
|
<KnowledgeStatsComponent
|
|
|
|
|
|
:knowledge-stats="allStatsData?.knowledge"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
ref="knowledgeStatsRef"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 对话记录 - 占据1x1网格 -->
|
|
|
|
|
|
<div class="grid-item conversations">
|
|
|
|
|
|
<a-card class="conversations-section" title="对话记录" :loading="loading">
|
|
|
|
|
|
<template #extra>
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="filters.user_id"
|
|
|
|
|
|
placeholder="用户ID"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
style="width: 120px"
|
|
|
|
|
|
@change="handleFilterChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="filters.status"
|
|
|
|
|
|
placeholder="状态"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
style="width: 100px"
|
|
|
|
|
|
@change="handleFilterChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-select-option value="active">活跃</a-select-option>
|
|
|
|
|
|
<a-select-option value="deleted">已删除</a-select-option>
|
|
|
|
|
|
<a-select-option value="all">全部</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
<a-button size="small" @click="loadConversations" :loading="loading">
|
|
|
|
|
|
刷新
|
|
|
|
|
|
</a-button>
|
2025-10-08 22:36:35 +08:00
|
|
|
|
<a-button size="small" @click="feedbackModal.show()">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
反馈详情
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<a-table
|
|
|
|
|
|
:columns="conversationColumns"
|
|
|
|
|
|
:data-source="conversations"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
:pagination="conversationPagination"
|
|
|
|
|
|
@change="handleTableChange"
|
|
|
|
|
|
row-key="thread_id"
|
|
|
|
|
|
size="small"
|
2025-10-05 14:50:34 +08:00
|
|
|
|
>
|
2025-10-05 21:45:39 +08:00
|
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
|
|
<template v-if="column.key === 'title'">
|
2025-10-07 17:14:58 +08:00
|
|
|
|
<a @click="handleViewDetail(record)" class="conversation-title" :class="{ 'loading': loadingDetail }">{{ record.title || '未命名对话' }}</a>
|
2025-10-05 21:45:39 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
<template v-if="column.key === 'status'">
|
|
|
|
|
|
<a-tag :color="record.status === 'active' ? 'green' : 'red'" size="small">
|
|
|
|
|
|
{{ record.status === 'active' ? '活跃' : '已删除' }}
|
|
|
|
|
|
</a-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-if="column.key === 'updated_at'">
|
|
|
|
|
|
<span class="time-text">{{ formatDate(record.updated_at) }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-if="column.key === 'actions'">
|
2025-10-07 17:14:58 +08:00
|
|
|
|
<a-button type="link" size="small" @click="handleViewDetail(record)" :loading="loadingDetail">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
详情
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</template>
|
2025-10-05 14:50:34 +08:00
|
|
|
|
</template>
|
2025-10-05 21:45:39 +08:00
|
|
|
|
</a-table>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-05 14:50:34 +08:00
|
|
|
|
|
2025-10-08 22:36:35 +08:00
|
|
|
|
<!-- 反馈模态框 -->
|
|
|
|
|
|
<FeedbackModalComponent ref="feedbackModal" />
|
2025-10-04 22:21:30 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-10-05 21:45:39 +08:00
|
|
|
|
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
2025-10-04 22:21:30 +08:00
|
|
|
|
import { message } from 'ant-design-vue'
|
|
|
|
|
|
import { dashboardApi } from '@/apis/dashboard_api'
|
2025-10-13 15:08:54 +08:00
|
|
|
|
import dayjs, { parseToShanghai } from '@/utils/time'
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 导入子组件
|
|
|
|
|
|
import StatusBar from '@/components/StatusBar.vue'
|
|
|
|
|
|
import UserStatsComponent from '@/components/dashboard/UserStatsComponent.vue'
|
|
|
|
|
|
import ToolStatsComponent from '@/components/dashboard/ToolStatsComponent.vue'
|
|
|
|
|
|
import KnowledgeStatsComponent from '@/components/dashboard/KnowledgeStatsComponent.vue'
|
|
|
|
|
|
import AgentStatsComponent from '@/components/dashboard/AgentStatsComponent.vue'
|
2025-10-07 00:15:14 +08:00
|
|
|
|
import CallStatsComponent from '@/components/dashboard/CallStatsComponent.vue'
|
|
|
|
|
|
import StatsOverviewComponent from '@/components/dashboard/StatsOverviewComponent.vue'
|
2025-10-08 22:36:35 +08:00
|
|
|
|
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
|
|
|
|
|
|
|
|
|
|
|
// 组件引用
|
|
|
|
|
|
const feedbackModal = ref(null)
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 统计数据 - 使用新的响应式结构
|
|
|
|
|
|
const basicStats = ref({})
|
|
|
|
|
|
const allStatsData = ref({
|
|
|
|
|
|
users: null,
|
|
|
|
|
|
tools: null,
|
|
|
|
|
|
knowledge: null,
|
|
|
|
|
|
agents: null
|
2025-10-04 22:21:30 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤器
|
|
|
|
|
|
const filters = reactive({
|
|
|
|
|
|
user_id: '',
|
|
|
|
|
|
agent_id: '',
|
|
|
|
|
|
status: 'active',
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 对话列表
|
|
|
|
|
|
const conversations = ref([])
|
|
|
|
|
|
const loading = ref(false)
|
2025-10-07 17:14:58 +08:00
|
|
|
|
const loadingDetail = ref(false)
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// 调用统计子组件引用
|
|
|
|
|
|
const callStatsRef = ref(null)
|
|
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 分页
|
2025-10-05 21:45:39 +08:00
|
|
|
|
const conversationPagination = reactive({
|
2025-10-04 22:21:30 +08:00
|
|
|
|
current: 1,
|
2025-10-05 21:45:39 +08:00
|
|
|
|
pageSize: 8,
|
2025-10-04 22:21:30 +08:00
|
|
|
|
total: 0,
|
2025-10-05 21:45:39 +08:00
|
|
|
|
showSizeChanger: false,
|
|
|
|
|
|
showQuickJumper: false,
|
|
|
|
|
|
showTotal: (total, range) => `${range[0]}-${range[1]} / ${total}`,
|
2025-10-04 22:21:30 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 表格列定义
|
2025-10-05 21:45:39 +08:00
|
|
|
|
const conversationColumns = [
|
2025-10-04 22:21:30 +08:00
|
|
|
|
{
|
2025-10-05 21:45:39 +08:00
|
|
|
|
title: '对话标题',
|
2025-10-04 22:21:30 +08:00
|
|
|
|
dataIndex: 'title',
|
|
|
|
|
|
key: 'title',
|
2025-10-05 21:45:39 +08:00
|
|
|
|
ellipsis: true,
|
2025-10-04 22:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-10-05 21:45:39 +08:00
|
|
|
|
title: '用户',
|
2025-10-04 22:21:30 +08:00
|
|
|
|
dataIndex: 'user_id',
|
|
|
|
|
|
key: 'user_id',
|
2025-10-05 21:45:39 +08:00
|
|
|
|
width: '80px',
|
|
|
|
|
|
ellipsis: true,
|
2025-10-04 22:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '消息数',
|
|
|
|
|
|
dataIndex: 'message_count',
|
|
|
|
|
|
key: 'message_count',
|
2025-10-05 21:45:39 +08:00
|
|
|
|
width: '60px',
|
|
|
|
|
|
align: 'center',
|
2025-10-04 22:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '状态',
|
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
|
key: 'status',
|
2025-10-05 21:45:39 +08:00
|
|
|
|
width: '70px',
|
|
|
|
|
|
align: 'center',
|
2025-10-04 22:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '更新时间',
|
|
|
|
|
|
dataIndex: 'updated_at',
|
|
|
|
|
|
key: 'updated_at',
|
2025-10-05 21:45:39 +08:00
|
|
|
|
width: '120px',
|
2025-10-04 22:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
key: 'actions',
|
2025-10-05 21:45:39 +08:00
|
|
|
|
width: '60px',
|
|
|
|
|
|
align: 'center',
|
2025-10-04 22:21:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 子组件引用
|
|
|
|
|
|
const userStatsRef = ref(null)
|
|
|
|
|
|
const toolStatsRef = ref(null)
|
|
|
|
|
|
const knowledgeStatsRef = ref(null)
|
|
|
|
|
|
const agentStatsRef = ref(null)
|
|
|
|
|
|
|
|
|
|
|
|
// 加载统计数据 - 使用并行API调用
|
|
|
|
|
|
const loadAllStats = async () => {
|
|
|
|
|
|
loading.value = true
|
2025-10-04 22:21:30 +08:00
|
|
|
|
try {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 使用并行API调用获取所有统计数据
|
|
|
|
|
|
const response = await dashboardApi.getAllStats()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新基础统计数据
|
|
|
|
|
|
basicStats.value = response.basic
|
|
|
|
|
|
|
|
|
|
|
|
// 更新详细统计数据
|
|
|
|
|
|
allStatsData.value = {
|
|
|
|
|
|
users: response.users,
|
|
|
|
|
|
tools: response.tools,
|
|
|
|
|
|
knowledge: response.knowledge,
|
|
|
|
|
|
agents: response.agents
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Dashboard 数据加载完成:', response)
|
|
|
|
|
|
message.success('数据加载成功')
|
2025-10-04 22:21:30 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载统计数据失败:', error)
|
|
|
|
|
|
message.error('加载统计数据失败')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果并行请求失败,尝试单独加载基础数据
|
|
|
|
|
|
try {
|
|
|
|
|
|
const basicResponse = await dashboardApi.getStats()
|
|
|
|
|
|
basicStats.value = basicResponse
|
|
|
|
|
|
message.warning('详细数据加载失败,仅显示基础统计')
|
|
|
|
|
|
} catch (basicError) {
|
|
|
|
|
|
console.error('加载基础统计数据也失败:', basicError)
|
|
|
|
|
|
message.error('无法加载任何统计数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 保留原有的loadStats函数以兼容旧代码
|
|
|
|
|
|
const loadStats = loadAllStats
|
|
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 加载对话列表
|
|
|
|
|
|
const loadConversations = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
user_id: filters.user_id || undefined,
|
|
|
|
|
|
agent_id: filters.agent_id || undefined,
|
|
|
|
|
|
status: filters.status,
|
2025-10-05 21:45:39 +08:00
|
|
|
|
limit: conversationPagination.pageSize,
|
|
|
|
|
|
offset: (conversationPagination.current - 1) * conversationPagination.pageSize,
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await dashboardApi.getConversations(params)
|
|
|
|
|
|
conversations.value = response
|
|
|
|
|
|
// Note: 由于后端没有返回总数,这里暂时设置为当前数据长度
|
2025-10-05 21:45:39 +08:00
|
|
|
|
conversationPagination.total = response.length
|
2025-10-04 22:21:30 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载对话列表失败:', error)
|
|
|
|
|
|
message.error('加载对话列表失败')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 日期格式化
|
|
|
|
|
|
const formatDate = (dateString) => {
|
|
|
|
|
|
if (!dateString) return '-'
|
2025-10-13 15:08:54 +08:00
|
|
|
|
const parsed = parseToShanghai(dateString)
|
|
|
|
|
|
if (!parsed) return '-'
|
|
|
|
|
|
const now = dayjs().tz('Asia/Shanghai')
|
|
|
|
|
|
const diffDays = now.startOf('day').diff(parsed.startOf('day'), 'day')
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
if (diffDays === 0) {
|
2025-10-13 15:08:54 +08:00
|
|
|
|
return parsed.format('HH:mm')
|
|
|
|
|
|
}
|
|
|
|
|
|
if (diffDays === 1) {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
return '昨天'
|
2025-10-13 15:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (diffDays < 7) {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
return `${diffDays}天前`
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
2025-10-13 15:08:54 +08:00
|
|
|
|
return parsed.format('MM-DD')
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查看对话详情
|
|
|
|
|
|
const handleViewDetail = async (record) => {
|
|
|
|
|
|
try {
|
2025-10-07 17:14:58 +08:00
|
|
|
|
loadingDetail.value = true
|
2025-10-04 22:21:30 +08:00
|
|
|
|
const detail = await dashboardApi.getConversationDetail(record.thread_id)
|
|
|
|
|
|
console.log(detail)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取对话详情失败:', error)
|
|
|
|
|
|
message.error('获取对话详情失败')
|
|
|
|
|
|
} finally {
|
2025-10-07 17:14:58 +08:00
|
|
|
|
loadingDetail.value = false
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理过滤器变化
|
|
|
|
|
|
const handleFilterChange = () => {
|
2025-10-13 12:37:06 +08:00
|
|
|
|
conversationPagination.current = 1
|
2025-10-04 22:21:30 +08:00
|
|
|
|
loadConversations()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理表格变化
|
|
|
|
|
|
const handleTableChange = (pag) => {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
conversationPagination.current = pag.current
|
|
|
|
|
|
conversationPagination.pageSize = pag.pageSize
|
2025-10-04 22:21:30 +08:00
|
|
|
|
loadConversations()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 14:50:34 +08:00
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// 清理函数 - 清理所有子组件的图表实例
|
|
|
|
|
|
const cleanupCharts = () => {
|
|
|
|
|
|
if (userStatsRef.value?.cleanup) userStatsRef.value.cleanup()
|
2025-10-07 00:15:14 +08:00
|
|
|
|
if (toolStatsRef.value?.cleanup) userStatsRef.value.cleanup()
|
2025-10-05 21:45:39 +08:00
|
|
|
|
if (knowledgeStatsRef.value?.cleanup) knowledgeStatsRef.value.cleanup()
|
|
|
|
|
|
if (agentStatsRef.value?.cleanup) agentStatsRef.value.cleanup()
|
2025-10-07 00:15:14 +08:00
|
|
|
|
if (callStatsRef.value?.cleanup) callStatsRef.value.cleanup()
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
// 初始化
|
|
|
|
|
|
onMounted(() => {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
loadAllStats()
|
2025-10-04 22:21:30 +08:00
|
|
|
|
loadConversations()
|
|
|
|
|
|
})
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理图表
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
cleanupCharts()
|
|
|
|
|
|
})
|
2025-10-04 22:21:30 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
2025-10-04 22:21:30 +08:00
|
|
|
|
.dashboard-container {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// padding: 0 24px 24px 24px;
|
|
|
|
|
|
background-color: var(--gray-25);
|
2025-10-04 22:21:30 +08:00
|
|
|
|
min-height: calc(100vh - 64px);
|
2025-10-05 21:45:39 +08:00
|
|
|
|
overflow-x: hidden;
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// Dashboard 特有的网格布局
|
|
|
|
|
|
.dashboard-grid {
|
2025-10-04 22:21:30 +08:00
|
|
|
|
display: grid;
|
2025-10-05 21:45:39 +08:00
|
|
|
|
padding: 16px;
|
|
|
|
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
|
|
|
|
grid-template-rows: auto auto;
|
2025-10-04 22:21:30 +08:00
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 24px;
|
2025-10-05 21:45:39 +08:00
|
|
|
|
min-height: 600px;
|
|
|
|
|
|
|
|
|
|
|
|
.grid-item {
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
.conversations-section,
|
|
|
|
|
|
.call-stats-section {
|
2025-10-07 00:15:14 +08:00
|
|
|
|
border-color: var(--gray-200);
|
2025-10-05 21:45:39 +08:00
|
|
|
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 大页面布局:第一行 2x1 + 1x1,第二行 3x1x1
|
|
|
|
|
|
&.call-stats {
|
|
|
|
|
|
grid-column: 1 / 3;
|
|
|
|
|
|
grid-row: 1 / 2;
|
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.user-stats {
|
|
|
|
|
|
grid-column: 3 / 4;
|
|
|
|
|
|
grid-row: 1 / 2;
|
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.agent-stats {
|
|
|
|
|
|
grid-column: 1 / 2;
|
|
|
|
|
|
grid-row: 2 / 3;
|
|
|
|
|
|
min-height: 350px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.tool-stats {
|
|
|
|
|
|
grid-column: 2 / 3;
|
|
|
|
|
|
grid-row: 2 / 3;
|
|
|
|
|
|
min-height: 350px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.knowledge-stats {
|
|
|
|
|
|
grid-column: 3 / 4;
|
|
|
|
|
|
grid-row: 2 / 3;
|
|
|
|
|
|
min-height: 350px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.conversations {
|
|
|
|
|
|
grid-column: 1 / 4;
|
|
|
|
|
|
grid-row: 3 / 4;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// Dashboard 特有的卡片样式
|
|
|
|
|
|
.conversations-section,
|
|
|
|
|
|
.call-stats-section {
|
|
|
|
|
|
background-color: var(--gray-0);
|
|
|
|
|
|
border: 1px solid var(--gray-200);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
box-shadow: none;
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
|
|
|
|
|
&:hover {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
background-color: var(--gray-25);
|
2025-10-07 00:15:14 +08:00
|
|
|
|
border-color: var(--gray-200);
|
2025-10-05 21:45:39 +08:00
|
|
|
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
:deep(.ant-card-head) {
|
|
|
|
|
|
border-bottom: 1px solid var(--gray-200);
|
|
|
|
|
|
min-height: 56px;
|
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
|
background-color: var(--gray-0);
|
|
|
|
|
|
|
|
|
|
|
|
.ant-card-head-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--gray-1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-card-body) {
|
|
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
|
background-color: var(--gray-0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-card-extra) {
|
|
|
|
|
|
.ant-space {
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// Dashboard 特有的占位符样式
|
|
|
|
|
|
.placeholder-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 200px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder-icon {
|
|
|
|
|
|
width: 64px;
|
|
|
|
|
|
height: 64px;
|
|
|
|
|
|
background-color: var(--gray-100);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
color: var(--gray-500);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder-text {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--gray-800);
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder-subtitle {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
}
|
2025-10-05 14:50:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
// Dashboard 特有的对话记录样式
|
2025-10-04 22:21:30 +08:00
|
|
|
|
.conversations-section {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
.conversation-title {
|
|
|
|
|
|
color: var(--main-500);
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
transition: color 0.2s ease;
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
&:hover {
|
|
|
|
|
|
color: var(--main-600);
|
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-04 22:21:30 +08:00
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
.time-text {
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// 调用统计模块样式
|
|
|
|
|
|
.call-stats-section {
|
|
|
|
|
|
.call-stats-container {
|
|
|
|
|
|
.call-summary {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
2025-10-05 21:45:39 +08:00
|
|
|
|
gap: 16px;
|
2025-10-07 00:15:14 +08:00
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
|
|
|
|
|
|
.summary-card {
|
|
|
|
|
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #1e293b;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-label {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
|
.chart {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 280px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ant-card-extra) {
|
|
|
|
|
|
.ant-space {
|
|
|
|
|
|
gap: 8px;
|
2025-10-05 21:45:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-07 00:15:14 +08:00
|
|
|
|
}
|
2025-10-05 21:45:39 +08:00
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
// Dashboard 特有的响应式设计
|
|
|
|
|
|
@media (max-width: 1200px) {
|
2025-10-05 21:45:39 +08:00
|
|
|
|
.dashboard-grid {
|
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
|
grid-template-rows: auto auto auto;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.grid-item {
|
|
|
|
|
|
// 小页面布局:第一行 2x1,第二行和第三行各是 2x1x1
|
|
|
|
|
|
&.call-stats {
|
|
|
|
|
|
grid-column: 1 / 3;
|
|
|
|
|
|
grid-row: 1 / 2;
|
|
|
|
|
|
min-height: 350px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.user-stats {
|
|
|
|
|
|
grid-column: 1 / 2;
|
|
|
|
|
|
grid-row: 2 / 3;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.agent-stats {
|
|
|
|
|
|
grid-column: 2 / 3;
|
|
|
|
|
|
grid-row: 2 / 3;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.tool-stats {
|
|
|
|
|
|
grid-column: 1 / 2;
|
|
|
|
|
|
grid-row: 3 / 4;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.knowledge-stats {
|
|
|
|
|
|
grid-column: 2 / 3;
|
|
|
|
|
|
grid-row: 3 / 4;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.conversations {
|
|
|
|
|
|
grid-column: 1 / 3;
|
|
|
|
|
|
grid-row: 4 / 5;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.dashboard-container {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dashboard-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.grid-item {
|
|
|
|
|
|
&.call-stats,
|
|
|
|
|
|
&.agent-stats,
|
|
|
|
|
|
&.user-stats,
|
|
|
|
|
|
&.tool-stats,
|
|
|
|
|
|
&.knowledge-stats,
|
|
|
|
|
|
&.conversations {
|
|
|
|
|
|
grid-column: 1 / 2;
|
|
|
|
|
|
grid-row: auto;
|
|
|
|
|
|
min-height: 300px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 00:15:14 +08:00
|
|
|
|
.call-stats-section {
|
|
|
|
|
|
.call-stats-container {
|
|
|
|
|
|
.call-summary {
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.summary-card {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-label {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
|
.chart {
|
|
|
|
|
|
height: 200px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 21:45:39 +08:00
|
|
|
|
.placeholder-content {
|
|
|
|
|
|
height: 150px;
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder-icon {
|
|
|
|
|
|
width: 48px;
|
|
|
|
|
|
height: 48px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder-text {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder-subtitle {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-04 22:21:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|