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.

3803 lines
95 KiB

6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
6 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
6 months ago
12 months ago
12 months ago
12 months ago
7 months ago
9 months ago
6 months ago
6 months ago
7 months ago
7 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
12 months ago
12 months ago
12 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
6 months ago
6 months ago
6 months ago
7 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
6 months ago
6 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 {
  6. getUserCountAPI,
  7. showExchangeAPI,
  8. godExchangeAPI,
  9. exchangeAPI,
  10. getGoldCoinAPI,
  11. } from "../api/AIxiaocaishen";
  12. import { ElMessage } from "element-plus";
  13. import AIchat from "./AIchat.vue";
  14. import AIfind from "./AIfind.vue";
  15. import AiEmotion from "./AiEmotion.vue";
  16. import deepNine from "./deepNine.vue";
  17. import Feedback from "./Feedback.vue";
  18. import Announcement from "./Announcement.vue";
  19. import { useAppBridge } from "../assets/js/useAppBridge.js";
  20. import { useDataStore } from "@/store/dataList.js";
  21. import { useChatStore } from "../store/chat";
  22. import { useEmotionAudioStore } from "../store/emotionAudio";
  23. import { useAudioStore } from "../store/audio";
  24. import { useDeepNineStore } from "../store/deepNine";
  25. import _ from "lodash";
  26. import logo from "../assets/img/homePage/logo.png";
  27. import madeInHL from "../assets/img/homePage/madeInHL.png";
  28. import getCountAll from "../assets/img/homePage/get-count-all.png";
  29. import announcementBtn from "../assets/img/homePage/announcement.png";
  30. import thinkActive from "../assets/img/homePage/tail/think-active.png";
  31. import thinkNoActive from "../assets/img/homePage/tail/think-no-active.png";
  32. import languageBtn from "../assets/img/homePage/tail/language.png";
  33. import dbqbButton01 from "../assets/img/AiEmotion/dbqb-button01.png";
  34. import dbqbButton02 from "../assets/img/AiEmotion/dbqb-button02.png";
  35. import emotionButton01 from "../assets/img/AiEmotion/emotion-button01.png";
  36. import emotionButton02 from "../assets/img/AiEmotion/emotion-button02.png";
  37. import voice from "../assets/img/homePage/tail/voice.png";
  38. import voiceNoActive from "../assets/img/homePage/tail/voice-no-active.png";
  39. import sendBtn from "../assets/img/homePage/tail/send.png";
  40. import msgBtn from "../assets/img/homePage/tail/msg.png";
  41. import feedbackBtn from "../assets/img/Feedback/feedbackBtn.png";
  42. import back from "../assets/img/Feedback/back.png";
  43. import HistoryRecord from "./components/HistoryRecord.vue";
  44. import VConsole from "vconsole";
  45. const vConsole = new VConsole();
  46. const isMobile = ref(null);
  47. //免费的Token的到期时间
  48. const freeTokenExpireTime = ref("2026-06-30");
  49. // Token清除时间范围
  50. const tokenClearTimeRange = ref({
  51. startTime: '2025-07-01',
  52. endTime: '2025-12-31'
  53. });
  54. // 获取 AiEmotion 组件的 ref
  55. const aiEmotionRef = ref(null);
  56. // 获取深度九大模型组件的 ref
  57. const deepNineRef = ref(null);
  58. // 获取历史记录组件的 ref
  59. const historyRecordRef = ref(null);
  60. // import { useUserStore } from "../store/userPessionCode.js";
  61. const { getQueryVariable, setActiveTabIndex, getUserInfo } = useDataStore();
  62. const dataStore = useDataStore();
  63. const chatStore = useChatStore();
  64. const deepNineStore = useDeepNineStore();
  65. // 变量
  66. // 音频管理
  67. const emotionAudioStore = useEmotionAudioStore();
  68. const audioStore = useAudioStore();
  69. // 控制Token变动记录弹窗显示
  70. const tokenRecordVisible = ref(false);
  71. const tokenRecordList = ref([
  72. ]);
  73. import axios from 'axios'
  74. // 获取Token变动记录接口
  75. const getTokenChangeLogs = async () => {
  76. try {
  77. const APIurl = import.meta.env.VITE_APP_API_BASE_URL
  78. const res = await axios.post(
  79. `${APIurl}/api/aiEmotion/client/viewTokenChangeLogs`,
  80. {
  81. token: localStorage.getItem('localToken')
  82. }
  83. )
  84. if (res.data.code === 200) {
  85. // 接口字段映射到前端表格格式
  86. const records = res.data.data.map((item, index) => ({
  87. id: index + 1,
  88. change: item.count,
  89. type: item.token_type === 1 ? '付费' : '免费',
  90. time: item.created_at,
  91. remark: item.reason
  92. }))
  93. tokenRecordList.value = records
  94. } else {
  95. ElMessage.error(res.data.msg || '获取Token流水失败')
  96. }
  97. } catch (error) {
  98. ElMessage.error(error.message || '网络异常')
  99. }
  100. }
  101. // 获取免费Token过期时间和清除时间范围接口
  102. const getTokenExpireInfo = async () => {
  103. try {
  104. const APIurl = import.meta.env.VITE_APP_API_BASE_URL
  105. const res = await axios.post(
  106. `${APIurl}/api/aiEmotion/client/getCleanUpFreeTokenTime`,
  107. {
  108. token: localStorage.getItem('localToken')
  109. }
  110. )
  111. if (res.data.code === 200) {
  112. // 更新过期时间(只取年月日)
  113. if (res.data.data.expiredTime) {
  114. freeTokenExpireTime.value = res.data.data.expiredTime.split(' ')[0]
  115. }
  116. // 更新清除时间范围
  117. if (res.data.data.startTime && res.data.data.endTime) {
  118. tokenClearTimeRange.value = {
  119. startTime: res.data.data.startTime,
  120. endTime: res.data.data.endTime
  121. }
  122. }
  123. }
  124. } catch (error) {
  125. console.error('获取Token过期信息失败', error)
  126. }
  127. }
  128. // Token清除提示
  129. const showTokenClearDialog = ref(false)
  130. // 当前要显示的清除文案
  131. const currentClearText = ref('')
  132. // 点击查看Token变动记录
  133. const showTokenRecord = async () => {
  134. await getTokenChangeLogs()
  135. tokenRecordVisible.value = true
  136. };
  137. const APIurl = import.meta.env.VITE_APP_API_BASE_URL
  138. // 清除弹窗提醒接口
  139. const clearPopupReminder = async () => {
  140. try {
  141. const res = await axios.post(
  142. `${APIurl}/api/aiEmotion/client/clearPopupReminder`,
  143. {
  144. token: localStorage.getItem('localToken')
  145. }
  146. )
  147. console.log('clearPopupReminder 接口返回:', res.data)
  148. if (res.data.code === 200) {
  149. // 接口返回 data 为 true 时,显示清除提示弹窗
  150. if (res.data.data === true) {
  151. console.log('设置 showTokenClearDialog = true')
  152. showTokenClearDialog.value = true
  153. }
  154. } else {
  155. ElMessage.error(res.data.msg || '获取清除提醒状态失败')
  156. }
  157. } catch (error) {
  158. ElMessage.error(error.message || '网络异常')
  159. }
  160. }
  161. // 判断是否需要显示Token清除提示
  162. const checkTokenClearTip = () => {
  163. console.log('进入了')
  164. // 拿到当前登录用户的 token
  165. const token = localStorage.getItem('localToken')
  166. if (!token) return
  167. // 用 token 做唯一标记 key
  168. const key = `token_clear_tip_shown_${token}`
  169. const hasShown = localStorage.getItem(key)
  170. // 只要曾经弹过,就不再弹
  171. if (hasShown === '1') return
  172. const formatDate = (dateStr) => {
  173. const date = new Date(dateStr)
  174. return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(
  175. date.getDate()
  176. ).padStart(2, '0')} `
  177. }
  178. const startTime = formatDate(tokenClearTimeRange.value.startTime)
  179. const endTime = formatDate(tokenClearTimeRange.value.endTime)
  180. currentClearText.value = `【通知】${startTime} - ${endTime} 期间获得的AI情绪大模型和夺宝奇兵大模型免费 Token 已统一清除,过期未使用额度不予保留、不累计、不顺延。`
  181. showTokenClearDialog.value = true
  182. }
  183. // 关闭弹窗,并标记:这个token永远不再弹
  184. const closeTokenClearDialog = () => {
  185. showTokenClearDialog.value = false
  186. const token = localStorage.getItem('localToken')
  187. if (!token) return
  188. // 标记:此用户已看过弹窗
  189. const key = `token_clear_tip_shown_${token}`
  190. localStorage.setItem(key, '1')
  191. }
  192. // 根据当前页面类型获取对应的音频store
  193. const getCurrentAudioStore = () => {
  194. return activeTab.value === "AiEmotion" ? emotionAudioStore : audioStore;
  195. };
  196. const isVoice = computed(() => {
  197. const currentStore = getCurrentAudioStore();
  198. return currentStore.isVoiceEnabled;
  199. });
  200. const toggleVoice = () => {
  201. const currentStore = getCurrentAudioStore();
  202. if (!currentStore.isVoiceEnabled) {
  203. // 如果语音功能关闭,先开启语音功能
  204. currentStore.toggleVoice();
  205. } else {
  206. // 如果语音功能开启,则切换播放/暂停状态
  207. if (currentStore.currentAudioUrl || currentStore.ttsUrl) {
  208. // 有音频时切换播放/暂停
  209. currentStore.togglePlayPause();
  210. } else {
  211. // 没有音频时关闭语音功能
  212. currentStore.toggleVoice();
  213. }
  214. }
  215. };
  216. // 将默认值改为从 sessionStorage 中获取,如果没有则使用默认值 'aifindCow'为第一个默认tab
  217. const activeTab = ref(sessionStorage.getItem("activeTabAI") || "AIchat");
  218. const activeIndex = ref(
  219. parseInt(sessionStorage.getItem("activeIndexAI") || "0")
  220. );
  221. // 手机端选择器的值,与activeTab保持同步
  222. const activeTabMobile = ref(activeTab.value);
  223. // 下拉菜单开关状态
  224. const isDropdownOpen = ref(false);
  225. // 模型选项数据
  226. const modelOptions = [
  227. {
  228. label: "夺宝奇兵大模型",
  229. value: "AIchat",
  230. image:
  231. "https://d31zlh4on95l9h.cloudfront.net/images/0389f08c458c20b715be7acebf7a8eb2.png",
  232. },
  233. {
  234. label: "AI情绪大模型",
  235. value: "AiEmotion",
  236. image:
  237. "https://d31zlh4on95l9h.cloudfront.net/images/aed3597ea187c5af6c88d5a5c67fc9c1.png",
  238. },
  239. {
  240. label: "深度九大模型",
  241. value: "deepNine",
  242. image:
  243. "https://d31zlh4on95l9h.cloudfront.net/images/35bf808538183be0062e4647570a9abd.png",
  244. },
  245. ];
  246. // 监听activeTab变化,同步到activeTabMobile
  247. watch(activeTab, (newVal) => {
  248. activeTabMobile.value = newVal;
  249. });
  250. // 切换下拉菜单显示状态
  251. const toggleDropdown = () => {
  252. isDropdownOpen.value = !isDropdownOpen.value;
  253. };
  254. // 选择选项
  255. const selectOption = (value) => {
  256. activeTabMobile.value = value;
  257. isDropdownOpen.value = false;
  258. const tabIndexMap = {
  259. AIchat: 0,
  260. AiEmotion: 1,
  261. deepNine: 2,
  262. };
  263. setActiveTab(value, tabIndexMap[value]);
  264. };
  265. // 获取当前选中选项的标签
  266. const getSelectedOptionLabel = () => {
  267. const option = modelOptions.find(
  268. (opt) => opt.value === activeTabMobile.value
  269. );
  270. return option ? option.label : "请选择模型";
  271. };
  272. // 获取当前选中选项的图片
  273. const getSelectedOptionImage = () => {
  274. const option = modelOptions.find(
  275. (opt) => opt.value === activeTabMobile.value
  276. );
  277. return option ? option.image : "";
  278. };
  279. // 点击外部关闭下拉菜单
  280. onMounted(async () => {
  281. document.addEventListener("click", (e) => {
  282. const container = document.querySelector(".custom-select-container");
  283. if (container && !container.contains(e.target) && isDropdownOpen.value) {
  284. isDropdownOpen.value = false;
  285. }
  286. });
  287. // 获取Token过期信息
  288. getTokenExpireInfo()
  289. // 调用清除弹窗提醒接口
  290. await clearPopupReminder()
  291. // 检查是否需要显示Token清除提示
  292. checkTokenClearTip()
  293. });
  294. // 手机端选择器变化处理(保留原函数以兼容其他地方可能的调用)
  295. const handleMobileTabChange = (value) => {
  296. const tabIndexMap = {
  297. AIchat: 0,
  298. AiEmotion: 1,
  299. deepNine: 2,
  300. };
  301. setActiveTab(value, tabIndexMap[value]);
  302. };
  303. const tabs = computed(() => [
  304. {
  305. name: "AIchat",
  306. label: "夺宝奇兵大模型",
  307. },
  308. // {
  309. // name: "AIfind",
  310. // label: "发现",
  311. // },
  312. {
  313. name: "AiEmotion",
  314. label: "AI情绪大模型",
  315. },
  316. {
  317. name: "deepNine",
  318. label: "深度九大模型",
  319. },
  320. ]);
  321. // 修改 setActiveTab 方法,添加一个可选参数 forceAIchat
  322. const setActiveTab = (tab, index, forceAIchat = false) => {
  323. isScrolling.value = false; //回复滚动到底部方法
  324. isAnnouncementVisible.value = false;
  325. // 重置输入框禁用状态,防止页面切换时状态残留
  326. console.log("tab", tab, "index", index);
  327. if (tab == "AIchat") {
  328. isInputDisabled.value = chatStore.chatInput;
  329. console.log("切换到AIchat页面,输入框状态为", isInputDisabled.value);
  330. } else if (tab == "AiEmotion") {
  331. isInputDisabled.value = chatStore.emotionInput;
  332. console.log("切换到AiEmotion页面,输入框状态为", isInputDisabled.value);
  333. } else if (tab == "deepNine") {
  334. isInputDisabled.value = chatStore.deepNineInput;
  335. console.log("切换到deepNine页面,输入框状态为", isInputDisabled.value);
  336. } else {
  337. isInputDisabled.value = false;
  338. }
  339. if (forceAIchat && activeTab.value !== "AIchat") {
  340. activeTab.value = "AIchat";
  341. activeIndex.value = 0;
  342. sessionStorage.setItem("activeTabAI", "AIchat");
  343. sessionStorage.setItem("activeIndexAI", "0");
  344. } else {
  345. activeTab.value = tab;
  346. activeIndex.value = index;
  347. sessionStorage.setItem("activeTabAI", tab);
  348. sessionStorage.setItem("activeIndexAI", index.toString());
  349. }
  350. setActiveTabIndex(index);
  351. console.log(tab, index, "tab, index");
  352. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  353. // 在页面切换时获取最新的次数
  354. chatStore.getUserCount();
  355. };
  356. // 修改 activeComponent 的计算逻辑
  357. const activeComponent = computed(() => {
  358. if (activeTab.value === "AIchat") {
  359. return AIchat;
  360. } else if (activeTab.value === "AIfind") {
  361. return AIfind;
  362. } else if (activeTab.value === "AiEmotion") {
  363. return AiEmotion; // 新增逻辑
  364. } else if (activeTab.value === "deepNine") {
  365. return deepNine;
  366. }
  367. });
  368. const activeTwoTab = computed(() => {
  369. if (isAnnouncementVisible.value) {
  370. return Announcement;
  371. } else {
  372. return Feedback;
  373. }
  374. });
  375. // 新增一个方法,调用时先判断是否处于 AIchat,若不在则跳转到 AIchat
  376. const ensureAIchat = () => {
  377. setActiveTab("AIchat", 0, true);
  378. };
  379. // 获取次数
  380. const UserCount = computed(() => chatStore.UserCount);
  381. //总的Token
  382. const totalToken = computed(() => chatStore.UserCount);
  383. //付费的Token
  384. const paidToken = computed(() => chatStore.PaidCount);
  385. //免费的Token
  386. const freeToken = computed(() => chatStore.FreeCount);
  387. const getCount = () => {
  388. console.log("点击了获取次数的按钮");
  389. };
  390. // 深度思考
  391. const isThinking = ref(true);
  392. const toggleThink = () => {
  393. isThinking.value = !isThinking.value;
  394. };
  395. // 发送消息
  396. const message = ref("");
  397. // 传输对象
  398. const messages = ref([]);
  399. // 信息加载状态
  400. const isLoading = computed(() => {
  401. chatStore.isLoading;
  402. });
  403. // 输入框禁用状态
  404. const isInputDisabled = ref(false);
  405. // 添加用户消息
  406. const updateMessage = (title) => {
  407. message.value = title;
  408. console.log("updateMessage 的值:", title);
  409. };
  410. watch(
  411. () => chatStore.announcementMsg,
  412. (newVal) => {
  413. console.log("监听到公告改变", chatStore.announcementMsg);
  414. if (chatStore.announcementMsg && !isInputDisabled.value) {
  415. message.value = chatStore.announcementMsg;
  416. chatStore.announcementMsg = null;
  417. }
  418. }
  419. );
  420. watch(
  421. () => dataStore.isFeedback,
  422. async (newVal) => {
  423. if (!dataStore.isFeedback) {
  424. // 重置公告页面显示状态
  425. isAnnouncementVisible.value = false;
  426. await nextTick();
  427. // 监听页面高度
  428. throttledHeightListener();
  429. }
  430. }
  431. );
  432. watch(
  433. () => chatStore.chatInput,
  434. async (newVal) => {
  435. if (activeTab.value == "AIchat") {
  436. isInputDisabled.value = chatStore.chatInput;
  437. }
  438. }
  439. );
  440. watch(
  441. () => chatStore.emotionInput,
  442. async (newVal) => {
  443. if (activeTab.value == "AiEmotion") {
  444. isInputDisabled.value = chatStore.emotionInput;
  445. }
  446. }
  447. );
  448. watch(
  449. () => chatStore.deepNineInput,
  450. async (newVal) => {
  451. console.log("deepNineInput", chatStore.deepNineInput);
  452. if (activeTab.value == "deepNine") {
  453. isInputDisabled.value = chatStore.deepNineInput;
  454. }
  455. }
  456. );
  457. const sendMessage = async () => {
  458. if (
  459. localStorage.getItem("localToken") == null ||
  460. localStorage.getItem("localToken") == ""
  461. ) {
  462. ElMessage.error("请先登录");
  463. return;
  464. }
  465. // 检查输入内容是否为空
  466. if (!message.value || !message.value.trim()) {
  467. ElMessage.warning("输入内容不能为空");
  468. return;
  469. }
  470. isScrolling.value = false;
  471. // 注意:历史记录会在消息发送后自动更新,无需手动添加
  472. // 取消历史记录选中状态
  473. if (historyRecordRef) {
  474. historyRecordRef.value.selectedRecordId = null;
  475. }
  476. // 判断当前是否为 AiEmotion 组件
  477. if (activeTab.value === "AiEmotion") {
  478. // 禁用输入框
  479. isInputDisabled.value = true;
  480. chatStore.emotionInput = true;
  481. // 调用 AiEmotion 组件的 handleSendMessage 方法
  482. aiEmotionRef.value?.handleSendMessage(message.value, () => {
  483. // 打字机效果完成后的回调,重新启用输入框
  484. isInputDisabled.value = false;
  485. chatStore.emotionInput = false;
  486. });
  487. message.value = ""; // 清空输入框
  488. return;
  489. }
  490. // 判断当前是否为深度九大模型
  491. if (activeTab.value === "deepNine") {
  492. // 禁用输入框
  493. isInputDisabled.value = true;
  494. chatStore.deepNineInput = true;
  495. // 获取深度九大模型的 store
  496. const deepNineStore = useDeepNineStore();
  497. // 添加消息到深度九大模型的 store 中
  498. const messageContent = message.value;
  499. message.value = ""; // 清空输入框
  500. setTimeout(() => {
  501. console.log("深度九大模型:添加消息", messageContent);
  502. messages.value = [
  503. ...messages.value,
  504. {
  505. sender: "user",
  506. content: messageContent,
  507. audioArray: [],
  508. audioStatus: false,
  509. },
  510. ];
  511. }, 200);
  512. return;
  513. }
  514. // 调用 ensureAIchat 确保跳转到 AIchat 页面
  515. ensureAIchat();
  516. if (isInputDisabled.value) return;
  517. isInputDisabled.value = true;
  518. chatStore.chatInput = true;
  519. const messageContent = message.value;
  520. // 重置消息输入框
  521. message.value = "";
  522. setTimeout(() => {
  523. console.log("延时后添加消息", messageContent);
  524. // 发送消息时,设置 isLoading 为 true
  525. messages.value = [
  526. ...messages.value,
  527. {
  528. sender: "user",
  529. content: messageContent,
  530. audioArray: [],
  531. audioStatus: false,
  532. },
  533. ];
  534. console.log(messages.value, "messages.value");
  535. }, 200);
  536. };
  537. // 重新启用输入框的方法
  538. const enableInput = () => {
  539. console.log("解除禁用");
  540. isInputDisabled.value = false;
  541. };
  542. // 处理历史记录选择
  543. const handleHistorySelect = (stockData) => {
  544. console.log("接收到历史记录数据:", stockData);
  545. // 只在情绪大模型中处理历史记录选择
  546. if (activeTab.value === "AiEmotion") {
  547. // 等待组件渲染完成后调用addStock方法
  548. nextTick(() => {
  549. if (aiEmotionRef.value && aiEmotionRef.value.addStock) {
  550. aiEmotionRef.value.addStock(stockData);
  551. } else {
  552. console.error("AiEmotion组件或addStock方法不可用");
  553. }
  554. });
  555. } else {
  556. console.log("历史记录选择仅在情绪大模型中有效");
  557. }
  558. };
  559. // 公告
  560. // 新增一个变量来控制是否显示公告页面
  561. const isAnnouncementVisible = ref(false);
  562. // Token规则提示框相关
  563. const tokenRuleDialogVisible = ref(false);
  564. const hasShownTokenRule = ref({
  565. AIchat: sessionStorage.getItem("hasShownTokenRule_AIchat") === "true",
  566. AiEmotion: sessionStorage.getItem("hasShownTokenRule_AiEmotion") === "true",
  567. deepNine: sessionStorage.getItem("hasShownTokenRule_deepNine") === "true",
  568. });
  569. // 关闭Token规则提示框
  570. const closeTokenRuleDialog = () => {
  571. tokenRuleDialogVisible.value = false;
  572. };
  573. // 打开Token规则提示框
  574. const openTokenRuleDialog = () => {
  575. tokenRuleDialogVisible.value = true;
  576. };
  577. // 添加全局点击事件监听器,使任何点击动作都能关闭提示框
  578. // 定义处理函数,以便正确移除事件监听器
  579. const handleGlobalClick = (event) => {
  580. // 检查点击事件是否来自"兑换规则"按钮
  581. const changeRuleElement = document.querySelector(".changeRule");
  582. if (
  583. changeRuleElement &&
  584. (changeRuleElement === event.target ||
  585. changeRuleElement.contains(event.target))
  586. ) {
  587. // 如果点击的是"兑换规则"按钮,不关闭提示框
  588. return;
  589. }
  590. if (tokenRuleDialogVisible.value) {
  591. tokenRuleDialogVisible.value = false;
  592. }
  593. };
  594. onMounted(() => {
  595. document.addEventListener("click", handleGlobalClick);
  596. });
  597. // 在组件卸载时移除事件监听器
  598. onUnmounted(() => {
  599. document.removeEventListener("click", handleGlobalClick);
  600. });
  601. // 检查是否需要显示Token规则提示框(从其他页面跳转过来时)
  602. const checkTokenRuleOnPageLoad = () => {
  603. const activeTab = sessionStorage.getItem("activeTabAI");
  604. const fromExternalPage = sessionStorage.getItem("fromExternalPage");
  605. if (
  606. fromExternalPage === "true" &&
  607. activeTab &&
  608. !hasShownTokenRule.value[activeTab]
  609. ) {
  610. tokenRuleDialogVisible.value = true;
  611. hasShownTokenRule.value[activeTab] = true;
  612. sessionStorage.setItem(`hasShownTokenRule_${activeTab}`, "true");
  613. // 清除标记,避免重复显示
  614. sessionStorage.removeItem("fromExternalPage");
  615. }
  616. };
  617. const showAnnouncement = async () => {
  618. console.log("打开公告");
  619. dataStore.isFeedback = true; // 显示用户反馈页面
  620. isScrolling.value = false; //回复滚动到底部方法
  621. isAnnouncementVisible.value = true; // 显示公告页面
  622. if (isMobile.value) {
  623. if (historyRecordRef) {
  624. historyRecordRef.value.isCollapsed = true;
  625. }
  626. }
  627. };
  628. // 跳转用户反馈
  629. const showFeedback = () => {
  630. console.log("打开用户反馈");
  631. dataStore.isFeedback = true; // 显示用户反馈页面
  632. isAnnouncementVisible.value = false; // 显示反馈页面
  633. if (isMobile.value) {
  634. if (historyRecordRef) {
  635. historyRecordRef.value.isCollapsed = true;
  636. }
  637. }
  638. };
  639. // 保证发送消息时,滚动屏在底部
  640. const tabContentAIchat = ref(null);
  641. const tabContentAiEmotion = ref(null);
  642. const tabContentDeepNine = ref(null);
  643. const isScrolling = ref(false); //判断用户是否在滚动
  644. // AiEmotion页面高度监听器相关变量
  645. const aiEmotionHeightObserver = ref(null);
  646. const isAiEmotionAutoScrollEnabled = ref(false);
  647. const isAiEmotionUserScrolling = ref(false); // 用户是否正在手动滚动
  648. const aiEmotionScrollTimer = ref(null); // 滚动检测定时器
  649. const isChartInteracting = ref(false); // 图表是否正在交互
  650. const chartInteractionTimer = ref(null); // 图表交互检测定时器
  651. // 获取当前活动页面的滚动容器
  652. const getCurrentScrollContainer = () => {
  653. if (activeTab.value === "AIchat") {
  654. return tabContentAIchat.value;
  655. } else if (activeTab.value === "AiEmotion") {
  656. return tabContentAiEmotion.value;
  657. } else if (activeTab.value === "deepNine") {
  658. return tabContentDeepNine.value;
  659. }
  660. return null;
  661. };
  662. const smoothScrollToBottom = async () => {
  663. const container = getCurrentScrollContainer();
  664. if (!container) return;
  665. await nextTick(); // 确保在DOM更新后执行
  666. if (!isScrolling.value) {
  667. container.scrollTop = container.scrollHeight - container.offsetHeight;
  668. }
  669. };
  670. const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 300, {
  671. trailing: false,
  672. });
  673. // AiEmotion页面自动滚动到底部的防抖函数
  674. const debouncedAiEmotionScrollToBottom = _.debounce(() => {
  675. if (
  676. activeTab.value === "AiEmotion" &&
  677. isAiEmotionAutoScrollEnabled.value &&
  678. !isAiEmotionUserScrolling.value &&
  679. !isChartInteracting.value
  680. ) {
  681. const container = tabContentAiEmotion.value;
  682. if (container) {
  683. container.scrollTop = container.scrollHeight - container.offsetHeight;
  684. }
  685. }
  686. }, 150);
  687. // 启动AiEmotion页面高度监听器
  688. const startAiEmotionHeightObserver = () => {
  689. // 先停止之前的监听器
  690. stopAiEmotionHeightObserver();
  691. isAiEmotionAutoScrollEnabled.value = true;
  692. // 创建ResizeObserver监听页面内容变化
  693. aiEmotionHeightObserver.value = new ResizeObserver((entries) => {
  694. if (
  695. isAiEmotionAutoScrollEnabled.value &&
  696. activeTab.value === "AiEmotion" &&
  697. !isChartInteracting.value
  698. ) {
  699. debouncedAiEmotionScrollToBottom();
  700. }
  701. });
  702. // 监听document.body的尺寸变化
  703. if (document.body) {
  704. aiEmotionHeightObserver.value.observe(document.body);
  705. }
  706. // 创建MutationObserver监听DOM结构变化
  707. const mutationObserver = new MutationObserver((mutations) => {
  708. let shouldScroll = false;
  709. mutations.forEach((mutation) => {
  710. if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
  711. // 检查新增的节点是否包含实际内容
  712. const hasContent = Array.from(mutation.addedNodes).some((node) => {
  713. if (node.nodeType === Node.ELEMENT_NODE) {
  714. return node.offsetHeight > 0 || node.scrollHeight > 0;
  715. }
  716. return (
  717. node.nodeType === Node.TEXT_NODE &&
  718. node.textContent.trim().length > 0
  719. );
  720. });
  721. if (hasContent) {
  722. shouldScroll = true;
  723. }
  724. }
  725. });
  726. if (
  727. shouldScroll &&
  728. isAiEmotionAutoScrollEnabled.value &&
  729. activeTab.value === "AiEmotion" &&
  730. !isChartInteracting.value
  731. ) {
  732. debouncedAiEmotionScrollToBottom();
  733. }
  734. });
  735. // 监听AiEmotion页面的主要内容区域的DOM变化
  736. const aiEmotionContainer = tabContentAiEmotion.value;
  737. if (aiEmotionContainer) {
  738. mutationObserver.observe(aiEmotionContainer, {
  739. childList: true,
  740. subtree: true,
  741. attributes: false,
  742. characterData: true,
  743. });
  744. }
  745. // 保存mutationObserver引用以便清理
  746. aiEmotionHeightObserver.value.mutationObserver = mutationObserver;
  747. // 为AiEmotion页面的滚动容器添加滚动事件监听器
  748. if (aiEmotionContainer) {
  749. aiEmotionContainer.addEventListener("scroll", handleAiEmotionUserScroll, {
  750. passive: true,
  751. });
  752. // 保存滚动事件监听器引用以便清理
  753. aiEmotionHeightObserver.value.scrollListener = handleAiEmotionUserScroll;
  754. }
  755. console.log("AiEmotion页面高度监听器已启动");
  756. };
  757. // AiEmotion页面用户滚动检测
  758. const handleAiEmotionUserScroll = () => {
  759. // 标记用户正在滚动
  760. isAiEmotionUserScrolling.value = true;
  761. // 清除之前的定时器
  762. if (aiEmotionScrollTimer.value) {
  763. clearTimeout(aiEmotionScrollTimer.value);
  764. }
  765. // 设置定时器,2秒后恢复自动滚动
  766. // aiEmotionScrollTimer.value = setTimeout(() => {
  767. // isAiEmotionUserScrolling.value = false;
  768. // console.log("AiEmotion页面用户滚动检测:恢复自动滚动");
  769. // }, 2000);
  770. };
  771. // 图表交互状态管理
  772. const handleChartInteractionStart = () => {
  773. console.log("图表交互开始,临时禁用自动滚动");
  774. isChartInteracting.value = true;
  775. // 清除之前的定时器
  776. if (chartInteractionTimer.value) {
  777. clearTimeout(chartInteractionTimer.value);
  778. }
  779. };
  780. const handleChartInteractionEnd = () => {
  781. // 清除之前的定时器
  782. if (chartInteractionTimer.value) {
  783. clearTimeout(chartInteractionTimer.value);
  784. }
  785. // 设置定时器,1秒后恢复自动滚动
  786. chartInteractionTimer.value = setTimeout(() => {
  787. isChartInteracting.value = false;
  788. console.log("图表交互结束,恢复自动滚动");
  789. }, 1000);
  790. };
  791. // 暴露图表交互管理函数给全局使用
  792. window.handleChartInteractionStart = handleChartInteractionStart;
  793. window.handleChartInteractionEnd = handleChartInteractionEnd;
  794. // 处理AiEmotion页面的滚动请求
  795. const handleAiEmotionScrollToBottom = () => {
  796. if (activeTab.value === "AiEmotion") {
  797. const container = tabContentAiEmotion.value;
  798. if (container) {
  799. // 使用nextTick确保DOM已更新
  800. nextTick(() => {
  801. container.scrollTop = container.scrollHeight - container.offsetHeight;
  802. console.log("AiEmotion页面:执行容器滚动到底部");
  803. });
  804. }
  805. }
  806. };
  807. // 停止AiEmotion页面高度监听器
  808. const stopAiEmotionHeightObserver = () => {
  809. isAiEmotionAutoScrollEnabled.value = false;
  810. isAiEmotionUserScrolling.value = false;
  811. // 清理滚动检测定时器
  812. if (aiEmotionScrollTimer.value) {
  813. clearTimeout(aiEmotionScrollTimer.value);
  814. aiEmotionScrollTimer.value = null;
  815. }
  816. if (aiEmotionHeightObserver.value) {
  817. // 清理ResizeObserver
  818. aiEmotionHeightObserver.value.disconnect();
  819. // 清理MutationObserver
  820. if (aiEmotionHeightObserver.value.mutationObserver) {
  821. aiEmotionHeightObserver.value.mutationObserver.disconnect();
  822. aiEmotionHeightObserver.value.mutationObserver = null;
  823. }
  824. // 清理滚动事件监听器
  825. if (
  826. aiEmotionHeightObserver.value.scrollListener &&
  827. tabContentAiEmotion.value
  828. ) {
  829. tabContentAiEmotion.value.removeEventListener(
  830. "scroll",
  831. aiEmotionHeightObserver.value.scrollListener
  832. );
  833. aiEmotionHeightObserver.value.scrollListener = null;
  834. }
  835. aiEmotionHeightObserver.value = null;
  836. }
  837. console.log("AiEmotion页面高度监听器已停止");
  838. };
  839. watch(
  840. () => chatStore.messages.length,
  841. () => {
  842. // console.log('messages变化了')
  843. // 只有在AIchat页面时才执行自动滚动
  844. if (activeTab.value === "AIchat" || activeTab.value === "deepNine") {
  845. throttledSmoothScrollToBottom();
  846. }
  847. },
  848. { deep: false, immediate: true }
  849. );
  850. watch(
  851. () => deepNineStore.messages.length,
  852. () => {
  853. // console.log('messages变化了')
  854. // 只有在AIchat页面时才执行自动滚动
  855. if (activeTab.value === "AIchat" || activeTab.value === "deepNine") {
  856. throttledSmoothScrollToBottom();
  857. }
  858. },
  859. { deep: false, immediate: true }
  860. );
  861. // 监听-当点击夺宝奇兵大模型的历史记录时,滚动到顶部
  862. watch(
  863. () => chatStore.dbqbClickRecord,
  864. async (newValue, oldValue) => {
  865. const container = getCurrentScrollContainer();
  866. if (!container) return;
  867. await nextTick(); // 确保在DOM更新后执行
  868. container.scrollTop = 0;
  869. }
  870. );
  871. // 监听-当点击深度九大模型的历史记录时,滚动到顶部
  872. watch(
  873. () => deepNineStore.dbqbClickRecord,
  874. async (newValue, oldValue) => {
  875. const container = getCurrentScrollContainer();
  876. if (!container) return;
  877. await nextTick(); // 确保在DOM更新后执行
  878. container.scrollTop = 0;
  879. }
  880. );
  881. watch(
  882. () => chatStore.dbqbScrollToTop,
  883. async (newValue, oldValue) => {
  884. const container = getCurrentScrollContainer();
  885. if (!container) return;
  886. await nextTick(); // 确保在DOM更新后执行
  887. container.scrollTop = 0;
  888. }
  889. );
  890. watch(
  891. activeTab,
  892. async () => {
  893. console.log("activeTab变化了", activeTab.value);
  894. if (
  895. activeTab.value == "AIchat" ||
  896. activeTab.value == "AiEmotion" ||
  897. activeTab.value == "deepNine"
  898. ) {
  899. if (historyRecordRef.value && historyRecordRef.value.getHistoryList) {
  900. let model =
  901. activeTab.value == "AIchat"
  902. ? 1
  903. : activeTab.value == "AiEmotion"
  904. ? 2
  905. : 3;
  906. const result = historyRecordRef.value.getHistoryList({
  907. model: model,
  908. token: localStorage.getItem("localToken"),
  909. });
  910. }
  911. }
  912. if (activeTab.value === "AIchat" || activeTab.value === "deepNine") {
  913. isScrolling.value = false; //回复滚动到底部方法
  914. // 停止AiEmotion页面的高度监听器
  915. stopAiEmotionHeightObserver();
  916. setTimeout(() => {
  917. // throttledSmoothScrollToBottom();
  918. }, 100);
  919. } else if (activeTab.value === "AiEmotion") {
  920. // 启动AiEmotion页面的高度监听器
  921. await nextTick(); // 确保DOM更新后启动监听器
  922. startAiEmotionHeightObserver();
  923. } else {
  924. // 其他页面时停止AiEmotion页面的高度监听器
  925. stopAiEmotionHeightObserver();
  926. }
  927. },
  928. { deep: true, immediate: true }
  929. );
  930. // 获取token的核心函数
  931. // const fnGetToken = () => {
  932. // // console.log('进入fnGetToken')
  933. // window.JWready = (ress) => {
  934. // // console.log('进入JWready')
  935. // try {
  936. // ress = JSON.parse(ress);
  937. // // console.log(ress, 'ress')
  938. // } catch (error) {
  939. // console.log(error, "fnGetToken error");
  940. // } //platform为5是app端
  941. // // platform.value = ress.data.platform
  942. // // 处理平台判断
  943. // console.log(ress.data.platform, "ress.data.platform");
  944. // if (!ress.data.platform) {
  945. // // 非App环境通过URL参数获取
  946. // localStorage.setItem(
  947. // "localToken",
  948. // decodeURIComponent(String(getQueryVariable("token")))
  949. // );
  950. // // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  951. // } else {
  952. // // App环境通过桥接获取
  953. // useAppBridge().packageFun(
  954. // "JWgetStorage",
  955. // (response) => {
  956. // const res = JSON.parse(response); // 解析返回的结果
  957. // localStorage.setItem("localToken", res.data);
  958. // // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
  959. // },
  960. // 5,
  961. // {
  962. // key: "token",
  963. // }
  964. // );
  965. // }
  966. // };
  967. // // console.log('出来了')
  968. // // 触发App桥接
  969. // useAppBridge().packageFun("JWwebReady", () => {}, 5, {});
  970. // };
  971. // 在setTimeout中延迟执行
  972. // setTimeout(() => {
  973. // fnGetToken();
  974. // }, 800);
  975. const heightListener = () => {
  976. const tabContainer = getCurrentScrollContainer();
  977. if (!tabContainer) return;
  978. let befortop = 0;
  979. const scrollHandler = () => {
  980. const aftertop = tabContainer.scrollTop;
  981. // 新增底部判断逻辑
  982. const isBottom =
  983. aftertop + tabContainer.offsetHeight + 70 >= tabContainer.scrollHeight;
  984. if (activeTab.value === "AIchat") {
  985. if (aftertop - befortop > 0) {
  986. // console.log("向下滚动");
  987. isScrolling.value = true;
  988. } else {
  989. // console.log("向上滚动");
  990. isScrolling.value = true;
  991. }
  992. // 添加底部状态检测
  993. if (isBottom) {
  994. // console.log("滚动到底部");
  995. isScrolling.value = false;
  996. }
  997. }
  998. befortop = aftertop;
  999. };
  1000. // console.log(isScrolling.value, 'isScrolling.value')
  1001. tabContainer.addEventListener("scroll", scrollHandler);
  1002. };
  1003. const throttledHeightListener = _.throttle(heightListener, 500, {
  1004. trailing: false,
  1005. });
  1006. // const goToRecharge = () => {
  1007. // console.log("点击充值");
  1008. // // http://39.101.133.168:8919/payment/recharge/index?
  1009. // // url=http%3A%2F%2Flocalhost%3A8080%2FLiveActivity%2Fpck
  1010. // // &platform=1
  1011. // // &token=+S4h5QEE1hTIb4CxphrnbZi0+fEeMx8pywnIlrmTmo4QO6IolWnVWu5r+J4rKXMwK41UPfKqyIp+RvWmtM8
  1012. // const userAgent = navigator.userAgent.toLowerCase();
  1013. // const mobileKeywords = ["mobile", "android", "iphone", "ipad", "ipod"];
  1014. // const isMobile = mobileKeywords.some((keyword) =>
  1015. // userAgent.includes(keyword)
  1016. // );
  1017. // console.log(isMobile ? "手机" : "电脑");
  1018. // const url = encodeURI("http://39.101.133.168:8857/aixiaocaishen/homePage");
  1019. // console.log(url, "url");
  1020. // const platform = isMobile ? 2 : 1;
  1021. // const token = encodeURIComponent(localStorage.getItem("localToken"));
  1022. // console.log(token, "token");
  1023. // const rechargeUrl =
  1024. // "http://39.101.133.168:8919/payment/recharge/index?" +
  1025. // "url=" +
  1026. // url +
  1027. // "&platform=" +
  1028. // platform +
  1029. // "&token=" +
  1030. // token;
  1031. // console.log(rechargeUrl, "rechargeUrl");
  1032. // window.location.href = rechargeUrl;
  1033. // // window.open(rechargeUrl)
  1034. // };
  1035. const adjustFooterPosition = async (height) => {
  1036. const html = document.querySelector("html");
  1037. const body = document.querySelector("body");
  1038. const isAndroid = /Android/i.test(navigator.userAgent);
  1039. if (isAndroid) {
  1040. console.log("是安卓设备");
  1041. console.log("window.visualViewport", window.visualViewport.height);
  1042. const homePage = document.querySelector(".homepage");
  1043. // homePage.style.height = `${height}px`;
  1044. // homePage.style.height = `460px`;
  1045. html.scrollTop = 0;
  1046. } else {
  1047. console.log("非安卓设备");
  1048. console.log("调整底部位置", height);
  1049. const homePage = document.querySelector(".homepage");
  1050. homePage.style.height = `${height}px`;
  1051. html.scrollTop = 0;
  1052. }
  1053. await nextTick();
  1054. html.scrollTop = 0;
  1055. setTimeout(() => {
  1056. // 隐藏滚动条
  1057. html.style.overflow = "hidden";
  1058. body.style.overflow = "hidden";
  1059. }, 200);
  1060. };
  1061. // 是否正在输入法组合
  1062. const inputing = ref(false);
  1063. const onFocus = function () {
  1064. const visualViewport = window.visualViewport;
  1065. // 获取可视区域高度
  1066. setTimeout(() => {
  1067. console.log("输入框聚焦");
  1068. console.log(visualViewport.height, "visualViewport.height");
  1069. const keyboardHeight = window.innerHeight - visualViewport.height;
  1070. console.log(window.innerHeight, "window.innerHeight");
  1071. console.log(keyboardHeight, "keyboardHeight");
  1072. adjustFooterPosition(visualViewport.height);
  1073. }, 200);
  1074. };
  1075. const onBlur = function () {
  1076. inputing.value = false;
  1077. const visualViewport = window.visualViewport;
  1078. setTimeout(() => {
  1079. console.log("输入框失焦");
  1080. const keyboardHeight = window.innerHeight - visualViewport.height;
  1081. console.log(window.innerHeight, "window.innerHeight");
  1082. console.log(visualViewport.height, "visualViewport.height");
  1083. console.log(keyboardHeight, "keyboardHeight");
  1084. adjustFooterPosition(visualViewport.height);
  1085. }, 200);
  1086. };
  1087. let touchmoveHandlerRef = null;
  1088. const touchmoveHandler = (e) => {
  1089. if (!dataStore.isFeedback) {
  1090. if (historyRecordRef) {
  1091. if (!historyRecordRef.value.isCollapsed) {
  1092. return;
  1093. }
  1094. }
  1095. // 判断触摸目标是否在当前活动页面的可滚动区域内
  1096. const currentContainer = getCurrentScrollContainer();
  1097. const isScrollableArea =
  1098. currentContainer && currentContainer.contains(e.target);
  1099. // 如果不在可滚动区域,则阻止滚动
  1100. if (!isScrollableArea) {
  1101. e.preventDefault();
  1102. }
  1103. }
  1104. };
  1105. const judgeDevice = async () => {
  1106. // 延时300ms
  1107. await new Promise((resolve) => setTimeout(resolve, 200));
  1108. const userAgent = navigator.userAgent;
  1109. isMobile.value =
  1110. /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
  1111. userAgent
  1112. );
  1113. console.log("当前设备为:", isMobile.value ? "移动端" : "PC端");
  1114. };
  1115. const throttledJudgeDevice = _.throttle(judgeDevice, 300, {
  1116. trailing: false,
  1117. });
  1118. const expandHistory = () => {
  1119. if (
  1120. historyRecordRef.value &&
  1121. historyRecordRef.value.isCollapsed !== undefined
  1122. ) {
  1123. console.log("存在");
  1124. historyRecordRef.value.isCollapsed = !historyRecordRef.value.isCollapsed;
  1125. if (activeTab.value == "AIchat") {
  1126. chatStore.aiChatCall = true;
  1127. } else if (activeTab.value == "AiEmotion") {
  1128. chatStore.aiEmotionCall = true;
  1129. } else if (activeTab.value == "deepNine") {
  1130. chatStore.deepNineCall = true;
  1131. }
  1132. }
  1133. };
  1134. const feedbackBack = () => {
  1135. dataStore.isFeedback = false;
  1136. };
  1137. const backToHome = () => {
  1138. if (isMobile.value) {
  1139. console.log("用户是移动端");
  1140. // 调用原生方法跳转到首页
  1141. uni.postMessage({
  1142. data: {
  1143. val: {
  1144. name: "JWopenView",
  1145. extra: {
  1146. data: {
  1147. type: 3,
  1148. },
  1149. },
  1150. },
  1151. },
  1152. });
  1153. } else {
  1154. console.log("用户是pc端");
  1155. const env = import.meta.env.VITE_ENV;
  1156. console.log("当前的环境为:", env);
  1157. if (env == "development" || env == "test") {
  1158. window.parent.location.href =
  1159. "http://121.89.234.155:8807/hljw/homepage?menu=999999991";
  1160. } else {
  1161. window.parent.postMessage(
  1162. {
  1163. type: "NAVIGATE_TO_HOMEPAGE",
  1164. menu: "999999991",
  1165. },
  1166. "*"
  1167. );
  1168. }
  1169. // window.parent.location.href = window.parent.document.referrer
  1170. }
  1171. };
  1172. // 8.18金币兑换Token start
  1173. const userInfo = ref({
  1174. nickname: "",
  1175. img: "",
  1176. jwcode: "",
  1177. });
  1178. const changeRule = ref({
  1179. gold: 1,
  1180. token: 1,
  1181. });
  1182. const changeLevelList = ref([
  1183. { position: 10, calculatedPosition: 10 },
  1184. { position: 20, calculatedPosition: 20 },
  1185. { position: 50, calculatedPosition: 50 },
  1186. { position: 100, calculatedPosition: 100 },
  1187. { position: 200, calculatedPosition: 200 },
  1188. { position: 500, calculatedPosition: 500 },
  1189. { position: 1000, calculatedPosition: 1000 },
  1190. ]);
  1191. const activeLevel = ref(
  1192. changeLevelList.value[0] || { position: 10, calculatedPosition: 10 }
  1193. );
  1194. const gold = ref(90);
  1195. // 点击剩余次数会弹出的弹窗
  1196. // 新增一个 ref 来控制弹窗的显示与隐藏
  1197. const dialogVisible = ref(false);
  1198. const rechargeDialogVisible = ref(false);
  1199. const confirmDialogVisible = ref(false);
  1200. const changeSuccessDialogVisible = ref(false);
  1201. //兑换锁定状态
  1202. const isExchanging = ref(false);
  1203. // 图片加载错误处理
  1204. const handleImageError = (event) => {
  1205. console.error("图片加载失败:", event.target.src);
  1206. // 可以设置默认图片或重试逻辑
  1207. event.target.src =
  1208. "https://cdn.legu168.com/jtzy/Product/pcjingwang/images/userimg.png";
  1209. };
  1210. // 获取次数
  1211. const showCount = async () => {
  1212. try {
  1213. if (activeTab.value == "deepNine") {
  1214. return;
  1215. }
  1216. if (
  1217. !dataStore.userInfo.img ||
  1218. !dataStore.userInfo.nickname ||
  1219. !dataStore.userInfo.jwcode
  1220. ) {
  1221. console.log("缺少用户信息,调用方法");
  1222. await getUserInfo();
  1223. }
  1224. userInfo.value.nickname = dataStore.userInfo.nickname;
  1225. userInfo.value.img = dataStore.userInfo.img;
  1226. userInfo.value.jwcode = dataStore.userInfo.jwcode;
  1227. console.log("userInfo", userInfo.value);
  1228. const [res, res2] = await Promise.all([
  1229. showExchangeAPI(),
  1230. getGoldCoinAPI({
  1231. token: String(localStorage.getItem("localToken")),
  1232. }),
  1233. ]);
  1234. changeLevelList.value = res.data;
  1235. activeLevel.value = changeLevelList.value[0];
  1236. changeRule.value.token = res.data[0].ratio;
  1237. gold.value = res2.data.total;
  1238. // 显示弹窗
  1239. dialogVisible.value = true;
  1240. console.log("dialogVisible 的值:", dialogVisible.value); // 添加日志确认
  1241. } catch (e) {
  1242. console.error("获取兑换列表出错", e);
  1243. }
  1244. };
  1245. const chooseLevel = (item) => {
  1246. activeLevel.value = item;
  1247. };
  1248. const changeToken = () => {
  1249. if (isExchanging.value) {
  1250. return;
  1251. }
  1252. if (gold.value < activeLevel.value.position) {
  1253. rechargeDialogVisible.value = true;
  1254. return;
  1255. } else {
  1256. confirmDialogVisible.value = true;
  1257. }
  1258. };
  1259. const goRecharge = () => {
  1260. console.log("执行前往充值方法");
  1261. sessionStorage.setItem("rechargeFlag", "1");
  1262. sessionStorage.setItem("activeLevel", JSON.stringify(activeLevel.value));
  1263. if (isMobile.value) {
  1264. console.log("用户是移动端");
  1265. uni.postMessage({
  1266. data: {
  1267. val: {
  1268. name: "JWopenView",
  1269. extra: {
  1270. data: {
  1271. type: 4,
  1272. },
  1273. },
  1274. },
  1275. },
  1276. });
  1277. } else {
  1278. console.log("用户是pc端");
  1279. const env = import.meta.env.VITE_ENV;
  1280. console.log("当前的环境为:", env);
  1281. if (env == "development" || env == "test") {
  1282. window.parent.location.href =
  1283. "http://121.89.234.155:8807/user/myGold?token=" +
  1284. encodeURIComponent(localStorage.getItem("localToken")) +
  1285. "&where=xiaocaishen&successUrl=https://hwjb.homilychart.com/aixiaocaishen/homePage";
  1286. // "&where=xiaocaishen&successUrl=http://192.168.1.5:3000/aixiaocaishen/homePage";
  1287. } else {
  1288. window.parent.location.href =
  1289. "https://web.homilychart.com/user/myGold?token=" +
  1290. encodeURIComponent(localStorage.getItem("localToken")) +
  1291. "&where=xiaocaishen&successUrl=https://mp.homilychart.com/aixiaocaishen/homePage";
  1292. }
  1293. // window.parent.location.href = window.parent.document.referrer
  1294. }
  1295. };
  1296. const goChange = async () => {
  1297. if (isExchanging.value) {
  1298. return;
  1299. }
  1300. try {
  1301. //锁定兑换状态
  1302. isExchanging.value = true;
  1303. await exchangeAPI({
  1304. token: String(localStorage.getItem("localToken")),
  1305. num: activeLevel.value.position,
  1306. });
  1307. confirmDialogVisible.value = false;
  1308. dialogVisible.value = false;
  1309. changeSuccessDialogVisible.value = true;
  1310. // setTimeout(() => {
  1311. // changeSuccessDialogVisible.value = false;
  1312. // }, 2000);
  1313. // 刷新次数
  1314. await chatStore.getUserCount();
  1315. } catch (e) {
  1316. console.error("兑换失败", e);
  1317. }finally{
  1318. //解锁兑换状态
  1319. isExchanging.value = false;
  1320. }
  1321. };
  1322. // 8.18金币兑换Token end
  1323. onMounted(async () => {
  1324. // 检查是否需要显示Token规则提示框
  1325. checkTokenRuleOnPageLoad();
  1326. throttledJudgeDevice();
  1327. // 禁用全局触摸滚动
  1328. touchmoveHandlerRef = touchmoveHandler;
  1329. document.addEventListener("touchmove", touchmoveHandlerRef, {
  1330. passive: false,
  1331. });
  1332. setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
  1333. // 获取次数
  1334. await chatStore.getUserCount();
  1335. // 滚动到底部
  1336. throttledSmoothScrollToBottom();
  1337. // 监听页面高度
  1338. throttledHeightListener();
  1339. // 添加输入框焦点处理
  1340. // handleInputFocus();
  1341. // 初始化视口高度变量
  1342. // updateAppHeight();
  1343. // 添加原生事件监听器
  1344. window.showCountHandler = showCount;
  1345. window.addEventListener("resize", throttledJudgeDevice);
  1346. window.receiveUniAppMessage = async function (messageData) {
  1347. console.log("收到 uni-app 消息:", messageData);
  1348. // 根据消息类型进行不同处理
  1349. switch (messageData.type) {
  1350. case "paymentSuccess":
  1351. // 处理支付成功消息
  1352. const [res1, res2] = await Promise.all([
  1353. godExchangeAPI({ state: 1 }),
  1354. getGoldCoinAPI({ token: String(localStorage.getItem("localToken")) }),
  1355. ]);
  1356. gold.value = res2.data.total;
  1357. rechargeDialogVisible.value = false;
  1358. break;
  1359. default:
  1360. console.log("未知消息类型:", messageData.type);
  1361. }
  1362. };
  1363. if (
  1364. sessionStorage.getItem("rechargeFlag") == "1" &&
  1365. getQueryVariable("successType") == "success"
  1366. ) {
  1367. await godExchangeAPI({ state: 1 });
  1368. await showCount();
  1369. activeLevel.value =
  1370. JSON.parse(sessionStorage.getItem("activeLevel")) ||
  1371. changeLevelList.value[0];
  1372. console.log("activeLevel", activeLevel.value);
  1373. sessionStorage.removeItem("activeLevel");
  1374. sessionStorage.setItem("rechargeFlag", "0");
  1375. }
  1376. });
  1377. onUnmounted(() => {
  1378. window.removeEventListener("resize", throttledJudgeDevice);
  1379. if (touchmoveHandlerRef) {
  1380. console.log("卸载touchmoveHandlerRef组件");
  1381. document.removeEventListener("touchmove", touchmoveHandlerRef);
  1382. }
  1383. // 清理AiEmotion页面的高度监听器
  1384. stopAiEmotionHeightObserver();
  1385. delete window.showCountHandler;
  1386. delete window.receiveUniAppMessage;
  1387. // 清理图表交互相关的定时器和全局函数
  1388. if (chartInteractionTimer.value) {
  1389. clearTimeout(chartInteractionTimer.value);
  1390. }
  1391. if (window.handleChartInteractionStart) {
  1392. delete window.handleChartInteractionStart;
  1393. }
  1394. if (window.handleChartInteractionEnd) {
  1395. delete window.handleChartInteractionEnd;
  1396. }
  1397. });
  1398. </script>
  1399. <template>
  1400. <div class="homepage" id="testId">
  1401. <!-- 历史记录组件 -->
  1402. <HistoryRecord
  1403. ref="historyRecordRef"
  1404. :current-type="activeTab"
  1405. @selectRecord="handleHistorySelect"
  1406. :isMobile="isMobile"
  1407. @showAnnouncement="showAnnouncement"
  1408. @showFeedback="showFeedback"
  1409. />
  1410. <div
  1411. v-if="isMobile && !historyRecordRef?.isCollapsed"
  1412. class="zhezhao"
  1413. @click="expandHistory"
  1414. ></div>
  1415. <el-container
  1416. v-if="!dataStore.isFeedback"
  1417. class="main-container"
  1418. :class="{
  1419. collapsed: !isMobile && historyRecordRef?.isCollapsed,
  1420. unCollapsed: !isMobile && !historyRecordRef?.isCollapsed,
  1421. }"
  1422. >
  1423. <!-- AI小财神头部 logo 次数 公告 -->
  1424. <el-header class="homepage-head">
  1425. <!-- logo -->
  1426. <div class="homepage-logo" v-if="isMobile">
  1427. <img
  1428. class="expand"
  1429. @click="expandHistory"
  1430. src="https://d31zlh4on95l9h.cloudfront.net/images/37fe3d79a8a700f6c674c9f0e7af066b.png"
  1431. alt="icon"
  1432. />
  1433. <img :src="logo" alt="图片加载失败" class="logo1" />
  1434. <!-- <img :src="madeInHL" alt="图片加载失败" class="logo2" /> -->
  1435. </div>
  1436. <div class="homepage-right-group" v-if="isMobile">
  1437. <div
  1438. class="count-badge"
  1439. @click="showCount"
  1440. :class="{ 'deepnine-model': activeTab === 'deepNine' }"
  1441. >
  1442. <img
  1443. v-if="activeTab !== 'deepNine'"
  1444. src="https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png"
  1445. class="action-btn"
  1446. />
  1447. <img
  1448. v-else
  1449. src="https://d31zlh4on95l9h.cloudfront.net/images/4621e0d1463a13c6654fe39215ee64c2.png"
  1450. class="action-btn"
  1451. />
  1452. <div class="count-number">{{ UserCount }}</div>
  1453. <div class="clickGetCount">
  1454. {{
  1455. activeTab === "deepNine" ? "深度九专属Token" : "点击获取Token"
  1456. }}
  1457. </div>
  1458. </div>
  1459. <div class="backToHomeBtn" @click="backToHome()">
  1460. <img
  1461. src="https://d31zlh4on95l9h.cloudfront.net/images/9272c00f21a2724c3a76e375949ee1de.png"
  1462. alt="返回首页"
  1463. class="backImg"
  1464. />
  1465. <div class="backContent">返回首页</div>
  1466. </div>
  1467. <!-- <img
  1468. :src="announcementBtn"
  1469. class="announcement-btn action-btn"
  1470. @click="showAnnouncement"
  1471. />
  1472. <img
  1473. :src="feedbackBtn"
  1474. class="announcement-btn action-btn"
  1475. @click="showFeedback"
  1476. /> -->
  1477. </div>
  1478. </el-header>
  1479. <!-- 主体部分小人 问题轮询图 对话内容 -->
  1480. <el-main class="homepage-body">
  1481. <div class="main-wrapper">
  1482. <section class="tab-section">
  1483. <div
  1484. class="tab-container"
  1485. :class="{
  1486. pcTabContainer: !isMobile,
  1487. }"
  1488. >
  1489. <div class="tab-items-container">
  1490. <div
  1491. v-for="(tab, index) in tabs"
  1492. :key="tab.name"
  1493. @click="setActiveTab(tab.name, index)"
  1494. :class="[
  1495. 'tab-item',
  1496. { active: activeIndex === index && !isAnnouncementVisible },
  1497. ]"
  1498. >
  1499. <span>{{ tab.label }}</span>
  1500. </div>
  1501. </div>
  1502. <div v-if="!isMobile" class="pc-count-badge">
  1503. <div class="pc-countBtn" @click="showCount">
  1504. <div
  1505. class="pc-action-btn"
  1506. :class="{
  1507. 'dbqb-model': activeTab === 'dbqb',
  1508. 'emotion-model': activeTab === 'emotion',
  1509. 'deepnine-model': activeTab === 'deepNine',
  1510. }"
  1511. >
  1512. <div class="pc-count-number">{{ UserCount }}</div>
  1513. </div>
  1514. <div class="pc-clickGetCount">
  1515. {{
  1516. activeTab === "deepNine"
  1517. ? "深度九专属Token"
  1518. : "点击获取Token"
  1519. }}
  1520. </div>
  1521. </div>
  1522. <div class="pc-backToHomeBtn" @click="backToHome()">
  1523. <img
  1524. src="https://d31zlh4on95l9h.cloudfront.net/images/9272c00f21a2724c3a76e375949ee1de.png"
  1525. alt="返回首页"
  1526. class="pc-backImg"
  1527. />
  1528. <div class="pc-backContent">返回首页</div>
  1529. </div>
  1530. </div>
  1531. </div>
  1532. </section>
  1533. <!-- AIchat页面的独立滚动容器 -->
  1534. <div
  1535. v-show="activeTab === 'AIchat'"
  1536. class="tab-content"
  1537. :class="{
  1538. pcTabContent: !isMobile,
  1539. }"
  1540. ref="tabContentAIchat"
  1541. >
  1542. <component
  1543. v-if="activeTab === 'AIchat'"
  1544. :is="activeComponent"
  1545. :messages="messages"
  1546. @updateMessage="updateMessage"
  1547. @sendMessage="sendMessage"
  1548. @ensureAIchat="ensureAIchat"
  1549. @enableInput="enableInput"
  1550. />
  1551. </div>
  1552. <!-- AiEmotion页面的独立滚动容器 -->
  1553. <div
  1554. v-show="activeTab === 'AiEmotion'"
  1555. class="tab-content"
  1556. :class="{
  1557. pcTabContent: !isMobile,
  1558. }"
  1559. ref="tabContentAiEmotion"
  1560. >
  1561. <component
  1562. v-if="activeTab === 'AiEmotion'"
  1563. :is="activeComponent"
  1564. :messages="messages"
  1565. @updateMessage="updateMessage"
  1566. @sendMessage="sendMessage"
  1567. @ensureAIchat="ensureAIchat"
  1568. @enableInput="enableInput"
  1569. @scrollToBottom="handleAiEmotionScrollToBottom"
  1570. @showCount="showCount"
  1571. ref="aiEmotionRef"
  1572. />
  1573. </div>
  1574. <!-- deepNine页面的独立滚动容器 -->
  1575. <div
  1576. v-show="activeTab === 'deepNine'"
  1577. class="tab-content"
  1578. :class="{
  1579. pcTabContent: !isMobile,
  1580. }"
  1581. ref="tabContentDeepNine"
  1582. >
  1583. <component
  1584. v-if="activeTab === 'deepNine'"
  1585. :is="activeComponent"
  1586. :messages="messages"
  1587. @updateMessage="updateMessage"
  1588. @sendMessage="sendMessage"
  1589. @ensureAIchat="ensureAIchat"
  1590. @enableInput="enableInput"
  1591. @scrollToBottom="handleDeepNineScrollToBottom"
  1592. @showCount="showCount"
  1593. ref="deepNineRef"
  1594. />
  1595. </div>
  1596. </div>
  1597. </el-main>
  1598. <!-- 尾部 问题输入框 深度思考 多语言 语音播报 -->
  1599. <el-footer
  1600. class="homepage-footer"
  1601. :class="{
  1602. pcFooter: !isMobile,
  1603. }"
  1604. id="input"
  1605. >
  1606. <!-- 第一行按钮 -->
  1607. <div class="footer-first-line">
  1608. <div class="left-group">
  1609. <!-- <img v-if="isThinking" :src="thinkActive" @click="toggleThink" class="action-btn" />
  1610. <img v-else :src="thinkNoActive" @click="toggleThink" class="action-btn" />
  1611. <img :src="languageBtn" @click="changeLanguage" class="action-btn" /> -->
  1612. <!-- PC端按钮 -->
  1613. <template v-if="!isMobile">
  1614. <!-- 夺宝奇兵大模型按钮 -->
  1615. <img
  1616. :src="activeTab === 'AIchat' ? dbqbButton01 : dbqbButton02"
  1617. @click="setActiveTab('AIchat', 0)"
  1618. class="action-btn model-btn"
  1619. alt="夺宝奇兵大模型"
  1620. />
  1621. <!-- AI情绪大模型按钮 -->
  1622. <img
  1623. :src="
  1624. activeTab === 'AiEmotion' ? emotionButton01 : emotionButton02
  1625. "
  1626. @click="setActiveTab('AiEmotion', 1)"
  1627. class="action-btn model-btn"
  1628. alt="AI情绪大模型"
  1629. />
  1630. <!-- 深度九大模型按钮 -->
  1631. <img
  1632. :src="
  1633. activeTab === 'deepNine'
  1634. ? 'https://d31zlh4on95l9h.cloudfront.net/images/07349ebc846a6772b1ab8a47b54635ff.png'
  1635. : 'https://d31zlh4on95l9h.cloudfront.net/images/29f0ad8e4e47f58952cc4a957ce9db10.png'
  1636. "
  1637. @click="setActiveTab('deepNine', 2)"
  1638. class="action-btn model-btn"
  1639. alt="深度九大模型"
  1640. />
  1641. </template>
  1642. <!-- 手机端下拉选择 - 自定义组件 -->
  1643. <template v-else>
  1644. <div class="custom-select-container">
  1645. <div class="custom-select-header" @click="toggleDropdown">
  1646. <div class="select-option-with-image">
  1647. <img :src="getSelectedOptionImage()" class="option-image" />
  1648. <span>{{ getSelectedOptionLabel() }}</span>
  1649. </div>
  1650. <div
  1651. class="dropdown-arrow"
  1652. :class="{ 'arrow-up': isDropdownOpen }"
  1653. >
  1654. <img
  1655. src="https://d31zlh4on95l9h.cloudfront.net/images/5f96dfcbff643cbbcf0f2790dbb4e54a.png"
  1656. style="width: 24px; height: 24px; object-fit: contain"
  1657. alt="箭头"
  1658. />
  1659. </div>
  1660. </div>
  1661. <div class="custom-select-dropdown" v-if="isDropdownOpen">
  1662. <div
  1663. v-for="option in modelOptions"
  1664. :key="option.value"
  1665. class="custom-select-option"
  1666. :class="{
  1667. 'option-selected': activeTabMobile === option.value,
  1668. }"
  1669. @click="selectOption(option.value)"
  1670. >
  1671. <div class="select-option-with-image">
  1672. <img :src="option.image" class="option-image" />
  1673. <span>{{ option.label }}</span>
  1674. </div>
  1675. </div>
  1676. </div>
  1677. </div>
  1678. </template>
  1679. <!-- <img v-if="
  1680. getCurrentAudioStore().isVoiceEnabled &&
  1681. getCurrentAudioStore().isPlaying
  1682. " :src="voice" @click="toggleVoice" class="action-btn" style="animation: pulse 1.5s infinite" />
  1683. <img v-else-if="
  1684. getCurrentAudioStore().isVoiceEnabled &&
  1685. !getCurrentAudioStore().isPlaying
  1686. " :src="voiceNoActive" @click="toggleVoice" class="action-btn" />
  1687. <img v-else :src="voiceNoActive" @click="toggleVoice" class="action-btn" /> -->
  1688. </div>
  1689. </div>
  1690. <!-- 第二行输入框 -->
  1691. <div class="footer-second-line">
  1692. <!-- <img :src="msgBtn" class="msg-icon" /> -->
  1693. <div class="input-container">
  1694. <el-input
  1695. type="textarea"
  1696. v-model="message"
  1697. @focus="onFocus"
  1698. @blur="onBlur"
  1699. :autosize="{ minRows: 1, maxRows: 4 }"
  1700. class="msg-input"
  1701. @keydown.enter.exact.prevent="
  1702. isLoading || isInputDisabled ? null : sendMessage()
  1703. "
  1704. :disabled="isInputDisabled"
  1705. resize="none"
  1706. :class="{ input: !message && !inputing }"
  1707. @compositionstart="inputing = true"
  1708. @compositionend="inputing = false"
  1709. >
  1710. </el-input>
  1711. <img
  1712. :src="
  1713. isInputDisabled
  1714. ? 'https://d31zlh4on95l9h.cloudfront.net/images/aa192bcbc1682c97e1bc6fb422f2afff.png'
  1715. : 'https://d31zlh4on95l9h.cloudfront.net/images/e6ec2ae238ced85b74e0912e988f243e.png'
  1716. "
  1717. @click="sendMessage"
  1718. class="action-btn send-btn-inner"
  1719. :style="{
  1720. opacity: isInputDisabled ? 0.5 : 1,
  1721. cursor: isInputDisabled ? 'not-allowed' : 'pointer',
  1722. }"
  1723. />
  1724. </div>
  1725. </div>
  1726. </el-footer>
  1727. </el-container>
  1728. <el-container
  1729. v-else
  1730. class="main-container"
  1731. :class="{
  1732. collapsed: historyRecordRef?.isCollapsed && !isMobile,
  1733. unCollapsed: !historyRecordRef?.isCollapsed && !isMobile,
  1734. }"
  1735. >
  1736. <el-header class="homepage-head">
  1737. <!-- logo -->
  1738. <div class="homepage-logo">
  1739. <img
  1740. src="https://d31zlh4on95l9h.cloudfront.net/images/4c91fce359a1c184772857594c38e3b4.png"
  1741. alt="返回按钮"
  1742. class="backToHomeImg"
  1743. @click="feedbackBack"
  1744. />
  1745. <!-- <img :src="madeInHL" alt="图片加载失败" class="logo2" /> -->
  1746. </div>
  1747. <div class="homepage-right-group">
  1748. <div class="count-badge" @click="showCount">
  1749. <img
  1750. v-if="activeTab !== 'deepNine'"
  1751. src="https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png"
  1752. class="action-btn"
  1753. />
  1754. <img
  1755. v-else
  1756. src="https://d31zlh4on95l9h.cloudfront.net/images/4621e0d1463a13c6654fe39215ee64c2.png"
  1757. class="action-btn"
  1758. />
  1759. <div class="count-number">{{ UserCount }}</div>
  1760. <div class="clickGetCount">点击获取Token</div>
  1761. </div>
  1762. <div class="backToHomeBtn" @click="backToHome()">
  1763. <img
  1764. src="https://d31zlh4on95l9h.cloudfront.net/images/9272c00f21a2724c3a76e375949ee1de.png"
  1765. alt="返回首页"
  1766. class="backImg"
  1767. />
  1768. <div class="backContent">返回首页</div>
  1769. </div>
  1770. <!-- <img
  1771. :src="announcementBtn"
  1772. class="announcement-btn action-btn"
  1773. @click="showAnnouncement"
  1774. />
  1775. <img
  1776. :src="feedbackBtn"
  1777. class="announcement-btn action-btn"
  1778. @click="showFeedback"
  1779. /> -->
  1780. </div>
  1781. </el-header>
  1782. <!-- 主体部分小人 问题轮询图 对话内容 -->
  1783. <el-main class="homepage-body">
  1784. <component :is="activeTwoTab" />
  1785. </el-main>
  1786. </el-container>
  1787. <!-- 弹窗 -->
  1788. <!-- 新增弹窗组件 -->
  1789. <el-dialog v-if="!isMobile" v-model="dialogVisible" width="48%">
  1790. <!-- 中间内容部分 -->
  1791. <div class="changeMsg">
  1792. <div class="changeInfo">
  1793. <div class="changeImg">
  1794. <img
  1795. :src="userInfo.img"
  1796. alt="头像"
  1797. class="changeImgClass"
  1798. @error="handleImageError"
  1799. />
  1800. </div>
  1801. <div class="changeContent">
  1802. <div class="changeUsername">{{ userInfo.nickname }}</div>
  1803. <div class="changeJwcode">精网号{{ userInfo.jwcode }}</div>
  1804. </div>
  1805. </div>
  1806. <div class="changeRule" @click="openTokenRuleDialog">
  1807. 兑换规则{{ changeRule.gold }}金币={{ changeRule.token }}Token
  1808. <div>点击查看详情</div>
  1809. </div>
  1810. </div>
  1811. <div class="token-info-row">
  1812. <div class="token-info-left">
  1813. <div class="token-total1">Token总数{{ totalToken }}</div>
  1814. <div class="token-paid">永久Token{{ paidToken }}</div>
  1815. <div class="token-free">
  1816. 免费Token{{ freeToken }}
  1817. (到期时间{{ freeTokenExpireTime }})
  1818. </div>
  1819. </div>
  1820. <div class="token-info-right" @click="showTokenRecord">
  1821. 查看Token变动记录
  1822. </div>
  1823. </div>
  1824. <div class="changeLevel">
  1825. <div class="changeLevelTitle">兑换Token</div>
  1826. <div class="changeLevelContent">
  1827. <div
  1828. class="changeLevelItems"
  1829. v-for="item in changeLevelList"
  1830. :key="item"
  1831. :class="{
  1832. changeLevelItemsActive: item.position == activeLevel.position,
  1833. }"
  1834. @click="chooseLevel(item)"
  1835. >
  1836. <div class="changeLevelItem">
  1837. <div class="changeLevelItemToken">
  1838. <img
  1839. src="https://d31zlh4on95l9h.cloudfront.net/images/403ef762dd2f335df3b0c9e3fe488375.png"
  1840. alt="token"
  1841. class="changeLevelItemTokenImg"
  1842. />
  1843. {{ item.calculatedPosition }}
  1844. </div>
  1845. <div class="changeLevelItemToken">{{ item.position }} 金币</div>
  1846. </div>
  1847. </div>
  1848. </div>
  1849. </div>
  1850. <div class="changeNow">
  1851. 应付金额
  1852. <div class="changePay">{{ activeLevel.position }}</div>
  1853. (金币余额{{ gold }})
  1854. </div>
  1855. <div class="changeBtn" @click="changeToken">立即兑换</div>
  1856. </el-dialog>
  1857. <el-dialog v-else v-model="dialogVisible" width="80%">
  1858. <!-- 中间内容部分 -->
  1859. <div class="changeMsg">
  1860. <div class="changeInfo">
  1861. <div class="changeImg">
  1862. <img
  1863. :src="userInfo.img"
  1864. alt="头像"
  1865. class="changeImgClass"
  1866. @error="handleImageError"
  1867. />
  1868. </div>
  1869. <div class="changeContent">
  1870. <div class="changeJwcode">{{ userInfo.jwcode }}</div>
  1871. </div>
  1872. </div>
  1873. <div class="changeLevelTitle">
  1874. <div class="changeRule" @click="openTokenRuleDialog">
  1875. 兑换规则{{ changeRule.gold }}金币={{ changeRule.token }}Token
  1876. <div>点击查看详情</div>
  1877. </div>
  1878. </div>
  1879. </div>
  1880. <div class="changeLevel">
  1881. <div class="token-record">
  1882. <div class="token-row-top">
  1883. <div class="token-total-mobile">Token总数{{ totalToken }}</div>
  1884. <div class="token-change" @click="showTokenRecord">查看Token变动记录</div>
  1885. </div>
  1886. <div class="token-paid-mobile">付费Token{{ paidToken }}</div>
  1887. <div class="token-free-mobile">免费Token{{ freeToken }}
  1888. <br>
  1889. (到期时间{{ freeTokenExpireTime }})
  1890. </div>
  1891. </div>
  1892. <div class="changeLevelContent">
  1893. <div
  1894. class="changeLevelItems"
  1895. v-for="item in changeLevelList"
  1896. :key="item"
  1897. :class="{
  1898. changeLevelItemsActive: item.position == activeLevel.position,
  1899. }"
  1900. @click="chooseLevel(item)"
  1901. >
  1902. <div class="changeLevelItem">
  1903. <div class="changeLevelItemToken">
  1904. <img
  1905. src="https://d31zlh4on95l9h.cloudfront.net/images/403ef762dd2f335df3b0c9e3fe488375.png"
  1906. alt="token"
  1907. class="changeLevelItemTokenImg"
  1908. />
  1909. {{ item.calculatedPosition }}
  1910. </div>
  1911. <div class="changeLevelItemToken">{{ item.position }} 金币</div>
  1912. </div>
  1913. </div>
  1914. </div>
  1915. </div>
  1916. <div class="changeNow">
  1917. 应付金额
  1918. <div class="changePay">{{ activeLevel.position }}</div>
  1919. (金币余额{{ gold }})
  1920. </div>
  1921. <div class="changeBtn" @click="changeToken">立即兑换</div>
  1922. </el-dialog>
  1923. <!-- Token变动记录弹窗 -->
  1924. <el-dialog
  1925. v-model="tokenRecordVisible"
  1926. :width="isMobile ? '90%' : '60%'"
  1927. :show-close="true"
  1928. >
  1929. <!-- 顶部统计区 -->
  1930. <div class="token-record-header">
  1931. <div class="token-total">Token总数{{ totalToken }}</div>
  1932. <div class="token-row">
  1933. <div class="token-free">免费Token{{ freeToken }}
  1934. <br>
  1935. (到期时间{{ freeTokenExpireTime }})</div>
  1936. <div class="token-permanent">永久Token{{ paidToken }}</div>
  1937. </div>
  1938. </div>
  1939. <!-- 表格区 -->
  1940. <div class="token-record-table">
  1941. <table>
  1942. <thead>
  1943. <tr>
  1944. <th>序号</th>
  1945. <th>数量变化</th>
  1946. <th>Token类型</th>
  1947. <th>操作时间</th>
  1948. <th>备注</th>
  1949. </tr>
  1950. </thead>
  1951. <tbody>
  1952. <tr v-for="(item, index) in tokenRecordList" :key="item.id">
  1953. <td>{{ index + 1 }}</td>
  1954. <td :class="{ 'token-plus': item.change > 0, 'token-minus': item.change < 0 }">
  1955. {{ item.change > 0 ? '+' : '' }}{{ item.change }}
  1956. </td>
  1957. <td>{{ item.type }}</td>
  1958. <td>{{ item.time }}</td>
  1959. <td>{{ item.remark }}</td>
  1960. </tr>
  1961. </tbody>
  1962. </table>
  1963. </div>
  1964. </el-dialog>
  1965. <el-dialog
  1966. v-model="rechargeDialogVisible"
  1967. :width="isMobile ? '60%' : '30%'"
  1968. :show-close="false"
  1969. >
  1970. <div class="rechargeDialogTitle">温馨提示</div>
  1971. <div class="rechargeDialogContent">
  1972. 尊敬的用户您好您当前的金币余额不足无法进行兑换可充值金币后进行兑换点击下方的前往充值可进行充值
  1973. </div>
  1974. <div class="rechargeDialogBtnGroup">
  1975. <div class="recharge" @click="goRecharge">前往充值</div>
  1976. <div
  1977. class="rechargeDialogCancel"
  1978. @click="rechargeDialogVisible = false"
  1979. >
  1980. 取消
  1981. </div>
  1982. </div>
  1983. </el-dialog>
  1984. <el-dialog
  1985. v-model="confirmDialogVisible"
  1986. :width="isMobile ? '60%' : '30%'"
  1987. :show-close="false"
  1988. align-center="true"
  1989. >
  1990. <div class="confirmDialogTitle">兑换</div>
  1991. <div class="confirmDialogContent">
  1992. 尊敬的用户您好您确认要花费{{ activeLevel.position }}金币兑换{{
  1993. activeLevel.calculatedPosition
  1994. }}Token吗
  1995. </div>
  1996. <div class="confirmDialogBtnGroup">
  1997. <div class="confirmDialogConfirm" @click="goChange()">确认</div>
  1998. <div class="confirmDialogCancel" @click="confirmDialogVisible = false">
  1999. 取消
  2000. </div>
  2001. </div>
  2002. </el-dialog>
  2003. <el-dialog
  2004. v-model="changeSuccessDialogVisible"
  2005. :width="isMobile ? '60%' : '30%'"
  2006. :show-close="false"
  2007. class="changeSuccessDialog"
  2008. align-center
  2009. center
  2010. >
  2011. <div class="changeSuccessDialogTitle">
  2012. <img
  2013. v-if="!isMobile"
  2014. src="https://d31zlh4on95l9h.cloudfront.net/images/84edd341b2ddec464fc4475254f7a309.png"
  2015. style="scale: 0.7"
  2016. alt="token图标"
  2017. />
  2018. 兑换成功
  2019. </div>
  2020. <div class="changeSuccessDialogContent">
  2021. 尊敬的用户恭喜您成功兑换{{ activeLevel.calculatedPosition }} Token
  2022. </div>
  2023. <div class="changeSuccessDialogFooter">
  2024. <button
  2025. class="confirmButton"
  2026. @click="changeSuccessDialogVisible = false"
  2027. >
  2028. 确定
  2029. </button>
  2030. </div>
  2031. </el-dialog>
  2032. <!-- 免费Token清除提示弹窗 -->
  2033. <el-dialog v-model="showTokenClearDialog" :width="isMobile ? '80%' : '40%'" :show-close="false" align-center>
  2034. <div class="tokenClearDialogTitle">通知</div>
  2035. <div class="tokenClearDialogContent">
  2036. {{ currentClearText }}
  2037. </div>
  2038. <div class="tokenClearDialogFooter">
  2039. <button class="tokenClearDialogBtn" @click="closeTokenClearDialog">
  2040. 我已知晓
  2041. </button>
  2042. </div>
  2043. </el-dialog>
  2044. <!-- Token规则提示框 -->
  2045. <div
  2046. v-if="tokenRuleDialogVisible"
  2047. class="tokenRuleDialog"
  2048. @click="closeTokenRuleDialog"
  2049. >
  2050. <div class="tokenRuleDialogContent" @click.stop>
  2051. <div class="tokenRuleDialogClose" @click="closeTokenRuleDialog">
  2052. <el-icon>
  2053. <Close />
  2054. </el-icon>
  2055. </div>
  2056. <div class="tokenRuleDialogTitle">Token规则</div>
  2057. <div class="tokenRuleSection">
  2058. <div class="tokenRuleSectionTitle">Token消耗规则</div>
  2059. <div class="tokenRuleItem">
  2060. "夺宝奇兵大模型""AI情绪大模型"中搜索股票消耗普通Token"深度九大模型"中搜索股票消耗专属Token
  2061. </div>
  2062. <div class="tokenRuleItem">
  2063. "夺宝奇兵大模型""AI情绪大模型""深度九大模型"中搜索股票若搜索成功内容正常生成则会消耗1Token
  2064. </div>
  2065. <div class="tokenRuleItem">
  2066. "夺宝奇兵大模型""AI情绪大模型""深度九大模型"中搜索股票若搜索有误无法生成内容则不会消耗Token
  2067. </div>
  2068. <div class="tokenRuleItem">
  2069. 搜索同一只股票产出内容相同时只扣除1Token
  2070. </div>
  2071. <div class="tokenRuleItem">
  2072. "夺宝奇兵"中的Token和"AI小财神"中的普通Token是互通的
  2073. </div>
  2074. </div>
  2075. <div class="tokenRuleSection">
  2076. <div class="tokenRuleSectionTitle">Token兑换规则</div>
  2077. <div class="tokenRuleItem">
  2078. 点击右上角"获取Token"即可进入Token兑换页进行金币兑换Token只能兑换普通Token
  2079. </div>
  2080. <div class="tokenRuleItem">
  2081. 金币兑换Token的比例为1金币=1Token一经兑换不予退还
  2082. </div>
  2083. </div>
  2084. <div class="tokenRuleNote">
  2085. 注意报告生成过程中请耐心等待在此期间请勿进行页面刷新操作以免导致报告生成进程中断
  2086. </div>
  2087. </div>
  2088. </div>
  2089. </div>
  2090. </template>
  2091. <style scoped>
  2092. /* 标签栏 */
  2093. .tab-container {
  2094. display: flex;
  2095. margin-bottom: 10px;
  2096. height: 100%;
  2097. position: relative;
  2098. align-items: center;
  2099. width: 100%;
  2100. /* 使用三段式布局 */
  2101. justify-content: space-between;
  2102. }
  2103. .tab-items-container {
  2104. display: flex;
  2105. justify-content: center;
  2106. flex: 1;
  2107. gap: 200px;
  2108. }
  2109. .pcTabContainer {
  2110. padding: 0 20px;
  2111. /* 添加内边距,避免内容贴边 */
  2112. }
  2113. .tab-item {
  2114. cursor: pointer;
  2115. padding: 8px 12px;
  2116. font-size: clamp(18px, 3vw, 20px);
  2117. color: #fff;
  2118. transition: all 0.3s;
  2119. border-bottom: 2px solid transparent;
  2120. font-weight: bold;
  2121. /* 确保tab项目不会被挤压 */
  2122. flex-shrink: 0;
  2123. }
  2124. .tab-item.active {
  2125. /* color: #000;
  2126. border-color: #000; */
  2127. background: linear-gradient(0deg, #ffffff, #fec13e);
  2128. -webkit-background-clip: text;
  2129. background-clip: text;
  2130. -webkit-text-fill-color: transparent;
  2131. color: transparent;
  2132. border-color: #fec13e;
  2133. }
  2134. .tab-item:not(.active):hover {
  2135. color: #999999;
  2136. }
  2137. .tab-content {
  2138. overflow-y: auto;
  2139. overflow-x: hidden;
  2140. scroll-behavior: smooth;
  2141. height: 100%;
  2142. /* 添加平滑滚动效果 */
  2143. }
  2144. .pcTabContent {
  2145. padding: 0 6%;
  2146. }
  2147. .backToHomeImg {
  2148. width: 40px;
  2149. height: 40px;
  2150. margin-left: 10px;
  2151. }
  2152. @media (max-width: 768px) {
  2153. .tab-container {
  2154. justify-content: center;
  2155. }
  2156. .tab-items-container {
  2157. justify-content: center;
  2158. gap: 0px;
  2159. /* 移动端使用更小的间距 */
  2160. }
  2161. .tab-item {
  2162. font-size: clamp(14px, 3vw, 16px);
  2163. padding: 6px 10px;
  2164. }
  2165. }
  2166. /* Token清除弹窗样式 */
  2167. .tokenClearDialogTitle {
  2168. font-size: 1.7rem;
  2169. color: #4e86fe;
  2170. display: flex;
  2171. justify-content: center;
  2172. align-items: center;
  2173. letter-spacing: 10px;
  2174. }
  2175. .tokenClearDialogContent {
  2176. padding: 20px;
  2177. font-size: 1.2rem;
  2178. line-height: 1.8;
  2179. color: #333;
  2180. }
  2181. .tokenClearDialogFooter {
  2182. display: flex;
  2183. justify-content: center;
  2184. padding: 0 20px 20px;
  2185. }
  2186. .tokenClearDialogBtn {
  2187. color: white;
  2188. background-color: #4e86fe;
  2189. padding: 10px 30px;
  2190. border-radius: 13px;
  2191. cursor: pointer;
  2192. border: none;
  2193. font-size: 1rem;
  2194. transition: all 0.3s ease;
  2195. }
  2196. .tokenClearDialogBtn:hover {
  2197. background-color: #3a73e6;
  2198. }
  2199. </style>
  2200. <style scoped>
  2201. html {
  2202. height: 100dvh;
  2203. overflow: hidden !important;
  2204. position: fixed;
  2205. margin: 0;
  2206. padding: 0;
  2207. -webkit-overflow-scrolling: auto;
  2208. /* 禁用 iOS 弹性滚动 */
  2209. }
  2210. body {
  2211. height: 100dvh;
  2212. overflow: clip;
  2213. margin: 0;
  2214. padding: 0;
  2215. -webkit-overflow-scrolling: auto;
  2216. /* 禁用 iOS 弹性滚动 */
  2217. position: fixed;
  2218. }
  2219. #app {
  2220. overflow: hidden;
  2221. height: 100%;
  2222. margin: 0;
  2223. padding: 0;
  2224. }
  2225. .homepage {
  2226. /* height: var(--app-height, 100vh); */
  2227. height: var(--app-height, 100vh);
  2228. margin: 0 auto;
  2229. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/98c8230d386012c9f1e70bf05a30de5e.png");
  2230. background-size: 100% 100%;
  2231. background-repeat: no-repeat;
  2232. background-position: center;
  2233. display: flex;
  2234. flex-direction: row;
  2235. /* 改为水平布局 */
  2236. overflow: hidden;
  2237. position: fixed;
  2238. top: 0;
  2239. left: 0;
  2240. right: 0;
  2241. bottom: 0;
  2242. width: 100%;
  2243. /* -webkit-overflow-scrolling: touch; */
  2244. }
  2245. .zhezhao {
  2246. width: 100%;
  2247. height: 100%;
  2248. background-color: rgba(0, 0, 0, 0.5);
  2249. z-index: 100;
  2250. position: fixed;
  2251. }
  2252. @media (min-width: 769px) {
  2253. .main-container {
  2254. flex: 1;
  2255. transition: margin-left 0.3s ease;
  2256. display: flex;
  2257. flex-direction: column;
  2258. overflow: hidden;
  2259. }
  2260. .main-container.unCollapsed {
  2261. margin-left: 300px;
  2262. /* 为历史记录组件留出空间 */
  2263. }
  2264. /* 当历史记录组件折叠时调整主容器边距 */
  2265. .main-container.collapsed {
  2266. margin-left: 40px;
  2267. }
  2268. }
  2269. /* 移动端适配 */
  2270. @media (max-width: 768px) {
  2271. .homepage {
  2272. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/90d31d7052e729c63acb9e2cb94d1307.png");
  2273. }
  2274. }
  2275. .homepage .el-container {
  2276. height: 100%;
  2277. flex-direction: column;
  2278. display: flex;
  2279. width: 100%;
  2280. overflow: hidden;
  2281. /* 防止容器滚动 */
  2282. }
  2283. .el-container .el-header {
  2284. flex-shrink: 0;
  2285. /* 防止头部压缩 */
  2286. height: auto;
  2287. min-height: 60px;
  2288. padding: 5px 0;
  2289. position: sticky;
  2290. top: 0;
  2291. z-index: 10;
  2292. /* background-color: rgba(255, 255, 255, 0.9); */
  2293. }
  2294. .el-container .el-main {
  2295. flex: 1;
  2296. /* 自动占据剩余空间 */
  2297. overflow: hidden;
  2298. /* 主容器不滚动 */
  2299. display: flex;
  2300. flex-direction: column;
  2301. min-height: 0;
  2302. /* 允许内容区域缩小 */
  2303. position: relative;
  2304. height: auto;
  2305. }
  2306. .el-container .el-footer {
  2307. flex-shrink: 0;
  2308. height: auto;
  2309. min-height: 70px;
  2310. position: sticky;
  2311. bottom: 0;
  2312. z-index: 20;
  2313. background-color: rgba(211, 24, 24, 0);
  2314. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  2315. -webkit-transform: translateZ(0);
  2316. transform: translateZ(0);
  2317. padding-bottom: env(safe-area-inset-bottom, 0);
  2318. /* 适配iPhone X及以上的底部安全区域 */
  2319. }
  2320. .homepage-head {
  2321. padding: 0px;
  2322. display: flex;
  2323. position: relative;
  2324. justify-content: space-between;
  2325. width: 100%;
  2326. }
  2327. .homepage-right-group {
  2328. display: flex;
  2329. /* gap: 8px; */
  2330. align-items: center;
  2331. margin-left: auto;
  2332. margin-right: 5px;
  2333. }
  2334. .homepage-right-group .action-btn {
  2335. height: 40px;
  2336. }
  2337. .count-badge {
  2338. position: relative;
  2339. cursor: pointer;
  2340. }
  2341. .count-badge:hover {
  2342. transform: scale(1.05);
  2343. }
  2344. .count-number {
  2345. position: absolute;
  2346. top: 16px;
  2347. right: 0px;
  2348. width: 68%;
  2349. text-align: center;
  2350. color: #6a00ff;
  2351. }
  2352. .deepnine-model .count-number {
  2353. color: blue;
  2354. font-size: 12px;
  2355. font-weight: bold;
  2356. }
  2357. .clickGetCount {
  2358. width: 100%;
  2359. text-align: center;
  2360. color: white;
  2361. font-size: 12px;
  2362. }
  2363. .backToHomeBtn {
  2364. display: flex;
  2365. flex-direction: column;
  2366. justify-content: center;
  2367. align-items: center;
  2368. cursor: pointer;
  2369. }
  2370. .backToHomeBtn:hover {
  2371. transform: scale(1.05);
  2372. }
  2373. .backImg {
  2374. width: 60%;
  2375. height: auto;
  2376. }
  2377. .backContent {
  2378. width: 100%;
  2379. text-align: center;
  2380. color: white;
  2381. font-size: 12px;
  2382. white-space: nowrap;
  2383. }
  2384. .pc-count-badge {
  2385. display: flex;
  2386. height: 100%;
  2387. gap: 10px;
  2388. align-items: center;
  2389. /* 移除绝对定位,使用flex布局自然定位 */
  2390. flex-shrink: 0;
  2391. }
  2392. .pc-countBtn {
  2393. height: 60px;
  2394. width: 115px;
  2395. position: relative;
  2396. cursor: pointer;
  2397. display: flex;
  2398. flex-direction: column;
  2399. align-items: center;
  2400. }
  2401. .pc-countBtn:hover {
  2402. transform: scale(1.05);
  2403. }
  2404. .pc-action-btn {
  2405. width: 100%;
  2406. height: 70%;
  2407. background-repeat: no-repeat;
  2408. background-size: 100% 100%;
  2409. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/74e20c65c9ef2526477c63ad68698a50.png");
  2410. }
  2411. .pc-action-btn.deepnine-model {
  2412. background-image: url("https://d31zlh4on95l9h.cloudfront.net/images/4621e0d1463a13c6654fe39215ee64c2.png");
  2413. }
  2414. .pc-count-number {
  2415. position: absolute;
  2416. top: 15px;
  2417. right: 4px;
  2418. width: 68%;
  2419. text-align: center;
  2420. color: #6a00ff;
  2421. }
  2422. .deepnine-model .pc-count-number {
  2423. color: blue;
  2424. font-size: 15px;
  2425. font-weight: bold;
  2426. }
  2427. .pc-clickGetCount {
  2428. width: 100%;
  2429. text-align: center;
  2430. color: white;
  2431. font-size: 12px;
  2432. }
  2433. .pc-backToHomeBtn {
  2434. width: 30%;
  2435. height: 100%;
  2436. display: flex;
  2437. flex-direction: column;
  2438. align-items: center;
  2439. cursor: pointer;
  2440. }
  2441. .pc-backImg {
  2442. width: 100%;
  2443. height: auto;
  2444. }
  2445. .pc-backContent {
  2446. width: 100%;
  2447. text-align: center;
  2448. color: white;
  2449. font-size: 12px;
  2450. }
  2451. .pc-backToHomeBtn:hover {
  2452. transform: scale(1.05);
  2453. }
  2454. .homepage-right-group .announcement-btn {
  2455. cursor: pointer;
  2456. transition: transform 0.3s;
  2457. }
  2458. .homepage-right-group .announcement-btn:hover {
  2459. transform: scale(1.3);
  2460. }
  2461. .homepage-body {
  2462. padding: 0px;
  2463. display: flex;
  2464. flex-direction: column;
  2465. flex: 1;
  2466. min-height: 0;
  2467. /* 允许内容区域缩小 */
  2468. overflow: hidden;
  2469. }
  2470. .main-wrapper {
  2471. height: 100%;
  2472. display: flex;
  2473. flex-direction: column;
  2474. flex: 1;
  2475. min-height: 0;
  2476. /* 允许内容区域缩小 */
  2477. }
  2478. .tab-section {
  2479. flex-shrink: 0;
  2480. /* 禁止伸缩 */
  2481. }
  2482. .tab-content {
  2483. flex: 1;
  2484. overflow-y: auto;
  2485. min-height: 0;
  2486. /* 关键:允许内容收缩 */
  2487. }
  2488. .homepage-logo {
  2489. height: 100%;
  2490. width: fit-content;
  2491. display: flex;
  2492. /* flex-direction: column; */
  2493. align-items: center;
  2494. justify-content: center;
  2495. margin-left: 20px;
  2496. margin-right: auto;
  2497. position: relative;
  2498. gap: 10px;
  2499. }
  2500. .expand {
  2501. font-size: 2.5rem;
  2502. cursor: pointer;
  2503. color: white;
  2504. }
  2505. .logo1 {
  2506. width: 110px;
  2507. height: auto;
  2508. margin-bottom: 8px;
  2509. }
  2510. .logo2 {
  2511. width: 80px;
  2512. height: auto;
  2513. }
  2514. /* 尾部 */
  2515. .homepage-footer {
  2516. display: flex;
  2517. flex-direction: column;
  2518. gap: 5px;
  2519. flex-shrink: 0;
  2520. /* width: 100%; */
  2521. background-color: #fff;
  2522. }
  2523. .pcFooter {
  2524. margin: 0 6% 4%;
  2525. }
  2526. .footer-first-line {
  2527. display: flex;
  2528. justify-content: space-between;
  2529. align-items: center;
  2530. padding: 5px 15px;
  2531. flex-shrink: 0;
  2532. }
  2533. .left-group {
  2534. display: flex;
  2535. gap: 15px;
  2536. }
  2537. .action-btn {
  2538. cursor: pointer;
  2539. transition: transform 0.2s;
  2540. height: 28px;
  2541. }
  2542. .model-btn {
  2543. height: 32px;
  2544. transition: all 0.3s ease;
  2545. }
  2546. .model-btn:hover {
  2547. transform: scale(1.1);
  2548. }
  2549. /* 手机端模型选择器样式 */
  2550. .mobile-model-select {
  2551. width: 180px;
  2552. --el-select-border-color-hover: var(--el-border-color-hover);
  2553. --el-select-disabled-border: var(--el-disabled-border-color);
  2554. --el-select-font-size: var(--el-font-size-base);
  2555. --el-select-close-hover-color: var(--el-text-color-secondary);
  2556. --el-select-input-focus-border-color: var(--el-color-primary);
  2557. --el-select-input-font-size: 14px;
  2558. }
  2559. .send-btn {
  2560. margin-left: 10px;
  2561. height: 33px !important;
  2562. width: auto;
  2563. /* margin-right: 5px; */
  2564. }
  2565. .input-container {
  2566. position: relative;
  2567. width: 100%;
  2568. }
  2569. .send-btn-inner {
  2570. position: absolute;
  2571. right: 10px;
  2572. top: 50%;
  2573. transform: translateY(-50%);
  2574. height: 28px !important;
  2575. width: auto;
  2576. z-index: 10;
  2577. transition: all 0.3s ease;
  2578. }
  2579. .send-btn-inner:hover {
  2580. transform: translateY(-50%) scale(1.1);
  2581. }
  2582. /* 音频播放动画 */
  2583. @keyframes pulse {
  2584. 0% {
  2585. transform: scale(1);
  2586. }
  2587. 50% {
  2588. transform: scale(1.1);
  2589. }
  2590. 100% {
  2591. transform: scale(1);
  2592. }
  2593. }
  2594. .footer-second-line {
  2595. position: relative;
  2596. display: flex;
  2597. align-items: center;
  2598. padding: 5px 15px 10px;
  2599. flex-shrink: 0;
  2600. }
  2601. .msg-icon {
  2602. position: absolute;
  2603. left: 25px;
  2604. top: 50%;
  2605. transform: translateY(-50%);
  2606. width: 24px;
  2607. z-index: 999;
  2608. }
  2609. .msg-input:deep(.el-textarea__inner) {
  2610. border: none !important;
  2611. box-shadow: none !important;
  2612. overflow-y: auto !important;
  2613. transition: all 0.2s ease-out;
  2614. resize: none !important;
  2615. line-height: 1.5 !important;
  2616. max-height: 100px !important;
  2617. padding-right: 45px !important;
  2618. }
  2619. .msg-input {
  2620. min-height: 34px;
  2621. width: 100%;
  2622. border-radius: 5px;
  2623. font-size: 16px;
  2624. transition: all 0.3s ease-out;
  2625. overflow-y: hidden;
  2626. box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3);
  2627. background: #fff;
  2628. z-index: 5;
  2629. /* 添加iOS设备特殊处理 */
  2630. -webkit-appearance: none;
  2631. appearance: none;
  2632. }
  2633. .msg-input:focus {
  2634. outline: none;
  2635. }
  2636. .input::before {
  2637. content: "请输入股票名称或股票代码...";
  2638. position: absolute;
  2639. left: 11px;
  2640. top: 5px;
  2641. color: var(--el-text-color-secondary);
  2642. pointer-events: none;
  2643. white-space: nowrap;
  2644. overflow-x: hidden;
  2645. text-overflow: ellipsis;
  2646. width: 80%;
  2647. z-index: 6;
  2648. }
  2649. .changeMsg {
  2650. display: flex;
  2651. width: 100%;
  2652. margin-bottom: 30px;
  2653. justify-content: center;
  2654. gap: 20px;
  2655. }
  2656. .changeInfo {
  2657. display: flex;
  2658. background-color: #f8f8f8;
  2659. border-radius: 5px;
  2660. padding: 10px 20px;
  2661. width: 40%;
  2662. white-space: nowrap;
  2663. align-items: center;
  2664. justify-content: center;
  2665. }
  2666. .changeImg {
  2667. height: 100%;
  2668. display: flex;
  2669. justify-content: center;
  2670. align-items: center;
  2671. margin-right: 10px;
  2672. }
  2673. .changeImgClass {
  2674. width: 50px;
  2675. height: auto;
  2676. }
  2677. .changeContent {
  2678. display: flex;
  2679. flex-direction: column;
  2680. justify-content: center;
  2681. font-weight: bold;
  2682. }
  2683. .changeRule {
  2684. display: flex;
  2685. flex-direction: column;
  2686. background-color: #f8f8f8;
  2687. border-radius: 5px;
  2688. text-align: center;
  2689. align-items: center;
  2690. justify-content: center;
  2691. color: #4e86fe;
  2692. white-space: nowrap;
  2693. padding: 5px 20px;
  2694. min-width: 40%;
  2695. transition: all 0.3s ease;
  2696. cursor: pointer;
  2697. }
  2698. .changeRule:hover {
  2699. background-color: #e8f0ff;
  2700. box-shadow: 0 2px 8px rgba(78, 134, 254, 0.2);
  2701. transform: translateY(-2px);
  2702. }
  2703. .token-info-row {
  2704. display: flex;
  2705. width: 100%;
  2706. margin-bottom: 30px;
  2707. justify-content: center;
  2708. gap: 20px;
  2709. }
  2710. .token-info-left {
  2711. display: flex;
  2712. flex-direction: column;
  2713. justify-content: center;
  2714. gap: 6px;
  2715. background-color: #f8f8f8;
  2716. border-radius: 5px;
  2717. padding: 10px 20px;
  2718. width: 40%;
  2719. font-weight: bold;
  2720. }
  2721. .token-info-right {
  2722. display: flex;
  2723. flex-direction: column;
  2724. justify-content: center;
  2725. align-items: center;
  2726. white-space: nowrap;
  2727. gap: 6px;
  2728. background-color: #f8f8f8;
  2729. border-radius: 5px;
  2730. padding: 10px 20px;
  2731. color: #4e86fe;
  2732. min-width: 40%;
  2733. transition: all 0.3s ease;
  2734. cursor: pointer;
  2735. }
  2736. .token-info-right:hover {
  2737. background-color: #e8f0ff;
  2738. box-shadow: 0 2px 8px rgba(78, 134, 254, 0.2);
  2739. transform: translateY(-2px);
  2740. }
  2741. /* Token变动记录弹窗样式 */
  2742. .token-record-header {
  2743. background-color: #f8f8f8;
  2744. border-radius: 8px;
  2745. padding: 15px 20px;
  2746. margin-bottom: 20px;
  2747. }
  2748. .token-total {
  2749. font-size: 20px;
  2750. color: #20303d;
  2751. font-weight: 500;
  2752. text-align: center;
  2753. margin-bottom: 10px;
  2754. }
  2755. .token-row {
  2756. display: flex;
  2757. justify-content: space-around;
  2758. font-size: 16px;
  2759. }
  2760. .token-total1 {
  2761. color: #20303d;
  2762. }
  2763. .token-paid {
  2764. color: #20303d;
  2765. }
  2766. .token-free {
  2767. color: #20303d;
  2768. }
  2769. .token-permanent {
  2770. color: #20303d;
  2771. }
  2772. .token-record-table {
  2773. width: 100%;
  2774. max-height: 300px;
  2775. overflow-x: auto;
  2776. overflow-y: auto;
  2777. }
  2778. .token-record-table table {
  2779. width: 100%;
  2780. border-collapse: collapse;
  2781. }
  2782. .token-record-table th,
  2783. .token-record-table td {
  2784. padding: 12px 8px;
  2785. text-align: center;
  2786. border-bottom: 1px solid #eee;
  2787. }
  2788. .token-record-table th {
  2789. background-color: #f8f8f8;
  2790. font-weight: 500;
  2791. }
  2792. .token-plus {
  2793. color: #00b42a;
  2794. }
  2795. .token-minus {
  2796. color: #ff4d4f;
  2797. }
  2798. /* 移动端适配 */
  2799. @media (max-width: 768px) {
  2800. .changeMsg {
  2801. display: flex;
  2802. justify-content: space-between;
  2803. align-items: center;
  2804. margin-bottom: 16px;
  2805. gap: 0;
  2806. }
  2807. .changeInfo {
  2808. display: flex;
  2809. align-items: center;
  2810. width: auto;
  2811. padding: 0;
  2812. margin: 0;
  2813. }
  2814. .changeImgClass {
  2815. width: 40px !important;
  2816. height: 40px !important;
  2817. border-radius: 4px !important;
  2818. }
  2819. .changeLevelTitle {
  2820. align-items: left;
  2821. width: auto;
  2822. padding: 0;
  2823. margin: 0 !important;
  2824. }
  2825. .changeRule {
  2826. margin: 0 !important;
  2827. padding: 0 !important;
  2828. align-items: center !important;
  2829. }
  2830. .token-record {
  2831. background: #f5f5f5 !important;
  2832. padding: 16px !important;
  2833. border-radius: 4px !important;
  2834. margin-bottom: 20px !important;
  2835. }
  2836. .token-row-top {
  2837. display: flex !important;
  2838. justify-content: space-between !important;
  2839. align-items: center !important;
  2840. margin-bottom: 5px !important;
  2841. }
  2842. .token-total-mobile {
  2843. font-size: 16px !important;
  2844. color: #222 !important;
  2845. }
  2846. .token-change {
  2847. font-size: 16px !important;
  2848. color: #4e86fe !important;
  2849. font-weight: bold;
  2850. }
  2851. .token-paid-mobile,
  2852. .token-free-mobile {
  2853. font-size: 16px !important;
  2854. line-height: 2 !important;
  2855. color: #222 !important;
  2856. }
  2857. }
  2858. .changeLevel {
  2859. display: flex;
  2860. flex-direction: column;
  2861. }
  2862. .changeLevelTitle {
  2863. font-weight: bold;
  2864. margin-bottom: 10px;
  2865. }
  2866. .changeLevelContent {
  2867. display: flex;
  2868. flex-wrap: wrap;
  2869. gap: 20px;
  2870. margin-bottom: 10px;
  2871. }
  2872. .changeLevelItems {
  2873. display: flex;
  2874. background-color: #f8f8f8;
  2875. width: 20%;
  2876. min-width: 70px;
  2877. max-width: 150px;
  2878. justify-content: center;
  2879. align-items: center;
  2880. flex-direction: column;
  2881. border-radius: 10px;
  2882. padding: 5px;
  2883. cursor: pointer;
  2884. }
  2885. .changeLevelItems:hover {
  2886. background-color: #ecf2ff;
  2887. }
  2888. .changeLevelItemsActive {
  2889. border: 1px solid #4e86fe;
  2890. background-color: #ecf2ff;
  2891. }
  2892. .changeLevelItem {
  2893. display: flex;
  2894. flex-direction: column;
  2895. justify-content: center;
  2896. align-items: center;
  2897. }
  2898. .changeLevelItemToken {
  2899. display: flex;
  2900. justify-content: center;
  2901. align-items: center;
  2902. }
  2903. .changeLevelItemTokenImg {
  2904. width: 40px;
  2905. height: 40px;
  2906. }
  2907. .changeNow {
  2908. display: flex;
  2909. white-space: nowrap;
  2910. /* font-weight: bold; */
  2911. margin-bottom: 15px;
  2912. align-items: center;
  2913. }
  2914. .changePay {
  2915. color: #4e86fe;
  2916. margin: 0px 5px;
  2917. font-size: 1.1rem;
  2918. }
  2919. .changeBtn {
  2920. width: 40%;
  2921. max-width: 350px;
  2922. background-color: #4e86fe;
  2923. color: white;
  2924. display: flex;
  2925. justify-content: center;
  2926. align-content: center;
  2927. padding: 10px;
  2928. border-radius: 5px;
  2929. cursor: pointer;
  2930. margin: 0 auto;
  2931. }
  2932. .changeBtn:hover {
  2933. background-color: #3a73e6;
  2934. }
  2935. .rechargeDialogTitle {
  2936. font-size: 1.7rem;
  2937. /* font-weight: bold; */
  2938. color: #4e86fe;
  2939. display: flex;
  2940. justify-content: center;
  2941. align-items: center;
  2942. letter-spacing: 10px;
  2943. }
  2944. .rechargeDialogContent {
  2945. padding: 20px;
  2946. font-size: 1.2rem;
  2947. }
  2948. .rechargeDialogBtnGroup {
  2949. display: flex;
  2950. font-size: 1.2rem;
  2951. padding: 0px 20px;
  2952. justify-content: space-between;
  2953. }
  2954. .recharge {
  2955. color: white;
  2956. background-color: #4e86fe;
  2957. padding: 10px 20px;
  2958. border-radius: 13px;
  2959. cursor: pointer;
  2960. min-width: 20%;
  2961. text-align: center;
  2962. white-space: nowrap;
  2963. }
  2964. .recharge:hover {
  2965. background-color: #3a73e6;
  2966. }
  2967. .rechargeDialogCancel {
  2968. border: 1px solid rgb(202, 202, 202);
  2969. padding: 10px 20px;
  2970. border-radius: 13px;
  2971. cursor: pointer;
  2972. min-width: 20%;
  2973. text-align: center;
  2974. }
  2975. .rechargeDialogCancel:hover {
  2976. background-color: #ecf2ff;
  2977. }
  2978. .confirmDialogTitle {
  2979. font-size: 1.7rem;
  2980. /* font-weight: bold; */
  2981. color: #4e86fe;
  2982. display: flex;
  2983. justify-content: center;
  2984. align-items: center;
  2985. letter-spacing: 10px;
  2986. }
  2987. .confirmDialogContent {
  2988. padding: 20px;
  2989. font-size: 1.2rem;
  2990. }
  2991. .confirmDialogBtnGroup {
  2992. display: flex;
  2993. font-size: 1.2rem;
  2994. padding: 0px 20px;
  2995. justify-content: space-between;
  2996. }
  2997. .confirmDialogConfirm {
  2998. color: white;
  2999. background-color: #4e86fe;
  3000. padding: 10px 20px;
  3001. border-radius: 13px;
  3002. cursor: pointer;
  3003. min-width: 20%;
  3004. text-align: center;
  3005. }
  3006. .confirmDialogConfirm:hover {
  3007. background-color: #3a73e6;
  3008. }
  3009. .confirmDialogCancel {
  3010. border: 1px solid rgb(202, 202, 202);
  3011. padding: 10px 20px;
  3012. border-radius: 13px;
  3013. cursor: pointer;
  3014. min-width: 20%;
  3015. text-align: center;
  3016. }
  3017. .confirmDialogCancel:hover {
  3018. background-color: #ecf2ff;
  3019. }
  3020. .changeSuccessDialogTitle {
  3021. font-size: 1.7rem;
  3022. font-weight: bold;
  3023. color: #7849de;
  3024. display: flex;
  3025. justify-content: center;
  3026. align-items: center;
  3027. letter-spacing: 10px;
  3028. }
  3029. .changeSuccessDialogTitle image {
  3030. font-size: 1.7rem;
  3031. font-weight: bold;
  3032. color: #7849de;
  3033. display: flex;
  3034. justify-content: center;
  3035. align-items: center;
  3036. letter-spacing: 10px;
  3037. }
  3038. .changeSuccessDialogContent {
  3039. padding: 20px;
  3040. color: white;
  3041. font-size: 1.2rem;
  3042. font-weight: bold;
  3043. text-align: center;
  3044. }
  3045. /* Token规则提示框样式 - 与DBQBmodel无权限弹出框一致 */
  3046. .tokenRuleDialog {
  3047. width: 100%;
  3048. display: flex;
  3049. justify-content: center;
  3050. align-items: center;
  3051. position: fixed;
  3052. bottom: 20%;
  3053. color: white;
  3054. z-index: 9999;
  3055. }
  3056. .tokenRuleDialogContent {
  3057. position: relative;
  3058. border-radius: 5px;
  3059. border: 1px solid white;
  3060. padding: 20px 30px;
  3061. background-color: #261176;
  3062. display: flex;
  3063. flex-direction: column;
  3064. justify-content: center;
  3065. align-items: flex-start;
  3066. max-width: 500px;
  3067. width: 70vw;
  3068. max-height: 70vh;
  3069. /* overflow-y: auto; */
  3070. box-sizing: border-box;
  3071. }
  3072. .tokenRuleDialogClose {
  3073. border-radius: 5px;
  3074. border: 1px solid white;
  3075. background-color: #8621d9;
  3076. padding: 2px;
  3077. display: flex;
  3078. justify-content: center;
  3079. align-items: center;
  3080. position: absolute;
  3081. top: 0px;
  3082. right: 0px;
  3083. width: 24px;
  3084. height: 24px;
  3085. cursor: pointer;
  3086. color: white;
  3087. }
  3088. .tokenRuleDialogTitle {
  3089. color: #fec13e;
  3090. font-size: 20px;
  3091. font-weight: bold;
  3092. text-align: center;
  3093. width: 100%;
  3094. }
  3095. .tokenRuleSection {
  3096. width: 100%;
  3097. }
  3098. .tokenRuleSectionTitle {
  3099. color: #fec13e;
  3100. font-size: 16px;
  3101. font-weight: bold;
  3102. margin-bottom: 8px;
  3103. }
  3104. .tokenRuleItem {
  3105. color: white;
  3106. font-size: 16px;
  3107. line-height: 1.5;
  3108. }
  3109. .tokenRuleNote {
  3110. background: rgba(134, 33, 217, 0.3);
  3111. border: 1px solid #fec13e;
  3112. border-radius: 5px;
  3113. padding: 12px;
  3114. color: white;
  3115. font-size: 14px;
  3116. line-height: 1.5;
  3117. text-align: center;
  3118. word-wrap: break-word;
  3119. overflow-wrap: break-word;
  3120. }
  3121. @media (max-width: 768px) {
  3122. .tokenRuleDialog {
  3123. bottom: 20%;
  3124. }
  3125. .tokenRuleDialogContent {
  3126. width: 80vw;
  3127. padding: 15px 20px;
  3128. max-height: 85vh;
  3129. }
  3130. .tokenRuleDialogTitle {
  3131. font-size: 18px;
  3132. }
  3133. .tokenRuleSectionTitle {
  3134. font-size: 14px;
  3135. margin-bottom: 6px;
  3136. }
  3137. .tokenRuleItem {
  3138. font-size: 13px;
  3139. line-height: 1.4;
  3140. word-wrap: break-word;
  3141. overflow-wrap: break-word;
  3142. }
  3143. .tokenRuleNote {
  3144. font-size: 12px;
  3145. padding: 10px;
  3146. line-height: 1.4;
  3147. }
  3148. .tokenRuleDialogClose {
  3149. width: 20px;
  3150. height: 20px;
  3151. }
  3152. }
  3153. @media (max-width: 768px) {
  3154. .logo1 {
  3155. max-width: 110px;
  3156. width: 25vw;
  3157. }
  3158. .homepage-right-group {
  3159. gap: 10px;
  3160. }
  3161. .homepage-right-group .action-btn {
  3162. height: 30px;
  3163. }
  3164. .count-number {
  3165. top: 10px;
  3166. font-size: 12px;
  3167. }
  3168. .backImg {
  3169. width: 35px;
  3170. height: auto;
  3171. }
  3172. .action-btn {
  3173. height: 21px;
  3174. }
  3175. .footer-second-line {
  3176. padding: 5px 10px 10px;
  3177. }
  3178. .msg-input {
  3179. /* min-height: 44px; */
  3180. /* height: 44px; */
  3181. font-size: 16px;
  3182. }
  3183. /* .changeImg {
  3184. width: 30px;
  3185. height: 30px;
  3186. } */
  3187. .changeLevel {
  3188. flex-direction: horizontal;
  3189. }
  3190. .changeLevelContent {
  3191. display: flex;
  3192. /* justify-content: center; */
  3193. }
  3194. .changeLevelItems {
  3195. flex: 0 0 calc(33% - 30px);
  3196. /* margin-right: auto; */
  3197. font-size: 0.7rem;
  3198. min-width: 0px;
  3199. }
  3200. /* .changeLevelItems {
  3201. } */
  3202. .changeLevelTitle {
  3203. align-items: center;
  3204. display: flex;
  3205. flex-direction: column;
  3206. }
  3207. .changeRule {
  3208. margin-left: 10px;
  3209. width: 0%;
  3210. background-color: white;
  3211. }
  3212. .changeMsg {
  3213. gap: 10px 20px;
  3214. margin-bottom: 10px;
  3215. }
  3216. .changeImgClass {
  3217. width: 30px;
  3218. height: 30px;
  3219. }
  3220. /* .changeContent {
  3221. font-size: 0.5rem;
  3222. } */
  3223. .changeLevelItemToken {
  3224. white-space: nowrap;
  3225. }
  3226. .changeLevelItemTokenImg {
  3227. width: 20px;
  3228. height: 20px;
  3229. }
  3230. .changeInfo {
  3231. margin: 0px auto;
  3232. }
  3233. .changeBtn {
  3234. margin: 0px auto;
  3235. }
  3236. .rechargeDialogTitle {
  3237. font-size: 1.3rem;
  3238. }
  3239. .rechargeDialogContent {
  3240. padding: 10px 0px;
  3241. font-size: 0.8rem;
  3242. }
  3243. .rechargeDialogBtnGroup {
  3244. font-size: 1rem;
  3245. }
  3246. .recharge {
  3247. padding: 5px 10px;
  3248. }
  3249. .rechargeDialogCancel {
  3250. padding: 5px 10px;
  3251. }
  3252. .confirmDialogTitle {
  3253. font-size: 1.3rem;
  3254. }
  3255. .confirmDialogContent {
  3256. padding: 10px 0px;
  3257. font-size: 0.8rem;
  3258. }
  3259. .confirmDialogBtnGroup {
  3260. font-size: 1rem;
  3261. }
  3262. .confirmDialogConfirm {
  3263. padding: 5px 10px;
  3264. }
  3265. .confirmDialogCancel {
  3266. padding: 5px 10px;
  3267. }
  3268. .changeSuccessDialogTitle {
  3269. font-size: 1.3rem;
  3270. }
  3271. .changeSuccessDialogContent {
  3272. font-size: 1rem;
  3273. }
  3274. }
  3275. @media (min-width: 320px) and (max-width: 375px) {
  3276. .tokenRuleDialog {
  3277. bottom: 10%;
  3278. }
  3279. }
  3280. </style>
  3281. <style>
  3282. .changeSuccessDialog {
  3283. background: linear-gradient(180deg, #80d3f8, #8080ff);
  3284. }
  3285. .changeSuccessDialogFooter {
  3286. display: flex;
  3287. justify-content: center;
  3288. margin-top: 20px;
  3289. }
  3290. .confirmButton {
  3291. background: #8a52df;
  3292. color: white;
  3293. border: none;
  3294. border-radius: 5px;
  3295. padding: 8px 30px;
  3296. font-size: 16px;
  3297. cursor: pointer;
  3298. transition: all 0.3s ease;
  3299. }
  3300. .confirmButton:hover {
  3301. background: #3a75e6;
  3302. transform: translateY(-2px);
  3303. box-shadow: 0 2px 8px rgba(78, 134, 254, 0.3);
  3304. }
  3305. /* 自定义下拉选择框样式 */
  3306. .custom-select-container {
  3307. position: relative;
  3308. width: 100%;
  3309. margin-bottom: 10px;
  3310. font-size: 14px;
  3311. user-select: none;
  3312. }
  3313. .custom-select-header {
  3314. display: flex;
  3315. align-items: center;
  3316. justify-content: space-between;
  3317. padding: 8px 12px;
  3318. background: #3549b9;
  3319. color: white;
  3320. border-radius: 4px;
  3321. cursor: pointer;
  3322. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  3323. gap: 10px;
  3324. }
  3325. .dropdown-arrow {
  3326. transition: transform 0.3s ease;
  3327. display: flex;
  3328. }
  3329. .arrow-up {
  3330. transform: rotate(180deg);
  3331. }
  3332. .custom-select-dropdown {
  3333. position: absolute;
  3334. bottom: 100%;
  3335. left: 0;
  3336. width: 100%;
  3337. max-height: 200px;
  3338. overflow-y: auto;
  3339. background: #3549b9;
  3340. border-radius: 4px;
  3341. box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
  3342. z-index: 100;
  3343. margin-bottom: 4px;
  3344. color: white;
  3345. }
  3346. .custom-select-option {
  3347. padding: 8px 12px;
  3348. cursor: pointer;
  3349. transition: background-color 0.2s;
  3350. }
  3351. .custom-select-option:hover {
  3352. background-color: #f5f7fa;
  3353. }
  3354. .option-selected {
  3355. background-color: #6a00ff;
  3356. color: white;
  3357. font-weight: 500;
  3358. }
  3359. .select-option-with-image {
  3360. display: flex;
  3361. align-items: center;
  3362. gap: 10px;
  3363. }
  3364. .option-image {
  3365. width: 24px;
  3366. height: 24px;
  3367. object-fit: contain;
  3368. }
  3369. </style>