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.

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