Browse Source

feat(TotalIntroduction): 新增镉元素地图图层支持并修复数据接口

1. 替换土壤pH地图的数据源表名为le_origin_ph_map
2. 新增镉元素相关的GeoServer配置项cdGeoserver和reboundCdGeoserver
3. 重构地图切换逻辑,支持pH和镉元素双图层切换
4. 新增调试日志和地图渲染刷新逻辑
5. 修复镉元素数据统计的属性名动态获取问题
6. 更新WFS请求接口适配新的数据图层
7. 新增国际化文档说明文件
yes-yes-yes-k 5 days ago
parent
commit
1d0fd97531

+ 0 - 11
src/views/User/dataStatistics/DetectionStatistics.vue

@@ -40,17 +40,6 @@ export default {
 }
 </script>
 <style scoped>
-.scale-wrapper {
-   padding: 20px;
-  /* 添加70%透明度的渐变背景 */
-  background: linear-gradient(
-    135deg, 
-    rgba(230, 247, 255, 0.7) 0%, 
-    rgba(240, 248, 255, 0.7) 100%
-  );
-  min-height: 80vh;
-  box-sizing: border-box;
-}
 
 /* 核心:改为3列网格布局 */
 .layout-container {

+ 71 - 12
src/views/User/introduction/TotalIntroduction.vue

@@ -75,12 +75,12 @@
     center:[25.222903, 113.25383],
     zoom:10,  // 调小缩放级别,显示更大范围
     //  获取所有点的 API 地址
-    getPoint:'/api/vector/export/all?table_name=le_data_block_map&format=geojson',
+    getPoint:'/api/vector/export/all?table_name=le_origin_ph_map&format=geojson',
     geoserver:{
       url:'/geoserver',
       workspace:'acidmap',
       layerGroup:'leshujukanbanmap',
-      dataLayer:'le_data_block_map',
+      dataLayer:'le_origin_ph_map',
       wmsUrl:'/geoserver/acidmap/wms',
       phField: 'ph_mean'
     },
@@ -91,6 +91,22 @@
       dataLayer:'le_data_reflux_result',
       wmsUrl:'/geoserver/acidmap/wms',
       phField: 'le_data__4'
+    },
+    cdGeoserver:{
+      url:'/geoserver',
+      workspace:'acidmap',
+      layerGroup:'CropCd_block_map_with_boundary',
+      dataLayer:'Crop_cd_block_map',
+      wmsUrl:'/geoserver/acidmap/wms',
+      cdField: 'CropCd_mea'
+    },
+    reboundCdGeoserver:{
+      url:'/geoserver',
+      workspace:'acidmap',
+      layerGroup:'PrediCropCd_block_with_boundary',
+      dataLayer:'Predi_Cropcd_block',
+      wmsUrl:'/geoserver/acidmap/wms',
+      cdField: 'sceCropCd_'
     }
    }
    // 图层配置已验证:正常地图使用 le_data_block_map,反酸地图使用 le_data_reflux_result
@@ -154,9 +170,9 @@ function getPHComment(avgPH) {
     if(!mapRef2.value) return 
     map2 = L.map(mapRef2.value).setView(CONFIG.center,CONFIG.zoom)
 
-    // 第二个地图展示 CropCd_block_map_with_boundary 图层
-    wmsLayer2 = L.tileLayer.wms(CONFIG.geoserver.wmsUrl, {
-      layers: `${CONFIG.geoserver.workspace}:CropCd_block_map_with_boundary`,
+    // 第二个地图展示 CD 图层
+    wmsLayer2 = L.tileLayer.wms(CONFIG.cdGeoserver.wmsUrl, {
+      layers: `${CONFIG.cdGeoserver.workspace}:${CONFIG.cdGeoserver.layerGroup}`,
       format: 'image/png',
       transparent: true,
       version: '1.1.0',
@@ -168,15 +184,19 @@ function getPHComment(avgPH) {
 
   async function switchMap(mapType) {
     if (!map || !wmsLayer) return
+    if (!map2 || !wmsLayer2) return
+    
+    console.log('🔄 开始切换地图,目标类型:', mapType)
+    console.log('️ map2 存在:', !!map2, 'wmsLayer2 存在:', !!wmsLayer2)
     
     currentMapType.value = mapType
     
+    // 切换 pH 地图
     const config = mapType === 'rebound' ? CONFIG.reboundGeoserver : CONFIG.geoserver
+    console.log('📊 pH 配置:', config.layerGroup, config.dataLayer)
     
-    // 移除旧的 WMS 图层
     map.removeLayer(wmsLayer)
     
-    // 创建新的 WMS 图层
     wmsLayer = L.tileLayer.wms(config.wmsUrl, {
       layers: `${config.workspace}:${config.layerGroup}`,
       format: 'image/png',
@@ -186,11 +206,46 @@ function getPHComment(avgPH) {
       attribution: '© GeoServer - Acidmap'
     }).addTo(map)
     
-    // console.log(`地图已切换到:${mapType === 'rebound' ? '反酸一周期后' : '实施降酸措施'}`)
-    // console.log('新图层:', `${config.workspace}:${config.layerGroup}`)
+    console.log('✅ pH 地图图层已切换')
+    
+    // 切换 CD 地图
+    const cdConfig = mapType === 'rebound' ? CONFIG.reboundCdGeoserver : CONFIG.cdGeoserver
+    console.log(' CD 配置:', cdConfig.layerGroup, cdConfig.dataLayer, cdConfig.cdField)
+    console.log('🔗 CD WMS 完整图层名:', `${cdConfig.workspace}:${cdConfig.layerGroup}`)
+    
+    // 移除旧图层(和 pH 地图使用相同的方式)
+    map2.removeLayer(wmsLayer2)
+    
+    // 创建新图层并添加到地图
+    wmsLayer2 = L.tileLayer.wms(cdConfig.wmsUrl, {
+      layers: `${cdConfig.workspace}:${cdConfig.layerGroup}`,
+      format: 'image/png',
+      transparent: true,
+      version: '1.1.0',
+      srs:'EPSG:4326',
+      attribution: '© GeoServer - Crop CD'
+    }).addTo(map2)
+    
+    // 监听瓦片加载事件
+    wmsLayer2.on('tileload', function(e) {
+      console.log(' CD 瓦片加载成功:', e.tile.src)
+    })
+    wmsLayer2.on('tileerror', function(e) {
+      console.error(' CD 瓦片加载失败! 完整URL:', e.tile.src, '错误信息:', e.error)
+    })
+    console.log('✅ CD 地图图层已切换,新图层:', wmsLayer2.options.layers)
+    
+    // 刷新地图渲染
+    await nextTick()
+    map2.invalidateSize()
+    wmsLayer2.redraw()
     
-    // 重新加载对应地图的数据和统计
+    // 重新加载 pH 地图的数据和统计
     await loadMapData()
+    
+    // 重新加载 CD 地图的数据和统计
+    await loadCDData()
+    console.log('🎉 地图切换完成')
   }
   
   // 加载当前地图类型的数据
@@ -354,6 +409,8 @@ async function loadCDStatistics() {
     // 等待数据加载完成
     if (cdSampleData.value.length === 0) return;
     
+    const cdConfig = currentMapType.value === 'rebound' ? CONFIG.reboundCdGeoserver : CONFIG.cdGeoserver
+    
     let cdCount = 0
     let avgCD = 0
     let safeCount = 0      // 0.0-0.2 mg/kg
@@ -368,7 +425,7 @@ async function loadCDStatistics() {
 
     cdSampleData.value.forEach(feature => {
       // 获取 CD 含量和面积
-      const cdValue = feature.properties.CropCd_mea
+      const cdValue = feature.properties[cdConfig.cdField]
       const numericCd = typeof cdValue === 'string' ? parseFloat(cdValue) : cdValue
       const area = parseFloat(feature.properties.area) || 0
       
@@ -441,7 +498,9 @@ async function loadCDStatistics() {
 // 加载第二个地图的 CD 数据
 async function loadCDData() {
   try {
-    const url = `${CONFIG.geoserver.url}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${CONFIG.geoserver.workspace}:Crop_cd_block_map&outputFormat=application/json`
+    const cdConfig = currentMapType.value === 'rebound' ? CONFIG.reboundCdGeoserver : CONFIG.cdGeoserver
+    
+    const url = `${cdConfig.url}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${cdConfig.workspace}:${cdConfig.dataLayer}&outputFormat=application/json`
     
     const response = await fetch(url);
     

+ 316 - 1
语言切换.txt

@@ -1 +1,316 @@
-node scripts/extract-i18n.js 文件路径,详细到.vue/.ts
+## 项目中英文转换逻辑说明
+
+### 一、国际化(i18n)架构概述
+
+本项目采用 **Vue I18n** 实现中英文双语切换,核心架构包含配置层、语言包层和组件调用层三个层次。
+
+---
+
+### 二、核心文件结构
+
+| 文件路径 | 功能说明 |
+|---------|---------|
+| `src/i18n.ts` | Vue I18n 核心配置,注册语言包 |
+| `src/i18n.d.ts` | TypeScript 类型定义文件 |
+| `src/locales/zh.json` | 中文语言包(键值对存储) |
+| `src/locales/en.json` | 英文语言包(与中文键结构一致) |
+| `src/locales/index.js` | 备用语言管理模块 |
+| `scripts/extract-i18n.js` | 自动提取国际化标记的脚本 |
+
+---
+
+### 三、中英文转换核心逻辑
+
+#### 3.1 配置初始化 (`src/i18n.ts`)
+
+```typescript
+import { createI18n } from 'vue-i18n';
+import zh from './locales/zh.json';
+import en from './locales/en.json';
+
+const i18n = createI18n({
+legacy: false,           // 使用 Composition API 模式
+locale: 'zh',            // 默认语言:中文
+fallbackLocale: 'en',    // 回退语言:英文
+messages: { zh, en },    // 语言包映射对象
+});
+
+export default i18n;
+```
+
+**关键配置说明:**
+- `legacy: false` - 启用 Vue 3 Composition API 兼容模式
+- `locale` - 当前活跃语言标识
+- `fallbackLocale` - 当某个 key 在当前语言包不存在时,使用回退语言
+
+#### 3.2 语言包结构
+
+语言包采用**嵌套 JSON 结构**,支持层级键路径:
+
+```json
+// src/locales/zh.json
+{
+"Header": {
+    "title": "土壤酸化智能预测专家系统",
+    "welcome": "欢迎",
+    "logout": "退出登录"
+},
+"login": {
+    "userTitle": "用户登录",
+    "loginButton": "登录",
+    "loginFailed": "登录失败,请检查用户名或密码"
+},
+"validation": {
+    "usernameRequired": "请输入账号",
+    "passwordLength": "密码长度为 3 到 16 个字符"
+}
+}
+```
+
+#### 3.3 组件调用方式
+
+**Vue 模板中的使用:**
+
+```vue
+<template>
+<!-- 直接使用 t() 函数 -->
+<h2>{{ t('login.userTitle') }}</h2>
+
+<!-- 表单验证 -->
+<el-input :placeholder="t('validation.usernameRequired')" />
+
+<!-- 动态文本 -->
+<el-button>{{ t('login.loginButton') }}</el-button>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n';
+
+const { t, locale } = useI18n();
+
+// 切换语言
+const toggleLanguage = () => {
+locale.value = locale.value === 'zh' ? 'en' : 'zh';
+localStorage.setItem('lang', locale.value);
+};
+</script>
+```
+
+**TypeScript 文件中的使用:**
+
+```typescript
+// 在组合式函数中使用
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+
+// 用于消息提示
+ElMessage.success(t('login.loginSuccess'));
+
+// 用于动态文本
+const errorMsg = t('login.loginFailed');
+```
+
+---
+
+### 四、语言切换流程
+
+#### 4.1 完整切换流程(以登录页为例)
+
+```typescript
+// src/views/login/loginView.vue
+const toggleLanguage = async () => {
+// 1. 计算新语言
+const newLocale = locale.value === "zh" ? "en" : "zh";
+
+// 2. 更新 Vue I18n locale(触发视图自动更新)
+locale.value = newLocale;
+
+// 3. 持久化到 localStorage(刷新页面保持语言设置)
+localStorage.setItem("lang", newLocale);
+
+// 4. 可选:同步到后端(保存用户语言偏好)
+try {
+    await changeLanguageAPI({
+    language: newLocale,
+    userId: store.userInfo?.userId,
+    timestamp: Date.now(),
+    });
+} catch (error) {
+    console.error("语言切换同步失败:", error);
+}
+};
+```
+
+#### 4.2 初始化时的语言恢复
+
+在应用启动时,从 localStorage 读取上次保存的语言:
+
+```typescript
+// src/main.ts
+const savedLang = localStorage.getItem('lang') || 'zh';
+const i18n = createI18n({
+locale: savedLang,
+// ... 其他配置
+});
+```
+
+---
+
+### 五、国际化标记提取脚本
+
+#### 5.1 脚本位置和功能
+
+**脚本文件:** `scripts/extract-i18n.js`
+
+**核心功能:**
+- 自动扫描 `.vue` 和 `.ts` 文件
+- 识别特定标记格式的中文文本
+- 自动生成/更新语言包文件
+
+#### 5.2 使用方法
+
+```bash
+# 扫描整个 src 目录(默认)
+node scripts/extract-i18n.js
+
+# 扫描指定目录
+node scripts/extract-i18n.js src/views/User
+
+# 扫描单个文件
+node scripts/extract-i18n.js src/components/layout/AppLayout.vue
+```
+
+#### 5.3 提取标记格式
+
+在 Vue/TS 文件中使用以下格式标记需要国际化的文本:
+
+```vue
+<!--i18n:模块名.键名-->中文文本内容
+```
+
+**示例:**
+
+```vue
+<!--i18n:Header.title-->土壤酸化智能预测专家系统
+<!--i18n:login.userTitle-->用户登录
+<!--i18n:Menu.dataManagement-->数据管理
+```
+
+#### 5.4 脚本处理流程
+
+```
+1. 解析命令行参数 → 确定扫描路径
+2. 读取已有语言包 → 保留历史翻译
+3. 递归扫描文件 → 收集 .vue/.ts 文件
+4. 正则匹配标记 → 提取键名和中文文本
+5. 构建嵌套对象 → 处理键路径(如 Menu.subMenu.item)
+6. 写入语言包 → 更新 zh.json 和 en.json
+```
+
+#### 5.5 脚本核心代码解析
+
+**标记匹配正则:**
+```javascript
+const regex = /<!--i18n:(\S+)-->([\u4e00-\u9fa5\w\s,.,。;;!!??::()()]+?)(?=\r?\n|['"<]|$)/g;
+```
+
+**嵌套键设置函数:**
+```javascript
+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}" 已作为字符串存在`);
+    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;
+}
+}
+```
+
+---
+
+### 六、语言包维护规范
+
+| 规范项 | 说明 |
+|-------|------|
+| **键命名** | 使用 `模块名.子模块.键名` 格式,如 `Menu.dataManagement` |
+| **结构对齐** | 中英文语言包必须保持**完全相同的键结构** |
+| **值处理** | 中文包存储中文原文,英文包存储对应英文翻译 |
+| **新增翻译** | 同时在 `zh.json` 和 `en.json` 中添加键值对 |
+| **标记规范** | 使用 `<!--i18n:键名-->` 标记便于脚本提取 |
+| **避免硬编码** | 所有用户可见文本必须通过 `t()` 函数获取 |
+
+---
+
+### 七、现有翻译覆盖范围
+
+| 模块 | 说明 |
+|-----|------|
+| **Header** | 顶部导航栏文本 |
+| **login/register/logout** | 登录、注册、退出相关 |
+| **Menu** | 侧边栏菜单项 |
+| **validation** | 表单验证提示 |
+| **AcidModelMap** | 酸化模型地图相关 |
+| **PhPrediction** | pH 预测模型相关 |
+| **Calculation** | 计算模块相关 |
+| **DetectionStatistics** | 检测统计相关 |
+| **SoilCdStatistics** | 土壤镉统计相关 |
+| **LandCultivatedStatistics** | 耕地统计相关 |
+
+---
+
+### 八、Element Plus 组件国际化
+
+Element Plus 组件默认使用中文,如需支持英文需额外配置:
+
+```typescript
+// src/main.ts
+import ElementPlus from 'element-plus';
+import zhCn from 'element-plus/es/locale/lang/zh-cn';
+import en from 'element-plus/es/locale/lang/en';
+
+// 根据当前语言动态切换 Element Plus 语言
+app.use(ElementPlus, {
+locale: locale.value === 'zh' ? zhCn : en,
+});
+```
+
+---
+
+### 九、扩展说明
+
+#### 9.1 添加新翻译流程
+
+1. **标记文本**:在 Vue/TS 文件中添加 `<!--i18n:键名-->` 标记
+2. **运行脚本**:执行 `node scripts/extract-i18n.js` 提取标记
+3. **翻译英文**:在 `en.json` 中填写对应英文翻译
+4. **使用翻译**:在组件中使用 `t('键名')` 获取文本
+
+#### 9.2 语言切换事件通知
+
+通过自定义事件通知其他组件语言变化:
+
+```typescript
+// 触发语言切换事件
+window.dispatchEvent(new CustomEvent('languageChanged', { 
+detail: { locale: locale.value } 
+}));
+
+// 监听语言切换事件
+window.addEventListener('languageChanged', (e) => {
+console.log('语言已切换为:', e.detail.locale);
+});
+```