You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
444 lines
11 KiB
444 lines
11 KiB
<script setup>
|
|
// 移除未使用的导入
|
|
import { ref, nextTick, watch,onMounted } from 'vue'
|
|
import { useAppBridge } from '../assets/js/useAppBridge'
|
|
import { useUserInfo } from '../store/userPermissionCode'
|
|
const { getQueryVariable } = useUserInfo()
|
|
const isTokenValid = ref(false)
|
|
const fnGetToken = () => {
|
|
localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
|
|
console.log(localStorage.getItem('localToken'));
|
|
}
|
|
setTimeout(() => {
|
|
fnGetToken()
|
|
}, 800)
|
|
|
|
// 公共参数
|
|
const commonParams = {
|
|
token: localStorage.getItem('localToken'),
|
|
}
|
|
|
|
|
|
// 验证 token
|
|
const validateToken = async () => {
|
|
const token = localStorage.getItem('localToken')
|
|
if (!token) {
|
|
console.error('未找到 token,请重新登录')
|
|
return false
|
|
}
|
|
// const isValid = await validateTokenAPI(token)
|
|
// if (!isValid) {
|
|
// console.error('Token 无效,请重新登录')
|
|
// return false
|
|
// }
|
|
return true
|
|
}
|
|
|
|
// 页面加载时验证 token
|
|
validateToken().then((isValid) => {
|
|
isTokenValid.value = isValid
|
|
if (!isValid) {
|
|
// 可以在这里添加跳转到登录页等逻辑
|
|
console.error('Token 验证失败,请重新登录')
|
|
}
|
|
})
|
|
|
|
|
|
// 定义Props(可配置参数)
|
|
const props = defineProps({
|
|
apiUrl: {
|
|
type: String,
|
|
default: 'http://localhost:5000/ask'
|
|
},
|
|
initialGreeting: {
|
|
type: String,
|
|
default: '您好!请问有什么可以帮助您?'
|
|
}
|
|
})
|
|
// 响应式数据
|
|
const messages = ref([
|
|
{
|
|
content: props.initialGreeting,
|
|
sender: 'bot',
|
|
timestamp: new Date()
|
|
}
|
|
])
|
|
const inputMessage = ref('')
|
|
const messageContainer = ref(null)
|
|
const isLoading = ref(false) // 新增:加载状态
|
|
|
|
// 自动滚动到底部
|
|
const scrollToBottom = () => {
|
|
nextTick(() => {
|
|
if (messageContainer.value) {
|
|
messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
|
}
|
|
})
|
|
}
|
|
// 发送消息
|
|
const sendMessage = async () => {
|
|
|
|
if (!isTokenValid.value) {
|
|
console.error('Token 验证失败,无法发送消息')
|
|
return
|
|
}
|
|
|
|
if (isLoading.value) return;
|
|
const content = inputMessage.value.trim()
|
|
if (!content) return
|
|
// 添加用户消息
|
|
messages.value.push({
|
|
content,
|
|
sender: 'user',
|
|
timestamp: new Date()
|
|
})
|
|
// 清空输入框
|
|
inputMessage.value = ''
|
|
scrollToBottom()
|
|
|
|
// 显示加载动画
|
|
isLoading.value = true
|
|
messages.value.push({
|
|
content: '我正在思考...',
|
|
sender: 'bot',
|
|
timestamp: new Date(),
|
|
isLoading: true
|
|
})
|
|
scrollToBottom()
|
|
|
|
try {
|
|
// 调用API获取回复
|
|
const response = await fetch(props.apiUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ question: content })
|
|
})
|
|
const data = await response.json()
|
|
|
|
// 移除加载消息
|
|
messages.value = messages.value.filter(msg => !msg.isLoading)
|
|
|
|
// 添加机器人回复
|
|
messages.value.push({
|
|
content: data.answer,
|
|
sender: 'bot',
|
|
timestamp: new Date()
|
|
})
|
|
scrollToBottom()
|
|
} catch (error) {
|
|
console.error('API请求失败:', error)
|
|
// 移除加载消息
|
|
messages.value = messages.value.filter(msg => !msg.isLoading)
|
|
|
|
messages.value.push({
|
|
content: '服务暂时不可用,请稍后再试',
|
|
sender: 'bot',
|
|
timestamp: new Date()
|
|
})
|
|
scrollToBottom()
|
|
} finally {
|
|
// 隐藏加载动画
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
// 格式化时间显示
|
|
const formatTime = (date) => {
|
|
return new Date(date).toLocaleTimeString([], {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})
|
|
}
|
|
// 自适应输入框高度
|
|
const adjustInputHeight = () => {
|
|
const textarea = document.querySelector('.message-input')
|
|
textarea.style.height = 'auto'
|
|
textarea.style.height = `${textarea.scrollHeight}px`
|
|
}
|
|
// 监听输入内容变化
|
|
watch(inputMessage, adjustInputHeight)
|
|
|
|
// 在组件挂载后按顺序执行操作
|
|
onMounted(async () => {
|
|
// 先获取 token
|
|
fnGetToken()
|
|
// 再验证 token
|
|
const isValid = await validateToken()
|
|
isTokenValid.value = isValid
|
|
if (!isValid) {
|
|
console.error('Token 验证失败,请重新登录')
|
|
}
|
|
})
|
|
|
|
</script>
|
|
<template>
|
|
<!-- 聊天容器 -->
|
|
<div class="chat-container">
|
|
<!-- 聊天框头部 -->
|
|
<div class="chat-header">夺宝奇兵智能客服</div>
|
|
<!-- 消息展示区域 -->
|
|
<div class="message-list" ref="messageContainer">
|
|
<div v-for="(message, index) in messages" :key="index" class="message-item" :class="[message.sender]">
|
|
<!-- 机器人头像 -->
|
|
<div v-if="message.sender === 'bot'" class="bot-avatar">
|
|
<img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar">
|
|
</div>
|
|
<div class="message-bubble">
|
|
<div class="message-content">
|
|
<!-- 显示加载动画 -->
|
|
<span v-if="message.isLoading">
|
|
{{ message.content }}
|
|
<el-icon class="is-loading">
|
|
<Loading />
|
|
</el-icon>
|
|
</span>
|
|
<span v-else>{{ message.content }}</span>
|
|
</div>
|
|
<div class="message-time">{{ formatTime(message.timestamp) }}</div>
|
|
</div>
|
|
<!-- 用户头像 -->
|
|
<div v-if="message.sender === 'user'" class="user-avatar">
|
|
<img src="/src/assets/img/avatar/小柒.png" alt="User Avatar">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 输入区域 -->
|
|
<div class="input-area">
|
|
<textarea v-model="inputMessage" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()"
|
|
placeholder="输入您的问题..." rows="1" class="message-input"></textarea>
|
|
<el-tooltip content="机器人正在思考" :disabled="!isLoading">
|
|
<template #content>
|
|
机器人正在思考
|
|
</template>
|
|
<button @click="sendMessage" :disabled="!isTokenValid || isLoading" class="send-button">
|
|
<!-- 使用ElementPlus的发送图标 -->
|
|
<span v-if="isLoading">
|
|
<el-icon class="is-loading">
|
|
<Loading />
|
|
</el-icon>
|
|
</span>
|
|
<span v-else class="send-button-content">
|
|
<el-icon>
|
|
<Position />
|
|
</el-icon>
|
|
<span> 发送</span>
|
|
</span>
|
|
</button>
|
|
</el-tooltip>
|
|
</div>
|
|
<!-- 未登录覆盖层 -->
|
|
<div v-if="!isTokenValid" class="overlay">
|
|
<div class="overlay-content">用户未登录</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chat-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 90vh;
|
|
width: 90vw;
|
|
max-width: 800px;
|
|
margin: 0;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 12px;
|
|
background: #f8f9fa;
|
|
overflow: hidden;
|
|
/* 新增样式,实现水平和垂直居中 */
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
/* 聊天框头部样式 */
|
|
.chat-header {
|
|
background-color: #007bff;
|
|
color: white;
|
|
padding: 16px;
|
|
font-size: 1.2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.message-list {
|
|
flex: 1;
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
background: white;
|
|
}
|
|
|
|
.message-item {
|
|
display: flex;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.message-item.user {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.bot-avatar {
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.bot-avatar img {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.user-avatar {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.user-avatar img {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.message-bubble {
|
|
max-width: 80%;
|
|
padding: 12px 16px;
|
|
border-radius: 15px;
|
|
position: relative;
|
|
}
|
|
|
|
.message-content span {
|
|
display: block;
|
|
/* 确保元素显示 */
|
|
}
|
|
|
|
.message-item.user .message-bubble {
|
|
background: #007bff;
|
|
color: white;
|
|
border-bottom-right-radius: 4px;
|
|
}
|
|
|
|
.message-item.bot .message-bubble {
|
|
background: #f1f3f5;
|
|
color: #212529;
|
|
border-bottom-left-radius: 4px;
|
|
}
|
|
|
|
.message-time {
|
|
font-size: 0.75rem;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
margin-top: 4px;
|
|
text-align: right;
|
|
}
|
|
|
|
.message-item.bot .message-time {
|
|
color: rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
.input-area {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 16px;
|
|
border-top: 1px solid #e0e0e0;
|
|
background: white;
|
|
}
|
|
|
|
.message-input {
|
|
flex: 1;
|
|
padding: 10px 16px;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 20px;
|
|
resize: none;
|
|
max-height: 120px;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.send-button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100px;
|
|
/* 调整宽度以适应文字 */
|
|
height: 40px;
|
|
border: none;
|
|
border-radius: 20px;
|
|
/* 调整圆角 */
|
|
background: #007bff;
|
|
color: white;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
/* 添加过渡效果 */
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
/* 添加阴影 */
|
|
font-size: 16px;
|
|
/* 调整字体大小 */
|
|
font-weight: 600;
|
|
/* 调整字体粗细 */
|
|
|
|
}
|
|
|
|
.send-button:hover {
|
|
background: #0056b3;
|
|
transform: translateY(-2px);
|
|
/* 悬停时向上移动 */
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
/* 悬停时增加阴影 */
|
|
}
|
|
|
|
/* 新增加载状态样式 */
|
|
.loading-state {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.send-button-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
/* 调整文字和图标间距 */
|
|
}
|
|
|
|
/* .send-button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: #007bff;
|
|
color: white;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.send-button:hover {
|
|
background: #0056b3;
|
|
} */
|
|
|
|
.send-button svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
.overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
/* 透明度 50% 的白色背景 */
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 1;
|
|
/* 确保覆盖层在聊天框上方 */
|
|
}
|
|
|
|
.overlay-content {
|
|
font-size: 36px;
|
|
font-weight: bold;
|
|
color: #f60707;
|
|
}
|
|
</style>
|