AppLayout.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. <template>
  2. <div
  3. class="layout-wrapper"
  4. :class="{ 'full-screen': isFullScreen }"
  5. :style="isSpecialBg ? backgroundStyle : {}"
  6. >
  7. <!-- 背景层 -->
  8. <div v-if="isSpecialBg" class="background-layer"></div>
  9. <!-- Header -->
  10. <el-header
  11. class="layout-header"
  12. v-if="!isFullScreen"
  13. :class="{ 'transparent-header': isSpecialBg }"
  14. >
  15. <div class="logo-title-row">
  16. <!-- 左侧:Logo和标题 -->
  17. <div class="left-section">
  18. <img src="@/assets/logo.png" alt="Logo" class="logo" />
  19. <span class="project-name" :class="{ 'light-text': isSpecialBg }">
  20. <!-- 土壤酸化智能预测专家系统 -->
  21. {{ t('Header.title') }}
  22. </span>
  23. </div>
  24. <!-- 右侧:用户信息 -->
  25. <div class="right-section" v-if="!isSelectCity">
  26. <div class="user-info-row">
  27. <div class="lang-buttons" v-if="true"> <!-- 设为false隐藏,选择一种方式即可 -->
  28. <!-- 语言切换按钮 -->
  29. <button
  30. class="lang-toggle-btn"
  31. :class="{ 'light-text': isSpecialBg }"
  32. @click="toggleLanguage"
  33. :title="currentLang === 'zh' ? 'Switch to English' : '切换到中文'"
  34. >
  35. <i class="el-icon-globe"></i>
  36. <span class="lang-label">{{ currentLang === 'zh' ? '中文' : 'English' }}</span>
  37. </button>
  38. </div>
  39. <span class="welcome-text" :class="{ 'light-text': isSpecialBg }">
  40. {{ t('Header.welcome') }} {{ userInfo.name }}
  41. </span>
  42. <el-dropdown>
  43. <span class="el-dropdown-link">
  44. <el-avatar
  45. :size="40"
  46. :class="{ 'light-avatar-border': isSpecialBg }"
  47. src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
  48. />
  49. </span>
  50. <template #dropdown>
  51. <el-dropdown-menu>
  52. <el-dropdown-item disabled
  53. > {{ t('Header.username') }} :{{ userInfo.name }}</el-dropdown-item
  54. >
  55. <el-dropdown-item divided @click="handleLogout"
  56. > {{ t('Header.logout') }} </el-dropdown-item
  57. >
  58. </el-dropdown-menu>
  59. </template>
  60. </el-dropdown>
  61. </div>
  62. </div>
  63. </div>
  64. </el-header>
  65. <div class="tabs-row" v-if="!isFullScreen">
  66. <el-tabs
  67. v-if="showTabs"
  68. v-model="activeName"
  69. class="demo-tabs"
  70. :style="tabStyle"
  71. @tab-click="handleClick"
  72. >
  73. <el-tab-pane v-for="tab in tabs" :key="tab.name" :name="tab.name">
  74. <template #label>
  75. <i :class="['tab-icon', tab.icon]"></i>
  76. <span class="tab-label-text">{{ tab.label }}</span>
  77. </template>
  78. </el-tab-pane>
  79. </el-tabs>
  80. <div v-else class="single-tab" @click="handleClick(tabs[0], $event)">
  81. <i :class="['tab-icon', tabs[0].icon]"></i>
  82. <span class="tab-label-text">{{ tabs[0].label }}</span>
  83. </div>
  84. </div>
  85. <!-- Main Content -->
  86. <el-container class="layout-main-container">
  87. <el-aside v-if="showAside && showTabs" class="layout-aside">
  88. <component
  89. :is="AsideComponent"
  90. :activeTab="activeName"
  91. :showTabs="showTabs"
  92. />
  93. </el-aside>
  94. <el-main class="layout-content-wrapper" :style="mainStyle">
  95. <div
  96. class="scrollable-content"
  97. :class="{ 'transparent-scroll': isSpecialBg }"
  98. >
  99. <div :class="{ 'select-city-container': isSelectCity }">
  100. <RouterView />
  101. </div>
  102. </div>
  103. </el-main>
  104. </el-container>
  105. <!-- 全局消息提示组件 -->
  106. <div v-if="showGlobalMessage" class="global-message">
  107. <div class="message-content" :class="messageType">
  108. {{ globalMessage }}
  109. </div>
  110. </div>
  111. </div>
  112. </template>
  113. <script setup lang="ts">
  114. import { ref, reactive, computed, watch, defineAsyncComponent } from "vue";
  115. import { useRouter, useRoute } from "vue-router";
  116. import { useTokenStore } from "@/stores/mytoken";
  117. import { ElMessageBox, ElMessage } from "element-plus"; // 确保导入ElMessage
  118. import { logout } from "@/API/users";
  119. import { useI18n } from 'vue-i18n'
  120. const { t,locale } =useI18n()
  121. const currentLang = ref(locale.value || 'zh')
  122. // 切换语言
  123. const changeLanguage = () => {
  124. locale.value = currentLang.value
  125. localStorage.setItem('language', currentLang.value)
  126. // 触发一个自定义事件,让其他组件知道语言改变了
  127. window.dispatchEvent(new CustomEvent('languageChanged', {
  128. detail: { lang: currentLang.value }
  129. }))
  130. }
  131. // 按钮点击切换
  132. const toggleLanguage = () => {
  133. currentLang.value = currentLang.value === 'zh' ?'en':'zh'
  134. changeLanguage()
  135. }
  136. // 监听语言变化
  137. watch(locale, (newVal) => {
  138. currentLang.value = newVal
  139. })
  140. const router = useRouter();
  141. const route = useRoute();
  142. const tokenStore = useTokenStore();
  143. const currentBgImage = ref("");
  144. // ============ 新增状态 ============
  145. const showGlobalMessage = ref(false);
  146. const globalMessage = ref("");
  147. const messageType = ref("success"); // 消息类型: success/error
  148. currentBgImage.value = `url(${new URL('../../assets/bg/background.jpg', import.meta.url).href})`;
  149. // 是否特殊背景(始终返回true → 所有页面应用特殊背景样式)
  150. const isSpecialBg = computed(() => true);
  151. const backgroundStyle = computed(() => ({
  152. backgroundImage: currentBgImage.value,
  153. backgroundSize: "cover",
  154. backgroundPosition: "center",
  155. backgroundRepeat: "no-repeat",
  156. backgroundAttachment: "fixed",
  157. }));
  158. const isFullScreen = computed(() => route.meta.fullScreen === true);
  159. const isSelectCity = computed(() => route.path === "/select-city");
  160. // 当前用户信息
  161. const userInfo = reactive({
  162. name: tokenStore.userName,
  163. type: tokenStore.userType,
  164. });
  165. const tabs = computed(() => {
  166. if (userInfo.type === "admin") {
  167. return [
  168. {
  169. name: "dataManagement",
  170. label: t('Menu.dataManagement'),
  171. icon: "el-icon-folder",
  172. routes: [
  173. "/soilAcidReductionData",
  174. "/soilAcidificationData",
  175. "/crossSectionSampleData",
  176. "/irrigationWaterInputFluxData",
  177. "/heavyMetalEnterpriseData",
  178. "/atmosphericSampleData",
  179. ],
  180. },
  181. {
  182. name: "infoManagement",
  183. label: t('Menu.infoManagement'),
  184. icon: "el-icon-document",
  185. routes: ["/IntroductionUpdate"],
  186. },
  187. {
  188. name: "modelManagement",
  189. label: t('Menu.modelManagement'),
  190. icon: "el-icon-cpu",
  191. routes: ["/ModelSelection", "/thres", "/ModelTrain"],
  192. },
  193. {
  194. name: "userManagement",
  195. label: t('Menu.userManagement'),
  196. icon: "el-icon-user",
  197. routes: ["/UserManagement"],
  198. },
  199. ];
  200. } else {
  201. return [
  202. {
  203. name: "introduction",
  204. label: t('Menu.swIntroduce'),
  205. icon: "el-icon-info-filled",
  206. routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"],
  207. },
  208. {
  209. name: "acidmodelmap",
  210. label: t('Menu.mapcalulate'),
  211. icon: "el-icon-data-analysis",
  212. routes: ["/shaoguan_acidmodelmap","nanxiong_acidmodelmap"],
  213. },
  214. {
  215. name: "Calculation",
  216. label: t('Menu.reversionmodel'),
  217. icon: "el-icon-data-analysis",
  218. routes: ["/Calculation", "/SoilAcidReductionIterativeEvolution"],
  219. },
  220. {
  221. name: "AcidNeutralizationModel",
  222. label: t('Menu.reducemodel'),
  223. icon: "el-icon-data-analysis",
  224. routes: ["/AcidNeutralizationModel", "/SoilAcidificationIterativeEvolution"],
  225. },
  226. {
  227. name: "dataStatistics",
  228. label: t('Menu.dataStatistics'),
  229. icon: "el-icon-pie-chart",
  230. routes: [
  231. "/DetectionStatistics",
  232. "/FarmlandPollutionStatistics",
  233. "/LandClutivatesStatistics",
  234. "/SoilacidificationStatistics",
  235. ],
  236. },
  237. ];
  238. }
  239. });
  240. const activeName = ref(tabs.value[0]?.name || "");
  241. const showTabs = computed(() => tabs.value.length > 1);
  242. const tabStyle = computed(() =>
  243. tabs.value.length === 1 ? { width: "100%", justifyContent: "center" } : {}
  244. );
  245. let hasNavigated = false;
  246. watch(
  247. () => activeName.value,
  248. (newTab) => {
  249. const tab = tabs.value.find((t) => t.name === newTab);
  250. const targetPath = tab?.routes?.[0];
  251. if (!hasNavigated) {
  252. hasNavigated = true;
  253. return;
  254. }
  255. if (tab && targetPath && router.currentRoute.value.path !== targetPath)
  256. router.push({ path: targetPath });
  257. },
  258. { immediate: true }
  259. );
  260. // 点击 tab
  261. const activeAsideTab = ref(activeName.value || "");
  262. const handleClick = (tab: any, _event?: Event) => {
  263. activeAsideTab.value = tab.name || tab.props?.name;
  264. };
  265. // 动态加载侧边栏
  266. const AsideComponent = computed(() => {
  267. return [
  268. "dataManagement",
  269. "infoManagement",
  270. "modelManagement",
  271. "userManagement",
  272. ].includes(activeName.value)
  273. ? defineAsyncComponent(() => import("./AppAsideForTab2.vue"))
  274. : defineAsyncComponent(() => import("./AppAside.vue"));
  275. });
  276. // 是否显示侧边栏
  277. const showAside = computed(
  278. () =>
  279. !isFullScreen.value && !["cropRiskAssessment"].includes(activeName.value)
  280. );
  281. // ============ 显示全局消息 ============
  282. // 登出逻辑
  283. const handleLogout = async () => {
  284. try {
  285. await ElMessageBox.confirm("确定要退出登录吗?", "提示", {
  286. confirmButtonText: "确定",
  287. cancelButtonText: "取消",
  288. type: "warning",
  289. });
  290. await logout();
  291. tokenStore.clearToken();
  292. // 使用ElMessage而不是全局消息提示
  293. ElMessage.success("退出登录成功");
  294. // 延迟跳转,让用户看到提示消息
  295. setTimeout(() => {
  296. router.push("/login");
  297. }, 1000);
  298. } catch (error) {
  299. // 区分用户取消操作和真正的错误
  300. if (error === 'cancel' || error?.toString().includes('cancel')) {
  301. // console.log("用户取消退出登录");
  302. } else {
  303. ElMessage.error("退出失败,请重试");
  304. console.error("退出登录错误:", error);
  305. }
  306. }
  307. };
  308. // 内容区样式
  309. const mainStyle = computed(() => ({
  310. padding: ["mapView", "infoManagement"].includes(activeName.value)
  311. ? "0"
  312. : "20px",
  313. overflow: "hidden",
  314. }));
  315. </script>
  316. <style>
  317. /* 隐藏所有滚动条 */
  318. *::-webkit-scrollbar {
  319. display: none;
  320. /* Chrome, Safari, Opera */
  321. }
  322. * {
  323. -ms-overflow-style: none;
  324. /* IE and Edge */
  325. scrollbar-width: none;
  326. /* Firefox */
  327. }
  328. /* 整体布局容器 */
  329. .layout-wrapper {
  330. display: flex;
  331. flex-direction: column;
  332. height: 100vh;
  333. overflow: hidden;
  334. position: relative; /* 创建层叠上下文 */
  335. background-color: #f5f7fa; /* 默认背景色 */
  336. /* 确保 layout-wrapper 自身的 z-index 不干扰子元素,或根据需要设置 */
  337. /* z-index: 0; */ /* 通常不需要显式设置,除非有特殊需求 */
  338. }
  339. /* 背景层 - 关键修改 */
  340. .background-layer {
  341. position: absolute;
  342. top: 0;
  343. left: 0;
  344. width: 100%;
  345. height: 100%;
  346. /* 将背景层的 z-index 设为一个较低的负值或非常小的正值,确保它在最底层 */
  347. z-index: -1; /* 修改点:使用负值确保其在最底层,避免任何可能的遮挡 */
  348. background-size: cover;
  349. background-position: center;
  350. background-repeat: no-repeat;
  351. filter: brightness(0.8);
  352. }
  353. /* 全屏页面特殊处理 */
  354. .layout-wrapper.full-screen {
  355. background: none;
  356. min-height: 100vh;
  357. }
  358. /* 特殊背景页面的Header样式 */
  359. .transparent-header {
  360. background: transparent !important;
  361. backdrop-filter: blur(2px); /* 添加轻微模糊效果增强可读性 */
  362. }
  363. /* 特殊背景页面的文字颜色 */
  364. .light-text {
  365. color: #ffffff !important; /* 白色文字 */
  366. text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); /* 文字阴影增强可读性 */
  367. }
  368. /* 特殊背景页面的头像边框 */
  369. .light-avatar-border {
  370. border: 2px solid #ffffff !important; /* 白色边框 */
  371. }
  372. /* 内容区域在特殊背景页面透明 */
  373. .transparent-scroll {
  374. background-color: transparent !important;
  375. }
  376. /* Header 样式 */
  377. .layout-header {
  378. height: 80px;
  379. display: flex;
  380. align-items: center;
  381. justify-content: space-between;
  382. color: #333; /* 深色文字 */
  383. flex-shrink: 0;
  384. /* 确保在背景层上方 */
  385. position: relative;
  386. /* 修改点:增大 z-index 确保在背景层之上 */
  387. z-index: 10; /* 增大 z-index */
  388. background-color: white; /* 默认背景色 */
  389. }
  390. /* 修改Header布局样式 */
  391. .logo-title-row {
  392. display: flex;
  393. align-items: center;
  394. justify-content: space-between; /* 左右两侧分布 */
  395. width: 100%;
  396. gap: 24px;
  397. }
  398. .title-and-user {
  399. display: flex;
  400. flex-direction: row;
  401. flex: 1;
  402. }
  403. /* 用户信息区域样式调整 */
  404. .user-info-row {
  405. display: flex;
  406. align-items: center;
  407. gap: 16px;
  408. color: #333;
  409. }
  410. .welcome-text {
  411. font-size: 28px;
  412. font-weight: 500;
  413. color: #ffffff;
  414. white-space: nowrap; /* 防止文字换行 */
  415. }
  416. /* Tab 区域 - 不透明 */
  417. .tabs-row {
  418. height: 48px;
  419. display: flex;
  420. justify-content: center;
  421. align-items: center;
  422. padding: 0 24px;
  423. background: linear-gradient(to right, #1092d8, #02c3ad);
  424. border-bottom: none !important;
  425. flex-shrink: 0;
  426. /* 确保在背景层上方 */
  427. position: relative;
  428. /* 修改点:增大 z-index 确保在背景层之上 */
  429. z-index: 10; /* 增大 z-index */
  430. }
  431. /* el-tabs 外层容器 */
  432. .demo-tabs {
  433. height: 48px !important;
  434. display: flex;
  435. align-items: center;
  436. width: 100%;
  437. padding: 0 !important;
  438. margin: 0 !important;
  439. border-bottom: none !important;
  440. }
  441. /* 清除滑块条和底部线条 */
  442. .el-tabs__nav-wrap::after,
  443. .el-tabs__active-bar {
  444. display: none !important;
  445. height: 0 !important;
  446. border: none !important;
  447. }
  448. .el-tabs__nav-scroll {
  449. padding: 0 !important;
  450. margin: 0 !important;
  451. }
  452. /* Tabs 单项样式 */
  453. .el-tabs__item {
  454. height: 48px !important;
  455. line-height: 48px !important;
  456. display: flex !important;
  457. align-items: center;
  458. justify-content: center;
  459. padding: 0 20px !important;
  460. font-size: 20px;
  461. font-weight: 600;
  462. border-radius: 10px;
  463. transition: all 0.2s ease-in-out;
  464. background-color: transparent;
  465. position: relative;
  466. /* 修改点:增大 z-index 确保在背景层之上 */
  467. z-index: 10; /* 增大 z-index */
  468. }
  469. /* 激活 Tab */
  470. .el-tabs__item.is-active {
  471. background-color: #2a53ba;
  472. color: #ffffff;
  473. font-weight: 700;
  474. box-shadow: 0 4px 16px rgba(26, 188, 156, 0.4);
  475. /* 修改点:增大 z-index 确保在背景层之上 */
  476. z-index: 11; /* 激活项可以更高一点 */
  477. }
  478. /* 鼠标悬停 */
  479. .el-tabs__item:hover {
  480. background-color: #455a64;
  481. color: #ffffff;
  482. /* 修改点:增大 z-index 确保在背景层之上 */
  483. z-index: 11; /* 悬停时也可以更高 */
  484. }
  485. /* 图标样式 */
  486. .tab-icon {
  487. font-size: 24px;
  488. margin-right: 4px;
  489. color: inherit;
  490. }
  491. /* 文字样式 */
  492. .tab-label-text {
  493. font-size: 20px;
  494. color: inherit;
  495. line-height: 1;
  496. display: inline-block;
  497. }
  498. .logo {
  499. height: 60px;
  500. }
  501. .project-name {
  502. font-size: 32px; /* 适当调整字体大小 */
  503. font-weight: bold;
  504. color: #333;
  505. white-space: nowrap; /* 防止标题换行 */
  506. }
  507. /* 响应式设计:在小屏幕上调整布局 */
  508. @media (max-width: 1200px) {
  509. .project-name {
  510. font-size: 20px;
  511. }
  512. .welcome-text {
  513. font-size: 14px;
  514. }
  515. }
  516. @media (max-width: 768px) {
  517. .logo-title-row {
  518. flex-wrap: wrap;
  519. gap: 12px;
  520. }
  521. .left-section {
  522. order: 1;
  523. width: 100%;
  524. justify-content: center;
  525. }
  526. .right-section {
  527. order: 2;
  528. width: 100%;
  529. justify-content: center;
  530. }
  531. .project-name {
  532. font-size: 18px;
  533. text-align: center;
  534. }
  535. }
  536. .layout-main-container {
  537. flex: 1;
  538. display: flex;
  539. overflow: hidden;
  540. min-height: 0;
  541. /* 确保在背景层上方 */
  542. position: relative;
  543. /* 修改点:增大 z-index 确保在背景层之上 */
  544. z-index: 10; /* 增大 z-index */
  545. }
  546. /* 侧边栏 - 白色背景 */
  547. .layout-aside {
  548. width: 280px;
  549. background: linear-gradient(to bottom, #b7f1fc, #fff8f0);
  550. border-right: 1px solid;
  551. overflow-y: auto;
  552. color: #000000;
  553. padding-top: 8px;
  554. height: 100%;
  555. position: relative;
  556. /* 修改点:增大 z-index 确保在背景层之上 */
  557. z-index: 10; /* 增大 z-index */
  558. }
  559. /* 隐藏侧边栏滚动条 */
  560. .layout-aside::-webkit-scrollbar {
  561. display: none;
  562. }
  563. .layout-aside .el-menu-item,
  564. .layout-aside .el-sub-menu__title {
  565. font-size: 18px;
  566. font-weight: 500;
  567. color: #000000;
  568. background-color: transparent;
  569. transition: all 0.2s ease;
  570. border-radius: 6px;
  571. padding: 12px 16px !important;
  572. }
  573. .layout-aside .el-menu-item:hover,
  574. .layout-aside .el-sub-menu__title:hover {
  575. background-color: rgba(16, 146, 216, 0.1);
  576. color: #1092d8;
  577. }
  578. .layout-aside .el-menu-item.is-active,
  579. .layout-aside .el-sub-menu__title.is-active {
  580. background: linear-gradient(to right, #1092d8, #02c3ad);
  581. color: #000000 !important;
  582. border-radius: 8px;
  583. font-weight: 600;
  584. box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25);
  585. /* 修改点:增大 z-index 确保在背景层之上 */
  586. z-index: 11; /* 激活项可以更高一点 */
  587. }
  588. .layout-content-wrapper {
  589. flex: 1;
  590. overflow: hidden;
  591. display: flex;
  592. flex-direction: column;
  593. position: relative;
  594. /* 修改点:增大 z-index 确保在背景层之上 */
  595. z-index: 10; /* 增大 z-index */
  596. }
  597. /* 强制重置 el-tabs header 高度/边距/背景/阴影,避免背景层穿透错位 */
  598. .el-tabs__header.is-top {
  599. height: 48px !important;
  600. margin: 0 !important;
  601. padding: 0 !important;
  602. border: none !important;
  603. background: transparent !important;
  604. box-shadow: none !important;
  605. /* 修改点:增大 z-index 确保在背景层之上 */
  606. z-index: 10 !important; /* 增大 z-index */
  607. }
  608. /* 全屏页面特殊处理 */
  609. .layout-wrapper.full-screen .layout-main-container {
  610. height: 100vh;
  611. }
  612. .scrollable-content {
  613. flex: 1;
  614. overflow: auto;
  615. padding: 0 20px;
  616. box-sizing: border-box;
  617. background-color: white; /* 默认背景色 */
  618. position: relative; /* 确保它可以参与 z-index 计算 */
  619. /* 修改点:增大 z-index 确保在背景层之上 */
  620. z-index: 10; /* 增大 z-index */
  621. }
  622. .scrollable-content.transparent-scroll {
  623. background-color: transparent;
  624. /* 修改点:增大 z-index 确保在背景层之上 */
  625. z-index: 10; /* 即使透明也应保持在上层 */
  626. }
  627. /* 左侧区域 */
  628. .left-section {
  629. display: flex;
  630. align-items: center;
  631. gap: 24px;
  632. flex-shrink: 0; /* 防止缩小 */
  633. }
  634. /* 右侧区域 */
  635. .right-section {
  636. display: flex;
  637. align-items: center;
  638. justify-content: flex-end;
  639. flex-shrink: 0; /* 防止缩小 */
  640. }
  641. /* 语言切换按钮样式 */
  642. .lang-toggle-btn {
  643. display: flex;
  644. align-items: center;
  645. gap: 6px;
  646. padding: 8px 16px;
  647. border: 2px solid rgba(255, 255, 255, 0.6);
  648. border-radius: 20px;
  649. background: rgba(255, 255, 255, 0.15);
  650. backdrop-filter: blur(8px);
  651. color: #ffffff;
  652. font-size: 14px;
  653. font-weight: 500;
  654. cursor: pointer;
  655. transition: all 0.3s ease;
  656. outline: none;
  657. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  658. }
  659. .lang-toggle-btn:hover {
  660. background: rgba(255, 255, 255, 0.25);
  661. border-color: rgba(255, 255, 255, 0.9);
  662. transform: translateY(-2px);
  663. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
  664. }
  665. .lang-toggle-btn:active {
  666. transform: translateY(0);
  667. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  668. }
  669. .lang-toggle-btn .el-icon-globe {
  670. font-size: 18px;
  671. color: #ffffff;
  672. }
  673. .lang-toggle-btn .lang-label {
  674. font-size: 13px;
  675. font-weight: 600;
  676. letter-spacing: 0.5px;
  677. }
  678. /* 浅色文字模式(特殊背景页面) */
  679. .lang-toggle-btn.light-text {
  680. border-color: rgba(255, 255, 255, 0.6);
  681. color: #ffffff;
  682. }
  683. .lang-toggle-btn.light-text .el-icon-globe {
  684. color: #ffffff;
  685. }
  686. /* 响应式设计 */
  687. @media (max-width: 768px) {
  688. .lang-toggle-btn {
  689. padding: 6px 12px;
  690. font-size: 12px;
  691. }
  692. .lang-toggle-btn .el-icon-globe {
  693. font-size: 16px;
  694. }
  695. .lang-toggle-btn .lang-label {
  696. font-size: 11px;
  697. }
  698. }
  699. </style>