mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-10 03:16:42 +08:00
- 在app store中新增showAICallMinimized状态用于控制AI Call最小化窗口显示 - 添加对应的mutation和action用于更新最小化窗口显示状态 - 在AICall.vue组件中实现最小化窗口的条件显示逻辑 - 修改case消息组件中的链接跳转方式为路由跳转 - 更新AI聊天接口的请求地址为统一网关路径 - 在App.vue中添加路由监听,根据路由动态控制AI Call窗口显示状态- 实现case和caseDetail路由下显示AI Call最小化窗口的逻辑
439 lines
11 KiB
Vue
439 lines
11 KiB
Vue
<template>
|
||
<div>
|
||
<!-- 最大化状态的弹窗 -->
|
||
<el-dialog
|
||
v-if="dialogVisible"
|
||
:visible="true"
|
||
width="800px"
|
||
:close-on-click-modal="false"
|
||
:show-close="true"
|
||
@close="onClose"
|
||
class="case-expert-dialog"
|
||
:modal="false"
|
||
:append-to-body="true"
|
||
:fullscreen="false"
|
||
top="10vh"
|
||
v-show="windowState === 'maximized'"
|
||
>
|
||
<!-- 标题 -->
|
||
<div slot="title" class="dialog-title">
|
||
<span>案例专家</span>
|
||
<el-button
|
||
type="text"
|
||
class="window-control-btn"
|
||
@click="minimizeWindow"
|
||
>
|
||
<i class="el-icon-minus"></i>
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 内容区域 -->
|
||
<div class="content-wrapper">
|
||
<div
|
||
class="welcome-message"
|
||
ref="messageContainer"
|
||
@scroll="handleScroll"
|
||
>
|
||
<div class="message-text" v-for="(item, index) in messageList" :key="index">
|
||
<messages :messageData="item" :suggestions="suggestions"></messages>
|
||
</div>
|
||
<div class="message-suggestions" v-if="messageList.length > 0 && messageList[messageList.length-1].textCompleted">
|
||
<div class="suggestion-item" v-for="(item, index) in suggestions" :key="index">
|
||
<a @click="sendSuggestions(item)"> {{ item }} →</a>
|
||
</div>
|
||
</div>
|
||
<div v-if="isLoading" class="loading-message">
|
||
<div class="loading-dots">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 输入框区域 -->
|
||
<send-message
|
||
v-model="AIContent"
|
||
:message-list="messageList"
|
||
:suggestions="suggestions"
|
||
@loading="handleLoading"
|
||
@update-message="updateMessage"
|
||
@update-suggestions="updateSuggestions"
|
||
@new-conversation="startNewConversation"
|
||
:disabled="isLoading"
|
||
class="input-area-wrapper"
|
||
ref="sendMessage"
|
||
/>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 最小化状态的弹窗 -->
|
||
<div
|
||
class="minimized-window"
|
||
v-show="windowState === 'minimized' && showMinimizedWindow"
|
||
@click="maximizeWindow"
|
||
>
|
||
<div class="minimized-content">
|
||
<span class="window-title">案例专家</span>
|
||
<el-button
|
||
type="text"
|
||
class="window-control-btn"
|
||
@click.stop="maximizeWindow"
|
||
>
|
||
<i class="el-icon-full-screen"></i>
|
||
</el-button>
|
||
</div>
|
||
<div class="minimized-message">
|
||
<div v-if="messageList.length <= 1 && messageList[0].isBot">
|
||
当前暂无对话内容,去创建对话吧
|
||
</div>
|
||
<div v-else>
|
||
{{ getLastUserMessage() }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapState } from 'vuex'
|
||
import messages from './components/messages.vue'
|
||
import sendMessage from './components/sendMessage.vue'
|
||
|
||
export default {
|
||
name: 'CaseExpertDialog',
|
||
props: {
|
||
dialogVisible: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
components: {
|
||
messages,
|
||
sendMessage
|
||
},
|
||
computed: {
|
||
...mapState('app', ['showAICallMinimized']),
|
||
showMinimizedWindow() {
|
||
// 只有在Vuex状态为true时才显示最小化窗口
|
||
return this.showAICallMinimized;
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
AIContent: '',
|
||
isLoading: false,
|
||
windowState: 'maximized', // 'maximized' 或 'minimized'
|
||
messageList: [
|
||
{
|
||
typing:true,
|
||
isBot: true, // 是否为机器人
|
||
text: `<p><b>您好!我是京东方案侧智能问答助手,随时为您服务。</b></p>
|
||
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
|
||
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
|
||
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
|
||
}
|
||
],
|
||
suggestions:[],
|
||
isAutoScroll: true // 是否自动滚动
|
||
}
|
||
},
|
||
watch: {
|
||
dialogVisible(newVal) {
|
||
console.log(newVal);
|
||
if (newVal) {
|
||
// 每次打开时默认最大化
|
||
this.windowState = 'maximized';
|
||
}
|
||
},
|
||
messageList: {
|
||
handler() {
|
||
this.$nextTick(() => {
|
||
this.scrollToBottom();
|
||
});
|
||
},
|
||
deep: true
|
||
}
|
||
},
|
||
methods: {
|
||
onClose() {
|
||
console.log('关闭弹窗')
|
||
this.$emit('close')
|
||
// 可以在这里执行其他逻辑
|
||
},
|
||
|
||
minimizeWindow() {
|
||
this.windowState = 'minimized';
|
||
},
|
||
|
||
maximizeWindow() {
|
||
this.windowState = 'maximized';
|
||
},
|
||
|
||
getLastUserMessage() {
|
||
// 从后往前找用户消息
|
||
for (let i = this.messageList.length - 1; i >= 0; i--) {
|
||
if (!this.messageList[i].isBot) {
|
||
// 移除HTML标签只返回纯文本
|
||
const tempDiv = document.createElement('div');
|
||
tempDiv.innerHTML = this.messageList[i].text;
|
||
return tempDiv.textContent || tempDiv.innerText || '';
|
||
}
|
||
}
|
||
return '';
|
||
},
|
||
|
||
// 处理加载状态
|
||
handleLoading(status) {
|
||
this.isLoading = status;
|
||
},
|
||
|
||
// 更新消息
|
||
updateMessage(message) {
|
||
// 由于Vue的响应式系统,message对象的更改会自动更新视图
|
||
// 这里不需要额外的操作
|
||
},
|
||
updateSuggestions(arr){
|
||
this.suggestions = arr
|
||
},
|
||
// 处理建议
|
||
sendSuggestions(item){
|
||
// this.suggestions = []
|
||
this.AIContent = item
|
||
setTimeout(()=>{
|
||
this.$refs.sendMessage.handleSend()
|
||
this.AIContent = ''
|
||
},500)
|
||
},
|
||
startNewConversation() {
|
||
this.messageList = [
|
||
{
|
||
isBot: true,
|
||
text: `<p><b>您好!我是京东方案侧智能问答助手,随时为您服务。</b></p>
|
||
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
|
||
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
|
||
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
|
||
}
|
||
];
|
||
this.AIContent = '';
|
||
this.isLoading = false;
|
||
},
|
||
|
||
// 处理滚动事件
|
||
handleScroll(event) {
|
||
const element = event.target;
|
||
// 判断是否滚动到底部
|
||
const isAtBottom = element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
|
||
|
||
// 如果滚动到底部,则开启自动滚动
|
||
// 如果离开底部,则关闭自动滚动
|
||
this.isAutoScroll = isAtBottom;
|
||
},
|
||
|
||
// 滚动到底部
|
||
scrollToBottom() {
|
||
if (this.isAutoScroll && this.$refs.messageContainer) {
|
||
this.$refs.messageContainer.scrollTop = this.$refs.messageContainer.scrollHeight;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.case-expert-dialog {
|
||
::v-deep .el-dialog{
|
||
background: url("./components/u762.svg") no-repeat ;
|
||
background-size: cover;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
|
||
//background-color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
::v-deep .el-dialog__body{
|
||
padding: 10px;
|
||
//font-size: 12px;
|
||
*{
|
||
font-size:unset ;
|
||
}
|
||
}
|
||
|
||
.dialog-title {
|
||
background: transparent;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
padding-right: 20px;
|
||
|
||
.icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.window-control-btn {
|
||
font-size: 18px;
|
||
padding: 5px 10px;
|
||
color: #000000; /* 黑色图标 */
|
||
}
|
||
}
|
||
|
||
|
||
.message-suggestions{
|
||
display: flex;
|
||
flex-direction: column;
|
||
.suggestion-item{
|
||
cursor: pointer;
|
||
float: right;
|
||
padding: 5px 15px;
|
||
box-sizing: border-box;
|
||
background-color: rgba(228, 231, 237, 1);
|
||
border-radius: 5px;
|
||
margin-bottom: 5px;
|
||
}
|
||
}
|
||
.content-wrapper {
|
||
padding: 20px;
|
||
background-color: transparent;
|
||
border-radius: 8px;
|
||
height: 550px;
|
||
position: relative;
|
||
//margin-bottom: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.welcome-message {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
margin-bottom: 10px;
|
||
flex:1;
|
||
overflow-y: auto;
|
||
|
||
.avatar {
|
||
margin-right: 12px;
|
||
img {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background-color: #007aff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: white;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.message-text {
|
||
width: 100%;
|
||
//margin-bottom: 15px;
|
||
|
||
p {
|
||
color: #333;
|
||
//font-size: 14px;
|
||
line-height: 1.6;
|
||
margin: 8px 0;
|
||
}
|
||
}
|
||
|
||
.loading-message {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
margin-bottom: 15px;
|
||
|
||
.avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background-color: #007aff;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.loading-dots {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
span {
|
||
display: inline-block;
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background-color: #999;
|
||
margin-right: 5px;
|
||
animation: loading 1.4s infinite ease-in-out both;
|
||
|
||
&:nth-child(1) {
|
||
animation-delay: -0.32s;
|
||
}
|
||
|
||
&:nth-child(2) {
|
||
animation-delay: -0.16s;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.input-area-wrapper {
|
||
//position: absolute;
|
||
//bottom: 10px;
|
||
//width: calc(100% - 40px);
|
||
}
|
||
|
||
@keyframes loading {
|
||
0%, 80%, 100% {
|
||
transform: scale(0);
|
||
}
|
||
40% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.minimized-window {
|
||
position: fixed;
|
||
right: 20px;
|
||
bottom: 20px;
|
||
width: 300px;
|
||
background: url("./components/u762.svg") no-repeat;
|
||
background-size: cover;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
z-index: 2000;
|
||
cursor: pointer;
|
||
|
||
.minimized-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10px 15px;
|
||
border-bottom: 1px solid #eee;
|
||
|
||
.window-title {
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.window-control-btn {
|
||
font-size: 16px;
|
||
padding: 3px 8px;
|
||
color: #000000; /* 黑色图标 */
|
||
}
|
||
}
|
||
|
||
.minimized-message {
|
||
padding: 15px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
min-height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
</style> |