Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
水玉婷
ai-wechat
Commits
d54fa34e
Commit
d54fa34e
authored
Apr 23, 2026
by
水玉婷
Browse files
feat:修复历史记录滚动加载以及数据错乱bug
parent
8dd97c5a
Changes
1
Hide whitespace changes
Inline
Side-by-side
src/views/History.vue
View file @
d54fa34e
...
...
@@ -34,7 +34,6 @@
v-model=
"searchKeyword"
type=
"text"
placeholder=
"搜索历史记录"
@
input=
"filterHistory"
/>
</div>
</div>
...
...
@@ -42,29 +41,7 @@
<div
class=
"history-list"
@
scroll=
"handleScroll"
@
touchstart=
"handleTouchStart"
@
touchmove=
"handleTouchMove"
@
touchend=
"handleTouchEnd"
@
mousedown=
"handleMouseDown"
@
mousemove=
"handleMouseMove"
@
mouseup=
"handleMouseUp"
@
mouseleave=
"handleMouseLeave"
>
<!-- 下拉刷新指示器 -->
<div
v-if=
"isPullingDown || isRefreshing"
class=
"refresh-indicator"
>
<div
class=
"refresh-spinner"
:class=
"
{ 'refreshing': isRefreshing }"
>
</div>
<span
v-if=
"!isRefreshing"
>
下拉刷新
</span>
<span
v-else
>
刷新中...
</span>
</div>
<!-- 历史记录列表 -->
<div
v-for=
"session in recentSessions"
...
...
@@ -80,24 +57,19 @@
</div>
</div>
<!-- 上拉加载更多提示 -->
<div
v-if=
"hasMore && !isLoading && recentSessions.length > 0"
class=
"pull-up-hint"
>
<span>
上拉加载更多历史记录
</span>
</div>
<!-- 加载更多指示器 -->
<div
v-if=
"isLoading && !isRefreshing"
class=
"load-more-indicator"
>
<div
class=
"loading-spinner"
></div>
<span>
加载中...
</span>
</div>
<!-- 没有更多数据提示 -->
<div
v-if=
"!hasMore && recentSessions.length > 0"
class=
"no-more-data"
>
<span>
没有更多数据了
</span>
</div>
<!-- 加载更多指示器 -->
<div
v-if=
"isLoading"
class=
"load-more-indicator"
>
<div
class=
"loading-spinner"
></div>
<span>
加载中...
</span>
</div>
<!-- 没有更多数据提示 -->
<div
v-if=
"!hasMore && recentSessions.length > 0"
class=
"no-more-data"
>
<span>
没有更多数据了
</span>
</div>
<!-- 空状态 -->
<div
v-if=
"recentSessions.length === 0 && !isLoading
"
class=
"empty-state"
>
<div
v-if=
"recentSessions.length === 0 && !isLoading"
class=
"empty-state"
>
<file-search-outlined
class=
"empty-icon"
/>
<p>
暂无历史记录
</p>
</div>
...
...
@@ -155,12 +127,7 @@ const currentPage = ref(1);
const
pageSize
=
ref
(
20
);
const
hasMore
=
ref
(
true
);
const
isLoading
=
ref
(
false
);
const
isRefreshing
=
ref
(
false
);
// 下拉刷新相关数据
const
pullDownStartY
=
ref
(
0
);
const
isPullingDown
=
ref
(
false
);
const
isMouseDown
=
ref
(
false
);
const
isInitialized
=
ref
(
false
);
// 防止重复加载
// API配置
...
...
@@ -225,22 +192,29 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
// 获取对话记录(支持分页)
const
getChatRecordList
=
async
(
isLoadMore
=
false
)
=>
{
// 检查是否还有更多数据,如果没有则直接返回
if
(
isLoadMore
&&
!
hasMore
.
value
)
{
return
;
}
if
(
isLoading
.
value
)
return
;
isLoading
.
value
=
true
;
if
(
!
isLoadMore
)
{
//
下拉刷新
:重置分页
,但不清空数据
//
首次加载
:重置分页
currentPage
.
value
=
1
;
isRefreshing
.
value
=
true
;
hasMore
.
value
=
true
;
}
try
{
// 添加延时效果,让加载过程更平滑
await
delay
(
500
);
// 加载更多时,应该请求下一页数据,而不是当前页
const
requestPageNum
=
isLoadMore
?
currentPage
.
value
+
1
:
currentPage
.
value
;
let
res
=
await
axios
.
post
(
`
${
apiBaseUrl
}
/aiService/ask/history`
,
{
pageNum
:
currentPage
.
value
,
pageNum
:
requestPageNum
,
pageSize
:
pageSize
.
value
,
queryObject
:
{
appId
:
chatParams
.
appId
,
...
...
@@ -250,124 +224,69 @@ const getChatRecordList = async (isLoadMore = false) => {
if
(
res
.
data
.
code
===
0
)
{
let
{
data
=
[],
total
=
0
}
=
res
.
data
.
data
||
[];
// 反转数据,最新的在前面
const
newData
=
(
data
||
[]).
map
((
item
:
any
)
=>
({
...
item
,
isEdit
:
false
,
}));
if
(
isLoadMore
)
{
//
上拉
加载更多:追加数据
// 加载更多:追加数据
historyList
.
value
=
[...
historyList
.
value
,
...
newData
];
// 加载更多成功后增加页码
currentPage
.
value
++
;
}
else
{
//
下拉刷新
:替换数据
//
首次加载
:替换数据
historyList
.
value
=
newData
;
}
totalCount
.
value
=
total
;
// 判断是否还有更多数据
hasMore
.
value
=
historyList
.
value
.
length
<
total
;
// 计算当前已加载的数据总量
const
loadedCount
=
historyList
.
value
.
length
;
// 1. 已加载数量必须小于总数量
// 2. 当前页返回的数据量等于pageSize说明可能还有更多数据
const
currentPageDataCount
=
newData
.
length
;
hasMore
.
value
=
loadedCount
<
total
&&
currentPageDataCount
===
pageSize
.
value
;
// 首次加载完成后设置初始化标志
if
(
!
isLoadMore
)
{
isInitialized
.
value
=
true
;
// 首次加载完成后检查是否需要自动加载更多
setTimeout
(()
=>
{
checkAutoLoadMore
();
},
100
);
}
// 加载更多完成后也检查是否需要继续自动加载
if
(
isLoadMore
)
{
currentPage
.
value
++
;
setTimeout
(()
=>
{
checkAutoLoadMore
();
},
100
);
}
}
}
catch
(
error
)
{
console
.
error
(
'
获取历史记录失败:
'
,
error
);
}
finally
{
isLoading
.
value
=
false
;
isRefreshing
.
value
=
false
;
}
};
// 通用的开始事件处理
const
handleStart
=
(
clientY
:
number
,
target
:
HTMLElement
,
event
:
Event
)
=>
{
// 检查是否点击了历史记录项(阻止下拉刷新)
const
historyItem
=
(
event
.
target
as
HTMLElement
).
closest
(
'
.history-item
'
);
if
(
historyItem
)
{
// 点击了历史记录项,不触发下拉刷新
return
;
}
// 检查是否需要自动加载更多数据
const
checkAutoLoadMore
=
()
=>
{
if
(
!
isInitialized
.
value
||
isLoading
.
value
||
!
hasMore
.
value
)
return
;
// 只有在顶部、没有在滚动、且列表可以滚动时才允许下拉刷新
const
canScroll
=
target
.
scrollHeight
>
target
.
clientHeight
;
if
(
target
.
scrollTop
===
0
&&
!
isRefreshing
.
value
&&
canScroll
)
{
pullDownStartY
.
value
=
clientY
;
isPullingDown
.
value
=
true
;
}
};
// 下拉刷新触摸事件处理
const
handleTouchStart
=
(
event
:
TouchEvent
)
=>
{
const
target
=
event
.
currentTarget
as
HTMLElement
;
handleStart
(
event
.
touches
[
0
].
clientY
,
target
,
event
);
};
// 通用的移动事件处理
const
handleMove
=
(
clientY
:
number
)
=>
{
const
isActive
=
isPullingDown
.
value
||
isMouseDown
.
value
;
if
(
!
isActive
)
return
;
const
historyListEl
=
document
.
querySelector
(
'
.history-list
'
);
if
(
!
historyListEl
)
return
;
const
pullDownDistance
=
Math
.
max
(
0
,
clientY
-
pullDownStartY
.
value
)
;
const
{
scrollHeight
,
clientHeight
}
=
historyListEl
;
// 移除最大下拉距离限制,让动画更自然
// 使用缓动函数让下拉效果更平滑
if
(
pullDownDistance
>
100
)
{
// 超过100px后增加阻尼效果
// 这里可以添加其他逻辑,但不再存储到响应式变量
}
};
const
handleTouchMove
=
(
event
:
TouchEvent
)
=>
{
handleMove
(
event
.
touches
[
0
].
clientY
);
};
// 通用的结束事件处理
const
handleEnd
=
()
=>
{
// 直接触发刷新,不再检查下拉距离
handleRefresh
();
// 重置状态
isPullingDown
.
value
=
false
;
isMouseDown
.
value
=
false
;
};
const
handleTouchEnd
=
()
=>
{
if
(
!
isPullingDown
.
value
)
return
;
handleEnd
();
};
// PC端鼠标事件处理
const
handleMouseDown
=
(
event
:
MouseEvent
)
=>
{
const
target
=
event
.
currentTarget
as
HTMLElement
;
handleStart
(
event
.
clientY
,
target
,
event
);
isMouseDown
.
value
=
true
;
};
const
handleMouseMove
=
(
event
:
MouseEvent
)
=>
{
handleMove
(
event
.
clientY
);
};
const
handleMouseUp
=
()
=>
{
if
(
!
isMouseDown
.
value
)
return
;
handleEnd
();
};
const
handleMouseLeave
=
()
=>
{
// 鼠标离开时取消下拉状态
if
(
isMouseDown
.
value
)
{
isMouseDown
.
value
=
false
;
isPullingDown
.
value
=
false
;
// 如果内容高度小于等于容器高度,说明屏幕足够高,可以自动加载更多
if
(
scrollHeight
<=
clientHeight
)
{
handleLoadMore
();
}
};
// 下拉刷新
const
handleRefresh
=
()
=>
{
getChatRecordList
(
false
);
};
// 上拉加载更多
const
handleLoadMore
=
()
=>
{
if
(
!
hasMore
.
value
||
isLoading
.
value
)
return
;
...
...
@@ -378,6 +297,16 @@ const handleLoadMore = () => {
const
handleScroll
=
(
event
:
Event
)
=>
{
const
target
=
event
.
target
as
HTMLElement
;
const
{
scrollTop
,
scrollHeight
,
clientHeight
}
=
target
;
// 只有在初始化完成后才处理滚动事件
if
(
!
isInitialized
.
value
)
return
;
// 当内容高度小于容器高度时,直接触发加载更多
if
(
scrollHeight
<=
clientHeight
&&
hasMore
.
value
&&
!
isLoading
.
value
)
{
handleLoadMore
();
return
;
}
// 检查是否滚动到底部(距离底部50px时触发加载)
if
(
scrollHeight
-
scrollTop
-
clientHeight
<
50
&&
hasMore
.
value
&&
!
isLoading
.
value
)
{
handleLoadMore
();
...
...
@@ -402,14 +331,42 @@ const recentSessions = computed(() => {
const
sessions
=
filteredSessions
.
value
;
return
sessions
;
});
// 重置所有状态到初始状态
const
resetAllState
=
()
=>
{
// 重置分页相关状态
currentPage
.
value
=
1
;
hasMore
.
value
=
true
;
isLoading
.
value
=
false
;
isInitialized
.
value
=
false
;
// 重置搜索状态
searchKeyword
.
value
=
''
;
// 重置数据状态
historyList
.
value
=
[];
totalCount
.
value
=
0
;
// 重置当前会话状态
currentSessionId
.
value
=
''
;
currentSessionDetail
.
value
=
{
title
:
''
};
sessionKey
.
value
=
''
;
};
// 方法
const
toggleHistoryPanel
=
()
=>
{
isHistoryPanelOpen
.
value
=
!
isHistoryPanelOpen
.
value
;
const
willOpen
=
!
isHistoryPanelOpen
.
value
;
if
(
willOpen
)
{
// 打开历史记录面板时重置所有状态
resetAllState
();
// 重新加载历史记录
getChatRecordList
();
}
isHistoryPanelOpen
.
value
=
willOpen
;
};
const
filterHistory
=
()
=>
{
// 搜索功能已通过计算属性实现
};
const
selectSession
=
(
session
:
any
)
=>
{
currentSessionId
.
value
=
session
.
id
;
...
...
@@ -643,33 +600,6 @@ onMounted(() => {
flex: 1;
overflow-y: auto;
padding: 8px 0;
max-height: calc(100vh - 184px); // 设置固定高度,确保可以滚动
// 下拉刷新指示器
.refresh-indicator {
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
color: #666;
font-size: 14px;
transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
z-index: 1;
.refresh-spinner {
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #1890ff;
border-radius: 50%;
margin-right: 8px;
transition: transform 0.3s ease;
&.refreshing {
animation: spin 1s linear infinite;
}
}
}
// 加载更多指示器
.load-more-indicator {
...
...
@@ -691,30 +621,6 @@ onMounted(() => {
}
}
// 上拉加载更多提示
.pull-up-hint {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
color: #999;
font-size: 14px;
.pull-up-icon {
font-size: 20px;
margin-bottom: 8px;
animation: bounce 2s infinite;
color: #1890ff;
}
span {
font-size: 12px;
}
}
// 没有更多数据提示
.no-more-data {
text-align: center;
...
...
@@ -826,18 +732,7 @@ onMounted(() => {
}
}
// 弹跳动画
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
60% {
transform: translateY(-3px);
}
}
// 主内容区域
.main-content {
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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