|
@@ -1,3 +1,116 @@
|
|
|
+<template>
|
|
|
+ <div class="layout-wrapper" :class="{ 'full-screen': isFullScreen }">
|
|
|
+ <!-- 背景层 - 只在需要透明的地方显示 -->
|
|
|
+ <div
|
|
|
+ class="background-layer"
|
|
|
+ v-if="!isFullScreen && !isSelectCity && !isExcludedRoute"
|
|
|
+ ></div>
|
|
|
+
|
|
|
+ <!-- Header 部分 -->
|
|
|
+ <el-header
|
|
|
+ class="layout-header"
|
|
|
+ v-if="!isFullScreen"
|
|
|
+ :class="{ 'excluded-bg-header': isExcludedRoute }"
|
|
|
+ >
|
|
|
+ <div class="logo-title-row">
|
|
|
+ <img src="@/assets/logo.png" alt="Logo" class="logo" />
|
|
|
+ <div class="title-and-user">
|
|
|
+ <span
|
|
|
+ class="project-name"
|
|
|
+ :class="{ 'excluded-text': isExcludedRoute }"
|
|
|
+ >
|
|
|
+ 区域土壤重金属污染风险评估
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <!-- 用户信息 - 在select-city页面隐藏 -->
|
|
|
+ <div
|
|
|
+ class="user-info-row"
|
|
|
+ v-if="!isSelectCity"
|
|
|
+ :class="{ 'excluded-text': isExcludedRoute }"
|
|
|
+ >
|
|
|
+ <span class="welcome-text">
|
|
|
+ 欢迎{{
|
|
|
+ tokenStore.token.loginType === "admin" ? "管理员" : "用户"
|
|
|
+ }}登录成功
|
|
|
+ </span>
|
|
|
+ <el-dropdown>
|
|
|
+ <span class="el-dropdown-link">
|
|
|
+ <el-avatar
|
|
|
+ :size="40"
|
|
|
+ :class="{ 'excluded-avatar-border': isExcludedRoute }"
|
|
|
+ src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item disabled
|
|
|
+ >用户名:{{ userInfo.name }}</el-dropdown-item
|
|
|
+ >
|
|
|
+ <el-dropdown-item divided @click="handleLogout"
|
|
|
+ >退出登录</el-dropdown-item
|
|
|
+ >
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-header>
|
|
|
+
|
|
|
+ <!-- Tab 区域 - 不透明 -->
|
|
|
+ <div class="tabs-row" v-if="!isFullScreen">
|
|
|
+ <el-tabs
|
|
|
+ v-if="showTabs"
|
|
|
+ v-model="activeName"
|
|
|
+ class="demo-tabs"
|
|
|
+ :style="tabStyle"
|
|
|
+ @tab-click="handleClick"
|
|
|
+ >
|
|
|
+ <el-tab-pane v-for="tab in tabs" :key="tab.name" :name="tab.name">
|
|
|
+ <template #label>
|
|
|
+ <i :class="['tab-icon', tab.icon]"></i>
|
|
|
+ <span class="tab-label-text">{{ tab.label }}</span>
|
|
|
+ </template>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ <div v-else class="single-tab" @click="handleClick(tabs[0], $event)">
|
|
|
+ <i :class="['tab-icon', tabs[0].icon]"></i>
|
|
|
+ <span class="tab-label-text">{{ tabs[0].label }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主体区域 -->
|
|
|
+ <el-container class="layout-main-container">
|
|
|
+ <!-- 侧边栏 - 不透明 -->
|
|
|
+ <el-aside v-if="showAside && showTabs" class="layout-aside">
|
|
|
+ <component
|
|
|
+ :is="AsideComponent"
|
|
|
+ :activeTab="activeName"
|
|
|
+ :showTabs="showTabs"
|
|
|
+ />
|
|
|
+ </el-aside>
|
|
|
+
|
|
|
+ <!-- 内容区域 -->
|
|
|
+ <el-main
|
|
|
+ class="layout-content-wrapper"
|
|
|
+ :style="mainStyle"
|
|
|
+ :class="{ 'excluded-bg-content': isExcludedRoute }"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="scrollable-content"
|
|
|
+ :style="{
|
|
|
+ backgroundImage: currentBgImage,
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div :class="{ 'select-city-container': isSelectCity }">
|
|
|
+ <RouterView />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-main>
|
|
|
+ </el-container>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, computed, watch, defineAsyncComponent } from "vue";
|
|
|
import { useRouter, useRoute } from "vue-router";
|
|
@@ -5,102 +118,289 @@ import { useTokenStore } from "@/stores/mytoken";
|
|
|
import { ElMessageBox, ElMessage } from "element-plus";
|
|
|
import { logout } from "@/API/users";
|
|
|
|
|
|
+// 使用更可靠的图片导入方式
|
|
|
+function getImageUrl(name: string) {
|
|
|
+ try {
|
|
|
+ return new URL(`../../assets/bg/${name}`, import.meta.url).href;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("加载背景图失败:", error);
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const router = useRouter();
|
|
|
const route = useRoute();
|
|
|
const tokenStore = useTokenStore();
|
|
|
+const currentBgImage = ref("");
|
|
|
|
|
|
-// 新增:判断当前路由是否全屏模式(无头部和侧边栏)
|
|
|
+// 是否为全屏页面
|
|
|
const isFullScreen = computed(() => route.meta.fullScreen === true);
|
|
|
|
|
|
-// 根据用户类型动态定义 Tab 数据
|
|
|
+// 判断是否是select-city页面
|
|
|
+const isSelectCity = computed(() => route.path === "/select-city");
|
|
|
+
|
|
|
+// 背景图路径映射 - 使用简单路径
|
|
|
+const routeBackgroundMap: Record<string, string> = {
|
|
|
+ "/samplingMethodDevice1": getImageUrl("irrigation.jpg"),
|
|
|
+ "/irriSampleData": getImageUrl("irrigation.jpg"),
|
|
|
+ "/csSampleData": getImageUrl("irrigation.jpg"),
|
|
|
+ "/irriInputFlux": getImageUrl("irrigation.jpg"),
|
|
|
+ "/prodInputFlux": getImageUrl("agricultural_input.png"),
|
|
|
+ "/airSampleData": getImageUrl("atmospheric_deposition.png"),
|
|
|
+ "/airInputFlux": getImageUrl("atmospheric_deposition.png"),
|
|
|
+ "/heavyMetalEnterprise": getImageUrl("atmospheric_deposition.png"),
|
|
|
+ "/grainRemovalInputFlux": getImageUrl("grain_removal.png"),
|
|
|
+ "/strawRemovalInputFlux": getImageUrl("straw_removal.png"),
|
|
|
+ "/subsurfaceLeakageInputFlux": getImageUrl("subsurface_leakage.jpg"),
|
|
|
+ "/surfaceRunoffInputFlux": getImageUrl("surface_runoff.jpg"),
|
|
|
+};
|
|
|
+
|
|
|
+// Tab 配置
|
|
|
const tabs = computed(() => {
|
|
|
if (tokenStore.token.loginType === "admin") {
|
|
|
return [
|
|
|
- { name: "parameterConfig", label: "参数配置", routes: ["/ModelSelection", "/thres", "/ModelTrain"] },
|
|
|
- { name: "dataManagement", label: "数据管理", routes: ["/Visualization", "/Visualizatio", "/AdminRegionData", "/SoilAssessmentUnitData", "/SoilHeavyMetalData", "/CropHeavyMetalData", "/LandUseTypeData", "/SoilAcidificationData", "/ClimateInfoData", "/GeographicEnvInfoData"] },
|
|
|
- { name: "infoManagement", label: "信息管理", routes: ["/IntroductionUpdate"] },
|
|
|
- { name: "modelManagement", label: "模型管理及配置", routes: ["/Admin/CadmiumPredictionModel", "/Admin/EffectiveCadmiumModel", "/Admin/RiceRiskModel", "/Admin/AcidReductionModel", "/Admin/WheatRiskModel", "/Admin/VegetableRiskModel"] },
|
|
|
- { name: "userManagement", label: "用户管理", routes: ["/Admin/UserRegistration", "/Admin/UserManagement"] },
|
|
|
+ {
|
|
|
+ name: "dataManagement",
|
|
|
+ label: "数据管理",
|
|
|
+ icon: "el-icon-folder",
|
|
|
+ routes: [
|
|
|
+ "/soilAcidReductionData",
|
|
|
+ "/soilAcidificationData",
|
|
|
+ "/AdminRegionData",
|
|
|
+ "/SoilAssessmentUnitData",
|
|
|
+ "/SoilHeavyMetalData",
|
|
|
+ "/CropHeavyMetalData",
|
|
|
+ "/LandUseTypeData",
|
|
|
+ "/ClimateInfoData",
|
|
|
+ "/GeographicEnvInfoData",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "infoManagement",
|
|
|
+ label: "信息管理",
|
|
|
+ icon: "el-icon-document",
|
|
|
+ routes: ["/IntroductionUpdate"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "modelManagement",
|
|
|
+ label: "模型管理及配置",
|
|
|
+ icon: "el-icon-cpu",
|
|
|
+ routes: [
|
|
|
+ "/CadmiumPredictionModel",
|
|
|
+ "/EffectiveCadmiumModel",
|
|
|
+ "/Admin/RiceRiskModel",
|
|
|
+ "/AdminModelSelection",
|
|
|
+ "/Admin/thres",
|
|
|
+ "/Admin/ModelTrain",
|
|
|
+ "/Admin/WheatRiskModel",
|
|
|
+ "/Admin/VegetableRiskModel",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "userManagement",
|
|
|
+ label: "用户管理",
|
|
|
+ icon: "el-icon-user",
|
|
|
+ routes: ["/UserManagement", "/UserRegistration"],
|
|
|
+ },
|
|
|
];
|
|
|
} else {
|
|
|
return [
|
|
|
- { name: "shuJuKanBan", label: "数据看板", routes: ["/shuJuKanBan"] },
|
|
|
- { name: "introduction", label: "软件简介", routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"] },
|
|
|
- { name: "heavyMetalFluxCalculation", label: "重金属输入输出通量计算", routes: ["/irrigationWater", "/agriculturalProductInput", "/atmosphericDryWetDeposition", "/surfaceRunoff", "/cropRemoval", "/subsurfaceFlow"] },
|
|
|
- { name: "mapView", label: "地图展示", routes: ["/mapView"] },
|
|
|
- { name: "cadmiumPrediction", label: "土壤镉含量预测", routes: ["/TotalCadmiumPrediction", "/EffectiveCadmiumPrediction"] },
|
|
|
- { name: "cropRiskAssessment", label: "作物安全生产风险阈值评估", routes: ["/cropRiskAssessment"] },
|
|
|
- { name: "farmlandQualityAssessment", label: "耕地质量评估", routes: ["/farmlandQualityAssessment"] },
|
|
|
- { name: "soilAcidificationPrediction", label: "土壤酸化预测", routes: ["/Calculation", "/AcidNeutralizationModel"] },
|
|
|
- { name: "scenarioSimulation", label: "情景模拟", routes: ["/TraditionalFarmingRisk", "/HeavyMetalCadmiumControl", "/SoilAcidificationControl"] },
|
|
|
- { name: "dataStatistics", label: "数据统计报表", routes: ["/DetectionStatistics", "/FarmlandPollutionStatistics", "/PlantingRiskStatistics"] },
|
|
|
- ];
|
|
|
+ {
|
|
|
+ name: "shuJuKanBan",
|
|
|
+ label: "数据看板",
|
|
|
+ icon: "el-icon-data-analysis",
|
|
|
+ routes: ["/shuJuKanBan"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "introduction",
|
|
|
+ label: "软件简介",
|
|
|
+ icon: "el-icon-info-filled",
|
|
|
+ routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "HmOutFlux",
|
|
|
+ label: "重金属输入通量",
|
|
|
+ icon: "el-icon-refresh",
|
|
|
+ routes: [
|
|
|
+ "/samplingMethodDevice1",
|
|
|
+ "/irriSampleData",
|
|
|
+ "/csSampleData",
|
|
|
+ "/irriInputFlux",
|
|
|
+ "/samplingDesc2",
|
|
|
+ "/prodInputFlux",
|
|
|
+ "/samplingDesc3",
|
|
|
+ "/airSampleData",
|
|
|
+ "/airInputFlux",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "hmInFlux",
|
|
|
+ label: "重金属输出通量",
|
|
|
+ icon: "el-icon-refresh",
|
|
|
+ routes: [
|
|
|
+ "/samplingDesc1",
|
|
|
+ "/grainRemovalInputFlux",
|
|
|
+ "/samplingDesc2",
|
|
|
+ "/strawRemovalInputFlux",
|
|
|
+ "/samplingDesc3",
|
|
|
+ "/subsurfaceLeakageInputFlux",
|
|
|
+ "/samplingDesc4",
|
|
|
+ "/surfaceRunoffInputFlux",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "mapView",
|
|
|
+ label: "地图展示",
|
|
|
+ icon: "el-icon-map-location",
|
|
|
+ routes: ["/mapView"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "cadmiumPrediction",
|
|
|
+ label: "土壤污染物含量预测",
|
|
|
+ icon: "el-icon-c-scale-to-original",
|
|
|
+ routes: [
|
|
|
+ "/totalInputFlux",
|
|
|
+ "/TotalCadmiumPrediction",
|
|
|
+ "/totalOutputFlux",
|
|
|
+ "/netFlux",
|
|
|
+ "/currentYearConcentration",
|
|
|
+ "/EffectiveCadmiumPrediction",
|
|
|
+ "CropCadmiumPrediction",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "cropRiskAssessment",
|
|
|
+ label: "作物风险评估",
|
|
|
+ icon: "el-icon-warning",
|
|
|
+ routes: ["/cropRiskAssessment"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "farmlandQualityAssessment",
|
|
|
+ label: "耕地质量评估",
|
|
|
+ icon: "el-icon-rank",
|
|
|
+ routes: ["/farmlandQualityAssessment"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "soilAcidificationPrediction",
|
|
|
+ label: "土壤酸化预测",
|
|
|
+ icon: "el-icon-magic-stick",
|
|
|
+ routes: ["/Calculation", "/AcidNeutralizationModel"],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "scenarioSimulation",
|
|
|
+ label: "情景模拟",
|
|
|
+ icon: "el-icon-s-operation",
|
|
|
+ routes: [
|
|
|
+ "/TraditionalFarmingRisk",
|
|
|
+ "/HeavyMetalCadmiumControl",
|
|
|
+ "/SoilAcidificationControl",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "dataStatistics",
|
|
|
+ label: "数据统计",
|
|
|
+ icon: "el-icon-pie-chart",
|
|
|
+ routes: [
|
|
|
+ "/DetectionStatistics",
|
|
|
+ "/FarmlandPollutionStatistics",
|
|
|
+ "/PlantingRiskStatistics",
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ].filter(tab => !["shuJuKanBan", "mapView", "introduction"].includes(tab.name));
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-// 当前激活的 Tab
|
|
|
-const activeName = ref(tabs.value[0]?.name || ""); // 确保默认激活的是第一个 Tab
|
|
|
-
|
|
|
-// 控制是否显示 Tabs
|
|
|
+// 计算:当前激活 tab - 保持不变
|
|
|
+const activeName = ref(tabs.value[0]?.name || "");
|
|
|
const showTabs = computed(() => tabs.value.length > 1);
|
|
|
-
|
|
|
-// 控制 Tabs 样式
|
|
|
-const tabStyle = computed(() => {
|
|
|
- return tabs.value.length === 1 ? { width: "100%", justifyContent: "center" } : {};
|
|
|
-});
|
|
|
-
|
|
|
-// 用于控制首次是否跳转
|
|
|
+const tabStyle = computed(() =>
|
|
|
+ tabs.value.length === 1 ? { width: "100%", justifyContent: "center" } : {}
|
|
|
+);
|
|
|
let hasNavigated = false;
|
|
|
|
|
|
-// 监听 activeName 的变化,加载对应数据,且首次不自动跳转
|
|
|
watch(
|
|
|
() => activeName.value,
|
|
|
(newTab) => {
|
|
|
- const tab = tabs.value.find(t => t.name === newTab);
|
|
|
+ const tab = tabs.value.find((t) => t.name === newTab);
|
|
|
const targetPath = tab?.routes?.[0];
|
|
|
-
|
|
|
if (!hasNavigated) {
|
|
|
hasNavigated = true;
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
if (tab && targetPath && router.currentRoute.value.path !== targetPath) {
|
|
|
- console.log("跳转目标:", targetPath);
|
|
|
router.push({ path: targetPath });
|
|
|
}
|
|
|
},
|
|
|
{ immediate: true }
|
|
|
);
|
|
|
|
|
|
-// Tab 点击事件
|
|
|
+// 显式列出所有不需要背景图的路由路径
|
|
|
+const bgExcludedRoutes = computed(() => {
|
|
|
+ return [
|
|
|
+ // 列出所有不需要背景图的路由路径
|
|
|
+ "/shuJuKanBan",
|
|
|
+ "/mapView",
|
|
|
+ "/cropRiskAssessment",
|
|
|
+ "/farmlandQualityAssessment",
|
|
|
+ "/dataStatistics",
|
|
|
+
|
|
|
+ // 强制"重金属输入通量"和"重金属输出通量"下的所有路由为白色背景
|
|
|
+ ...tabs.value
|
|
|
+ .filter(tab => ["HmOutFlux", "hmInFlux"].includes(tab.name))
|
|
|
+ .flatMap(tab => tab.routes)
|
|
|
+ ];
|
|
|
+});
|
|
|
+
|
|
|
+// 判断当前路由是否在排除列表中
|
|
|
+const isExcludedRoute = computed(() => bgExcludedRoutes.value.includes(route.path));
|
|
|
+
|
|
|
+// 获取当前页面的背景图
|
|
|
+const getBgImage = (path: string) => {
|
|
|
+ // 若当前路径属于排除项,不显示背景图
|
|
|
+ if (isExcludedRoute.value) return "";
|
|
|
+
|
|
|
+ return routeBackgroundMap[path] || "";
|
|
|
+};
|
|
|
+
|
|
|
+// 当前背景图
|
|
|
+watch(
|
|
|
+ () => route.path,
|
|
|
+ (newPath) => {
|
|
|
+ currentBgImage.value = getBgImage(newPath) ? `url(${getBgImage(newPath)})` : "";
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+// 点击切换 tab
|
|
|
const handleClick = (tab: any, event: Event) => {
|
|
|
activeAsideTab.value = tab.props.name;
|
|
|
};
|
|
|
|
|
|
-// 动态选择侧边栏组件
|
|
|
+// 动态加载侧边栏组件
|
|
|
const AsideComponent = computed(() => {
|
|
|
- if (["parameterConfig", "dataManagement", "infoManagement", "modelManagement", "userManagement"].includes(activeName.value)) {
|
|
|
+ if (
|
|
|
+ ["parameterConfig", "dataManagement", "infoManagement", "modelManagement", "userManagement"]
|
|
|
+ .includes(activeName.value)
|
|
|
+ ) {
|
|
|
return defineAsyncComponent(() => import("./AppAsideForTab2.vue"));
|
|
|
- } else if (["introduction", "acidModel", "neutralizationModel", "mapView", "cadmiumPrediction", "cropRiskAssessment", "farmlandQualityAssessment", "soilAcidificationPrediction", "scenarioSimulation", "dataStatistics", "heavyMetalFluxCalculation"].includes(activeName.value)) {
|
|
|
+ } else {
|
|
|
return defineAsyncComponent(() => import("./AppAside.vue"));
|
|
|
}
|
|
|
- return null;
|
|
|
});
|
|
|
|
|
|
-// 控制侧边栏显示(全屏时隐藏)
|
|
|
+// 是否显示侧边栏
|
|
|
const showAside = computed(() => {
|
|
|
- return !isFullScreen.value && activeName.value !== "mapView" && activeName.value !== "shuJuKanBan" && activeName.value !== "cropRiskAssessment" && activeName.value !== "farmlandQualityAssessment"; // 数据看板不显示侧边栏
|
|
|
+ return (
|
|
|
+ !isFullScreen.value &&
|
|
|
+ activeName.value !== "mapView" &&
|
|
|
+ activeName.value !== "cropRiskAssessment" &&
|
|
|
+ activeName.value !== "farmlandQualityAssessment"
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
-// 固定选中侧边栏选项
|
|
|
-const activeAsideTab = ref(activeName.value || "shuJuKanBan");
|
|
|
-
|
|
|
-// 用户信息
|
|
|
-const userInfo = reactive({
|
|
|
- name: tokenStore.token.name || "未登录",
|
|
|
-});
|
|
|
+const activeAsideTab = ref(activeName.value || "");
|
|
|
+const userInfo = reactive({ name: tokenStore.token.name || "未登录" });
|
|
|
|
|
|
-// 处理退出逻辑
|
|
|
const handleLogout = async () => {
|
|
|
try {
|
|
|
await ElMessageBox.confirm("确定要退出登录吗?", "提示", {
|
|
@@ -112,230 +412,338 @@ const handleLogout = async () => {
|
|
|
tokenStore.clearToken();
|
|
|
ElMessage.success("退出成功");
|
|
|
router.push("/login");
|
|
|
- } catch (error) {
|
|
|
+ } catch {
|
|
|
ElMessage.info("已取消退出");
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 添加全局错误处理逻辑(如果需要)
|
|
|
-watch(
|
|
|
- () => route,
|
|
|
- (newRoute) => {
|
|
|
- console.log(`[AppLayout] Route changed to: ${newRoute.path}`);
|
|
|
- },
|
|
|
- { immediate: true }
|
|
|
-);
|
|
|
+// 内容区样式
|
|
|
+const mainStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ padding: ["mapView", "infoManagement"].includes(activeName.value) ? "0" : "20px",
|
|
|
+ overflow: "hidden",
|
|
|
+ };
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
-<template>
|
|
|
- <div class="common-layout" :class="{ 'full-screen': isFullScreen }">
|
|
|
- <el-container style="height: 100vh">
|
|
|
- <!-- 只在非全屏模式显示顶部导航栏 -->
|
|
|
- <el-header v-if="!isFullScreen" class="header">
|
|
|
- <div class="header-content">
|
|
|
- <!-- 添加 logo 图片 -->
|
|
|
- <div class="logo-container">
|
|
|
- <img src="@/assets/logo.png" alt="Logo 1" class="logo" />
|
|
|
- </div>
|
|
|
- <span class="project-name">酸性土壤精准治酸智能专家系统</span>
|
|
|
- <el-tabs
|
|
|
- v-if="showTabs"
|
|
|
- v-model="activeName"
|
|
|
- class="demo-tabs"
|
|
|
- :style="tabStyle"
|
|
|
- @tab-click="handleClick"
|
|
|
- >
|
|
|
- <el-tab-pane
|
|
|
- v-for="tab in tabs"
|
|
|
- :key="tab.name"
|
|
|
- :label="tab.label"
|
|
|
- :name="tab.name"
|
|
|
- />
|
|
|
- </el-tabs>
|
|
|
- <div v-else class="single-tab" @click="handleClick(tabs[0], $event)">
|
|
|
- {{ tabs[0]?.label }}
|
|
|
- </div>
|
|
|
- <el-dropdown>
|
|
|
- <span class="el-dropdown-link">
|
|
|
- <el-avatar
|
|
|
- :size="40"
|
|
|
- :src="'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'"
|
|
|
- />
|
|
|
- </span>
|
|
|
- <template #dropdown>
|
|
|
- <el-dropdown-menu>
|
|
|
- <el-dropdown-item disabled>用户名:{{ userInfo.name }}</el-dropdown-item>
|
|
|
- <el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </template>
|
|
|
- </el-dropdown>
|
|
|
- </div>
|
|
|
- </el-header>
|
|
|
-
|
|
|
- <el-container>
|
|
|
- <!-- 非全屏且满足条件时显示侧边栏 -->
|
|
|
- <el-aside
|
|
|
- v-if="!isFullScreen && showAside && showTabs"
|
|
|
- :width="'200px'"
|
|
|
- class="aside"
|
|
|
- >
|
|
|
- <component
|
|
|
- :is="AsideComponent"
|
|
|
- :activeTab="activeName"
|
|
|
- :showTabs="showTabs"
|
|
|
- />
|
|
|
- </el-aside>
|
|
|
-
|
|
|
- <el-container class="header-and-main" :style="isFullScreen ? { height: '100vh' } : {}">
|
|
|
- <el-main
|
|
|
- :style="isFullScreen ? { padding: 0 } : { padding: activeName === 'mapView' || activeName === 'infoManagement' ? '0' : '20px', backgroundColor: '#ecf0f1' }"
|
|
|
- >
|
|
|
- <el-scrollbar :style="isFullScreen ? { height: '100vh' } : { height: 'auto' }">
|
|
|
- <RouterView />
|
|
|
- </el-scrollbar>
|
|
|
- </el-main>
|
|
|
- </el-container>
|
|
|
- </el-container>
|
|
|
- </el-container>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
+<style>
|
|
|
+/* 隐藏所有滚动条 */
|
|
|
+*::-webkit-scrollbar {
|
|
|
+ display: none; /* Chrome, Safari, Opera */
|
|
|
+}
|
|
|
|
|
|
-<style scoped>
|
|
|
-.common-layout {
|
|
|
- display: flex;
|
|
|
- height: 100%;
|
|
|
- font-family: 'Roboto', sans-serif;
|
|
|
- background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
|
|
|
+* {
|
|
|
+ -ms-overflow-style: none; /* IE and Edge */
|
|
|
+ scrollbar-width: none; /* Firefox */
|
|
|
}
|
|
|
|
|
|
-/* 新增:全屏布局,移除间距,充满视图 */
|
|
|
-.full-screen {
|
|
|
+/* 整体布局容器 */
|
|
|
+.layout-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
height: 100vh;
|
|
|
overflow: hidden;
|
|
|
+ position: relative;
|
|
|
}
|
|
|
|
|
|
-.full-screen .el-main,
|
|
|
-.full-screen .el-scrollbar {
|
|
|
- height: 100vh !important;
|
|
|
- padding: 0 !important;
|
|
|
- background-color: transparent !important;
|
|
|
+/* 全屏页面特殊处理 */
|
|
|
+.layout-wrapper.full-screen {
|
|
|
+ background: none;
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+/* 背景层 - 用于透明部分 */
|
|
|
+.background-layer {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: url("@/assets/header-bg.jpg") center center / cover no-repeat;
|
|
|
+ background-attachment: fixed;
|
|
|
+ z-index: -1; /* 确保在内容下方 */
|
|
|
}
|
|
|
|
|
|
-.header {
|
|
|
+/* 透明 Header */
|
|
|
+.layout-header {
|
|
|
+ height: 150px;
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
align-items: center;
|
|
|
- padding: 15px 30px;
|
|
|
- background-color: #ffffff;
|
|
|
- border-bottom: 1px solid #e5e7eb;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
- border-radius: 10px;
|
|
|
+ justify-content: space-between;
|
|
|
+ background-color: transparent !important;
|
|
|
+ backdrop-filter: none !important;
|
|
|
+ -webkit-backdrop-filter: none !important;
|
|
|
+ border-bottom: none;
|
|
|
+ box-shadow: none !important;
|
|
|
+ color: #f0f3f7;
|
|
|
+ flex-shrink: 0;
|
|
|
+ position: relative; /* 确保在背景层上方 */
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 排除背景图页面的Header样式 */
|
|
|
+.excluded-bg-header {
|
|
|
+ background-color: #ffffff !important;
|
|
|
+ border-bottom: 1px solid #eee !important;
|
|
|
}
|
|
|
|
|
|
-.header-content {
|
|
|
+.logo-title-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
+ gap: 24px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
-.project-name {
|
|
|
- font-size: 1.2rem;
|
|
|
- font-weight: bold;
|
|
|
- color: #1f2937;
|
|
|
- margin-right: 20px;
|
|
|
+.title-and-user {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
|
|
|
-.demo-tabs {
|
|
|
- flex-grow: 1;
|
|
|
- margin-right: 20px;
|
|
|
+/* 用户信息区域 */
|
|
|
+.user-info-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- background-color: #f9fafb;
|
|
|
- border-radius: 8px;
|
|
|
- padding: 5px;
|
|
|
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 24px;
|
|
|
+ background-color: transparent !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ color: #ffffff;
|
|
|
+ padding-top: 1px;
|
|
|
+ position: static;
|
|
|
+ z-index: 100;
|
|
|
}
|
|
|
|
|
|
-.demo-tabs > .el-tabs__header {
|
|
|
- border-bottom: none;
|
|
|
+/* 排除背景图页面的文字颜色 */
|
|
|
+.excluded-text {
|
|
|
+ color: #333 !important;
|
|
|
}
|
|
|
|
|
|
-.demo-tabs > .el-tabs__item {
|
|
|
- color: #374151;
|
|
|
- font-size: 14px;
|
|
|
+/* 头像边框白色 */
|
|
|
+.el-dropdown-link .el-avatar {
|
|
|
+ border: 2px solid white;
|
|
|
+}
|
|
|
+
|
|
|
+/* 排除背景图页面的头像边框 */
|
|
|
+.excluded-avatar-border {
|
|
|
+ border: 2px solid #333 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.welcome-text {
|
|
|
+ font-size: 28px;
|
|
|
font-weight: 500;
|
|
|
- padding: 8px 16px;
|
|
|
- border-radius: 6px;
|
|
|
- transition: background-color 0.3s ease, color 0.3s ease;
|
|
|
+ color: #ffffff !important;
|
|
|
}
|
|
|
|
|
|
-.demo-tabs > .el-tabs__item:hover {
|
|
|
- background-color: #e5e7eb;
|
|
|
- color: #2563eb;
|
|
|
+/* 排除背景图页面的欢迎文字颜色 */
|
|
|
+.excluded-text .welcome-text {
|
|
|
+ color: #333 !important;
|
|
|
}
|
|
|
|
|
|
-.demo-tabs > .el-tabs__item.is-active {
|
|
|
- background-color: #2563eb;
|
|
|
- color: #ffffff;
|
|
|
- font-weight: bold;
|
|
|
- box-shadow: inset 0 0 6px rgba(37, 99, 235, 0.5);
|
|
|
+/* Tab 区域 - 不透明 */
|
|
|
+.tabs-row {
|
|
|
+ height: 48px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 24px;
|
|
|
+ background: linear-gradient(to right, #1092d8, #02c3ad);
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+ -webkit-backdrop-filter: blur(10px);
|
|
|
+ border-bottom: none !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ margin-bottom: 0 !important;
|
|
|
+ flex-shrink: 0;
|
|
|
+ position: relative; /* 确保在背景层上方 */
|
|
|
+ z-index: 1;
|
|
|
}
|
|
|
|
|
|
-.single-tab {
|
|
|
+/* el-tabs 外层容器 */
|
|
|
+.demo-tabs {
|
|
|
+ height: 48px !important;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
width: 100%;
|
|
|
- height: 40px;
|
|
|
- background-color: #2563eb;
|
|
|
+ padding: 0 !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ border-bottom: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 清除滑块条和底部线条 */
|
|
|
+.el-tabs__nav-wrap::after,
|
|
|
+.el-tabs__active-bar {
|
|
|
+ display: none !important;
|
|
|
+ height: 0 !important;
|
|
|
+ border: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tabs__nav-scroll {
|
|
|
+ padding: 0 !important;
|
|
|
+ margin: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* Tabs 单项样式 */
|
|
|
+.el-tabs__item {
|
|
|
+ height: 48px !important;
|
|
|
+ line-height: 48px !important;
|
|
|
+ display: flex !important;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0 20px !important;
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #cfd8dc;
|
|
|
+ border-radius: 10px;
|
|
|
+ transition: all 0.2s ease-in-out;
|
|
|
+ background-color: transparent;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 激活 Tab */
|
|
|
+.el-tabs__item.is-active {
|
|
|
+ background-color: #2a53ba;
|
|
|
color: #ffffff;
|
|
|
- font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+ box-shadow: 0 4px 16px rgba(26, 188, 156, 0.4);
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
+
|
|
|
+/* 鼠标悬停 */
|
|
|
+.el-tabs__item:hover {
|
|
|
+ background-color: #455a64;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图标样式 */
|
|
|
+.tab-icon {
|
|
|
+ font-size: 24px;
|
|
|
+ margin-right: 4px;
|
|
|
+ color: inherit;
|
|
|
+}
|
|
|
+
|
|
|
+/* 文字样式 */
|
|
|
+.tab-label-text {
|
|
|
+ font-size: 20px;
|
|
|
+ color: inherit;
|
|
|
+ line-height: 1;
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.logo {
|
|
|
+ height: 60px;
|
|
|
+}
|
|
|
+
|
|
|
+.project-name {
|
|
|
+ font-size: 48px;
|
|
|
font-weight: bold;
|
|
|
- border-radius: 8px;
|
|
|
- cursor: pointer;
|
|
|
- transition: background-color 0.3s ease;
|
|
|
- user-select: none;
|
|
|
+ margin-top: 30px;
|
|
|
+ color: #f0f3f7;
|
|
|
}
|
|
|
|
|
|
-.single-tab:hover {
|
|
|
- background-color: #1d4ed8;
|
|
|
+/* 排除背景图页面的项目名称颜色 */
|
|
|
+.excluded-text.project-name {
|
|
|
+ color: #333 !important;
|
|
|
}
|
|
|
|
|
|
-.aside {
|
|
|
- padding: 0;
|
|
|
- background-color: #f9fafb;
|
|
|
- border-radius: 10px;
|
|
|
- margin-left: 10px;
|
|
|
- box-shadow: 0 0 10px rgb(32 33 36 / 8%);
|
|
|
+.layout-main-container {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
}
|
|
|
|
|
|
-.header-and-main {
|
|
|
- border-radius: 10px;
|
|
|
- background-color: #ffffff;
|
|
|
- margin-left: 10px;
|
|
|
+/* 侧边栏 - 不透明 */
|
|
|
+.layout-aside {
|
|
|
+ width: 360px;
|
|
|
+ background: linear-gradient(to bottom, #B7F1FC, #FFF8F0 );
|
|
|
+ border-right: 1px solid;
|
|
|
+ overflow-y: auto;
|
|
|
+ color: #000000;
|
|
|
+ padding-top: 8px;
|
|
|
height: 100%;
|
|
|
+ position: relative;
|
|
|
+ z-index: 2; /* 确保在背景层上方 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 隐藏侧边栏滚动条 */
|
|
|
+.layout-aside::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-aside .el-menu-item,
|
|
|
+.layout-aside .el-sub-menu__title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #000000;
|
|
|
+ background-color: transparent;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 12px 16px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-aside .el-menu-item:hover,
|
|
|
+.layout-aside .el-sub-menu__title:hover {
|
|
|
+ background-color: rgba(16, 146, 216, 0.1);
|
|
|
+ color: #1092d8;
|
|
|
+}
|
|
|
+
|
|
|
+.layout-aside .el-menu-item.is-active,
|
|
|
+.layout-aside .el-sub-menu__title.is-active {
|
|
|
+ background: linear-gradient(to right, #1092d8, #02c3ad);
|
|
|
+ color: #000000 !important;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-weight: 600;
|
|
|
+ box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25);
|
|
|
+}
|
|
|
+
|
|
|
+.layout-content-wrapper {
|
|
|
+ flex: 1;
|
|
|
overflow: hidden;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
+ position: relative;
|
|
|
}
|
|
|
|
|
|
-.el-main {
|
|
|
- overflow-y: auto;
|
|
|
+/* 排除背景图页面的内容区域 */
|
|
|
+.excluded-bg-content {
|
|
|
+ background-color: #ffffff !important;
|
|
|
}
|
|
|
|
|
|
-.el-scrollbar {
|
|
|
- height: 100%;
|
|
|
+/* 可滑动内容区域 */
|
|
|
+.scrollable-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow: auto;
|
|
|
+ padding: 0 20px;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
-.logo-container {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
+/* 强制重置 el-tabs header 高度/边距/背景/阴影,避免背景层穿透错位 */
|
|
|
+.el-tabs__header.is-top {
|
|
|
+ height: 48px !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ border: none !important;
|
|
|
+ background: transparent !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+ z-index: 0 !important;
|
|
|
}
|
|
|
|
|
|
-.logo {
|
|
|
- height: 40px;
|
|
|
- width: auto;
|
|
|
+/* 全屏页面特殊处理 */
|
|
|
+.layout-wrapper.full-screen .layout-main-container {
|
|
|
+ height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+.scrollable-content {
|
|
|
+ flex: 1;
|
|
|
+ overflow: auto;
|
|
|
+ padding: 0 20px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background-size: cover;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: center;
|
|
|
+ transition: background-image 0.3s ease-in-out;
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|