ForcePilot/web/src/views/DashboardView.vue

325 lines
7.9 KiB
Vue
Raw Normal View History

<template>
<div class="dashboard-container">
<!-- 统计卡片区域 -->
<div class="stats-section">
<a-card class="stat-card" :loading="loading">
<a-statistic title="总对话数" :value="stats.total_conversations" />
</a-card>
<a-card class="stat-card" :loading="loading">
<a-statistic title="活跃对话" :value="stats.active_conversations" />
</a-card>
<a-card class="stat-card" :loading="loading">
<a-statistic title="总消息数" :value="stats.total_messages" />
</a-card>
<a-card class="stat-card" :loading="loading">
<a-statistic title="用户数" :value="stats.total_users" />
</a-card>
</div>
<!-- 过滤器区域 -->
<a-card class="filter-section" title="筛选条件">
<a-row :gutter="16">
<a-col :span="6">
<a-input
v-model:value="filters.user_id"
placeholder="用户ID"
allow-clear
@change="handleFilterChange"
/>
</a-col>
<a-col :span="6">
<a-input
v-model:value="filters.agent_id"
placeholder="智能体ID"
allow-clear
@change="handleFilterChange"
/>
</a-col>
<a-col :span="6">
<a-select
v-model:value="filters.status"
placeholder="状态"
style="width: 100%"
@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-col>
<a-col :span="6">
<a-button type="primary" @click="loadConversations" :loading="loading">
刷新
</a-button>
</a-col>
</a-row>
</a-card>
<!-- 对话列表区域 -->
<a-card class="conversations-section" title="对话记录">
<a-table
:columns="columns"
:data-source="conversations"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
row-key="thread_id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'">
<a @click="handleViewDetail(record)">{{ record.title }}</a>
</template>
<template v-if="column.key === 'status'">
<a-tag :color="record.status === 'active' ? 'green' : 'red'">
{{ record.status }}
</a-tag>
</template>
<template v-if="column.key === 'created_at'">
{{ formatDate(record.created_at) }}
</template>
<template v-if="column.key === 'updated_at'">
{{ formatDate(record.updated_at) }}
</template>
<template v-if="column.key === 'actions'">
<a-button type="link" size="small" @click="handleViewDetail(record)">
查看详情
</a-button>
</template>
</template>
</a-table>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { dashboardApi } from '@/apis/dashboard_api'
// 统计数据
const stats = reactive({
total_conversations: 0,
active_conversations: 0,
total_messages: 0,
total_users: 0,
})
// 过滤器
const filters = reactive({
user_id: '',
agent_id: '',
status: 'active',
})
// 对话列表
const conversations = ref([])
const loading = ref(false)
// 分页
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100'],
})
// 表格列定义
const columns = [
{
title: '标题',
dataIndex: 'title',
key: 'title',
width: '20%',
},
{
title: '用户ID',
dataIndex: 'user_id',
key: 'user_id',
width: '12%',
},
{
title: '智能体ID',
dataIndex: 'agent_id',
key: 'agent_id',
width: '12%',
},
{
title: '消息数',
dataIndex: 'message_count',
key: 'message_count',
width: '8%',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: '8%',
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at',
width: '15%',
},
{
title: '更新时间',
dataIndex: 'updated_at',
key: 'updated_at',
width: '15%',
},
{
title: '操作',
key: 'actions',
width: '10%',
},
]
// 加载统计数据
const loadStats = async () => {
try {
const response = await dashboardApi.getStats()
Object.assign(stats, response)
} catch (error) {
console.error('加载统计数据失败:', error)
message.error('加载统计数据失败')
}
}
// 加载对话列表
const loadConversations = async () => {
loading.value = true
try {
const params = {
user_id: filters.user_id || undefined,
agent_id: filters.agent_id || undefined,
status: filters.status,
limit: pagination.pageSize,
offset: (pagination.current - 1) * pagination.pageSize,
}
const response = await dashboardApi.getConversations(params)
conversations.value = response
// Note: 由于后端没有返回总数,这里暂时设置为当前数据长度
// 如果需要精确分页,需要后端返回 total count
} catch (error) {
console.error('加载对话列表失败:', error)
message.error('加载对话列表失败')
} finally {
loading.value = false
}
}
// 查看对话详情
const handleViewDetail = async (record) => {
try {
loading.value = true
const detail = await dashboardApi.getConversationDetail(record.thread_id)
console.log('========================================')
console.log('=== 数据库原始数据 ===')
console.log('========================================')
console.log('完整对话数据JSON:')
console.log(JSON.stringify(detail, null, 2))
console.log('\n')
console.log('完整对话数据(对象):')
console.log(detail)
console.log('========================================')
message.success('数据库原始数据已输出到控制台')
} catch (error) {
console.error('获取对话详情失败:', error)
message.error('获取对话详情失败')
} finally {
loading.value = false
}
}
// 处理过滤器变化
const handleFilterChange = () => {
pagination.current = 1
loadConversations()
}
// 处理表格变化
const handleTableChange = (pag) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
loadConversations()
}
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
}
// 初始化
onMounted(() => {
loadStats()
loadConversations()
})
</script>
<style scoped lang="less">
.dashboard-container {
padding: 24px;
background-color: var(--bg-color);
min-height: calc(100vh - 64px);
}
.stats-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background-color: var(--card-bg-color);
border-radius: 8px;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
}
.filter-section {
margin-bottom: 24px;
background-color: var(--card-bg-color);
border-radius: 8px;
}
.conversations-section {
background-color: var(--card-bg-color);
border-radius: 8px;
}
:deep(.ant-table) {
background-color: transparent;
}
:deep(.ant-table-thead > tr > th) {
background-color: var(--hover-bg-color);
border-bottom: 1px solid var(--border-color);
}
:deep(.ant-table-tbody > tr > td) {
border-bottom: 1px solid var(--border-color);
}
:deep(.ant-table-tbody > tr:hover > td) {
background-color: var(--hover-bg-color);
}
</style>