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.

1313 lines
34 KiB

5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
2 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
  1. <script setup>
  2. // 导入
  3. import { ref, computed, onMounted, watch, nextTick, onUnmounted, h } from "vue";
  4. import { setHeight } from "../utils/setHeight";
  5. import { getUserCountAPI } from "../api/AIxiaocaishen";
  6. import { ElMessage } from "element-plus";
  7. import AIchat from "./AIchat.vue";
  8. import AIfind from "./AIfind.vue";
  9. import Feedback from "./Feedback.vue";
  10. import { useAppBridge } from "../assets/js/useAppBridge.js";
  11. import { useDataStore } from "@/store/dataList.js";
  12. import { useChatStore } from "../store/chat";
  13. import { useEmotionAudioStore } from "../store/emotionAudio";
  14. import { useAudioStore } from "../store/audio";
  15. import _ from "lodash";
  16. import logo from "../assets/img/homePage/logo.png";
  17. import madeInHL from "../assets/img/homePage/madeInHL.png";
  18. import getCountAll from "../assets/img/homePage/get-count-all.png";
  19. import announcementBtn from "../assets/img/homePage/announcement.png";
  20. import thinkActive from "../assets/img/homePage/tail/think-active.png";
  21. import thinkNoActive from "../assets/img/homePage/tail/think-no-active.png";
  22. import languageBtn from "../assets/img/homePage/tail/language.png";
  23. import dbqbButton01 from "../assets/img/AiEmotion/dbqb-button01.png";
  24. import dbqbButton02 from "../assets/img/AiEmotion/dbqb-button02.png";
  25. import emotionButton01 from "../assets/img/AiEmotion/emotion-button01.png";
  26. import emotionButton02 from "../assets/img/AiEmotion/emotion-button02.png";
  27. import voice from "../assets/img/homePage/tail/voice.png";
  28. import voiceNoActive from "../assets/img/homePage/tail/voice-no-active.png";
  29. import sendBtn from "../assets/img/homePage/tail/send.png";
  30. import msgBtn from "../assets/img/homePage/tail/msg.png";
  31. import feedbackBtn from "../assets/img/Feedback/feedbackBtn.png";
  32. import AiEmotion from "./AiEmotion.vue";
  33. import HistoryRecord from "./components/HistoryRecord.vue";
  34. // import VConsole from "vconsole";
  35. // const vConsole = new VConsole();
  36. const isMobile = ref(null);
  37. // 获取 AiEmotion 组件的 ref
  38. const aiEmotionRef = ref(null);
  39. // 获取历史记录组件的 ref
  40. const historyRecordRef = ref(null);
  41. // import { useUserStore } from "../store/userPessionCode.js";
  42. const { getQueryVariable, setActiveTabIndex } = useDataStore();
  43. const dataStore = useDataStore();
  44. const chatStore = useChatStore();
  45. // 变量
  46. // 音频管理
  47. const emotionAudioStore = useEmotionAudioStore();
  48. const audioStore = useAudioStore();
  49. // 根据当前页面类型获取对应的音频store
  50. const getCurrentAudioStore = () => {
  51. return activeTab.value === "AiEmotion" ? emotionAudioStore : audioStore;
  52. };
  53. const isVoice = computed(() => {
  54. const currentStore = getCurrentAudioStore();
  55. return currentStore.isVoiceEnabled;
  56. });
  57. const toggleVoice = () => {
  58. const currentStore = getCurrentAudioStore();
  59. if (!currentStore.isVoiceEnabled) {
  60. // 如果语音功能关闭,先开启语音功能
  61. currentStore.toggleVoice();
  62. } else {
  63. // 如果语音功能开启,则切换播放/暂停状态
  64. if (currentStore.currentAudioUrl || currentStore.ttsUrl) {
  65. // 有音频时切换播放/暂停
  66. currentStore.togglePlayPause();
  67. } else {
  68. // 没有音频时关闭语音功能
  69. currentStore.toggleVoice();
  70. }
  71. }
  72. };
  73. // 将默认值改为从 sessionStorage 中获取,如果没有则使用默认值 'aifindCow'为第一个默认tab
  74. const activeTab = ref(sessionStorage.getItem("activeTabAI") || "AIchat");
  75. const activeIndex = ref(
  76. parseInt(sessionStorage.getItem("activeIndexAI") || "0")
  77. );
  78. const tabs = computed(() => [
  79. {
  80. name: "AIchat",
  81. label: "夺宝奇兵大模型",
  82. },
  83. // {
  84. // name: "AIfind",
  85. // label: "发现",
  86. // },
  87. {
  88. name: "AiEmotion",
  89. label: "AI情绪大模型",
  90. },
  91. ]);
  92. // 修改 setActiveTab 方法,添加一个可选参数 forceAIchat
  93. const setActiveTab = (tab, index, forceAIchat = false) => {
  94. isScrolling.value = false; //回复滚动到底部方法
  95. isAnnouncementVisible.value = false;
  96. // 重置输入框禁用状态,防止页面切换时状态残留
  97. isInputDisabled.value = false;
  98. if (forceAIchat && activeTab.value !== "AIchat") {
  99. activeTab.value = "AIchat";
  100. activeIndex.value = 0;
  101. sessionStorage.setItem("activeTabAI", "AIchat");
  102. sessionStorage.setItem("activeIndexAI", "0");
  103. } else {
  104. activeTab.value = tab;
  105. activeIndex.value = index;
  106. sessionStorage.setItem("activeTabAI", tab);
  107. sessionStorage.setItem("activeIndexAI", index.toString());
  108. }
  109. setActiveTabIndex(index);
  110. console.log(tab, index, "tab, index");
  111. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  112. };
  113. // 修改 activeComponent 的计算逻辑
  114. const activeComponent = computed(() => {
  115. if (isAnnouncementVisible.value) {
  116. return Announcement;
  117. }
  118. if (activeTab.value === "AIchat") {
  119. return AIchat;
  120. } else if (activeTab.value === "AIfind") {
  121. return AIfind;
  122. } else if (activeTab.value === "AiEmotion") {
  123. return AiEmotion; // 新增逻辑
  124. }
  125. });
  126. // 新增一个方法,调用时先判断是否处于 AIchat,若不在则跳转到 AIchat
  127. const ensureAIchat = () => {
  128. setActiveTab("AIchat", 0, true);
  129. };
  130. // 获取次数
  131. const UserCount = computed(() => chatStore.UserCount);
  132. const getCount = () => {
  133. console.log("点击了获取次数的按钮");
  134. };
  135. // 深度思考
  136. const isThinking = ref(true);
  137. const toggleThink = () => {
  138. isThinking.value = !isThinking.value;
  139. };
  140. // 发送消息
  141. const message = ref("");
  142. // 传输对象
  143. const messages = ref([]);
  144. // 信息加载状态
  145. const isLoading = computed(() => {
  146. chatStore.isLoading;
  147. });
  148. // 输入框禁用状态(用于情绪大模型)
  149. const isInputDisabled = ref(false);
  150. // 添加用户消息
  151. const updateMessage = (title) => {
  152. message.value = title;
  153. // console.log("updateMessage 的值:", title);
  154. };
  155. const sendMessage = async () => {
  156. if (
  157. localStorage.getItem("localToken") == null ||
  158. localStorage.getItem("localToken") == ""
  159. ) {
  160. ElMessage.error("请先登录");
  161. return;
  162. }
  163. // 检查输入内容是否为空
  164. if (!message.value || !message.value.trim()) {
  165. ElMessage.warning("输入内容不能为空");
  166. return;
  167. }
  168. isScrolling.value = false;
  169. // 添加到历史记录
  170. if (historyRecordRef.value && message.value.trim()) {
  171. historyRecordRef.value.addRecord(message.value.trim(), activeTab.value);
  172. }
  173. // 判断当前是否为 AiEmotion 组件
  174. if (activeTab.value === "AiEmotion") {
  175. // 禁用输入框
  176. isInputDisabled.value = true;
  177. // 调用 AiEmotion 组件的 handleSendMessage 方法
  178. aiEmotionRef.value?.handleSendMessage(message.value, () => {
  179. // 打字机效果完成后的回调,重新启用输入框
  180. isInputDisabled.value = false;
  181. });
  182. message.value = ""; // 清空输入框
  183. return;
  184. }
  185. // 调用 ensureAIchat 确保跳转到 AIchat 页面
  186. ensureAIchat();
  187. console.log(isInputDisabled.value, "isInputDisabled.value1111");
  188. if (isInputDisabled.value) return;
  189. isInputDisabled.value = true;
  190. console.log(isInputDisabled.value, "isInputDisabled.value2222");
  191. const messageContent = message.value;
  192. // 重置消息输入框
  193. message.value = "";
  194. setTimeout(() => {
  195. console.log("延时后添加消息", messageContent);
  196. // 发送消息时,设置 isLoading 为 true
  197. messages.value = [
  198. ...messages.value,
  199. {
  200. sender: "user",
  201. content: messageContent,
  202. timestamp: new Date().toISOString(),
  203. },
  204. ];
  205. console.log(messages.value, "messages.value");
  206. }, 200);
  207. };
  208. // 重新启用输入框的方法
  209. const enableInput = () => {
  210. isInputDisabled.value = false;
  211. };
  212. // 处理历史记录选择
  213. const handleHistorySelect = (record) => {
  214. // 设置输入框内容
  215. message.value = record.question;
  216. // 如果记录类型与当前激活的tab不同,切换到对应的tab
  217. if (record.type !== activeTab.value) {
  218. const tabIndex = record.type === "AIchat" ? 0 : 1;
  219. setActiveTab(record.type, tabIndex);
  220. }
  221. };
  222. // 公告
  223. // 引入公告组件
  224. import Announcement from "./Announcement.vue";
  225. // 新增一个变量来控制是否显示公告页面
  226. const isAnnouncementVisible = ref(false);
  227. const showAnnouncement = async () => {
  228. console.log("打开公告");
  229. dataStore.isFeedback = false; // 显示用户反馈页面
  230. isScrolling.value = false; //回复滚动到底部方法
  231. setActiveTab("", -1); // 清空当前选中状态
  232. isAnnouncementVisible.value = true; // 显示公告页面
  233. };
  234. // 跳转用户反馈
  235. const showFeedback = () => {
  236. console.log("打开用户反馈");
  237. dataStore.isFeedback = true; // 显示用户反馈页面
  238. };
  239. // 点击剩余次数会弹出的弹窗
  240. // 新增一个 ref 来控制弹窗的显示与隐藏
  241. const dialogVisible = ref(false);
  242. // 获取次数
  243. const showCount = () => {
  244. console.log("显示剩余次数");
  245. // 显示弹窗
  246. dialogVisible.value = true;
  247. console.log("dialogVisible 的值:", dialogVisible.value); // 添加日志确认
  248. };
  249. // 保证发送消息时,滚动屏在底部
  250. const tabContent = ref(null);
  251. const isScrolling = ref(false); //判断用户是否在滚动
  252. const smoothScrollToBottom = async () => {
  253. // console.log("调用滚动到底部的方法");
  254. // await nextTick();
  255. const container = tabContent.value;
  256. // console.log(container, 'container')
  257. // console.log(isScrolling.value, 'isScrolling.value')
  258. if (!container) return;
  259. await nextTick(); // 确保在DOM更新后执行
  260. if (!isScrolling.value) {
  261. container.scrollTop = container.scrollHeight - container.offsetHeight;
  262. // container.scrollTop = container.scrollHeight;
  263. // container.scrollTop = container.offsetHeight;
  264. // container.scrollTop = container.scrollHeight + container.offsetHeight;
  265. // console.log(container.scrollHeight, container.offsetHeight, container.scrollHeight - container.offsetHeight, container.scrollTop, "总长度", "可视长度", "位置")
  266. }
  267. };
  268. const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 300, {
  269. trailing: false,
  270. });
  271. watch(
  272. () => chatStore.messages,
  273. () => {
  274. // console.log('messages变化了')
  275. // 只有在AIchat页面时才执行自动滚动
  276. if (activeTab.value === "AIchat") {
  277. throttledSmoothScrollToBottom();
  278. }
  279. // setTimeout(throttledSmoothScrollToBottom, 100);
  280. },
  281. { deep: true, immediate: true }
  282. );
  283. watch(
  284. activeTab,
  285. async () => {
  286. console.log("activeTab变化了", activeTab.value);
  287. if (activeTab.value === "AIchat") {
  288. isScrolling.value = false; //回复滚动到底部方法
  289. setTimeout(() => {
  290. throttledSmoothScrollToBottom();
  291. }, 100);
  292. }
  293. // AiEmotion页面不执行自动滚动,避免刷新后滚动到底部
  294. // setTimeout(throttledSmoothScrollToBottom, 100);
  295. },
  296. { deep: true, immediate: true }
  297. );
  298. // 获取token的核心函数
  299. const fnGetToken = () => {
  300. // console.log('进入fnGetToken')
  301. window.JWready = (ress) => {
  302. // console.log('进入JWready')
  303. try {
  304. ress = JSON.parse(ress);
  305. // console.log(ress, 'ress')
  306. } catch (error) {
  307. console.log(error, "fnGetToken error");
  308. } //platform为5是app端
  309. // platform.value = ress.data.platform
  310. // 处理平台判断
  311. console.log(ress.data.platform, "ress.data.platform");
  312. if (!ress.data.platform) {
  313. // 非App环境通过URL参数获取
  314. localStorage.setItem(
  315. "localToken",
  316. decodeURIComponent(String(getQueryVariable("token")))
  317. );
  318. // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  319. } else {
  320. // App环境通过桥接获取
  321. useAppBridge().packageFun(
  322. "JWgetStorage",
  323. (response) => {
  324. const res = JSON.parse(response); // 解析返回的结果
  325. localStorage.setItem("localToken", res.data);
  326. // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  327. },
  328. 5,
  329. {
  330. key: "token",
  331. }
  332. );
  333. }
  334. };
  335. // console.log('出来了')
  336. // 触发App桥接
  337. useAppBridge().packageFun("JWwebReady", () => { }, 5, {});
  338. };
  339. // 在setTimeout中延迟执行
  340. setTimeout(() => {
  341. fnGetToken();
  342. }, 800);
  343. const heightListener = () => {
  344. const tabContainer = tabContent.value;
  345. let befortop = 0;
  346. const scrollHandler = () => {
  347. const aftertop = tabContainer.scrollTop;
  348. // 新增底部判断逻辑
  349. const isBottom =
  350. aftertop + tabContainer.offsetHeight + 70 >= tabContainer.scrollHeight;
  351. if (activeTab.value === "AIchat") {
  352. if (aftertop - befortop > 0) {
  353. // console.log('向下滚动');
  354. isScrolling.value = true;
  355. } else {
  356. // console.log('向上滚动');
  357. isScrolling.value = true;
  358. }
  359. // 添加底部状态检测
  360. if (isBottom) {
  361. // console.log('滚动到底部');
  362. isScrolling.value = false;
  363. }
  364. }
  365. befortop = aftertop;
  366. };
  367. // console.log(isScrolling.value, 'isScrolling.value')
  368. tabContainer.addEventListener("scroll", scrollHandler);
  369. };
  370. const throttledHeightListener = _.throttle(heightListener, 500, {
  371. trailing: false,
  372. });
  373. // const goToRecharge = () => {
  374. // console.log("点击充值");
  375. // // http://39.101.133.168:8919/payment/recharge/index?
  376. // // url=http%3A%2F%2Flocalhost%3A8080%2FLiveActivity%2Fpck
  377. // // &platform=1
  378. // // &token=+S4h5QEE1hTIb4CxphrnbZi0+fEeMx8pywnIlrmTmo4QO6IolWnVWu5r+J4rKXMwK41UPfKqyIp+RvWmtM8
  379. // const userAgent = navigator.userAgent.toLowerCase();
  380. // const mobileKeywords = ["mobile", "android", "iphone", "ipad", "ipod"];
  381. // const isMobile = mobileKeywords.some((keyword) =>
  382. // userAgent.includes(keyword)
  383. // );
  384. // console.log(isMobile ? "手机" : "电脑");
  385. // const url = encodeURI("http://39.101.133.168:8857/aixiaocaishen/homePage");
  386. // console.log(url, "url");
  387. // const platform = isMobile ? 2 : 1;
  388. // const token = encodeURIComponent(localStorage.getItem("localToken"));
  389. // console.log(token, "token");
  390. // const rechargeUrl =
  391. // "http://39.101.133.168:8919/payment/recharge/index?" +
  392. // "url=" +
  393. // url +
  394. // "&platform=" +
  395. // platform +
  396. // "&token=" +
  397. // token;
  398. // console.log(rechargeUrl, "rechargeUrl");
  399. // window.location.href = rechargeUrl;
  400. // // window.open(rechargeUrl)
  401. // };
  402. const adjustFooterPosition = (height) => {
  403. const html = document.querySelector("html");
  404. const body = document.querySelector("body");
  405. const isAndroid = /Android/i.test(navigator.userAgent);
  406. if (isAndroid) {
  407. console.log("是安卓设备");
  408. console.log("window.visualViewport", window.visualViewport.height);
  409. const homePage = document.querySelector(".homepage");
  410. homePage.style.height = `${height}px`;
  411. // homePage.style.height = `460px`;
  412. html.scrollTop = 0;
  413. } else {
  414. console.log("非安卓设备");
  415. console.log("调整底部位置", height);
  416. const homePage = document.querySelector(".homepage");
  417. homePage.style.height = `${height}px`;
  418. html.scrollTop = 0;
  419. }
  420. setTimeout(() => {
  421. // 隐藏滚动条
  422. html.style.overflow = "hidden";
  423. body.style.overflow = "hidden";
  424. }, 200);
  425. };
  426. const onFocus = function () {
  427. const visualViewport = window.visualViewport;
  428. // 获取可视区域高度
  429. setTimeout(() => {
  430. console.log("输入框聚焦");
  431. console.log(visualViewport.height, "visualViewport.height");
  432. const keyboardHeight = window.innerHeight - visualViewport.height;
  433. console.log(window.innerHeight, "window.innerHeight");
  434. console.log(keyboardHeight, "keyboardHeight");
  435. adjustFooterPosition(visualViewport.height);
  436. }, 200);
  437. };
  438. const onBlur = function () {
  439. const visualViewport = window.visualViewport;
  440. setTimeout(() => {
  441. console.log("输入框失焦");
  442. const keyboardHeight = window.innerHeight - visualViewport.height;
  443. console.log(window.innerHeight, "window.innerHeight");
  444. console.log(visualViewport.height, "visualViewport.height");
  445. console.log(keyboardHeight, "keyboardHeight");
  446. adjustFooterPosition(visualViewport.height);
  447. }, 200);
  448. };
  449. // window.addEventListener("resize", () => {
  450. // // 检测是否为iOS设备
  451. // const isIOS = /iPhone|iPad|iPod|ios/i.test(navigator.userAgent);
  452. // console.log("是否为iOS设备:", isIOS);
  453. // if (!isIOS) {
  454. // console.log("窗口大小变化");
  455. // const homePage = document.querySelector(".homepage");
  456. // homePage.style.height = `${window.innerHeight}px`;
  457. // }
  458. // });
  459. let touchmoveHandlerRef = null;
  460. const touchmoveHandler = (e) => {
  461. if (!dataStore.isFeedback) {
  462. // 判断触摸目标是否在可滚动区域内
  463. const isScrollableArea = e.target.closest(".tab-content");
  464. // 如果不在可滚动区域,则阻止滚动
  465. if (!isScrollableArea) {
  466. e.preventDefault();
  467. }
  468. }
  469. };
  470. const judgeDevice = async () => {
  471. // 延时300ms
  472. await new Promise((resolve) => setTimeout(resolve, 200));
  473. const userAgent = navigator.userAgent;
  474. isMobile.value =
  475. /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
  476. userAgent
  477. );
  478. console.log("当前设备为:", isMobile.value ? "移动端" : "PC端");
  479. };
  480. const throttledJudgeDevice = _.throttle(judgeDevice, 300, {
  481. trailing: false,
  482. });
  483. const expandHistory = () => {
  484. if (historyRecordRef) {
  485. console.log("存在");
  486. historyRecordRef.value.isCollapsed = !historyRecordRef.value.isCollapsed;
  487. }
  488. };
  489. onMounted(async () => {
  490. throttledJudgeDevice();
  491. // 禁用全局触摸滚动
  492. touchmoveHandlerRef = touchmoveHandler;
  493. document.addEventListener("touchmove", touchmoveHandlerRef, {
  494. passive: false,
  495. });
  496. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  497. // 获取次数
  498. await chatStore.getUserCount();
  499. // 滚动到底部
  500. throttledSmoothScrollToBottom();
  501. // 监听页面高度
  502. throttledHeightListener();
  503. // 添加输入框焦点处理
  504. // handleInputFocus();
  505. // 初始化视口高度变量
  506. // updateAppHeight();
  507. // 添加原生事件监听器
  508. window.addEventListener("resize", throttledJudgeDevice);
  509. });
  510. onUnmounted(() => {
  511. window.removeEventListener("resize", throttledJudgeDevice);
  512. if (touchmoveHandlerRef) {
  513. console.log("卸载touchmoveHandlerRef组件");
  514. document.removeEventListener("touchmove", touchmoveHandlerRef);
  515. }
  516. });
  517. </script>
  518. <template>
  519. <div class="homepage" id="testId">
  520. <!-- 历史记录组件 -->
  521. <HistoryRecord
  522. ref="historyRecordRef"
  523. :current-type="activeTab"
  524. @selectRecord="handleHistorySelect"
  525. :is-mobile="isMobile"
  526. @showAnnouncement="showAnnouncement"
  527. @showFeedback="showFeedback"
  528. />
  529. <div
  530. v-if="isMobile && !historyRecordRef?.isCollapsed"
  531. class="zhezhao"
  532. @click="expandHistory"
  533. ></div>
  534. <el-container
  535. v-if="!dataStore.isFeedback"
  536. class="main-container"
  537. :class="{
  538. collapsed: historyRecordRef?.isCollapsed && !isMobile,
  539. unCollapsed: !historyRecordRef?.isCollapsed && !isMobile,
  540. }"
  541. >
  542. <!-- AI小财神头部 logo 次数 公告 -->
  543. <el-header class="homepage-head">
  544. <!-- logo -->
  545. <div class="homepage-logo" v-if="isMobile">
  546. <img
  547. class="expand"
  548. @click="expandHistory"
  549. src="https://d31zlh4on95l9h.cloudfront.net/images/37fe3d79a8a700f6c674c9f0e7af066b.png"
  550. alt="icon"
  551. />
  552. <img :src="logo" alt="图片加载失败" class="logo1" />
  553. <!-- <img :src="madeInHL" alt="图片加载失败" class="logo2" /> -->
  554. </div>
  555. <div class="homepage-right-group" v-if="isMobile">
  556. <div class="count-badge" @click="showCount">
  557. <img
  558. src="https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png"
  559. class="action-btn"
  560. />
  561. <div class="count-number">{{ UserCount }}</div>
  562. <div class="clickGetCount">点击获取次数</div>
  563. </div>
  564. <!-- <img
  565. :src="announcementBtn"
  566. class="announcement-btn action-btn"
  567. @click="showAnnouncement"
  568. />
  569. <img
  570. :src="feedbackBtn"
  571. class="announcement-btn action-btn"
  572. @click="showFeedback"
  573. /> -->
  574. </div>
  575. </el-header>
  576. <!-- 主体部分小人 问题轮询图 对话内容 -->
  577. <el-main class="homepage-body">
  578. <div class="main-wrapper">
  579. <section class="tab-section">
  580. <div
  581. class="tab-container"
  582. :class="{
  583. pcTabContainer: !isMobile,
  584. }"
  585. >
  586. <div
  587. v-for="(tab, index) in tabs"
  588. :key="tab.name"
  589. @click="setActiveTab(tab.name, index)"
  590. :class="[
  591. 'tab-item',
  592. { active: activeIndex === index && !isAnnouncementVisible },
  593. ]"
  594. >
  595. <span>{{ tab.label }}</span>
  596. </div>
  597. <div v-if="!isMobile" class="pc-count-badge" @click="showCount">
  598. <div class="pc-action-btn">
  599. <div class="pc-count-number">{{ UserCount }}</div>
  600. </div>
  601. <div class="pc-clickGetCount">点击获取次数</div>
  602. </div>
  603. </div>
  604. </section>
  605. <div class="tab-content" ref="tabContent">
  606. <component :is="activeComponent" :messages="messages" @updateMessage="updateMessage"
  607. @sendMessage="sendMessage" @ensureAIchat="ensureAIchat" @enableInput="enableInput" ref="aiEmotionRef" />
  608. </div>
  609. </div>
  610. </el-main>
  611. <!-- 尾部 问题输入框 深度思考 多语言 语音播报 -->
  612. <el-footer class="homepage-footer" id="input">
  613. <!-- 第一行按钮 -->
  614. <div class="footer-first-line">
  615. <div class="left-group">
  616. <!-- <img v-if="isThinking" :src="thinkActive" @click="toggleThink" class="action-btn" />
  617. <img v-else :src="thinkNoActive" @click="toggleThink" class="action-btn" />
  618. <img :src="languageBtn" @click="changeLanguage" class="action-btn" /> -->
  619. <!-- 夺宝奇兵大模型按钮 -->
  620. <img :src="activeTab === 'AIchat' ? dbqbButton01 : dbqbButton02" @click="setActiveTab('AIchat', 0)"
  621. class="action-btn model-btn" alt="夺宝奇兵大模型" />
  622. <!-- AI情绪大模型按钮 -->
  623. <img :src="activeTab === 'AiEmotion' ? emotionButton01 : emotionButton02
  624. " @click="setActiveTab('AiEmotion', 1)" class="action-btn model-btn" alt="AI情绪大模型" />
  625. <!-- <img v-if="
  626. getCurrentAudioStore().isVoiceEnabled &&
  627. getCurrentAudioStore().isPlaying
  628. " :src="voice" @click="toggleVoice" class="action-btn" style="animation: pulse 1.5s infinite" />
  629. <img v-else-if="
  630. getCurrentAudioStore().isVoiceEnabled &&
  631. !getCurrentAudioStore().isPlaying
  632. " :src="voiceNoActive" @click="toggleVoice" class="action-btn" />
  633. <img v-else :src="voiceNoActive" @click="toggleVoice" class="action-btn" /> -->
  634. </div>
  635. </div>
  636. <!-- 第二行输入框 -->
  637. <div class="footer-second-line">
  638. <img :src="msgBtn" class="msg-icon" />
  639. <el-input
  640. type="textarea"
  641. v-model="message"
  642. @focus="onFocus"
  643. @blur="onBlur"
  644. :autosize="{ minRows: 1, maxRows: 4 }"
  645. placeholder="请输入股票名称或股票代码..."
  646. class="msg-input"
  647. @keydown.enter.exact.prevent="
  648. isLoading || isInputDisabled ? null : sendMessage()
  649. "
  650. :disabled="isInputDisabled"
  651. resize="none"
  652. >
  653. </el-input>
  654. <img
  655. :src="sendBtn"
  656. @click="sendMessage"
  657. class="action-btn send-btn"
  658. :style="{
  659. opacity: isInputDisabled ? 0.5 : 1,
  660. cursor: isInputDisabled ? 'not-allowed' : 'pointer',
  661. }"
  662. />
  663. </div>
  664. </el-footer>
  665. </el-container>
  666. <el-container
  667. v-else
  668. class="main-container"
  669. :class="{
  670. collapsed: historyRecordRef?.isCollapsed && !isMobile,
  671. unCollapsed: !historyRecordRef?.isCollapsed && !isMobile,
  672. }"
  673. >
  674. <el-header class="homepage-head">
  675. <!-- logo -->
  676. <div class="homepage-logo">
  677. <img :src="logo" alt="图片加载失败" class="logo1" />
  678. <!-- <img :src="madeInHL" alt="图片加载失败" class="logo2" /> -->
  679. </div>
  680. <div class="homepage-right-group">
  681. <div class="count-badge" @click="showCount">
  682. <img
  683. src="https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png"
  684. class="action-btn"
  685. />
  686. <div class="count-number">{{ UserCount }}</div>
  687. <div class="clickGetCount">点击获取次数</div>
  688. </div>
  689. <!-- <img
  690. :src="announcementBtn"
  691. class="announcement-btn action-btn"
  692. @click="showAnnouncement"
  693. />
  694. <img
  695. :src="feedbackBtn"
  696. class="announcement-btn action-btn"
  697. @click="showFeedback"
  698. /> -->
  699. </div>
  700. </el-header>
  701. <!-- 主体部分小人 问题轮询图 对话内容 -->
  702. <el-main class="homepage-body">
  703. <feedback :is="Feedback" />
  704. </el-main>
  705. </el-container>
  706. <!-- 弹窗 -->
  707. <!-- 新增弹窗组件 -->
  708. <el-dialog v-model="dialogVisible" max-width="65%">
  709. <!-- 自定义标题插槽实现居中显示 -->
  710. <template #header>
  711. <div style="text-align: center">
  712. <span>活动规则</span>
  713. </div>
  714. </template>
  715. <!-- 中间内容部分 -->
  716. <div class="ruleContent">
  717. <p>试运行期间AI小财神可以检索全市场数据</p>
  718. <p>每个市场20支股票股票详情参见公告页面</p>
  719. <!-- <p>弘历会员每人每日拥有10次检索机会</p> -->
  720. </div>
  721. <!-- <template #footer> -->
  722. <!-- 添加一个div来包裹按钮并设置样式使其居中 -->
  723. <!-- <div style="text-align: center"> -->
  724. <!-- <el-button style="background-color: orange; color: white; border: none" @click="goToRecharge"> -->
  725. <!-- 去充值 -->
  726. <!-- </el-button> -->
  727. <!-- </div> -->
  728. <!-- </template> -->
  729. </el-dialog>
  730. </div>
  731. </template>
  732. <style scoped>
  733. /* 标签栏 */
  734. .tab-container {
  735. display: flex;
  736. gap: 30px;
  737. margin-bottom: 10px;
  738. height: 100%;
  739. position: relative;
  740. /* 新增右对齐 */
  741. }
  742. .pcTabContainer {
  743. margin-left: 40px;
  744. justify-content: center;
  745. align-items: center;
  746. gap: 25vw;
  747. }
  748. .tab-item {
  749. cursor: pointer;
  750. padding: 8px 12px;
  751. font-size: clamp(18px, 3vw, 20px);
  752. /* color: #999; */
  753. color: #fff;
  754. transition: all 0.3s;
  755. border-bottom: 2px solid transparent;
  756. font-weight: bold;
  757. }
  758. .tab-item.active {
  759. /* color: #000;
  760. border-color: #000; */
  761. background: linear-gradient(0deg, #ffffff, #fec13e);
  762. -webkit-background-clip: text;
  763. background-clip: text;
  764. -webkit-text-fill-color: transparent;
  765. color: transparent;
  766. border-color: #fec13e;
  767. }
  768. .tab-item:not(.active):hover {
  769. color: #999999;
  770. }
  771. .tab-content {
  772. overflow-y: auto;
  773. overflow-x: hidden;
  774. scroll-behavior: smooth;
  775. height: 100%;
  776. /* 添加平滑滚动效果 */
  777. }
  778. @media (max-width: 768px) {
  779. .tab-container {
  780. gap: 15px;
  781. padding: 0 10px;
  782. }
  783. .tab-item {
  784. font-size: clamp(14px, 3vw, 16px);
  785. padding: 6px 10px;
  786. }
  787. }
  788. </style>
  789. <style scoped>
  790. html {
  791. height: 100dvh;
  792. overflow: hidden !important;
  793. position: fixed;
  794. margin: 0;
  795. padding: 0;
  796. -webkit-overflow-scrolling: auto;
  797. /* 禁用 iOS 弹性滚动 */
  798. }
  799. body {
  800. height: 100dvh;
  801. overflow: clip;
  802. margin: 0;
  803. padding: 0;
  804. -webkit-overflow-scrolling: auto;
  805. /* 禁用 iOS 弹性滚动 */
  806. position: fixed;
  807. }
  808. #app {
  809. overflow: hidden;
  810. height: 100%;
  811. margin: 0;
  812. padding: 0;
  813. }
  814. .homepage {
  815. /* height: var(--app-height, 100vh); */
  816. height: var(--app-height, 100vh);
  817. margin: 0 auto;
  818. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/2dc3c13a74100b906e809d26b66db211.png");
  819. background-size: 100% 100%;
  820. background-repeat: no-repeat;
  821. background-position: center;
  822. display: flex;
  823. flex-direction: row;
  824. /* 改为水平布局 */
  825. overflow: hidden;
  826. position: fixed;
  827. top: 0;
  828. left: 0;
  829. right: 0;
  830. bottom: 0;
  831. width: 100%;
  832. /* -webkit-overflow-scrolling: touch; */
  833. }
  834. .main-container {
  835. flex: 1;
  836. transition: margin-left 0.3s ease;
  837. display: flex;
  838. flex-direction: column;
  839. overflow: hidden;
  840. }
  841. .main-container.unCollapsed {
  842. margin-left: 300px; /* 为历史记录组件留出空间 */
  843. }
  844. /* 当历史记录组件折叠时调整主容器边距 */
  845. .main-container.collapsed {
  846. margin-left: 40px;
  847. }
  848. .zhezhao {
  849. width: 100%;
  850. height: 100%;
  851. background-color: rgba(0, 0, 0, 0.5);
  852. z-index: 100;
  853. position: fixed;
  854. }
  855. /* 移动端适配 */
  856. @media (max-width: 768px) {
  857. .homepage {
  858. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/90d31d7052e729c63acb9e2cb94d1307.png");
  859. }
  860. .main-container {
  861. /* margin-left: 280px; */
  862. }
  863. .main-container.unCollapsed {
  864. margin-left: 280px;
  865. }
  866. .main-container.collapsed {
  867. margin-left: 40px;
  868. }
  869. }
  870. .homepage .el-container {
  871. height: 100%;
  872. flex-direction: column;
  873. display: flex;
  874. width: 100%;
  875. overflow: hidden;
  876. /* 防止容器滚动 */
  877. }
  878. .el-container .el-header {
  879. flex-shrink: 0;
  880. /* 防止头部压缩 */
  881. height: auto;
  882. min-height: 60px;
  883. padding: 5px 0;
  884. position: sticky;
  885. top: 0;
  886. z-index: 10;
  887. /* background-color: rgba(255, 255, 255, 0.9); */
  888. }
  889. .el-container .el-main {
  890. flex: 1;
  891. /* 自动占据剩余空间 */
  892. overflow: hidden;
  893. /* 主容器不滚动 */
  894. display: flex;
  895. flex-direction: column;
  896. min-height: 0;
  897. /* 允许内容区域缩小 */
  898. position: relative;
  899. height: auto;
  900. }
  901. .el-container .el-footer {
  902. flex-shrink: 0;
  903. height: auto;
  904. min-height: 70px;
  905. position: sticky;
  906. bottom: 0;
  907. z-index: 20;
  908. background-color: rgba(211, 24, 24, 0);
  909. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  910. -webkit-transform: translateZ(0);
  911. transform: translateZ(0);
  912. padding-bottom: env(safe-area-inset-bottom, 0);
  913. /* 适配iPhone X及以上的底部安全区域 */
  914. }
  915. .homepage-head {
  916. padding: 0px;
  917. display: flex;
  918. position: relative;
  919. justify-content: space-between;
  920. width: 100%;
  921. }
  922. .homepage-right-group {
  923. display: flex;
  924. gap: 8px;
  925. align-items: center;
  926. margin-left: auto;
  927. margin-right: 20px;
  928. }
  929. .homepage-right-group .action-btn {
  930. height: 40px;
  931. }
  932. .count-badge {
  933. position: relative;
  934. cursor: pointer;
  935. }
  936. .count-badge:hover {
  937. transform: scale(1.05);
  938. }
  939. .count-number {
  940. position: absolute;
  941. top: 16px;
  942. right: 0px;
  943. width: 68%;
  944. text-align: center;
  945. color: #6a00ff;
  946. font-size: 14px;
  947. font-weight: bold;
  948. }
  949. .clickGetCount {
  950. width: 100%;
  951. text-align: center;
  952. color: white;
  953. font-size: 12px;
  954. }
  955. .pc-count-badge {
  956. width: 120px;
  957. height: 100%;
  958. margin-left: auto;
  959. margin-right: 20px;
  960. position: absolute;
  961. right: 20px;
  962. }
  963. .pc-count-badge:hover {
  964. transform: scale(1.05);
  965. }
  966. .pc-action-btn {
  967. width: 100%;
  968. height: 70%;
  969. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png");
  970. background-repeat: no-repeat;
  971. background-size: 100% 100%;
  972. }
  973. .pc-count-number {
  974. position: absolute;
  975. top: 15px;
  976. right: 4px;
  977. width: 68%;
  978. text-align: center;
  979. color: #6a00ff;
  980. font-size: 15px;
  981. font-weight: bold;
  982. }
  983. .pc-clickGetCount {
  984. width: 100%;
  985. text-align: center;
  986. color: white;
  987. font-size: 12px;
  988. }
  989. .homepage-right-group .announcement-btn {
  990. cursor: pointer;
  991. transition: transform 0.3s;
  992. }
  993. .homepage-right-group .announcement-btn:hover {
  994. transform: scale(1.3);
  995. }
  996. .homepage-body {
  997. padding: 0px;
  998. display: flex;
  999. flex-direction: column;
  1000. flex: 1;
  1001. min-height: 0;
  1002. /* 允许内容区域缩小 */
  1003. overflow: hidden;
  1004. }
  1005. .main-wrapper {
  1006. height: 100%;
  1007. display: flex;
  1008. flex-direction: column;
  1009. flex: 1;
  1010. min-height: 0;
  1011. /* 允许内容区域缩小 */
  1012. }
  1013. .tab-section {
  1014. flex-shrink: 0;
  1015. /* 禁止伸缩 */
  1016. }
  1017. .tab-content {
  1018. flex: 1;
  1019. overflow-y: auto;
  1020. min-height: 0;
  1021. /* 关键:允许内容收缩 */
  1022. }
  1023. .homepage-logo {
  1024. height: 100%;
  1025. width: fit-content;
  1026. display: flex;
  1027. /* flex-direction: column; */
  1028. align-items: center;
  1029. justify-content: center;
  1030. margin-left: 20px;
  1031. margin-right: auto;
  1032. position: relative;
  1033. gap: 10px;
  1034. }
  1035. .expand {
  1036. font-size: 2.5rem;
  1037. cursor: pointer;
  1038. color: white;
  1039. }
  1040. .logo1 {
  1041. width: 110px;
  1042. height: auto;
  1043. margin-bottom: 8px;
  1044. }
  1045. .logo2 {
  1046. width: 80px;
  1047. height: auto;
  1048. }
  1049. /* 尾部 */
  1050. .homepage-footer {
  1051. display: flex;
  1052. flex-direction: column;
  1053. gap: 5px;
  1054. flex-shrink: 0;
  1055. width: 100%;
  1056. background-color: #fff;
  1057. }
  1058. .footer-first-line {
  1059. display: flex;
  1060. justify-content: space-between;
  1061. align-items: center;
  1062. padding: 5px 15px;
  1063. flex-shrink: 0;
  1064. }
  1065. .left-group {
  1066. display: flex;
  1067. gap: 15px;
  1068. }
  1069. .action-btn {
  1070. cursor: pointer;
  1071. transition: transform 0.2s;
  1072. height: 28px;
  1073. }
  1074. .model-btn {
  1075. height: 32px;
  1076. transition: all 0.3s ease;
  1077. }
  1078. .model-btn:hover {
  1079. transform: scale(1.1);
  1080. }
  1081. .send-btn {
  1082. margin-left: 10px;
  1083. height: 33px !important;
  1084. width: auto;
  1085. /* margin-right: 5px; */
  1086. }
  1087. /* 音频播放动画 */
  1088. @keyframes pulse {
  1089. 0% {
  1090. transform: scale(1);
  1091. }
  1092. 50% {
  1093. transform: scale(1.1);
  1094. }
  1095. 100% {
  1096. transform: scale(1);
  1097. }
  1098. }
  1099. .footer-second-line {
  1100. position: relative;
  1101. display: flex;
  1102. align-items: center;
  1103. padding: 5px 15px 10px;
  1104. flex-shrink: 0;
  1105. }
  1106. .msg-icon {
  1107. position: absolute;
  1108. left: 25px;
  1109. top: 50%;
  1110. transform: translateY(-50%);
  1111. width: 24px;
  1112. z-index: 999;
  1113. }
  1114. .msg-input:deep(.el-textarea__inner) {
  1115. border: none !important;
  1116. box-shadow: none !important;
  1117. overflow-y: auto !important;
  1118. transition: all 0.2s ease-out;
  1119. padding: 8px 20px 8px 45px !important;
  1120. resize: none !important;
  1121. line-height: 1.5 !important;
  1122. max-height: 100px !important;
  1123. }
  1124. .msg-input:deep(.el-textarea__inner::placeholder) {
  1125. white-space: nowrap !important;
  1126. overflow: hidden !important;
  1127. text-overflow: ellipsis !important;
  1128. }
  1129. .msg-input {
  1130. min-height: 34px;
  1131. width: 100%;
  1132. border-radius: 20px;
  1133. font-size: 16px;
  1134. transition: all 0.3s ease-out;
  1135. overflow-y: hidden;
  1136. box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3);
  1137. background: #fff;
  1138. z-index: 5;
  1139. /* 添加iOS设备特殊处理 */
  1140. -webkit-appearance: none;
  1141. appearance: none;
  1142. }
  1143. .msg-input:focus {
  1144. outline: none;
  1145. }
  1146. @media (max-width: 768px) {
  1147. .action-btn {
  1148. height: 21px;
  1149. }
  1150. .footer-second-line {
  1151. padding: 5px 10px 10px;
  1152. }
  1153. .msg-input {
  1154. /* min-height: 44px; */
  1155. /* height: 44px; */
  1156. font-size: 16px;
  1157. }
  1158. }
  1159. .ruleContent {
  1160. text-align: center;
  1161. }
  1162. </style>