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.

443 lines
11 KiB

  1. <script setup>
  2. // 移除未使用的导入
  3. import { ref, nextTick, watch,onMounted } from 'vue'
  4. import { useAppBridge } from '../assets/js/useAppBridge'
  5. import { useUserInfo } from '../store/userPermissionCode'
  6. const { getQueryVariable } = useUserInfo()
  7. const isTokenValid = ref(false)
  8. const fnGetToken = () => {
  9. localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
  10. console.log(localStorage.getItem('localToken'));
  11. }
  12. setTimeout(() => {
  13. fnGetToken()
  14. }, 800)
  15. // 公共参数
  16. const commonParams = {
  17. token: localStorage.getItem('localToken'),
  18. }
  19. // 验证 token
  20. const validateToken = async () => {
  21. const token = localStorage.getItem('localToken')
  22. if (!token) {
  23. console.error('未找到 token,请重新登录')
  24. return false
  25. }
  26. // const isValid = await validateTokenAPI(token)
  27. // if (!isValid) {
  28. // console.error('Token 无效,请重新登录')
  29. // return false
  30. // }
  31. return true
  32. }
  33. // 页面加载时验证 token
  34. validateToken().then((isValid) => {
  35. isTokenValid.value = isValid
  36. if (!isValid) {
  37. // 可以在这里添加跳转到登录页等逻辑
  38. console.error('Token 验证失败,请重新登录')
  39. }
  40. })
  41. // 定义Props(可配置参数)
  42. const props = defineProps({
  43. apiUrl: {
  44. type: String,
  45. default: 'http://localhost:5000/ask'
  46. },
  47. initialGreeting: {
  48. type: String,
  49. default: '您好!请问有什么可以帮助您?'
  50. }
  51. })
  52. // 响应式数据
  53. const messages = ref([
  54. {
  55. content: props.initialGreeting,
  56. sender: 'bot',
  57. timestamp: new Date()
  58. }
  59. ])
  60. const inputMessage = ref('')
  61. const messageContainer = ref(null)
  62. const isLoading = ref(false) // 新增:加载状态
  63. // 自动滚动到底部
  64. const scrollToBottom = () => {
  65. nextTick(() => {
  66. if (messageContainer.value) {
  67. messageContainer.value.scrollTop = messageContainer.value.scrollHeight
  68. }
  69. })
  70. }
  71. // 发送消息
  72. const sendMessage = async () => {
  73. if (!isTokenValid.value) {
  74. console.error('Token 验证失败,无法发送消息')
  75. return
  76. }
  77. if (isLoading.value) return;
  78. const content = inputMessage.value.trim()
  79. if (!content) return
  80. // 添加用户消息
  81. messages.value.push({
  82. content,
  83. sender: 'user',
  84. timestamp: new Date()
  85. })
  86. // 清空输入框
  87. inputMessage.value = ''
  88. scrollToBottom()
  89. // 显示加载动画
  90. isLoading.value = true
  91. messages.value.push({
  92. content: '我正在思考...',
  93. sender: 'bot',
  94. timestamp: new Date(),
  95. isLoading: true
  96. })
  97. scrollToBottom()
  98. try {
  99. // 调用API获取回复
  100. const response = await fetch(props.apiUrl, {
  101. method: 'POST',
  102. headers: { 'Content-Type': 'application/json' },
  103. body: JSON.stringify({ question: content })
  104. })
  105. const data = await response.json()
  106. // 移除加载消息
  107. messages.value = messages.value.filter(msg => !msg.isLoading)
  108. // 添加机器人回复
  109. messages.value.push({
  110. content: data.answer,
  111. sender: 'bot',
  112. timestamp: new Date()
  113. })
  114. scrollToBottom()
  115. } catch (error) {
  116. console.error('API请求失败:', error)
  117. // 移除加载消息
  118. messages.value = messages.value.filter(msg => !msg.isLoading)
  119. messages.value.push({
  120. content: '服务暂时不可用,请稍后再试',
  121. sender: 'bot',
  122. timestamp: new Date()
  123. })
  124. scrollToBottom()
  125. } finally {
  126. // 隐藏加载动画
  127. isLoading.value = false
  128. }
  129. }
  130. // 格式化时间显示
  131. const formatTime = (date) => {
  132. return new Date(date).toLocaleTimeString([], {
  133. hour: '2-digit',
  134. minute: '2-digit'
  135. })
  136. }
  137. // 自适应输入框高度
  138. const adjustInputHeight = () => {
  139. const textarea = document.querySelector('.message-input')
  140. textarea.style.height = 'auto'
  141. textarea.style.height = `${textarea.scrollHeight}px`
  142. }
  143. // 监听输入内容变化
  144. watch(inputMessage, adjustInputHeight)
  145. // 在组件挂载后按顺序执行操作
  146. onMounted(async () => {
  147. // 先获取 token
  148. fnGetToken()
  149. // 再验证 token
  150. const isValid = await validateToken()
  151. isTokenValid.value = isValid
  152. if (!isValid) {
  153. console.error('Token 验证失败,请重新登录')
  154. }
  155. })
  156. </script>
  157. <template>
  158. <!-- 聊天容器 -->
  159. <div class="chat-container">
  160. <!-- 聊天框头部 -->
  161. <div class="chat-header">夺宝奇兵智能客服</div>
  162. <!-- 消息展示区域 -->
  163. <div class="message-list" ref="messageContainer">
  164. <div v-for="(message, index) in messages" :key="index" class="message-item" :class="[message.sender]">
  165. <!-- 机器人头像 -->
  166. <div v-if="message.sender === 'bot'" class="bot-avatar">
  167. <img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar">
  168. </div>
  169. <div class="message-bubble">
  170. <div class="message-content">
  171. <!-- 显示加载动画 -->
  172. <span v-if="message.isLoading">
  173. {{ message.content }}
  174. <el-icon class="is-loading">
  175. <Loading />
  176. </el-icon>
  177. </span>
  178. <span v-else>{{ message.content }}</span>
  179. </div>
  180. <div class="message-time">{{ formatTime(message.timestamp) }}</div>
  181. </div>
  182. <!-- 用户头像 -->
  183. <div v-if="message.sender === 'user'" class="user-avatar">
  184. <img src="/src/assets/img/avatar/小柒.png" alt="User Avatar">
  185. </div>
  186. </div>
  187. </div>
  188. <!-- 输入区域 -->
  189. <div class="input-area">
  190. <textarea v-model="inputMessage" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()"
  191. placeholder="输入您的问题..." rows="1" class="message-input"></textarea>
  192. <el-tooltip content="机器人正在思考" :disabled="!isLoading">
  193. <template #content>
  194. 机器人正在思考
  195. </template>
  196. <button @click="sendMessage" :disabled="!isTokenValid || isLoading" class="send-button">
  197. <!-- 使用ElementPlus的发送图标 -->
  198. <span v-if="isLoading">
  199. <el-icon class="is-loading">
  200. <Loading />
  201. </el-icon>
  202. </span>
  203. <span v-else class="send-button-content">
  204. <el-icon>
  205. <Position />
  206. </el-icon>
  207. <span> 发送</span>
  208. </span>
  209. </button>
  210. </el-tooltip>
  211. </div>
  212. <!-- 未登录覆盖层 -->
  213. <div v-if="!isTokenValid" class="overlay">
  214. <div class="overlay-content">用户未登录</div>
  215. </div>
  216. </div>
  217. </template>
  218. <style scoped>
  219. .chat-container {
  220. display: flex;
  221. flex-direction: column;
  222. height: 90vh;
  223. width: 90vw;
  224. max-width: 800px;
  225. margin: 0;
  226. border: 1px solid #e0e0e0;
  227. border-radius: 12px;
  228. background: #f8f9fa;
  229. overflow: hidden;
  230. /* 新增样式,实现水平和垂直居中 */
  231. position: absolute;
  232. top: 50%;
  233. left: 50%;
  234. transform: translate(-50%, -50%);
  235. }
  236. /* 聊天框头部样式 */
  237. .chat-header {
  238. background-color: #007bff;
  239. color: white;
  240. padding: 16px;
  241. font-size: 1.2rem;
  242. text-align: center;
  243. }
  244. .message-list {
  245. flex: 1;
  246. padding: 20px;
  247. overflow-y: auto;
  248. background: white;
  249. }
  250. .message-item {
  251. display: flex;
  252. margin-bottom: 16px;
  253. }
  254. .message-item.user {
  255. justify-content: flex-end;
  256. }
  257. .bot-avatar {
  258. margin-right: 10px;
  259. }
  260. .bot-avatar img {
  261. width: 40px;
  262. height: 40px;
  263. border-radius: 50%;
  264. object-fit: cover;
  265. }
  266. .user-avatar {
  267. margin-left: 10px;
  268. }
  269. .user-avatar img {
  270. width: 40px;
  271. height: 40px;
  272. border-radius: 50%;
  273. object-fit: cover;
  274. }
  275. .message-bubble {
  276. max-width: 80%;
  277. padding: 12px 16px;
  278. border-radius: 15px;
  279. position: relative;
  280. }
  281. .message-content span {
  282. display: block;
  283. /* 确保元素显示 */
  284. }
  285. .message-item.user .message-bubble {
  286. background: #007bff;
  287. color: white;
  288. border-bottom-right-radius: 4px;
  289. }
  290. .message-item.bot .message-bubble {
  291. background: #f1f3f5;
  292. color: #212529;
  293. border-bottom-left-radius: 4px;
  294. }
  295. .message-time {
  296. font-size: 0.75rem;
  297. color: rgba(255, 255, 255, 0.8);
  298. margin-top: 4px;
  299. text-align: right;
  300. }
  301. .message-item.bot .message-time {
  302. color: rgba(0, 0, 0, 0.6);
  303. }
  304. .input-area {
  305. display: flex;
  306. align-items: center;
  307. gap: 12px;
  308. padding: 16px;
  309. border-top: 1px solid #e0e0e0;
  310. background: white;
  311. }
  312. .message-input {
  313. flex: 1;
  314. padding: 10px 16px;
  315. border: 1px solid #e0e0e0;
  316. border-radius: 20px;
  317. resize: none;
  318. max-height: 120px;
  319. font-family: inherit;
  320. }
  321. .send-button {
  322. display: flex;
  323. align-items: center;
  324. justify-content: center;
  325. width: 100px;
  326. /* 调整宽度以适应文字 */
  327. height: 40px;
  328. border: none;
  329. border-radius: 20px;
  330. /* 调整圆角 */
  331. background: #007bff;
  332. color: white;
  333. cursor: pointer;
  334. transition: all 0.3s ease;
  335. /* 添加过渡效果 */
  336. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  337. /* 添加阴影 */
  338. font-size: 16px;
  339. /* 调整字体大小 */
  340. font-weight: 600;
  341. /* 调整字体粗细 */
  342. }
  343. .send-button:hover {
  344. background: #0056b3;
  345. transform: translateY(-2px);
  346. /* 悬停时向上移动 */
  347. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  348. /* 悬停时增加阴影 */
  349. }
  350. /* 新增加载状态样式 */
  351. .loading-state {
  352. background: #ccc;
  353. cursor: not-allowed;
  354. }
  355. .send-button-content {
  356. display: flex;
  357. align-items: center;
  358. justify-content: center;
  359. gap: 8px;
  360. /* 调整文字和图标间距 */
  361. }
  362. /* .send-button {
  363. display: flex;
  364. align-items: center;
  365. justify-content: center;
  366. width: 40px;
  367. height: 40px;
  368. border: none;
  369. border-radius: 50%;
  370. background: #007bff;
  371. color: white;
  372. cursor: pointer;
  373. transition: background 0.2s;
  374. }
  375. .send-button:hover {
  376. background: #0056b3;
  377. } */
  378. .send-button svg {
  379. width: 20px;
  380. height: 20px;
  381. }
  382. .overlay {
  383. position: absolute;
  384. top: 0;
  385. left: 0;
  386. width: 100%;
  387. height: 100%;
  388. background-color: rgba(255, 255, 255, 0.8);
  389. /* 透明度 50% 的白色背景 */
  390. display: flex;
  391. justify-content: center;
  392. align-items: center;
  393. z-index: 1;
  394. /* 确保覆盖层在聊天框上方 */
  395. }
  396. .overlay-content {
  397. font-size: 36px;
  398. font-weight: bold;
  399. color: #f60707;
  400. }
  401. </style>