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.

870 lines
23 KiB

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