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.

2100 lines
54 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
1 week 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. console.log("tab", tab, "index", index);
  99. if (tab == "AIchat") {
  100. isInputDisabled.value = chatStore.chatInput;
  101. console.log("切换到AIchat页面,输入框状态为", isInputDisabled.value);
  102. } else if (tab == "AiEmotion") {
  103. isInputDisabled.value = chatStore.emotionInput;
  104. console.log("切换到AiEmotion页面,输入框状态为", isInputDisabled.value);
  105. } else {
  106. isInputDisabled.value = false;
  107. }
  108. if (forceAIchat && activeTab.value !== "AIchat") {
  109. activeTab.value = "AIchat";
  110. activeIndex.value = 0;
  111. sessionStorage.setItem("activeTabAI", "AIchat");
  112. sessionStorage.setItem("activeIndexAI", "0");
  113. } else {
  114. activeTab.value = tab;
  115. activeIndex.value = index;
  116. sessionStorage.setItem("activeTabAI", tab);
  117. sessionStorage.setItem("activeIndexAI", index.toString());
  118. }
  119. setActiveTabIndex(index);
  120. console.log(tab, index, "tab, index");
  121. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  122. };
  123. // 修改 activeComponent 的计算逻辑
  124. const activeComponent = computed(() => {
  125. if (activeTab.value === "AIchat") {
  126. return AIchat;
  127. } else if (activeTab.value === "AIfind") {
  128. return AIfind;
  129. } else if (activeTab.value === "AiEmotion") {
  130. return AiEmotion; // 新增逻辑
  131. }
  132. });
  133. const activeTwoTab = computed(() => {
  134. if (isAnnouncementVisible.value) {
  135. return Announcement;
  136. } else {
  137. return Feedback;
  138. }
  139. });
  140. // 新增一个方法,调用时先判断是否处于 AIchat,若不在则跳转到 AIchat
  141. const ensureAIchat = () => {
  142. setActiveTab("AIchat", 0, true);
  143. };
  144. // 获取次数
  145. const UserCount = computed(() => chatStore.UserCount);
  146. const getCount = () => {
  147. console.log("点击了获取次数的按钮");
  148. };
  149. // 深度思考
  150. const isThinking = ref(true);
  151. const toggleThink = () => {
  152. isThinking.value = !isThinking.value;
  153. };
  154. // 发送消息
  155. const message = ref("");
  156. // 传输对象
  157. const messages = ref([]);
  158. // 信息加载状态
  159. const isLoading = computed(() => {
  160. chatStore.isLoading;
  161. });
  162. // 输入框禁用状态
  163. const isInputDisabled = ref(false);
  164. // 添加用户消息
  165. const updateMessage = (title) => {
  166. message.value = title;
  167. console.log("updateMessage 的值:", title);
  168. };
  169. watch(
  170. () => chatStore.announcementMsg,
  171. (newVal) => {
  172. console.log("监听到公告改变", chatStore.announcementMsg);
  173. if (chatStore.announcementMsg && !isInputDisabled.value) {
  174. message.value = chatStore.announcementMsg;
  175. chatStore.announcementMsg = null;
  176. }
  177. }
  178. );
  179. watch(
  180. () => dataStore.isFeedback,
  181. async (newVal) => {
  182. if (!dataStore.isFeedback) {
  183. // 重置公告页面显示状态
  184. isAnnouncementVisible.value = false;
  185. await nextTick();
  186. // 监听页面高度
  187. throttledHeightListener();
  188. }
  189. }
  190. );
  191. watch(
  192. () => chatStore.chatInput,
  193. async (newVal) => {
  194. if (activeTab.value == "AIchat") {
  195. isInputDisabled.value = chatStore.chatInput;
  196. }
  197. }
  198. );
  199. watch(
  200. () => chatStore.emotionInput,
  201. async (newVal) => {
  202. if (activeTab.value == "AiEmotion") {
  203. isInputDisabled.value = chatStore.emotionInput;
  204. }
  205. }
  206. );
  207. const sendMessage = async () => {
  208. if (
  209. localStorage.getItem("localToken") == null ||
  210. localStorage.getItem("localToken") == ""
  211. ) {
  212. ElMessage.error("请先登录");
  213. return;
  214. }
  215. // 检查输入内容是否为空
  216. if (!message.value || !message.value.trim()) {
  217. ElMessage.warning("输入内容不能为空");
  218. return;
  219. }
  220. isScrolling.value = false;
  221. // 注意:历史记录会在消息发送后自动更新,无需手动添加
  222. // 取消历史记录选中状态
  223. if (historyRecordRef) {
  224. historyRecordRef.value.selectedRecordId = null;
  225. }
  226. // 判断当前是否为 AiEmotion 组件
  227. if (activeTab.value === "AiEmotion") {
  228. // 禁用输入框
  229. isInputDisabled.value = true;
  230. chatStore.emotionInput = true;
  231. // 调用 AiEmotion 组件的 handleSendMessage 方法
  232. aiEmotionRef.value?.handleSendMessage(message.value, () => {
  233. // 打字机效果完成后的回调,重新启用输入框
  234. isInputDisabled.value = false;
  235. chatStore.emotionInput = false;
  236. });
  237. message.value = ""; // 清空输入框
  238. return;
  239. }
  240. // 调用 ensureAIchat 确保跳转到 AIchat 页面
  241. ensureAIchat();
  242. if (isInputDisabled.value) return;
  243. isInputDisabled.value = true;
  244. chatStore.chatInput = true;
  245. const messageContent = message.value;
  246. // 重置消息输入框
  247. message.value = "";
  248. setTimeout(() => {
  249. console.log("延时后添加消息", messageContent);
  250. // 发送消息时,设置 isLoading 为 true
  251. messages.value = [
  252. ...messages.value,
  253. {
  254. sender: "user",
  255. content: messageContent,
  256. audioArray: [],
  257. audioStatus: false,
  258. },
  259. ];
  260. console.log(messages.value, "messages.value");
  261. }, 200);
  262. };
  263. // 重新启用输入框的方法
  264. const enableInput = () => {
  265. console.log("解除禁用");
  266. isInputDisabled.value = false;
  267. };
  268. // 处理历史记录选择
  269. const handleHistorySelect = (stockData) => {
  270. console.log("接收到历史记录数据:", stockData);
  271. // 如果当前不在AiEmotion页面,切换到AiEmotion页面
  272. // if (activeTab.value !== 'AiEmotion') {
  273. // setActiveTab('AiEmotion', 1);
  274. // }
  275. // 等待组件渲染完成后调用addStock方法
  276. nextTick(() => {
  277. if (aiEmotionRef.value && aiEmotionRef.value.addStock) {
  278. aiEmotionRef.value.addStock(stockData);
  279. } else {
  280. console.error("AiEmotion组件或addStock方法不可用");
  281. }
  282. });
  283. };
  284. // 公告
  285. // 新增一个变量来控制是否显示公告页面
  286. const isAnnouncementVisible = ref(false);
  287. const showAnnouncement = async () => {
  288. console.log("打开公告");
  289. dataStore.isFeedback = true; // 显示用户反馈页面
  290. isScrolling.value = false; //回复滚动到底部方法
  291. isAnnouncementVisible.value = true; // 显示公告页面
  292. if (isMobile.value) {
  293. if (historyRecordRef) {
  294. historyRecordRef.value.isCollapsed = true;
  295. }
  296. }
  297. };
  298. // 跳转用户反馈
  299. const showFeedback = () => {
  300. console.log("打开用户反馈");
  301. dataStore.isFeedback = true; // 显示用户反馈页面
  302. isAnnouncementVisible.value = false; // 显示反馈页面
  303. if (isMobile.value) {
  304. if (historyRecordRef) {
  305. historyRecordRef.value.isCollapsed = true;
  306. }
  307. }
  308. };
  309. // 点击剩余次数会弹出的弹窗
  310. // 新增一个 ref 来控制弹窗的显示与隐藏
  311. const dialogVisible = ref(false);
  312. // 获取次数
  313. const showCount = () => {
  314. console.log("显示剩余次数");
  315. // 显示弹窗
  316. dialogVisible.value = true;
  317. console.log("dialogVisible 的值:", dialogVisible.value); // 添加日志确认
  318. };
  319. // 保证发送消息时,滚动屏在底部
  320. const tabContentAIchat = ref(null);
  321. const tabContentAiEmotion = ref(null);
  322. const isScrolling = ref(false); //判断用户是否在滚动
  323. // AiEmotion页面高度监听器相关变量
  324. const aiEmotionHeightObserver = ref(null);
  325. const isAiEmotionAutoScrollEnabled = ref(false);
  326. const isAiEmotionUserScrolling = ref(false); // 用户是否正在手动滚动
  327. const aiEmotionScrollTimer = ref(null); // 滚动检测定时器
  328. // 获取当前活动页面的滚动容器
  329. const getCurrentScrollContainer = () => {
  330. if (activeTab.value === 'AIchat') {
  331. return tabContentAIchat.value;
  332. } else if (activeTab.value === 'AiEmotion') {
  333. return tabContentAiEmotion.value;
  334. }
  335. return null;
  336. };
  337. const smoothScrollToBottom = async () => {
  338. // console.log("调用滚动到底部的方法");
  339. // await nextTick();
  340. const container = getCurrentScrollContainer();
  341. // console.log(container, 'container')
  342. // console.log(isScrolling.value, 'isScrolling.value')
  343. if (!container) return;
  344. await nextTick(); // 确保在DOM更新后执行
  345. if (!isScrolling.value) {
  346. container.scrollTop = container.scrollHeight - container.offsetHeight;
  347. // container.scrollTop = container.scrollHeight;
  348. // container.scrollTop = container.offsetHeight;
  349. // container.scrollTop = container.scrollHeight + container.offsetHeight;
  350. // console.log(container.scrollHeight, container.offsetHeight, container.scrollHeight - container.offsetHeight, container.scrollTop, "总长度", "可视长度", "位置")
  351. }
  352. };
  353. const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 300, {
  354. trailing: false,
  355. });
  356. // AiEmotion页面自动滚动到底部的防抖函数
  357. const debouncedAiEmotionScrollToBottom = _.debounce(() => {
  358. if (activeTab.value === 'AiEmotion' && isAiEmotionAutoScrollEnabled.value && !isAiEmotionUserScrolling.value) {
  359. const container = tabContentAiEmotion.value;
  360. if (container) {
  361. container.scrollTop = container.scrollHeight - container.offsetHeight;
  362. }
  363. }
  364. }, 150);
  365. // 启动AiEmotion页面高度监听器
  366. const startAiEmotionHeightObserver = () => {
  367. // 先停止之前的监听器
  368. stopAiEmotionHeightObserver();
  369. isAiEmotionAutoScrollEnabled.value = true;
  370. // 创建ResizeObserver监听页面内容变化
  371. aiEmotionHeightObserver.value = new ResizeObserver((entries) => {
  372. if (isAiEmotionAutoScrollEnabled.value && activeTab.value === 'AiEmotion') {
  373. debouncedAiEmotionScrollToBottom();
  374. }
  375. });
  376. // 监听document.body的尺寸变化
  377. if (document.body) {
  378. aiEmotionHeightObserver.value.observe(document.body);
  379. }
  380. // 创建MutationObserver监听DOM结构变化
  381. const mutationObserver = new MutationObserver((mutations) => {
  382. let shouldScroll = false;
  383. mutations.forEach((mutation) => {
  384. if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
  385. // 检查新增的节点是否包含实际内容
  386. const hasContent = Array.from(mutation.addedNodes).some((node) => {
  387. if (node.nodeType === Node.ELEMENT_NODE) {
  388. return node.offsetHeight > 0 || node.scrollHeight > 0;
  389. }
  390. return (
  391. node.nodeType === Node.TEXT_NODE &&
  392. node.textContent.trim().length > 0
  393. );
  394. });
  395. if (hasContent) {
  396. shouldScroll = true;
  397. }
  398. }
  399. });
  400. if (shouldScroll && isAiEmotionAutoScrollEnabled.value && activeTab.value === 'AiEmotion') {
  401. debouncedAiEmotionScrollToBottom();
  402. }
  403. });
  404. // 监听AiEmotion页面的主要内容区域的DOM变化
  405. const aiEmotionContainer = tabContentAiEmotion.value;
  406. if (aiEmotionContainer) {
  407. mutationObserver.observe(aiEmotionContainer, {
  408. childList: true,
  409. subtree: true,
  410. attributes: false,
  411. characterData: true,
  412. });
  413. }
  414. // 保存mutationObserver引用以便清理
  415. aiEmotionHeightObserver.value.mutationObserver = mutationObserver;
  416. // 为AiEmotion页面的滚动容器添加滚动事件监听器
  417. if (aiEmotionContainer) {
  418. aiEmotionContainer.addEventListener('scroll', handleAiEmotionUserScroll, { passive: true });
  419. // 保存滚动事件监听器引用以便清理
  420. aiEmotionHeightObserver.value.scrollListener = handleAiEmotionUserScroll;
  421. }
  422. console.log("AiEmotion页面高度监听器已启动");
  423. };
  424. // AiEmotion页面用户滚动检测
  425. const handleAiEmotionUserScroll = () => {
  426. // 标记用户正在滚动
  427. isAiEmotionUserScrolling.value = true;
  428. // 清除之前的定时器
  429. if (aiEmotionScrollTimer.value) {
  430. clearTimeout(aiEmotionScrollTimer.value);
  431. }
  432. // 设置定时器,2秒后恢复自动滚动
  433. // aiEmotionScrollTimer.value = setTimeout(() => {
  434. // isAiEmotionUserScrolling.value = false;
  435. // console.log("AiEmotion页面用户滚动检测:恢复自动滚动");
  436. // }, 2000);
  437. };
  438. // 处理AiEmotion页面的滚动请求
  439. const handleAiEmotionScrollToBottom = () => {
  440. if (activeTab.value === 'AiEmotion') {
  441. const container = tabContentAiEmotion.value;
  442. if (container) {
  443. // 使用nextTick确保DOM已更新
  444. nextTick(() => {
  445. container.scrollTop = container.scrollHeight - container.offsetHeight;
  446. console.log("AiEmotion页面:执行容器滚动到底部");
  447. });
  448. }
  449. }
  450. };
  451. // 停止AiEmotion页面高度监听器
  452. const stopAiEmotionHeightObserver = () => {
  453. isAiEmotionAutoScrollEnabled.value = false;
  454. isAiEmotionUserScrolling.value = false;
  455. // 清理滚动检测定时器
  456. if (aiEmotionScrollTimer.value) {
  457. clearTimeout(aiEmotionScrollTimer.value);
  458. aiEmotionScrollTimer.value = null;
  459. }
  460. if (aiEmotionHeightObserver.value) {
  461. // 清理ResizeObserver
  462. aiEmotionHeightObserver.value.disconnect();
  463. // 清理MutationObserver
  464. if (aiEmotionHeightObserver.value.mutationObserver) {
  465. aiEmotionHeightObserver.value.mutationObserver.disconnect();
  466. aiEmotionHeightObserver.value.mutationObserver = null;
  467. }
  468. // 清理滚动事件监听器
  469. if (aiEmotionHeightObserver.value.scrollListener && tabContentAiEmotion.value) {
  470. tabContentAiEmotion.value.removeEventListener('scroll', aiEmotionHeightObserver.value.scrollListener);
  471. aiEmotionHeightObserver.value.scrollListener = null;
  472. }
  473. aiEmotionHeightObserver.value = null;
  474. }
  475. console.log("AiEmotion页面高度监听器已停止");
  476. };
  477. watch(
  478. () => chatStore.messages.length,
  479. () => {
  480. // console.log('messages变化了')
  481. // 只有在AIchat页面时才执行自动滚动
  482. if (activeTab.value === "AIchat") {
  483. throttledSmoothScrollToBottom();
  484. }
  485. // setTimeout(throttledSmoothScrollToBottom, 100);
  486. },
  487. { deep: false, immediate: true }
  488. );
  489. watch(
  490. () => chatStore.dbqbClickRecord,
  491. async (newValue, oldValue) => {
  492. const container = getCurrentScrollContainer();
  493. if (!container) return;
  494. await nextTick(); // 确保在DOM更新后执行
  495. container.scrollTop = 0;
  496. }
  497. );
  498. watch(
  499. activeTab,
  500. async () => {
  501. console.log("activeTab变化了", activeTab.value);
  502. if (activeTab.value == "AIchat" || activeTab.value == "AiEmotion") {
  503. if (historyRecordRef.value && historyRecordRef.value.getHistoryList) {
  504. const result = historyRecordRef.value.getHistoryList({
  505. model: activeTab.value == "AIchat" ? 1 : 2,
  506. token: localStorage.getItem("localToken"),
  507. });
  508. }
  509. }
  510. if (activeTab.value === "AIchat") {
  511. isScrolling.value = false; //回复滚动到底部方法
  512. // 停止AiEmotion页面的高度监听器
  513. stopAiEmotionHeightObserver();
  514. setTimeout(() => {
  515. // throttledSmoothScrollToBottom();
  516. }, 100);
  517. } else if (activeTab.value === "AiEmotion") {
  518. // 启动AiEmotion页面的高度监听器
  519. await nextTick(); // 确保DOM更新后启动监听器
  520. startAiEmotionHeightObserver();
  521. } else {
  522. // 其他页面时停止AiEmotion页面的高度监听器
  523. stopAiEmotionHeightObserver();
  524. }
  525. // AiEmotion页面不执行自动滚动,避免刷新后滚动到底部
  526. // setTimeout(throttledSmoothScrollToBottom, 100);
  527. },
  528. { deep: true, immediate: true }
  529. );
  530. // 获取token的核心函数
  531. const fnGetToken = () => {
  532. // console.log('进入fnGetToken')
  533. window.JWready = (ress) => {
  534. // console.log('进入JWready')
  535. try {
  536. ress = JSON.parse(ress);
  537. // console.log(ress, 'ress')
  538. } catch (error) {
  539. console.log(error, "fnGetToken error");
  540. } //platform为5是app端
  541. // platform.value = ress.data.platform
  542. // 处理平台判断
  543. console.log(ress.data.platform, "ress.data.platform");
  544. if (!ress.data.platform) {
  545. // 非App环境通过URL参数获取
  546. localStorage.setItem(
  547. "localToken",
  548. decodeURIComponent(String(getQueryVariable("token")))
  549. );
  550. // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  551. } else {
  552. // App环境通过桥接获取
  553. useAppBridge().packageFun(
  554. "JWgetStorage",
  555. (response) => {
  556. const res = JSON.parse(response); // 解析返回的结果
  557. localStorage.setItem("localToken", res.data);
  558. // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  559. },
  560. 5,
  561. {
  562. key: "token",
  563. }
  564. );
  565. }
  566. };
  567. // console.log('出来了')
  568. // 触发App桥接
  569. useAppBridge().packageFun("JWwebReady", () => {}, 5, {});
  570. };
  571. // 在setTimeout中延迟执行
  572. setTimeout(() => {
  573. fnGetToken();
  574. }, 800);
  575. const heightListener = () => {
  576. const tabContainer = getCurrentScrollContainer();
  577. if (!tabContainer) return;
  578. let befortop = 0;
  579. const scrollHandler = () => {
  580. const aftertop = tabContainer.scrollTop;
  581. // 新增底部判断逻辑
  582. const isBottom =
  583. aftertop + tabContainer.offsetHeight + 70 >= tabContainer.scrollHeight;
  584. if (activeTab.value === "AIchat") {
  585. if (aftertop - befortop > 0) {
  586. // console.log("向下滚动");
  587. isScrolling.value = true;
  588. } else {
  589. // console.log("向上滚动");
  590. isScrolling.value = true;
  591. }
  592. // 添加底部状态检测
  593. if (isBottom) {
  594. // console.log("滚动到底部");
  595. isScrolling.value = false;
  596. }
  597. }
  598. befortop = aftertop;
  599. };
  600. // console.log(isScrolling.value, 'isScrolling.value')
  601. tabContainer.addEventListener("scroll", scrollHandler);
  602. };
  603. const throttledHeightListener = _.throttle(heightListener, 500, {
  604. trailing: false,
  605. });
  606. // const goToRecharge = () => {
  607. // console.log("点击充值");
  608. // // http://39.101.133.168:8919/payment/recharge/index?
  609. // // url=http%3A%2F%2Flocalhost%3A8080%2FLiveActivity%2Fpck
  610. // // &platform=1
  611. // // &token=+S4h5QEE1hTIb4CxphrnbZi0+fEeMx8pywnIlrmTmo4QO6IolWnVWu5r+J4rKXMwK41UPfKqyIp+RvWmtM8
  612. // const userAgent = navigator.userAgent.toLowerCase();
  613. // const mobileKeywords = ["mobile", "android", "iphone", "ipad", "ipod"];
  614. // const isMobile = mobileKeywords.some((keyword) =>
  615. // userAgent.includes(keyword)
  616. // );
  617. // console.log(isMobile ? "手机" : "电脑");
  618. // const url = encodeURI("http://39.101.133.168:8857/aixiaocaishen/homePage");
  619. // console.log(url, "url");
  620. // const platform = isMobile ? 2 : 1;
  621. // const token = encodeURIComponent(localStorage.getItem("localToken"));
  622. // console.log(token, "token");
  623. // const rechargeUrl =
  624. // "http://39.101.133.168:8919/payment/recharge/index?" +
  625. // "url=" +
  626. // url +
  627. // "&platform=" +
  628. // platform +
  629. // "&token=" +
  630. // token;
  631. // console.log(rechargeUrl, "rechargeUrl");
  632. // window.location.href = rechargeUrl;
  633. // // window.open(rechargeUrl)
  634. // };
  635. const adjustFooterPosition = (height) => {
  636. const html = document.querySelector("html");
  637. const body = document.querySelector("body");
  638. const isAndroid = /Android/i.test(navigator.userAgent);
  639. if (isAndroid) {
  640. console.log("是安卓设备");
  641. console.log("window.visualViewport", window.visualViewport.height);
  642. const homePage = document.querySelector(".homepage");
  643. homePage.style.height = `${height}px`;
  644. // homePage.style.height = `460px`;
  645. html.scrollTop = 0;
  646. } else {
  647. console.log("非安卓设备");
  648. console.log("调整底部位置", height);
  649. const homePage = document.querySelector(".homepage");
  650. homePage.style.height = `${height}px`;
  651. html.scrollTop = 0;
  652. }
  653. setTimeout(() => {
  654. // 隐藏滚动条
  655. html.style.overflow = "hidden";
  656. body.style.overflow = "hidden";
  657. }, 200);
  658. };
  659. // 是否正在输入法组合
  660. const inputing = ref(false);
  661. const onFocus = function () {
  662. const visualViewport = window.visualViewport;
  663. // 获取可视区域高度
  664. setTimeout(() => {
  665. console.log("输入框聚焦");
  666. console.log(visualViewport.height, "visualViewport.height");
  667. const keyboardHeight = window.innerHeight - visualViewport.height;
  668. console.log(window.innerHeight, "window.innerHeight");
  669. console.log(keyboardHeight, "keyboardHeight");
  670. adjustFooterPosition(visualViewport.height);
  671. }, 200);
  672. };
  673. const onBlur = function () {
  674. inputing.value = false;
  675. const visualViewport = window.visualViewport;
  676. setTimeout(() => {
  677. console.log("输入框失焦");
  678. const keyboardHeight = window.innerHeight - visualViewport.height;
  679. console.log(window.innerHeight, "window.innerHeight");
  680. console.log(visualViewport.height, "visualViewport.height");
  681. console.log(keyboardHeight, "keyboardHeight");
  682. adjustFooterPosition(visualViewport.height);
  683. }, 200);
  684. };
  685. // window.addEventListener("resize", () => {
  686. // // 检测是否为iOS设备
  687. // const isIOS = /iPhone|iPad|iPod|ios/i.test(navigator.userAgent);
  688. // console.log("是否为iOS设备:", isIOS);
  689. // if (!isIOS) {
  690. // console.log("窗口大小变化");
  691. // const homePage = document.querySelector(".homepage");
  692. // homePage.style.height = `${window.innerHeight}px`;
  693. // }
  694. // });
  695. let touchmoveHandlerRef = null;
  696. const touchmoveHandler = (e) => {
  697. if (!dataStore.isFeedback) {
  698. if (historyRecordRef) {
  699. if (!historyRecordRef.value.isCollapsed) {
  700. return;
  701. }
  702. }
  703. // 判断触摸目标是否在当前活动页面的可滚动区域内
  704. const currentContainer = getCurrentScrollContainer();
  705. const isScrollableArea = currentContainer && currentContainer.contains(e.target);
  706. // 如果不在可滚动区域,则阻止滚动
  707. if (!isScrollableArea) {
  708. e.preventDefault();
  709. }
  710. }
  711. };
  712. const judgeDevice = async () => {
  713. // 延时300ms
  714. await new Promise((resolve) => setTimeout(resolve, 200));
  715. const userAgent = navigator.userAgent;
  716. isMobile.value =
  717. /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
  718. userAgent
  719. );
  720. console.log("当前设备为:", isMobile.value ? "移动端" : "PC端");
  721. };
  722. const throttledJudgeDevice = _.throttle(judgeDevice, 300, {
  723. trailing: false,
  724. });
  725. const expandHistory = () => {
  726. // if (activeTab.value == "AIchat" || activeTab.value == "AiEmotion") {
  727. // historyRecordRef.value.getHistoryList({
  728. // token: localStorage.getItem("localToken"),
  729. // model: activeTab.value == "AIchat" ? 1 : 2,
  730. // });
  731. // }
  732. if (
  733. historyRecordRef.value &&
  734. historyRecordRef.value.isCollapsed !== undefined
  735. ) {
  736. console.log("存在");
  737. historyRecordRef.value.isCollapsed = !historyRecordRef.value.isCollapsed;
  738. if (activeTab.value == "AIchat") {
  739. chatStore.aiChatCall = true;
  740. } else if (activeTab.value == "AiEmotion") {
  741. chatStore.aiEmotionCall = true;
  742. }
  743. }
  744. };
  745. const backToHome = () => {
  746. if (isMobile.value) {
  747. console.log("用户是移动端");
  748. // 调用原生方法跳转到首页
  749. uni.postMessage({
  750. data: {
  751. val: {
  752. name: "JWopenView",
  753. extra: {
  754. data: {
  755. type: 3,
  756. },
  757. },
  758. },
  759. },
  760. });
  761. } else {
  762. console.log("用户是pc端");
  763. const env = import.meta.env.VITE_ENV;
  764. console.log("当前的环境为:", env);
  765. if (env == "development" || env == "test") {
  766. window.parent.location.href =
  767. "http://121.89.234.155:8807/hljw/homepage?menu=999999991";
  768. } else if (env == "product") {
  769. window.parent.location.href =
  770. "https://web.homilychart.com/product/hljw/homepage?menu=999999991";
  771. } else if (env == "production") {
  772. window.parent.location.href =
  773. "https://web.homilychart.com/hljw/homepage?menu=999999991";
  774. }
  775. // window.parent.location.href = window.parent.document.referrer
  776. }
  777. };
  778. // 8.18金币兑换Token start
  779. const userInfo = ref({
  780. username: "HomilyLink",
  781. jwcode: "90042088",
  782. img: " https://d31zlh4on95l9h.cloudfront.net/images/403ef762dd2f335df3b0c9e3fe488375.png",
  783. });
  784. const changeRule = ref("1金币=1Token");
  785. const changeLevelList = ref([
  786. { position: 10, calculatedPosition: 10 },
  787. { position: 20, calculatedPosition: 20 },
  788. { position: 50, calculatedPosition: 50 },
  789. { position: 100, calculatedPosition: 100 },
  790. { position: 200, calculatedPosition: 200 },
  791. { position: 500, calculatedPosition: 500 },
  792. { position: 1000, calculatedPosition: 1000 },
  793. ]);
  794. const activeLevel = ref(
  795. changeLevelList.value[0] || { position: 10, calculatedPosition: 10 }
  796. );
  797. const gold = ref(0);
  798. const rechargeDialogVisible = ref(false);
  799. const chooseLevel = (item) => {
  800. activeLevel.value = item;
  801. };
  802. const changeToken = () => {
  803. if (gold.value < activeLevel.value.position) {
  804. rechargeDialogVisible.value = true;
  805. return;
  806. }
  807. };
  808. const goRecharge = () => {
  809. if (isMobile.value) {
  810. console.log("用户是移动端");
  811. } else {
  812. console.log("用户是pc端");
  813. const env = import.meta.env.VITE_ENV;
  814. console.log("当前的环境为:", env);
  815. if (env == "development" || env == "test") {
  816. window.parent.location.href =
  817. "http://192.168.1.24:8080/user/myGold?token=" +
  818. localStorage.getItem("localToken") +
  819. "&where=xiaocaishen&successUrl=http://192.168.1.5:3000/aixiaocaishen/homePage";
  820. } else if (env == "product") {
  821. // window.parent.location.href =
  822. // 'https://web.homilychart.com/product/hljw/homepage?menu=999999991'
  823. } else if (env == "production") {
  824. // window.parent.location.href = 'https://web.homilychart.com/hljw/homepage?menu=999999991'
  825. }
  826. // window.parent.location.href = window.parent.document.referrer
  827. }
  828. };
  829. // 8.18金币兑换Token end
  830. onMounted(async () => {
  831. throttledJudgeDevice();
  832. // 禁用全局触摸滚动
  833. touchmoveHandlerRef = touchmoveHandler;
  834. document.addEventListener("touchmove", touchmoveHandlerRef, {
  835. passive: false,
  836. });
  837. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  838. // 获取次数
  839. await chatStore.getUserCount();
  840. // 滚动到底部
  841. throttledSmoothScrollToBottom();
  842. // 监听页面高度
  843. throttledHeightListener();
  844. // 添加输入框焦点处理
  845. // handleInputFocus();
  846. // 初始化视口高度变量
  847. // updateAppHeight();
  848. // 添加原生事件监听器
  849. window.addEventListener("resize", throttledJudgeDevice);
  850. if (getQueryVariable("successType") == "success") {
  851. dialogVisible.value = true;
  852. window.parent.location.href = window.parent.location.href.replace(
  853. "successType=success",
  854. ""
  855. );
  856. }
  857. });
  858. onUnmounted(() => {
  859. window.removeEventListener("resize", throttledJudgeDevice);
  860. if (touchmoveHandlerRef) {
  861. console.log("卸载touchmoveHandlerRef组件");
  862. document.removeEventListener("touchmove", touchmoveHandlerRef);
  863. }
  864. // 清理AiEmotion页面的高度监听器
  865. stopAiEmotionHeightObserver();
  866. });
  867. </script>
  868. <template>
  869. <div class="homepage" id="testId">
  870. <!-- 历史记录组件 -->
  871. <HistoryRecord
  872. ref="historyRecordRef"
  873. :current-type="activeTab"
  874. @selectRecord="handleHistorySelect"
  875. :isMobile="isMobile"
  876. @showAnnouncement="showAnnouncement"
  877. @showFeedback="showFeedback"
  878. />
  879. <div
  880. v-if="isMobile && !historyRecordRef?.isCollapsed"
  881. class="zhezhao"
  882. @click="expandHistory"
  883. ></div>
  884. <el-container
  885. v-if="!dataStore.isFeedback"
  886. class="main-container"
  887. :class="{
  888. collapsed: historyRecordRef?.isCollapsed && !isMobile,
  889. unCollapsed: !historyRecordRef?.isCollapsed && !isMobile,
  890. }"
  891. >
  892. <!-- AI小财神头部 logo 次数 公告 -->
  893. <el-header class="homepage-head">
  894. <!-- logo -->
  895. <div class="homepage-logo" v-if="isMobile">
  896. <img
  897. class="expand"
  898. @click="expandHistory"
  899. src="https://d31zlh4on95l9h.cloudfront.net/images/37fe3d79a8a700f6c674c9f0e7af066b.png"
  900. alt="icon"
  901. />
  902. <img :src="logo" alt="图片加载失败" class="logo1" />
  903. <!-- <img :src="madeInHL" alt="图片加载失败" class="logo2" /> -->
  904. </div>
  905. <div class="homepage-right-group" v-if="isMobile">
  906. <div class="count-badge" @click="showCount">
  907. <img
  908. src="https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png"
  909. class="action-btn"
  910. />
  911. <div class="count-number">{{ UserCount }}</div>
  912. <div class="clickGetCount">点击获取Token</div>
  913. </div>
  914. <div class="backToHomeBtn" @click="backToHome()">
  915. <img
  916. src="https://d31zlh4on95l9h.cloudfront.net/images/d8b388e461423f79087ddbe016002217.png"
  917. alt="返回首页"
  918. class="backImg"
  919. />
  920. <div class="backContent">返回首页</div>
  921. </div>
  922. <!-- <img
  923. :src="announcementBtn"
  924. class="announcement-btn action-btn"
  925. @click="showAnnouncement"
  926. />
  927. <img
  928. :src="feedbackBtn"
  929. class="announcement-btn action-btn"
  930. @click="showFeedback"
  931. /> -->
  932. </div>
  933. </el-header>
  934. <!-- 主体部分小人 问题轮询图 对话内容 -->
  935. <el-main class="homepage-body">
  936. <div class="main-wrapper">
  937. <section class="tab-section">
  938. <div
  939. class="tab-container"
  940. :class="{
  941. pcTabContainer: !isMobile,
  942. }"
  943. >
  944. <div
  945. v-for="(tab, index) in tabs"
  946. :key="tab.name"
  947. @click="setActiveTab(tab.name, index)"
  948. :class="[
  949. 'tab-item',
  950. { active: activeIndex === index && !isAnnouncementVisible },
  951. ]"
  952. >
  953. <span>{{ tab.label }}</span>
  954. </div>
  955. <div v-if="!isMobile" class="pc-count-badge">
  956. <div class="pc-countBtn" @click="showCount">
  957. <div class="pc-action-btn">
  958. <div class="pc-count-number">{{ UserCount }}</div>
  959. </div>
  960. <div class="pc-clickGetCount">点击获取Token</div>
  961. </div>
  962. <div class="pc-backToHomeBtn" @click="backToHome()">
  963. <img
  964. src="https://d31zlh4on95l9h.cloudfront.net/images/d8b388e461423f79087ddbe016002217.png"
  965. alt="返回首页"
  966. class="pc-backImg"
  967. />
  968. <div class="pc-backContent">返回首页</div>
  969. </div>
  970. </div>
  971. </div>
  972. </section>
  973. <!-- AIchat页面的独立滚动容器 -->
  974. <div
  975. v-show="activeTab === 'AIchat'"
  976. class="tab-content"
  977. :class="{
  978. pcTabContent: !isMobile,
  979. }"
  980. ref="tabContentAIchat"
  981. >
  982. <component
  983. v-if="activeTab === 'AIchat'"
  984. :is="activeComponent"
  985. :messages="messages"
  986. @updateMessage="updateMessage"
  987. @sendMessage="sendMessage"
  988. @ensureAIchat="ensureAIchat"
  989. @enableInput="enableInput"
  990. />
  991. </div>
  992. <!-- AiEmotion页面的独立滚动容器 -->
  993. <div
  994. v-show="activeTab === 'AiEmotion'"
  995. class="tab-content"
  996. :class="{
  997. pcTabContent: !isMobile,
  998. }"
  999. ref="tabContentAiEmotion"
  1000. >
  1001. <component
  1002. v-if="activeTab === 'AiEmotion'"
  1003. :is="activeComponent"
  1004. :messages="messages"
  1005. @updateMessage="updateMessage"
  1006. @sendMessage="sendMessage"
  1007. @ensureAIchat="ensureAIchat"
  1008. @enableInput="enableInput"
  1009. @scrollToBottom="handleAiEmotionScrollToBottom"
  1010. ref="aiEmotionRef"
  1011. />
  1012. </div>
  1013. </div>
  1014. </el-main>
  1015. <!-- 尾部 问题输入框 深度思考 多语言 语音播报 -->
  1016. <el-footer
  1017. class="homepage-footer"
  1018. :class="{
  1019. pcFooter: !isMobile,
  1020. }"
  1021. id="input"
  1022. >
  1023. <!-- 第一行按钮 -->
  1024. <div class="footer-first-line">
  1025. <div class="left-group">
  1026. <!-- <img v-if="isThinking" :src="thinkActive" @click="toggleThink" class="action-btn" />
  1027. <img v-else :src="thinkNoActive" @click="toggleThink" class="action-btn" />
  1028. <img :src="languageBtn" @click="changeLanguage" class="action-btn" /> -->
  1029. <!-- 夺宝奇兵大模型按钮 -->
  1030. <img
  1031. :src="activeTab === 'AIchat' ? dbqbButton01 : dbqbButton02"
  1032. @click="setActiveTab('AIchat', 0)"
  1033. class="action-btn model-btn"
  1034. alt="夺宝奇兵大模型"
  1035. />
  1036. <!-- AI情绪大模型按钮 -->
  1037. <img
  1038. :src="
  1039. activeTab === 'AiEmotion' ? emotionButton01 : emotionButton02
  1040. "
  1041. @click="setActiveTab('AiEmotion', 1)"
  1042. class="action-btn model-btn"
  1043. alt="AI情绪大模型"
  1044. />
  1045. <!-- <img v-if="
  1046. getCurrentAudioStore().isVoiceEnabled &&
  1047. getCurrentAudioStore().isPlaying
  1048. " :src="voice" @click="toggleVoice" class="action-btn" style="animation: pulse 1.5s infinite" />
  1049. <img v-else-if="
  1050. getCurrentAudioStore().isVoiceEnabled &&
  1051. !getCurrentAudioStore().isPlaying
  1052. " :src="voiceNoActive" @click="toggleVoice" class="action-btn" />
  1053. <img v-else :src="voiceNoActive" @click="toggleVoice" class="action-btn" /> -->
  1054. </div>
  1055. </div>
  1056. <!-- 第二行输入框 -->
  1057. <div class="footer-second-line">
  1058. <!-- <img :src="msgBtn" class="msg-icon" /> -->
  1059. <div class="input-container">
  1060. <el-input
  1061. type="textarea"
  1062. v-model="message"
  1063. @focus="onFocus"
  1064. @blur="onBlur"
  1065. :autosize="{ minRows: 1, maxRows: 4 }"
  1066. class="msg-input"
  1067. @keydown.enter.exact.prevent="
  1068. isLoading || isInputDisabled ? null : sendMessage()
  1069. "
  1070. :disabled="isInputDisabled"
  1071. resize="none"
  1072. :class="{ input: !message && !inputing }"
  1073. @compositionstart="inputing = true"
  1074. @compositionend="inputing = false"
  1075. >
  1076. </el-input>
  1077. <img
  1078. :src="
  1079. isInputDisabled
  1080. ? 'https://d31zlh4on95l9h.cloudfront.net/images/aa192bcbc1682c97e1bc6fb422f2afff.png'
  1081. : 'https://d31zlh4on95l9h.cloudfront.net/images/e6ec2ae238ced85b74e0912e988f243e.png'
  1082. "
  1083. @click="sendMessage"
  1084. class="action-btn send-btn-inner"
  1085. :style="{
  1086. opacity: isInputDisabled ? 0.5 : 1,
  1087. cursor: isInputDisabled ? 'not-allowed' : 'pointer',
  1088. }"
  1089. />
  1090. </div>
  1091. </div>
  1092. </el-footer>
  1093. </el-container>
  1094. <el-container
  1095. v-else
  1096. class="main-container"
  1097. :class="{
  1098. collapsed: historyRecordRef?.isCollapsed && !isMobile,
  1099. unCollapsed: !historyRecordRef?.isCollapsed && !isMobile,
  1100. }"
  1101. >
  1102. <el-header class="homepage-head">
  1103. <!-- logo -->
  1104. <!-- <div class="homepage-logo">
  1105. <img :src="logo" alt="图片加载失败" class="logo1" />
  1106. <img :src="madeInHL" alt="图片加载失败" class="logo2" />
  1107. </div> -->
  1108. <div class="homepage-right-group">
  1109. <div class="count-badge" @click="showCount">
  1110. <img
  1111. src="https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png"
  1112. class="action-btn"
  1113. />
  1114. <div class="count-number">{{ UserCount }}</div>
  1115. <div class="clickGetCount">点击获取Token</div>
  1116. </div>
  1117. <div class="backToHomeBtn" @click="backToHome()">
  1118. <img
  1119. src="https://d31zlh4on95l9h.cloudfront.net/images/d8b388e461423f79087ddbe016002217.png"
  1120. alt="返回首页"
  1121. class="backImg"
  1122. />
  1123. <div class="backContent">返回首页</div>
  1124. </div>
  1125. <!-- <img
  1126. :src="announcementBtn"
  1127. class="announcement-btn action-btn"
  1128. @click="showAnnouncement"
  1129. />
  1130. <img
  1131. :src="feedbackBtn"
  1132. class="announcement-btn action-btn"
  1133. @click="showFeedback"
  1134. /> -->
  1135. </div>
  1136. </el-header>
  1137. <!-- 主体部分小人 问题轮询图 对话内容 -->
  1138. <el-main class="homepage-body">
  1139. <component :is="activeTwoTab" />
  1140. </el-main>
  1141. </el-container>
  1142. <!-- 弹窗 -->
  1143. <!-- 新增弹窗组件 -->
  1144. <el-dialog v-model="dialogVisible" :width="isMobile ? '80%' : '60%'">
  1145. <!-- 中间内容部分 -->
  1146. <div class="changeMsg">
  1147. <div class="changeInfo">
  1148. <div class="changeImg">
  1149. <img :src="userInfo.img" alt="头像" class="changeImgClass" />
  1150. </div>
  1151. <div class="changeContent">
  1152. <div class="changeUsername">{{ userInfo.username }}</div>
  1153. <div class="changeJwcode">精网号{{ userInfo.jwcode }}</div>
  1154. </div>
  1155. </div>
  1156. <div class="changeRule">兑换规则{{ changeRule }}</div>
  1157. </div>
  1158. <div class="changeLevel">
  1159. <div class="changeLevelTitle">兑换Token</div>
  1160. <div class="changeLevelContent">
  1161. <div
  1162. class="changeLevelItems"
  1163. v-for="item in changeLevelList"
  1164. :key="item"
  1165. :class="{
  1166. changeLevelItemsActive: item.position == activeLevel.position,
  1167. }"
  1168. @click="chooseLevel(item)"
  1169. >
  1170. <div class="changeLevelItem">
  1171. <div class="changeLevelItemToken">
  1172. <img
  1173. src="https://d31zlh4on95l9h.cloudfront.net/images/403ef762dd2f335df3b0c9e3fe488375.png"
  1174. alt="token"
  1175. class="changeLevelItemTokenImg"
  1176. />
  1177. {{ item.calculatedPosition }}
  1178. </div>
  1179. <div class="changeLevelItemToken">{{ item.position }} 金币</div>
  1180. </div>
  1181. </div>
  1182. </div>
  1183. </div>
  1184. <div class="changeNow">
  1185. 应付金额
  1186. <div class="changePay">{{ activeLevel.position }}</div>
  1187. (金币余额{{ gold }})
  1188. </div>
  1189. <div class="changeBtn" @click="changeToken">立即兑换</div>
  1190. </el-dialog>
  1191. <el-dialog
  1192. v-model="rechargeDialogVisible"
  1193. :width="isMobile ? '60%' : '40%'"
  1194. :show-close="false"
  1195. >
  1196. <div class="rechargeDialogTitle">温馨提示</div>
  1197. <div class="rechargeDialogContent">
  1198. 尊敬的用户您好您当前的金币余额不足无法进行兑换可充值金币后进行兑换点击下方的前往充值可进行充值
  1199. </div>
  1200. <div class="rechargeDialogBtnGroup">
  1201. <div class="recharge" @click="goRecharge">前往充值</div>
  1202. <div
  1203. class="rechargeDialogCancel"
  1204. @click="rechargeDialogVisible = false"
  1205. >
  1206. 取消
  1207. </div>
  1208. </div>
  1209. </el-dialog>
  1210. </div>
  1211. </template>
  1212. <style scoped>
  1213. /* 标签栏 */
  1214. .tab-container {
  1215. display: flex;
  1216. margin-bottom: 10px;
  1217. height: 100%;
  1218. position: relative;
  1219. justify-content: center;
  1220. align-items: center;
  1221. gap: 25vw;
  1222. /* 新增右对齐 */
  1223. }
  1224. .pcTabContainer {
  1225. }
  1226. .tab-item {
  1227. cursor: pointer;
  1228. padding: 8px 12px;
  1229. font-size: clamp(18px, 3vw, 20px);
  1230. /* color: #999; */
  1231. color: #fff;
  1232. transition: all 0.3s;
  1233. border-bottom: 2px solid transparent;
  1234. font-weight: bold;
  1235. }
  1236. .tab-item.active {
  1237. /* color: #000;
  1238. border-color: #000; */
  1239. background: linear-gradient(0deg, #ffffff, #fec13e);
  1240. -webkit-background-clip: text;
  1241. background-clip: text;
  1242. -webkit-text-fill-color: transparent;
  1243. color: transparent;
  1244. border-color: #fec13e;
  1245. }
  1246. .tab-item:not(.active):hover {
  1247. color: #999999;
  1248. }
  1249. .tab-content {
  1250. overflow-y: auto;
  1251. overflow-x: hidden;
  1252. scroll-behavior: smooth;
  1253. height: 100%;
  1254. /* 添加平滑滚动效果 */
  1255. }
  1256. .pcTabContent {
  1257. padding: 0 6%;
  1258. }
  1259. @media (max-width: 768px) {
  1260. .tab-container {
  1261. gap: 15px;
  1262. padding: 0 10px;
  1263. }
  1264. .tab-item {
  1265. font-size: clamp(14px, 3vw, 16px);
  1266. padding: 6px 10px;
  1267. }
  1268. }
  1269. </style>
  1270. <style scoped>
  1271. html {
  1272. height: 100dvh;
  1273. overflow: hidden !important;
  1274. position: fixed;
  1275. margin: 0;
  1276. padding: 0;
  1277. -webkit-overflow-scrolling: auto;
  1278. /* 禁用 iOS 弹性滚动 */
  1279. }
  1280. body {
  1281. height: 100dvh;
  1282. overflow: clip;
  1283. margin: 0;
  1284. padding: 0;
  1285. -webkit-overflow-scrolling: auto;
  1286. /* 禁用 iOS 弹性滚动 */
  1287. position: fixed;
  1288. }
  1289. #app {
  1290. overflow: hidden;
  1291. height: 100%;
  1292. margin: 0;
  1293. padding: 0;
  1294. }
  1295. .homepage {
  1296. /* height: var(--app-height, 100vh); */
  1297. height: var(--app-height, 100vh);
  1298. margin: 0 auto;
  1299. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/2dc3c13a74100b906e809d26b66db211.png");
  1300. background-size: 100% 100%;
  1301. background-repeat: no-repeat;
  1302. background-position: center;
  1303. display: flex;
  1304. flex-direction: row;
  1305. /* 改为水平布局 */
  1306. overflow: hidden;
  1307. position: fixed;
  1308. top: 0;
  1309. left: 0;
  1310. right: 0;
  1311. bottom: 0;
  1312. width: 100%;
  1313. /* -webkit-overflow-scrolling: touch; */
  1314. }
  1315. .main-container {
  1316. flex: 1;
  1317. transition: margin-left 0.3s ease;
  1318. display: flex;
  1319. flex-direction: column;
  1320. overflow: hidden;
  1321. }
  1322. .main-container.unCollapsed {
  1323. margin-left: 300px; /* 为历史记录组件留出空间 */
  1324. }
  1325. /* 当历史记录组件折叠时调整主容器边距 */
  1326. .main-container.collapsed {
  1327. margin-left: 3%;
  1328. }
  1329. .zhezhao {
  1330. width: 100%;
  1331. height: 100%;
  1332. background-color: rgba(0, 0, 0, 0.5);
  1333. z-index: 100;
  1334. position: fixed;
  1335. }
  1336. /* 移动端适配 */
  1337. @media (max-width: 768px) {
  1338. .homepage {
  1339. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/90d31d7052e729c63acb9e2cb94d1307.png");
  1340. }
  1341. .main-container {
  1342. /* margin-left: 280px; */
  1343. }
  1344. .main-container.unCollapsed {
  1345. margin-left: 280px;
  1346. }
  1347. .main-container.collapsed {
  1348. margin-left: 40px;
  1349. }
  1350. }
  1351. .homepage .el-container {
  1352. height: 100%;
  1353. flex-direction: column;
  1354. display: flex;
  1355. width: 100%;
  1356. overflow: hidden;
  1357. /* 防止容器滚动 */
  1358. }
  1359. .el-container .el-header {
  1360. flex-shrink: 0;
  1361. /* 防止头部压缩 */
  1362. height: auto;
  1363. min-height: 60px;
  1364. padding: 5px 0;
  1365. position: sticky;
  1366. top: 0;
  1367. z-index: 10;
  1368. /* background-color: rgba(255, 255, 255, 0.9); */
  1369. }
  1370. .el-container .el-main {
  1371. flex: 1;
  1372. /* 自动占据剩余空间 */
  1373. overflow: hidden;
  1374. /* 主容器不滚动 */
  1375. display: flex;
  1376. flex-direction: column;
  1377. min-height: 0;
  1378. /* 允许内容区域缩小 */
  1379. position: relative;
  1380. height: auto;
  1381. }
  1382. .el-container .el-footer {
  1383. flex-shrink: 0;
  1384. height: auto;
  1385. min-height: 70px;
  1386. position: sticky;
  1387. bottom: 0;
  1388. z-index: 20;
  1389. background-color: rgba(211, 24, 24, 0);
  1390. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  1391. -webkit-transform: translateZ(0);
  1392. transform: translateZ(0);
  1393. padding-bottom: env(safe-area-inset-bottom, 0);
  1394. /* 适配iPhone X及以上的底部安全区域 */
  1395. }
  1396. .homepage-head {
  1397. padding: 0px;
  1398. display: flex;
  1399. position: relative;
  1400. justify-content: space-between;
  1401. width: 100%;
  1402. }
  1403. .homepage-right-group {
  1404. display: flex;
  1405. gap: 8px;
  1406. align-items: center;
  1407. margin-left: auto;
  1408. margin-right: 20px;
  1409. }
  1410. .homepage-right-group .action-btn {
  1411. height: 40px;
  1412. }
  1413. .count-badge {
  1414. position: relative;
  1415. cursor: pointer;
  1416. }
  1417. .count-badge:hover {
  1418. transform: scale(1.05);
  1419. }
  1420. .count-number {
  1421. position: absolute;
  1422. top: 16px;
  1423. right: 0px;
  1424. width: 68%;
  1425. text-align: center;
  1426. color: #6a00ff;
  1427. font-size: 14px;
  1428. font-weight: bold;
  1429. }
  1430. .clickGetCount {
  1431. width: 100%;
  1432. text-align: center;
  1433. color: white;
  1434. font-size: 12px;
  1435. }
  1436. .backToHomeBtn {
  1437. display: flex;
  1438. flex-direction: column;
  1439. justify-content: center;
  1440. align-items: center;
  1441. }
  1442. .backImg {
  1443. width: 100%;
  1444. height: auto;
  1445. }
  1446. .backContent {
  1447. width: 100%;
  1448. text-align: center;
  1449. color: white;
  1450. font-size: 12px;
  1451. white-space: nowrap;
  1452. }
  1453. .pc-count-badge {
  1454. width: 200px;
  1455. height: 100%;
  1456. position: absolute;
  1457. right: 20px;
  1458. display: flex;
  1459. }
  1460. .pc-countBtn {
  1461. width: 65%;
  1462. height: 100%;
  1463. position: relative;
  1464. }
  1465. .pc-countBtn:hover {
  1466. transform: scale(1.05);
  1467. }
  1468. .pc-action-btn {
  1469. width: 100%;
  1470. height: 70%;
  1471. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png");
  1472. background-repeat: no-repeat;
  1473. background-size: 100% 100%;
  1474. }
  1475. .pc-count-number {
  1476. position: absolute;
  1477. top: 15px;
  1478. right: 4px;
  1479. width: 68%;
  1480. text-align: center;
  1481. color: #6a00ff;
  1482. font-size: 15px;
  1483. font-weight: bold;
  1484. }
  1485. .pc-clickGetCount {
  1486. width: 100%;
  1487. text-align: center;
  1488. color: white;
  1489. font-size: 12px;
  1490. }
  1491. .pc-backToHomeBtn {
  1492. width: 35%;
  1493. height: 100%;
  1494. display: flex;
  1495. flex-direction: column;
  1496. align-items: center;
  1497. }
  1498. .pc-backImg {
  1499. width: auto;
  1500. height: 70%;
  1501. }
  1502. .pc-backContent {
  1503. width: 100%;
  1504. text-align: center;
  1505. color: white;
  1506. font-size: 12px;
  1507. }
  1508. .pc-backToHomeBtn:hover {
  1509. transform: scale(1.05);
  1510. }
  1511. .homepage-right-group .announcement-btn {
  1512. cursor: pointer;
  1513. transition: transform 0.3s;
  1514. }
  1515. .homepage-right-group .announcement-btn:hover {
  1516. transform: scale(1.3);
  1517. }
  1518. .homepage-body {
  1519. padding: 0px;
  1520. display: flex;
  1521. flex-direction: column;
  1522. flex: 1;
  1523. min-height: 0;
  1524. /* 允许内容区域缩小 */
  1525. overflow: hidden;
  1526. }
  1527. .main-wrapper {
  1528. height: 100%;
  1529. display: flex;
  1530. flex-direction: column;
  1531. flex: 1;
  1532. min-height: 0;
  1533. /* 允许内容区域缩小 */
  1534. }
  1535. .tab-section {
  1536. flex-shrink: 0;
  1537. /* 禁止伸缩 */
  1538. }
  1539. .tab-content {
  1540. flex: 1;
  1541. overflow-y: auto;
  1542. min-height: 0;
  1543. /* 关键:允许内容收缩 */
  1544. }
  1545. .homepage-logo {
  1546. height: 100%;
  1547. width: fit-content;
  1548. display: flex;
  1549. /* flex-direction: column; */
  1550. align-items: center;
  1551. justify-content: center;
  1552. margin-left: 20px;
  1553. margin-right: auto;
  1554. position: relative;
  1555. gap: 10px;
  1556. }
  1557. .expand {
  1558. font-size: 2.5rem;
  1559. cursor: pointer;
  1560. color: white;
  1561. }
  1562. .logo1 {
  1563. width: 110px;
  1564. height: auto;
  1565. margin-bottom: 8px;
  1566. }
  1567. .logo2 {
  1568. width: 80px;
  1569. height: auto;
  1570. }
  1571. /* 尾部 */
  1572. .homepage-footer {
  1573. display: flex;
  1574. flex-direction: column;
  1575. gap: 5px;
  1576. flex-shrink: 0;
  1577. /* width: 100%; */
  1578. background-color: #fff;
  1579. }
  1580. .pcFooter {
  1581. margin: 0 6% 4%;
  1582. }
  1583. .footer-first-line {
  1584. display: flex;
  1585. justify-content: space-between;
  1586. align-items: center;
  1587. padding: 5px 15px;
  1588. flex-shrink: 0;
  1589. }
  1590. .left-group {
  1591. display: flex;
  1592. gap: 15px;
  1593. }
  1594. .action-btn {
  1595. cursor: pointer;
  1596. transition: transform 0.2s;
  1597. height: 28px;
  1598. }
  1599. .model-btn {
  1600. height: 32px;
  1601. transition: all 0.3s ease;
  1602. }
  1603. .model-btn:hover {
  1604. transform: scale(1.1);
  1605. }
  1606. .send-btn {
  1607. margin-left: 10px;
  1608. height: 33px !important;
  1609. width: auto;
  1610. /* margin-right: 5px; */
  1611. }
  1612. .input-container {
  1613. position: relative;
  1614. width: 100%;
  1615. }
  1616. .send-btn-inner {
  1617. position: absolute;
  1618. right: 10px;
  1619. top: 50%;
  1620. transform: translateY(-50%);
  1621. height: 28px !important;
  1622. width: auto;
  1623. z-index: 10;
  1624. transition: all 0.3s ease;
  1625. }
  1626. .send-btn-inner:hover {
  1627. transform: translateY(-50%) scale(1.1);
  1628. }
  1629. /* 音频播放动画 */
  1630. @keyframes pulse {
  1631. 0% {
  1632. transform: scale(1);
  1633. }
  1634. 50% {
  1635. transform: scale(1.1);
  1636. }
  1637. 100% {
  1638. transform: scale(1);
  1639. }
  1640. }
  1641. .footer-second-line {
  1642. position: relative;
  1643. display: flex;
  1644. align-items: center;
  1645. padding: 5px 15px 10px;
  1646. flex-shrink: 0;
  1647. }
  1648. .msg-icon {
  1649. position: absolute;
  1650. left: 25px;
  1651. top: 50%;
  1652. transform: translateY(-50%);
  1653. width: 24px;
  1654. z-index: 999;
  1655. }
  1656. .msg-input:deep(.el-textarea__inner) {
  1657. border: none !important;
  1658. box-shadow: none !important;
  1659. overflow-y: auto !important;
  1660. transition: all 0.2s ease-out;
  1661. resize: none !important;
  1662. line-height: 1.5 !important;
  1663. max-height: 100px !important;
  1664. padding-right: 45px !important;
  1665. }
  1666. .msg-input {
  1667. min-height: 34px;
  1668. width: 100%;
  1669. border-radius: 5px;
  1670. font-size: 16px;
  1671. transition: all 0.3s ease-out;
  1672. overflow-y: hidden;
  1673. box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3);
  1674. background: #fff;
  1675. z-index: 5;
  1676. /* 添加iOS设备特殊处理 */
  1677. -webkit-appearance: none;
  1678. appearance: none;
  1679. }
  1680. .msg-input:focus {
  1681. outline: none;
  1682. }
  1683. .input::before {
  1684. content: "请输入股票名称或股票代码...";
  1685. position: absolute;
  1686. left: 11px;
  1687. top: 5px;
  1688. color: var(--el-text-color-secondary);
  1689. pointer-events: none;
  1690. white-space: nowrap;
  1691. overflow-x: hidden;
  1692. text-overflow: ellipsis;
  1693. width: 80%;
  1694. z-index: 6;
  1695. }
  1696. @media (max-width: 768px) {
  1697. .action-btn {
  1698. height: 21px;
  1699. }
  1700. .footer-second-line {
  1701. padding: 5px 10px 10px;
  1702. }
  1703. .msg-input {
  1704. /* min-height: 44px; */
  1705. /* height: 44px; */
  1706. font-size: 16px;
  1707. }
  1708. }
  1709. .changeMsg {
  1710. display: flex;
  1711. width: 100%;
  1712. margin-bottom: 30px;
  1713. flex-wrap: wrap;
  1714. gap: 20px;
  1715. }
  1716. .changeInfo {
  1717. display: flex;
  1718. background-color: #f8f8f8;
  1719. border-radius: 5px;
  1720. padding: 10px 20px;
  1721. /* width: 40%; */
  1722. white-space: nowrap;
  1723. }
  1724. .changeImg {
  1725. height: 100%;
  1726. display: flex;
  1727. justify-content: center;
  1728. align-items: center;
  1729. margin-right: 10px;
  1730. }
  1731. .changeImgClass {
  1732. }
  1733. .changeContent {
  1734. display: flex;
  1735. flex-direction: column;
  1736. justify-content: center;
  1737. font-weight: bold;
  1738. }
  1739. .changeRule {
  1740. display: flex;
  1741. background-color: #f8f8f8;
  1742. border-radius: 5px;
  1743. text-align: center;
  1744. align-items: center;
  1745. justify-content: center;
  1746. color: #4e86fe;
  1747. white-space: nowrap;
  1748. padding: 5px 20px;
  1749. min-width: 40%;
  1750. }
  1751. .changeLevel {
  1752. display: flex;
  1753. flex-direction: column;
  1754. }
  1755. .changeLevelTitle {
  1756. font-weight: bold;
  1757. margin-bottom: 10px;
  1758. }
  1759. .changeLevelContent {
  1760. display: flex;
  1761. flex-wrap: wrap;
  1762. gap: 15px;
  1763. margin-bottom: 10px;
  1764. }
  1765. .changeLevelItems {
  1766. display: flex;
  1767. background-color: #f8f8f8;
  1768. width: 20%;
  1769. justify-content: center;
  1770. align-items: center;
  1771. flex-direction: column;
  1772. border-radius: 10px;
  1773. padding: 5px;
  1774. cursor: pointer;
  1775. }
  1776. .changeLevelItems:hover {
  1777. background-color: #ecf2ff;
  1778. }
  1779. .changeLevelItemsActive {
  1780. border: 1px solid #4e86fe;
  1781. background-color: #ecf2ff;
  1782. }
  1783. .changeLevelItem {
  1784. display: flex;
  1785. flex-direction: column;
  1786. justify-content: center;
  1787. align-items: center;
  1788. }
  1789. .changeLevelItemToken {
  1790. display: flex;
  1791. justify-content: center;
  1792. align-items: center;
  1793. }
  1794. .changeLevelItemTokenImg {
  1795. width: 40px;
  1796. height: 40px;
  1797. }
  1798. .changeNow {
  1799. display: flex;
  1800. white-space: nowrap;
  1801. /* font-weight: bold; */
  1802. margin-bottom: 15px;
  1803. align-items: center;
  1804. }
  1805. .changePay {
  1806. color: #4e86fe;
  1807. margin: 0px 5px;
  1808. font-size: 1.1rem;
  1809. }
  1810. .changeBtn {
  1811. width: 40%;
  1812. background-color: #4e86fe;
  1813. color: white;
  1814. display: flex;
  1815. justify-content: center;
  1816. align-content: center;
  1817. padding: 10px;
  1818. border-radius: 5px;
  1819. cursor: pointer;
  1820. }
  1821. .changeBtn:hover {
  1822. background-color: #3a73e6;
  1823. }
  1824. .rechargeDialogTitle {
  1825. font-size: 1.7rem;
  1826. /* font-weight: bold; */
  1827. color: #4e86fe;
  1828. display: flex;
  1829. justify-content: center;
  1830. align-items: center;
  1831. letter-spacing: 10px;
  1832. }
  1833. .rechargeDialogContent {
  1834. padding: 20px;
  1835. font-size: 1.2rem;
  1836. }
  1837. .rechargeDialogBtnGroup {
  1838. display: flex;
  1839. font-size: 1.2rem;
  1840. padding: 0px 20px;
  1841. justify-content: space-between;
  1842. }
  1843. .recharge {
  1844. color: white;
  1845. background-color: #4e86fe;
  1846. padding: 10px 20px;
  1847. border-radius: 13px;
  1848. cursor: pointer;
  1849. width: 20%;
  1850. text-align: center;
  1851. }
  1852. .recharge:hover {
  1853. background-color: #3a73e6;
  1854. }
  1855. .rechargeDialogCancel {
  1856. border: 1px solid rgb(202, 202, 202);
  1857. padding: 10px 20px;
  1858. border-radius: 13px;
  1859. cursor: pointer;
  1860. width: 20%;
  1861. text-align: center;
  1862. }
  1863. .rechargeDialogCancel:hover {
  1864. background-color: #ecf2ff;
  1865. }
  1866. </style>