刷新自动回到底部
This commit is contained in:
@@ -50,11 +50,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
getHistory
|
getHistory
|
||||||
} from '../../api/aiChat/ai_index.js';
|
} from '../../api/aiChat/ai_index.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HistoryDrawer',
|
name: 'HistoryDrawer',
|
||||||
props: {
|
props: {
|
||||||
visible: Boolean
|
visible: Boolean
|
||||||
@@ -215,32 +215,32 @@ export default {
|
|||||||
this.closeDrawer();
|
this.closeDrawer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 抽屉容器样式 */
|
/* 抽屉容器样式 */
|
||||||
.drawer-container {
|
.drawer-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 遮罩层样式 */
|
/* 遮罩层样式 */
|
||||||
.drawer-mask {
|
.drawer-mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 抽屉内容区域样式 */
|
/* 抽屉内容区域样式 */
|
||||||
.drawer-content {
|
.drawer-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -250,10 +250,10 @@ export default {
|
|||||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 头部样式 */
|
/* 头部样式 */
|
||||||
.drawer-header {
|
.drawer-header {
|
||||||
padding: 15px 15px 10px;
|
padding: 15px 15px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -261,139 +261,139 @@ export default {
|
|||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-icon {
|
.close-icon {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 搜索栏样式 */
|
/* 搜索栏样式 */
|
||||||
.search-bar {
|
.search-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-icon {
|
.search-icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
tint-color: #999;
|
tint-color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-icon {
|
.clear-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
tint-color: #999;
|
tint-color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 历史列表样式 */
|
/* 历史列表样式 */
|
||||||
.history-list {
|
.history-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分组标题样式 */
|
/* 分组标题样式 */
|
||||||
.group-title {
|
.group-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #888;
|
color: #888;
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 历史记录项样式 */
|
/* 历史记录项样式 */
|
||||||
.history-item {
|
.history-item {
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 日期时间样式 */
|
/* 日期时间样式 */
|
||||||
.datetime {
|
.datetime {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 消息内容样式 */
|
/* 消息内容样式 */
|
||||||
.record {
|
.record {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-msg {
|
.user-msg {
|
||||||
display: block;
|
display: block;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-msg {
|
.ai-msg {
|
||||||
display: block;
|
display: block;
|
||||||
color: #333;
|
color: #333;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background-color: #eef7ff;
|
background-color: #eef7ff;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 关键词高亮样式 */
|
/* 关键词高亮样式 */
|
||||||
.highlight {
|
.highlight {
|
||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分隔线样式 */
|
/* 分隔线样式 */
|
||||||
.divider {
|
.divider {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态提示样式 */
|
/* 空状态提示样式 */
|
||||||
.empty-tip {
|
.empty-tip {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修复输入框占位符样式 */
|
/* 修复输入框占位符样式 */
|
||||||
input::-webkit-input-placeholder {
|
input::-webkit-input-placeholder {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -101,30 +101,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* ========== 依赖 ========== */
|
/* ========== 依赖 ========== */
|
||||||
import HistoryDrawer from '@/components/aiChat/HistoryDrawer.vue';
|
import HistoryDrawer from '@/components/aiChat/HistoryDrawer.vue';
|
||||||
import {
|
import {
|
||||||
createChatStream
|
createChatStream
|
||||||
} from '@/utils/ai_stream.js';
|
} from '@/utils/ai_stream.js';
|
||||||
import {
|
import {
|
||||||
sendFeedback,
|
sendFeedback,
|
||||||
getHistory
|
getHistory
|
||||||
} from '@/api/aiChat/ai_index.js';
|
} from '@/api/aiChat/ai_index.js';
|
||||||
import MarkdownIt from 'markdown-it';
|
import MarkdownIt from 'markdown-it';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
/* DOMPurify 白名单加固 */
|
/* DOMPurify 白名单加固 */
|
||||||
DOMPurify.addHook('afterSanitizeAttributes', node => {
|
DOMPurify.addHook('afterSanitizeAttributes', node => {
|
||||||
if (node.tagName === 'A') node.setAttribute('rel', 'noopener noreferrer');
|
if (node.tagName === 'A') node.setAttribute('rel', 'noopener noreferrer');
|
||||||
});
|
});
|
||||||
|
|
||||||
const md = new MarkdownIt({
|
const md = new MarkdownIt({
|
||||||
html: true,
|
html: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true
|
typographer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
HistoryDrawer
|
HistoryDrawer
|
||||||
},
|
},
|
||||||
@@ -162,38 +162,52 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从本地存储获取conversation_id
|
|
||||||
this.conversation_id = uni.getStorageSync('conversation_id') || null;
|
this.conversation_id = uni.getStorageSync('conversation_id') || null;
|
||||||
|
|
||||||
// 初始化聊天并确保滚动到底部
|
// 先初始化消息,再滚动到底部
|
||||||
this.initChat().then(() => {
|
this.initChat().then(() => {
|
||||||
// 多重延迟确保DOM完全渲染
|
// 使用三重确保策略处理不同设备的渲染差异
|
||||||
|
const scrollWithRetry = (attempt = 0) => {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.forceScrollToBottom();
|
this.forceScrollToBottom();
|
||||||
// 再次确保滚动(处理某些设备的渲染延迟)
|
|
||||||
setTimeout(() => {
|
|
||||||
this.forceScrollToBottom();
|
|
||||||
}, 500);
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
// 第二次尝试(处理iOS等需要额外触发的设备)
|
||||||
* 页面显示时触发 - 确保每次进入页面都滚动到底部
|
|
||||||
*/
|
|
||||||
onShow() {
|
|
||||||
// 确保页面显示时滚动到底部
|
|
||||||
this.$nextTick(() => {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.forceScrollToBottom();
|
this.forceScrollToBottom(30); // 使用稍小的偏移量
|
||||||
// 额外延迟处理某些设备的渲染问题
|
|
||||||
|
// 第三次确保(针对动态内容加载的情况)
|
||||||
|
if (attempt < 2) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.forceScrollToBottom();
|
const query = uni.createSelectorQuery()
|
||||||
|
.in(this);
|
||||||
|
query.select('.message-list')
|
||||||
|
.boundingClientRect(rect => {
|
||||||
|
if (rect && Math.abs(rect
|
||||||
|
.scrollHeight -
|
||||||
|
rect.height - this
|
||||||
|
.scrollTop) > 50) {
|
||||||
|
scrollWithRetry(
|
||||||
|
attempt + 1
|
||||||
|
); // 递归调用直到成功
|
||||||
|
}
|
||||||
|
}).exec();
|
||||||
|
}, attempt === 0 ? 500 : 800);
|
||||||
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始触发
|
||||||
|
scrollWithRetry();
|
||||||
|
}).catch(e => {
|
||||||
|
console.error('初始化失败:', e);
|
||||||
|
// 失败时仍尝试滚动
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => this.forceScrollToBottom(), 300);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ---------- 方法 ---------- */
|
/* ---------- 方法 ---------- */
|
||||||
@@ -208,15 +222,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 强制滚动到底部 - 优化版本
|
* 强制滚动到底部
|
||||||
|
* @param {number} offset - 额外的偏移量,默认为50
|
||||||
*/
|
*/
|
||||||
forceScrollToBottom() {
|
forceScrollToBottom(offset = 50) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
try {
|
||||||
const query = uni.createSelectorQuery().in(this);
|
const query = uni.createSelectorQuery().in(this);
|
||||||
query.select('.message-list').boundingClientRect(rect => {
|
query.select('.message-list').boundingClientRect(rect => {
|
||||||
if (rect && rect.scrollHeight > rect.height) {
|
if (rect) {
|
||||||
// 计算需要滚动的距离
|
const targetScrollTop = rect.scrollHeight - rect.height - offset;
|
||||||
const targetScrollTop = rect.scrollHeight - rect.height + 50; // 额外50px确保完全显示
|
|
||||||
this.scrollTop = targetScrollTop;
|
this.scrollTop = targetScrollTop;
|
||||||
|
|
||||||
// 双重确保滚动生效
|
// 双重确保滚动生效
|
||||||
@@ -228,23 +243,25 @@ export default {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}).exec();
|
}).exec();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('滚动到底部失败:', error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
forceScrollToTop() {
|
// forceScrollToTop() {
|
||||||
this.$nextTick(() => {
|
// this.$nextTick(() => {
|
||||||
// 直接滚动到顶部
|
// this.scrollTop = 0;
|
||||||
this.scrollTop = 999;
|
|
||||||
|
|
||||||
// 双重确保滚动生效
|
// // 双重确保滚动生效
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
this.scrollTop = 1;
|
// this.scrollTop = 1;
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
this.scrollTop = 0;
|
// this.scrollTop = 0;
|
||||||
}, 50);
|
// }, 50);
|
||||||
}, 100);
|
// }, 100);
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换历史记录抽屉显示状态
|
* 切换历史记录抽屉显示状态
|
||||||
@@ -866,10 +883,10 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 引入全局样式 */
|
/* 引入全局样式 */
|
||||||
@import '@/static/scss/ai_index.css';
|
@import '@/static/scss/ai_index.css';
|
||||||
</style>
|
</style>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 518 KiB After Width: | Height: | Size: 4.3 KiB |
Reference in New Issue
Block a user