Files
xiaozhi-esp32-server/main/manager-mobile/src/pages/chat-history/index.vue
T

404 lines
8.8 KiB
Vue

<script lang="ts" setup>
import type { ChatSession } from '@/api/chat-history/types'
import { computed, onMounted, ref } from 'vue'
import { getChatSessions } from '@/api/chat-history/chat-history'
import { t } from '@/i18n'
defineOptions({
name: 'ChatHistory',
})
const props = withDefaults(defineProps<Props>(), {
agentId: 'default',
})
// 接收props
interface Props {
agentId?: string
}
// 获取屏幕边界到安全区域距离
let safeAreaInsets: any
let systemInfo: any
// #ifdef MP-WEIXIN
systemInfo = uni.getWindowInfo()
safeAreaInsets = systemInfo.safeArea
? {
top: systemInfo.safeArea.top,
right: systemInfo.windowWidth - systemInfo.safeArea.right,
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
left: systemInfo.safeArea.left,
}
: null
// #endif
// #ifndef MP-WEIXIN
systemInfo = uni.getSystemInfoSync()
safeAreaInsets = systemInfo.safeAreaInsets
// #endif
// 聊天会话数据
const sessionList = ref<ChatSession[]>([])
const loading = ref(false)
const loadingMore = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const pageSize = 10
// 使用传入的智能体ID
const currentAgentId = computed(() => {
return props.agentId
})
// 加载聊天会话列表
async function loadChatSessions(page = 1, isRefresh = false) {
try {
console.log(t('chatHistory.getChatSessions'), { page, isRefresh })
// 检查是否有当前选中的智能体
if (!currentAgentId.value) {
console.warn(t('chatHistory.noSelectedAgent'))
sessionList.value = []
return
}
if (page === 1) {
loading.value = true
}
else {
loadingMore.value = true
}
const response = await getChatSessions(currentAgentId.value, {
page,
limit: pageSize,
})
if (page === 1) {
sessionList.value = response.list || []
}
else {
sessionList.value.push(...(response.list || []))
}
// 更新分页信息
hasMore.value = (response.list?.length || 0) === pageSize
currentPage.value = page
}
catch (error) {
console.error(t('chatHistory.getChatSessionsFailed'), error)
if (page === 1) {
sessionList.value = []
}
}
finally {
loading.value = false
loadingMore.value = false
}
}
// 暴露给父组件的刷新方法
async function refresh() {
currentPage.value = 1
hasMore.value = true
await loadChatSessions(1, true)
}
// 暴露给父组件的加载更多方法
async function loadMore() {
if (!hasMore.value || loadingMore.value) {
return
}
await loadChatSessions(currentPage.value + 1)
}
// 格式化时间
function formatTime(timeStr: string) {
if (!timeStr)
return t('chatHistory.unknownTime')
// 处理时间字符串,确保格式正确
const date = new Date(timeStr.replace(' ', 'T')) // 转换为ISO格式
const now = new Date()
// 检查日期是否有效
if (Number.isNaN(date.getTime())) {
return timeStr // 如果解析失败,直接返回原字符串
}
const diff = now.getTime() - date.getTime()
// 小于1分钟
if (diff < 60000)
return t('chatHistory.justNow')
// 小于1小时
if (diff < 3600000)
return t('chatHistory.minutesAgo', { minutes: Math.floor(diff / 60000) })
// 小于1天(24小时)
if (diff < 86400000)
return t('chatHistory.hoursAgo', { hours: Math.floor(diff / 3600000) })
// 小于7天
if (diff < 604800000) {
const days = Math.floor(diff / 86400000)
return t('chatHistory.daysAgo', { days })
}
// 超过7天,显示具体日期
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const currentYear = now.getFullYear()
// 如果是当前年份,不显示年份
if (year === currentYear) {
return `${month}-${day}`
}
return `${year}-${month}-${day}`
}
// 进入聊天详情
function goToChatDetail(session: ChatSession) {
uni.navigateTo({
url: `/pages/chat-history/detail?sessionId=${session.sessionId}&agentId=${currentAgentId.value}`,
})
}
onMounted(async () => {
// 智能体已简化为默认
loadChatSessions(1)
})
// 暴露方法给父组件
defineExpose({
refresh,
loadMore,
})
</script>
<template>
<view class="chat-history-container" style="background: #f5f7fb; min-height: 100%;">
<!-- 加载状态 -->
<view v-if="loading && sessionList.length === 0" class="loading-container">
<wd-loading color="#336cff" />
<text class="loading-text">
{{ t('chatHistory.loading') }}
</text>
</view>
<!-- 会话列表 -->
<view v-else-if="sessionList.length > 0" class="session-container">
<!-- 聊天会话列表 -->
<view class="session-list">
<view
v-for="session in sessionList"
:key="session.sessionId"
class="session-item"
@click="goToChatDetail(session)"
>
<view class="session-card">
<view class="session-info">
<view class="session-header">
<text class="session-title">
{{ session.title || `${t('chatHistory.conversationRecord')} ${session.sessionId.substring(0, 8)}...` }}
</text>
<text class="session-time">
{{ formatTime(session.createdAt) }}
</text>
</view>
<view class="session-meta">
<text class="chat-count">
{{ t('chatHistory.totalChats', { count: session.chatCount }) }}
</text>
</view>
</view>
<wd-icon name="arrow-right" custom-class="arrow-icon" />
</view>
</view>
</view>
<!-- 加载更多状态 -->
<view v-if="loadingMore" class="loading-more">
<wd-loading color="#336cff" size="24" />
<text class="loading-more-text">
{{ t('chatHistory.loading') }}
</text>
</view>
<!-- 没有更多数据 -->
<view v-else-if="!hasMore && sessionList.length > 0" class="no-more">
<text class="no-more-text">
{{ t('chatHistory.noMoreData') }}
</text>
</view>
</view>
<!-- 空状态 -->
<view v-else-if="!loading" class="empty-state">
<wd-icon name="chat" custom-class="empty-icon" />
<text class="empty-text">
{{ t('chatHistory.noChatRecords') }}
</text>
<text class="empty-desc">
{{ t('chatHistory.chatRecordsDescription') }}
</text>
</view>
</view>
</template>
<style lang="scss" scoped>
.chat-history-container {
position: relative;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666666;
}
.loading-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx;
gap: 16rpx;
.loading-more-text {
font-size: 26rpx;
color: #666666;
}
}
.no-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx;
.no-more-text {
font-size: 26rpx;
color: #999999;
}
}
.navbar-section {
background: #ffffff;
}
.status-bar {
background: #ffffff;
width: 100%;
}
.session-list {
display: flex;
flex-direction: column;
gap: 24rpx;
padding: 20rpx;
box-sizing: border-box;
}
.session-item {
background: #fbfbfb;
border-radius: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
border: 1rpx solid #eeeeee;
overflow: hidden;
cursor: pointer;
transition: all 0.2s ease;
&:active {
background: #f8f9fa;
}
}
.session-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
.session-info {
width: 94%;
.session-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
.session-title {
font-size: 32rpx;
font-weight: 600;
color: #232338;
width: 70%;
word-break: break-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.session-time {
font-size: 24rpx;
color: #9d9ea3;
}
}
.session-meta {
.chat-count {
font-size: 28rpx;
color: #65686f;
}
}
}
:deep(.arrow-icon) {
font-size: 24rpx;
color: #c7c7cc;
margin-left: 16rpx;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
text-align: center;
:deep(.empty-icon) {
font-size: 120rpx;
color: #d9d9d9;
margin-bottom: 32rpx;
}
.empty-text {
font-size: 32rpx;
color: #666666;
margin-bottom: 16rpx;
font-weight: 500;
}
.empty-desc {
font-size: 26rpx;
color: #999999;
line-height: 1.5;
}
}
</style>