#8 数据展示修复和页面展示完善

Open
lirong wants to merge 1 commits from qw/lili into qw/master

+ 3 - 1
.hintrc

@@ -9,6 +9,8 @@
       {
         "html-has-lang": "off"
       }
-    ]
+    ],
+    "typescript-config/consistent-casing": "off",
+    "typescript-config/strict": "off"
   }
 }

+ 9 - 0
components.d.ts

@@ -23,31 +23,40 @@ declare module 'vue' {
     CrossSetionData1: typeof import('./src/components/irrpollution/crossSetionData1.vue')['default']
     CrossSetionData2: typeof import('./src/components/irrpollution/crossSetionData2.vue')['default']
     CrossSetionTencentmap: typeof import('./src/components/irrpollution/crossSetionTencentmap.vue')['default']
+    ElAlert: typeof import('element-plus/es')['ElAlert']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElEmpty: typeof import('element-plus/es')['ElEmpty']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    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']
+    ElLoading: typeof import('element-plus/es')['ElLoading']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElOption: typeof import('element-plus/es')['ElOption']
     ElRadio: typeof import('element-plus/es')['ElRadio']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElTable: typeof import('element-plus/es')['ElTable']
     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']
     HeavyMetalEnterprisechart: typeof import('./src/components/atmpollution/heavyMetalEnterprisechart.vue')['default']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
     IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']

File diff suppressed because it is too large
+ 890 - 31
package-lock.json


+ 3 - 1
package.json

@@ -13,6 +13,7 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
+    "@intlify/unplugin-vue-i18n": "^6.0.8",
     "@turf/turf": "^7.2.0",
     "@types/d3": "^7.4.3",
     "@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -32,13 +33,14 @@
     "leaflet": "^1.9.4",
     "leaflet-compass": "^1.5.6",
     "leaflet.chinatmsproviders": "^3.0.6",
+    "lodash.kebabcase": "^4.1.1",
     "pinia": "^2.3.0",
     "proj4": "^2.19.10",
     "proj4leaflet": "^1.0.2",
     "sass": "^1.84.0",
     "vue": "^3.5.13",
     "vue-echarts": "^7.0.3",
-    "vue-i18n": "^11.1.3",
+    "vue-i18n": "^9.8.0",
     "vue-leaflet": "^0.1.0",
     "vue-router": "^4.5.0",
     "xlsx": "^0.18.5"

+ 178 - 0
scripts/extract-i18n.js

@@ -0,0 +1,178 @@
+//提取中文到zh.json和en.json文件中,方便网页的中英文切换
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+// 基础路径处理
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const args = process.argv.slice(2);
+const targetPath = args[0] ? path.resolve(__dirname, '../', args[0]) : null;
+
+// 语言包路径配置
+const localesDir = path.resolve(__dirname, '../src/locales');
+const zhPath = path.join(localesDir, 'zh.json');
+const enPath = path.join(localesDir, 'en.json');
+
+// 初始化语言包数据
+let zh = {};
+let en = {};
+
+// 读取已有语言包(保留已有翻译)
+try {
+  if (fs.existsSync(zhPath)) {
+    zh = JSON.parse(fs.readFileSync(zhPath, 'utf-8'));
+  }
+  if (fs.existsSync(enPath)) {
+    en = JSON.parse(fs.readFileSync(enPath, 'utf-8'));
+  }
+} catch (e) {
+  console.error('⚠️ 语言包解析错误,将使用空对象初始化', e);
+  zh = {};
+  en = {};
+}
+
+/**
+ * 递归设置嵌套对象的值(处理键路径)
+ * @param {Object} obj - 目标对象
+ * @param {string} keyPath - 键路径(如 "a.b.c")
+ * @param {string} value - 要设置的值
+ */
+function setNestedValue(obj, keyPath, value) {
+  const keys = keyPath.split('.');
+  let current = obj;
+
+  for (let i = 0; i < keys.length - 1; i++) {
+    const key = keys[i];
+    // 处理键名冲突(已存在字符串值时跳过)
+    if (typeof current[key] === 'string') {
+      console.warn(`⚠️ 键名冲突:"${key}" 已作为字符串存在,跳过路径 "${keyPath}"`);
+      return;
+    }
+    // 初始化不存在的键为对象
+    if (!current[key] || typeof current[key] !== 'object') {
+      current[key] = {};
+    }
+    current = current[key];
+  }
+
+  const lastKey = keys[keys.length - 1];
+  // 仅添加新键,不覆盖已有值
+  if (current[lastKey] === undefined) {
+    current[lastKey] = value;
+    console.log(`✅ 新增翻译:${keyPath} → ${value}`);
+  }
+}
+
+/**
+ * 扫描目标路径下的所有Vue和TS文件
+ * @param {string[]} fileList - 文件列表容器
+ * @returns {string[]} 扫描到的文件路径列表
+ */
+function scanFiles(fileList = []) {
+  if (!targetPath) {
+    // 未指定路径时默认扫描src目录
+    const scanRoot = path.resolve(__dirname, '../src');
+    scanDir(scanRoot, fileList);
+  } else {
+    const stats = fs.statSync(targetPath);
+    if (stats.isDirectory()) {
+      scanDir(targetPath, fileList);
+    } else if (targetPath.endsWith('.vue') || targetPath.endsWith('.ts')) {
+      fileList.push(targetPath);
+    } else {
+      console.warn(`⚠️ 跳过非Vue/TS文件:${targetPath}`);
+    }
+  }
+  return fileList;
+}
+
+/**
+ * 递归扫描目录中的文件
+ * @param {string} dir - 目录路径
+ * @param {string[]} fileList - 文件列表容器
+ */
+function scanDir(dir, fileList) {
+  const files = fs.readdirSync(dir);
+  for (const file of files) {
+    const fullPath = path.join(dir, file);
+    // 跳过无关目录
+    if (file.includes('node_modules') || file.includes('dist') || file.includes('.git')) {
+      continue;
+    }
+    const stats = fs.statSync(fullPath);
+    if (stats.isDirectory()) {
+      scanDir(fullPath, fileList);
+    } else if (fullPath.endsWith('.vue') || fullPath.endsWith('.ts')) {
+      fileList.push(fullPath);
+    }
+  }
+}
+
+/**
+ * 从文件内容中提取i18n标记文本
+ * @param {string} content - 文件内容
+ * @param {string} filePath - 文件路径(用于错误提示)
+ */
+function extractI18nMarkers(content, filePath) {
+  // 匹配格式:<!--i18n:key-->中文文本(支持换行和常见标点)
+  const regex = /<!--i18n:(\S+)-->([\u4e00-\u9fa5\w\s,.,。;;!!??::()()]+?)(?=\r?\n|['"<]|$)/g;
+  let match;
+
+  while ((match = regex.exec(content)) !== null) {
+    const key = match[1].trim();
+    const text = match[2].trim().replace(/\s+/g, ' '); // 清理多余空格
+
+    if (!key) {
+      console.warn(`⚠️ 缺少键名(文件:${filePath}):${match[0]}`);
+      continue;
+    }
+    if (!text) {
+      console.warn(`⚠️ 缺少文本内容(文件:${filePath},键:${key})`);
+      continue;
+    }
+
+    // 写入语言包
+    setNestedValue(zh, key, text);
+    setNestedValue(en, key, en[key] || ''); // 英文保留已有翻译,否则留空
+  }
+}
+
+/**
+ * 执行提取流程
+ */
+function runExtraction() {
+  const files = scanFiles();
+  
+  if (files.length === 0) {
+    console.log('⚠️ 未找到任何需要处理的Vue/TS文件');
+    return;
+  }
+
+  // 处理所有扫描到的文件
+  files.forEach(file => {
+    try {
+      const content = fs.readFileSync(file, 'utf-8');
+      extractI18nMarkers(content, file);
+    } catch (e) {
+      console.error(`⚠️ 处理文件失败:${file}`, e);
+    }
+  });
+
+  // 确保语言包目录存在
+  if (!fs.existsSync(localesDir)) {
+    fs.mkdirSync(localesDir, { recursive: true });
+  }
+
+  // 写入语言包文件
+  fs.writeFileSync(zhPath, JSON.stringify(zh, null, 2), 'utf-8');
+  fs.writeFileSync(enPath, JSON.stringify(en, null, 2), 'utf-8');
+
+  console.log(`\n✅ 提取完成!共处理 ${files.length} 个文件`);
+  console.log(`📄 中文语言包:${zhPath}`);
+  console.log(`📄 英文语言包:${enPath}`);
+}
+
+// 启动提取
+runExtraction();
+    

+ 1 - 6
src/App.vue

@@ -1,12 +1,7 @@
 <script setup lang='ts'>
 import { RouterView } from "vue-router"
 import request from './utils/request';
-request({
-  url: '/table',
-  method: 'get'
-}).then(res => {
-  console.log(res)
-})
+
 </script>
 
 <template>

+ 3 - 3
src/components/layout/AppAside.vue

@@ -11,7 +11,7 @@
         <el-sub-menu v-if="item.children" :index="item.index">
           <template #title>
             <el-icon><component :is="item.icon" /></el-icon>
-            <span>{{ item.label }}</span>
+            <span>{{$t(item.label)}}</span>
           </template>
           <el-menu-item
             v-for="child in item.children"
@@ -20,7 +20,7 @@
             @click="handleMenuClick(child.index)"
           >
             <el-icon><component :is="child.icon" /></el-icon>
-            <span>{{ child.label }}</span>
+            <span>{{ $t(child.label) }}</span>
           </el-menu-item>
         </el-sub-menu>
 
@@ -30,7 +30,7 @@
           @click="handleMenuClick(item.index)"
         >
           <el-icon><component :is="item.icon" /></el-icon>
-          <span>{{ item.label }}</span>
+          <span>{{ $t(item.label) }}</span>
         </el-menu-item>
       </template>
     </el-menu>

+ 180 - 108
src/components/layout/AppLayout.vue

@@ -6,19 +6,39 @@
     <!-- 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="logo-and-lang">
+           <img src="@/assets/logo.png" alt="Logo" class="logo" />
+
+           <!--语言切换按钮(左上方,logo右侧)-->
+           <div class="language-toggle-wrapper" :class="{ 'light-text':isSpecialBg}">
+             <span class="text-toggle" @click="toggleLanguage">
+               {{ currentLanguageName }}
+             </span>
+           </div>
+        </div>
+       
+
         <div class="title-and-user">
           <span class="project-name" :class="{ 'light-text': isSpecialBg }">
-            区域土壤重金属污染风险评估
+            <!--i18n:Title-->{{ $t("Title")}}
           </span>
 
           <!-- 用户信息 - 在select-city页面隐藏 -->
           <div class="user-info-row" v-if="!isSelectCity">
-            <span class="welcome-text" :class="{ 'light-text': isSpecialBg }">
-              欢迎{{
-                tokenStore.token.loginType === "admin" ? "管理员" : "用户"
-              }}登录成功
-            </span>
+           <span class="welcome-text" :class="{ 'light-text': isSpecialBg }">
+          <!-- 欢迎语部分 -->
+          <span :class="getLangClass($t('login.welcome'))">
+            {{ $t("login.welcome") }}
+          </span>
+          <!-- 角色名部分(管理员/用户) -->
+           <span :class="getLangClass(roleText)">
+             {{ roleText }}
+           </span>
+           <!-- 登录成功提示部分 -->
+           <span :class="getLangClass($t('login.loginSuccess'))">
+             {{ $t("login.loginSuccess") }}
+           </span>
+          </span>
             <el-dropdown>
               <span class="el-dropdown-link">
                 <el-avatar :size="40" :class="{ 'light-avatar-border': isSpecialBg }"
@@ -26,8 +46,8 @@
               </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>{{ $t("login.userName")}}{{ userInfo.name }}</el-dropdown-item>
+                  <el-dropdown-item divided @click="handleLogout">{{ $t("login.exitlogin")}}</el-dropdown-item>
                 </el-dropdown-menu>
               </template>
             </el-dropdown>
@@ -42,13 +62,17 @@
         <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>
+            <span class="tab-label-text"
+            :class="locale === 'en'?'en-label':'zh-label'"
+            >{{ 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>
+        <span class="tab-label-text"
+        :class="locale === 'en' ?'en-label':'zh-label'"
+        >{{ tabs[0].label }}</span>
       </div>
     </div>
 
@@ -77,12 +101,26 @@ import { useRouter, useRoute } from "vue-router";
 import { useTokenStore } from "@/stores/mytoken";
 import { ElMessageBox, ElMessage } from "element-plus";
 import { logout } from "@/API/users";
+import { useI18n } from "vue-i18n";
+const { t }=useI18n();
 
+const {locale}=useI18n();//获取i18n实例
 const router = useRouter();
 const route = useRoute();
 const tokenStore = useTokenStore();
 const currentBgImage = ref("");
 
+//当前语言名称
+const currentLanguageName = computed(()=>{
+  return locale.value ==="zh"?"English":"中文";
+})
+
+const toggleLanguage = ()=>{
+  locale.value = locale.value === "zh"?"en":"zh";
+  localStorage.setItem("lang",locale.value);
+  ElMessage.success(`已切换为${locale.value === 'zh' ?'中文' :'英文'}`)
+}
+
 // 定义需要背景图的特殊路由和对应图片文件名
 const bgRouteMap: Record<string, string> = {
   // 添加灌溉水相关页面的背景图
@@ -163,7 +201,7 @@ const tabs = computed(() => {
     return [
       {
         name: "dataManagement",
-        label: "数据管理",
+        label: t("Menu.dataManagement"),//数据管理
         icon: "el-icon-folder",
         routes: [
           "/soilAcidReductionData",
@@ -179,13 +217,13 @@ const tabs = computed(() => {
       },
       {
         name: "infoManagement",
-        label: "信息管理",
+        label: t("Menu.infoManagement"),//信息管理
         icon: "el-icon-document",
         routes: ["/IntroductionUpdate"],
       },
       {
         name: "modelManagement",
-        label: "模型管理及配置",
+        label: t("Menu.modelManagement"),//模型管理及配置
         icon: "el-icon-cpu",
         routes: [
           "/CadmiumPredictionModel",
@@ -200,7 +238,7 @@ const tabs = computed(() => {
       },
       {
         name: "userManagement",
-        label: "用户管理",
+        label: t("Menu.userManagement"),//用户管理
         icon: "el-icon-user",
         routes: ["/UserManagement", "/UserRegistration"],
       },
@@ -209,19 +247,19 @@ const tabs = computed(() => {
     return [
       {
         name: "shuJuKanBan",
-        label: "数据看板",
+        label: t("i18n:Menu.shuJuKanBan"),//数据看板
         icon: "el-icon-data-analysis",
         routes: ["/shuJuKanBan"],
       },
       {
         name: "introduction",
-        label: "软件简介",
+        label: t("i18n:Menu.introduction"),//软件简介
         icon: "el-icon-info-filled",
         routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"],
       },
       {
         name: "HmOutFlux",
-        label: "重金属输入通量",
+        label: t("Menu.HmOutFlux"),//重金属输入通量
         icon: "el-icon-refresh",
         routes: [
           "/samplingMethodDevice1",
@@ -238,7 +276,7 @@ const tabs = computed(() => {
       },
       {
         name: "hmInFlux",
-        label: "重金属输出通量",
+        label: t("Menu.hmInFlux"),//重金属输出通量
         icon: "el-icon-refresh",
         routes: [
           "/samplingDesc1",
@@ -253,13 +291,13 @@ const tabs = computed(() => {
       },
       {
         name: "mapView",
-        label: "地图展示",
+        label: t("Menu.mapView"),//地图展示
         icon: "el-icon-map-location",
         routes: ["/mapView"],
       },
       {
         name: "cadmiumPrediction",
-        label: "土壤污染物含量预测",
+        label: t("Menu.cadmiumPrediction"),//土壤污染物含量预测
         icon: "el-icon-c-scale-to-original",
         routes: [
           "/totalInputFlux",
@@ -273,25 +311,25 @@ const tabs = computed(() => {
       },
       {
         name: "cropRiskAssessment",
-        label: "作物风险评估",
+        label: t("Menu.cropRiskAssessment"),//作物风险评估
         icon: "el-icon-warning",
         routes: ["/cropRiskAssessment"],
       },
       {
         name: "farmlandQualityAssessment",
-        label: "耕地质量评估",
+        label:t("Menu.farmlandQualityAssessment"),//耕地质量评估
         icon: "el-icon-rank",
         routes: ["/farmlandQualityAssessment"],
       },
       {
         name: "soilAcidificationPrediction",
-        label: "土壤酸化预测",
+        label: t("Menu.soilAcidificationPrediction"),//土壤酸化预测
         icon: "el-icon-magic-stick",
         routes: ["/Calculation", "/AcidNeutralizationModel"],
       },
       {
         name: "scenarioSimulation",
-        label: "情景模拟",
+        label: t("Menu.scenarioSimulation"),//情景模拟
         icon: "el-icon-s-operation",
         routes: [
           "/TraditionalFarmingRisk",
@@ -301,7 +339,7 @@ const tabs = computed(() => {
       },
       {
         name: "dataStatistics",
-        label: "数据统计",
+        label: t("Menu.dataStatistics"),//数据统计
         icon: "el-icon-pie-chart",
         routes: [
           "/DetectionStatistics",
@@ -390,20 +428,29 @@ const mainStyle = computed(() => {
     overflow: "hidden",
   };
 });
-</script>
 
+// 新增:判断文本是否为中文,返回对应样式类
+const getLangClass = (text: string) => {
+  // 匹配中文Unicode范围(\u4e00-\u9fa5)
+  const isChinese = /[\u4e00-\u9fa5]/.test(text);
+  return isChinese ? 'welcome-chinese' : 'welcome-english';
+};
+
+// 新增:角色名文本(建议用i18n翻译,避免硬编码)
+const roleText = computed(() => {
+  return tokenStore.token.loginType === "admin" 
+    ? t('role.admin')  // 中文"管理员",英文"Admin"(需在i18n配置)
+    : t('role.user');  // 中文"用户",英文"User"(需在i18n配置)
+});
+</script>
 <style>
 /* 隐藏所有滚动条 */
 *::-webkit-scrollbar {
   display: none;
-  /* Chrome, Safari, Opera */
 }
-
 * {
   -ms-overflow-style: none;
-  /* IE and Edge */
   scrollbar-width: none;
-  /* Firefox */
 }
 
 /* 整体布局容器 */
@@ -413,11 +460,10 @@ const mainStyle = computed(() => {
   height: 100vh;
   overflow: hidden;
   position: relative;
-  background-color: #f5f7fa;
-  /* 默认背景色 */
+  background-color: transparent;
 }
 
-/* 背景层 - 关键修改 */
+/* 背景层 */
 .background-layer {
   position: absolute;
   top: 0;
@@ -437,32 +483,6 @@ const mainStyle = computed(() => {
   min-height: 100vh;
 }
 
-/* 特殊背景页面的Header样式 */
-.transparent-header {
-  background: transparent !important;
-  backdrop-filter: blur(2px);
-  /* 添加轻微模糊效果增强可读性 */
-}
-
-/* 特殊背景页面的文字颜色 */
-.light-text {
-  color: #ffffff !important;
-  /* 白色文字 */
-  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
-  /* 文字阴影增强可读性 */
-}
-
-/* 特殊背景页面的头像边框 */
-.light-avatar-border {
-  border: 2px solid #ffffff !important;
-  /* 白色边框 */
-}
-
-/* 内容区域在特殊背景页面透明 */
-.transparent-scroll {
-  background-color: transparent !important;
-}
-
 /* Header 样式 */
 .layout-header {
   height: 150px;
@@ -470,64 +490,95 @@ const mainStyle = computed(() => {
   align-items: center;
   justify-content: space-between;
   color: #333;
-  /* 深色文字 */
   flex-shrink: 0;
   position: relative;
-  z-index: 2;
-  /* 确保在背景层上方 */
-  background-color: white;
-  /* 默认背景色 */
+  z-index: 1;
+  background-color: transparent;
 }
 
 .logo-title-row {
   display: flex;
   align-items: center;
-  gap: 24px;
+  gap: 40px;
   width: 100%;
 }
 
+.logo-and-lang {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+}
+
 .title-and-user {
   display: flex;
   flex-direction: column;
   flex: 1;
 }
 
-/* 用户信息区域 */
 .user-info-row {
   display: flex;
   align-items: center;
   justify-content: flex-end;
   gap: 24px;
   color: #333;
-  /* 深色文字 */
   padding-top: 1px;
   position: static;
   z-index: 1;
 }
 
-.welcome-text {
-  font-size: 28px;
-  font-weight: 500;
-  color: #333 !important;
-  /* 深色文字 */
+/* 欢迎文本-中文样式 */
+.welcome-text .welcome-chinese {
+  font-family: "Microsoft YaHei", "SimHei", sans-serif; /* 中文字体 */
+  font-size: 28px; /* 中文字号 */
+  margin: 0 2px; /* 与其他片段的间距 */
+}
+
+/* 欢迎文本-英文样式 */
+.welcome-text .welcome-english {
+  font-family: "Arial", "Helvetica", sans-serif; /* 英文字体 */
+  font-size: 24px; /* 英文字号(可与中文不同) */
+  letter-spacing: 0.5px; /* 英文字母间距优化 */
+  margin: 0 2px; /* 与其他片段的间距 */
 }
 
-/* Tab 区域 - 不透明 */
+/* 特殊背景下的浅色文本兼容 */
+.welcome-text.light-text .welcome-chinese,
+.welcome-text.light-text .welcome-english {
+  color: #fff; /* 保持与原light-text的颜色一致 */
+}
+
+/* Tab 区域核心修复 */
 .tabs-row {
   height: 48px;
   display: flex;
-  justify-content: center;
   align-items: center;
-  padding: 0 24px;
+  padding: 0 12px;
   background: linear-gradient(to right, #1092d8, #02c3ad);
   border-bottom: none !important;
   flex-shrink: 0;
   position: relative;
   z-index: 2;
-  /* 确保在背景层上方 */
+  overflow-x: auto; /* 外层滚动容器 */
+  overflow-y: hidden;
+}
+
+/* 隐藏 Tab 滚动条 */
+.tabs-row::-webkit-scrollbar {
+  display: none;
+}
+
+/* 修复 Element Plus Tabs 内部滚动 */
+.el-tabs__nav-scroll {
+  overflow-x: auto !important;
+  overflow-y: hidden !important;
+  width: 100% !important;
+}
+.el-tabs__nav {
+  flex-wrap: nowrap !important;
+  width: auto !important;
 }
 
-/* el-tabs 外层容器 */
 .demo-tabs {
   height: 48px !important;
   display: flex;
@@ -538,37 +589,29 @@ const mainStyle = computed(() => {
   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;
+  padding: 0 12px !important; /* 更小内边距 */
+  min-width: auto !important;
+  font-size: 14px; /* 统一字体大小 */
   font-weight: 600;
   border-radius: 10px;
   transition: all 0.2s ease-in-out;
   background-color: transparent;
   position: relative;
   z-index: 1;
+  white-space: nowrap;
 }
 
-/* 激活 Tab */
 .el-tabs__item.is-active {
   background-color: #2a53ba;
   color: #ffffff;
@@ -577,25 +620,52 @@ const mainStyle = computed(() => {
   z-index: 2;
 }
 
-/* 鼠标悬停 */
 .el-tabs__item:hover {
   background-color: #455a64;
   color: #ffffff;
 }
 
-/* 图标样式 */
 .tab-icon {
   font-size: 24px;
   margin-right: 4px;
   color: inherit;
 }
 
-/* 文字样式 */
+/* 语言切换 */
+.language-toggle-wrapper {
+  margin: 0 20px;
+  display: flex;
+  align-items: center;
+}
+
+.text-toggle {
+  color: black;
+  font-size: 16px;
+  cursor: pointer;
+  text-decoration: underline;
+  transition: color 0.3s;
+  white-space: nowrap;
+}
+
+/* 标签文字样式 */
 .tab-label-text {
-  font-size: 20px;
-  color: inherit;
-  line-height: 1;
-  display: inline-block;
+  white-space: nowrap !important;
+  overflow: hidden !important;
+  text-overflow: ellipsis;
+}
+
+.zh-label {
+  font-size: 20px; 
+  color: black; 
+  font-weight: 1000;
+}
+
+.en-label {
+  font-size: 14px; 
+  color: black; 
+  letter-spacing: 0.2px; 
+  font-weight: 500;
+  white-space: nowrap;
 }
 
 .logo {
@@ -607,7 +677,6 @@ const mainStyle = computed(() => {
   font-weight: bold;
   margin-top: 30px;
   color: #333;
-  /* 深色文字 */
 }
 
 .layout-main-container {
@@ -617,10 +686,8 @@ const mainStyle = computed(() => {
   min-height: 0;
   position: relative;
   z-index: 1;
-  /* 确保在背景层上方 */
 }
 
-/* 侧边栏 - 白色背景 */
 .layout-aside {
   width: 360px;
   background: linear-gradient(to bottom, #B7F1FC, #FFF8F0);
@@ -631,23 +698,31 @@ const mainStyle = computed(() => {
   height: 100%;
   position: relative;
   z-index: 2;
-  /* 确保在背景层上方 */
 }
 
-/* 隐藏侧边栏滚动条 */
 .layout-aside::-webkit-scrollbar {
   display: none;
 }
 
-.layout-aside .el-menu-item,
+.layout-aside .el-menu-item {
+  font-size: 16px;
+  font-weight: 500; /* 可选:调整粗细 */
+  color: #333; /* 可选:调整颜色 */
+  /* 其他公共样式(如 padding、过渡效果 可保留) */
+  background-color: transparent;
+  transition: all 0.2s ease;
+  border-radius: 6px;
+  padding: 12px 16px !important;
+}
 .layout-aside .el-sub-menu__title {
-  font-size: 18px;
+  font-size: 15px;
   font-weight: 500;
   color: #000000;
   background-color: transparent;
   transition: all 0.2s ease;
   border-radius: 6px;
   padding: 12px 16px !important;
+  letter-spacing: -1px;
 }
 
 .layout-aside .el-menu-item:hover,
@@ -673,7 +748,6 @@ const mainStyle = computed(() => {
   position: relative;
 }
 
-/* 强制重置 el-tabs header 高度/边距/背景/阴影,避免背景层穿透错位 */
 .el-tabs__header.is-top {
   height: 48px !important;
   margin: 0 !important;
@@ -684,7 +758,6 @@ const mainStyle = computed(() => {
   z-index: 0 !important;
 }
 
-/* 全屏页面特殊处理 */
 .layout-wrapper.full-screen .layout-main-container {
   height: 100vh;
 }
@@ -694,8 +767,7 @@ const mainStyle = computed(() => {
   overflow: auto;
   padding: 0 20px;
   box-sizing: border-box;
-  background-color: white;
-  /* 默认背景色 */
+  background-color: transparent;
 }
 
 .scrollable-content.transparent-scroll {

+ 52 - 52
src/components/layout/menuItems.ts

@@ -31,61 +31,61 @@ import {
   export const menuItems: MenuItem[] = [
     {
       index: '/shuJuKanBan',
-      label: '数据看板',
+      label: 'shuJuKanBan.Title',//<!--i18n:shuJuKanBan.Title-->数据看板
       icon: Monitor,
      tab: 'shuJuKanBan'
     },
     {
       index: '/SoilPro',
-      label: '软件简介',
+      label: 'SoilPro.Title',//<!--i18n:SoilPro.Title-->软件简介
       icon: InfoFilled,
       tab: 'introduction'
     },
     {
       index: '/Overview',
-      label: '项目简介',
+      label: 'Overview.Title',//<!--i18n:Overview.Title-->项目简介
       icon: Collection,
       tab: 'introduction'
     },
     {
       index: '/ResearchFindings',
-      label: '研究成果',
+      label: 'ResearchFindings.Title',//<!--i18n:ResearchFindings.Title-->研究成果
       icon: Histogram,
       tab: 'introduction'
     },
     {
       index: '/Unit',
-      label: '团队信息',
+      label: 'Unit.Title',//<!--i18n:Unit.Title-->团队信息
       icon: HelpFilled,
       tab: 'introduction'
     },
     {
       index: 'irrigationWater',
-      label: '灌溉水',
+      label: 'irrigationwater.Title',//灌溉水
       icon: Watermelon,
       tab: 'HmOutFlux',
       children: [
         {
           index: '/samplingMethodDevice1',
-          label: '采样方法和装置',
+          label: 'irrigationwater.irrigationwaterMethodsTitle',
           icon: Sunny,
           tab: 'HmOutFlux'
         },
         {
           index: '/irriSampleData',
-          label: '灌溉水采样数据',
+          label: 'irrigationwater.pointTitle',
           icon: Coin,
           tab: 'HmOutFlux'
         },
         {
           index: '/csSampleData',
-          label: '断面采样数据',
+          label: 'irrigationwater.crosssectionTitle',
           icon: Cloudy,
           tab: 'HmOutFlux'
         },
         {
           index: '/irriInputFlux',
-          label: '灌溉水输入通量',
+          label: 'irrigationwater.InputfluxTitle',
           icon: Cloudy,
           tab: 'HmOutFlux'
         }
@@ -93,19 +93,19 @@ import {
     },
     {
       index: 'inputFlux',
-      label: '农产品投入',
+      label: 'agriInput.Title',//农产品投入
       icon: Watermelon,
       tab: 'HmOutFlux',
       children: [
         {
           index: '/farmInputSamplingDesc',
-          label: '采样说明',
+          label: 'agriInput.farmInputSamplingDescTitle',//采样说明
           icon: Sunny,
           tab: 'HmOutFlux'
         },
         {
           index: '/prodInputFlux',
-          label: '农产品输入通量',
+          label: 'agriInput.prodInputFluxTitle',//农产品输入通量
           icon: Coin,
           tab: 'HmOutFlux'
         },
@@ -113,31 +113,31 @@ import {
     },
     {
       index: 'atmosDeposition',
-      label: '大气干湿沉降',
+      label: 'atmosDeposition.Title',//大气干湿沉降
       icon: Watermelon,
       tab: 'HmOutFlux',
       children: [
         {
           index: '/AtmosDepositionSamplingDesc',
-          label: '采样说明',
+          label: 'atmosDeposition.AtmosDepositionSamplingDescTitle',//采样说明
           icon: Sunny,
           tab: 'HmOutFlux'
         },
         {
           index: '/heavyMetalEnterprise',
-          label: '涉重企业',
+          label: 'atmosDeposition.heavyMetalEnterpriseTitle',//涉重企业
           icon: Coin,
           tab: 'HmOutFlux'
         },
         {
           index: '/airSampleData',
-          label: '大气采样数据',
+          label: 'atmosDeposition.airSampleDataTitle',//大气采样数据
           icon: Sunny,
           tab: 'HmOutFlux'
         },
         {
           index: '/airInputFlux',
-          label: '大气输入通量',
+          label: 'atmosDeposition.airInputFluxTitle',//大气输入通量
           icon: Coin,
           tab: 'HmOutFlux'
         },
@@ -145,19 +145,19 @@ import {
     },
     {
       index: 'grainRemoval',
-      label: '籽粒移除',
+      label: 'grainRemoval.Title',//<!--i18n:grainRemoval.Title-->籽粒移除
       icon: WindPower,
       tab: 'hmInFlux',
       children: [
         {
           index: '/samplingDesc1',
-          label: '采样说明',
+          label: 'grainRemoval.samplingDesc1',//<!--i18n:grainRemoval.samplingDesc1-->采样说明
           icon: Watermelon,
           tab: 'hmInFlux'
         },
         {
           index: '/grainRemovalInputFlux',
-          label: '籽粒移除输出通量',
+          label: 'grainRemoval.grainRemovalInputFlux',//<!--i18n:grainRemoval.grainRemovalInputFlux-->籽粒移除输出通量
           icon: List,
           tab: 'hmInFlux'
         }
@@ -165,19 +165,19 @@ import {
     },
     {
       index: 'strawRemoval',
-      label: '秸秆移除',
+      label: 'strawRemoval.Title',//<!--i18n:strawRemoval.Title-->秸秆移除
       icon: WindPower,
       tab: 'hmInFlux',
       children: [
         {
           index: '/samplingDesc2',
-          label: '采样说明',
+          label: 'strawRemoval.samplingDesc2',//<!--i18n:strawRemoval.samplingDesc2-->采样说明
           icon: Watermelon,
           tab: 'hmInFlux'
         },
         {
           index: '/strawRemovalInputFlux',
-          label: '秸秆移除输出通量',
+          label: 'strawRemoval.strawRemovalInputFlux',//<!--i18n:strawRemoval.strawRemovalInputFlux-->秸秆移除输出通量
           icon: List,
           tab: 'hmInFlux'
         }
@@ -185,19 +185,19 @@ import {
     },
     {
       index: 'subsurfaceLeakage',
-      label: '地下渗漏',
+      label: 'subsurfaceLeakage.Title',//<!--i18n:subsurfaceLeakage.Title-->地下渗漏
       icon: WindPower,
       tab: 'hmInFlux',
       children: [
         {
           index: '/samplingDesc3',
-          label: '采样说明',
+          label: 'subsurfaceLeakage.samplingDesc3',//<!--i18n:subsurfaceLeakage.samplingDesc3-->采样说明
           icon: Watermelon,
           tab: 'hmInFlux'
         },
         {
           index: '/subsurfaceLeakageInputFlux',
-          label: '地下渗漏输入通量',
+          label: 'subsurfaceLeakage.subsurfaceLeakageInputFlux',//<!--i18n:subsurfaceLeakage.subsurfaceLeakageInputFlux-->地下渗漏输入通量
           icon: List,
           tab: 'hmInFlux'
         }
@@ -205,19 +205,19 @@ import {
     },
     {
       index: 'surfaceRunoff',
-      label: '地表径流',
+      label: 'surfaceRunoff.Title',//<!--i18n:surfaceRunoff.Title-->地表径流
       icon: WindPower,
       tab: 'hmInFlux',
       children: [
         {
           index: '/samplingDesc4',
-          label: '采样说明',
+          label: 'surfaceRunoff.samplingDesc4',//<!--i18n:surfaceRunoff.samplingDesc4-->采样说明
           icon: Watermelon,
           tab: 'hmInFlux'
         },
         {
           index: '/surfaceRunoffInputFlux',
-          label: '地表径流输入通量',
+          label: 'surfaceRunoff.surfaceRunoffInputFlux',//<!--i18n:surfaceRunoff.surfaceRunoffInputFlux-->地表径流输入通量
           icon: List,
           tab: 'hmInFlux'
         }
@@ -225,79 +225,79 @@ import {
     },
     {
       index: '/mapView',
-      label: '地图展示',
+      label: 'mapView.Title',//<!--i18n:mapView.Title-->地图展示
       icon: Location,
       tab: 'mapView'
     },
     {
       index: '/totalInputFlux',
-      label: '输入总通量',
+      label: 'totalInputFlux.Title',//<!--i18n:totalInputFlux.Title-->输入总通量
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/totalOutputFlux',
-      label: '输出总通量',
+      label: 'totalOutputFlux.Title',//<!--i18n:totalOutputFlux.Title-->输出总通量
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/netFlux',
-      label: '净通量',
+      label: 'netFlux.Title',//<!--i18n:netFlux.Title-->净通量
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/currentYearConcentration',
-      label: '当年浓度',
+      label: 'currentYearConcentration.Title',//<!--i18n:currentYearConcentration.Title-->当年浓度
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/TotalCadmiumPrediction',
-      label: '土壤镉的总含量预测',
+      label: 'TotalCadmiumPrediction.Title',//<!--i18n:TotalCadmiumPrediction.Title-->土壤镉的总含量预测
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/EffectiveCadmiumPrediction',
-      label: '土壤镉有效态含量预测',
+      label: 'EffectiveCadmiumPrediction.Title',//<!--i18n:EffectiveCadmiumPrediction.Title-->土壤镉有效态含量预测
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/CropCadmiumPrediction',
-      label: '土壤镉作物态含量预测',
+      label: 'CropCadmiumPrediction.Title',//<!--i18n:CropCadmiumPrediction.Title-->土壤镉作物态含量预测
       icon: PieChart,
       tab: 'cadmiumPrediction'
     },
     {
       index: '/cropRiskAssessment',
-      label: '水稻镉污染风险',
+      label: 'cropRiskAssessment.Title',//<!--i18n:cropRiskAssessment.Title-->水稻镉污染风险
       icon: Compass,
       tab: 'cropRiskAssessment'
     },
     {
       index: '/farmlandQualityAssessment',
-      label: '韶关',
+      label: 'farmlandQualityAssessment.Title',//<!--i18n:farmlandQualityAssessment.Title-->韶关
       icon: DataLine,
       tab: 'farmlandQualityAssessment'
     },
     {
       index: '/acidModel',
-      label: '土壤反酸',
+      label: 'acidModel.Title',//<!--i18n:acidModel.Title-->土壤反酸
       icon: MagicStick,
       tab: 'soilAcidificationPrediction',
       children: [
         {
           index: '/Calculation',
-          label: '土壤反酸预测',
+          label: 'acidModel.CalculationTitle',//<!--i18n:acidModel.CalculationTitle-->土壤反酸预测
           icon: Sunny,
           tab: 'heavyMetalFluxCalculation'
         },
         {
           index: '/SoilAcidReductionIterativeEvolution',
-          label: '反酸模型迭代可视化',
+          label: 'acidModel.SoilAcidReductionIterativeEvolutionTitle',//<!--i18n:acidModel.SoilAcidReductionIterativeEvolutionTitle-->反酸模型迭代可视化
           icon: Coin,
           tab: 'heavyMetalFluxCalculation'
         }
@@ -305,19 +305,19 @@ import {
     },
     {
       index: '/neutralizationModel',
-      label: '土壤降酸',
+      label: 'neutralizationModel.Title',//<!--i18n:neutralizationModel.Title-->土壤降酸
       icon: MagicStick,
       tab: 'soilAcidificationPrediction',
       children: [
         {
           index: '/AcidNeutralizationModel',
-          label: '土壤降酸预测',
+          label: 'neutralizationModel.AcidNeutralizationModelTitle',//<!--i18n:neutralizationModel.AcidNeutralizationModelTitle-->土壤降酸预测
           icon: Sunny,
           tab: 'heavyMetalFluxCalculation'
         },
         {
           index: '/SoilAcidificationIterativeEvolution',
-          label: '土壤降酸预测',
+          label: 'neutralizationModel.SoilAcidificationIterativeEvolutionTitle',//<!--i18n:neutralizationModel.SoilAcidificationIterativeEvolutionTitle-->土壤降酸可视化
           icon: Coin,
           tab: 'heavyMetalFluxCalculation'
         }
@@ -325,37 +325,37 @@ import {
     },
     {
       index: '/TraditionalFarmingRisk',
-      label: '传统耕种习惯风险趋势',
+      label: 'TraditionalFarmingRisk.Title',//<!--i18n:TraditionalFarmingRisk.Title-->传统耕种习惯风险趋势
       icon: MenuIcon,
       tab: 'scenarioSimulation'
     },
     {
       index: '/HeavyMetalCadmiumControl',
-      label: '重金属镉污染治理',
+      label: 'HeavyMetalCadmiumControl.Title',//<!--i18n:HeavyMetalCadmiumControl.Title-->重金属镉污染治理
       icon: MenuIcon,
       tab: 'scenarioSimulation'
     },
     {
       index: '/SoilAcidificationControl',
-      label: '土壤酸化治理',
+      label: 'SoilAcidificationControl.Title',//<!--i18n:SoilAcidificationControl.Title-->土壤酸化治理
       icon: MenuIcon,
       tab: 'scenarioSimulation'
     },
     {
       index: '/DetectionStatistics',
-      label: '检测信息统计',
+      label: 'DetectionStatistics.Title',//<!--i18n:DetectionStatistics.Title-->检测信息统计
       icon: List,
       tab: 'dataStatistics'
     },
     {
       index: '/FarmlandPollutionStatistics',
-      label: '耕地污染信息统计',
+      label: 'FarmlandPollutionStatistics.Title',//<!--i18n:FarmlandPollutionStatistics.Title-->耕地污染信息统计
       icon: List,
       tab: 'dataStatistics'
     },
     {
       index: '/PlantingRiskStatistics',
-      label: '种植风险信息统计',
+      label: 'PlantingRiskStatistics.Title',//<!--i18n:PlantingRiskStatistics.Title-->种植风险信息统计
       icon: List,
       tab: 'dataStatistics'
     }

+ 6 - 0
src/i18n.d.ts

@@ -0,0 +1,6 @@
+type Translation = (path: string, option?: Record<string, any>) => import('vue').ComputedRef<string>
+interface I18n {
+  locale: string
+  language: Record<string, any>
+  t: Translation
+}

+ 174 - 1
src/locales/en.json

@@ -16,7 +16,14 @@
     "adminTitle": "Admin Login",
     "successMessage": "Login successful",
     "errorMessage": "Login failed",
-    "loginSuccess": "Login successful"
+    "loginSuccess": "Login successful",
+    "welcome": "Welcome ",
+    "userName": "Username",
+    "exitlogin": "Log out"
+  },
+  "role":{
+    "admin":"Admin",
+    "user":"User"
   },
   "register": {
     "title": "Register",
@@ -38,5 +45,171 @@
     "requiredUsername": "Username is required",
     "requiredPassword": "Password is required",
     "requiredConfirmPassword": "Confirm password is required"
+  },
+  "irrigationwater": {
+    "Title": "irrigationwater",
+    "irrigationwaterMethodsTitle": "Sampling method and device",
+    "irrigationwaterMethods": {
+      "title1": "1. Sampling container and process",
+      "content1": "The sampling containers are all 500mL white polyethylene bottles, the sampling volume is 500mL, the sampling process is carried out under different weather conditions, the main weather conditions include cloudy, cloudy and light rain, the surrounding environment of the sampling point is mainly rivers, and only a few samples are collected in the canal or waterfall area.",
+      "photo1": "Figure 1-1 Sampling container",
+      "photo2": "Fig.1-2 Sampling site",
+      "photo3": "Fig.1-3 Irrigation water sampling equipment",
+      "title2": "2. Sample preservation and on-site conditions",
+      "content2": "In order to ensure the integrity of the samples and the accuracy of the data, the storage methods after sampling include refrigeration, protection from light, ensuring that the label is intact and taking effective shock absorption measures to avoid vibration and damage during transportation.",
+      "photo4": "Figure 2-1 Sampling site",
+      "photo5": "Figure 2-2 Sampling site",
+      "photo6": "Figure 2-3 Sampling site"
+    },
+    "pointTitle": "Irrigation water sampling data",
+    "point": {
+      "pointMapTitle": "Sampling point map",
+      "pointLineTitle": "The data list of sampling points",
+      "pointChartTitle": "The average concentration of heavy metals in various districts and counties of Shaoguan City"
+    },
+    "crosssectionTitle": "Cross-sectional sampling data",
+    "crosssection": {
+      "crosssectionMapTitle": "Section data map",
+      "crosssectionLineTitle": "Section data details",
+      "crosssectionRiverChartTitle": "Histogram of the average concentration of Cd in each river",
+      "crosssectionCityChartTitle": "Histogram of the average concentration of Cd in each district and county"
+    },
+    "InputfluxTitle": "Irrigation water input flux"
+  },
+  "atmosDeposition": {
+    "Title": "Atmospheric dry and wet deposition",
+    "AtmosDepositionSamplingDescTitle": "Sampling instructions",
+    "heavyMetalEnterpriseTitle": "Involved in heavy enterprises",
+    "heavyMetalEnterprise": {
+      "MapTitle": "The map of heavy enterprises",
+      "LineTitle": "The list of data of important enterprises",
+      "ChartTitle": "Average atmospheric particulate matter emissions of enterprises in various districts and counties(t/a)"
+    },
+    "airSampleDataTitle": "Atmospheric sampling data",
+    "airSampleData": {
+      "button1": "Sampling method:",
+      "button2": "Sampling by weight",
+      "button3": "Sampling by volume",
+      "MapTitle": "Sampling point map",
+      "LineTitle": "The data list of sampling points",
+      "ChartTitle": "Histogram of average atmospheric heavy metal pollution in each district and county"
+    },
+    "airInputFluxTitle": "Atmospheric input flux"
+  },
+  "agriInput": {
+    "Title": "Agricultural product input",
+    "farmInputSamplingDescTitle": "Sampling instructions",
+    "prodInputFluxTitle": "Agricultural input flux"
+  },
+  "Title": "Regional soil heavy metal pollution risk assessment",
+  "Menu": {
+    "dataManagement": "Data management",
+    "infoManagement": "Information management",
+    "modelManagement": "Model management and configuration",
+    "userManagement": "User management",
+    "shuJuKanBan": "Data dashboard",
+    "introduction": "Software Introduction",
+    "HmOutFlux": "Heavy metal input flux",
+    "hmInFlux": "Heavy metal output flux",
+    "mapView": "Map display",
+    "cadmiumPrediction": "Soil pollutant content prediction",
+    "cropRiskAssessment": "Crop risk assessment",
+    "farmlandQualityAssessment": "Cultivated land quality assessment",
+    "soilAcidificationPrediction": "Soil acidification prediction",
+    "scenarioSimulation": "Scenario simulation",
+    "dataStatistics": "Statistics"
+  },
+  "shuJuKanBan": {
+    "Title": "Data dashboard"
+  },
+  "SoilPro": {
+    "Title": "Software Introduction"
+  },
+  "Overview": {
+    "Title": "Project Introduction"
+  },
+  "ResearchFindings": {
+    "Title": "Research results"
+  },
+  "Unit": {
+    "Title": "Team information"
+  },
+  "grainRemoval": {
+    "Title": "Grain removal",
+    "samplingDesc1": "Sampling instructions",
+    "grainRemovalInputFlux": "Grain removal output flux"
+  },
+  "strawRemoval": {
+    "Title": "Straw removal",
+    "samplingDesc2": "Sampling instructions",
+    "strawRemovalInputFlux": "Straw removal output flux"
+  },
+  "subsurfaceLeakage": {
+    "Title": "Underground leakage",
+    "samplingDesc3": "Sampling instructions",
+    "subsurfaceLeakageInputFlux": "Underground seepage input flux"
+  },
+  "surfaceRunoff": {
+    "Title": "Surface runoff",
+    "samplingDesc4": "Sampling instructions",
+    "surfaceRunoffInputFlux": "Surface runoff input flux"
+  },
+  "mapView": {
+    "Title": "Map display"
+  },
+  "totalInputFlux": {
+    "Title": "Enter the total flux"
+  },
+  "totalOutputFlux": {
+    "Title": "Output total throughput"
+  },
+  "netFlux": {
+    "Title": "Net Flux"
+  },
+  "currentYearConcentration": {
+    "Title": "Concentration of the year"
+  },
+  "TotalCadmiumPrediction": {
+    "Title": "Prediction of total soil cadmium content"
+  },
+  "EffectiveCadmiumPrediction": {
+    "Title": "Prediction of available content of cadmium in soil"
+  },
+  "CropCadmiumPrediction": {
+    "Title": "Prediction of soil cadmium crop content"
+  },
+  "cropRiskAssessment": {
+    "Title": "Risk of cadmium contamination in rice"
+  },
+  "farmlandQualityAssessment": {
+    "Title": "Shaoguan"
+  },
+  "acidModel": {
+    "Title": "Soil acid reflux",
+    "CalculationTitle": "Soil acid reflux prediction",
+    "SoilAcidReductionIterativeEvolutionTitle": "Acid reflux model iteration visualization"
+  },
+  "neutralizationModel": {
+    "Title": "Soil acid reduction",
+    "AcidNeutralizationModelTitle": "Soil acid reduction prediction",
+    "SoilAcidificationIterativeEvolutionTitle": "Soil acidification evolves iteratively"
+  },
+  "TraditionalFarmingRisk": {
+    "Title": "Risk trends of traditional farming habits"
+  },
+  "HeavyMetalCadmiumControl": {
+    "Title": "Heavy metal cadmium pollution control"
+  },
+  "SoilAcidificationControl": {
+    "Title": "Soil acidification control"
+  },
+  "DetectionStatistics": {
+    "Title": "Detection information statistics"
+  },
+  "FarmlandPollutionStatistics": {
+    "Title": "Statistics of cultivated land pollution information"
+  },
+  "PlantingRiskStatistics": {
+    "Title": "Planting risk information statistics"
   }
 }

+ 175 - 1
src/locales/zh.json

@@ -1,4 +1,8 @@
 {
+  "role":{
+    "admin":"管理员",
+    "user":"用户"
+  },
   "login": {
     "title": "登录",
     "username": "账号",
@@ -16,7 +20,10 @@
     "adminTitle": "管理员登录",
     "successMessage": "登录成功",
     "errorMessage": "登录失败",
-    "loginSuccess": "登录成功"
+    "loginSuccess": "登录成功",
+    "welcome": "欢迎",
+    "userName": "用户名",
+    "exitlogin": "退出登录"
   },
   "register": {
     "title": "注册",
@@ -38,5 +45,172 @@
     "requiredUsername": "账号为必填项",
     "requiredPassword": "密码为必填项",
     "requiredConfirmPassword": "确认密码为必填项"
+  },
+  "irrigationwater": {
+    "Title": "灌溉水",
+    "irrigationwaterMethodsTitle": "采样方法和装置",
+    "irrigationwaterMethods": {
+      "title1": "1.采样容器与过程",
+      "content1": "采样容器均为500mL的白色聚乙烯瓶,采样体积均为500mL,采样过程在不同天气条件下进行,主要天气状况包括多云、阴天和小雨,采样点周边环境主要为河流,只有少数样品采集于水渠或瀑布区域。",
+      "photo1": "图1-1 采样容器",
+      "photo2": "图1-2 采样现场",
+      "photo3": "图1-3 灌溉水采样设备",
+      "title2": "2.样品保存与现场情况",
+      "content2": "绝大多数样品状态为无色、无沉淀、无味、无悬浮物,只有少量样品稍显浑浊并含有沉淀物,为了保证样品的完整性和数据的准确性,采样后的保存方式包括了冷藏、避光、确保标签完好以及采取有效的减震措施,以避免运输过程中的振动和损坏。",
+      "photo4": "图2-1 采样现场",
+      "photo5": "图2-2 采样现场",
+      "photo6": "图2-3 采样现场"
+    },
+    "pointTitle": "灌溉水采样数据",
+    "point": {
+      "pointMapTitle": "采样点地图展示",
+      "pointLineTitle": "采样点数据列表展示",
+      "pointChartTitle": "韶关市各区县重金属平均浓度"
+    },
+    "crosssectionTitle": "断面采样数据",
+    "crosssection": {
+      "crosssectionMapTitle": "断面数据地图展示",
+      "crosssectionLineTitle": "断面数据详情",
+      "crosssectionRiverChartTitle": "各河流Cd的平均浓度柱状图",
+      "crosssectionCityChartTitle": "各区县的Cd平均浓度柱状图"
+    },
+    "InputfluxTitle": "灌溉水输入通量"
+  },
+  "atmosDeposition": {
+    "Title": "大气干湿沉降",
+    "AtmosDepositionSamplingDescTitle": "采样说明",
+    "heavyMetalEnterpriseTitle": "涉重企业",
+    "heavyMetalEnterprise": {
+      "MapTitle": "涉重企业地图展示",
+      "LineTitle": "涉重企业数据列表展示",
+      "ChartTitle": "各区县企业平均大气颗粒物排放(t/a)"
+    },
+    "airSampleDataTitle": "大气采样数据",
+    "airSampleData": {
+      "button1": "采样方式:",
+      "button2": "按重量采样",
+      "button3": "按体积采样",
+      "MapTitle": "采样点地图展示",
+      "LineTitle": "采样点数据列表展示",
+      "ChartTitle": "各区县平均大气重金属污染柱状图"
+    },
+    "airInputFluxTitle": "大气输入通量"
+  },
+  "agriInput": {
+    "Title": "农产品投入",
+    "farmInputSamplingDescTitle": "采样说明",
+    "prodInputFluxTitle": "农产品输入通量"
+  },
+  "Title": "区域土壤重金属污染风险评估",
+  "Menu": {
+    "dataManagement": "数据管理",
+    "infoManagement": "信息管理",
+    "modelManagement": "模型管理及配置",
+    "userManagement": "用户管理",
+    "shuJuKanBan": "数据看板",
+    "introduction": "软件简介",
+    "HmOutFlux": "重金属输入通量",
+    "hmInFlux": "重金属输出通量",
+    "mapView": "地图展示",
+    "cadmiumPrediction": "土壤污染物含量预测",
+    "cropRiskAssessment": "作物风险评估",
+    "farmlandQualityAssessment": "耕地质量评估",
+    "soilAcidificationPrediction": "土壤酸化预测",
+    "scenarioSimulation": "情景模拟",
+    "dataStatistics": "数据统计"
+  },
+
+  "shuJuKanBan": {
+    "Title": "数据看板"
+  },
+  "SoilPro": {
+    "Title": "软件简介"
+  },
+  "Overview": {
+    "Title": "项目简介"
+  },
+  "ResearchFindings": {
+    "Title": "研究成果"
+  },
+  "Unit": {
+    "Title": "团队信息"
+  },
+  "grainRemoval": {
+    "Title": "籽粒移除",
+    "samplingDesc1": "采样说明",
+    "grainRemovalInputFlux": "籽粒移除输出通量"
+  },
+  "strawRemoval": {
+    "Title": "秸秆移除",
+    "samplingDesc2": "采样说明",
+    "strawRemovalInputFlux": "秸秆移除输出通量"
+  },
+  "subsurfaceLeakage": {
+    "Title": "地下渗漏",
+    "samplingDesc3": "采样说明",
+    "subsurfaceLeakageInputFlux": "地下渗漏输入通量"
+  },
+  "surfaceRunoff": {
+    "Title": "地表径流",
+    "samplingDesc4": "采样说明",
+    "surfaceRunoffInputFlux": "地表径流输入通量"
+  },
+  "mapView": {
+    "Title": "地图展示"
+  },
+  "totalInputFlux": {
+    "Title": "输入总通量"
+  },
+  "totalOutputFlux": {
+    "Title": "输出总通量"
+  },
+  "netFlux": {
+    "Title": "净通量"
+  },
+  "currentYearConcentration": {
+    "Title": "当年浓度"
+  },
+  "TotalCadmiumPrediction": {
+    "Title": "土壤镉的总含量预测"
+  },
+  "EffectiveCadmiumPrediction": {
+    "Title": "土壤镉有效态含量预测"
+  },
+  "CropCadmiumPrediction": {
+    "Title": "土壤镉作物态含量预测"
+  },
+  "cropRiskAssessment": {
+    "Title": "水稻镉污染风险"
+  },
+  "farmlandQualityAssessment": {
+    "Title": "韶关"
+  },
+  "acidModel": {
+    "Title": "土壤反酸",
+    "CalculationTitle": "土壤反酸预测",
+    "SoilAcidReductionIterativeEvolutionTitle": "反酸模型迭代可视化"
+  },
+  "neutralizationModel": {
+    "Title": "土壤降酸",
+    "AcidNeutralizationModelTitle": "土壤降酸预测",
+    "SoilAcidificationIterativeEvolutionTitle": "土壤降酸迭代可视化"
+  },
+  "TraditionalFarmingRisk": {
+    "Title": "传统耕种习惯风险趋势"
+  },
+  "HeavyMetalCadmiumControl": {
+    "Title": "重金属镉污染治理"
+  },
+  "SoilAcidificationControl": {
+    "Title": "土壤酸化治理"
+  },
+  "DetectionStatistics": {
+    "Title": "检测信息统计"
+  },
+  "FarmlandPollutionStatistics": {
+    "Title": "耕地污染信息统计"
+  },
+  "PlantingRiskStatistics": {
+    "Title": "种植风险信息统计"
   }
 }

+ 1 - 1
src/utils/request.ts

@@ -5,7 +5,7 @@ import { useTokenStore } from '@/stores/mytoken'; // 引入Pinia store
 // 创建 axios 实例
 const request = axios.create({ 
     baseURL: import.meta.env.VITE_API_URL, // 使用环境变量配置的API URL
-    timeout: 1000,
+    timeout: 10000,
     headers: {
         'Content-Type': 'application/json',
     },

+ 6 - 6
src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue

@@ -2,18 +2,18 @@
   <div class="page-container">
     
    <div class="calculation-selector">
-    <span class="selector-title">计算方式:</span>
+    <span class="selector-title"><!--i18n:atmosDeposition.airSampleData.button1-->{{ $t("atmosDeposition.airSampleData.button1") }}</span>
     <select 
       v-model="calculationMethod" 
       class="calculation-select"
     >
-      <option value="weight">按重量计算</option>
-      <option value="volume">按体积计算</option>
+      <option value="weight"><!--i18n:atmosDeposition.airSampleData.button2-->{{ $t("atmosDeposition.airSampleData.button2") }}</option>
+      <option value="volume"><!--i18n:atmosDeposition.airSampleData.button3-->{{ $t("atmosDeposition.airSampleData.button3") }}</option>
     </select>
   </div>
 
    <div class="point-map">
-    <div class="component-title">采样点地图展示</div>
+    <div class="component-title"><!--i18n:atmosDeposition.airSampleData.MapTitle-->{{ $t("atmosDeposition.airSampleData.MapTitle") }}</div>
     <div class="map-holder">
       <atmsamplemap :calculation-method="calculationMethod"/>
      </div>
@@ -21,12 +21,12 @@
 
   
    <div class="point-line">
-    <div class="component-title">采样点数据列表展示</div>
+    <div class="component-title"><!--i18n:atmosDeposition.airSampleData.LineTitle-->{{ $t("atmosDeposition.airSampleData.LineTitle") }}</div>
     <AirsampleLine :calculation-method="calculationMethod"/>
    </div>
 
    <div>
-    <div class="component-title">各区县平均大气重金属污染柱状图</div>
+    <div class="component-title"><!--i18n:atmosDeposition.airSampleData.ChartTitle-->{{ $t("atmosDeposition.airSampleData.ChartTitle") }}</div>
     <AirsampleChart :calculation-method="calculationMethod"/>
    </div>
   </div>

+ 3 - 3
src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue

@@ -2,7 +2,7 @@
   <div class="page-container">
     
    <div class="point-map">
-    <div class="component-title">涉重企业地图展示</div>
+    <div class="component-title"><!--i18n:atmosDeposition.heavyMetalEnterprise.MapTitle-->{{ $t("atmosDeposition.heavyMetalEnterprise.MapTitle")}}</div>
     <div class="map-holder">
       <atmcompanymap/>
     </div>
@@ -10,12 +10,12 @@
 
   
    <div class="point-line">
-    <div class="component-title">涉重企业数据列表展示</div>
+    <div class="component-title"><!--i18n:atmosDeposition.heavyMetalEnterprise.LineTitle-->{{ $t("atmosDeposition.heavyMetalEnterprise.LineTitle")}}</div>
     <atmcompanyline/>
    </div>
 
    <div>
-    <div class="component-title">各区县企业平均大气颗粒物排放(t/a)</div>
+    <div class="component-title"><!--i18n:atmosDeposition.heavyMetalEnterprise.ChartTitle-->{{ $t("atmosDeposition.heavyMetalEnterprise.ChartTitle")}}</div>
     <HeavyMetalEnterprisechart/>
    </div>
   </div>

+ 4 - 4
src/views/User/HmOutFlux/irrigationWater/crossSection.vue

@@ -1,15 +1,15 @@
 <template>
   <div class="cross-container">
-    <h3 class="table-title">断面数据地图展示</h3>
+    <h3 class="table-title">{{ $t("irrigationwater.crosssection.crosssectionMapTitle") }}</h3>
     <div class="map-holder"><crosssectionmap/></div>
 
-     <h3 class="table-title">断面数据详情</h3>
+     <h3 class="table-title">{{ $t("irrigationwater.crosssection.crosssectionLineTitle") }}</h3>
      <div><CrossSectionSamplelineData/></div>
 
-     <h3 class="table-title"> 各河流Cd的平均浓度柱状图</h3>
+     <h3 class="table-title"> {{ $t("irrigationwater.crosssection.crosssectionRiverChartTitle") }}</h3>
      <div><CrossSetionData1/></div>
 
-     <h3 class="table-title">各区县的Cd平均浓度柱状图</h3>
+     <h3 class="table-title">{{ $t("irrigationwater.crosssection.crosssectionCityChartTitle") }}</h3>
      <div><CrossSetionData2/></div>
   </div>
 

+ 4 - 8
src/views/User/HmOutFlux/irrigationWater/irriWaterSampleData.vue

@@ -1,13 +1,9 @@
 <template>
   <div class="page-container">
-    <div class="header">
-        <h1>灌溉水采样点分析系统</h1>
-        <p>韶关市水环境重金属监测数据可视化分析平台</p>
-      </div>
-
+    
   <div class="main-content">
    <div class="point-map">
-    <div class="component-title">采样点地图展示</div>
+    <div class="component-title">{{ $t('irrigationwater.point.pointMapTitle') }}</div>
     <div class="map-holder">
         <irrwatermap/>
     </div>
@@ -15,12 +11,12 @@
 
   
    <div class="point-line">
-    <div class="component-title">采样点数据列表展示</div>
+    <div class="component-title">{{ $t('irrigationwater.point.pointLineTitle') }}</div>
     <Waterdataline/>
    </div>
 
    <div class="charts-line">
-    <div class="component-title">韶关市各区县重金属平均浓度</div>
+    <div class="component-title">{{ $t('irrigationwater.point.pointChartTitle') }}</div>
     <Waterassaydata2/>
    </div>
   </div>

+ 20 - 13
src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue

@@ -1,44 +1,51 @@
 <template>
   <div class="sampling-process">
-    <h2>1.采样容器与过程</h2>
+    <!--i18n:irrigationwaterMethods.title1-->
+    <h2>{{ $t("irrigationwater.irrigationwaterMethods.title1") }}</h2>
+    <!--i18n:irrigationwaterMethods.content1-->
     <p>
-      采样容器均为500mL的白色聚乙烯瓶,采样体积均为500mL,采样过程在不同天气条件下进行,主要天气状况包括多云、阴天和小雨,
-      采样点周边环境主要为河流,只有少数样品采集于水渠或瀑布区域。
+      {{ $t("irrigationwater.irrigationwaterMethods.content1") }}
     </p>
 
     <div class="image-row">
       <div class="image-container">
         <el-image :src="image1" alt="采样容器" class="sampling-image"></el-image>
-        <p class="image-caption">图1-1 采样容器</p>
+        <!--i18n:irrigationwaterMethods.photo1-->
+        <p class="image-caption">{{ $t("irrigationwater.irrigationwaterMethods.photo1") }}</p>
       </div>
       <div class="image-container">
         <el-image :src="image2" alt="采样现场" class="sampling-image"></el-image>
-        <p class="image-caption">图1-2 采样现场</p>
+        <!--i18n:irrigationwaterMethods.photo2-->
+        <p class="image-caption">{{ $t("irrigationwater.irrigationwaterMethods.photo2") }}</p>
       </div>
       <div class="image-container">
         <el-image :src="image3" alt="灌溉水采样设备" class="sampling-image"></el-image>
-        <p class="image-caption">图1-3 灌溉水采样设备</p>
+        <!--i18n:irrigationwaterMethods.photo3-->
+        <p class="image-caption">{{ $t("irrigationwater.irrigationwaterMethods.photo3") }}</p>
       </div>
     </div>
-
-    <h2>2.样品保存与现场情况</h2>
+    <!--i18n:irrigationwaterMethods.title2-->
+    <h2>{{ $t("irrigationwater.irrigationwaterMethods.title2") }}</h2>
+    <!--i18n:irrigationwaterMethods.content2-->
     <p>
-      绝大多数样品状态为无色、无沉淀、无味、无悬浮物,只有少量样品稍显浑浊并含有沉淀物,为了保证样品的完整性和数据的准确性,
-      采样后的保存方式包括了冷藏、避光、确保标签完好以及采取有效的减震措施,以避免运输过程中的振动和损坏。
+      {{ $t("irrigationwater.irrigationwaterMethods.content2") }}
     </p>
 
     <div class="image-row">
       <div class="image-container">
         <el-image :src="fieldImage1" alt="工作人员采样现场" class="sampling-image"></el-image>
-        <p class="image-caption">图2-1 采样现场</p>
+        <!--i18n:irrigationwaterMethods.photo4-->
+        <p class="image-caption">{{ $t("irrigationwater.irrigationwaterMethods.photo4") }}</p>
       </div>
       <div class="image-container">
         <el-image :src="fieldImage2" alt="工作人员采样现场" class="sampling-image"></el-image>
-        <p class="image-caption">图2-2 采样现场</p>
+        <!--i18n:irrigationwaterMethods.photo5-->
+        <p class="image-caption">{{ $t("irrigationwater.irrigationwaterMethods.photo5") }}</p>
       </div>
       <div class="image-container">
         <el-image :src="fieldImage3" alt="工作人员采样现场" class="sampling-image"></el-image>
-        <p class="image-caption">图2-3 采样现场</p>
+        <!--i18n:irrigationwaterMethods.photo6-->
+        <p class="image-caption">{{ $t("irrigationwater.irrigationwaterMethods.photo6") }}</p>
       </div>
     </div>
   </div>

+ 534 - 43
src/views/User/dataStatistics/DetectionStatistics.vue

@@ -1,53 +1,544 @@
 <template>
-  <div class="">
-    <!-- 添加删除和导入按钮 -->
-    <el-button type="danger" @click="handleBatchDelete" :disabled="!selectedRows.length">删除</el-button>
-    <el-button type="primary" @click="handleImport">导入</el-button>
-
-    <!-- 添加表格 -->
-    <el-table
-      :data="tableData"
-      style="width: 100%"
-      @selection-change="handleSelectionChange"
-    >
-      <el-table-column type="selection" width="55" />
-      <el-table-column prop="name" label="名称" width="180" />
-      <el-table-column prop="date" label="日期" width="180" />
-      <el-table-column prop="status" label="状态" width="180" />
-    </el-table>
+  <div class="metal-tables-container">
+    <!-- 灌溉水表格 -->
+    <div class="table-section">
+      <el-row :gutter="10" align="middle">
+        <el-col :span="20">
+          <div class="title">灌溉水采样点各重金属平均值</div>
+        </el-col>
+        <el-col :span="4" style="text-align: right;">
+          <div class="sample-count" v-if="waterValidSamples > 0">
+            有效样本:{{ waterValidSamples }}个
+          </div>
+        </el-col>
+      </el-row>
+      
+      <el-table 
+        v-if="waterTableData.length && !waterLoading && !waterError" 
+        :data="waterTableData" 
+        border 
+        style="width: 100%"
+      >
+        <el-table-column label="指标" prop="indicator" width="80" />
+        <el-table-column 
+          v-for="(metal, field) in waterMetals" 
+          :key="field" 
+          :label="metal.label" 
+          
+        >
+          <template #default="scope">
+            {{ scope.row[field] }}
+          </template>
+        </el-table-column>
+      </el-table>
+      
+      <div v-else class="empty-state" v-if="!waterLoading && !waterError">
+        <el-empty description="灌溉水数据为空,请刷新" />
+      </div>
+      <el-alert 
+        v-if="waterError" 
+        type="error" 
+        :description="waterError" 
+        show-icon 
+        class="error-alert"
+      />
+    </div>
+
+    <!-- 断面数据表格 -->
+    <div class="table-section">
+      <el-row :gutter="10" align="middle">
+        <el-col :span="20">
+          <div class="title">断面采样点镉含量平均值</div>
+        </el-col>
+        <el-col :span="4" style="text-align: right;">
+          <div class="sample-count" v-if="sectionValidSamples > 0">
+            有效样本:{{ sectionValidSamples }}个
+          </div>
+        </el-col>
+      </el-row>
+      
+      <el-table 
+        v-if="sectionTableData.length && !sectionLoading && !sectionError" 
+        :data="sectionTableData" 
+        border 
+        style="width: 100%"
+      >
+        <el-table-column label="指标" prop="indicator" width="80" />
+        <el-table-column 
+          v-for="(metal, field) in sectionMetals" 
+          :key="field" 
+          :label="metal.label" 
+          
+        >
+          <template #default="scope">
+            {{ scope.row[field] }}
+          </template>
+        </el-table-column>
+      </el-table>
+      
+      <div v-else class="empty-state" v-if="!sectionLoading && !sectionError">
+        <el-empty description="断面数据为空,请刷新" />
+      </div>
+      <el-alert 
+        v-if="sectionError" 
+        type="error" 
+        :description="sectionError" 
+        show-icon 
+        class="error-alert"
+      />
+    </div>
+
+    <!-- 大气企业表格 -->
+    <div class="table-section">
+      <el-row :gutter="10" align="middle">
+        <el-col :span="20">
+          <div class="title">大气企业颗粒物排放平均值</div>
+        </el-col>
+        <el-col :span="4" style="text-align: right;">
+          <div class="sample-count" v-if="atmoCompanyValidSamples > 0">
+            有效样本:{{ atmoCompanyValidSamples }}个
+          </div>
+        </el-col>
+      </el-row>
+      
+      <el-table 
+        v-if="atmoCompanyTableData.length && !atmoCompanyLoading && !atmoCompanyError" 
+        :data="atmoCompanyTableData" 
+        border 
+        style="width: 100%"
+      >
+        <el-table-column label="指标" prop="indicator" width="80" />
+        <el-table-column 
+          v-for="(metric, field) in atmoCompanyMetrics" 
+          :key="field" 
+          :label="metric.label" 
+          
+        >
+          <template #default="scope">
+            {{ scope.row[field]}}
+          </template>
+        </el-table-column>
+      </el-table>
+      
+      <div v-else class="empty-state" v-if="!atmoCompanyLoading && !atmoCompanyError">
+        <el-empty description="大气企业数据为空,请刷新" />
+      </div>
+      <el-alert 
+        v-if="atmoCompanyError" 
+        type="error" 
+        :description="atmoCompanyError" 
+        show-icon 
+        class="error-alert"
+      />
+    </div>
+
+    <!-- 大气样本表格 -->
+    <div class="table-section">
+      <el-row :gutter="10" align="middle">
+        <el-col :span="20">
+          <div class="title">大气样本重金属平均值</div>
+        </el-col>
+        <el-col :span="4" style="text-align: right;">
+          <div class="sample-count" v-if="atmoSampleValidSamples > 0">
+            有效样本:{{ atmoSampleValidSamples }}个
+          </div>
+        </el-col>
+      </el-row>
+      
+      <el-table 
+        v-if="atmoSampleTableData.length && !atmoSampleLoading && !atmoSampleError" 
+        :data="atmoSampleTableData" 
+        border 
+        style="width: 100%"
+      >
+        <el-table-column label="指标" prop="indicator" width="80" />
+        <el-table-column 
+          v-for="(metric, field) in atmoSampleMetrics" 
+          :key="field" 
+          :label="metric.label" 
+          :width="getColWidth(field)"
+        >
+          <template #default="scope">
+            {{ scope.row[field] }}
+          </template>
+        </el-table-column>
+      </el-table>
+      
+      <div v-else class="empty-state" v-if="!atmoSampleLoading && !atmoSampleError">
+        <el-empty description="大气样本数据为空,请刷新" />
+      </div>
+      <el-alert 
+        v-if="atmoSampleError" 
+        type="error" 
+        :description="atmoSampleError" 
+        show-icon 
+        class="error-alert"
+      />
+    </div>
+
+    <!-- 统一刷新按钮 -->
+    <div class="refresh-btn">
+      <el-button 
+        type="primary" 
+        @click="refreshAll" 
+        :loading="waterLoading || sectionLoading || atmoCompanyLoading || atmoSampleLoading"
+        icon="el-icon-refresh"
+      >
+        刷新所有数据
+      </el-button>
+    </div>
   </div>
 </template>
 
-<script>
-export default {
-  name: 'DetectionStatistics',
-  data() {
-    return {
-      tableData: [
-        { name: '示例1', date: '2023-01-01', status: '正常' },
-        { name: '示例2', date: '2023-01-02', status: '异常' }
-      ],
-      selectedRows: [] // 存储选中的行
-    };
-  },
-  methods: {
-    handleSelectionChange(selection) {
-      this.selectedRows = selection;
-    },
-    handleBatchDelete() {
-      this.tableData = this.tableData.filter(
-        item => !this.selectedRows.includes(item)
-      );
-      this.selectedRows = [];
-    },
-    handleImport() {
-      // 导入逻辑
-      console.log('导入按钮点击');
-    }
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue';
+import axios from 'axios';
+
+// ====================== 通用工具类型 ======================
+type MetricsMap<T extends string> = { [K in T]: { label: string } };
+type TableRow<T extends string> = { indicator: string } & { [K in T]: number };
+
+// ====================== 灌溉水模块(严格类型) ======================
+const waterMetals = {
+  cr_concentration: { label: '铬含量(ug/L)' },
+  as_concentration: { label: '砷含量(ug/L)' },
+  cd_concentration: { label: '镉含量(ug/L)' },
+  hg_concentration: { label: '汞含量(ug/L)' },
+  pb_concentration: { label: '铅含量(ug/L)' },
+};
+type WaterMetalKey = keyof typeof waterMetals;
+type WaterRow = TableRow<WaterMetalKey>;
+
+const waterLoading = ref(false);
+const waterError = ref('');
+const waterData = ref<{ [K in WaterMetalKey]?: number | string }[]>([]);
+const waterTableData = ref<WaterRow[]>([]);
+const waterValidSamples = ref(0); // 有效样本数
+
+const calculateWaterAverage = () => {
+  const stats: Record<WaterMetalKey, { sum: number; count: number }> = {} as any;
+  (Object.keys(waterMetals) as WaterMetalKey[]).forEach(key => {
+    stats[key] = { sum: 0, count: 0 };
+  });
+
+  let validSamples = 0;
+  waterData.value.forEach(item => {
+    let hasValidMetric = false;
+    (Object.keys(waterMetals) as WaterMetalKey[]).forEach(key => {
+      const value = Number(item[key]);
+      if (!isNaN(value)) {
+        stats[key].sum += value;
+        stats[key].count += 1;
+        hasValidMetric = true;
+      }
+    });
+    if (hasValidMetric) validSamples++;
+  });
+
+  waterTableData.value = [(Object.keys(waterMetals) as WaterMetalKey[]).reduce((row, key) => {
+    row.indicator = '平均值';
+    row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
+    return row;
+  }, {} as WaterRow)];
+
+  waterValidSamples.value = validSamples;
+};
+
+const fetchWaterData = async () => {
+  try {
+    waterLoading.value = true;
+    waterError.value = '';
+    const res = await axios.get(
+      'http://localhost:8000/api/vector/export/all?table_name=water_sampling_data'
+    );
+    waterData.value = res.data.features.map((f: { properties: any }) => f.properties);
+    calculateWaterAverage();
+  } catch (err: any) {
+    waterError.value = err.message;
+    waterTableData.value = [];
+  } finally {
+    waterLoading.value = false;
   }
 };
+
+// ====================== 断面模块(严格类型) ======================
+const sectionMetals = { cd_concentration: { label: '镉含量(ug/L)' } };
+type SectionMetalKey = keyof typeof sectionMetals;
+type SectionRow = TableRow<SectionMetalKey>;
+
+const sectionLoading = ref(false);
+const sectionError = ref('');
+const sectionData = ref<{ [K in SectionMetalKey]?: number | string }[]>([]);
+const sectionTableData = ref<SectionRow[]>([]);
+const sectionValidSamples = ref(0); // 有效样本数
+
+const calculateSectionAverage = () => {
+  const stats: Record<SectionMetalKey, { sum: number; count: number }> = {} as any;
+  (Object.keys(sectionMetals) as SectionMetalKey[]).forEach(key => {
+    stats[key] = { sum: 0, count: 0 };
+  });
+
+  let validSamples = 0;
+  sectionData.value.forEach(item => {
+    let hasValidMetric = false;
+    (Object.keys(sectionMetals) as SectionMetalKey[]).forEach(key => {
+      const value = Number(item[key]);
+      if (!isNaN(value)) {
+        stats[key].sum += value;
+        stats[key].count += 1;
+        hasValidMetric = true;
+      }
+    });
+    if (hasValidMetric) validSamples++;
+  });
+
+  sectionTableData.value = [(Object.keys(sectionMetals) as SectionMetalKey[]).reduce((row, key) => {
+    row.indicator = '平均值';
+    row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
+    return row;
+  }, {} as SectionRow)];
+
+  sectionValidSamples.value = validSamples;
+};
+
+const fetchSectionData = async () => {
+  try {
+    sectionLoading.value = true;
+    sectionError.value = '';
+    const res = await fetch('http://localhost:8000/api/vector/export/all?table_name=cross_section');
+    if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
+    let rawText = await res.text();
+    rawText = rawText.replace(/:\s*NaN/g, ': null'); // 修复 NaN
+    const geoJSON = JSON.parse(rawText);
+    sectionData.value = geoJSON.features.map((f: { properties: any }) => f.properties);
+    calculateSectionAverage();
+  } catch (err: any) {
+    sectionError.value = err.message;
+    sectionTableData.value = [];
+  } finally {
+    sectionLoading.value = false;
+  }
+};
+
+// ====================== 大气企业模块(严格类型) ======================
+const atmoCompanyMetrics = {
+  particulate_emission: { label: '颗粒物排放量' },
+};
+type AtmoCompanyKey = keyof typeof atmoCompanyMetrics;
+type AtmoCompanyRow = TableRow<AtmoCompanyKey>;
+
+const atmoCompanyLoading = ref(false);
+const atmoCompanyError = ref('');
+const atmoCompanyData = ref<{ [K in AtmoCompanyKey]?: number | string }[]>([]);
+const atmoCompanyTableData = ref<AtmoCompanyRow[]>([]);
+const atmoCompanyValidSamples = ref(0); // 有效样本数
+
+const calculateAtmoCompanyAverage = () => {
+  const stats: Record<AtmoCompanyKey, { sum: number; count: number }> = {} as any;
+  (Object.keys(atmoCompanyMetrics) as AtmoCompanyKey[]).forEach(key => {
+    stats[key] = { sum: 0, count: 0 };
+  });
+
+  let validSamples = 0;
+  atmoCompanyData.value.forEach(item => {
+    let hasValidMetric = false;
+    (Object.keys(atmoCompanyMetrics) as AtmoCompanyKey[]).forEach(key => {
+      const value = Number(item[key]);
+      if (!isNaN(value)) {
+        stats[key].sum += value;
+        stats[key].count += 1;
+        hasValidMetric = true;
+      }
+    });
+    if (hasValidMetric) validSamples++;
+  });
+
+  atmoCompanyTableData.value = [(Object.keys(atmoCompanyMetrics) as AtmoCompanyKey[]).reduce((row, key) => {
+    row.indicator = '平均值';
+    row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
+    return row;
+  }, {} as AtmoCompanyRow)];
+
+  atmoCompanyValidSamples.value = validSamples;
+};
+
+const fetchAtmoCompanyData = async () => {
+  try {
+    atmoCompanyLoading.value = true;
+    atmoCompanyError.value = '';
+    const res = await fetch('http://localhost:8000/api/vector/export/all?table_name=atmo_company');
+    if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
+    let rawText = await res.text();
+    rawText = rawText.replace(/:\s*NaN/g, ': null'); // 修复 NaN
+    const geoJSON = JSON.parse(rawText);
+    atmoCompanyData.value = geoJSON.features.map((f: { properties: any }) => f.properties);
+    calculateAtmoCompanyAverage();
+  } catch (err: any) {
+    atmoCompanyError.value = err.message;
+    atmoCompanyTableData.value = [];
+  } finally {
+    atmoCompanyLoading.value = false;
+  }
+};
+
+// ====================== 大气样本模块(严格类型) ======================
+const weightColumns = [
+  { key: 'Cr_particulate', label: 'Cr mg/kg' },
+  { key: 'As_particulate', label: 'As mg/kg' },
+  { key: 'Cd_particulate', label: 'Cd mg/kg' },
+  { key: 'Hg_particulate', label: 'Hg mg/kg' },
+  { key: 'Pb_particulate', label: 'Pb mg/kg' },
+  { key: 'particle_weight', label: '颗粒物重量 mg' },
+];
+
+const atmoSampleMetrics = weightColumns.reduce((obj, col) => {
+  obj[col.key] = { label: col.label };
+  return obj;
+}, {} as Record<string, { label: string }>);
+type AtmoSampleKey = keyof typeof atmoSampleMetrics;
+type AtmoSampleRow = TableRow<AtmoSampleKey>;
+
+const atmoSampleLoading = ref(false);
+const atmoSampleError = ref('');
+const atmoSampleData = ref<{ [K in AtmoSampleKey]?: number | string }[]>([]);
+const atmoSampleTableData = ref<AtmoSampleRow[]>([]);
+const atmoSampleValidSamples = ref(0); // 有效样本数
+
+const getColWidth = computed(() => (field: AtmoSampleKey) => {
+  const col = weightColumns.find(c => c.key === field);
+  return col ;
+});
+
+const calculateAtmoSampleAverage = () => {
+  const stats: Record<AtmoSampleKey, { sum: number; count: number }> = {} as any;
+  (Object.keys(atmoSampleMetrics) as AtmoSampleKey[]).forEach(key => {
+    stats[key] = { sum: 0, count: 0 };
+  });
+
+  let validSamples = 0;
+  atmoSampleData.value.forEach(item => {
+    let hasValidMetric = false;
+    (Object.keys(atmoSampleMetrics) as AtmoSampleKey[]).forEach(key => {
+      const value = Number(item[key]);
+      if (!isNaN(value)) {
+        stats[key].sum += value;
+        stats[key].count += 1;
+        hasValidMetric = true;
+      }
+    });
+    if (hasValidMetric) validSamples++;
+  });
+
+  atmoSampleTableData.value = [(Object.keys(atmoSampleMetrics) as AtmoSampleKey[]).reduce((row, key) => {
+    row.indicator = '平均值';
+    row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
+    return row;
+  }, {} as AtmoSampleRow)];
+
+  atmoSampleValidSamples.value = validSamples;
+};
+
+const fetchAtmoSampleData = async () => {
+  try {
+    atmoSampleLoading.value = true;
+    atmoSampleError.value = '';
+    const res = await fetch('http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data');
+    if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
+    let rawText = await res.text();
+    rawText = rawText.replace(/:\s*NaN/g, ': null'); // 修复 NaN
+    const geoJSON = JSON.parse(rawText);
+    atmoSampleData.value = geoJSON.features.map((f: { properties: any }) => f.properties);
+    calculateAtmoSampleAverage();
+  } catch (err: any) {
+    atmoSampleError.value = err.message;
+    atmoSampleTableData.value = [];
+  } finally {
+    atmoSampleLoading.value = false;
+  }
+};
+
+// ====================== 统一刷新 ======================
+const refreshAll = () => {
+  fetchWaterData();
+  fetchSectionData();
+  fetchAtmoCompanyData();
+  fetchAtmoSampleData();
+};
+
+onMounted(() => {
+  refreshAll();
+});
 </script>
 
 <style scoped>
- 
+.metal-tables-container {
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
+}
+
+.table-section {
+  margin-bottom: 30px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #eee;
+}
+
+.table-section:last-child {
+  border-bottom: none;
+  margin-bottom: 0;
+  padding-bottom: 0;
+}
+
+.title {
+  font-size: 20px;
+  font-weight: 500;
+  margin-bottom: 8px;
+  color: #333;
+}
+
+.sample-count {
+  font-size: 14px;
+  color: #666;
+  font-style: italic;
+}
+
+.error-alert {
+  margin: 16px 0;
+}
+
+.empty-state {
+  padding: 40px 0;
+  text-align: center;
+}
+
+.refresh-btn {
+  text-align: center;
+  margin-top: 10px;
+}
+
+/* 关键样式:自适应列宽 + 内容不换行 */
+.el-table {
+  table-layout: auto; /* 列宽自适应内容 */
+  min-width: 100%;    /* 确保容器宽度不足时触发滚动 */
+}
+
+/* 覆盖Element UI的单元格样式(提高优先级) */
+.el-table td,
+.el-table th {
+  white-space: nowrap !important;   /* 强制不换行 */
+  overflow: hidden !important;      /* 溢出隐藏 */
+  text-overflow: ellipsis !important; /* 溢出省略号 */
+  word-break: normal !important;    /* 禁止自动断词 */
+}
+
+.el-table__cell {
+  white-space: nowrap !important; /* 强制不换行 */
+  overflow: hidden;               /* 溢出隐藏 */
+  text-overflow: ellipsis;        /* 溢出显示省略号 */
+}
+
+.table-section {
+  overflow-x: auto; /* 横向滚动 */
+}
 </style>

+ 4 - 1
tsconfig.json

@@ -10,5 +10,8 @@
     {
       "path": "./tsconfig.vitest.json"
     }
-  ]
+  ],
+  "compilerOptions": {
+    "types": ["unplugin-vue-i18n/client"] // 显式引入类型定义
+  }
 }

+ 10 - 1
vite.config.ts

@@ -11,6 +11,11 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 import Icons from 'unplugin-icons/vite'
 import IconsResolver from 'unplugin-icons/resolver'
 
+import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; 
+import path from 'path'
+
+const  __dirname = path.dirname(fileURLToPath(import.meta.url));
+
 // https://vite.dev/config/
 export default defineConfig({
   plugins: [
@@ -52,4 +57,8 @@ export default defineConfig({
       '@': fileURLToPath(new URL('./src', import.meta.url))
     },
   },
-})
+})
+
+function kebabCase(text: any) {
+  throw new Error('Function not implemented.')
+}

+ 1 - 0
语言切换.txt

@@ -0,0 +1 @@
+node scripts/extract-i18n.js 文件路径,详细到.vue/.ts

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