Commit 96cde8ad1629f1e24d39492040a6e5568481f8be

Authored by 戈灵虎
1 parent f731d73f

feat(garden): 新增行道树巡检功能支持栽种年月和树种类型

Showing 16 changed files with 474 additions and 6 deletions
api_document_tree_inspection.md 0 → 100644
  1 +# 行道树巡查需求改造 - 前端接口对接文档
  2 +
  3 +> 更新日期:2026-06-05
  4 +> 后端开发:已完成,编译通过
  5 +> 涉及模块:一树一档案、行道树巡检与风险评估
  6 +
  7 +---
  8 +
  9 +## 一、字段变更总览
  10 +
  11 +### 1.1 一树一档案(garden_tree)
  12 +
  13 +| 变更类型 | 字段名 | 说明 |
  14 +|---------|--------|------|
  15 +| ⚠️ 废弃 | `estimationtreeage` | 估测树龄,**不再使用**,保留兼容 |
  16 +| ✅ 新增 | `plantingDate` | 栽种年月,格式 `yyyy-MM`,如 `"2015-06"` |
  17 +| ✅ 新增 | `treeSpeciesType` | 树种类型,**字典值**,字典类型:`tree_species_type` |
  18 +
  19 +### 1.2 巡检记录(garden_tree_inspection)
  20 +
  21 +| 变更类型 | 字段名 | 说明 |
  22 +|---------|--------|------|
  23 +| ⚠️ 废弃 | `weight.plantingYears` | 栽植年限文本选择,**不再使用**,由系统自动计算 |
  24 +| ✅ 新增 | `weight.plantingDate` | 栽植年月,格式 `yyyy-MM`,如 `"2015-06"` |
  25 +
  26 +### 1.3 字典配置
  27 +
  28 +| 字典类型 | 字典值 | 标签 |
  29 +|---------|--------|------|
  30 +| `tree_species_type` | `1` | 深根性树种 |
  31 +| `tree_species_type` | `2` | 浅根性树种 |
  32 +
  33 +> 前端通过通用字典接口获取:`GET /garden/common/dict-type?dict_type=tree_species_type`
  34 +
  35 +---
  36 +
  37 +## 二、一树一档案相关接口变更
  38 +
  39 +### 2.1 新增/编辑行道树
  40 +
  41 +**接口**:`POST /garden/tree/create` 或 `PUT /garden/tree/update`
  42 +
  43 +请求体新增字段:
  44 +
  45 +```json
  46 +{
  47 + "id": 1024,
  48 + "treenumber": "D0001-P1-0001",
  49 + "treetype": "香樟",
  50 + "dbh": "30",
  51 +
  52 + "plantingDate": "2015-06",
  53 + "treeSpeciesType": "1",
  54 +
  55 + "estimationtreeage": null
  56 +}
  57 +```
  58 +
  59 +| 字段 | 类型 | 必填 | 说明 |
  60 +|------|------|------|------|
  61 +| `plantingDate` | String | 否 | 栽种年月,格式 `yyyy-MM`,替代原 `estimationtreeage` |
  62 +| `treeSpeciesType` | String | 否 | 树种类型字典值(`1`=深根性,`2`=浅根性) |
  63 +| `estimationtreeage` | String | 否 | **已废弃**,前端应改用 `plantingDate` |
  64 +
  65 +### 2.2 获取行道树详情
  66 +
  67 +**接口**:`GET /garden/tree/get?id={id}`
  68 +
  69 +响应新增字段:
  70 +
  71 +```json
  72 +{
  73 + "code": 0,
  74 + "data": {
  75 + "id": 1024,
  76 + "treenumber": "D0001-P1-0001",
  77 + "estimationtreeage": "10",
  78 +
  79 + "plantingDate": "2015-06",
  80 + "treeSpeciesType": "1",
  81 +
  82 + "...": "其他字段不变"
  83 + }
  84 +}
  85 +```
  86 +
  87 +> **注意**:详情页中原"预估年龄/估测树龄"改为显示"栽种年月",取 `plantingDate` 字段值。`treeSpeciesType` 用字典翻译显示。
  88 +
  89 +### 2.3 行道树列表分页(PC端)
  90 +
  91 +**接口**:`GET /garden/tree/page`
  92 +
  93 +新增查询参数:
  94 +
  95 +| 参数 | 类型 | 说明 |
  96 +|------|------|------|
  97 +| `treeSpeciesType` | String | 树种类型筛选,传字典值(`1` 或 `2`) |
  98 +
  99 +示例:
  100 +```
  101 +GET /garden/tree/page?pageNo=1&pageSize=20&treeSpeciesType=1
  102 +```
  103 +
  104 +> 列表中在"胸径"后面新增"树种类别"展示列,值通过字典翻译。
  105 +
  106 +---
  107 +
  108 +## 三、巡检记录相关接口变更
  109 +
  110 +### 3.1 提交巡检记录
  111 +
  112 +**接口**:`POST /garden/tree-inspection/create`
  113 +
  114 +`weight` 对象变更:
  115 +
  116 +```json
  117 +{
  118 + "treeId": 1024,
  119 + "inspectionTime": "2026-06-05 14:30:00",
  120 + "root": { "disease": 0, "anchorage": 0, "cutting": 0 },
  121 + "collar": { "woodDamage": 5, "barkDamage": 0, "loosening": 0 },
  122 + "trunk": { "woodDamage": 0, "tilt": 0, "barkDamage": 0 },
  123 + "crown": { "looseBranch": 0, "collarAbnormal": 0, "ventilationBalance": 0 },
  124 + "weight": {
  125 + "treeSpeciesType": "深根性树种",
  126 + "plantingDate": "2015-06",
  127 + "isWindCorridor": false,
  128 + "treePoolType": "联通树池",
  129 + "treePoolWidthDbhRatio": "7 倍及以上"
  130 + },
  131 + "result": {
  132 + "isEmergency": false,
  133 + "windPower": null,
  134 + "defectScore": 5,
  135 + "normalResult": { "score": 5.0, "level": "I级 (基本无风险)" },
  136 + "emergencyResult": null
  137 + },
  138 + "status": {
  139 + "photos": ["https://xxx.com/photo1.jpg"]
  140 + }
  141 +}
  142 +```
  143 +
  144 +**weight 对象字段说明**:
  145 +
  146 +| 字段 | 类型 | 必填 | 说明 |
  147 +|------|------|------|------|
  148 +| `treeSpeciesType` | String | **是** | 树种类型文本:`"深根性树种"` 或 `"浅根性树种"` |
  149 +| `plantingDate` | String | 否 | 栽植年月,格式 `yyyy-MM`。**填写后系统自动计算年限和权重** |
  150 +| `plantingYears` | String | 否 | **已废弃**,无需传入。后端根据 `plantingDate` 自动计算 |
  151 +| `isWindCorridor` | Boolean | **是** | 是否处于风口 |
  152 +| `treePoolType` | String | **是** | 树池类型 |
  153 +| `treePoolWidthDbhRatio` | String | **是** | 树池宽度与胸径比 |
  154 +
  155 +**后端自动行为**:
  156 +- 根据 `plantingDate` 自动计算栽植年限(10年以内/10-30年/30年以上)和对应权重
  157 +- 提交成功后,自动将 `treeSpeciesType` 和 `plantingDate` **回写**到一树一档案详情
  158 +
  159 +### 3.2 获取巡检详情
  160 +
  161 +**接口**:`GET /garden/tree-inspection/get?id={id}`
  162 +
  163 +响应 `weight` 对象新增字段:
  164 +
  165 +```json
  166 +{
  167 + "code": 0,
  168 + "data": {
  169 + "id": 100,
  170 + "treeId": 1024,
  171 + "inspectionTime": "2026-06-05 14:30:00",
  172 + "weight": {
  173 + "treeSpeciesType": "深根性树种",
  174 + "treeSpeciesWeight": 1.0,
  175 + "plantingYears": "栽植 10-30 年",
  176 + "plantingYearsWeight": 1.1,
  177 + "plantingDate": "2015-06",
  178 + "isWindCorridor": false,
  179 + "windCorridorWeight": 1.0,
  180 + "treePoolType": "联通树池",
  181 + "treePoolWeight": 1.0,
  182 + "treePoolWidthDbhRatio": "7 倍及以上",
  183 + "treePoolRatioWeight": 1.0
  184 + },
  185 + "...": "其他字段不变"
  186 + }
  187 +}
  188 +```
  189 +
  190 +| 新增字段 | 类型 | 说明 |
  191 +|---------|------|------|
  192 +| `weight.plantingDate` | String | 栽植年月(用户填写的原始值) |
  193 +| `weight.plantingYears` | String | 系统自动计算的栽植年限文本 |
  194 +
  195 +### 3.3 ⭐ 获取最近一次巡检记录(新增接口)
  196 +
  197 +**接口**:`GET /garden/tree-inspection/latest?treeId={treeId}`
  198 +
  199 +**用途**:汛期频繁巡检场景,用户提交第一次巡检后,后续每次打开新增界面时调用此接口,**自动带入上一次填写记录**,用户可修改后再提交。
  200 +
  201 +**请求参数**:
  202 +
  203 +| 参数 | 类型 | 必填 | 说明 |
  204 +|------|------|------|------|
  205 +| `treeId` | Long | 是 | 一树一档案ID |
  206 +
  207 +**响应**:
  208 +
  209 +```json
  210 +{
  211 + "code": 0,
  212 + "data": {
  213 + "id": 100,
  214 + "treeId": 1024,
  215 + "treenumber": "D0001-P1-0001",
  216 + "inspectionTime": "2026-06-05 14:30:00",
  217 + "inspectorId": 10001,
  218 + "inspectorName": "张三",
  219 + "root": { "disease": 0, "anchorage": 0, "cutting": 0 },
  220 + "collar": { "woodDamage": 5, "barkDamage": 0, "loosening": 0 },
  221 + "trunk": { "woodDamage": 0, "tilt": 0, "barkDamage": 0 },
  222 + "crown": { "looseBranch": 0, "collarAbnormal": 0, "ventilationBalance": 0 },
  223 + "weight": {
  224 + "treeSpeciesType": "深根性树种",
  225 + "treeSpeciesWeight": 1.0,
  226 + "plantingYears": "栽植 10-30 年",
  227 + "plantingYearsWeight": 1.1,
  228 + "plantingDate": "2015-06",
  229 + "isWindCorridor": false,
  230 + "windCorridorWeight": 1.0,
  231 + "treePoolType": "联通树池",
  232 + "treePoolWeight": 1.0,
  233 + "treePoolWidthDbhRatio": "7 倍及以上",
  234 + "treePoolRatioWeight": 1.0
  235 + },
  236 + "result": {
  237 + "isEmergency": false,
  238 + "windPower": null,
  239 + "windPowerWeight": 1.0,
  240 + "defectScore": 5,
  241 + "normalResult": { "score": 5.0, "level": "I级 (基本无风险)" },
  242 + "emergencyResult": null
  243 + },
  244 + "status": {
  245 + "photos": ["https://xxx.com/photo1.jpg"]
  246 + }
  247 + }
  248 +}
  249 +```
  250 +
  251 +> **特殊情况**:如果该树木没有任何巡检记录,`data` 返回 `null`。
  252 +
  253 +**前端处理逻辑**:
  254 +1. 用户点击"新增巡检"时,调用 `GET /latest?treeId=xxx`
  255 +2. 如果 `data` 不为 `null`,将返回的各字段值预填到表单中(**注意不要预填 `id`、`inspectionTime`、`inspectorId`、`inspectorName` 等系统字段**)
  256 +3. 用户可在预填基础上修改,最终提交的数据以用户最后修改为准
  257 +
  258 +---
  259 +
  260 +## 四、前端改造要点清单
  261 +
  262 +### 4.1 PC端-行道树新增/编辑页
  263 +
  264 +- [ ] "估测年龄"字段 → 改为"栽种年月",使用**年月选择器**(`yyyy-MM`),绑定 `plantingDate`
  265 +- [ ] 新增"树种类型"字段,使用**下拉选择**,数据源为字典 `tree_species_type`,绑定 `treeSpeciesType`,**必选**
  266 +
  267 +### 4.2 PC端-行道树详情页
  268 +
  269 +- [ ] "估测树龄"字段 → 改为显示"栽种年月",取 `plantingDate` 值
  270 +- [ ] 新增"树种类型"展示,`treeSpeciesType` 通过字典翻译显示
  271 +
  272 +### 4.3 PC端-行道树列表页
  273 +
  274 +- [ ] 检索区域新增"树种类型"筛选项(下拉,数据源为字典 `tree_species_type`)
  275 +- [ ] 列表表头在"胸径"后新增"树种类别"列,值通过字典翻译
  276 +
  277 +### 4.4 PC端-巡检记录展示
  278 +
  279 +- [ ] 第13项"该行道树属于什么树种?"展示 `weight.treeSpeciesType`
  280 +- [ ] 第14项"该行道树的栽植年限属于哪一种?"改为展示 `weight.plantingDate`(栽植年月)+ `weight.plantingYears`(自动计算的年限)
  281 +
  282 +### 4.5 小程序端-树木详情页
  283 +
  284 +- [ ] "预估年龄" → 改为"栽植年月",取 `plantingDate` 值
  285 +- [ ] 非必填项中去掉"请输入"三个字的占位文本
  286 +
  287 +### 4.6 小程序端-新增行道树录入
  288 +
  289 +- [ ] "预估年龄" → 改为"栽种年月",使用年月选择器,绑定 `plantingDate`
  290 +- [ ] 新增"树种类型"选择,字典 `tree_species_type`
  291 +
  292 +### 4.7 小程序端-巡检表单
  293 +
  294 +- [ ] 第13项"该行道树属于什么树种?"保持单选(深根性/浅根性),绑定 `weight.treeSpeciesType`
  295 +- [ ] 第14项"该行道树的栽植年限?"原三选一 → 改为**年月选择器**,绑定 `weight.plantingDate`,系统自动显示计算出的年限
  296 +- [ ] **汛期预填**:进入新增巡检页面时,调用 `GET /latest?treeId=xxx`,若有数据则预填表单
  297 +
  298 +---
  299 +
  300 +## 五、注意事项
  301 +
  302 +1. `estimationtreeage` 和 `plantingYears` 字段虽然废弃但**后端仍保留兼容**,前端可以分阶段迁移
  303 +2. 巡检提交接口中 `weight.treeSpeciesType` 传的是**文本**("深根性树种"/"浅根性树种"),不是字典值;而一树一档案中 `treeSpeciesType` 存的也是相同文本
  304 +3. `plantingDate` 格式必须严格为 `yyyy-MM`(如 `"2015-06"`),否则后端解析会失败并降级处理
  305 +4. `/latest` 接口返回 `data` 为 `null` 时表示无历史巡检记录,前端应展示空表单
... ...
sql/tree_inspection_planting_date.sql 0 → 100644
  1 +-- =============================================
  2 +-- 2026年6月 行道树巡查新增需求 数据库变更
  3 +-- =============================================
  4 +
  5 +-- 1. garden_tree 表新增字段
  6 +ALTER TABLE garden_tree ADD COLUMN planting_date VARCHAR(10) COMMENT '栽种年月(格式yyyy-MM)';
  7 +ALTER TABLE garden_tree ADD COLUMN tree_species_type VARCHAR(20) COMMENT '树种类型(字典类型:tree_species_type)';
  8 +
  9 +-- 2. garden_tree_inspection 表新增字段
  10 +ALTER TABLE garden_tree_inspection ADD COLUMN planting_date VARCHAR(10) COMMENT '栽植年月(格式yyyy-MM)';
  11 +
  12 +-- 3. 插入字典类型
  13 +INSERT INTO system_dict_type (name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
  14 +VALUES ('树种类型', 'tree_species_type', 0, '行道树树种类型(深根性/浅根性)', '1', NOW(), '1', NOW(), 0, NULL);
  15 +
  16 +-- 4. 插入字典数据(注意:dict_type 对应上面的 type 值)
  17 +INSERT INTO system_dict_data (sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted, deleted_time)
  18 +VALUES (1, '深根性树种', '1', 'tree_species_type', 0, 'default', '', '深根性树种', '1', NOW(), '1', NOW(), 0, NULL);
  19 +
  20 +INSERT INTO system_dict_data (sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted, deleted_time)
  21 +VALUES (2, '浅根性树种', '2', 'tree_species_type', 0, 'default', '', '浅根性树种', '1', NOW(), '1', NOW(), 0, NULL);
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreePageReqVO.java
... ... @@ -64,9 +64,16 @@ public class TreePageReqVO extends PageParam {
64 64 @Schema(description = "数据状态(已更新、未更新)")
65 65 private String datastate;
66 66  
67   - @Schema(description = "估测树龄")
  67 + @Schema(description = "估测树龄(已废弃)")
  68 + @Deprecated
68 69 private String estimationtreeage;
69 70  
  71 + @Schema(description = "栽种年月(格式yyyy-MM)")
  72 + private String plantingDate;
  73 +
  74 + @Schema(description = "树种类型(字典类型:tree_species_type)")
  75 + private String treeSpeciesType;
  76 +
70 77 @Schema(description = "冠幅东西")
71 78 private String canopyeastwest;
72 79  
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreeRespVO.java
... ... @@ -100,10 +100,19 @@ public class TreeRespVO implements VO {
100 100 // @ExcelProperty("数据状态(已更新、未更新)")
101 101 private String datastate;
102 102  
103   - @Schema(description = "估测树龄")
  103 + @Schema(description = "估测树龄(已废弃,请使用plantingDate栽种年月字段)")
104 104 @ExcelProperty("估测树龄")
  105 + @Deprecated
105 106 private String estimationtreeage;
106 107  
  108 + @Schema(description = "栽种年月(格式yyyy-MM)", example = "2015-06")
  109 + @ExcelProperty("栽种年月")
  110 + private String plantingDate;
  111 +
  112 + @Schema(description = "树种类型(字典类型:tree_species_type)", example = "1")
  113 + @ExcelProperty("树种类别")
  114 + private String treeSpeciesType;
  115 +
107 116 @Schema(description = "冠幅东西")
108 117 @ExcelProperty("冠幅东西")
109 118 private String canopyeastwest;
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreeSaveReqVO.java
... ... @@ -69,9 +69,16 @@ public class TreeSaveReqVO {
69 69 @Schema(description = "数据状态(已更新、未更新)")
70 70 private String datastate;
71 71  
72   - @Schema(description = "估测树龄")
  72 + @Schema(description = "估测树龄(已废弃,请使用plantingDate栽种年月字段)")
  73 + @Deprecated
73 74 private String estimationtreeage;
74 75  
  76 + @Schema(description = "栽种年月(格式yyyy-MM)", example = "2015-06")
  77 + private String plantingDate;
  78 +
  79 + @Schema(description = "树种类型(字典类型:tree_species_type)", example = "1")
  80 + private String treeSpeciesType;
  81 +
75 82 @Schema(description = "冠幅东西")
76 83 private String canopyeastwest;
77 84  
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treeinspection/TreeInspectionController.java
... ... @@ -47,4 +47,12 @@ public class TreeInspectionController {
47 47 return success(respPageResult);
48 48 }
49 49  
  50 + @GetMapping("/latest")
  51 + @Operation(summary = "获取最近一次巡检记录")
  52 + @Parameter(name = "treeId", description = "一树一档案ID", required = true, example = "1024")
  53 + @PreAuthorize("@ss.hasPermission('garden:tree-inspection:query')")
  54 + public CommonResult<TreeInspectionRespVO> getLatestTreeInspection(@RequestParam("treeId") Long treeId) {
  55 + return success(treeInspectionService.getLatestTreeInspection(treeId));
  56 + }
  57 +
50 58 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/AppTreeInspectionController.java
... ... @@ -50,4 +50,11 @@ public class AppTreeInspectionController {
50 50 return success(respPageResult);
51 51 }
52 52  
  53 + @GetMapping("/latest")
  54 + @Operation(summary = "获取最近一次巡检记录(汛期频繁巡检预填)")
  55 + @Parameter(name = "treeId", description = "一树一档案ID", required = true, example = "1024")
  56 + public CommonResult<TreeInspectionRespVO> getLatestTreeInspection(@RequestParam("treeId") Long treeId) {
  57 + return success(treeInspectionService.getLatestTreeInspection(treeId));
  58 + }
  59 +
53 60 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionRespVO.java
... ... @@ -123,6 +123,9 @@ public class TreeInspectionRespVO {
123 123 @Schema(description = "栽植年限权重常数", example = "1.1")
124 124 private Double plantingYearsWeight;
125 125  
  126 + @Schema(description = "栽植年月(格式yyyy-MM)", example = "2015-06")
  127 + private String plantingDate;
  128 +
126 129 @Schema(description = "是否处于风口", example = "false")
127 130 private Boolean isWindCorridor;
128 131  
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionSaveReqVO.java
... ... @@ -133,10 +133,13 @@ public class TreeInspectionSaveReqVO {
133 133 @NotBlank(message = "树种类型不能为空")
134 134 private String treeSpeciesType;
135 135  
136   - @Schema(description = "栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上", requiredMode = Schema.RequiredMode.REQUIRED, example = "栽植 10-30 年")
137   - @NotBlank(message = "栽植年限不能为空")
  136 + @Schema(description = "栽植年限(已废弃,请使用plantingDate)", example = "栽植 10-30 年")
  137 + @Deprecated
138 138 private String plantingYears;
139 139  
  140 + @Schema(description = "栽植年月(格式yyyy-MM)", example = "2015-06")
  141 + private String plantingDate;
  142 +
140 143 @Schema(description = "是否处于风口", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
141 144 @NotNull(message = "是否处于风口标识不能为空")
142 145 private Boolean isWindCorridor;
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/convert/treeinspection/TreeInspectionConvert.java
... ... @@ -29,6 +29,7 @@ public interface TreeInspectionConvert {
29 29 @Mapping(source = "crown.ventilationBalance", target = "crownVentilationBalance"),
30 30 @Mapping(source = "weight.treeSpeciesType", target = "treeSpeciesType"),
31 31 @Mapping(source = "weight.plantingYears", target = "plantingYears"),
  32 + @Mapping(source = "weight.plantingDate", target = "plantingDate"),
32 33 @Mapping(source = "weight.isWindCorridor", target = "isWindCorridor"),
33 34 @Mapping(source = "weight.treePoolType", target = "treePoolType"),
34 35 @Mapping(source = "weight.treePoolWidthDbhRatio", target = "treePoolWidthDbhRatio"),
... ... @@ -60,6 +61,7 @@ public interface TreeInspectionConvert {
60 61 @Mapping(source = "treeSpeciesWeight", target = "weight.treeSpeciesWeight"),
61 62 @Mapping(source = "plantingYears", target = "weight.plantingYears"),
62 63 @Mapping(source = "plantingYearsWeight", target = "weight.plantingYearsWeight"),
  64 + @Mapping(source = "plantingDate", target = "weight.plantingDate"),
63 65 @Mapping(source = "isWindCorridor", target = "weight.isWindCorridor"),
64 66 @Mapping(source = "windCorridorWeight", target = "weight.windCorridorWeight"),
65 67 @Mapping(source = "treePoolType", target = "weight.treePoolType"),
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/tree/TreeDO.java
... ... @@ -176,6 +176,16 @@ public class TreeDO {
176 176 */
177 177 private Long companyId;
178 178  
  179 + /**
  180 + * 栽种年月(格式 yyyy-MM)
  181 + */
  182 + private String plantingDate;
  183 +
  184 + /**
  185 + * 树种类型(字典类型:tree_species_type)
  186 + */
  187 + private String treeSpeciesType;
  188 +
179 189 @TableField(exist = false)
180 190 private List<String> treeImgList;
181 191  
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treeinspection/TreeInspectionDO.java
... ... @@ -138,6 +138,11 @@ public class TreeInspectionDO extends TenantBaseDO {
138 138 private String plantingYears;
139 139  
140 140 /**
  141 + * 栽植年月(格式 yyyy-MM),由用户在巡检时填写
  142 + */
  143 + private String plantingDate;
  144 +
  145 + /**
141 146 * 栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2
142 147 */
143 148 private Double plantingYearsWeight;
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/tree/TreeMapper.java
... ... @@ -47,6 +47,7 @@ public interface TreeMapper extends BaseMapperX&lt;TreeDO&gt; {
47 47 .eqIfPresent(TreeDO::getOldtreeownership, reqVO.getOldtreeownership())
48 48 .eqIfPresent(TreeDO::getDatastate, reqVO.getDatastate())
49 49 .eqIfPresent(TreeDO::getEstimationtreeage, reqVO.getEstimationtreeage())
  50 + .eqIfPresent(TreeDO::getTreeSpeciesType, reqVO.getTreeSpeciesType())
50 51 .eqIfPresent(TreeDO::getCanopyeastwest, reqVO.getCanopyeastwest())
51 52 .eqIfPresent(TreeDO::getCanopysouthnorth, reqVO.getCanopysouthnorth())
52 53 .eqIfPresent(TreeDO::getWeekday, reqVO.getWeekday())
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treeinspection/TreeInspectionMapper.java
... ... @@ -23,4 +23,16 @@ public interface TreeInspectionMapper extends BaseMapperX&lt;TreeInspectionDO&gt; {
23 23 );
24 24 }
25 25  
  26 + /**
  27 + * 查询某棵树最近一次巡检记录
  28 + */
  29 + default TreeInspectionDO selectLatestByTreeId(Long treeId) {
  30 + return selectOne(new LambdaQueryWrapperX<TreeInspectionDO>()
  31 + .eq(TreeInspectionDO::getTreeId, treeId)
  32 + .orderByDesc(TreeInspectionDO::getInspectionTime)
  33 + .orderByDesc(TreeInspectionDO::getId)
  34 + .last("LIMIT 1")
  35 + );
  36 + }
  37 +
26 38 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionService.java
... ... @@ -36,4 +36,12 @@ public interface TreeInspectionService {
36 36 */
37 37 PageResult<TreeInspectionDO> getTreeInspectionPage(TreeInspectionPageReqVO pageReqVO);
38 38  
  39 + /**
  40 + * 获取某棵树最近一次巡检记录(用于汛期频繁巡检预填)
  41 + *
  42 + * @param treeId 一树一档案ID
  43 + * @return 最近一次巡检记录的详情 VO,无记录则返回 null
  44 + */
  45 + TreeInspectionRespVO getLatestTreeInspection(Long treeId);
  46 +
39 47 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImpl.java
... ... @@ -4,8 +4,14 @@ import lombok.extern.slf4j.Slf4j;
4 4 import org.springframework.stereotype.Service;
5 5 import org.springframework.validation.annotation.Validated;
6 6 import org.springframework.transaction.annotation.Transactional;
  7 +import org.springframework.util.StringUtils;
7 8 import jakarta.annotation.Resource;
8 9  
  10 +import java.time.LocalDate;
  11 +import java.time.YearMonth;
  12 +import java.time.format.DateTimeFormatter;
  13 +import java.time.temporal.ChronoUnit;
  14 +
9 15 import com.zteits.urbanops.framework.common.pojo.PageResult;
10 16 import com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil;
11 17 import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils;
... ... @@ -61,7 +67,16 @@ public class TreeInspectionServiceImpl implements TreeInspectionService {
61 67 double treeSpeciesWeight = "浅根性树种".equals(entity.getTreeSpeciesType()) ? 1.1 : 1.0;
62 68 entity.setTreeSpeciesWeight(treeSpeciesWeight);
63 69  
64   - double plantingYearsWeight = getPlantingYearsWeight(entity.getPlantingYears());
  70 + // 4.1 如果传入了 plantingDate(栽植年月),自动计算 plantingYears 文本和权重
  71 + double plantingYearsWeight;
  72 + if (StringUtils.hasText(entity.getPlantingDate())) {
  73 + String autoPlantingYears = calculatePlantingYearsFromDate(entity.getPlantingDate());
  74 + entity.setPlantingYears(autoPlantingYears);
  75 + plantingYearsWeight = getPlantingYearsWeight(autoPlantingYears);
  76 + } else {
  77 + // 兼容旧接口:直接使用 plantingYears 文本
  78 + plantingYearsWeight = getPlantingYearsWeight(entity.getPlantingYears());
  79 + }
65 80 entity.setPlantingYearsWeight(plantingYearsWeight);
66 81  
67 82 double windCorridorWeight = Boolean.TRUE.equals(entity.getIsWindCorridor()) ? 2.0 : 1.0;
... ... @@ -134,6 +149,21 @@ public class TreeInspectionServiceImpl implements TreeInspectionService {
134 149  
135 150 // 7. 保存到数据库
136 151 treeInspectionMapper.insert(entity);
  152 +
  153 + // 8. 将巡检记录中的树种类型和栽植年月回写到一树一档案
  154 + boolean needUpdateTree = false;
  155 + if (StringUtils.hasText(entity.getTreeSpeciesType())) {
  156 + tree.setTreeSpeciesType(entity.getTreeSpeciesType());
  157 + needUpdateTree = true;
  158 + }
  159 + if (StringUtils.hasText(entity.getPlantingDate())) {
  160 + tree.setPlantingDate(entity.getPlantingDate());
  161 + needUpdateTree = true;
  162 + }
  163 + if (needUpdateTree) {
  164 + treeMapper.updateById(tree);
  165 + }
  166 +
137 167 return entity.getId();
138 168 }
139 169  
... ... @@ -151,8 +181,38 @@ public class TreeInspectionServiceImpl implements TreeInspectionService {
151 181 return treeInspectionMapper.selectPage(pageReqVO);
152 182 }
153 183  
  184 + @Override
  185 + public TreeInspectionRespVO getLatestTreeInspection(Long treeId) {
  186 + TreeInspectionDO latest = treeInspectionMapper.selectLatestByTreeId(treeId);
  187 + if (latest == null) {
  188 + return null;
  189 + }
  190 + return TreeInspectionConvert.INSTANCE.convert(latest);
  191 + }
  192 +
154 193 // ========== 权重因子规则辅助类 ==========
155 194  
  195 + /**
  196 + * 根据栽植年月(yyyy-MM)计算栽植年限文本
  197 + */
  198 + private String calculatePlantingYearsFromDate(String plantingDate) {
  199 + try {
  200 + YearMonth planting = YearMonth.parse(plantingDate, DateTimeFormatter.ofPattern("yyyy-MM"));
  201 + YearMonth now = YearMonth.now();
  202 + long years = ChronoUnit.YEARS.between(planting, now);
  203 + if (years < 10) {
  204 + return "栽植 10 年以内";
  205 + } else if (years <= 30) {
  206 + return "栽植 10-30 年";
  207 + } else {
  208 + return "栽植 30 年以上";
  209 + }
  210 + } catch (Exception e) {
  211 + log.warn("解析栽植年月失败: {}", plantingDate, e);
  212 + return null;
  213 + }
  214 + }
  215 +
156 216 private double getPlantingYearsWeight(String years) {
157 217 if (years == null) return 1.0;
158 218 if (years.contains("30年")) return 1.2;
... ...