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.

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