<template>
|
<DialogHistory title="历史建议" v-model="dialogVisible" width="1200">
|
<!-- 左侧:对话列表 -->
|
<ConversationList
|
v-show="false"
|
:active-id="activeConversationId"
|
ref="conversationListRef"
|
/>
|
<!-- 右侧:对话详情 -->
|
<el-container class="detail-container">
|
<el-header class="header">
|
<div class="title">
|
{{ activeConversation?.title ? activeConversation?.title : '' }}
|
<span v-if="activeMessageList.length">({{ activeMessageList.length }})</span>
|
</div>
|
<div class="btns" v-if="activeConversation">
|
<el-button size="small" class="btn" @click="handlerMessageClear">
|
<Icon icon="heroicons-outline:archive-box-x-mark" color="#73C4FF" />
|
</el-button>
|
<el-button size="small" class="btn" @click="handleGoBottomMessage">
|
<Icon icon="ep:download" color="#73C4FF" />
|
</el-button>
|
<el-button size="small" class="btn" @click="handleGoTopMessage">
|
<Icon icon="ep:top" color="#73C4FF" />
|
</el-button>
|
</div>
|
</el-header>
|
|
<!-- main:消息列表 -->
|
<el-main class="main-container">
|
<div class="message-container">
|
<!-- 情况一:无聊天对话时 -->
|
<ConversationListEmpty
|
v-if="!activeConversation"
|
/>
|
<!-- 情况二:消息列表为空 -->
|
<MessageListEmpty
|
v-if="activeMessageList.length === 0 && activeConversation"
|
/>
|
<!-- 情况三:消息列表不为空 -->
|
<HistoryMessageList
|
v-if="activeMessageList.length > 0"
|
ref="messageRef"
|
:conversation="activeConversation"
|
:list="activeMessageList"
|
/>
|
</div>
|
</el-main>
|
</el-container>
|
|
</DialogHistory>
|
</template>
|
|
<script setup lang="ts">
|
import {ChatMessageApi, ChatMessageVO} from '@/api/ai/chat/message'
|
import { ChatConversationVO } from '@/api/ai/chat/conversation'
|
import ConversationList from '../conversation/HistoryConversationList.vue'
|
import HistoryMessageList from './HistoryMessageList.vue'
|
import MessageListEmpty from './MessageListEmpty.vue'
|
import ConversationListEmpty from '../conversation/ConversationListEmpty.vue'
|
|
/** AI 聊天对话 列表 */
|
defineOptions({ name: 'HistoryMessageDialog' })
|
|
const route = useRoute() // 路由
|
const message = useMessage() // 消息弹窗
|
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
|
// 聊天对话
|
const conversationListRef = ref()
|
const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
|
|
// 消息列表
|
const messageRef = ref()
|
const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
|
|
|
/** 打开弹窗 */
|
const open = async (messages: ChatMessageVO[], conversation: ChatConversationVO) => {
|
dialogVisible.value = true
|
await nextTick() // 等待弹窗DOM挂载
|
activeMessageList.value = messages
|
activeConversation.value = conversation
|
}
|
|
defineExpose({ open }) // 提供方法给 parent 调用
|
|
/** 回到 message 列表的顶部 */
|
const handleGoTopMessage = () => {
|
messageRef.value.handlerGoTop()
|
}
|
|
/** 回到 message 列表的底部 */
|
const handleGoBottomMessage = () => {
|
messageRef.value.handleGoBottom()
|
}
|
|
/** 处理 message 清空 */
|
const handlerMessageClear = async () => {
|
if (!activeConversation.value) {
|
return
|
}
|
try {
|
// 确认提示
|
await message.delConfirm('确认清空对话消息?')
|
// 清空对话
|
await ChatMessageApi.deleteByConversationId(activeConversation.value.id)
|
// 刷新 message 列表
|
activeMessageList.value = []
|
} catch {}
|
}
|
|
/** 初始化 **/
|
onMounted(async () => {
|
})
|
</script>
|
|
<style lang="scss" scoped>
|
|
// 头部
|
.detail-container {
|
display: flex;
|
flex-direction: column;
|
width: 100%;
|
height: 820px;
|
background-color: rgba(0, 0, 0, 0); /* 透明背景 */
|
z-index: 1;
|
.header {
|
display: flex;
|
flex-direction: row;
|
align-items: center;
|
justify-content: space-between;
|
box-shadow: 0 0 0 0 #dcdfe6;
|
|
.title {
|
font-size: 18px;
|
font-weight: bold;
|
color: gold;
|
}
|
|
.btns {
|
display: flex;
|
width: 300px;
|
flex-direction: row;
|
justify-content: flex-end;
|
|
.btn {
|
padding: 10px;
|
}
|
|
/* 所有状态通用透明背景 */
|
:deep(.el-button) {
|
background: transparent !important;
|
border-color: currentColor; /* 保持与文字同色 */
|
color: #409EFF; /* 蓝色文字 */
|
}
|
|
/* 悬停状态 */
|
:deep(.el-button:hover) {
|
background: rgba(0, 0, 0, 0.05) !important; /* 轻微悬停反馈 */
|
}
|
|
/* 点击状态 */
|
:deep(.el-button:active) {
|
background: rgba(0, 0, 0, 0.1) !important;
|
}
|
|
/* 禁用状态 */
|
:deep(.el-button.is-disabled) {
|
opacity: 0.6;
|
background: transparent !important;
|
}
|
}
|
}
|
}
|
|
// main 容器
|
.main-container {
|
padding: 0;
|
position: relative;
|
overflow: hidden; /* 隐藏外层滚动条 */
|
width: 100%;
|
|
.message-container {
|
position: absolute;
|
top: 0;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
overflow-y: hidden;
|
/* Firefox */
|
scrollbar-width: thin;
|
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
|
/* WebKit */
|
&::-webkit-scrollbar {
|
width: 6px;
|
background: transparent;
|
}
|
&::-webkit-scrollbar-thumb {
|
border-radius: 4px;
|
background: rgba(0, 0, 0, 0.15);
|
transition: background 0.3s;
|
&:hover { background: rgba(0, 0, 0, 0.25); }
|
}
|
}
|
}
|
|
// 底部
|
.footer-container {
|
display: flex;
|
flex-direction: column;
|
height: 114px;
|
margin-left: 10px;
|
padding: 0;
|
|
// 输入框
|
.input-container {
|
display: flex;
|
flex-direction: column;
|
height: auto;
|
width: 876px;
|
margin: 0;
|
padding: 0;
|
overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
|
overflow-x: hidden; /* 水平方向隐藏滚动条 */
|
/* Firefox */
|
scrollbar-width: thin;
|
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
|
/* WebKit */
|
&::-webkit-scrollbar {
|
width: 6px;
|
background: transparent;
|
}
|
&::-webkit-scrollbar-thumb {
|
border-radius: 4px;
|
background: rgba(0, 0, 0, 0.15);
|
transition: background 0.3s;
|
&:hover { background: rgba(0, 0, 0, 0.25); }
|
}
|
|
.prompt-from {
|
display: flex;
|
flex-direction: column;
|
padding: 9px 10px;
|
width: 876px;
|
height: 114px;
|
background: rgba(115,196,255,0.05);
|
border-radius: 4px 4px 4px 4px;
|
border: 1px solid #73C4FF;
|
}
|
|
.prompt-input {
|
width: 876px;
|
height: 113.55px;
|
font-weight: 400;
|
font-size: 14px;
|
background-color: rgba(219,238,255,0);
|
line-height: 21px;
|
text-align: left;
|
font-style: normal;
|
text-transform: none;
|
border: 0;
|
color: rgba(219,238,255,0.6);
|
}
|
|
.prompt-input:focus {
|
outline: none;
|
}
|
|
.prompt-btns {
|
display: flex;
|
justify-content: space-between;
|
padding-bottom: 0;
|
padding-top: 5px;
|
|
.content {
|
/* 默认状态 */
|
.el-button {
|
background: transparent !important;
|
border-color: rgba(115, 196, 255, 0.5);
|
color: #73C4FF;
|
border-radius: 15px !important;
|
}
|
|
/* 上下文图标处理 */
|
.content-icon {
|
color: blue; /* 图标颜色 */
|
font-size: 18px;
|
margin-right: 10px;
|
background: url("@/assets/ai/zhuanlu/content.png");
|
vertical-align: middle;
|
}
|
|
/* 选中状态 */
|
.active-button {
|
background: #409eff !important;
|
border-color: #409eff !important;
|
color: white !important;
|
.content-icon {
|
background: url("@/assets/ai/zhuanlu/content_select.png");
|
vertical-align: middle;
|
}
|
}
|
|
/* 按钮组间距处理 */
|
.button-group .el-button {
|
margin-left: 0;
|
border-radius: 4px;
|
}
|
|
/* 悬停效果 */
|
.el-button:not(.active-button):hover {
|
border-color: rgba(115,196,255,0.5);
|
color: #409eff;
|
}
|
}
|
.message {
|
/* 所有状态通用透明背景 */
|
:deep(.el-button) {
|
background: rgba(73, 254, 210, 0.8) !important;
|
border-color: currentColor; /* 保持与文字同色 */
|
font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
|
font-weight: bold;
|
font-size: 16px;
|
color: #123C4E;
|
clip-path: polygon(
|
0 0,
|
100% 0,
|
100% 100%,
|
10px 100%, /* 右下方向留出10px */
|
0 calc(100% - 10px) /* 左上方向留出10px */
|
);
|
position: relative;
|
padding-left: 15px; /* 增加右侧留白 */
|
}
|
|
/* 悬停状态 */
|
:deep(.el-button:hover) {
|
background: rgba(73, 254, 210, 0.6) !important; /* 轻微悬停反馈 */
|
}
|
|
/* 点击状态 */
|
:deep(.el-button:active) {
|
background: rgba(73, 254, 210, 1) !important;
|
}
|
|
/* 禁用状态 */
|
:deep(.el-button.is-disabled) {
|
opacity: 0.6;
|
background: transparent !important;
|
}
|
|
/* 核心样式覆盖 */
|
:deep(.el-switch__core) {
|
background: transparent !important;
|
border-radius: 0 0 15px 0 !important;
|
border: none !important;
|
height: 40px !important;
|
}
|
|
/* 按钮内容容器 */
|
.button-content {
|
display: flex;
|
align-items: center;
|
padding: 0 15px;
|
height: 100%;
|
}
|
}
|
}
|
}
|
}
|
</style>
|