mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-04-22 09:26:36 +08:00
fix: 对话底部加载动画位置上移
This commit is contained in:
@@ -2,20 +2,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AnyObject } from 'typescript-api-pro';
|
import type { AnyObject } from 'typescript-api-pro';
|
||||||
import type { BubbleListInstance } from 'vue-element-plus-x/types/BubbleList';
|
import type { BubbleListInstance } from 'vue-element-plus-x/types/BubbleList';
|
||||||
import { Loading, Bottom } from '@element-plus/icons-vue';
|
import type { MessageItem as MessageItemType } from '@/composables/chat';
|
||||||
|
import type { UnifiedMessage } from '@/utils/apiFormatConverter';
|
||||||
|
import { Bottom, Loading } from '@element-plus/icons-vue';
|
||||||
import { ElIcon, ElMessage } from 'element-plus';
|
import { ElIcon, ElMessage } from 'element-plus';
|
||||||
import { useHookFetch } from 'hook-fetch/vue';
|
import { useHookFetch } from 'hook-fetch/vue';
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { Sender } from 'vue-element-plus-x';
|
import { Sender } from 'vue-element-plus-x';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { deleteMessages, unifiedSend } from '@/api';
|
import { deleteMessages, unifiedSend } from '@/api';
|
||||||
|
import { useFilePaste } from '@/composables/chat';
|
||||||
import { ChatHeader, DeleteModeToolbar, MessageItem } from '@/pages/chat/components';
|
import { ChatHeader, DeleteModeToolbar, MessageItem } from '@/pages/chat/components';
|
||||||
import { useChatStore, useUserStore } from '@/stores';
|
import { useChatStore, useUserStore } from '@/stores';
|
||||||
import { useFilesStore } from '@/stores/modules/files';
|
import { useFilesStore } from '@/stores/modules/files';
|
||||||
import { useModelStore } from '@/stores/modules/model';
|
import { useModelStore } from '@/stores/modules/model';
|
||||||
import { useSessionStore } from '@/stores/modules/session';
|
import { useSessionStore } from '@/stores/modules/session';
|
||||||
import { convertToApiFormat, parseStreamChunk, type UnifiedMessage } from '@/utils/apiFormatConverter';
|
import { convertToApiFormat, parseStreamChunk } from '@/utils/apiFormatConverter';
|
||||||
import { useFilePaste, type MessageItem as MessageItemType } from '@/composables/chat';
|
|
||||||
import '@/styles/github-markdown.css';
|
import '@/styles/github-markdown.css';
|
||||||
import '@/styles/yixin-markdown.scss';
|
import '@/styles/yixin-markdown.scss';
|
||||||
|
|
||||||
@@ -77,7 +79,7 @@ const { handlePaste } = useFilePaste({
|
|||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
},
|
},
|
||||||
addFiles: (files) => filesStore.setFilesList([...filesStore.filesList, ...files]),
|
addFiles: files => filesStore.setFilesList([...filesStore.filesList, ...files]),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建统一发送请求的包装函数
|
// 创建统一发送请求的包装函数
|
||||||
@@ -162,14 +164,15 @@ function checkIfAtBottom() {
|
|||||||
* 智能滚动到底部(仅在用户已在底部时滚动)
|
* 智能滚动到底部(仅在用户已在底部时滚动)
|
||||||
*/
|
*/
|
||||||
function smartScrollToBottom(force = false) {
|
function smartScrollToBottom(force = false) {
|
||||||
if (!scrollContainer.value) return;
|
if (!scrollContainer.value)
|
||||||
|
return;
|
||||||
|
|
||||||
// 强制滚动或用户已在底部时才滚动
|
// 强制滚动或用户已在底部时才滚动
|
||||||
if (force || isUserAtBottom.value) {
|
if (force || isUserAtBottom.value) {
|
||||||
const targetScroll = scrollContainer.value.scrollHeight - scrollContainer.value.clientHeight;
|
const targetScroll = scrollContainer.value.scrollHeight - scrollContainer.value.clientHeight;
|
||||||
scrollContainer.value.scrollTo({
|
scrollContainer.value.scrollTo({
|
||||||
top: targetScroll,
|
top: targetScroll,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
isUserAtBottom.value = true;
|
isUserAtBottom.value = true;
|
||||||
}
|
}
|
||||||
@@ -242,8 +245,10 @@ function handleDataChunk(chunk: AnyObject) {
|
|||||||
const thinkStart = parsed.content.includes('<think>');
|
const thinkStart = parsed.content.includes('<think>');
|
||||||
const thinkEnd = parsed.content.includes('</think>');
|
const thinkEnd = parsed.content.includes('</think>');
|
||||||
|
|
||||||
if (thinkStart) isThinking = true;
|
if (thinkStart)
|
||||||
if (thinkEnd) isThinking = false;
|
isThinking = true;
|
||||||
|
if (thinkEnd)
|
||||||
|
isThinking = false;
|
||||||
|
|
||||||
if (isThinking) {
|
if (isThinking) {
|
||||||
latest.thinkingStatus = 'thinking';
|
latest.thinkingStatus = 'thinking';
|
||||||
@@ -251,7 +256,8 @@ function handleDataChunk(chunk: AnyObject) {
|
|||||||
latest.thinlCollapse = true;
|
latest.thinlCollapse = true;
|
||||||
latest.reasoning_content += parsed.content.replace('<think>', '').replace('</think>', '');
|
latest.reasoning_content += parsed.content.replace('<think>', '').replace('</think>', '');
|
||||||
nextTick(() => smartScrollToBottom());
|
nextTick(() => smartScrollToBottom());
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
latest.thinkingStatus = 'end';
|
latest.thinkingStatus = 'end';
|
||||||
latest.loading = false;
|
latest.loading = false;
|
||||||
latest.content += parsed.content;
|
latest.content += parsed.content;
|
||||||
@@ -259,7 +265,8 @@ function handleDataChunk(chunk: AnyObject) {
|
|||||||
nextTick(() => smartScrollToBottom());
|
nextTick(() => smartScrollToBottom());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}
|
||||||
|
catch (err) {
|
||||||
console.error('解析数据时出错:', err);
|
console.error('解析数据时出错:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +275,8 @@ function handleDataChunk(chunk: AnyObject) {
|
|||||||
* 发送消息并处理流式响应
|
* 发送消息并处理流式响应
|
||||||
*/
|
*/
|
||||||
async function startSSE(chatContent: string) {
|
async function startSSE(chatContent: string) {
|
||||||
if (isSending.value) return;
|
if (isSending.value)
|
||||||
|
return;
|
||||||
|
|
||||||
// 检查是否有未上传完成的文件
|
// 检查是否有未上传完成的文件
|
||||||
const hasUnuploadedFiles = filesStore.filesList.some(f => !f.isUploaded);
|
const hasUnuploadedFiles = filesStore.filesList.some(f => !f.isUploaded);
|
||||||
@@ -315,13 +323,16 @@ async function startSSE(chatContent: string) {
|
|||||||
modelId: modelStore.currentModelInfo.modelId ?? '',
|
modelId: modelStore.currentModelInfo.modelId ?? '',
|
||||||
sessionId,
|
sessionId,
|
||||||
})) {
|
})) {
|
||||||
|
console.log('111');
|
||||||
handleDataChunk(chunk.result as AnyObject);
|
handleDataChunk(chunk.result as AnyObject);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
}
|
||||||
|
catch (err: any) {
|
||||||
if (err.name !== 'AbortError') {
|
if (err.name !== 'AbortError') {
|
||||||
console.error('Fetch error:', err);
|
console.error('Fetch error:', err);
|
||||||
}
|
}
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
finishSending();
|
finishSending();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,7 +361,8 @@ function buildMessagesContent(imageFiles: any[], textFiles: any[]) {
|
|||||||
fileContent += `<FILE_NAME>${fileItem.name}</FILE_NAME>\n`;
|
fileContent += `<FILE_NAME>${fileItem.name}</FILE_NAME>\n`;
|
||||||
fileContent += `<FILE_CONTENT>\n${fileItem.fileContent}\n</FILE_CONTENT>\n`;
|
fileContent += `<FILE_CONTENT>\n${fileItem.fileContent}\n</FILE_CONTENT>\n`;
|
||||||
fileContent += `</ATTACHMENT_FILE>\n`;
|
fileContent += `</ATTACHMENT_FILE>\n`;
|
||||||
if (idx < textFiles.length - 1) fileContent += '\n';
|
if (idx < textFiles.length - 1)
|
||||||
|
fileContent += '\n';
|
||||||
});
|
});
|
||||||
contentArray.push({ type: 'text', text: fileContent });
|
contentArray.push({ type: 'text', text: fileContent });
|
||||||
}
|
}
|
||||||
@@ -368,7 +380,8 @@ function buildMessagesContent(imageFiles: any[], textFiles: any[]) {
|
|||||||
baseMessage.content = contentArray.length > 1 || imageFiles.length > 0 || textFiles.length > 0
|
baseMessage.content = contentArray.length > 1 || imageFiles.length > 0 || textFiles.length > 0
|
||||||
? contentArray
|
? contentArray
|
||||||
: item.content;
|
: item.content;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
baseMessage.content = (item.role === 'ai' || item.role === 'assistant') && item.content.length > 10000
|
baseMessage.content = (item.role === 'ai' || item.role === 'assistant') && item.content.length > 10000
|
||||||
? `${item.content.substring(0, 10000)}...(内容过长,已省略)`
|
? `${item.content.substring(0, 10000)}...(内容过长,已省略)`
|
||||||
: item.content;
|
: item.content;
|
||||||
@@ -451,11 +464,13 @@ function exitDeleteMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleMessageSelection(item: MessageItemType) {
|
function toggleMessageSelection(item: MessageItemType) {
|
||||||
if (!item.id) return;
|
if (!item.id)
|
||||||
|
return;
|
||||||
const index = selectedMessageIds.value.indexOf(item.id);
|
const index = selectedMessageIds.value.indexOf(item.id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
selectedMessageIds.value.splice(index, 1);
|
selectedMessageIds.value.splice(index, 1);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
selectedMessageIds.value.push(item.id);
|
selectedMessageIds.value.push(item.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,7 +490,8 @@ async function confirmDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bubbleItems.value = bubbleItems.value.filter((item) => {
|
bubbleItems.value = bubbleItems.value.filter((item) => {
|
||||||
if (item.id === undefined) return true;
|
if (item.id === undefined)
|
||||||
|
return true;
|
||||||
return !savedIds.includes(item.id) && !tempIds.includes(item.id as number);
|
return !savedIds.includes(item.id) && !tempIds.includes(item.id as number);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -486,7 +502,8 @@ async function confirmDelete() {
|
|||||||
|
|
||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
exitDeleteMode();
|
exitDeleteMode();
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
ElMessage.error('删除失败');
|
ElMessage.error('删除失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,10 +521,12 @@ function cancelEdit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitEditMessage(item: MessageItemType) {
|
async function submitEditMessage(item: MessageItemType) {
|
||||||
if (isSending.value || !editingContent.value.trim()) return;
|
if (isSending.value || !editingContent.value.trim())
|
||||||
|
return;
|
||||||
|
|
||||||
const itemIndex = bubbleItems.value.findIndex(msg => msg.key === item.key);
|
const itemIndex = bubbleItems.value.findIndex(msg => msg.key === item.key);
|
||||||
if (itemIndex === -1) return;
|
if (itemIndex === -1)
|
||||||
|
return;
|
||||||
|
|
||||||
const messageId = item.id;
|
const messageId = item.id;
|
||||||
const newContent = editingContent.value.trim();
|
const newContent = editingContent.value.trim();
|
||||||
@@ -520,7 +539,8 @@ async function submitEditMessage(item: MessageItemType) {
|
|||||||
|
|
||||||
bubbleItems.value = bubbleItems.value.slice(0, itemIndex);
|
bubbleItems.value = bubbleItems.value.slice(0, itemIndex);
|
||||||
await startSSE(newContent);
|
await startSSE(newContent);
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
ElMessage.error('编辑失败');
|
ElMessage.error('编辑失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -534,10 +554,12 @@ function copy(item: MessageItemType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function regenerateMessage(item: MessageItemType) {
|
async function regenerateMessage(item: MessageItemType) {
|
||||||
if (isSending.value) return;
|
if (isSending.value)
|
||||||
|
return;
|
||||||
|
|
||||||
const itemIndex = bubbleItems.value.findIndex(msg => msg.key === item.key);
|
const itemIndex = bubbleItems.value.findIndex(msg => msg.key === item.key);
|
||||||
if (itemIndex === -1) return;
|
if (itemIndex === -1)
|
||||||
|
return;
|
||||||
|
|
||||||
// 找到对应的用户消息
|
// 找到对应的用户消息
|
||||||
let targetUserMessageIndex = -1;
|
let targetUserMessageIndex = -1;
|
||||||
@@ -563,7 +585,8 @@ async function regenerateMessage(item: MessageItemType) {
|
|||||||
|
|
||||||
bubbleItems.value = bubbleItems.value.slice(0, targetUserMessageIndex);
|
bubbleItems.value = bubbleItems.value.slice(0, targetUserMessageIndex);
|
||||||
await startSSE(targetUserMessage.content || '');
|
await startSSE(targetUserMessage.content || '');
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
ElMessage.error('重新生成失败');
|
ElMessage.error('重新生成失败');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,7 +602,8 @@ watch(
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (val > 0) {
|
if (val > 0) {
|
||||||
senderRef.value?.openHeader();
|
senderRef.value?.openHeader();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
senderRef.value?.closeHeader();
|
senderRef.value?.closeHeader();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -589,6 +613,23 @@ watch(
|
|||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('paste', handlePaste);
|
document.addEventListener('paste', handlePaste);
|
||||||
|
|
||||||
|
// 获取 BubbleList 内部的滚动容器
|
||||||
|
nextTick(() => {
|
||||||
|
if (bubbleListRef.value?.$el) {
|
||||||
|
// 尝试获取 BubbleList 内部的滚动容器
|
||||||
|
const innerContainer = bubbleListRef.value.$el.querySelector('[style*="overflow"]');
|
||||||
|
if (innerContainer) {
|
||||||
|
scrollContainer.value = innerContainer;
|
||||||
|
scrollContainer.value.addEventListener('scroll', handleScroll);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 如果找不到内部容器,使用 BubbleList 的根元素
|
||||||
|
scrollContainer.value = bubbleListRef.value.$el;
|
||||||
|
scrollContainer.value.addEventListener('scroll', handleScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -597,6 +638,10 @@ onUnmounted(() => {
|
|||||||
if (scrollUpdateTimer !== null) {
|
if (scrollUpdateTimer !== null) {
|
||||||
clearTimeout(scrollUpdateTimer);
|
clearTimeout(scrollUpdateTimer);
|
||||||
}
|
}
|
||||||
|
// 移除滚动事件监听
|
||||||
|
if (scrollContainer.value) {
|
||||||
|
scrollContainer.value.removeEventListener('scroll', handleScroll);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -616,33 +661,33 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 消息列表 -->
|
<!-- 消息列表 -->
|
||||||
<div ref="scrollContainer" class="chat-with-id__messages-wrapper" @scroll="handleScroll">
|
<div class="chat-with-id__messages-wrapper">
|
||||||
<BubbleList
|
<BubbleList
|
||||||
ref="bubbleListRef"
|
ref="bubbleListRef"
|
||||||
:list="bubbleItems"
|
:list="bubbleItems"
|
||||||
max-height="calc(100vh - 240px)"
|
max-height="calc(100vh - 240px)"
|
||||||
:class="{ 'delete-mode': isDeleteMode }"
|
:class="{ 'delete-mode': isDeleteMode }"
|
||||||
>
|
>
|
||||||
<template #content="{ item }">
|
<template #content="{ item }">
|
||||||
<MessageItem
|
<MessageItem
|
||||||
:item="item"
|
:item="item"
|
||||||
:is-delete-mode="isDeleteMode"
|
:is-delete-mode="isDeleteMode"
|
||||||
:is-editing="editingMessageKey === item.key"
|
:is-editing="editingMessageKey === item.key"
|
||||||
:edit-content="editingContent"
|
:edit-content="editingContent"
|
||||||
:is-sending="isSending"
|
:is-sending="isSending"
|
||||||
:is-selected="item.id ? selectedMessageIds.includes(item.id) : false"
|
:is-selected="item.id ? selectedMessageIds.includes(item.id) : false"
|
||||||
@toggle-selection="toggleMessageSelection"
|
@toggle-selection="toggleMessageSelection"
|
||||||
@edit="startEditMessage"
|
@edit="startEditMessage"
|
||||||
@cancel-edit="cancelEdit"
|
@cancel-edit="cancelEdit"
|
||||||
@submit-edit="submitEditMessage"
|
@submit-edit="submitEditMessage"
|
||||||
@update:edit-content="editingContent = $event"
|
@update:edit-content="editingContent = $event"
|
||||||
@copy="copy"
|
@copy="copy"
|
||||||
@regenerate="regenerateMessage"
|
@regenerate="regenerateMessage"
|
||||||
@delete="enterDeleteMode"
|
@delete="enterDeleteMode"
|
||||||
@image-preview="handleImagePreview"
|
@image-preview="handleImagePreview"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</BubbleList>
|
</BubbleList>
|
||||||
|
|
||||||
<!-- 滚动到底部按钮 -->
|
<!-- 滚动到底部按钮 -->
|
||||||
<Transition name="scroll-btn-fade">
|
<Transition name="scroll-btn-fade">
|
||||||
@@ -775,34 +820,7 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow: hidden; // 移除滚动,让 BubbleList 内部处理
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
// 平滑滚动
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
|
|
||||||
// Webkit 浏览器滚动条样式 (Chrome, Safari, Edge)
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(144, 147, 153, 0.3);
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(144, 147, 153, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firefox 滚动条样式
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 滚动到底部按钮 ====================
|
// ==================== 滚动到底部按钮 ====================
|
||||||
@@ -847,11 +865,95 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 气泡列表基础样式覆盖
|
// 气泡列表基础样式覆盖 - 细滚动条一直显示
|
||||||
:deep(.el-bubble-list) {
|
:deep(.el-bubble-list) {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 细滚动条样式 ====================
|
||||||
|
// 对所有可能的滚动容器应用细滚动条样式
|
||||||
|
:deep(*) {
|
||||||
|
// 匹配具有overflow样式的元素
|
||||||
|
&[style*="overflow"],
|
||||||
|
&[style*="overflow: auto"],
|
||||||
|
&[style*="overflow-y: auto"],
|
||||||
|
&[style*="overflow: scroll"],
|
||||||
|
&[style*="overflow-y: scroll"] {
|
||||||
|
// Webkit 浏览器滚动条样式 (Chrome, Safari, Edge)
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(144, 147, 153, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(144, 147, 153, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firefox 滚动条样式
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时对 .el-bubble-list 的子元素应用滚动条样式
|
||||||
|
:deep(.el-bubble-list) {
|
||||||
|
// Webkit 浏览器滚动条样式 (Chrome, Safari, Edge)
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(144, 147, 153, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(144, 147, 153, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firefox 滚动条样式
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
|
||||||
|
|
||||||
|
// 对所有子元素也应用滚动条样式
|
||||||
|
* {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(144, 147, 153, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(144, 147, 153, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-bubble) {
|
:deep(.el-bubble) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -920,4 +1022,8 @@ onUnmounted(() => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(10px);
|
transform: translateY(10px);
|
||||||
}
|
}
|
||||||
|
// 对话列表底部三个点加载动画
|
||||||
|
:deep(.el-bubble-loading-wrap ){
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user