Commit 96cde8ad1629f1e24d39492040a6e5568481f8be
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,9 +64,16 @@ public class TreePageReqVO extends PageParam { | ||
| 64 | @Schema(description = "数据状态(已更新、未更新)") | 64 | @Schema(description = "数据状态(已更新、未更新)") |
| 65 | private String datastate; | 65 | private String datastate; |
| 66 | 66 | ||
| 67 | - @Schema(description = "估测树龄") | 67 | + @Schema(description = "估测树龄(已废弃)") |
| 68 | + @Deprecated | ||
| 68 | private String estimationtreeage; | 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 | @Schema(description = "冠幅东西") | 77 | @Schema(description = "冠幅东西") |
| 71 | private String canopyeastwest; | 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,10 +100,19 @@ public class TreeRespVO implements VO { | ||
| 100 | // @ExcelProperty("数据状态(已更新、未更新)") | 100 | // @ExcelProperty("数据状态(已更新、未更新)") |
| 101 | private String datastate; | 101 | private String datastate; |
| 102 | 102 | ||
| 103 | - @Schema(description = "估测树龄") | 103 | + @Schema(description = "估测树龄(已废弃,请使用plantingDate栽种年月字段)") |
| 104 | @ExcelProperty("估测树龄") | 104 | @ExcelProperty("估测树龄") |
| 105 | + @Deprecated | ||
| 105 | private String estimationtreeage; | 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 | @Schema(description = "冠幅东西") | 116 | @Schema(description = "冠幅东西") |
| 108 | @ExcelProperty("冠幅东西") | 117 | @ExcelProperty("冠幅东西") |
| 109 | private String canopyeastwest; | 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,9 +69,16 @@ public class TreeSaveReqVO { | ||
| 69 | @Schema(description = "数据状态(已更新、未更新)") | 69 | @Schema(description = "数据状态(已更新、未更新)") |
| 70 | private String datastate; | 70 | private String datastate; |
| 71 | 71 | ||
| 72 | - @Schema(description = "估测树龄") | 72 | + @Schema(description = "估测树龄(已废弃,请使用plantingDate栽种年月字段)") |
| 73 | + @Deprecated | ||
| 73 | private String estimationtreeage; | 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 | @Schema(description = "冠幅东西") | 82 | @Schema(description = "冠幅东西") |
| 76 | private String canopyeastwest; | 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,4 +47,12 @@ public class TreeInspectionController { | ||
| 47 | return success(respPageResult); | 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,4 +50,11 @@ public class AppTreeInspectionController { | ||
| 50 | return success(respPageResult); | 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,6 +123,9 @@ public class TreeInspectionRespVO { | ||
| 123 | @Schema(description = "栽植年限权重常数", example = "1.1") | 123 | @Schema(description = "栽植年限权重常数", example = "1.1") |
| 124 | private Double plantingYearsWeight; | 124 | private Double plantingYearsWeight; |
| 125 | 125 | ||
| 126 | + @Schema(description = "栽植年月(格式yyyy-MM)", example = "2015-06") | ||
| 127 | + private String plantingDate; | ||
| 128 | + | ||
| 126 | @Schema(description = "是否处于风口", example = "false") | 129 | @Schema(description = "是否处于风口", example = "false") |
| 127 | private Boolean isWindCorridor; | 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,10 +133,13 @@ public class TreeInspectionSaveReqVO { | ||
| 133 | @NotBlank(message = "树种类型不能为空") | 133 | @NotBlank(message = "树种类型不能为空") |
| 134 | private String treeSpeciesType; | 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 | private String plantingYears; | 138 | private String plantingYears; |
| 139 | 139 | ||
| 140 | + @Schema(description = "栽植年月(格式yyyy-MM)", example = "2015-06") | ||
| 141 | + private String plantingDate; | ||
| 142 | + | ||
| 140 | @Schema(description = "是否处于风口", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") | 143 | @Schema(description = "是否处于风口", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") |
| 141 | @NotNull(message = "是否处于风口标识不能为空") | 144 | @NotNull(message = "是否处于风口标识不能为空") |
| 142 | private Boolean isWindCorridor; | 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,6 +29,7 @@ public interface TreeInspectionConvert { | ||
| 29 | @Mapping(source = "crown.ventilationBalance", target = "crownVentilationBalance"), | 29 | @Mapping(source = "crown.ventilationBalance", target = "crownVentilationBalance"), |
| 30 | @Mapping(source = "weight.treeSpeciesType", target = "treeSpeciesType"), | 30 | @Mapping(source = "weight.treeSpeciesType", target = "treeSpeciesType"), |
| 31 | @Mapping(source = "weight.plantingYears", target = "plantingYears"), | 31 | @Mapping(source = "weight.plantingYears", target = "plantingYears"), |
| 32 | + @Mapping(source = "weight.plantingDate", target = "plantingDate"), | ||
| 32 | @Mapping(source = "weight.isWindCorridor", target = "isWindCorridor"), | 33 | @Mapping(source = "weight.isWindCorridor", target = "isWindCorridor"), |
| 33 | @Mapping(source = "weight.treePoolType", target = "treePoolType"), | 34 | @Mapping(source = "weight.treePoolType", target = "treePoolType"), |
| 34 | @Mapping(source = "weight.treePoolWidthDbhRatio", target = "treePoolWidthDbhRatio"), | 35 | @Mapping(source = "weight.treePoolWidthDbhRatio", target = "treePoolWidthDbhRatio"), |
| @@ -60,6 +61,7 @@ public interface TreeInspectionConvert { | @@ -60,6 +61,7 @@ public interface TreeInspectionConvert { | ||
| 60 | @Mapping(source = "treeSpeciesWeight", target = "weight.treeSpeciesWeight"), | 61 | @Mapping(source = "treeSpeciesWeight", target = "weight.treeSpeciesWeight"), |
| 61 | @Mapping(source = "plantingYears", target = "weight.plantingYears"), | 62 | @Mapping(source = "plantingYears", target = "weight.plantingYears"), |
| 62 | @Mapping(source = "plantingYearsWeight", target = "weight.plantingYearsWeight"), | 63 | @Mapping(source = "plantingYearsWeight", target = "weight.plantingYearsWeight"), |
| 64 | + @Mapping(source = "plantingDate", target = "weight.plantingDate"), | ||
| 63 | @Mapping(source = "isWindCorridor", target = "weight.isWindCorridor"), | 65 | @Mapping(source = "isWindCorridor", target = "weight.isWindCorridor"), |
| 64 | @Mapping(source = "windCorridorWeight", target = "weight.windCorridorWeight"), | 66 | @Mapping(source = "windCorridorWeight", target = "weight.windCorridorWeight"), |
| 65 | @Mapping(source = "treePoolType", target = "weight.treePoolType"), | 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,6 +176,16 @@ public class TreeDO { | ||
| 176 | */ | 176 | */ |
| 177 | private Long companyId; | 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 | @TableField(exist = false) | 189 | @TableField(exist = false) |
| 180 | private List<String> treeImgList; | 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,6 +138,11 @@ public class TreeInspectionDO extends TenantBaseDO { | ||
| 138 | private String plantingYears; | 138 | private String plantingYears; |
| 139 | 139 | ||
| 140 | /** | 140 | /** |
| 141 | + * 栽植年月(格式 yyyy-MM),由用户在巡检时填写 | ||
| 142 | + */ | ||
| 143 | + private String plantingDate; | ||
| 144 | + | ||
| 145 | + /** | ||
| 141 | * 栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2 | 146 | * 栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2 |
| 142 | */ | 147 | */ |
| 143 | private Double plantingYearsWeight; | 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<TreeDO> { | @@ -47,6 +47,7 @@ public interface TreeMapper extends BaseMapperX<TreeDO> { | ||
| 47 | .eqIfPresent(TreeDO::getOldtreeownership, reqVO.getOldtreeownership()) | 47 | .eqIfPresent(TreeDO::getOldtreeownership, reqVO.getOldtreeownership()) |
| 48 | .eqIfPresent(TreeDO::getDatastate, reqVO.getDatastate()) | 48 | .eqIfPresent(TreeDO::getDatastate, reqVO.getDatastate()) |
| 49 | .eqIfPresent(TreeDO::getEstimationtreeage, reqVO.getEstimationtreeage()) | 49 | .eqIfPresent(TreeDO::getEstimationtreeage, reqVO.getEstimationtreeage()) |
| 50 | + .eqIfPresent(TreeDO::getTreeSpeciesType, reqVO.getTreeSpeciesType()) | ||
| 50 | .eqIfPresent(TreeDO::getCanopyeastwest, reqVO.getCanopyeastwest()) | 51 | .eqIfPresent(TreeDO::getCanopyeastwest, reqVO.getCanopyeastwest()) |
| 51 | .eqIfPresent(TreeDO::getCanopysouthnorth, reqVO.getCanopysouthnorth()) | 52 | .eqIfPresent(TreeDO::getCanopysouthnorth, reqVO.getCanopysouthnorth()) |
| 52 | .eqIfPresent(TreeDO::getWeekday, reqVO.getWeekday()) | 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<TreeInspectionDO> { | @@ -23,4 +23,16 @@ public interface TreeInspectionMapper extends BaseMapperX<TreeInspectionDO> { | ||
| 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,4 +36,12 @@ public interface TreeInspectionService { | ||
| 36 | */ | 36 | */ |
| 37 | PageResult<TreeInspectionDO> getTreeInspectionPage(TreeInspectionPageReqVO pageReqVO); | 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,8 +4,14 @@ import lombok.extern.slf4j.Slf4j; | ||
| 4 | import org.springframework.stereotype.Service; | 4 | import org.springframework.stereotype.Service; |
| 5 | import org.springframework.validation.annotation.Validated; | 5 | import org.springframework.validation.annotation.Validated; |
| 6 | import org.springframework.transaction.annotation.Transactional; | 6 | import org.springframework.transaction.annotation.Transactional; |
| 7 | +import org.springframework.util.StringUtils; | ||
| 7 | import jakarta.annotation.Resource; | 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 | import com.zteits.urbanops.framework.common.pojo.PageResult; | 15 | import com.zteits.urbanops.framework.common.pojo.PageResult; |
| 10 | import com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil; | 16 | import com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil; |
| 11 | import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; | 17 | import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; |
| @@ -61,7 +67,16 @@ public class TreeInspectionServiceImpl implements TreeInspectionService { | @@ -61,7 +67,16 @@ public class TreeInspectionServiceImpl implements TreeInspectionService { | ||
| 61 | double treeSpeciesWeight = "浅根性树种".equals(entity.getTreeSpeciesType()) ? 1.1 : 1.0; | 67 | double treeSpeciesWeight = "浅根性树种".equals(entity.getTreeSpeciesType()) ? 1.1 : 1.0; |
| 62 | entity.setTreeSpeciesWeight(treeSpeciesWeight); | 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 | entity.setPlantingYearsWeight(plantingYearsWeight); | 80 | entity.setPlantingYearsWeight(plantingYearsWeight); |
| 66 | 81 | ||
| 67 | double windCorridorWeight = Boolean.TRUE.equals(entity.getIsWindCorridor()) ? 2.0 : 1.0; | 82 | double windCorridorWeight = Boolean.TRUE.equals(entity.getIsWindCorridor()) ? 2.0 : 1.0; |
| @@ -134,6 +149,21 @@ public class TreeInspectionServiceImpl implements TreeInspectionService { | @@ -134,6 +149,21 @@ public class TreeInspectionServiceImpl implements TreeInspectionService { | ||
| 134 | 149 | ||
| 135 | // 7. 保存到数据库 | 150 | // 7. 保存到数据库 |
| 136 | treeInspectionMapper.insert(entity); | 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 | return entity.getId(); | 167 | return entity.getId(); |
| 138 | } | 168 | } |
| 139 | 169 | ||
| @@ -151,8 +181,38 @@ public class TreeInspectionServiceImpl implements TreeInspectionService { | @@ -151,8 +181,38 @@ public class TreeInspectionServiceImpl implements TreeInspectionService { | ||
| 151 | return treeInspectionMapper.selectPage(pageReqVO); | 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 | private double getPlantingYearsWeight(String years) { | 216 | private double getPlantingYearsWeight(String years) { |
| 157 | if (years == null) return 1.0; | 217 | if (years == null) return 1.0; |
| 158 | if (years.contains("30年")) return 1.2; | 218 | if (years.contains("30年")) return 1.2; |