AppLayout.vue 20 KB

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