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.

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