Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
shun peng
/
lufa-ai
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
da7791dc
authored
Mar 31, 2026
by
pengshun
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: page chat analysis
parent
2005e2c8
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
142 additions
and
126 deletions
+142
-126
.DS_Store
+0
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/resources/mapper/chat/AiChatAnalysisMapper.xml
+12
-14
yudao-ui-admin-vue3/.DS_Store
+0
-0
yudao-ui-admin-vue3/src/views/ai/chat_analysis/analysis/index.vue
+2
-2
yudao-ui-admin-vue3/src/views/ai/chat_analysis/message/components/conversation/ConversationList.vue
+128
-110
No files found.
.DS_Store
View file @
da7791dc
No preview for this file type
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/resources/mapper/chat/AiChatAnalysisMapper.xml
View file @
da7791dc
...
...
@@ -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
yudao-ui-admin-vue3/.DS_Store
View file @
da7791dc
No preview for this file type
yudao-ui-admin-vue3/src/views/ai/chat_analysis/analysis/index.vue
View file @
da7791dc
...
...
@@ -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
||
10
0
)
>=
0
?
'↑'
:
'↓'
}}
{{
Math
.
abs
(
statsData
?.
percentQAPrevious
||
10
0
).
toFixed
(
2
)
}}
%
</span>
</div>
<div
class=
"compare-text"
>
较前一
{{
config
.
compareUnit
}}
</div>
...
...
yudao-ui-admin-vue3/src/views/ai/chat_analysis/message/components/conversation/ConversationList.vue
View file @
da7791dc
<
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=
"h
eader-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
.
i
d
))
const
uniqueNewList
=
newList
.
filter
(
item
=>
!
existingIds
.
has
(
item
.
i
d
))
const
existingIds
=
new
Set
(
conversationList
.
value
.
map
(
item
=>
item
.
chatI
d
))
const
uniqueNewList
=
newList
.
filter
(
item
=>
!
existingIds
.
has
(
item
.
chatI
d
))
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
:
5
px
;
cursor
:
pointer
;
&.active
{
background-color
:
#e6e6e6
;
.avatar
{
width
:
32
px
;
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment