Commit da7791dc by pengshun

fix: page chat analysis

parent 2005e2c8
No preview for this file type
......@@ -3,25 +3,22 @@
<mapper namespace="cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatAnalysisMapper">
<select id="selectPageLatestByChatId" resultType="cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatAnalysisDO">
SELECT a.*
SELECT *
FROM (
SELECT a.*,
ROW_NUMBER() OVER (
PARTITION BY a.chat_id
ORDER BY a.chat_time DESC, a.id DESC -- 按时间升序取最早,若时间相同则取id最小的
) AS rn
FROM ai_chat_analysis a
INNER JOIN (
SELECT chat_id, MAX(chat_time) AS max_time
FROM ai_chat_analysis
<where>
<if test="reqVO.user != null and reqVO.user != ''">
AND `user` = #{reqVO.user}
</if>
<if test="reqVO.question != null and reqVO.question != ''">
AND question LIKE CONCAT('%', #{reqVO.question}, '%')
</if>
<if test="reqVO.chatTime != null and reqVO.chatTime.length == 2">
AND chat_time BETWEEN #{reqVO.chatTime[0]} AND #{reqVO.chatTime[1]}
AND a.chat_time BETWEEN #{reqVO.chatTime[0]} AND #{reqVO.chatTime[1]}
</if>
</where>
GROUP BY chat_id
) b ON a.chat_id = b.chat_id AND a.chat_time = b.max_time
ORDER BY a.chat_time DESC
) t
WHERE t.rn = 1
ORDER BY t.chat_time DESC, t.id DESC -- 最终按时间倒序排序,满足分页需求
</select>
</mapper>
\ No newline at end of file
......@@ -16,8 +16,8 @@
<div class="stat-main">
<span class="num">{{ statsData?.totalCount || 0 }}</span>
<span :class="['growth', (statsData?.percentQAPrevious || 0) >= 0 ? 'up' : 'down']">
{{ (statsData?.percentQAPrevious || 0) >= 0 ? '↑' : '↓' }}
{{ Math.abs(statsData?.percentQAPrevious || 0).toFixed(2) }}%
{{ (statsData?.percentQAPrevious || 100) >= 0 ? '↑' : '↓' }}
{{ Math.abs(statsData?.percentQAPrevious || 100).toFixed(2) }}%
</span>
</div>
<div class="compare-text">较前一{{ config.compareUnit }}</div>
......
<template>
<el-aside width="260px" style="height: 100vh" class="conversation-container h-100%">
<div class="h-100%">
<el-aside width="260px" class="conversation-container">
<div class="header-section">
<el-date-picker
v-model="searchDate"
type="date"
......@@ -11,51 +11,49 @@
:editable="false"
@change="searchConversation"
/>
</div>
<div class="conversation-list" ref="conversationListRef" @scroll="handleScroll">
<!-- 加载中(首次) -->
<div v-if="loading" class="loading-wrapper">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
<!-- 无数据 -->
<el-empty v-else-if="conversationList.length === 0" description="暂无对话记录" />
<!-- 列表 -->
<div v-else class="conversation-items">
<div
class="conversation-item"
v-for="conversation in conversationList"
:key="conversation.id"
@click="handleConversationClick(conversation.id)"
@mouseover="hoverConversationId = conversation.id"
@mouseout="hoverConversationId = null"
>
<div
:class="
conversation.id === activeConversationId ? 'conversation active' : 'conversation'
"
>
<div class="title-wrapper">
<img class="avatar" :src="roleAvatarDefaultImg" />
<div class="title-info">
<span class="user">{{ conversation.user || '匿名用户' }}</span>
<span class="time">{{ formatChatTime(conversation.chatTime) }}</span>
</div>
<div class="conversation-list" ref="conversationListRef">
<div v-if="loading" class="loading-wrapper">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
<el-empty v-else-if="conversationList.length === 0" description="暂无对话记录" />
<div v-else class="conversation-items">
<div
class="conversation-item"
v-for="conversation in conversationList"
:key="conversation.id"
@click="handleConversationClick(conversation.id)"
>
<div :class="['conversation', { active: conversation.id === activeConversationId }]">
<div class="title-wrapper">
<img class="avatar" :src="roleAvatarDefaultImg" />
<div class="title-info">
<span class="user">{{ conversation.user || '匿名用户' }}</span>
<span class="time">{{ formatChatTime(conversation.chatTime) }}</span>
</div>
</div>
</div>
</div>
<!-- 加载更多指示器 -->
<div v-if="loadingMore" class="loading-wrapper">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载更多...</span>
<div v-if="hasMore" class="load-more-btn-wrapper">
<div class="load-more-btn" @click="loadMore">
<template v-if="!loadingMore">
<span>加载更多</span>
</template>
<template v-else>
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</template>
</div>
</div>
<div class="h-160px w-100%"></div>
<div v-else class="no-more-tip">没有更多了~</div>
</div>
</div>
</el-aside>
</template>
<script setup lang="ts">
import { ref, onMounted, toRefs, watch, reactive } from 'vue'
import { ChatAnalysisApi, ChatAnalysisVO } from '@/api/ai/chat/analysis'
......@@ -154,8 +152,8 @@ const loadData = async (append = false) => {
if (append) {
// 追加数据,需去重(根据 id)
const existingIds = new Set(conversationList.value.map(item => item.id))
const uniqueNewList = newList.filter(item => !existingIds.has(item.id))
const existingIds = new Set(conversationList.value.map(item => item.chatId))
const uniqueNewList = newList.filter(item => !existingIds.has(item.chatId))
conversationList.value.push(...uniqueNewList)
} else {
conversationList.value = newList
......@@ -189,23 +187,18 @@ const loadData = async (append = false) => {
}
/** 加载下一页 */
/** const loadMore = async () => {
if (!hasMore.value || loadingMore.value || loading.value) return
queryParams.pageNo++
await loadData(true)
} */
const loadMore = async () => {
if (!hasMore.value || loadingMore.value || loading.value) return
queryParams.pageNo++
await loadData(true)
}
/** 滚动监听 */
const handleScroll = (e: Event) => {
const target = e.target as HTMLElement
const scrollTop = target.scrollTop
const scrollHeight = target.scrollHeight
const clientHeight = target.clientHeight
// 滚动到底部(预留 10px 阈值)
if (scrollTop + clientHeight + 10 >= scrollHeight) {
loadMore()
}
}
/** 触发搜索(重置分页) */
const searchConversation = async () => {
......@@ -282,83 +275,85 @@ onMounted(() => {
.conversation-container {
display: flex;
flex-direction: column;
padding: 10px;
overflow: hidden;
height: 100%;
.btn-new-conversation {
padding: 18px 0;
width: 100%;
margin-bottom: 10px;
height: 100%; // 锁定侧边栏高度为视口高度
background-color: #fff;
border-right: 1px solid #e6e6e6;
overflow: hidden; // 禁止 aside 出现滚动条
box-sizing: border-box;
.header-section {
padding: 10px;
flex-shrink: 0; // 确保筛选框不被压缩
}
.search-date-picker {
width: 100% !important;
margin-bottom: 10px;
:deep(.el-input__wrapper) {
width: 100%;
box-sizing: border-box;
}
}
.conversation-list {
flex: 1;
overflow-y: auto;
height: 100%;
flex: 1; // 核心:自动填满剩下的所有垂直空间
overflow-y: auto; // 核心:内容超出时在这里产生滚动条
padding: 0 10px;
// 自定义滚动条美化(可选)
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 10px;
}
.loading-wrapper {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
gap: 8px;
color: #909399;
}
}
.conversation-items {
.conversation-item {
margin-bottom: 8px;
.conversation {
display: flex;
padding: 10px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
&:hover {
background-color: #f5f7fa;
}
&.active {
background-color: #eef5fe;
border-color: #409eff;
}
.conversation-items {
.conversation-item {
margin-top: 5px;
.conversation {
.title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-radius: 5px;
cursor: pointer;
&.active {
background-color: #e6e6e6;
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 10px;
}
.title-wrapper {
.title-info {
display: flex;
align-items: center;
.title-info {
display: flex;
flex-direction: column;
margin-left: 10px;
.user {
font-size: 14px;
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 12px;
color: #999;
line-height: 1.4;
}
flex-direction: column;
.user {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.avatar {
width: 25px;
height: 25px;
border-radius: 4px;
}
}
.button-wrapper {
display: flex;
.btn {
margin: 0;
padding: 4px;
.time {
font-size: 12px;
color: #999;
}
}
}
......@@ -366,12 +361,34 @@ onMounted(() => {
}
}
.tool-box {
padding: 10px 20px;
background: #f9f9f9;
display: flex;
justify-content: space-between;
cursor: pointer;
.load-more-btn-wrapper {
padding: 15px 0;
.load-more-btn {
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
padding: 8px;
background: #f0f2f5;
border-radius: 4px;
font-size: 13px;
color: #606266;
cursor: pointer;
&:hover {
background: #e4e7ed;
}
}
}
.no-more-tip {
text-align: center;
padding: 15px;
color: #c0c4cc;
font-size: 12px;
}
.bottom-padding {
height: 20px;
}
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment