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.

1146 lines
30 KiB

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