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.

945 lines
25 KiB

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