Browse Source

确保功能正常使用,页面样式与最新网页一致

yangtaodemon 2 weeks ago
parent
commit
8e9ff7d732

+ 0 - 6
components.d.ts

@@ -34,10 +34,7 @@ declare module 'vue' {
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
-    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
-    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElContainer: typeof import('element-plus/es')['ElContainer']
-    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -46,9 +43,7 @@ declare module 'vue' {
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElIcon: typeof import('element-plus/es')['ElIcon']
-    ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
@@ -61,7 +56,6 @@ declare module 'vue' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
-    ElTag: typeof import('element-plus/es')['ElTag']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     FluxcdStatictics: typeof import('./src/components/soilcdStatistics/fluxcdStatictics.vue')['default']

+ 1 - 1
package-lock.json

@@ -9141,7 +9141,7 @@
     },
     "node_modules/leaflet": {
       "version": "1.9.4",
-      "resolved": "https://registry.npmmirror.com/leaflet/-/leaflet-1.9.4.tgz",
+      "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
       "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
       "license": "BSD-2-Clause"
     },

+ 102 - 112
src/components/layout/AppAside.vue

@@ -1,143 +1,136 @@
 <template>
-  <el-scrollbar v-if="showTabs">
+  <el-scrollbar v-show="showTabs && activeTab !== 'selectCityAndCounty'">
     <el-menu
-      ref="menuRef"
       :collapse="isCollapse"
+      router
       unique-opened
-      :default-active="route.path"
+      :default-active="currentActiveIndex"
       :default-openeds="openKeys"
-      @select="handleMenuSelect"
       class="professional-menu"
+      @select="onMenuSelect"
     >
-      <template v-if="menuList.length">
-        <template v-for="item in menuList" :key="item.index">
-          <el-sub-menu v-if="item.children?.length" :index="item.index">
-            <template #title>
-              <el-icon v-if="item.icon">
-                <component :is="item.icon" />
-              </el-icon>
-              <span>{{ item.label }}</span>
-            </template>
-            <el-menu-item
-              v-for="child in item.children"
-              :key="child.index"
-              :index="child.index"
-            >
-              <el-icon v-if="child.icon">
-                <component :is="child.icon" />
-              </el-icon>
-              <span>{{ child.label }}</span>
-            </el-menu-item>
-          </el-sub-menu>
-          <el-menu-item v-else :index="item.index">
-            <el-icon v-if="item.icon">
-              <component :is="item.icon" />
-            </el-icon>
-            <span>{{ item.label }}</span>
+      <template v-for="item in filteredMenuItems" :key="item.index">
+        <el-sub-menu
+          v-if="item.children?.length"
+          :index="item.index"
+        >
+          <template #title>
+            <el-icon v-if="item.icon"><component :is="item.icon" /></el-icon>
+            <span>{{ $t(item.label) }}</span>
+          </template>
+
+          <el-menu-item
+            v-for="child in item.children"
+            :key="child.index"
+            :index="child.index"
+          >
+            <el-icon v-if="child.icon"><component :is="child.icon" /></el-icon>
+            <span>{{ $t(child.label) }}</span>
           </el-menu-item>
-        </template>
-      </template>
-      <template v-else>
-        <el-menu-item disabled>未知的 Tab:{{ activeTab }}</el-menu-item>
+        </el-sub-menu>
+
+        <el-menu-item
+          v-else
+          :index="item.index"
+        >
+          <el-icon v-if="item.icon"><component :is="item.icon" /></el-icon>
+          <span>{{ $t(item.label) }}</span>
+        </el-menu-item>
       </template>
     </el-menu>
   </el-scrollbar>
 </template>
 
 <script setup lang="ts">
-import {
-  inject,
-  reactive,
-  watch,
-  toRefs,
-  computed,
-  ref,
-  nextTick,
-} from "vue";
-import { useRouter, useRoute } from "vue-router";
-import { ElMessage } from "element-plus";
-import { useTokenStore } from "@/stores/mytoken";
-
-// 导入菜单配置(确保 admin 使用的是 menuItems2)
-import { tabMenuMap as userMenuMap } from "./menuItems";
-import { tabMenuMap as adminMenuMap } from "./menuItems2";
-
-const props = defineProps({
-  activeTab: { type: String, required: true },
-  showTabs: { type: Boolean, default: true },
-});
-
-const { activeTab } = toRefs(props);
-const isCollapse = inject("isCollapse", false);
+import { computed, inject, reactive, watch, nextTick } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { tabMenuMap, MenuItem } from './menuItems';
+
+interface Props {
+  activeTab: string;
+  showTabs?: boolean;
+}
+const props = defineProps<Props>();
 
 const router = useRouter();
 const route = useRoute();
-const tokenStore = useTokenStore();
-
-const currentMenuMap = computed(() =>
-  tokenStore.userType === "admin" ? adminMenuMap : userMenuMap
-);
-const menuList = computed(() => currentMenuMap.value[activeTab.value] || []);
+const isCollapse = inject<boolean>('isCollapse', false);
 
+const filteredMenuItems = computed<MenuItem[]>(() => tabMenuMap[props.activeTab] || []);
+const currentActiveIndex = computed(() => route.path);
 const openKeys = reactive<string[]>([]);
 
-// ✅ 手动声明 el-menu 实例缺失的 open/close 方法
-interface ElMenuExpose {
-  open: (index: string) => void;
-  close: (index: string) => void;
+/** 安全跳转,保证不会被组件报错阻断 */
+function safeNavigate(path: string) {
+  if (!path) return;
+  try {
+    router.replace(path).catch(err => {
+      console.warn('Navigation error:', err?.message ?? err);
+    });
+  } catch (e) {
+    console.warn('Unexpected navigation error:', e);
+  }
 }
-const menuRef = ref<ElMenuExpose | null>(null);
 
-/**
- * 根据当前路由路径,查找应展开的父菜单 index
- */
-function findOpenKeys(menuItems: any[], currentPath: string): string[] {
+/** 查找父菜单展开 */
+function findOpenKeys(menuItems: MenuItem[], path: string): string[] {
   for (const item of menuItems) {
-    if (item.children?.some((child: any) => child.index === currentPath)) {
-      return [item.index];
-    }
+    if (item.children?.some(child => child.index === path)) return [item.index];
   }
   return [];
 }
 
-/**
- * 菜单选择处理:手动跳转 + 展开父菜单
- */
-const handleMenuSelect = (index: string) => {
-  if (index !== route.path) {
-    router.push(index).catch((err) => {
-      if (err.name !== "NavigationDuplicated") {
-        ElMessage.error("导航失败");
-        console.error("[Menu] 跳转失败:", err);
+/** 处理菜单 select 事件 */
+function onMenuSelect(index: string) {
+  // 找对应菜单项
+  let parent: MenuItem | undefined;
+  let target: MenuItem | undefined;
+
+  for (const item of filteredMenuItems.value) {
+    if (item.children?.length) {
+      const child = item.children.find(c => c.index === index);
+      if (child) {
+        parent = item;
+        target = child;
+        break;
       }
-    });
+    } else if (item.index === index) {
+      target = item;
+      break;
+    }
   }
 
-  const keys = findOpenKeys(menuList.value, index);
-  openKeys.splice(0, openKeys.length, ...keys);
-  nextTick(() => {
-    keys.forEach((key) => {
-      menuRef.value?.open(key);
-    });
-  });
-};
+  if (!target) return;
+
+  // 更新父菜单展开
+  if (parent?.index) openKeys.splice(0, openKeys.length, parent.index);
 
-/**
- * ✅ 关键修复:同时监听 route.path 和 menuList 变化
- * 确保无论路由变还是菜单列表变,都能正确展开
- */
+  // 安全跳转
+  safeNavigate(target.index);
+}
+
+/** 路由变化自动同步展开 */
 watch(
-  () => [route.path, menuList.value],
-  () => {
-    const keys = findOpenKeys(menuList.value, route.path);
+  () => route.path,
+  (newPath) => {
+    const keys = findOpenKeys(filteredMenuItems.value, newPath);
     openKeys.splice(0, openKeys.length, ...keys);
-    nextTick(() => {
-      keys.forEach((key) => {
-        menuRef.value?.open(key);
-      });
-    });
   },
-  { immediate: true, deep: true }
+  { immediate: true, flush: 'post' }
+);
+
+/** Tab 切换时自动跳转第一个菜单 */
+watch(
+  () => props.activeTab,
+  (newTab) => {
+    const items = tabMenuMap[newTab] || [];
+    if (!items.length) return;
+
+    const firstItem = items[0];
+    const path = firstItem.children?.[0]?.index || firstItem.index;
+    if (path && route.path !== path) nextTick(() => safeNavigate(path));
+  },
+  { immediate: true }
 );
 </script>
 
@@ -201,11 +194,8 @@ watch(
   box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25);
 }
 :deep(.el-sub-menu__icon-arrow) {
-  position: absolute;
-  right: 16px;
-  top: 50%;
-  transform: translateY(-50%);
-  margin-right: 0 !important;
+  margin-left: auto;
+  margin-right: -160px;
   transition: transform 0.3s ease;
 }
-</style>
+</style>

+ 20 - 19
src/components/layout/AppAsideForTab2.vue

@@ -132,13 +132,15 @@ watch(
   border-right: none !important;
   padding-top: 12px;
 }
+
 :deep(.el-scrollbar__wrap),
 :deep(.el-scrollbar__view) {
-  background: transparent;
+  background: transparent !important;
   height: 100%;
   display: flex;
   flex-direction: column;
 }
+
 .professional-menu {
   background: transparent;
   border-right: none;
@@ -148,6 +150,7 @@ watch(
   flex-direction: column;
   min-height: 100%;
 }
+
 :deep(.el-menu-item),
 :deep(.el-sub-menu__title) {
   margin-left: 0 !important;
@@ -157,37 +160,35 @@ watch(
   padding-left: 40px !important;
   padding-right: 20px !important;
 }
+
 :deep(.el-sub-menu .el-menu-item) {
   background-color: rgba(252, 234, 183, 0.3) !important;
 }
-:deep(.el-sub-menu__title) {
-  font-size: 18px;
-  font-weight: 500;
-  color: #000000;
-  border-radius: 6px;
-  padding: 12px 16px !important;
-  transition: all 0.2s ease;
-  display: flex;
-  align-items: center;
-  padding-left: 20px !important;
-}
+
 :deep(.el-menu-item:hover),
 :deep(.el-sub-menu__title:hover) {
-  background-color: rgba(16, 146, 216, 0.1);
-  color: #1092d8;
+  background-color: rgba(16, 146, 216, 0.1) !important;
+  color: #1092d8 !important;
 }
+
 :deep(.el-menu-item.is-active),
 :deep(.el-sub-menu__title.is-active) {
-  background: linear-gradient(to right, #1092d8, #02c3ad);
+  background: linear-gradient(to right, #1092d8, #02c3ad) !important;
   color: #ffffff !important;
-  border-radius: 8px;
-  font-weight: 600;
-  box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25);
+  border-radius: 8px !important;
+  font-weight: 600 !important;
+  box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25) !important;
+}
+
+:deep(.el-sub-menu__title) {
+  display: flex;
+  align-items: center;
+  padding-left: 20px !important;
 }
+
 :deep(.el-sub-menu__icon-arrow) {
   margin-left: auto;
   margin-right: -160px;
   transition: transform 0.3s ease;
 }
 </style>
-

+ 17 - 28
src/components/layout/AppHeader.vue

@@ -12,13 +12,12 @@
           <!-- 用户头像 -->
           <el-avatar
             :size="40"
-            src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
+            :src="'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'"
           />
         </span>
         <template #dropdown>
           <el-dropdown-menu>
             <!-- 显示用户名 -->
-            <el-dropdown-item disabled>用户名:{{ userName }}</el-dropdown-item>
             <!-- 退出登录按钮 -->
             <el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
           </el-dropdown-menu>
@@ -29,48 +28,38 @@
 </template>
 
 <script setup lang="ts">
-import { computed } from "vue";
-import { ElMessageBox, ElMessage } from "element-plus";
+import { reactive } from "vue";
+import { ElMessage } from "element-plus";
 import { useTokenStore } from "@/stores/mytoken";
 import { logout } from "@/API/users";
 import router from "@/router";
 
-// 获取 store 实例
+// 获取 tokenStore 实例
 const tokenStore = useTokenStore();
 
-// 响应式用户名
-const userName = computed(() => tokenStore.userInfo?.name || "未登录");
-
-// 日志记录
+// 日志记录函数
 function logAction(action: string) {
   console.log(`[AppHeader] ${action}`);
 }
 
-// 错误提示
+// 错误提示函数
 function showError(message: string) {
   ElMessage.error(message);
 }
 
-// 退出登录逻辑
+// 处理退出逻辑
 const handleLogout = async () => {
   try {
     logAction("Logout initiated");
-    await ElMessageBox.confirm("确定要退出登录吗?", "提示", {
-      confirmButtonText: "确定",
-      cancelButtonText: "取消",
-      type: "warning",
-    });
     await logout(); // 调用退出接口
-    tokenStore.clearToken(); // 清除 store
+    tokenStore.clearToken(); // 清除本地存储的 token
     ElMessage.success("退出成功");
     logAction("Logout successful");
-    router.push("/login"); // 跳转登录页
+    router.push("/login"); // 跳转到登录页面
   } catch (error) {
     logAction("Logout failed");
     if (error instanceof Error) {
       showError(`退出失败:${error.message}`);
-    } else {
-      ElMessage.info("已取消退出");
     }
   }
 };
@@ -82,10 +71,10 @@ const handleLogout = async () => {
   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;
+  background-color: #ffffff; /* 白色背景 */
+  border-bottom: 1px solid #e5e7eb; /* 更细的分隔线 */
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 添加柔和阴影 */
+  border-radius: 10px; /* 添加圆角 */
 }
 
 .header-content {
@@ -96,7 +85,7 @@ const handleLogout = async () => {
 .left-content {
   font-size: 1.5rem;
   font-weight: bold;
-  color: #1f2937;
+  color: #1f2937; /* 深灰色字体 */
 }
 
 .el-dropdown-link {
@@ -107,7 +96,7 @@ const handleLogout = async () => {
 
 .el-avatar {
   margin-left: 10px;
-  border: 2px solid #e5e7eb;
-  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  border: 2px solid #e5e7eb; /* 添加边框 */
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影 */
 }
-</style>
+</style>

+ 387 - 249
src/components/layout/AppLayout.vue

@@ -7,22 +7,26 @@
     <!-- 背景层 -->
     <div v-if="isSpecialBg" class="background-layer"></div>
 
-    <!-- Header -->
+   <!-- Header -->
     <el-header
       class="layout-header"
       v-if="!isFullScreen"
       :class="{ 'transparent-header': isSpecialBg }"
     >
       <div class="logo-title-row">
-        <img src="@/assets/logo.png" alt="Logo" class="logo" />
-        <div class="title-and-user">
+        <!-- 左侧:Logo和标题 -->
+        <div class="left-section">
+          <img src="@/assets/logo.png" alt="Logo" class="logo" />
           <span class="project-name" :class="{ 'light-text': isSpecialBg }">
             土壤酸化智能预测专家系统
           </span>
+        </div>
 
-          <div class="user-info-row" v-if="!isSelectCity">
+        <!-- 右侧:用户信息 -->
+        <div class="right-section" v-if="!isSelectCity">
+          <div class="user-info-row">
             <span class="welcome-text" :class="{ 'light-text': isSpecialBg }">
-              欢迎 {{ userInfo.type === "admin" ? "管理员" : "用户" }} 登录成功
+              欢迎 {{ userInfo.name }} 登录成功
             </span>
             <el-dropdown>
               <span class="el-dropdown-link">
@@ -34,12 +38,12 @@
               </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-item disabled
+                    >用户名:{{ userInfo.name }}</el-dropdown-item
+                  >
+                  <el-dropdown-item divided @click="handleLogout"
+                    >退出登录</el-dropdown-item
+                  >
                 </el-dropdown-menu>
               </template>
             </el-dropdown>
@@ -55,7 +59,7 @@
         v-model="activeName"
         class="demo-tabs"
         :style="tabStyle"
-        @tab-click="(tab: { name: string; }) => handleClick(tab.name)"
+        @tab-click="handleClick"
       >
         <el-tab-pane v-for="tab in tabs" :key="tab.name" :name="tab.name">
           <template #label>
@@ -64,7 +68,7 @@
           </template>
         </el-tab-pane>
       </el-tabs>
-      <div v-else class="single-tab" @click="handleClick(tabs[0].name)">
+      <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>
@@ -73,7 +77,11 @@
     <!-- Main Content -->
     <el-container class="layout-main-container">
       <el-aside v-if="showAside && showTabs" class="layout-aside">
-        <AppAside :activeTab="activeName" :showTabs="showTabs" />
+        <component
+          :is="AsideComponent"
+          :activeTab="activeName"
+          :showTabs="showTabs"
+        />
       </el-aside>
 
       <el-main class="layout-content-wrapper" :style="mainStyle">
@@ -87,82 +95,39 @@
         </div>
       </el-main>
     </el-container>
+    
+    <!-- 全局消息提示组件 -->
+    <div v-if="showGlobalMessage" class="global-message">
+      <div class="message-content" :class="messageType">
+        {{ globalMessage }}
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch } from "vue";
+import { ref, reactive, computed, watch, defineAsyncComponent, onMounted } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { useTokenStore } from "@/stores/mytoken";
-import { ElMessageBox, ElMessage } from "element-plus";
+import { ElMessageBox, ElMessage } from "element-plus"; // 确保导入ElMessage
 import { logout } from "@/API/users";
-import AppAside from "./AppAside.vue";
+import { useI18n } from "vue-i18n";
 
+const { t } = useI18n();
 const router = useRouter();
 const route = useRoute();
 const tokenStore = useTokenStore();
+const currentBgImage = ref("");
 
-const userInfo = computed(() => ({
-  name: tokenStore.userName,
-  type: tokenStore.userType,
-}));
+// ============ 新增状态 ============
+const showGlobalMessage = ref(false);
+const globalMessage = ref("");
+const messageType = ref("success"); // 消息类型: success/error
 
-const activeName = ref("");
-
-const bgRouteMap: Record<string, string> = {
- // === 普通用户路由 ===
-  "/SoilPro": "background.jpg",
-  "/Overview": "background.jpg",
-  "/ResearchFindings": "background.jpg",
-  "/Unit": "background.jpg",
-  "/acidmodelmap": "background.jpg",
-  "/Calculation": "background.jpg",
-  "/SoilAcidReductionIterativeEvolution": "background.jpg",
-  "/AcidNeutralizationModel": "background.jpg",
-  "/SoilAcidificationIterativeEvolution": "background.jpg",
-  "/DetectionStatistics": "background.jpg",
-  "/FarmlandPollutionStatistics": "background.jpg",
-  "/LandClutivatesStatistics": "background.jpg",
-  "/SoilacidificationStatistics": "background.jpg",
-
-  // === 管理员路由 ===
-  "/soilAcidReductionData": "background.jpg",
-  "/soilAcidificationData": "background.jpg",
-  "/crossSectionSampleData": "background.jpg",
-  "/irrigationWaterInputFluxData": "background.jpg",
-  "/heavyMetalEnterpriseData": "background.jpg",
-  "/atmosphericSampleData": "background.jpg",
-  "/IntroductionUpdate": "background.jpg",
-  "/ModelSelection": "background.jpg",
-  "/thres": "background.jpg",
-  "/ModelTrain": "background.jpg",
-  "/UserManagement": "background.jpg",
-};
+currentBgImage.value = `url(${new URL('../../assets/bg/background.jpg', import.meta.url).href})`;
 
-const isSpecialBg = computed(() =>
-  Object.keys(bgRouteMap).includes(route.path)
-);
-
-function getBgImageUrl(): string {
-  if (bgRouteMap[route.path]) {
-    try {
-      return `url(${new URL("@/assets/bg/background.jpg", import.meta.url).href})`;
-    } catch (error) {
-      console.error("加载背景图失败:", error);
-      return "";
-    }
-  }
-  return "";
-}
-
-const currentBgImage = ref("");
-watch(
-  () => route.path,
-  () => {
-    currentBgImage.value = getBgImageUrl();
-  },
-  { immediate: true }
-);
+// 是否特殊背景(始终返回true → 所有页面应用特殊背景样式)
+const isSpecialBg = computed(() => true);
 
 const backgroundStyle = computed(() => ({
   backgroundImage: currentBgImage.value,
@@ -175,165 +140,220 @@ const backgroundStyle = computed(() => ({
 const isFullScreen = computed(() => route.meta.fullScreen === true);
 const isSelectCity = computed(() => route.path === "/select-city");
 
-const tabs = computed(() => {
-  if (userInfo.value.type === "admin") {
-    return [
-      {
-        name: "dataManagement",
-        label: "数据管理",
-        icon: "el-icon-folder",
-        routes: [
-          "/soilAcidReductionData",
-          "/soilAcidificationData",
-          "/crossSectionSampleData",
-          "/irrigationWaterInputFluxData",
-          "/heavyMetalEnterpriseData",
-          "/atmosphericSampleData",
-        ],
-      },
-      {
-        name: "infoManagement",
-        label: "信息管理",
-        icon: "el-icon-document",
-        routes: ["/IntroductionUpdate"],
-      },
-      {
-        name: "modelManagement",
-        label: "模型管理及配置",
-        icon: "el-icon-cpu",
-        routes: ["/ModelSelection", "/thres", "/ModelTrain"],
-      },
-      {
-        name: "userManagement",
-        label: "用户管理",
-        icon: "el-icon-user",
-        routes: ["/UserManagement"],
-      },
-    ];
-  } else {
-    return [
-      {
-        name: "introduction",
-        label: "软件简介",
-        icon: "el-icon-info-filled",
-        routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"],
-      },
-      {
-        name: "acidmodelmap",
-        label: "土壤酸化地块级计算",
-        icon: "el-icon-data-analysis",
-        routes: ["/acidmodelmap"],
-      },
-      {
-        name: "Calculation",
-        label: "土壤反酸模型",
-        icon: "el-icon-data-analysis",
-        routes: ["/Calculation", "/SoilAcidReductionIterativeEvolution"],
-      },
-      {
-        name: "AcidNeutralizationModel",
-        label: "土壤降酸模型",
-        icon: "el-icon-data-analysis",
-        routes: ["/AcidNeutralizationModel", "/SoilAcidificationIterativeEvolution"],
-      },
-      {
-        name: "dataStatistics",
-        label: "数据统计",
-        icon: "el-icon-pie-chart",
-        routes: [
-          "/DetectionStatistics",
-          "/FarmlandPollutionStatistics",
-          "/LandClutivatesStatistics",
-          "/SoilacidificationStatistics",
-        ],
-      },
-    ];
-  }
+// 当前用户信息
+const userInfo = reactive({
+  name: tokenStore.userName,
+  type: tokenStore.userType,
 });
 
-// 路由 ↔ Tab 双向同步
-watch(
-  () => route.path,
-  (currentPath) => {
-    const matchedTab = tabs.value.find(tab =>
-      tab.routes.includes(currentPath)
-    );
-    if (matchedTab && activeName.value !== matchedTab.name) {
-      activeName.value = matchedTab.name;
-    }
-  },
-  { immediate: true }
-);
+const tabs = computed(() => {
 
-// 初始化 activeName
-watch(
-  () => userInfo.value.type,
-  (type) => {
-    if (!activeName.value) {
-      activeName.value = type === "admin" ? "dataManagement" : "introduction";
-    }
-  },
-  { immediate: true }
+ if (userInfo.type === "admin") {
+
+  return [
+
+   {
+
+    name: "dataManagement",
+
+    label: "数据管理",
+
+    icon: "el-icon-folder",
+
+    routes: [
+
+     "/soilAcidReductionData",
+
+     "/soilAcidificationData",
+
+     "/crossSectionSampleData",
+
+     "/irrigationWaterInputFluxData",
+
+     "/heavyMetalEnterpriseData",
+
+     "/atmosphericSampleData",
+
+    ],
+
+   },
+
+   {
+
+    name: "infoManagement",
+
+    label: "信息管理",
+
+    icon: "el-icon-document",
+
+    routes: ["/IntroductionUpdate"],
+
+   },
+
+   {
+
+    name: "modelManagement",
+
+    label: "模型管理及配置",
+
+    icon: "el-icon-cpu",
+
+    routes: ["/ModelSelection", "/thres", "/ModelTrain"],
+
+   },
+
+   {
+
+    name: "userManagement",
+
+    label: "用户管理",
+
+    icon: "el-icon-user",
+
+    routes: ["/UserManagement"],
+
+   },
+
+  ];
+
+ } else {
+
+  return [
+
+   {
+
+    name: "introduction",
+
+    label: "软件简介",
+
+    icon: "el-icon-info-filled",
+
+    routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"],
+
+   },
+
+   {
+   name: "acidmodelmap",
+
+    label: "土壤酸化地块级计算",
+
+    icon: "el-icon-data-analysis",
+
+    routes: ["/acidmodelmap"],
+
+   },
+
+   {
+
+    name: "Calculation",
+
+    label: "土壤反酸模型",
+
+    icon: "el-icon-data-analysis",
+
+    routes: ["/Calculation", "/SoilAcidReductionIterativeEvolution"],
+
+   },
+
+   {
+
+    name: "AcidNeutralizationModel",
+
+    label: "土壤降酸模型",
+
+    icon: "el-icon-data-analysis",
+
+    routes: ["/AcidNeutralizationModel", "/SoilAcidificationIterativeEvolution"],
+
+   },
+
+   {
+
+    name: "dataStatistics",
+
+    label: "数据统计",
+
+    icon: "el-icon-pie-chart",
+
+    routes: [
+
+     "/DetectionStatistics",
+
+     "/FarmlandPollutionStatistics",
+
+     "/LandClutivatesStatistics",
+
+     "/SoilacidificationStatistics",
+
+   ],
+
+   },
+
+  ];
+
+ }
+
+});
+
+const activeName = ref(tabs.value[0]?.name || "");
+const showTabs = computed(() => tabs.value.length > 1);
+const tabStyle = computed(() =>
+  tabs.value.length === 1 ? { width: "100%", justifyContent: "center" } : {}
 );
+let hasNavigated = false;
 
-// 自动跳转到 Tab 第一个子路由(如果不在该 Tab 内)
 watch(
   () => activeName.value,
-  (newTabName) => {
-    if (!newTabName) return;
-    const currentTab = tabs.value.find(tab => tab.name === newTabName);
-    if (!currentTab) return;
-    if (currentTab.routes.includes(route.path)) return;
-
-    const firstRoute = currentTab.routes[0];
-    if (firstRoute && firstRoute !== route.path) {
-      router.push(firstRoute).catch((err) => {
-        if (err.name !== "NavigationDuplicated") {
-          console.warn("[Tab Auto Redirect] 跳转失败:", err);
-        }
-      });
+  (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)
+      router.push({ path: targetPath });
   },
   { immediate: true }
 );
 
-const showTabs = computed(() => tabs.value.length > 1);
-const tabStyle = computed(() =>
-  tabs.value.length === 1 ? { width: "100%", justifyContent: "center" } : {}
-);
-
-// ✅ 修复后的 handleClick:只接收 tabName
-const handleClick = (tabName: string) => {
-  const tab = tabs.value.find(t => t.name === tabName);
-  if (!tab) return;
-
-  if (tab.routes.includes(route.path)) {
-    return;
-  }
-
-  const targetRoute = tab.routes[0];
-  if (targetRoute && targetRoute !== route.path) {
-    router.push(targetRoute).catch((err) => {
-      if (err.name !== "NavigationDuplicated") {
-        console.warn("[Tab Click] 跳转失败:", err);
-      }
-    });
-  }
+// 点击 tab
+const activeAsideTab = ref(activeName.value || "");
+const handleClick = (tab: any, event?: Event) => {
+  activeAsideTab.value = tab.name || tab.props?.name;
 };
 
+// 动态加载侧边栏
+const AsideComponent = computed(() => {
+  return [
+    "dataManagement",
+    "infoManagement",
+    "modelManagement",
+    "userManagement",
+  ].includes(activeName.value)
+    ? defineAsyncComponent(() => import("./AppAsideForTab2.vue"))
+    : defineAsyncComponent(() => import("./AppAside.vue"));
+});
+
+// 是否显示侧边栏
 const showAside = computed(
   () =>
-    !isFullScreen.value &&
-    !["cropRiskAssessment"].includes(activeName.value)
+    !isFullScreen.value && !["cropRiskAssessment"].includes(activeName.value)
 );
 
-const mainStyle = computed(() => ({
-  padding: ["mapView", "infoManagement"].includes(activeName.value)
-    ? "0"
-    : "20px",
-  overflow: "hidden",
-}));
+// ============ 显示全局消息 ============
+const showMessage = (message: string, type: "success" | "error" = "success") => {
+  globalMessage.value = message;
+  messageType.value = type;
+  showGlobalMessage.value = true;
+  
+  // 3秒后自动隐藏消息
+  setTimeout(() => {
+    showGlobalMessage.value = false;
+  }, 3000);
+};
 
+// 登出逻辑
 const handleLogout = async () => {
   try {
     await ElMessageBox.confirm("确定要退出登录吗?", "提示", {
@@ -343,112 +363,149 @@ const handleLogout = async () => {
     });
     await logout();
     tokenStore.clearToken();
-    ElMessage.success("退出成功");
-    router.push("/login");
-  } catch {
-    ElMessage.info("已取消退出");
+    
+    // 使用ElMessage而不是全局消息提示
+    ElMessage.success("退出登录成功");
+    
+    // 延迟跳转,让用户看到提示消息
+    setTimeout(() => {
+      router.push("/login");
+    }, 1000);
+  } catch (error) {
+    // 区分用户取消操作和真正的错误
+    if (error === 'cancel' || error?.toString().includes('cancel')) {
+      console.log("用户取消退出登录");
+    } else {
+      ElMessage.error("退出失败,请重试");
+      console.error("退出登录错误:", error);
+    }
   }
 };
+
+// 内容区样式
+const mainStyle = computed(() => ({
+  padding: ["mapView", "infoManagement"].includes(activeName.value)
+    ? "0"
+    : "20px",
+  overflow: "hidden",
+}));
 </script>
 
 <style>
 /* 隐藏所有滚动条 */
 *::-webkit-scrollbar {
   display: none;
+  /* Chrome, Safari, Opera */
 }
 
 * {
   -ms-overflow-style: none;
+  /* IE and Edge */
   scrollbar-width: none;
+  /* Firefox */
 }
 
+/* 整体布局容器 */
 .layout-wrapper {
   display: flex;
   flex-direction: column;
   height: 100vh;
   overflow: hidden;
-  position: relative;
-  background-color: #f5f7fa;
+  position: relative; /* 创建层叠上下文 */
+  background-color: #f5f7fa; /* 默认背景色 */
+  /* 确保 layout-wrapper 自身的 z-index 不干扰子元素,或根据需要设置 */
+  /* z-index: 0; */ /* 通常不需要显式设置,除非有特殊需求 */
 }
 
+/* 背景层 - 关键修改 */
 .background-layer {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
-  z-index: 0;
+  /* 将背景层的 z-index 设为一个较低的负值或非常小的正值,确保它在最底层 */
+  z-index: -1; /* 修改点:使用负值确保其在最底层,避免任何可能的遮挡 */
   background-size: cover;
   background-position: center;
   background-repeat: no-repeat;
   filter: brightness(0.8);
 }
 
+/* 全屏页面特殊处理 */
 .layout-wrapper.full-screen {
   background: none;
   min-height: 100vh;
 }
 
+/* 特殊背景页面的Header样式 */
 .transparent-header {
   background: transparent !important;
-  backdrop-filter: blur(2px);
+  backdrop-filter: blur(2px); /* 添加轻微模糊效果增强可读性 */
 }
 
+/* 特殊背景页面的文字颜色 */
 .light-text {
-  color: #ffffff !important;
-  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
+  color: #ffffff !important; /* 白色文字 */
+  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); /* 文字阴影增强可读性 */
 }
 
+/* 特殊背景页面的头像边框 */
 .light-avatar-border {
-  border: 2px solid #ffffff !important;
+  border: 2px solid #ffffff !important; /* 白色边框 */
 }
 
+/* 内容区域在特殊背景页面透明 */
 .transparent-scroll {
   background-color: transparent !important;
 }
 
+/* Header 样式 */
 .layout-header {
-  height: 150px;
+  height: 80px;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  color: #333;
+  color: #333; /* 深色文字 */
   flex-shrink: 0;
+  /* 确保在背景层上方 */
   position: relative;
-  z-index: 2;
-  background-color: white;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
+  background-color: white; /* 默认背景色 */
 }
 
+/* 修改Header布局样式 */
 .logo-title-row {
   display: flex;
   align-items: center;
-  gap: 24px;
+  justify-content: space-between; /* 左右两侧分布 */
   width: 100%;
+  gap: 24px;
 }
 
 .title-and-user {
   display: flex;
-  flex-direction: column;
+  flex-direction: row;
   flex: 1;
 }
 
+/* 用户信息区域样式调整 */
 .user-info-row {
   display: flex;
   align-items: center;
-  justify-content: flex-end;
-  gap: 24px;
+  gap: 16px;
   color: #333;
-  padding-top: 1px;
-  position: static;
-  z-index: 1;
 }
 
 .welcome-text {
   font-size: 28px;
   font-weight: 500;
-  color: #ffffff !important;
+  color: #ffffff;
+  white-space: nowrap; /* 防止文字换行 */
 }
 
+/* Tab 区域 - 不透明 */
 .tabs-row {
   height: 48px;
   display: flex;
@@ -458,10 +515,13 @@ const handleLogout = async () => {
   background: linear-gradient(to right, #1092d8, #02c3ad);
   border-bottom: none !important;
   flex-shrink: 0;
+  /* 确保在背景层上方 */
   position: relative;
-  z-index: 2;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
 }
 
+/* el-tabs 外层容器 */
 .demo-tabs {
   height: 48px !important;
   display: flex;
@@ -472,6 +532,7 @@ const handleLogout = async () => {
   border-bottom: none !important;
 }
 
+/* 清除滑块条和底部线条 */
 .el-tabs__nav-wrap::after,
 .el-tabs__active-bar {
   display: none !important;
@@ -484,6 +545,7 @@ const handleLogout = async () => {
   margin: 0 !important;
 }
 
+/* Tabs 单项样式 */
 .el-tabs__item {
   height: 48px !important;
   line-height: 48px !important;
@@ -497,28 +559,36 @@ const handleLogout = async () => {
   transition: all 0.2s ease-in-out;
   background-color: transparent;
   position: relative;
-  z-index: 1;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
 }
 
+/* 激活 Tab */
 .el-tabs__item.is-active {
   background-color: #2a53ba;
   color: #ffffff;
   font-weight: 700;
   box-shadow: 0 4px 16px rgba(26, 188, 156, 0.4);
-  z-index: 2;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 11; /* 激活项可以更高一点 */
 }
 
+/* 鼠标悬停 */
 .el-tabs__item:hover {
   background-color: #455a64;
   color: #ffffff;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 11; /* 悬停时也可以更高 */
 }
 
+/* 图标样式 */
 .tab-icon {
   font-size: 24px;
   margin-right: 4px;
   color: inherit;
 }
 
+/* 文字样式 */
 .tab-label-text {
   font-size: 20px;
   color: inherit;
@@ -531,10 +601,45 @@ const handleLogout = async () => {
 }
 
 .project-name {
-  font-size: 48px;
+  font-size: 32px; /* 适当调整字体大小 */
   font-weight: bold;
-  margin-top: 30px;
   color: #333;
+  white-space: nowrap; /* 防止标题换行 */
+}
+
+/* 响应式设计:在小屏幕上调整布局 */
+@media (max-width: 1200px) {
+  .project-name {
+    font-size: 20px;
+  }
+  
+  .welcome-text {
+    font-size: 14px;
+  }
+}
+
+@media (max-width: 768px) {
+  .logo-title-row {
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+  
+  .left-section {
+    order: 1;
+    width: 100%;
+    justify-content: center;
+  }
+  
+  .right-section {
+    order: 2;
+    width: 100%;
+    justify-content: center;
+  }
+  
+  .project-name {
+    font-size: 18px;
+    text-align: center;
+  }
 }
 
 .layout-main-container {
@@ -542,12 +647,15 @@ const handleLogout = async () => {
   display: flex;
   overflow: hidden;
   min-height: 0;
+  /* 确保在背景层上方 */
   position: relative;
-  z-index: 1;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
 }
 
+/* 侧边栏 - 白色背景 */
 .layout-aside {
-  width: 360px;
+  width: 280px;
   background: linear-gradient(to bottom, #b7f1fc, #fff8f0);
   border-right: 1px solid;
   overflow-y: auto;
@@ -555,9 +663,11 @@ const handleLogout = async () => {
   padding-top: 8px;
   height: 100%;
   position: relative;
-  z-index: 2;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
 }
 
+/* 隐藏侧边栏滚动条 */
 .layout-aside::-webkit-scrollbar {
   display: none;
 }
@@ -586,6 +696,8 @@ const handleLogout = async () => {
   border-radius: 8px;
   font-weight: 600;
   box-shadow: 0 2px 8px rgba(16, 146, 216, 0.25);
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 11; /* 激活项可以更高一点 */
 }
 
 .layout-content-wrapper {
@@ -594,8 +706,11 @@ const handleLogout = async () => {
   display: flex;
   flex-direction: column;
   position: relative;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
 }
 
+/* 强制重置 el-tabs header 高度/边距/背景/阴影,避免背景层穿透错位 */
 .el-tabs__header.is-top {
   height: 48px !important;
   margin: 0 !important;
@@ -603,9 +718,11 @@ const handleLogout = async () => {
   border: none !important;
   background: transparent !important;
   box-shadow: none !important;
-  z-index: 0 !important;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10 !important; /* 增大 z-index */
 }
 
+/* 全屏页面特殊处理 */
 .layout-wrapper.full-screen .layout-main-container {
   height: 100vh;
 }
@@ -615,10 +732,31 @@ const handleLogout = async () => {
   overflow: auto;
   padding: 0 20px;
   box-sizing: border-box;
-  background-color: white;
+  background-color: white; /* 默认背景色 */
+  position: relative; /* 确保它可以参与 z-index 计算 */
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 增大 z-index */
 }
 
 .scrollable-content.transparent-scroll {
   background-color: transparent;
+  /* 修改点:增大 z-index 确保在背景层之上 */
+  z-index: 10; /* 即使透明也应保持在上层 */
+}
+
+/* 左侧区域 */
+.left-section {
+  display: flex;
+  align-items: center;
+  gap: 24px;
+  flex-shrink: 0; /* 防止缩小 */
+}
+
+/* 右侧区域 */
+.right-section {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  flex-shrink: 0; /* 防止缩小 */
 }
 </style>

+ 169 - 323
src/components/soilStatictics/reducedataStatistics.vue

@@ -1,355 +1,201 @@
 <template>
   <div class="chart-container">
-    <div class="chart-controls">
-      <button @click="refreshData" class="refresh-btn">
-        <i class="fas fa-sync-alt"></i> 刷新数据
-      </button>
-      <div class="selected-ids">
-        <span>选中的ID: </span>
-        <span v-for="id in selectedIds" :key="id" class="id-tag">{{ id }}</span>
-      </div>
-    </div>
-    
-    <div v-if="loading" class="loading">
-      <p>数据加载中...</p>
-    </div>
-    
-    <div v-if="error" class="error">
-      <p>{{ error }}</p>
-    </div>
-    
-    <div ref="chartRef" class="chart" v-show="!loading && !error"></div>
+    <h3 class="title">酸化缓解Q_delta_pH柱状图</h3>
+    <div class="echarts-box" ref="chartRef"></div>
   </div>
 </template>
 
-<script>
-import { ref, onMounted, onUnmounted, nextTick } from 'vue';
+<script setup>
+import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
 import * as echarts from 'echarts';
 import { api5000 } from '@/utils/request'; // 导入 api5000 实例
 
-export default {
-  name: 'PhTrendChart',
-  setup() {
-    // 接口数据 - 不同时间点的数据集
-    const interfaces = [
-      { time: '20241229_201018', url: '/api/table-data?table_name=dataset_5' },
-      { time: '20250104_171827', url: '/api/table-data?table_name=dataset_35' },
-      { time: '20250104_171959', url: '/api/table-data?table_name=dataset_36' },
-      { time: '20250104_214026', url: '/api/table-data?table_name=dataset_37' },
-      { time: '20250308_161945', url: '/api/table-data?table_name=dataset_65' },
-      { time: '20250308_163248', url: '/api/table-data?table_name=dataset_66' },
-    ];
-    
-    const chartRef = ref(null);
-    const loading = ref(true);
-    const error = ref(null);
-    const selectedIds = ref([]);
-    const lastUpdate = ref(new Date().toLocaleString());
-    
-    let chartInstance = null;
-    let chartData = {};
-    
-    // 生成1-43之间的6个不重复随机数
-    function generateRandomIds() {
-      const ids = new Set();
-      while (ids.size < 6) {
-        const id = Math.floor(Math.random() * 43) + 1;
-        ids.add(id);
-      }
-      return Array.from(ids).sort((a, b) => a - b);
-    }
+// 图表相关
+const chartRef = ref(null);
+let myChart = null;
+
+// 数据状态
+const chartData = ref([]);
+const isLoading = ref(false);
+const errorMessage = ref('');
+
+// 接口地址(使用相对路径)
+const API_URL = '/api/table-data?table_name=dataset_77'; // 相对路径
+
+// 获取数据函数
+const fetchData = async () => {
+  // 重置状态
+  errorMessage.value = '';
+  isLoading.value = true;
+  
+  try {
+    // 使用 api5000 替代 axios
+    const response = await api5000.get(API_URL);
     
-    // 获取数据
-    async function fetchData() {
-      try {
-        loading.value = true;
-        error.value = null;
-        
-        const newSelectedIds = generateRandomIds();
-        selectedIds.value = newSelectedIds;
-        
-        // 初始化图表数据
-        const newChartData = {};
-        newSelectedIds.forEach(id => {
-          newChartData[id] = {
-            timestamps: [],
-            phValues: []
-          };
-        });
-        
-        // 按时间顺序获取每个表的数据
-        for (const intf of interfaces) {
-          // 使用 api5000 替代 fetch
-          const response = await api5000.get(intf.url);
-          
-          // 检查响应状态
-          if (response.status !== 200) {
-            throw new Error(`网络响应错误: ${response.status}`);
-          }
-          
-          const responseData = response.data;
-          const dataArray = responseData.data || [];
-          
-          for (const id of newSelectedIds) {
-            // 确保数据存在
-            if (!newChartData[id]) {
-              newChartData[id] = {
-                timestamps: [],
-                phValues: []
-              };
-            }
-            
-            // 查找匹配的项目,使用小写id
-            const targetItem = dataArray.find(item => item.id === id);
-            
-            if (targetItem) {
-              // 获取pH值,使用小写pH
-              const phValue = parseFloat(targetItem.pH || 0);
-              newChartData[id].timestamps.push(intf.time);
-              newChartData[id].phValues.push(phValue);
-            } else {
-              // 如果没有找到数据,添加null值保持数据一致性
-              newChartData[id].timestamps.push(intf.time);
-              newChartData[id].phValues.push(null);
-            }
-          }
+    // 检查响应数据是否符合预期格式
+    if (response.data && response.data.success) {
+      if (Array.isArray(response.data.data) && response.data.data.length > 0) {
+        if (response.data.data[0].id !== undefined && response.data.data[0].Q_delta_pH !== undefined) {
+          chartData.value = response.data.data;
+        } else {
+          errorMessage.value = '数据字段缺失:缺少 id 或 Delta_pH';
         }
-        
-        // 更新图表数据
-        chartData = newChartData;
-        lastUpdate.value = new Date().toLocaleString();
-        
-        // 确保DOM更新后再渲染图表
-        await nextTick();
-        renderChart();
-        
-      } catch (err) {
-        error.value = err.message || '获取数据失败';
-        console.error('获取数据错误:', err);
-      } finally {
-        loading.value = false;
+      } else {
+        errorMessage.value = '未获取到有效数据,请稍后重试';
       }
+    } else {
+      errorMessage.value = 'API返回失败状态';
     }
-    
-    // 渲染图表
-    function renderChart() {
-      if (!chartRef.value) {
-        console.error('图表容器未找到');
-        return;
-      }
-      
-      // 销毁旧的图表实例(如果存在)
-      if (chartInstance) {
-        try {
-          chartInstance.dispose();
-        } catch (e) {
-          console.warn('销毁图表实例时出错:', e);
-        }
-        chartInstance = null;
-      }
-      
-      try {
-        // 初始化新的ECharts实例
-        chartInstance = echarts.init(chartRef.value);
-        
-        // 准备系列数据
-        const series = [];
-        const xAxisData = interfaces.map(item => item.time);
-        
-        for (const id of selectedIds.value) {
-          const data = chartData[id]?.phValues || [];
-          series.push({
-            name: `ID: ${id}`,
-            type: 'line',
-            symbol: 'circle',
-            symbolSize: 8,
-            data: data,
-            lineStyle: { width: 2 },
-            emphasis: {
-              focus: 'series'
-            }
-          });
-        }
-        
-        const option = {
-          title: {
-            text: '酸化缓解样本点的pH值趋势图',
-            left: 'center',
-            top: 10
-          },
-          tooltip: {
-            trigger: 'axis',
-            axisPointer: {
-              type: 'cross',
-              label: {
-                backgroundColor: '#6a7985'
-              }
-            }
-          },
-          legend: {
-            data: selectedIds.value.map(id => `ID: ${id}`),
-            top: 40,
-            type: 'scroll'
-          },
-          grid: {
-            left: '8%',
-            right: '5%',
-            bottom: '10%',
-            top: '100px',
-            containLabel: true
-          },
-          xAxis: {
-            type: 'category',
-            boundaryGap: false,
-            data: xAxisData,
-            axisLabel: {
-              rotate: 30,
-              interval: 0
-            },
-            name: '时间'
-          },
-          yAxis: {
-            type: 'value',
-            scale: true,
-            name: 'pH值'
-          },
-          series: series
-        };
-
-        chartInstance.setOption(option);
-        
-        // 延迟resize调用
-        setTimeout(() => {
-          if (chartInstance) {
-            chartInstance.resize();
-          }
-        }, 100);
-      } catch (e) {
-        console.error('初始化图表时出错:', e);
-        error.value = '图表初始化失败: ' + e.message;
-      }
+  } catch (error) {
+    console.error('数据获取失败:', error);
+    // 根据错误类型显示不同信息
+    if (error.response) {
+      errorMessage.value = `请求失败 (${error.response.status}): ${error.response.statusText}`;
+    } else if (error.request) {
+      errorMessage.value = '未收到响应,请检查网络连接';
+    } else {
+      errorMessage.value = '请求发生错误,请稍后重试';
     }
-    
-    // 刷新数据
-    function refreshData() {
-      fetchData();
+  } finally {
+    isLoading.value = false;
+  }
+};
+
+// 初始化图表
+const initChart = () => {
+  // 确保DOM已挂载且有数据
+  if (!chartRef.value) {
+    console.error('图表容器未找到');
+    return;
+  }
+
+  if (chartData.value.length === 0) {
+    console.error('没有数据可用于渲染图表');
+    return;
+  }
+  
+  // 销毁已有实例
+  if (myChart) {
+    myChart.dispose();
+  }
+  
+  // 初始化图表
+  myChart = echarts.init(chartRef.value);
+  
+  // 提取数据
+  const xAxisData = chartData.value.map(item => item.id);
+  const seriesData = chartData.value.map(item => ({
+    value: item.Q_delta_pH,
+    itemStyle: {
+      // 根据值的正负设置不同颜色
+      color: item.Q_delta_pH >= 0 ? '#0F52BA' : '#F44336'
     }
-    
-    onMounted(() => {
-      // 确保DOM完全加载后再初始化图表
-      const initChart = () => {
-        if (chartRef.value) {
-          fetchData();
-        } else {
-          // 如果DOM还未准备好,延迟执行
-          setTimeout(initChart, 100);
-        }
-      };
-      
-      initChart();
-      
-      // 窗口大小变化时重绘图表
-      const resizeHandler = () => {
-        if (chartInstance) {
-          setTimeout(() => {
-            chartInstance.resize();
-          }, 100);
-        }
-      };
-      
-      window.addEventListener('resize', resizeHandler);
-      
-      // 在卸载时移除事件监听器
-      onUnmounted(() => {
-        window.removeEventListener('resize', resizeHandler);
-        if (chartInstance) {
-          try {
-            chartInstance.dispose();
-          } catch (e) {
-            console.warn('卸载时销毁图表实例出错:', e);
+  }));
+  
+  // 图表配置
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      },
+      formatter: function(params) {
+        const item = chartData.value.find(d => d.id === params[0].name);
+        return `ID: ${item.id}<br/>Q_delta_pH: ${item.Q_delta_pH.toFixed(4)}`;
+      }
+    },
+    grid: {
+      left: '5%',
+      right: '1%',
+      bottom: '5%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      name: 'ID',
+      nameLocation: 'middle',
+      nameGap: 30,
+    },
+    yAxis: {
+      type: 'value',
+      name: 'Q_delta_pH',
+      nameLocation: 'middle',
+      nameGap: 50,
+      axisLabel: {
+        formatter: '{value}'
+      }
+    },
+    series: [
+      {
+        name: 'Q_delta_pH',
+        type: 'bar',
+        data: seriesData,
+        barWidth: '60%',
+        label: {
+          show: false,
+          position: 'top',
+          formatter: function(params) {
+            return params.value.toFixed(4);
           }
         }
-      });
-    });
-    
-    return {
-      chartRef,
-      loading,
-      error,
-      selectedIds,
-      lastUpdate,
-      refreshData
-    };
+      }
+    ]
+  };
+  
+  // 设置图表配置
+  myChart.setOption(option);
+};
+
+// 监听数据变化,重新渲染图表
+watch(chartData, () => {
+  nextTick(()=>{
+    initChart();  
+  })
+});
+
+// 窗口大小变化时重绘图表
+const handleResize = () => {
+  if (myChart) {
+    myChart.resize();
   }
-}
+};
+
+// 组件挂载时初始化
+onMounted(async() => {
+  // 首次加载数据
+  await fetchData();
+  initChart();
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (myChart) {
+    myChart.dispose();
+  }
+  window.removeEventListener('resize', handleResize);
+});
 </script>
 
 <style scoped>
 .chart-container {
   width: 100%;
+  height: 470px;
   padding: 20px;
-  height: 550px;
   box-sizing: border-box;
 }
 
-.chart-controls {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 15px;
-  padding-bottom: 10px;
-  border-bottom: 1px solid #eee;
-}
-
-.refresh-btn {
-  background-color: #42b983;
-  color: white;
-  border: none;
-  padding: 8px 16px;
-  border-radius: 4px;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  transition: background-color 0.3s;
-}
-
-.refresh-btn:hover {
-  background-color: #359e75;
-}
-
-.selected-ids {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-  flex-wrap: wrap;
-}
-
-.id-tag {
-  background-color: #e6f7ff;
-  color: #1890ff;
-  padding: 3px 8px;
-  border-radius: 12px;
-  font-size: 12px;
-}
-
-.chart {
+.echarts-box {
   width: 100%;
-  height: calc(100% - 60px);
+  height: 100%;
   border: 1px solid #e0e0e0;
-  border-radius: 4px;
-}
-
-.loading, .error {
-  width: 100%;
-  height: calc(100% - 60px);
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  color: #666;
+  border-radius: 6px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 }
 
-.error {
-  color: #f5222d;
+.title {
+  text-align: center;
 }
 </style>

+ 8 - 8
src/components/soilStatictics/refluxcedataStatictics.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="chart-container">
-    <h3 class="title">酸化加剧Delta_pH柱状图</h3>
+    <h3 class="title">酸化加剧Delta_pH_105day柱状图</h3>
     <div class="echarts-box" ref="chartRef"></div>
   </div>
 </template>
@@ -20,7 +20,7 @@ const isLoading = ref(false);
 const errorMessage = ref('');
 
 // 接口地址(使用相对路径)
-const API_URL = '/api/table-data?table_name=dataset_60'; // 相对路径
+const API_URL = '/api/table-data?table_name=dataset_81'; // 相对路径
 
 // 获取数据函数
 const fetchData = async () => {
@@ -35,7 +35,7 @@ const fetchData = async () => {
     // 检查响应数据是否符合预期格式
     if (response.data && response.data.success) {
       if (Array.isArray(response.data.data) && response.data.data.length > 0) {
-        if (response.data.data[0].id !== undefined && response.data.data[0].Delta_pH !== undefined) {
+        if (response.data.data[0].id !== undefined && response.data.data[0].Delta_pH_105day !== undefined) {
           chartData.value = response.data.data;
         } else {
           errorMessage.value = '数据字段缺失:缺少 id 或 Delta_pH';
@@ -85,10 +85,10 @@ const initChart = () => {
   // 提取数据
   const xAxisData = chartData.value.map(item => item.id);
   const seriesData = chartData.value.map(item => ({
-    value: item.Delta_pH,
+    value: item.Delta_pH_105day,
     itemStyle: {
       // 根据值的正负设置不同颜色
-      color: item.Delta_pH >= 0 ? '#0F52BA' : '#F44336'
+      color: item.Delta_pH_105day >= 0 ? '#0F52BA' : '#F44336'
     }
   }));
   
@@ -101,7 +101,7 @@ const initChart = () => {
       },
       formatter: function(params) {
         const item = chartData.value.find(d => d.id === params[0].name);
-        return `ID: ${item.id}<br/>Delta_pH: ${item.Delta_pH.toFixed(4)}`;
+        return `ID: ${item.id}<br/>Delta_pH_105day: ${item.Delta_pH_105day.toFixed(4)}`;
       }
     },
     grid: {
@@ -119,7 +119,7 @@ const initChart = () => {
     },
     yAxis: {
       type: 'value',
-      name: 'Delta_pH',
+      name: 'Delta_pH_105day',
       nameLocation: 'middle',
       nameGap: 50,
       axisLabel: {
@@ -128,7 +128,7 @@ const initChart = () => {
     },
     series: [
       {
-        name: 'Delta_pH',
+        name: 'Delta_pH_105day',
         type: 'bar',
         data: seriesData,
         barWidth: '60%',

+ 4 - 1
src/stores/mytoken.ts

@@ -1,10 +1,10 @@
-// src/stores/mytoken.ts
 import { defineStore } from "pinia";
 
 export interface UserInfo {
   userId: number;
   name: string;
   loginType: "user" | "admin";
+  
 }
 
 interface TokenState {
@@ -13,15 +13,18 @@ interface TokenState {
 
 export const useTokenStore = defineStore("mytoken", {
   state: (): TokenState => ({
+    // 初始化时从 localStorage 恢复
     userInfo: JSON.parse(localStorage.getItem("userInfo") || "null"),
   }),
 
   actions: {
+    // 保存用户信息
     saveToken(userInfo: UserInfo) {
       this.userInfo = userInfo;
       localStorage.setItem("userInfo", JSON.stringify(userInfo));
     },
 
+    // 清理用户信息
     clearToken() {
       this.userInfo = null;
       localStorage.removeItem("userInfo");

+ 12 - 1
src/utils/request.ts

@@ -42,6 +42,15 @@ export const apiMain: CustomAxiosInstance = axios.create({
   withCredentials: true
 });
 
+export const api8080: CustomAxiosInstance = axios.create({
+  baseURL: isDevelopment
+    ? 'http://localhost:8080'
+    : 'https://www.soilgd.com',
+  timeout: 100000,
+  withCredentials: true
+});
+
+
 
 //  检查是否为GeoJSON响应
 function isGeoJSONResponse(response: AxiosResponse): boolean {
@@ -138,10 +147,12 @@ const setupInterceptors = (instance: CustomAxiosInstance) => {
 setupInterceptors(api5000);
 setupInterceptors(api8000);
 setupInterceptors(apiMain);
+setupInterceptors(api8080);
 
 // 导出所有API客户端
 export default {
   api5000,
   api8000,
-  apiMain
+  apiMain,
+  api8080
 };

+ 92 - 142
src/views/User/acidModel/Calculation.vue

@@ -6,106 +6,65 @@
       </div>
     </template>
 
-    <div class="model-info">
-      <p><strong>当前选中模型 ID:</strong> {{ selectedModelId }}</p>
-    </div>
-
     <el-form
       :model="form"
       ref="predictForm"
       label-width="240px"
       label-position="left"
     >
+      <el-form-item label="交换性氢(cmol/kg)" prop="H" :error="errorMessages.H" required>
+        <el-input
+          v-model="form.H"
+          size="large"
+          placeholder="请输入交换性氢0~5(cmol/kg)"
+          @input="handleInput('H', $event, 0, 5)"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="交换性铝(cmol/kg)" prop="Al" :error="errorMessages.Al" required>
+        <el-input
+          v-model="form.Al"
+          size="large"
+          placeholder="请输入交换性铝0~10(cmol/kg)"
+          @input="handleInput('Al', $event, 0, 10)"
+        ></el-input>
+      </el-form-item>
       <el-form-item label="土壤有机质(g/kg)" prop="OM" :error="errorMessages.OM" required>
         <el-input
           v-model="form.OM"
           size="large"
-          placeholder="请输入土壤有机质0~30(g/kg)"
-          @input="handleInput('OM', $event, 0, 30)"
+          placeholder="请输入土壤有机质0~35(g/kg)"
+          @input="handleInput('OM', $event, 0, 35)"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="硝酸盐(mg/kg)" prop="NO3" :error="errorMessages.NO3" required>
+        <el-input
+          v-model="form.NO3"
+          size="large"
+          placeholder="请输入硝酸盐0~70(mg/kg)"
+          @input="handleInput('NO3', $event, 0, 70)"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="铵盐(mg/kg)" prop="NH4" :error="errorMessages.NH4" required>
+        <el-input
+          v-model="form.NH4"
+          size="large"
+          placeholder="请输入铵盐0~20(mg/kg)"
+          @input="handleInput('NH4', $event, 0, 20)"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="阳离子交换量(cmol/kg)" prop="CEC" :error="errorMessages.CEC" required>
+        <el-input
+          v-model="form.CEC"
+          size="large"
+          placeholder="请输入阳离子交换量0~20(cmol/kg)"
+          @input="handleInput('CEC', $event, 0, 20)"
         ></el-input>
       </el-form-item>
 
-        <el-form-item
-          label="土壤粘粒(g/kg)"
-          prop="CL"
-          :error="errorMessages.CL"
-          required
-        >
-          <el-input
-            v-model="form.CL"
-            size="large"
-            placeholder="请输入土壤粘粒50~400(g/kg)"
-            @input="handleInput('CL', $event, 50, 400)"
-          >
-          </el-input>
-        </el-form-item>
-
-        <el-form-item
-          label="阳离子交换量(cmol/kg)"
-          prop="CEC"
-          :error="errorMessages.CEC"
-          required
-        >
-          <el-input
-            v-model="form.CEC"
-            size="large"
-            placeholder="请输入阳离子交换量0~15(cmol/kg)"
-            @input="handleInput('CEC', $event, 0, 15)"
-          >
-          </el-input>
-        </el-form-item>
-
-        <el-form-item
-          label="交换性氢(cmol/kg)"
-          prop="H_plus"
-          :error="errorMessages.H_plus"
-          required
-        >
-          <el-input
-            v-model="form.H_plus"
-            size="large"
-            placeholder="请输入交换性氢0~1(cmol/kg)"
-            @input="handleInput('H_plus', $event, 0, 1)"
-          >
-          </el-input>
-        </el-form-item>
-
-        <el-form-item
-          label="水解氮(g/kg)"
-          prop="N"
-          :error="errorMessages.N"
-          required
-        >
-          <el-input
-            v-model="form.N"
-            size="large"
-            placeholder="请输入水解氮0~0.2(g/kg)"
-            @input="handleInput('N', $event, 0, 0.2)"
-          >
-          </el-input>
-        </el-form-item>
-
-        <el-form-item
-          label="交换性铝(cmol/kg)"
-          prop="Al3_plus"
-          :error="errorMessages.Al3_plus"
-          required
-        >
-          <el-input
-            v-model="form.Al3_plus"
-            size="large"
-            placeholder="请输入交换性铝0~6(cmol/kg)"
-            @input="handleInput('Al3_plus', $event, 0, 6)"
-          >
-          </el-input>
-        </el-form-item>
-
-        <el-button type="primary" @click="onSubmit" class="onSubmit"
-          >计算</el-button
-        >
+      <el-button type="primary" @click="onSubmit" class="onSubmit">计算</el-button>
 
       <el-dialog v-model="dialogVisible" @close="onDialogClose" :close-on-click-modal="false" width="500px" align-center title="计算结果">
-        <span class="dialog-class">pH: {{ result }}</span>
+        <span class="dialog-class">ΔpH: {{ result }}</span>
         <template #footer>
           <el-button @click="dialogVisible = false">关闭</el-button>
         </template>
@@ -119,41 +78,41 @@ import { reactive, ref, nextTick, onMounted } from "vue";
 import { ElMessage } from "element-plus";
 import { api5000 } from "../../../utils/request"; // 使用api5000
 
-// 表单接口
+// 表单接口 - 根据文档修改
 interface Form {
+  H: number | null;
+  Al: number | null;
   OM: number | null;
-  CL: number | null;
+  NO3: number | null;
+  NH4: number | null;
   CEC: number | null;
-  H_plus: number | null;
-  N: number | null;
-  Al3_plus: number | null;
 }
 
-// 表单数据
+// 表单数据 - 根据文档修改
 const form = reactive<Form>({
+  H: null,
+  Al: null,
   OM: null,
-  CL: null,
+  NO3: null,
+  NH4: null,
   CEC: null,
-  H_plus: null,
-  N: null,
-  Al3_plus: null,
 });
 
 const result = ref<number | null>(null);
 const dialogVisible = ref(false);
 const predictForm = ref<any>(null);
 const errorMessages = reactive<Record<string, string>>({
+  H: "",
+  Al: "",
   OM: "",
-  CL: "",
+  NO3: "",
+  NH4: "",
   CEC: "",
-  H_plus: "",
-  N: "",
-  Al3_plus: "",
 });
 
 // 当前选中模型
-const selectedModelId = ref<number>(24); // 默认值
-const selectedModelName = ref<string>("默认模型");
+const selectedModelId = ref<number>(35); // 根据文档修改为35
+const selectedModelName = ref<string>("反酸预测模型");
 
 // 输入校验
 const handleInput = (field: keyof Form, event: Event, min: number, max: number) => {
@@ -190,17 +149,16 @@ const fetchSelectedModel = async () => {
   }
 };
 
-// 计算方法
+// 计算方法 - 根据文档修改参数
 const onSubmit = async () => {
-  // 输入校验
+  // 输入校验 - 根据新字段修改
   const inputConfigs = [
-    { field: "OM" as keyof Form, min: 0, max: 30 },
-    { field: "CL" as keyof Form, min: 50, max: 400 },
-    { field: "CEC" as keyof Form, min: 0, max: 15 },
-    { field: "CEC" as keyof Form, min: 0, max: 15 },
-    { field: "H_plus" as keyof Form, min: 0, max: 1 },
-    { field: "N" as keyof Form, min: 0, max: 0.2 },
-    { field: "Al3_plus" as keyof Form, min: 0, max: 6 },
+    { field: "H" as keyof Form, min: 0, max: 5 },
+    { field: "Al" as keyof Form, min: 0, max: 10},
+    { field: "OM" as keyof Form, min: 0, max: 35 },
+    { field: "NO3" as keyof Form, min: 0, max: 70 },
+    { field: "NH4" as keyof Form, min: 0, max: 20 },
+    { field: "CEC" as keyof Form, min: 0, max: 20 },
   ];
 
   let isValid = true;
@@ -220,17 +178,19 @@ const onSubmit = async () => {
     return;
   }
 
+  // 根据文档修改参数结构
   const data = {
     model_id: selectedModelId.value,
     parameters: {
+      H: form.H,
+      Al: form.Al,
       OM: form.OM,
-      CL: form.CL,
+      NO3: form.NO3,
+      NH4: form.NH4,
       CEC: form.CEC,
-      H_plus: form.H_plus,
-      N: form.N,
-      Al3_plus: form.Al3_plus,
     },
   };
+  
   try {
     const response = await api5000.post('/predict', data, {
       headers: {
@@ -272,16 +232,8 @@ onMounted(() => {
 </script>
 
 <style scoped>
-.container {
-  display: flex;
-  flex-direction: column;
-  gap: 20px;
-  max-width: 1400px;
-  margin: 0 auto;
-}
-
-.form-card {
-  width: 850px;
+.box-card {
+  max-width: 850px;
   margin: 0 auto;
   padding: 20px;
   background-color: #f0f5ff;
@@ -290,17 +242,19 @@ onMounted(() => {
 }
 
 .card-header {
+  font-size: 25px;
   text-align: center;
   color: #333;
   margin-bottom: 30px;
-  font-size: 25px;
 }
 
-.model-info {
-  text-align: center;
+.el-form-item {
   margin-bottom: 20px;
-  font-size: 16px;
-  color: #555;
+}
+
+:deep(.el-form-item__label) {
+  font-size: 18px;
+  color: #666;
 }
 
 .model-info {
@@ -310,16 +264,6 @@ onMounted(() => {
   color: #555;
 }
 
-
-.el-form-item {
-  margin-bottom: 20px;
-}
-
-:deep(.el-form-item__label) {
-  font-size: 18px;
-  color: #666;
-}
-
 .el-input {
   width: 80%;
 }
@@ -343,7 +287,13 @@ onMounted(() => {
   display: flex;
   justify-content: center;
   align-items: center;
-  height: 100%;
-  font-size: 20px;
+  height: 100%; /* 确保容器占满对话框内容区域 */
+  font-size: 22px;
+}
+
+@media (max-width: 576px) {
+  .el-form {
+    --el-form-label-width: 100px;
+  }
 }
-</style>
+</style>

+ 3 - 3
src/views/User/acidModel/SoilAcidReductionIterativeEvolution.vue

@@ -24,9 +24,9 @@ const showFinalScatterChart = ref(true);
 
 // 直接在代码里定义好参数
 const lineChartPathParam = ref('reflux'); 
-const initScatterModelId = ref(24);
-const midScatterModelId = ref(25);
-const finalScatterModelId = ref(26);
+const initScatterModelId = ref(35);
+const midScatterModelId = ref(35);
+const finalScatterModelId = ref(35);
 </script>
 
 <style scoped>

File diff suppressed because it is too large
+ 643 - 170
src/views/User/acidModel/acidmodelmap.vue


+ 6 - 6
src/views/User/introduction/Introduce.vue

@@ -15,8 +15,8 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'; // 移除 defineProps
-import axios from 'axios';
+import { ref, onMounted } from 'vue';
+import { api5000 } from '@/utils/request'; // 导入封装的api5000
 
 // 定义 targetId 为 prop
 const props = defineProps({
@@ -42,17 +42,17 @@ const isTitle = (paragraph: string) => {
 };
 
 const imageBaseUrl = ref(import.meta.env.VITE_IMAGE_BASE_URL);
-// 处理段落,将图片路径转换为 <img> 标签
+// 处理段落,将图片路径转换为  标签
 const processParagraph = (paragraph: string) => {
   // 假设图片路径是相对路径
   const imgRegex = /([^\s]+(\.jpg|\.jpeg|\.png|\.gif))/g;
-  return paragraph.replace(imgRegex, `<img src="${imageBaseUrl.value}$1" alt="图片" class="intro-image">`);
+  return paragraph.replace(imgRegex, ``);
 };
 
 onMounted(async () => {
   try {
-    // 从后端获取介绍数据
-    const response = await axios.get(`https://127.0.0.1:5000/software-intro/${props.targetId}`);
+    // 使用 api5000 调用接口
+    const response = await api5000.get(`/admin/software-intro/${props.targetId}`);
     const { title, intro } = response.data;
     // 保留 \r\n 换行符
     const processedIntro = intro.replace(/<p>/g, '').replace(/<\/p>/g, '\r\n').replace(/<br>/g, '');

+ 101 - 68
src/views/User/neutralizationModel/AcidNeutralizationModel.vue

@@ -6,11 +6,6 @@
       </div>
     </template>
 
-    
-    <div class="model-info">
-      <p><strong>当前选中模型 ID:</strong> {{ selectedModelId }}</p>
-    </div>
-
     <el-form
       :model="form"
       ref="predictForm"
@@ -49,6 +44,38 @@
         </el-input>
       </el-form-item>
 
+      <el-form-item
+        label="交换性氢(cmol/kg)"
+        prop="H"
+        :error="errorMessages.H"
+        required
+      >
+        <el-input
+          v-model="form.H"
+          size="large"
+          placeholder="请输入交换性氢 0~4 (cmol/kg)"
+          ref="inputRefs.H"
+          @input="handleInput('H', $event, 0, 4)"
+        >
+        </el-input>
+      </el-form-item>
+
+      <el-form-item
+        label="交换性铝(cmol/kg)"
+        prop="Al"
+        :error="errorMessages.Al"
+        required
+      >
+        <el-input
+          v-model="form.Al"
+          size="large"
+          placeholder="请输入交换性铝 0~8 (cmol/kg)"
+          ref="inputRefs.Al"
+          @input="handleInput('Al', $event, 0, 8)"
+        >
+        </el-input>
+      </el-form-item>
+
       <el-form-item
         label="土壤有机质(g/kg)"
         prop="OM"
@@ -58,57 +85,57 @@
         <el-input
           v-model="form.OM"
           size="large"
-          placeholder="请输入土壤有机质 10~30 (g/kg)"
+          placeholder="请输入土壤有机质 0~35 (g/kg)"
           ref="inputRefs.OM"
-          @input="handleInput('OM', $event, 10, 30)"
+          @input="handleInput('OM', $event, 0, 35)"
         >
         </el-input>
       </el-form-item>
 
       <el-form-item
-        label="土壤粘粒(g/kg)"
-        prop="CL"
-        :error="errorMessages.CL"
+        label="硝酸盐(mg/kg)"
+        prop="NO3"
+        :error="errorMessages.NO3"
         required
       >
         <el-input
-          v-model="form.CL"
+          v-model="form.NO3"
           size="large"
-          placeholder="请输入土壤粘粒 50~600 (g/kg)"
-          ref="inputRefs.CL"
-          @input="handleInput('CL', $event, 50, 600)"
+          placeholder="请输入硝酸盐 10~70 (mg/kg)"
+          ref="inputRefs.NO3"
+          @input="handleInput('NO3', $event, 10, 70)"
         >
         </el-input>
       </el-form-item>
 
       <el-form-item
-        label="交换性氢(cmol/kg)"
-        prop="H"
-        :error="errorMessages.H"
+        label="铵盐(mg/kg)"
+        prop="NH4"
+        :error="errorMessages.NH4"
         required
       >
         <el-input
-          v-model="form.H"
+          v-model="form.NH4"
           size="large"
-          placeholder="请输入交换性氢 0~4 (cmol/kg)"
-          ref="inputRefs.H"
-          @input="handleInput('H', $event, 0, 4)"
+          placeholder="请输入铵盐 0~20 (mg/kg)"
+          ref="inputRefs.NH4"
+          @input="handleInput('NH4', $event, 0, 20)"
         >
         </el-input>
       </el-form-item>
 
       <el-form-item
-        label="交换性铝(cmol/kg)"
-        prop="Al"
-        :error="errorMessages.Al"
+        label="阳离子交换量(cmol/kg)"
+        prop="CEC"
+        :error="errorMessages.CEC"
         required
       >
         <el-input
-          v-model="form.Al"
+          v-model="form.CEC"
           size="large"
-          placeholder="请输入交换性铝 0~4 (cmol/kg)"
-          ref="inputRefs.Al"
-          @input="handleInput('Al', $event, 0, 4)"
+          placeholder="请输入阳离子交换量 0~15 (cmol/kg)"
+          ref="inputRefs.CEC"
+          @input="handleInput('CEC', $event, 0, 15)"
         >
         </el-input>
       </el-form-item>
@@ -129,7 +156,6 @@
         <span class="dialog-class"
           >每亩地土壤表层(20cm)撒 {{ result }}吨生石灰</span
         ><br />
-        <span class="dialog-class">{{ result }}吨</span>
         <template #footer>
           <el-button @click="dialogVisible = false">关闭</el-button>
         </template>
@@ -144,20 +170,25 @@ import { ElMessage } from 'element-plus';
 import axios from 'axios';
 import { api5000 } from "../../../utils/request"; // 使用api5000
 
+// 根据文档修改表单接口
 const form = reactive<{
   init_pH: number | null,
   target_pH: number | null,
-  OM: number | null,
-  CL: number | null,
   H: number | null,
   Al: number | null,
+  OM: number | null,
+  NO3: number | null,
+  NH4: number | null,
+  CEC: number | null,
 }>({
   init_pH: null,
   target_pH: null,
-  OM: null,
-  CL: null,
   H: null,
   Al: null,
+  OM: null,
+  NO3: null,
+  NH4: null,
+  CEC: null,
 });
 
 const result = ref<number | null>(null);
@@ -166,15 +197,17 @@ const predictForm = ref<any>(null);
 const errorMessages = reactive({
   init_pH: '',
   target_pH: '',
-  OM: '',
-  CL: '',
   H: '',
   Al: '',
+  OM: '',
+  NO3: '',
+  NH4: '',
+  CEC: '',
 });
 
-// 当前选中模型
-const selectedModelId = ref<number>(6); // 默认值
-const selectedModelName = ref<string>("默认模型");
+// 当前选中模型 - 根据文档修改为33
+const selectedModelId = ref<number>(33);
+const selectedModelName = ref<string>("降酸预测模型");
 
 // 使用 ref 来存储输入框元素
 const inputRefs = reactive<{
@@ -182,10 +215,12 @@ const inputRefs = reactive<{
 }>({
   init_pH: ref<HTMLInputElement | null>(null),
   target_pH: ref<HTMLInputElement | null>(null),
-  OM: ref<HTMLInputElement | null>(null),
-  CL: ref<HTMLInputElement | null>(null),
   H: ref<HTMLInputElement | null>(null),
   Al: ref<HTMLInputElement | null>(null),
+  OM: ref<HTMLInputElement | null>(null),
+  NO3: ref<HTMLInputElement | null>(null),
+  NH4: ref<HTMLInputElement | null>(null),
+  CEC: ref<HTMLInputElement | null>(null),
 });
 
 // 限制输入为数字并校验范围
@@ -203,17 +238,17 @@ const handleInput = (field: keyof typeof form, event: Event, min: number, max: n
     inputElement.value = filteredValue;
 
     if (filteredValue === '') {
-      form[field] = null; // 当输入为空时设置为 null
+      form[field] = null;
       errorMessages[field] = '';
       return;
     }
 
     const numValue = parseFloat(filteredValue);
     if (isNaN(numValue) || numValue < min || numValue > max) {
-      form[field] = null; // 如果值无效,则设为 null
+      form[field] = null;
       errorMessages[field] = `输入值应在 ${min} 到 ${max} 之间且为有效数字`;
     } else {
-      form[field] = numValue; // 如果值有效,则设为解析后的数值
+      form[field] = numValue;
       errorMessages[field] = '';
     }
   } catch (error) {
@@ -233,22 +268,19 @@ const validateInput = (value: string | number, min: number, max: number): boolea
 // 获取当前保存的模型
 const fetchSelectedModel = async () => {
   try {
-    const resp = await api5000.post('/model-selection?model_type=reflux');
-    // 如果接口返回了有效数据,使用它;否则使用默认
+    const resp = await api5000.post('/model-selection?model_type=acid_reduction');
     if (resp.data && resp.data.data && resp.data.data.model_id) {
       selectedModelId.value = resp.data.data.model_id;
       selectedModelName.value = resp.data.data.model_name;
     } else {
-      // 接口没有返回选中模型,使用默认值
-      selectedModelId.value = 6;
-      selectedModelName.value = "默认降酸模型";
-      console.warn("未获取到选中模型,使用默认 model_id=6");
+      selectedModelId.value = 33;
+      selectedModelName.value = "降酸预测模型";
+      console.warn("未获取到选中模型,使用默认 model_id=33");
     }
   } catch (error) {
-    // 接口请求失败,也使用默认
-    selectedModelId.value = 6;
-    selectedModelName.value = "默认降酸模型";
-    console.warn("获取当前模型失败,使用默认 model_id=6:", error);
+    selectedModelId.value = 33;
+    selectedModelName.value = "降酸预测模型";
+    console.warn("获取当前模型失败,使用默认 model_id=33:", error);
   }
 };
 
@@ -257,16 +289,17 @@ onMounted(() => {
   fetchSelectedModel();
 });
 
-
-// 计算方法
+// 计算方法 - 根据文档修改参数
 const onSubmit = async () => {
   const inputConfigs = [
     { field: 'init_pH' as keyof typeof form, min: 3, max: 6 },
     { field: 'target_pH' as keyof typeof form, min: 5, max: 7 },
-    { field: 'OM' as keyof typeof form, min: 10, max: 30 },
-    { field: 'CL' as keyof typeof form, min: 50, max: 600 },
     { field: 'H' as keyof typeof form, min: 0, max: 4 },
-    { field: 'Al' as keyof typeof form, min: 0, max: 4 },
+    { field: 'Al' as keyof typeof form, min: 0, max: 8 },
+    { field: 'OM' as keyof typeof form, min: 0, max: 35 },
+    { field: 'NO3' as keyof typeof form, min: 10, max: 70 },
+    { field: 'NH4' as keyof typeof form, min: 0, max: 20 },
+    { field: 'CEC' as keyof typeof form, min: 0, max: 15 },
   ];
 
   let isValid = true;
@@ -274,7 +307,6 @@ const onSubmit = async () => {
     const { field, min, max } = config;
     const value = form[field];
     
-    // 检查是否为 null
     if (value === null) {
       isValid = false;
       errorMessages[field] = `输入值应在 ${min} 到 ${max} 之间且为有效数字`;
@@ -295,18 +327,21 @@ const onSubmit = async () => {
   }
 
   console.log('开始计算...');
+  // 根据文档修改参数结构
   const data = {
     model_id: selectedModelId.value,
     parameters: {
       init_pH: form.init_pH,
       target_pH: form.target_pH,
-      OM: form.OM,
-      CL: form.CL,
       H: form.H,
       Al: form.Al,
+      OM: form.OM,
+      NO3: form.NO3,
+      NH4: form.NH4,
+      CEC: form.CEC,
     }
   };
-  console.log('提交的数据:', data);//https://www.soilgd.com:5000
+  console.log('提交的数据:', data);
   try {
     const response = await api5000.post('/predict', data, {
       headers: {
@@ -315,7 +350,7 @@ const onSubmit = async () => {
     });
     console.log('预测结果:', response.data);
     if (response.data && typeof response.data.result === 'number') {
-      result.value = parseFloat(response.data.result.toFixed(2));
+      result.value = parseFloat((response.data.result / 10).toFixed(2));
     } else {
       console.error('未获取到有效的预测结果');
       ElMessage.error('未获取到有效的预测结果');
@@ -341,13 +376,11 @@ const onSubmit = async () => {
 const onDialogClose = () => {
   dialogVisible.value = false;
 
-  // 初始化 form 中的所有字段
   Object.keys(form).forEach((key) => {
     const typedKey = key as keyof typeof form;
     form[typedKey] = null;
   });
 
-  // 清除所有的错误信息
   Object.keys(errorMessages).forEach((key) => {
     const typedKey = key as keyof typeof errorMessages;
     errorMessages[typedKey] = '';
@@ -375,7 +408,7 @@ const onDialogClose = () => {
   font-size: 25px;
   text-align: center;
   color: #333;
-  margin-bottom: 30px;
+  margin-bottom: 0px;
 }
 
 .el-form-item {
@@ -417,7 +450,7 @@ const onDialogClose = () => {
   display: flex;
   justify-content: center;
   align-items: center;
-  height: 100%; /* 确保容器占满对话框内容区域 */
+  height: 100%;
   font-size: 22px;
 }
 
@@ -426,4 +459,4 @@ const onDialogClose = () => {
     --el-form-label-width: 100px;
   }
 }
-</style>
+</style>

+ 3 - 3
src/views/User/neutralizationModel/SoilAcidificationIterativeEvolution.vue

@@ -24,9 +24,9 @@ const showFinalScatterChart = ref(true);
 
 // 直接在代码里定义好参数
 const lineChartPathParam = ref('reduce'); 
-const initScatterModelId = ref(17);
-const midScatterModelId = ref(18);
-const finalScatterModelId = ref(19);
+const initScatterModelId = ref(33);
+const midScatterModelId = ref(33);
+const finalScatterModelId = ref(33);
 </script>
 
 <style scoped>

Some files were not shown because too many files changed in this diff