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.

633 lines
16 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  1. <script setup>
  2. // 导入
  3. import { ref, computed, onMounted, watch, nextTick } from "vue";
  4. import { setHeight } from "../utils/setHeight";
  5. import { getUserCountAPI } from "../api/AIxiaocaishen";
  6. import AIchat from "./AIchat.vue";
  7. import AIfind from "./AIfind.vue";
  8. import { useAppBridge } from '../assets/js/useAppBridge.js'
  9. import { useDataStore } from '@/store/dataList.js'
  10. import { useChatStore } from '../store/chat'
  11. import { useAudioStore } from '../store/audio'
  12. // import { useUserStore } from "../store/userPessionCode.js";
  13. const { getQueryVariable } = useDataStore()
  14. // 变量
  15. // 音频管理
  16. const audioStore = useAudioStore()
  17. const isVoice = computed(() => audioStore.isVoiceEnabled)
  18. const toggleVoice = () => {
  19. audioStore.toggleVoice()
  20. }
  21. // 将默认值改为从 sessionStorage 中获取,如果没有则使用默认值 'aifindCow'为第一个默认tab
  22. const activeTab = ref(sessionStorage.getItem("activeTabAI") || "AIchat");
  23. const activeIndex = ref(
  24. parseInt(sessionStorage.getItem("activeIndexAI") || "0")
  25. );
  26. const tabs = computed(() => [
  27. {
  28. name: "AIchat",
  29. label: "AI对话",
  30. },
  31. {
  32. name: "AIfind",
  33. label: "发现",
  34. },
  35. ]);
  36. // 修改 setActiveTab 方法,添加一个可选参数 forceAIchat
  37. const setActiveTab = (tab, index, forceAIchat = false) => {
  38. isAnnouncementVisible.value = false;
  39. if (forceAIchat && activeTab.value !== "AIchat") {
  40. activeTab.value = "AIchat";
  41. activeIndex.value = 0;
  42. sessionStorage.setItem("activeTabAI", "AIchat");
  43. sessionStorage.setItem("activeIndexAI", "0");
  44. } else {
  45. activeTab.value = tab;
  46. activeIndex.value = index;
  47. sessionStorage.setItem("activeTabAI", tab);
  48. sessionStorage.setItem("activeIndexAI", index.toString());
  49. }
  50. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  51. };
  52. // 修改 activeComponent 的计算逻辑
  53. const activeComponent = computed(() => {
  54. if (isAnnouncementVisible.value) {
  55. return Announcement;
  56. }
  57. return activeTab.value === "AIchat" ? AIchat : AIfind;
  58. });
  59. // 新增一个方法,调用时先判断是否处于 AIchat,若不在则跳转到 AIchat
  60. const ensureAIchat = () => {
  61. setActiveTab("AIchat", 0, true);
  62. };
  63. // 获取次数
  64. const UserCount = ref(0);
  65. const getUserCount = async () => {
  66. const result = await getUserCountAPI({ token: localStorage.getItem('localToken') });
  67. UserCount.value = result.data.hasCount;
  68. };
  69. const getCount = () => {
  70. console.log('点击了获取次数的按钮')
  71. }
  72. // 深度思考
  73. const isThinking = ref(true);
  74. const toggleThink = () => {
  75. isThinking.value = !isThinking.value;
  76. };
  77. // 发送消息
  78. const message = ref("");
  79. // 传输对象
  80. const messages = ref([]);
  81. // 信息加载状态
  82. const isLoading = ref(false);
  83. // 添加用户消息
  84. const updateMessage = (title) => {
  85. message.value = title;
  86. // console.log("updateMessage 的值:", title);
  87. };
  88. const sendMessage = async () => {
  89. // 调用 ensureAIchat 确保跳转到 AIchat 页面
  90. ensureAIchat();
  91. if (!message.value) return;
  92. if (isLoading.value) return;
  93. // 发送消息时,设置 isLoading 为 true
  94. messages.value = [
  95. ...messages.value,
  96. {
  97. sender: "user",
  98. content: message.value,
  99. timestamp: new Date().toISOString(),
  100. }
  101. ];
  102. // 重置消息输入框
  103. message.value = "";
  104. };
  105. // 公告
  106. // 引入公告组件
  107. import Announcement from "./Announcement.vue";
  108. // 新增一个变量来控制是否显示公告页面
  109. const isAnnouncementVisible = ref(false);
  110. const showAnnouncement = () => {
  111. console.log("打开公告");
  112. isAnnouncementVisible.value = true; // 显示公告页面
  113. activeTab.value = 'Announcement`'
  114. };
  115. // 点击剩余次数会弹出的弹窗
  116. // 新增一个 ref 来控制弹窗的显示与隐藏
  117. const dialogVisible = ref(false);
  118. // 获取次数
  119. const showCount = () => {
  120. console.log("显示剩余次数");
  121. // 显示弹窗
  122. dialogVisible.value = true;
  123. console.log("dialogVisible 的值:", dialogVisible.value); // 添加日志确认
  124. };
  125. // 保证发送消息时,滚动屏在底部
  126. const chatStore = useChatStore()
  127. const tabContent = ref(null);
  128. const isScrolling = ref(false); //判断用户是否在滚动
  129. const smoothScrollToBottom = async () => {
  130. await nextTick();
  131. const container = tabContent.value;
  132. if (!container) return;
  133. if (!isScrolling.value)
  134. container.scrollTop = container.scrollHeight - container.offsetHeight;
  135. requestAnimationFrame(() => {
  136. container.scrollTop = container.scrollHeight - container.offsetHeight;
  137. });
  138. }
  139. const handleScroll = function () {
  140. const scrollContainer = tabContent.value
  141. const scrollTop = scrollContainer.scrollTop
  142. const scrollHeight = scrollContainer.scrollHeight
  143. const offsetHeight = scrollContainer.offsetHeight
  144. console.log(scrollTop, scrollHeight, offsetHeight, "scrollTop, scrollHeight, offsetHeight");
  145. if (scrollTop + offsetHeight < scrollHeight) {
  146. // 用户开始滚动并在最底部之上,取消保持在最底部的效果
  147. isScrolling.value = true
  148. } else {
  149. // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
  150. isScrolling.value = false
  151. }
  152. console.log(isScrolling.value)
  153. }
  154. watch(
  155. () => chatStore.messages,
  156. () => {
  157. smoothScrollToBottom();
  158. },
  159. { deep: true, immediate: true }
  160. );
  161. watch(
  162. activeTab,
  163. async (newVal) => {
  164. smoothScrollToBottom();
  165. });
  166. // 在setTimeout中延迟执行
  167. setTimeout(() => {
  168. fnGetToken()
  169. }, 800)
  170. // 获取token的核心函数
  171. const fnGetToken = () => {
  172. window.JWready = (ress) => {
  173. // 处理平台判断
  174. if (!ress.data.platform) {
  175. // 非App环境通过URL参数获取
  176. localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
  177. // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  178. } else {
  179. // App环境通过桥接获取
  180. useAppBridge().packageFun(
  181. 'JWgetStorage',
  182. (response) => {
  183. const res = JSON.parse(response) // 解析返回的结果
  184. localStorage.setItem('localToken', res.data)
  185. // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  186. },
  187. 5,
  188. {
  189. key: 'token'
  190. }
  191. )
  192. }
  193. }
  194. // 触发App桥接
  195. useAppBridge().packageFun('JWwebReady', () => { }, 5, {})
  196. }
  197. onMounted(async () => {
  198. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  199. getUserCount();
  200. smoothScrollToBottom();
  201. // 监听滚动事件,判断用户滚动状态
  202. tabContent.value.addEventListener('scroll', handleScroll)
  203. })
  204. </script>
  205. <template>
  206. <div class="homepage" id="testId">
  207. <el-container>
  208. <!-- AI小财神头部 logo 次数 公告 -->
  209. <el-header class="homepage-head">
  210. <!-- logo -->
  211. <div class="homepage-logo">
  212. <img src="src\assets\img\homePage\logo.png" alt="图片加载失败" class="logo1" />
  213. <img src="src\assets\img\homePage\madeInHL.png" alt="图片加载失败" class="logo2" />
  214. </div>
  215. <div class="homepage-right-group">
  216. <div class="count-badge" @click="showCount">
  217. <img src="src\assets\img\homePage\get-count-all.png" class="action-btn" />
  218. <div class="count-number">{{ UserCount }}</div>
  219. </div>
  220. <img src="src\assets\img\homePage\announcement.png" class="announcement-btn action-btn"
  221. @click="showAnnouncement" />
  222. </div>
  223. </el-header>
  224. <!-- 主体部分小人 问题轮询图 对话内容 -->
  225. <el-main class="homepage-body">
  226. <div class="main-wrapper">
  227. <section class="tab-section">
  228. <div class="tab-container">
  229. <div v-for="(tab, index) in tabs" :key="tab.name" @click="setActiveTab(tab.name, index)"
  230. :class="['tab-item', { active: activeIndex === index }]">
  231. <span>{{ tab.label }}</span>
  232. </div>
  233. </div>
  234. </section>
  235. <div class="tab-content" ref="tabContent">
  236. <component :is="activeComponent" :messages="messages" @updateMessage="updateMessage"
  237. @sendMessage="sendMessage" />
  238. </div>
  239. </div>
  240. </el-main>
  241. <!-- 尾部 问题输入框 深度思考 多语言 语音播报 -->
  242. <el-footer class="homepage-footer">
  243. <!-- 第一行按钮 -->
  244. <div class="footer-first-line">
  245. <div class="left-group">
  246. <img v-if="isThinking" src="src\assets\img\homePage\tail\think-active.png" @click="toggleThink"
  247. class="action-btn" />
  248. <img v-else src="src\assets\img\homePage\tail\think-no-active.png" @click="toggleThink"
  249. class="action-btn" />
  250. <img src="src\assets\img\homePage\tail\language.png" @click="changeLanguage" class="action-btn" />
  251. <img v-if="isVoice" src="src\assets\img\homePage\tail\voice.png" @click="toggleVoice" class="action-btn" />
  252. <img v-else src="src\assets\img\homePage\tail\voice-no-active.png" @click="toggleVoice"
  253. class="action-btn" />
  254. </div>
  255. <img src="src\assets\img\homePage\tail\send.png" @click="sendMessage" class="action-btn send-btn" />
  256. </div>
  257. <!-- 第二行输入框 -->
  258. <div class="footer-second-line">
  259. <img src="src\assets\img\homePage\tail\msg.png" class="msg-icon" />
  260. <el-input type="textarea" v-model="message" :autosize="{ minRows: 1, maxRows: 4 }" placeholder="给AI小财神发消息..."
  261. class="msg-input" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()" resize="none">
  262. </el-input>
  263. </div>
  264. </el-footer>
  265. </el-container>
  266. <!-- 弹窗 -->
  267. <!-- 新增弹窗组件 -->
  268. <el-dialog v-model="dialogVisible" width="65%">
  269. <!-- 自定义标题插槽实现居中显示 -->
  270. <template #header>
  271. <div style="text-align: center">
  272. <span>活动规则</span>
  273. </div>
  274. </template>
  275. <!-- 中间内容部分 -->
  276. <p>
  277. 活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则
  278. </p>
  279. <template #footer>
  280. <!-- 添加一个div来包裹按钮并设置样式使其居中 -->
  281. <div style="text-align: center">
  282. <el-button style="background-color: orange; color: white; border: none" @click="dialogVisible = false">
  283. 去充值
  284. </el-button>
  285. </div>
  286. </template>
  287. </el-dialog>
  288. </div>
  289. </template>
  290. <style scoped>
  291. /* 标签栏 */
  292. .tab-container {
  293. display: flex;
  294. gap: 30px;
  295. margin-bottom: 10px;
  296. padding: 0 20px;
  297. justify-content: flex-end;
  298. /* 新增右对齐 */
  299. }
  300. .tab-item {
  301. cursor: pointer;
  302. padding: 8px 12px;
  303. font-size: clamp(18px, 3vw, 20px);
  304. color: #999;
  305. transition: all 0.3s;
  306. border-bottom: 2px solid transparent;
  307. font-weight: bold;
  308. }
  309. .tab-item.active {
  310. color: #000;
  311. border-color: #000;
  312. }
  313. .tab-item:not(.active):hover {
  314. color: #666;
  315. }
  316. .tab-content {
  317. /* height: 100%; */
  318. overflow-y: auto;
  319. overflow-x: hidden;
  320. scroll-behavior: smooth;
  321. /* 添加平滑滚动效果 */
  322. }
  323. @media (max-width: 768px) {
  324. .tab-container {
  325. gap: 15px;
  326. padding: 0 10px;
  327. }
  328. .tab-item {
  329. font-size: clamp(14px, 3vw, 16px);
  330. padding: 6px 10px;
  331. }
  332. }
  333. </style>
  334. <style scoped>
  335. .homepage {
  336. height: 100vh;
  337. margin: 0 auto;
  338. background-image: url(src/assets/img/homePage/bk.png);
  339. background-size: 100% 100%;
  340. background-repeat: no-repeat;
  341. background-position: center;
  342. display: flex;
  343. }
  344. .homepage .el-container {
  345. height: 100%;
  346. flex-direction: column;
  347. /* 明确纵向排列 */
  348. display: flex;
  349. overflow: hidden;
  350. }
  351. .el-container .el-header {
  352. height: 10%;
  353. /* 设置头部高度 */
  354. margin-top: 5px;
  355. }
  356. .el-container .el-main {
  357. overflow-y: hidden;
  358. overflow-x: hidden;
  359. /* 新增滚动条 */
  360. flex: 1 1 0%;
  361. min-height: 0;
  362. scrollbar-width: thin;
  363. scrollbar-color: #888 transparent;
  364. }
  365. .el-container .el-footer {
  366. /* height: 11%; */
  367. height: auto;
  368. min-height: 70px;
  369. gap: 5px;
  370. margin-top: 0;
  371. }
  372. .homepage-head {
  373. padding: 0px;
  374. /* 启用 flex 布局 */
  375. display: flex;
  376. position: relative;
  377. /* 左右分开布局 */
  378. justify-content: space-between;
  379. }
  380. .homepage-right-group {
  381. display: flex;
  382. gap: 8px;
  383. align-items: center;
  384. margin-left: auto;
  385. margin-right: 20px;
  386. }
  387. .homepage-right-group .action-btn {
  388. height: 40px;
  389. }
  390. .homepage-right-group .count-badge {
  391. position: relative;
  392. cursor: pointer;
  393. }
  394. .homepage-right-group .count-badge .count-number {
  395. position: absolute;
  396. top: 6px;
  397. right: 29px;
  398. color: #573dfc;
  399. font-size: 12px;
  400. font-weight: bold;
  401. }
  402. .homepage-right-group .announcement-btn {
  403. cursor: pointer;
  404. transition: transform 0.2s;
  405. }
  406. .homepage-right-group .announcement-btn:hover {
  407. transform: scale(1.05);
  408. }
  409. .homepage-body {
  410. padding: 0px;
  411. height: calc(100% - 70px);
  412. /* 根据底部高度调整 */
  413. }
  414. .main-wrapper {
  415. height: 100%;
  416. display: flex;
  417. flex-direction: column;
  418. }
  419. .tab-section {
  420. flex-shrink: 0;
  421. /* 禁止伸缩 */
  422. }
  423. .tab-content {
  424. flex: 1;
  425. overflow-y: auto;
  426. min-height: 0;
  427. /* 关键:允许内容收缩 */
  428. }
  429. .homepage-logo {
  430. height: 100%;
  431. /* 改为根据内容自适应 */
  432. width: fit-content;
  433. display: flex;
  434. flex-direction: column;
  435. align-items: center;
  436. justify-content: center;
  437. /* 固定左间距 */
  438. margin-left: 20px;
  439. margin-right: auto;
  440. /* 新增相对定位 */
  441. position: relative;
  442. }
  443. /* 新增媒体查询适配小屏幕 */
  444. @media (max-width: 768px) {
  445. .homepage-logo {
  446. margin-left: 10px;
  447. left: 0;
  448. }
  449. }
  450. .logo1 {
  451. width: 120px;
  452. /* 固定 logo1 尺寸 */
  453. height: auto;
  454. margin-bottom: 8px;
  455. /* 添加间距 */
  456. }
  457. .logo2 {
  458. width: 80px;
  459. /* 缩小 logo2 尺寸 */
  460. height: auto;
  461. }
  462. /* 尾部 */
  463. .homepage-footer {
  464. display: flex;
  465. flex-direction: column;
  466. gap: 5px;
  467. /* margin-top: auto; */
  468. margin-bottom: 20px;
  469. flex-shrink: 0;
  470. height: auto;
  471. }
  472. .footer-first-line {
  473. display: flex;
  474. justify-content: space-between;
  475. align-items: center;
  476. margin-bottom: auto;
  477. flex-shrink: 0;
  478. }
  479. .left-group {
  480. display: flex;
  481. gap: 15px;
  482. }
  483. .action-btn {
  484. cursor: pointer;
  485. transition: transform 0.2s;
  486. height: 28px;
  487. }
  488. .action-btn:hover {
  489. transform: scale(1.05);
  490. }
  491. .send-btn {
  492. margin-left: auto;
  493. margin-right: 5px;
  494. }
  495. .footer-second-line {
  496. position: relative;
  497. display: flex;
  498. height: auto;
  499. align-items: flex-end;
  500. flex: 1;
  501. margin-top: auto;
  502. min-height: 34px;
  503. }
  504. .msg-icon {
  505. position: absolute;
  506. left: 12px;
  507. top: 50%;
  508. transform: translateY(-50%);
  509. width: 24px;
  510. z-index: 1;
  511. }
  512. .msg-input:deep(.el-textarea__inner) {
  513. border: none !important;
  514. box-shadow: none !important;
  515. overflow-y: auto !important;
  516. /* 强制显示滚动条 */
  517. transition: all 0.2s ease-out;
  518. /* 添加过渡效果 */
  519. }
  520. /* .msg-input:deep(.el-textarea__inner:focus) {
  521. border: none !important;
  522. box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3) !important;
  523. } */
  524. .msg-input {
  525. min-height: 34px;
  526. max-height: 120px;
  527. width: calc(100% - 65px);
  528. border-radius: 20px;
  529. padding: 0px 20px 0 45px;
  530. font-size: 16px;
  531. transition: height 0.2s ease-out;
  532. /* 添加高度过渡效果 */
  533. overflow-y: hidden;
  534. /* 隐藏垂直滚动条 */
  535. box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3);
  536. background: #fff;
  537. }
  538. .msg-input:focus {
  539. outline: none;
  540. }
  541. @media (max-width: 768px) {
  542. .action-btn {
  543. height: 28px;
  544. }
  545. .footer-second-line {
  546. height: 50px;
  547. }
  548. .msg-input {
  549. font-size: 16px;
  550. }
  551. }
  552. </style>