Commit d54fa34e authored by 水玉婷's avatar 水玉婷
Browse files

feat:修复历史记录滚动加载以及数据错乱bug

parent 8dd97c5a
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
v-model="searchKeyword" v-model="searchKeyword"
type="text" type="text"
placeholder="搜索历史记录" placeholder="搜索历史记录"
@input="filterHistory"
/> />
</div> </div>
</div> </div>
...@@ -42,29 +41,7 @@ ...@@ -42,29 +41,7 @@
<div <div
class="history-list" class="history-list"
@scroll="handleScroll" @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"
...@@ -80,24 +57,19 @@ ...@@ -80,24 +57,19 @@
</div> </div>
</div> </div>
<!-- 上拉加载更多提示 --> <!-- 加载更多指示器 -->
<div v-if="hasMore && !isLoading && recentSessions.length > 0" class="pull-up-hint"> <div v-if="isLoading" class="load-more-indicator">
<span>上拉加载更多历史记录</span> <div class="loading-spinner"></div>
</div> <span>加载中...</span>
</div>
<!-- 加载更多指示器 -->
<div v-if="isLoading && !isRefreshing" class="load-more-indicator"> <!-- 没有更多数据提示 -->
<div class="loading-spinner"></div> <div v-if="!hasMore && recentSessions.length > 0" class="no-more-data">
<span>加载中...</span> <span>没有更多数据了</span>
</div> </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" /> <file-search-outlined class="empty-icon" />
<p>暂无历史记录</p> <p>暂无历史记录</p>
</div> </div>
...@@ -155,12 +127,7 @@ const currentPage = ref(1); ...@@ -155,12 +127,7 @@ const currentPage = ref(1);
const pageSize = ref(20); const pageSize = ref(20);
const hasMore = ref(true); const hasMore = ref(true);
const isLoading = ref(false); const isLoading = ref(false);
const isRefreshing = ref(false); const isInitialized = ref(false); // 防止重复加载
// 下拉刷新相关数据
const pullDownStartY = ref(0);
const isPullingDown = ref(false);
const isMouseDown = ref(false);
// API配置 // API配置
...@@ -225,22 +192,29 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); ...@@ -225,22 +192,29 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
// 获取对话记录(支持分页) // 获取对话记录(支持分页)
const getChatRecordList = async (isLoadMore = false) => { const getChatRecordList = async (isLoadMore = false) => {
// 检查是否还有更多数据,如果没有则直接返回
if (isLoadMore && !hasMore.value) {
return;
}
if (isLoading.value) return; if (isLoading.value) return;
isLoading.value = true; isLoading.value = true;
if (!isLoadMore) { if (!isLoadMore) {
// 下拉刷新:重置分页,但不清空数据 // 首次加载:重置分页
currentPage.value = 1; currentPage.value = 1;
isRefreshing.value = true; hasMore.value = true;
} }
try { try {
// 添加延时效果,让加载过程更平滑 // 添加延时效果,让加载过程更平滑
await delay(500); await delay(500);
// 加载更多时,应该请求下一页数据,而不是当前页
const requestPageNum = isLoadMore ? currentPage.value + 1 : currentPage.value;
let res = await axios.post(`${apiBaseUrl}/aiService/ask/history`, { let res = await axios.post(`${apiBaseUrl}/aiService/ask/history`, {
pageNum: currentPage.value, pageNum: requestPageNum,
pageSize: pageSize.value, pageSize: pageSize.value,
queryObject: { queryObject: {
appId: chatParams.appId, appId: chatParams.appId,
...@@ -250,124 +224,69 @@ const getChatRecordList = async (isLoadMore = false) => { ...@@ -250,124 +224,69 @@ const getChatRecordList = async (isLoadMore = false) => {
if (res.data.code === 0) { if (res.data.code === 0) {
let { data = [], total = 0 } = res.data.data || []; let { data = [], total = 0 } = res.data.data || [];
// 反转数据,最新的在前面
const newData = (data || []).map((item: any) => ({ const newData = (data || []).map((item: any) => ({
...item, ...item,
isEdit: false, isEdit: false,
})); }));
if (isLoadMore) { if (isLoadMore) {
// 上拉加载更多:追加数据 // 加载更多:追加数据
historyList.value = [...historyList.value, ...newData]; historyList.value = [...historyList.value, ...newData];
// 加载更多成功后增加页码
currentPage.value++;
} else { } else {
// 下拉刷新:替换数据 // 首次加载:替换数据
historyList.value = newData; historyList.value = newData;
} }
totalCount.value = total; 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) { if (isLoadMore) {
currentPage.value++; setTimeout(() => {
checkAutoLoadMore();
}, 100);
} }
} }
} catch (error) { } catch (error) {
console.error('获取历史记录失败:', error); console.error('获取历史记录失败:', error);
} finally { } finally {
isLoading.value = false; isLoading.value = false;
isRefreshing.value = false;
} }
}; };
// 通用的开始事件处理 // 检查是否需要自动加载更多数据
const handleStart = (clientY: number, target: HTMLElement, event: Event) => { const checkAutoLoadMore = () => {
// 检查是否点击了历史记录项(阻止下拉刷新) if (!isInitialized.value || isLoading.value || !hasMore.value) return;
const historyItem = (event.target as HTMLElement).closest('.history-item');
if (historyItem) {
// 点击了历史记录项,不触发下拉刷新
return;
}
// 只有在顶部、没有在滚动、且列表可以滚动时才允许下拉刷新 const historyListEl = document.querySelector('.history-list');
const canScroll = target.scrollHeight > target.clientHeight; if (!historyListEl) return;
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 pullDownDistance = Math.max(0, clientY - pullDownStartY.value); const { scrollHeight, clientHeight } = historyListEl;
// 移除最大下拉距离限制,让动画更自然 // 如果内容高度小于等于容器高度,说明屏幕足够高,可以自动加载更多
// 使用缓动函数让下拉效果更平滑 if (scrollHeight <= clientHeight) {
if (pullDownDistance > 100) { handleLoadMore();
// 超过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;
} }
}; };
// 下拉刷新
const handleRefresh = () => {
getChatRecordList(false);
};
// 上拉加载更多 // 上拉加载更多
const handleLoadMore = () => { const handleLoadMore = () => {
if (!hasMore.value || isLoading.value) return; if (!hasMore.value || isLoading.value) return;
...@@ -378,6 +297,16 @@ const handleLoadMore = () => { ...@@ -378,6 +297,16 @@ const handleLoadMore = () => {
const handleScroll = (event: Event) => { const handleScroll = (event: Event) => {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
const { scrollTop, scrollHeight, clientHeight } = target; const { scrollTop, scrollHeight, clientHeight } = target;
// 只有在初始化完成后才处理滚动事件
if (!isInitialized.value) return;
// 当内容高度小于容器高度时,直接触发加载更多
if (scrollHeight <= clientHeight && hasMore.value && !isLoading.value) {
handleLoadMore();
return;
}
// 检查是否滚动到底部(距离底部50px时触发加载) // 检查是否滚动到底部(距离底部50px时触发加载)
if (scrollHeight - scrollTop - clientHeight < 50 && hasMore.value && !isLoading.value) { if (scrollHeight - scrollTop - clientHeight < 50 && hasMore.value && !isLoading.value) {
handleLoadMore(); handleLoadMore();
...@@ -402,14 +331,42 @@ const recentSessions = computed(() => { ...@@ -402,14 +331,42 @@ const recentSessions = computed(() => {
const sessions = filteredSessions.value; const sessions = filteredSessions.value;
return sessions; 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 = () => { const toggleHistoryPanel = () => {
isHistoryPanelOpen.value = !isHistoryPanelOpen.value; const willOpen = !isHistoryPanelOpen.value;
if (willOpen) {
// 打开历史记录面板时重置所有状态
resetAllState();
// 重新加载历史记录
getChatRecordList();
}
isHistoryPanelOpen.value = willOpen;
}; };
const filterHistory = () => {
// 搜索功能已通过计算属性实现
};
const selectSession = (session: any) => { const selectSession = (session: any) => {
currentSessionId.value = session.id; currentSessionId.value = session.id;
...@@ -643,33 +600,6 @@ onMounted(() => { ...@@ -643,33 +600,6 @@ 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;
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 { .load-more-indicator {
...@@ -691,30 +621,6 @@ onMounted(() => { ...@@ -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 { .no-more-data {
text-align: center; text-align: center;
...@@ -826,18 +732,7 @@ onMounted(() => { ...@@ -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 { .main-content {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment