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
45a26348
Commit
45a26348
authored
Mar 10, 2026
by
水玉婷
Browse files
feat:历史记录添加上拉刷新下拉加载
parent
00898816
Changes
1
Hide whitespace changes
Inline
Side-by-side
src/views/History.vue
View file @
45a26348
...
@@ -36,7 +36,33 @@
...
@@ -36,7 +36,33 @@
</div>
</div>
</div>
</div>
<div
class=
"history-list"
>
<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
<div
v-for=
"session in recentSessions"
v-for=
"session in recentSessions"
:key=
"session.id"
:key=
"session.id"
...
@@ -51,7 +77,25 @@
...
@@ -51,7 +77,25 @@
</button>
</button>
</div>
</div>
<div
v-if=
"recentSessions.length === 0"
class=
"empty-state"
>
<!-- 上拉加载更多提示 -->
<div
v-if=
"hasMore && !isLoading && recentSessions.length > 0"
class=
"pull-up-hint"
>
<div
class=
"pull-up-icon"
>
↑
</div>
<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=
"recentSessions.length === 0 && !isLoading "
class=
"empty-state"
>
<file-search-outlined
class=
"empty-icon"
/>
<file-search-outlined
class=
"empty-icon"
/>
<p>
暂无历史记录
</p>
<p>
暂无历史记录
</p>
</div>
</div>
...
@@ -79,7 +123,7 @@
...
@@ -79,7 +123,7 @@
:token=
"userToken"
:token=
"userToken"
:appCode=
"appCode"
:appCode=
"appCode"
customClass=
"chat-history"
customClass=
"chat-history"
:key=
"currentSessionId
+new Date().getTime()
"
:key=
"currentSessionId"
/>
/>
</div>
</div>
</div>
</div>
...
@@ -105,6 +149,19 @@ const searchKeyword = ref('');
...
@@ -105,6 +149,19 @@ const searchKeyword = ref('');
const
currentSessionId
=
ref
(
''
);
const
currentSessionId
=
ref
(
''
);
const
currentSessionDetail
=
ref
({
title
:
''
});
const
currentSessionDetail
=
ref
({
title
:
''
});
// 分页相关数据
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
pullDownDistance
=
ref
(
0
);
const
isPullingDown
=
ref
(
false
);
const
isMouseDown
=
ref
(
false
);
// API配置
// API配置
const
getApiBaseUrl
=
()
=>
{
const
getApiBaseUrl
=
()
=>
{
...
@@ -133,28 +190,166 @@ interface Session {
...
@@ -133,28 +190,166 @@ interface Session {
}
}
const
historyList
=
ref
<
Session
[]
>
([]);
const
historyList
=
ref
<
Session
[]
>
([]);
// 获取对话记录
const
getChatRecordList
=
async
()
=>
{
// 延时函数
const
delay
=
(
ms
:
number
)
=>
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
ms
));
// 获取对话记录(支持分页)
const
getChatRecordList
=
async
(
isLoadMore
=
false
)
=>
{
if
(
isLoading
.
value
)
return
;
isLoading
.
value
=
true
;
if
(
!
isLoadMore
)
{
// 下拉刷新:重置分页,但不清空数据
currentPage
.
value
=
1
;
isRefreshing
.
value
=
true
;
}
try
{
// 添加延时效果,让加载过程更平滑
await
delay
(
500
);
let
res
=
await
axios
.
post
(
`
${
apiBaseUrl
}
/aiService/ask/history`
,
{
let
res
=
await
axios
.
post
(
`
${
apiBaseUrl
}
/aiService/ask/history`
,
{
pageNum
:
1
,
pageNum
:
currentPage
.
value
,
pageSize
:
20
,
pageSize
:
pageSize
.
value
,
queryObject
:
{
queryObject
:
{
appId
:
chatParams
.
appId
,
appId
:
chatParams
.
appId
,
offsetDay
:
365
,
offsetDay
:
365
,
},
},
});
});
if
(
res
.
data
.
code
===
0
)
{
if
(
res
.
data
.
code
===
0
)
{
let
{
data
=
[],
total
=
0
,
totalInfo
=
{}
}
=
res
.
data
.
data
||
[];
let
{
data
=
[],
total
=
0
,
totalInfo
=
{}
}
=
res
.
data
.
data
||
[];
historyList
.
value
=
(
data
.
reverse
()
||
[]).
map
((
item
)
=>
({
// 反转数据,最新的在前面
const
newData
=
(
data
.
reverse
()
||
[]).
map
((
item
:
any
)
=>
({
...
item
,
...
item
,
isEdit
:
false
,
isEdit
:
false
,
}));
}));
totalCount
.
value
=
total
;
appName
.
value
=
totalInfo
?.
app_name
||
''
;
if
(
isLoadMore
)
{
// 上拉加载更多:追加数据
historyList
.
value
=
[...
historyList
.
value
,
...
newData
];
}
else
{
// 下拉刷新:替换数据
historyList
.
value
=
newData
;
}
totalCount
.
value
=
total
;
appName
.
value
=
totalInfo
?.
app_name
||
''
;
// 判断是否还有更多数据
hasMore
.
value
=
historyList
.
value
.
length
<
total
;
if
(
isLoadMore
)
{
currentPage
.
value
++
;
}
}
}
}
catch
(
error
)
{
console
.
error
(
'
获取历史记录失败:
'
,
error
);
}
finally
{
isLoading
.
value
=
false
;
isRefreshing
.
value
=
false
;
}
};
// 通用的开始事件处理
const
handleStart
=
(
clientY
:
number
,
target
:
HTMLElement
)
=>
{
// 只有在顶部且没有在滚动时才允许下拉刷新
if
(
target
.
scrollTop
===
0
&&
!
isRefreshing
.
value
)
{
pullDownStartY
.
value
=
clientY
;
isPullingDown
.
value
=
true
;
}
};
// 下拉刷新触摸事件处理
const
handleTouchStart
=
(
event
:
TouchEvent
)
=>
{
const
target
=
event
.
currentTarget
as
HTMLElement
;
handleStart
(
event
.
touches
[
0
].
clientY
,
target
);
};
// 通用的移动事件处理
const
handleMove
=
(
clientY
:
number
)
=>
{
const
isActive
=
isPullingDown
.
value
||
isMouseDown
.
value
;
if
(
!
isActive
)
return
;
const
pullDownDistance
=
Math
.
max
(
0
,
clientY
-
pullDownStartY
.
value
);
// 移除最大下拉距离限制,让动画更自然
// 使用缓动函数让下拉效果更平滑
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
);
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
;
}
};
// 下拉刷新
const
handleRefresh
=
()
=>
{
getChatRecordList
(
false
);
};
};
// 上拉加载更多
const
handleLoadMore
=
()
=>
{
if
(
!
hasMore
.
value
||
isLoading
.
value
)
return
;
getChatRecordList
(
true
);
};
// 滚动事件处理
const
handleScroll
=
(
event
:
Event
)
=>
{
const
target
=
event
.
target
as
HTMLElement
;
const
{
scrollTop
,
scrollHeight
,
clientHeight
}
=
target
;
// 检查是否滚动到底部(距离底部50px时触发加载)
if
(
scrollHeight
-
scrollTop
-
clientHeight
<
50
&&
hasMore
.
value
&&
!
isLoading
.
value
)
{
handleLoadMore
();
}
};
// 显示限制
// 显示限制
const
displayLimit
=
20
;
const
displayLimit
=
computed
(()
=>
historyList
.
value
.
length
)
;
// 计算属性:过滤后的会话列表
// 计算属性:过滤后的会话列表
const
filteredSessions
=
computed
(()
=>
{
const
filteredSessions
=
computed
(()
=>
{
...
@@ -169,7 +364,7 @@ const filteredSessions = computed(() => {
...
@@ -169,7 +364,7 @@ const filteredSessions = computed(() => {
// 计算属性:最近显示的会话列表
// 计算属性:最近显示的会话列表
const
recentSessions
=
computed
(()
=>
{
const
recentSessions
=
computed
(()
=>
{
const
sessions
=
filteredSessions
.
value
;
const
sessions
=
filteredSessions
.
value
;
return
sessions
.
slice
(
0
,
displayLimit
)
;
return
sessions
;
});
});
// 方法
// 方法
const
toggleHistoryPanel
=
()
=>
{
const
toggleHistoryPanel
=
()
=>
{
...
@@ -411,6 +606,88 @@ onMounted(() => {
...
@@ -411,6 +606,88 @@ onMounted(() => {
flex: 1;
flex: 1;
overflow-y: auto;
overflow-y: auto;
padding: 8px 0;
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;
background: #ffffff;
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 {
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
color: #666;
font-size: 14px;
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 8px;
}
}
// 上拉加载更多提示
.pull-up-hint {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
color: #999;
font-size: 14px;
background: #fafafa;
border-top: 1px solid #f0f0f0;
.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;
padding: 16px;
color: #999;
font-size: 14px;
}
.history-item {
.history-item {
display: flex;
display: flex;
...
@@ -516,6 +793,19 @@ onMounted(() => {
...
@@ -516,6 +793,19 @@ onMounted(() => {
}
}
}
}
// 弹跳动画
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
60% {
transform: translateY(-3px);
}
}
// 主内容区域
// 主内容区域
.main-content {
.main-content {
flex: 1;
flex: 1;
...
@@ -523,6 +813,12 @@ onMounted(() => {
...
@@ -523,6 +813,12 @@ onMounted(() => {
flex-direction: column;
flex-direction: column;
}
}
// 旋转动画
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
// 响应式设计
// 响应式设计
@media (max-width: 768px) {
@media (max-width: 768px) {
.history-sidebar {
.history-sidebar {
...
...
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