Commit 04bc6697f25a776eb605a7afcef6bd2e090957ed

Authored by 颜惠青
2 parents 8db6dcd5 59879bf3

Merge branch 'dev'

Showing 93 changed files with 6678 additions and 89 deletions
AGENTS.md 0 → 100644
  1 +# AGENTS.md
  2 +
  3 +This document provides guidance for agentic coding tools working on the UrbanOps (urbanops) codebase.
  4 +
  5 +## Build & Test Commands
  6 +
  7 +### Build Commands
  8 +```bash
  9 +# Clean and compile all modules
  10 +mvn clean compile
  11 +
  12 +# Build entire project (skipping tests)
  13 +mvn clean install -DskipTests
  14 +
  15 +# Build specific module with dependencies
  16 +mvn clean install -DskipTests -pl <module-name> -am
  17 +```
  18 +
  19 +### Test Commands
  20 +```bash
  21 +# Run all tests
  22 +mvn test
  23 +
  24 +# Run all tests for a specific module
  25 +mvn test -pl urbanops-module-system
  26 +
  27 +# Run a single test class
  28 +mvn test -Dtest=AdminUserServiceImplTest
  29 +
  30 +# Run a single test method
  31 +mvn test -Dtest=AdminUserServiceImplTest#testCreateUser
  32 +
  33 +# Run tests with specific profile
  34 +mvn test -Punit-test
  35 +```
  36 +
  37 +## Project Structure
  38 +
  39 +- `urbanops-server` - Main Spring Boot application entry point
  40 +- `urbanops-module-system` - System management (users, roles, permissions, etc.)
  41 +- `urbanops-module-infra` - Infrastructure (files, jobs, configs, code generation)
  42 +- `urbanops-module-bpm` - Business Process Management (Flowable)
  43 +- `urbanops-module-xxx` - Business domain modules (garden, workorder, report, etc.)
  44 +- `urbanops-framework` - Shared framework components (security, redis, mybatis, etc.)
  45 +- `urbanops-dependencies` - Maven dependency version management
  46 +
  47 +## Code Style Guidelines
  48 +
  49 +### Package Structure
  50 +```
  51 +com.zteits.urbanops.module.{module-name}
  52 +├── controller/admin - Admin API controllers
  53 +├── controller/app - App API controllers
  54 +├── controller/bridge - Legacy bridge controllers
  55 +├── service/.../impl - Service implementations
  56 +├── dal/dataobject - Database entities (DO classes)
  57 +├── dal/mysql - MyBatis mappers
  58 +├── controller/.../vo - View Objects (Request/Response VOs)
  59 +├── convert - MapStruct converters
  60 +└── enums - Module-specific enums
  61 +```
  62 +
  63 +### Naming Conventions
  64 +- **Controllers**: `XxxController` (e.g., `UserController`)
  65 +- **Services**: `XxxService` (interface) and `XxxServiceImpl` (implementation)
  66 +- **Mappers**: `XxxMapper` (e.g., `AdminUserMapper`)
  67 +- **Data Objects**: `XxxDO` (e.g., `AdminUserDO`)
  68 +- **View Objects**: `XxxPageReqVO`, `XxxSaveReqVO`, `XxxRespVO`, `XxxSimpleRespVO`
  69 +- **Converters**: `XxxConvert` (MapStruct interface)
  70 +
  71 +### Code Organization
  72 +- DO classes extend `TenantBaseDO` or `BaseDO`, use `@TableName`, `@KeySequence`, `@Data`, `@EqualsAndHashCode(callSuper = true)`, `@Builder`
  73 +- VO classes use `@Schema` for documentation, `@NotBlank`/`@NotNull`/`@Size` for validation
  74 +- Controllers use `@Tag`, `@Operation`, `@PreAuthorize`, `@Valid`, `CommonResult` for responses
  75 +- Services use `@Service`, `@Slf4j`, `@Transactional(rollbackFor = Exception.class)`, `@Resource`
  76 +
  77 +### Error Handling
  78 +- Use `exception(ErrorCode)` from `ServiceExceptionUtil` to throw business exceptions
  79 +- Define error codes in module's `ErrorCodeConstants` interface
  80 +- Error code format: `1-002-xxx-xxx-xxx` (system module: 1-002-xxx-xxx-xxx)
  81 +
  82 +### Imports & Dependencies
  83 +- Organize: standard library → third-party → project packages
  84 +- No wildcard imports (e.g., avoid `import java.util.*`)
  85 +- Use Jakarta EE: `jakarta.*` imports
  86 +- Use Spring Boot 3.x and Spring Framework 6.x APIs
  87 +
  88 +### Validation
  89 +- Use Jakarta Bean Validation: `@NotNull`, `@NotBlank`, `@Size`, `@Pattern`, `@Email`
  90 +- Use `@Valid` for nested object validation
  91 +- Use `@AssertTrue` for complex validation with custom methods
  92 +
  93 +### Lombok Usage
  94 +- `@Data` for POJOs, `@Builder` for construction, `@EqualsAndHashCode(callSuper = true)` for DOs
  95 +- Configured in `lombok.config`: `lombok.accessors.chain=true`, `lombok.tostring.callsuper=CALL`
  96 +
  97 +### Testing Guidelines
  98 +- Extend `BaseMockitoUnitTest` (no DB) or `BaseDbUnitTest` (H2 database)
  99 +- Use JUnit 5: `@Test`, `@BeforeEach`, `@BeforeAll`
  100 +- Naming: `test{MethodName}_{scenario}`
  101 +- Use `assertPojoEquals()` for comparing DOs, `assertServiceException()` for exceptions
  102 +- Clean up test data via `@Sql` with `/sql/clean.sql`
  103 +
  104 +### Database
  105 +- Use MyBatis Plus, mappers extend `BaseMapper<XxxDO>`
  106 +- Use `TenantBaseDO` for multi-tenant tables, `BaseDO` for single-tenant
  107 +
  108 +### Security
  109 +- Use `@PreAuthorize` with format `{module}:{resource}:{action}` (e.g., `system:user:create`)
  110 +- Inject current user: `SecurityFrameworkUtils.getLoginUserId()`
  111 +
  112 +### Date/Time
  113 +- Use `java.time`, `LocalDateTime` for timestamps
  114 +- Format: `DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND`, `@DateTimeFormat`
  115 +
  116 +### Logging
  117 +- Use `@Slf4j`, levels: `ERROR`, `WARN`, `INFO`, `DEBUG`
  118 +- Log meaningful context, avoid sensitive info (passwords, tokens, PII)
  119 +
  120 +### API Documentation
  121 +- Use OpenAPI 3.0: `@Tag`, `@Operation`, `@Parameter`, `@Schema`
  122 +- Chinese descriptions in `@Schema`, mark required with `requiredMode`, provide `example` values
  123 +
  124 +### Module Communication
  125 +- Use module APIs (`urbanops-module-api`) for cross-module communication
  126 +- Use `@Lazy` to avoid circular dependencies
  127 +
  128 +### Adding New Features
  129 +1. Create DO class in `dal/dataobject`
  130 +2. Create Mapper interface in `dal/mysql`
  131 +3. Create Service interface and implementation
  132 +4. Create VO classes in `controller/.../vo`
  133 +5. Create Controller class
  134 +6. Create Convert interface (MapStruct)
  135 +7. Write unit tests extending appropriate base class
  136 +8. Add error codes to `ErrorCodeConstants`
  137 +
  138 +### Common Pitfalls
  139 +- Forgetting `@Transactional` on modifying service methods
  140 +- Using wrong DO base class (`BaseDO` vs `TenantBaseDO`)
  141 +- Not using `@Valid` for request body validation
  142 +- Not handling exceptions appropriately
  143 +- Direct database access bypassing Service layer
  144 +- Using `System.out.println` instead of logger
... ...
api_document.md 0 → 100644
  1 +# API 接口文档 - 行道树巡检记录与安全风险评估
  2 +
  3 +本文档用于指导微信小程序前端开发人员对接行道树巡检记录与安全风险评估接口。
  4 +为完美契合小程序详情页中按部位折叠面板的设计,接口设计采用了**分层嵌套结构**。
  5 +
  6 +---
  7 +
  8 +## 接口基础信息
  9 +
  10 +* **接口协议**:HTTP / HTTPS
  11 +* **请求格式**:`application/json;charset=utf-8`
  12 +* **响应格式**:`application/json;charset=utf-8`
  13 +* **接口基地址**:`/app-api/garden/tree-inspection` 或 `/app-api/business/tree-inspection`
  14 +* **认证方式**:请求头中需携带 `Authorization: Bearer <token>`
  15 +
  16 +---
  17 +
  18 +## 1. 提交巡检记录并进行风险评估
  19 +
  20 +小程序在巡检人员填写完成表单,点击提交按钮(图3)时调用该接口。
  21 +
  22 +* **接口路径**:`POST /create`
  23 +* **请求方法**:`POST`
  24 +
  25 +### 请求 JSON 参数说明 (`TreeInspectionSaveReqVO`)
  26 +
  27 +| 一级属性名 | 二级字段名 | 类型 | 是否必填 | 枚举值 / 说明 | 字段描述 |
  28 +| :--- | :--- | :--- | :--- | :--- | :--- |
  29 +| **treeId** | - | Long | 是 | 关联的树木档案 ID,如 `1024` | 树木 ID |
  30 +| **inspectionTime** | - | String | 是 | 格式 `"yyyy-MM-dd HH:mm:ss"` | 巡检发生的时间 |
  31 +| **root** | - | Object | 是 | 对应“树根部位”折叠面板 | 树根评估指标组 |
  32 +| | **disease** | Integer | 是 | `0`(无真菌危害/腐朽), `8`(存在危害/腐朽) | 是否存在病害? |
  33 +| | **anchorage** | Integer | 是 | `0`(良好无盘根隆起), `7`(存在隆起或盘根) | 根系下扎情况 |
  34 +| | **cutting** | Integer | 是 | `0`(无工程切根), `5`(存在工程切根) | 是否存在工程切根 |
  35 +| **collar** | - | Object | 是 | 对应“根颈部位”折叠面板 | 根颈评估指标组 |
  36 +| | **woodDamage** | Integer | 是 | `0`(无), `5`(<10%), `15`(10%-30%), `25`(30%-50%), `70`(>=50% 一票否决) | 根颈木质部受损 |
  37 +| | **barkDamage** | Integer | 是 | `0`(<10%), `2`(10%-30%), `4`(30%-50%), `6`(>=50%) | 根颈树皮受损 |
  38 +| | **loosening** | Integer | 是 | `0`(不存在松动), `100`(存在松动 一票否决) | 根颈是否松动 |
  39 +| **trunk** | - | Object | 是 | 对应“主干部位”折叠面板 | 主干评估指标组 |
  40 +| | **woodDamage** | Integer | 是 | `0`(无), `5`(<10%), `12`(10%-30%), `20`(30%-50%), `70`(>=50% 一票否决) | 主干木质部受损 |
  41 +| | **tilt** | Integer | 是 | `0`(<10°), `3`(10°-20°), `8`(20°-30°), `70`(>=30° 一票否决) | 主干倾斜度 |
  42 +| | **barkDamage** | Integer | 是 | `0`(<10%), `1`(10%-30%), `3`(30%-50%), `5`(>=50%) | 主干树皮受损 |
  43 +| **crown** | - | Object | 是 | 对应“树冠部位”折叠面板 | 树冠评估指标组 |
  44 +| | **looseBranch** | Integer | 是 | `0`(无易落枝), `2`(占比<1/10), `3`(占比>=1/10) | 观察是否存在易落枝 |
  45 +| | **collarAbnormal** | Integer | 是 | `0`(无异常), `3`(龟裂/卷皮), `5`(腐烂尚未成洞), `70`(空洞/蛀干 一票否决) | 枝干结合部异常 |
  46 +| | **ventilationBalance**| Integer | 是 | `0`(好不偏冠), `1`(偏冠或透风差不偏冠), `2`(偏冠冠幅适中), `5`(透风差冠幅大不偏冠), `8`(透风差大且偏冠) | 树冠透风与平衡性 |
  47 +| **weight** | - | Object | 是 | 对应“权重因子评估”面板 | 生理与生境权重因子组 |
  48 +| | **treeSpeciesType** | String | 是 | `"深根性树种"`, `"浅根性树种"` | 树种类型 (深根性1.0 / 浅根性1.1) |
  49 +| | **plantingYears** | String | 是 | `"栽植 10 年以内"`, `"栽植 10-30 年"`, `"栽植 30 年以上"` | 栽植年限 (对应 1.0, 1.1, 1.2) |
  50 +| | **isWindCorridor** | Boolean | 是 | `true`(是), `false`(否) | 是否处于风口 (对应权重 2.0 / 1.0) |
  51 +| | **treePoolType** | String | 是 | `"联通树池"`, `"独立树池"`, `"树池硬化"` | 树池类型 (对应 1.0, 1.2, 1.5) |
  52 +| | **treePoolWidthDbhRatio**| String| 是 | `"7 倍及以上"`, `"5 倍-7 倍"`, `"3 倍-5 倍"`, `"3 倍以下"` | 树池宽胸径比 (对应 1.0-1.3) |
  53 +| **result** | - | Object | 是 | 对应“风险评估结果”面板 | 风险评估结果表单项 |
  54 +| | **isEmergency** | Boolean | 是 | `true`(是), `false`(否) | 是否展开应急评估 |
  55 +| | **windPower** | String | 否 | `"7 级及以下"`, `"8-9 级"`, `"10 级"`, `"10 级以上"` | 极端风力等级(`isEmergency`为`true`时必填) |
  56 +| | **defectScore** | Integer | 否 | 输入缺陷总分,如 `9` (若前端已计算则优先以前端为准) | 缺陷总得分 |
  57 +| | **normalResult** | Object | 否 | 常规风险评估计算结果对象 | 常规安全评估结果组 |
  58 +| | **normalResult.score**| Double | 否 | 常规安全评估得分,如 `10.89` | 常规安全得分 |
  59 +| | **normalResult.level**| String | 否 | 常规安全风险等级,如 `"II级 (轻度风险)"` | 常规风险等级 |
  60 +| | **emergencyResult**| Object | 否 | 应急风险评估计算结果对象 | 应急安全评估结果组 |
  61 +| | **emergencyResult.score**| Double| 否 | 应急安全评估得分,如 `16.34` | 应急安全得分 |
  62 +| | **emergencyResult.level**| String| 否 | 应急安全风险等级,如 `"II级 (轻度风险)"` | 应急风险等级 |
  63 +| **status** | - | Object | 是 | 对应“现状及处理措施”面板 | 现状及处理措施数据组 |
  64 +| | **photos** | List | 否 | 数组,最多 5 个图片 URL,超过 5 个会报错拦截 | 现场采集照片 (限制最多5张) |
  65 +
  66 +### 请求示例 JSON
  67 +
  68 +```json
  69 +{
  70 + "treeId": 1024,
  71 + "inspectionTime": "2026-05-26 13:17:00",
  72 + "root": {
  73 + "disease": 0,
  74 + "anchorage": 0,
  75 + "cutting": 0
  76 + },
  77 + "collar": {
  78 + "woodDamage": 5,
  79 + "barkDamage": 0,
  80 + "loosening": 0
  81 + },
  82 + "trunk": {
  83 + "woodDamage": 0,
  84 + "tilt": 3,
  85 + "barkDamage": 0
  86 + },
  87 + "crown": {
  88 + "looseBranch": 0,
  89 + "collarAbnormal": 0,
  90 + "ventilationBalance": 1
  91 + },
  92 + "weight": {
  93 + "treeSpeciesType": "深根性树种",
  94 + "plantingYears": "栽植 10-30 年",
  95 + "isWindCorridor": false,
  96 + "treePoolType": "联通树池",
  97 + "treePoolWidthDbhRatio": "5 倍(含)-7 倍(不含)"
  98 + },
  99 + "result": {
  100 + "isEmergency": true,
  101 + "windPower": "8-9 级",
  102 + "defectScore": 9,
  103 + "normalResult": {
  104 + "score": 10.89,
  105 + "level": "II级 (轻度风险)"
  106 + },
  107 + "emergencyResult": {
  108 + "score": 16.34,
  109 + "level": "II级 (轻度风险)"
  110 + }
  111 + },
  112 + "status": {
  113 + "photos": [
  114 + "https://example.com/images/tree_whole.jpg",
  115 + "https://example.com/images/tree_detail1.jpg"
  116 + ]
  117 + }
  118 +}
  119 +```
  120 +
  121 +### 响应示例 JSON
  122 +
  123 +```json
  124 +{
  125 + "code": 0,
  126 + "data": 12,
  127 + "msg": ""
  128 +}
  129 +```
  130 +*(注:返回的 `data` 值为新建的巡检评估记录 ID。)*
  131 +
  132 +---
  133 +
  134 +## 2. 获得层级嵌套的巡检记录详情
  135 +
  136 +进入巡检历史记录详情页(图2)时,获取该记录所有计算指标及打分结果。该接口返回完全**层次分明、与表单高度对称的结构**,并额外输出每项计算的权重常数、各分类得分与最终安全等级描述。
  137 +
  138 +* **接口路径**:`GET /get`
  139 +* **请求方法**:`GET`
  140 +* **请求参数**:
  141 +
  142 +| 参数名 | 类型 | 是否必填 | 说明 |
  143 +| :--- | :--- | :--- | :--- |
  144 +| **id** | Long | 是 | 巡检记录 ID,例如 `12` |
  145 +
  146 +### 响应示例 JSON
  147 +
  148 +```json
  149 +{
  150 + "code": 0,
  151 + "data": {
  152 + "id": 12,
  153 + "treeId": 1024,
  154 + "treenumber": "D0001-P1-0001",
  155 + "inspectionTime": "2026-05-26 13:17:00",
  156 + "inspectorId": 10001,
  157 + "inspectorName": "张三",
  158 + "root": {
  159 + "disease": 0,
  160 + "anchorage": 0,
  161 + "cutting": 0
  162 + },
  163 + "collar": {
  164 + "woodDamage": 5,
  165 + "barkDamage": 0,
  166 + "loosening": 0
  167 + },
  168 + "trunk": {
  169 + "woodDamage": 0,
  170 + "tilt": 3,
  171 + "barkDamage": 0
  172 + },
  173 + "crown": {
  174 + "looseBranch": 0,
  175 + "collarAbnormal": 0,
  176 + "ventilationBalance": 1
  177 + },
  178 + "weight": {
  179 + "treeSpeciesType": "深根性树种",
  180 + "treeSpeciesWeight": 1.0,
  181 + "plantingYears": "栽植 10-30 年",
  182 + "plantingYearsWeight": 1.1,
  183 + "isWindCorridor": false,
  184 + "windCorridorWeight": 1.0,
  185 + "treePoolType": "联通树池",
  186 + "treePoolWeight": 1.0,
  187 + "treePoolWidthDbhRatio": "5 倍(含)-7 倍(不含)",
  188 + "treePoolRatioWeight": 1.1
  189 + },
  190 + "result": {
  191 + "isEmergency": true,
  192 + "windPower": "8-9 级",
  193 + "windPowerWeight": 1.5,
  194 + "defectScore": 9,
  195 + "normalResult": {
  196 + "score": 10.89,
  197 + "level": "II级 (轻度风险)"
  198 + },
  199 + "emergencyResult": {
  200 + "score": 16.34,
  201 + "level": "II级 (轻度风险)"
  202 + }
  203 + },
  204 + "status": {
  205 + "photos": [
  206 + "https://example.com/images/tree_whole.jpg",
  207 + "https://example.com/images/tree_detail1.jpg"
  208 + ]
  209 + }
  210 + },
  211 + "msg": ""
  212 +}
  213 +```
  214 +
  215 +---
  216 +
  217 +## 3. 分页查询单株树木的历史巡检记录
  218 +
  219 +对应“巡检记录列表”原型图(图1),按历史巡检时间倒序分页加载。
  220 +
  221 +* **接口路径**:`GET /page`
  222 +* **请求方法**:`GET`
  223 +* **请求参数**:
  224 +
  225 +| 参数名 | 类型 | 是否必填 | 示例值 | 说明 |
  226 +| :--- | :--- | :--- | :--- | :--- |
  227 +| **treeId** | Long | 是 | `1024` | 对应树木档案 ID |
  228 +| **pageNo** | Integer | 否 | `1` | 页码,从 1 开始 |
  229 +| **pageSize** | Integer | 否 | `10` | 每页行数 |
  230 +
  231 +### 响应示例 JSON
  232 +
  233 +```json
  234 +{
  235 + "code": 0,
  236 + "data": {
  237 + "list": [
  238 + {
  239 + "id": 12,
  240 + "treeId": 1024,
  241 + "treenumber": "D0001-P1-0001",
  242 + "inspectionTime": "2026-05-26 13:17:00",
  243 + "inspectorName": "张三",
  244 + "normalLevel": "II级 (轻度风险)",
  245 + "emergencyLevel": "II级 (轻度风险)",
  246 + "isEmergency": true,
  247 + "isTreated": false
  248 + },
  249 + {
  250 + "id": 2,
  251 + "treeId": 1024,
  252 + "treenumber": "D0001-P1-0001",
  253 + "inspectionTime": "2026-05-06 13:23:23",
  254 + "inspectorName": "李四",
  255 + "normalLevel": "I级 (基本无风险)",
  256 + "emergencyLevel": null,
  257 + "isEmergency": false,
  258 + "isTreated": true
  259 + }
  260 + ],
  261 + "total": 2
  262 + },
  263 + "msg": ""
  264 +}
  265 +```
  266 +
  267 +---
  268 +
  269 +## 3. 管理后台 - 行道树巡检与风险评估接口
  270 +
  271 +管理后台的“巡检记录”页签(如图所示)包含“巡检历史列表(左侧)”和“单条巡检详情面板(右侧)”。这两个功能可以直接调用本组管理后台 API:
  272 +
  273 +* **接口基地址**:`/admin-api/garden/tree-inspection`
  274 +* **权限标识**:`garden:tree-inspection:query`(需在管理后台的角色权限中进行配置)
  275 +
  276 +### 3.1 获得单条巡检评估详情
  277 +
  278 +对应管理后台点击左侧列表时,右侧展示的全部缺陷分、常规评估、应急评估以及现场照片(右侧详情面板)。
  279 +
  280 +* **接口路径**:`GET /get`
  281 +* **请求方法**:`GET`
  282 +* **请求参数**:同小程序端,根据主键 `id` 检索。
  283 +* **返回 JSON 结构**:与小程序端 `GET /get` 的响应示例格式**完全一致**。返回分层嵌套的 7 大模块,高度契合后台页面树根、根颈、主干、树冠、权重评估、风险评估结果、现状及照片的区块设计。
  284 +
  285 +### 3.2 分页查询单株树木的历史巡检记录
  286 +
  287 +对应管理后台中左侧用于折叠展示的多次巡检历史简要列表。
  288 +
  289 +* **接口路径**:`GET /page`
  290 +* **请求方法**:`GET`
  291 +* **请求参数**:同小程序端 `GET /page`,按 `treeId` 进行分页拉取。
  292 +* **返回 JSON 结构**:与小程序端 `GET /page` 的响应示例格式**完全一致**,按巡检时间由近及远倒序排列。
  293 +
  294 +---
  295 +
  296 +## 常见错误返回状态码
  297 +
  298 +| HTTP状态码 / code 码 | 错误提示内容 | 触发原因 |
  299 +| :--- | :--- | :--- |
  300 +| 400 | `status.photos: 最多只能上传5张现场照片` | 现场采集照片 `photos` 数组长度大于 5 时级联强校验报错 |
  301 +| 400 | `treeId: 关联树木ID不能为空` | 请求体中缺失 `treeId` |
  302 +| 400 | `inspectionTime: 巡检时间不能为空` | 请求体中缺失 `inspectionTime` |
  303 +| 403 | `Forbidden` | 管理后台用户未被授权 `garden:tree-inspection:query` 权限 |
  304 +| 500 / `1-100-008-001` | `巡检评估记录不存在` | 查询详情时 ID 在数据库中被标记删除或不存在 |
  305 +
... ...
deploy-test.sh 0 → 100755
  1 +#!/bin/bash
  2 +set -e
  3 +
  4 +# ==================== 配置 ====================
  5 +SERVER_HOST="172.17.16.14"
  6 +SERVER_USER="root"
  7 +SERVER_PASS="JCSS@2025!@#$"
  8 +UPLOAD_DIR="/home/user/test_jar"
  9 +RESTART_SCRIPT="restart.sh"
  10 +JAR_NAME="urbanops-server.jar"
  11 +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
  12 +JAR_PATH="$PROJECT_DIR/urbanops-server/target/$JAR_NAME"
  13 +
  14 +# ==================== 函数 ====================
  15 +
  16 +check_sshpass() {
  17 + if ! command -v sshpass &> /dev/null; then
  18 + echo "[check] sshpass 未安装,尝试安装..."
  19 + if command -v brew &> /dev/null; then
  20 + brew install hudochenkov/sshpass/sshpass
  21 + else
  22 + echo "[ERROR] 请手动安装 sshpass: brew install hudochenkov/sshpass/sshpass"
  23 + exit 1
  24 + fi
  25 + fi
  26 +}
  27 +
  28 +build() {
  29 + echo "[build] 开始 Maven 打包(跳过测试)..."
  30 + cd "$PROJECT_DIR"
  31 + mvn clean package -DskipTests
  32 + if [ -f "$JAR_PATH" ]; then
  33 + echo "[build] 打包完成: $JAR_PATH ($(du -sh "$JAR_PATH" | cut -f1))"
  34 + else
  35 + echo "[ERROR] 打包失败,未找到 $JAR_PATH"
  36 + exit 1
  37 + fi
  38 +}
  39 +
  40 +upload() {
  41 + echo "[upload] 上传 JAR 到 $SERVER_HOST:$UPLOAD_DIR/ ..."
  42 + sshpass -p "$SERVER_PASS" scp -o StrictHostKeyChecking=no "$JAR_PATH" "$SERVER_USER@$SERVER_HOST:$UPLOAD_DIR/"
  43 + echo "[upload] 上传完成"
  44 +}
  45 +
  46 +restart() {
  47 + echo "[restart] 调用远程重启脚本..."
  48 + sshpass -p "$SERVER_PASS" ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_HOST" \
  49 + "cd $UPLOAD_DIR && chmod +x $RESTART_SCRIPT && ./$RESTART_SCRIPT"
  50 + echo "[restart] 远程重启完成"
  51 +}
  52 +
  53 +# ==================== 主流程 ====================
  54 +
  55 +echo "========================================"
  56 +echo " UrbanOps 测试环境部署"
  57 +echo " 目标: $SERVER_USER@$SERVER_HOST:$UPLOAD_DIR"
  58 +echo "========================================"
  59 +
  60 +check_sshpass
  61 +build
  62 +upload
  63 +restart
  64 +
  65 +echo ""
  66 +echo "========================================"
  67 +echo " 部署完成"
  68 +echo "========================================"
... ...
guide.txt 0 → 100644
  1 +北京市园林绿化局
  2 +
  3 +行道树安全风险评估指南
  4 +(试行)
  5 +2024 年 11 月
  6 +
  7 + 目 录
  8 +
  9 +目 录
  10 +前 言 .......................................................................................................1
  11 +第一部分 概 述 ..................................................................................... 3
  12 +1.1 基本概念 ............................................................................................................ 3
  13 +1.2 目的及依据 ........................................................................................................ 3
  14 +1.3 适用范围 ............................................................................................................ 3
  15 +1.4 评估原则 ............................................................................................................ 4
  16 +1.5 评估周期 ............................................................................................................ 5
  17 +1.6 方法与工具 ........................................................................................................ 5
  18 +
  19 +第二部分 评估体系 ............................................................................... 7
  20 +2.1 评估指标体系 .................................................................................................... 7
  21 +2.2 风险得分计算 .................................................................................................. 10
  22 +2.3 风险等级判定 .................................................................................................. 11
  23 +
  24 +第三部分 评估流程 ............................................................................. 13
  25 +3.1 整体流程 .......................................................................................................... 13
  26 +3.2 划分风险区域 .................................................................................................. 13
  27 +3.3 道路初筛 .......................................................................................................... 14
  28 +3.4 精细评估 .......................................................................................................... 17
  29 +3.5 评估资料归档 .................................................................................................. 22
  30 +
  31 +第四部分 风险控制 ............................................................................. 25
  32 +4.1 风险防范原则 .................................................................................................. 25
  33 +4.2 常规防范措施 .................................................................................................. 25
  34 +4.3 风险处置措施 .................................................................................................. 26
  35 +4.4 应急抢险 .......................................................................................................... 30
  36 +
  37 +第五部分 附录 ..................................................................................... 31
  38 +附录 1 风险指标判断示例 ..................................................................................... 31
  39 +
  40 +i
  41 +
  42 + ii
  43 +
  44 + 前 言
  45 +本指南由北京市园林绿化局组织实施。
  46 +本指南起草单位:北京市园林绿化局城镇绿化处、北京
  47 +民生智库科技信息咨询有限公司。
  48 +本指南主要起草人:刘明星、周红英、朱永和、申明华、
  49 +陈晓晶、姚士才、常广新、杨志华、吴斌、巢阳、张华
  50 +伟、郭珺琪、张绮思、高天宇、胥心楠、池伯佳、胡嘉
  51 +琪、陈季琴、刘丽婕、杨曦。
  52 +本指南为首次发布。
  53 +
  54 +1
  55 +
  56 + 行道树安全风险评估指南(试行)
  57 +
  58 +2
  59 +
  60 + 第一部分 概 述
  61 +
  62 +第一部分 概 述
  63 +1.1 基本概念
  64 +行道树指种植于道路两侧及分车带、具有一定遮荫功能
  65 +并构成街景的乔木。
  66 +行道树安全风险是城市公共安全风险的一部分,本指南
  67 +所指的行道树安全风险指行道树发生倒伏、断折、落枝等事
  68 +件的可能性。
  69 +行道树安全风险评估是识别、分析和评价行道树发生倒
  70 +伏、断折、落枝可能性的过程。
  71 +1.2 目的及依据
  72 +为了科学评估行道树的潜在安全危险,及时采取处置措
  73 +施,消除行道树安全隐患,依据《风险管理
  74 +
  75 +风险评估技术》
  76 +
  77 +(GBT27921-2023),参考《城市树木健康诊断技术规程》
  78 +(DB11T 1692-2019)等规范文件,结合本市行道树实际情况,
  79 +编制本指南。
  80 +1.3 适用范围
  81 +本指南适用于本市范围内行道树(不含古树名木)的安
  82 +全风险评估,具体评估对象包括种植于人行道、机非隔离带、
  83 +主辅路隔离带、中央隔离带、其他道路两侧的乔木。
  84 +行道树安全风险评估应由具有园林绿化中级及以上技
  85 +术职称并有绿地养护管理经验的人员组织开展。
  86 +3
  87 +
  88 + 行道树安全风险评估指南(试行)
  89 +
  90 +1.4 评估原则
  91 +北京市行道树数量庞大,在资源有限的情况下,应先聚
  92 +焦重点区域,再聚焦单株树木开展安全风险评估。根据北京
  93 +市气象特征,冬季大风和汛期为行道树安全事件多发时段,
  94 +在常规情况下定期开展安全风险评估的同时,还应在极端大
  95 +风天气频发季节开展应急评估。
  96 +
  97 +聚焦重点区域。根据行道树倒伏、断折、落枝等事件对
  98 +公共安全影响的严重程度划分风险区域类别,其中,人员密
  99 +集、交通流量大的主次干路、景区、公园周边道路、城市风
  100 +口位置等区域为高风险区域,行道树安全风险评估应优先保
  101 +障高风险区域。不同风险区域分类参考见表 1。
  102 +表 1 行道树安全风险区域分类
  103 +类别
  104 +
  105 +解释
  106 +
  107 +示例
  108 +1.环路、主干路、次干路等重点道路;
  109 +2.环路、主干路、次干路以外人流量、车流量大的道路;
  110 +
  111 +高风险
  112 +
  113 +频繁使用、人员密
  114 +
  115 +区域
  116 +
  117 +集的道路
  118 +
  119 +3.位于政务活动场所、国际交往场所等重要区域的道路;
  120 +4.位于重点交通枢纽、商务区、会展区、商圈、重点公园等人
  121 +员活动密集区域的道路;
  122 +5.河道两侧、十字路口、立交桥周边、城区高楼之间的狭窄地
  123 +带等具有典型风口特征的位置。
  124 +
  125 +中风险
  126 +区域
  127 +
  128 +使用频率中等、人
  129 +员密集程度略低
  130 +的道路
  131 +
  132 +1.支路等车流量、人流量中等的道路;
  133 +2.位于一般公园、社区、普通医院等周边的道路。
  134 +
  135 +低风险
  136 +
  137 +使用频率较低的
  138 +
  139 +1.街巷等非重点道路人流量、车流量较少的道路;
  140 +
  141 +区域
  142 +
  143 +道路
  144 +
  145 +2.位于开放区域、林地、边缘区域等远离人活动区域的道路。
  146 +
  147 +聚焦单株树木精细评估。具体到每一条道路,首先应使
  148 +用目视法或简单工具对行道树进行初筛,筛选出具有风险特
  149 +征的单株行道树,对其进行精细评估。
  150 +4
  151 +
  152 + 第一部分 概 述
  153 +
  154 +聚焦重要时段。冬季大风和汛期为行道树安全风险防范
  155 +的重要时段,常规情况下的安全风险评估工作应在重要时段
  156 +一个月前完成,重要时段应结合气象预警信息开展应急评估,
  157 +以便提前采取防范措施。
  158 +1.5 评估周期
  159 +常绿行道树安全风险评估全年均可进行,落叶行道树安
  160 +全风险评估建议在生长期进行。评估开展频次根据实际管理
  161 +需求确定,高风险区域建议每半年开展一次,且至少有一次
  162 +于汛期前完成。
  163 +1.6 方法与工具
  164 +本指南以目视法为主,使用到的简易工具包括:胸径尺、
  165 +卷尺、测高器、橡皮锤、量角器等。
  166 +
  167 +5
  168 +
  169 + 行道树安全风险评估指南(试行)
  170 +
  171 +6
  172 +
  173 + 第二部分 评估体系
  174 +
  175 +第二部分 评估体系
  176 +2.1 评估指标体系
  177 +行道树安全风险评估指标体系包括【常规情况下的评估
  178 +指标】和【应急情况下的评估指标】。常规情况下的评估指
  179 +标适用于非汛期常规气象条件下的评估,应急情况下的评估
  180 +指标适用于汛期或极端天气来临之前的评估。
  181 +评估指标体系的整体框架如图 1 所示。
  182 +
  183 +图 1 行道树安全风险评估指标体系整体框架
  184 +
  185 +7
  186 +
  187 + 行道树安全风险评估指南(试行)
  188 +
  189 +2.1.1 常规情况下的评估指标体系
  190 +常规情况下,行道树安全风险评估从【树木缺陷】【树
  191 +木生理特性】和【树木生境】三个维度展开。
  192 +【树木缺陷】维度,依据“根本末”对安全风险的影响
  193 +力递减的原则,依次对“树根”“根颈”“主干”“树冠”
  194 +四个部位的 12 项指标进行赋分,分值越大,风险越高。满
  195 +分为 100 分,其中,树根部位分值为 20 分,根颈部位分值
  196 +为 31 分,主干部位分值为 33 分,树冠部位分值为 16 分,
  197 +各部位所占分值见图 2。
  198 +
  199 +图 2 【树木缺陷】指标中各部位所占分值示意图
  200 +
  201 +8
  202 +
  203 + 第二部分 评估体系
  204 +
  205 +其中,“树根”包括根部病害、根系下扎情况和工程切
  206 +根 3 项指标;“根颈”包括木质部受损、树皮受损 、根颈松
  207 +动 3 项指标;“主干”包括木质部受损、主干倾斜、树皮受
  208 +损 3 项指标;“树冠”包括易落枝、枝干结合部异常、树冠
  209 +透风情况及平衡性 3 项指标。
  210 +将五种缺陷较为严重的情况设置为“一票否决”项,直
  211 +接赋分为 100 分(判定为“极度风险”)或 70 分(判定为
  212 +“重度风险”),详见表 2。
  213 +表 2 【树木缺陷】指标中的“一票否决”项
  214 +“一票否决”项
  215 +根颈松动
  216 +
  217 +赋分及对应的风险等级
  218 +直接赋 100 分,归入极度风险
  219 +
  220 +根颈木质部受损达到 50%及以上
  221 +
  222 +直接赋 70 分,归入重度及以上风险
  223 +
  224 +主干木质部受损达到 50%及以上
  225 +
  226 +直接赋 70 分,归入重度及以上风险
  227 +
  228 +主干倾斜≥30°
  229 +
  230 +直接赋 70 分,归入重度及以上风险
  231 +
  232 +枝干结合部异常有明显空洞或蛀干痕迹
  233 +
  234 +直接赋 70 分,归入重度及以上风险
  235 +
  236 +【树木生理特性】维度包括“树种类型”和“栽植年限”
  237 +两项指标,均为权重指标,权重越高,对风险的影响越大。
  238 +“树种类型”分为深根性树种和浅根性树种,后者权重较高;
  239 +“栽植年限”分为栽植 10 年以内、栽植 10-30 年、栽植 30
  240 +年以上三档,年限越长,权重越高。
  241 +【树木生境】维度包括“是否处于风口”“树池类型”
  242 +和“树池宽度与胸径比”三项指标,均为权重指标,权重越
  243 +9
  244 +
  245 + 行道树安全风险评估指南(试行)
  246 +
  247 +高,对风险的影响越大。处于风口位置的行道树在大风天气
  248 +下承受了更大的风力,赋予较高权重;“树池类型”方面,
  249 +分联通树池、独立树池、树池硬化三种情况,赋予的权重依
  250 +次增大;树池宽度与胸径的比值越大,赋予的权重越小。
  251 +2.1.2 应急情况下的评估指标体系
  252 +汛期或极端天气频发季节,应结合气象预警信息开展应
  253 +急评估。应急情况下的评估指标体系包括“常规情况下安全
  254 +风险评估的全部指标”和“极端天气指标”。极端天气指标
  255 +中将“风力”作为权重因子进行赋权,风力越大,权重越高。
  256 +其中,风力 7 级及以下、8-9 级、10 级和 10 级以上的权重
  257 +值分别为 1.0、1.5、2.0、3.0。
  258 +2.2 风险得分计算
  259 +常规情况下,行道树安全风险评估得分计算公式为:
  260 +常规情况下安全风险得分 =【树木缺陷】各项指标得分相加
  261 +×【树木生理特性】各因子权重
  262 +×【树木生境】各因子权重
  263 +
  264 +应急情况下,行道树安全风险评估得分计算公式为:
  265 +应急情况下安全风险得分 =【树木缺陷】各项指标得分相加
  266 +×【树木生理特性】各因子权重
  267 +×【树木生境】各因子权重
  268 +×【极端天气】因子权重
  269 +
  270 +10
  271 +
  272 + 第二部分 评估体系
  273 +
  274 +2.3 风险等级判定
  275 +根据安全风险得分进行风险等级判定。行道树安全风险
  276 +等级分 5 级,安全风险得分与风险等级对应关系见表 3。
  277 +表 3 行道树安全风险等级划分
  278 +安全风险得分
  279 +
  280 +风险等级
  281 +
  282 +风险等级描述
  283 +
  284 +[0,10)
  285 +
  286 +I级
  287 +
  288 +基本无风险
  289 +
  290 +[10,30)
  291 +
  292 +II 级
  293 +
  294 +轻度风险
  295 +
  296 +[30,70)
  297 +
  298 +III 级
  299 +
  300 +中度风险
  301 +
  302 +[70,100)
  303 +
  304 +IV 级
  305 +
  306 +重度风险
  307 +
  308 +100+
  309 +
  310 +V级
  311 +
  312 +极度风险
  313 +
  314 +说明:当风险得分为临界值时,归入下一个较高风险等级。
  315 +
  316 +11
  317 +
  318 + 行道树安全风险评估指南(试行)
  319 +
  320 +12
  321 +
  322 + 第三部分 评估流程
  323 +
  324 +第三部分 评估流程
  325 +3.1 整体流程
  326 +评估前应做好准备工作,确定每次评估实施范围,统筹
  327 +安排人员和工具。由具有园林绿化中级及以上技术职称并有
  328 +行道树养护管理经验的人员牵头,组织辖区内管养单位的骨
  329 +干技术人员参与,并对参与人员进行充分培训。准备数量充
  330 +足的工具和调查评估表格。
  331 +具体实施时,先划分风险区域类别,对道路进行风险初
  332 +筛,确定需要调查的道路后聚焦道路上“有风险”的单株树
  333 +进行精细评估。评估完成后应将评估资料进行归档,建立风
  334 +险评估台账。
  335 +
  336 +图 3 行道树安全风险评估整体流程
  337 +
  338 +3.2 划分风险区域
  339 +综合考虑人员密集度、交通流量、道路等级等因素,将
  340 +辖区内的道路划分为高风险区域、中风险区域和低风险区域,
  341 +划分示例参考《表 1 行道树安全风险区域分类》。有条件
  342 +的应绘制不同风险区域的道路分布图,为评估工作提供可视
  343 +化参考。风险区域分布图示例见图 4。
  344 +13
  345 +
  346 + 行道树安全风险评估指南(试行)
  347 +
  348 +图 4 不同风险区域的道路分布图
  349 +
  350 +根据风险类别的优先级和现有人力物力条件,确定每次
  351 +评估拟覆盖的范围,原则上高风险区域应先行实施,若一次
  352 +评估无法覆盖所有道路,应制定分步评估计划,确保所有道
  353 +路每年至少评估一次。
  354 +在设计调查路线时,需兼顾科学性和合理性原则,以保
  355 +证评估工作的效率。
  356 +3.3 道路初筛
  357 +优先对高风险区域道路开展全覆盖初筛,中风险和低风
  358 +险区域根据安排适时开展。道路过长时,可分段进行。
  359 +采用目视法结合简易工具,对道路上行道树进行观察,
  360 +快速筛选具有以下任意一种特征的行道树:
  361 +14
  362 +
  363 + 第三部分 评估流程
  364 +
  365 +(1)根颈松动;
  366 +(2)根颈或主干木质部受损 10%以上;
  367 +(3)主干倾斜 20 度以上;
  368 +(4)枝干结合部有明显异常;
  369 +(5)处于风口且树冠结构明显失衡。
  370 +对筛选出的行道树涂抹标记,以便开展精细评估。道路
  371 +初步筛查使用的调查表见表 4。
  372 +表 4 行道树安全风险初步筛查表
  373 +所在区:____________
  374 +存在以下风险表征的行道树
  375 +道路
  376 +
  377 +道路
  378 +
  379 +编号
  380 +
  381 +名称
  382 +
  383 +风险区域分类 根颈
  384 +松动
  385 +
  386 +根颈或主干
  387 +
  388 +主干倾
  389 +
  390 +枝干结合 处于风口且
  391 +
  392 +木质部受损 斜 20 度 部有明显 树冠结构明
  393 +10%以上
  394 +
  395 +以上
  396 +
  397 +异常
  398 +
  399 +显失衡
  400 +
  401 +
  402 +
  403 +
  404 +
  405 +
  406 +
  407 +
  408 +
  409 +
  410 +
  411 +
  412 +
  413 +
  414 +
  415 +
  416 +
  417 +
  418 +
  419 +
  420 +
  421 +
  422 +
  423 +
  424 +
  425 +
  426 +
  427 +
  428 +
  429 +
  430 +
  431 +
  432 +
  433 +
  434 +
  435 +
  436 +
  437 +
  438 +
  439 +
  440 +
  441 +
  442 +
  443 +
  444 +
  445 +
  446 +
  447 +
  448 +
  449 +
  450 +
  451 +
  452 +
  453 +
  454 +
  455 +
  456 +
  457 +
  458 +
  459 +
  460 +
  461 +评估时间:
  462 +
  463 +年
  464 +
  465 +高风险区域
  466 +中风险区域
  467 +低风险区域
  468 +高风险区域
  469 +中风险区域
  470 +低风险区域
  471 +高风险区域
  472 +中风险区域
  473 +低风险区域
  474 +高风险区域
  475 +中风险区域
  476 +低风险区域
  477 +高风险区域
  478 +中风险区域
  479 +低风险区域
  480 +高风险区域
  481 +中风险区域
  482 +低风险区域
  483 +
  484 +是否纳
  485 +入精细
  486 +评估
  487 +
  488 +是
  489 +否
  490 +是
  491 +否
  492 +是
  493 +否
  494 +是
  495 +否
  496 +是
  497 +否
  498 +是
  499 +否
  500 +
  501 +评估单位:
  502 +评估人:
  503 +
  504 +月
  505 +
  506 +日
  507 +
  508 +15
  509 +
  510 + 行道树安全风险评估指南(试行)
  511 +填表说明:
  512 +(1)道路编号
  513 +D+四位数字,从 D0001 开始,对辖区内的道路依次编号,确保每条道路有
  514 +唯一编号。
  515 +(2)风险区域分类
  516 +参考本指南《表 1 行道树安全风险区域分类》,结合本区实际情况进行划
  517 +分。
  518 +(3)风口
  519 +受狭管效应影响,气流由开阔地带流入风口时,风速会急剧增大,狭管效
  520 +应示意图见图 5。
  521 +
  522 +图 5 城市建筑群间的狭管效应示意图
  523 +处于风口位置的行道树具有较高的安全风险,在行道树初筛时,准确判断
  524 +“风口”位置至关重要。判断“风口”的一般性建议如下:
  525 +“风口”的判断
  526 +风口一般位于受狭管效应影响的地带、水面附近等,包括但不限于以下地点:
  527 +
  528 +16
  529 +
  530 +
  531 +
  532 +河道两侧;
  533 +
  534 +
  535 +
  536 +湖面周边的迎风面;
  537 +
  538 +
  539 +
  540 +十字路口各个方位第一株行道树,尤其是东南角;
  541 +
  542 +
  543 +
  544 +立交桥周边的各方位第一株行道树,尤其是东南角;
  545 +
  546 +
  547 +
  548 +城区内高楼大厦间瞬间风力加强的狭窄地带;
  549 +
  550 +
  551 +
  552 +开敞空间,周边无任何遮挡物。
  553 +
  554 + 第三部分 评估流程
  555 +
  556 +3.4 精细评估
  557 +根据道路初筛结果,选择初步筛查标记出的风险树,开
  558 +展精细评估,填写表 5。按照“树根-根颈-主干-树冠”的
  559 +顺序,依次填写树木缺陷各项指标得分,记录树木生理特性
  560 +和树木生境各项权重因子的权重值,根据各项得分或权重值
  561 +计算风险得分,并判断对应的风险等级。
  562 +表 5 行道树安全风险精细评估调查表
  563 +风险树编号:D-P-
  564 +(一)基本信息表
  565 +树种
  566 +
  567 +栽植位置
  568 +
  569 +树高(m)
  570 +
  571 + 人行道
  572 +
  573 +胸径(cm)
  574 +
  575 +点位坐标
  576 +
  577 +一级指标 二级指标
  578 +
  579 +X:
  580 +
  581 +根系下扎情况
  582 +工程切根
  583 +
  584 +木质部受损
  585 +树木缺陷
  586 +根颈
  587 +树皮受损
  588 +
  589 +根颈松动
  590 +主干
  591 +
  592 +木质部受损
  593 +
  594 + 路侧绿地
  595 +
  596 +冠幅(m)
  597 +Y:
  598 +
  599 +(二)行道树缺陷评估
  600 +三级级指标
  601 +评分标准
  602 +根部病害
  603 +
  604 +树根
  605 +
  606 + 分车带
  607 +
  608 +赋分
  609 +
  610 +无真菌危害或腐朽情况
  611 +
  612 +0
  613 +
  614 +存在真菌危害或腐朽情况
  615 +
  616 +8
  617 +
  618 +根部下扎良好,无盘根或隆起
  619 +
  620 +0
  621 +
  622 +存在根部隆起或盘根
  623 +
  624 +7
  625 +
  626 +无工程切根
  627 +
  628 +0
  629 +
  630 +存在工程切根
  631 +
  632 +5
  633 +
  634 +无受损情况
  635 +
  636 +0
  637 +
  638 +受损程度<10%
  639 +
  640 +5
  641 +
  642 +受损程度介于 10%(含)-30%(不含)
  643 +
  644 +15
  645 +
  646 +受损程度介于 30%(含)-50%(不含)
  647 +
  648 +25
  649 +
  650 +受损程度≥50%
  651 +
  652 +70
  653 +
  654 +受损程度<10%
  655 +
  656 +0
  657 +
  658 +受损程度介于 10%-30%
  659 +
  660 +2
  661 +
  662 +受损程度介于 30%-50%
  663 +
  664 +4
  665 +
  666 +受损程度≥50%
  667 +
  668 +6
  669 +
  670 +不存在根颈松动
  671 +
  672 +0
  673 +
  674 +存在根颈松动
  675 +
  676 +100
  677 +
  678 +无受损情况
  679 +
  680 +0
  681 +
  682 +受损程度<10%
  683 +
  684 +5
  685 +
  686 +得分
  687 +
  688 +17
  689 +
  690 + 行道树安全风险评估指南(试行)
  691 +一级指标 二级指标
  692 +
  693 +三级级指标
  694 +
  695 +主干倾
  696 +
  697 +树皮受损
  698 +
  699 +易落枝
  700 +
  701 +树冠
  702 +(16)
  703 +
  704 +评分标准
  705 +
  706 +赋分
  707 +
  708 +受损程度介于 10%(含)-30%(不含)
  709 +
  710 +12
  711 +
  712 +受损程度介于 30%(含)-50%(不含)
  713 +
  714 +20
  715 +
  716 +受损程度≥50%
  717 +
  718 +70
  719 +
  720 +倾斜度<10°
  721 +
  722 +0
  723 +
  724 +倾斜度介于 10°(含)-20°(不含)
  725 +
  726 +3
  727 +
  728 +倾斜度介于 20°(含)-30°(不含)
  729 +
  730 +8
  731 +
  732 +倾斜度≥30°
  733 +
  734 +70
  735 +
  736 +受损程度<10%
  737 +
  738 +0
  739 +
  740 +受损程度介于 10%(含)-30%(不含)
  741 +
  742 +1
  743 +
  744 +受损程度介于 30%(含)-50%(不含)
  745 +
  746 +3
  747 +
  748 +受损程度≥50%
  749 +
  750 +5
  751 +
  752 +未发现易落枝
  753 +
  754 +0
  755 +
  756 +易落枝占整个树冠枝条数量的比例<
  757 +1/10
  758 +
  759 +2
  760 +
  761 +易落枝占整个树冠枝条数量的比例≥
  762 +1/10
  763 +
  764 +3
  765 +
  766 +无异常
  767 +
  768 +0
  769 +
  770 +枝干结合部异 有龟裂或卷皮情况
  771 +常
  772 +有腐烂现象但尚未形成明显空洞
  773 +
  774 +3
  775 +5
  776 +
  777 +有明显空洞或蛀干痕迹
  778 +
  779 +70
  780 +
  781 +透风情况较好,不偏冠
  782 +
  783 +0
  784 +
  785 +【透风情况较好但有明显偏冠】或【透
  786 +树冠透风情况 风性差但冠幅适中,不偏冠】
  787 +透风性差且明显偏冠,但冠幅适中
  788 +及平衡性
  789 +
  790 +得分
  791 +
  792 +1
  793 +2
  794 +
  795 +透风性差且冠幅较大,但不偏冠
  796 +
  797 +5
  798 +
  799 +透风性差、冠幅较大且明显偏冠
  800 +
  801 +8
  802 +
  803 +缺陷评估得分
  804 +(三)权重因子评估
  805 +一级权重因子
  806 +
  807 +二级权重因子
  808 +树种类型
  809 +
  810 +树木生理特性
  811 +栽植年限
  812 +
  813 +树木生境
  814 +
  815 +是否处于风口
  816 +树池类型
  817 +
  818 +18
  819 +
  820 +权重标准
  821 +
  822 +赋权
  823 +
  824 +深根性树种
  825 +
  826 +1.0
  827 +
  828 +浅根性树种
  829 +
  830 +1.1
  831 +
  832 +栽植 10 年以内
  833 +
  834 +1.0
  835 +
  836 +栽植 10-30 年
  837 +
  838 +1.1
  839 +
  840 +栽植 30 年以上
  841 +
  842 +1.2
  843 +
  844 +否
  845 +
  846 +1.0
  847 +
  848 +是
  849 +
  850 +2.0
  851 +
  852 +联通树池
  853 +
  854 +1.0
  855 +
  856 +权重值
  857 +
  858 + 第三部分 评估流程
  859 +独立树池
  860 +
  861 +1.2
  862 +
  863 +树池硬化
  864 +
  865 +1.5
  866 +
  867 +7 倍及以上
  868 +
  869 +1.0
  870 +
  871 +树池宽度与胸径 5 倍(含)-7 倍(不含)
  872 +比
  873 +3 倍(含)-5 倍(不含)
  874 +
  875 +1.1
  876 +
  877 +3 倍以下
  878 +
  879 +1.3
  880 +
  881 +1.2
  882 +
  883 +常规情况下的评估得分
  884 +(四)应急评估增项指标
  885 +一级权重因子
  886 +
  887 +二级权重因子
  888 +
  889 +极端天气
  890 +
  891 +风力
  892 +
  893 +权重标准
  894 +
  895 +赋权
  896 +
  897 +7 级及以下
  898 +
  899 +1.0
  900 +
  901 +8-9 级
  902 +
  903 +1.5
  904 +
  905 +10 级
  906 +
  907 +2.0
  908 +
  909 +10 级以上
  910 +
  911 +3.0
  912 +
  913 +权重值
  914 +
  915 +(五)风险得分及等级判定
  916 +计算安全风险得分
  917 +风险得分所属区间
  918 +
  919 +对应风险等级
  920 +
  921 +□ 得分<10
  922 +
  923 +□ 基本无安全风险
  924 +
  925 +□ 10≤得分<30
  926 +
  927 +□ 轻度安全风险
  928 +
  929 +□ 30≤得分<70
  930 +
  931 +□ 中度安全风险
  932 +
  933 +□ 70≤得分<100
  934 +
  935 +□ 重度安全风险
  936 +
  937 +□ 得分≥100
  938 +
  939 +□ 极度安全风险
  940 +
  941 +选择安全风险等级
  942 +
  943 +评估单位:
  944 +评估人:
  945 +
  946 +评估时间:
  947 +
  948 +年
  949 +
  950 +月
  951 +
  952 +日
  953 +
  954 +填表说明:
  955 +(1)风险树编号
  956 +按“道路编号-树群单元编号-行道树排号-顺序编号”的规则进行编号,即
  957 +DXXXX-PX-XXXX。
  958 +行道树排号编写规则:P+1 位数字,南北走向道路按照从东向西的顺序依
  959 +次命名 P1、P2、P3… …,东西走向的道路按照从北向南的顺序依次命名 P1、
  960 +P2、P3… …,示例见图 6。
  961 +
  962 +19
  963 +
  964 + 行道树安全风险评估指南(试行)
  965 +
  966 +图 6 行道树排号编写规则示例
  967 +比如,道路编号为 D0601,排号为 P1,则该树群单元中的第一株风险树编号
  968 +为 D0601-P1-0001。
  969 +(2)冠幅(m)
  970 +东西方向和南北方向冠幅的平均值。
  971 +(3)根部病害
  972 +仔细检查树根部位是否存在大型真菌或有明显的腐朽症状,若有则此项指
  973 +标得分为 8 分。
  974 +(4)根系下扎情况
  975 +观察根系是否存隆起或盘根现象,若有则此项指标得分为 7 分。
  976 +(5)工程切根
  977 +观察根部是否存在切口,调查人员也可以根据施工记录判定是否存在工程
  978 +切根情况。
  979 +(6)根颈木质部受损
  980 +根颈部位木质部受损情形包括腐朽、空洞、木质部开裂、明显的蛀干害虫
  981 +痕迹等,受损程度以最大受损截面占根颈截面面积的比例计算,根据测算的受
  982 +损比例选择相应的分值填写,若木质部受损程度大于等于 50%,则达到“一票
  983 +否决”标准,直接得分 70,归入“重度风险”。
  984 +(7)根颈树皮受损
  985 +根颈部树皮受损情形包括树皮脱落、木质部裸露、树皮机械损伤等,受损
  986 +程度以受损最大弧长占根颈周长的比例计算,根据测算的受损比例选择相应的
  987 +分值填写。
  988 +(8)根颈松动
  989 +用力推动树干,观察根颈部位是否有明显晃动,此项为“一票否决”项,
  990 +若存在根颈松动情况,直接得分 100,归入极度风险。
  991 +20
  992 +
  993 + 第三部分 评估流程
  994 +(9)主干木质部受损
  995 +主干部位木质部受损情形包括腐朽、空洞、木质部开裂、明显的蛀干害虫
  996 +痕迹等。受损程度以最大受损截面占主干截面面积的比例计算,主干外观无异
  997 +常,应使用橡皮锤敲击,如有不同于正常树干的声音,判断为存在空腐。根据
  998 +测算的受损比例选择相应的分值填写。若木质部受损程度大于等于 50%,则达
  999 +到“一票否决”标准,直接得分 70,归入“重度风险”。
  1000 +(10)主干倾斜
  1001 +使用量角器测量树干偏离竖直方向的度数,根据测量结果选择相应的分值
  1002 +填写,若主干倾斜程度大于等于 30°,则达到“一票否决”标准,直接得分 70,
  1003 +归入“重度风险”。
  1004 +(11)主干树皮受损
  1005 +主干部树皮受损情形包括树皮脱落、木质部裸露、树皮机械损伤等,受损
  1006 +程度以受损最大弧长占主干周长的比例计算,根据测算的受损比例选择相应的
  1007 +分值填写。
  1008 +(12)易落枝
  1009 +观察是否存在易落枝,包括枯死枝、蛀干枝或已折断未掉落的枝条,估算
  1010 +易落枝数量占树冠枝条数量的比例。根据观察结果选择相应的分值填写。
  1011 +(13)枝干结合部异常
  1012 +观察枝干结合部位是否存在异常,包括是否有龟裂或卷皮、腐烂、空洞或
  1013 +蛀干痕迹。根据异常程度选择相应的分值填写,若发现“有明显空洞或蛀干痕
  1014 +迹”,则达到“一票否决”标准,直接得分 70,归入“重度风险”。
  1015 +(14)树冠透风情况及平衡性
  1016 +观察是否存在树冠密不透风、冠幅过大或偏冠的情况,根据观察结果选择
  1017 +相应的分值填写。
  1018 +(15)树种类型
  1019 +常见树种根系类型划分参见表 6。
  1020 +表 6 常见树种根系分类
  1021 +序号
  1022 +
  1023 +树种
  1024 +
  1025 +根系分类
  1026 +
  1027 +序号
  1028 +
  1029 +树种
  1030 +
  1031 +根系分类
  1032 +
  1033 +1
  1034 +
  1035 +国槐
  1036 +
  1037 +深根性
  1038 +
  1039 +8
  1040 +
  1041 +栾树
  1042 +
  1043 +深根性
  1044 +
  1045 +2
  1046 +
  1047 +刺槐
  1048 +
  1049 +浅根性
  1050 +
  1051 +9
  1052 +
  1053 +垂柳
  1054 +
  1055 +浅根性
  1056 +
  1057 +3
  1058 +
  1059 +白蜡
  1060 +
  1061 +深根性
  1062 +
  1063 +10
  1064 +
  1065 +旱柳
  1066 +
  1067 +浅根性
  1068 +
  1069 +4
  1070 +
  1071 +银杏
  1072 +
  1073 +深根性
  1074 +
  1075 +11
  1076 +
  1077 +千头椿
  1078 +
  1079 +浅根性
  1080 +
  1081 +5
  1082 +
  1083 +悬铃木
  1084 +
  1085 +浅根性
  1086 +
  1087 +12
  1088 +
  1089 +臭椿
  1090 +
  1091 +浅根性
  1092 +
  1093 +6
  1094 +
  1095 +毛白杨
  1096 +
  1097 +深根性
  1098 +
  1099 +13
  1100 +
  1101 +油松
  1102 +
  1103 +深根性
  1104 +
  1105 +7
  1106 +
  1107 +加杨
  1108 +
  1109 +深根性
  1110 +
  1111 +14
  1112 +
  1113 +毛泡桐
  1114 +
  1115 +浅根性
  1116 +
  1117 +21
  1118 +
  1119 + 行道树安全风险评估指南(试行)
  1120 +
  1121 +3.5 评估资料归档
  1122 +3.5.1 照片的采集
  1123 +对开展精细评估的每株行道树,均需拍摄现状照片,照
  1124 +片数量不少于 3 张且包括以下角度取景:
  1125 +整体取景:含周边参照物,以展示单株行道树的树体形
  1126 +态及具体位置;
  1127 +树木生境照片:展示树池形态、周边设施、建筑等;
  1128 +细节照片:从不同角度清晰展示树木存在的缺陷问题,
  1129 +如行道树存在多处缺陷,则每种症状的特写均需采集。
  1130 +此外,如遇处于风口的行道树,则需拍摄周边风场环境
  1131 +的照片或视频。
  1132 +3.5.2 点位的采集
  1133 +采集完风险树的信息和照片后,采集风险树点位坐标,
  1134 +以树木编号命名,并以矢量数据保存。
  1135 +3.5.3 资料归档
  1136 +将各项调查评估信息进行汇总整理,形成行道树风险管
  1137 +理档案,具体包括:
  1138 + 风险区域划分图、道路初筛表、精细评估调查表;
  1139 + 对调查评估表格进行电子化,形成风险台账(见表 7);
  1140 + 照片文件夹以树木编号命名,形成照片库;
  1141 + 点位以树木编号命名,以矢量格式储存,与调查信息
  1142 +关联,形成行道树风险点位矢量数据库。
  1143 +22
  1144 +
  1145 + 第三部分 评估流程
  1146 +
  1147 +表 7 行道树安全风险台账表
  1148 +基本信息
  1149 +风险树
  1150 +
  1151 +所属
  1152 +
  1153 +所属道
  1154 +
  1155 +X坐
  1156 +
  1157 +Y坐
  1158 +
  1159 +编号
  1160 +
  1161 +区
  1162 +
  1163 +路
  1164 +
  1165 +标
  1166 +
  1167 +标
  1168 +
  1169 +树种
  1170 +
  1171 +风险评估结果
  1172 +树高
  1173 +
  1174 +胸径
  1175 +
  1176 +冠幅
  1177 +
  1178 +栽植
  1179 +
  1180 +(m)
  1181 +
  1182 +(cm)
  1183 +
  1184 +(m)
  1185 +
  1186 +位置
  1187 +
  1188 +常规情况
  1189 +
  1190 +应急情况
  1191 +
  1192 +下的风险
  1193 +
  1194 +下的风险
  1195 +
  1196 +等级
  1197 +
  1198 +等级
  1199 +
  1200 +风险处置
  1201 +建议处置
  1202 +措施
  1203 +
  1204 +是否已处
  1205 +置
  1206 +
  1207 +评估记录
  1208 +评估
  1209 +时间
  1210 +
  1211 +评估单位
  1212 +
  1213 +评估人
  1214 +
  1215 +23
  1216 +
  1217 + 行道树安全风险评估指南(试行)
  1218 +
  1219 +24
  1220 +
  1221 + 第四部分 风险控制
  1222 +
  1223 +第四部分 风险控制
  1224 +4.1 风险防范原则
  1225 +
  1226 +坚持保护优先的原则。行道树安全风险管理要严格坚持
  1227 +保护优先原则,重在找出问题,提出改善措施。
  1228 +
  1229 +安全风险分级管控原则。重度及以上安全风险树木及早
  1230 +处置,中度、轻度安全风险树木密切监测,采取缓解和保护
  1231 +措施。
  1232 +4.2 常规防范措施
  1233 +行道树作为有生命的城市基础设施,其安全风险管理贯
  1234 +穿树种规划、设计、施工、管养的整个过程。
  1235 +行道树树种规划阶段,应因地制宜,适地适树,优选抗
  1236 +性强、耐修剪、易栽活的乡土树种;设计阶段宜考虑丰富树
  1237 +种多样性和合理布局与搭配,提高行道树抗风险能力;施工
  1238 +阶段应关注施工质量和树木保护,确保严格按照相关规范和
  1239 +标准进行,工程验收时将安全风险指标考虑在内;管养阶段,
  1240 +除日常养护工作外,还应针对长势不良的行道树及时采取树
  1241 +池扩大、联通树池改造或土壤改良等复壮措施。
  1242 +建立常态化安全风险评估机制,实现行道树安全风险台
  1243 +账动态管理,并及时采取措施消除风险。注重收集的行道树
  1244 +安全事故数据,建立风险预警数据库,针对往年行道树安全
  1245 +事件频发的重点区域、位置设置警示标识,完善应急预案,
  1246 +加强巡查监测,提升行道树安全风险管理的有效性。
  1247 +
  1248 +25
  1249 +
  1250 + 行道树安全风险评估指南(试行)
  1251 +
  1252 +4.3 风险处置措施
  1253 +行道树安全风险是行道树健康风险的重要组成部分,按
  1254 +照保护优先和分级管控的原则,对于不同风险等级的行道树
  1255 +采取不同措施进行管理。
  1256 +评估为“轻度风险”“中度风险”的行道树,应依托树
  1257 +木医开展健康管理,减缓风险发展速度。
  1258 +评估为“重度风险”和“极度风险”的行道树,应立即
  1259 +采取措施,消除风险隐患。
  1260 +表 8 行道树安全风险等级对应的处置措施
  1261 +风险等级
  1262 +
  1263 +风险等级描述
  1264 +
  1265 +I级
  1266 +
  1267 +基本无风险
  1268 +
  1269 +II 级
  1270 +
  1271 +轻度风险
  1272 +
  1273 +III 级
  1274 +
  1275 +中度风险
  1276 +
  1277 +IV 级
  1278 +
  1279 +重度风险
  1280 +
  1281 +V级
  1282 +
  1283 +极度风险
  1284 +
  1285 +处置建议
  1286 +正常养护,每年定期开展安全风险评估。
  1287 +根据道路实际情况,结合存在的风险隐患点,开展修剪、支撑、创
  1288 +面或空洞修复、病虫害防治等治理措施,每半年巡查 1 次。
  1289 +加强巡查频次,每季度巡查一次,适时采取风险减缓措施。
  1290 +在极端天气来临前,特别是处于人流量较大区域风险树,在完成评
  1291 +估后 4 周内采取措施。对此类树木密切监察,直至风险降级或解除。
  1292 +立即采取措施,进行树木移除、危险枝清理或其他减轻风险的措施。
  1293 +
  1294 +4.3.1 针对特殊情形树木的移除
  1295 +一般情况下,能采取风险减缓措施的树木均不建议进行
  1296 +移除。
  1297 +针对风险等级达到重度安全风险及以上且不具备采取
  1298 +风险减缓措施条件的行道树,建议进行移除,并适时补植适
  1299 +宜规格的苗木,以保证景观效果。
  1300 +
  1301 +26
  1302 +
  1303 + 第四部分 风险控制
  1304 +建议进行树木移除的情形
  1305 +
  1306 +
  1307 +栽植 2 年以上且根颈出现松动时,应及时采取措施,进行树木移除;
  1308 +
  1309 +
  1310 +
  1311 +树根存在严重腐烂,无法起到支撑作用的行道树,应及时移除;
  1312 +
  1313 +
  1314 +
  1315 +木质部受损(空腐率)超过 50%且树势较弱的行道树;
  1316 +
  1317 +
  1318 +
  1319 +主干倾斜 30 度以上且无法通过缩冠减缓风险或不具备增加支撑条件的行道树;
  1320 +
  1321 +
  1322 +
  1323 +其他经过专家研讨一致认定应及时移除的行道树。
  1324 +
  1325 +4.3.2 针对木质部受损的行道树进行空洞、腐朽处理
  1326 +根颈、主干及大枝的木质部受损,尤其是出现腐朽、空
  1327 +洞时,应及时采取处理措施。
  1328 +(1)木质部腐朽处理措施
  1329 +主干、主枝上有明显裸露腐朽木质部的,首先清除木质
  1330 +部表面的松软碎末等杂物,不损伤活组织的前提下,使用已
  1331 +消毒的工具修整至活组织,喷洒杀菌剂后,再均匀喷洒水溶
  1332 +性防腐剂,待自然风干后均匀涂抹纯熟桐油等天然环保的防
  1333 +腐材料。
  1334 +(2)木质部空洞处理措施
  1335 +主干或主枝上有明显树洞的,针对不同类型空洞,建议
  1336 +采取不同的处置措施。树体修复施工宜在树木休眠期、天气
  1337 +干燥时进行。
  1338 +建议采取的空洞修复方式
  1339 +
  1340 +
  1341 +不易积水、存水的树洞,做好防腐处理,不填充封堵。
  1342 +
  1343 +
  1344 +
  1345 +易积水但不影响树体安全,可在适当位置设导流管(孔)顺利排出的树洞,做好防腐
  1346 +处理不填充封堵。
  1347 +
  1348 +
  1349 +
  1350 +敞开式、贯通式树洞不填充封堵,做好导水、防腐及安全加固处理。
  1351 +
  1352 +
  1353 +
  1354 +易进水、存水的树洞,应封堵洞口,做好排水、通风处理。
  1355 +
  1356 +27
  1357 +
  1358 + 行道树安全风险评估指南(试行)
  1359 +
  1360 +4.3.3 针对倾斜的行道树采取支撑措施
  1361 +针对倾斜且具备支撑、加固条件的行道树,在不妨碍车
  1362 +辆、行人通行的情况下进行支撑、加固。树体外观明显倾斜
  1363 +的行道树,宜采用“人字”硬支撑、拉纤等方法进行支撑、
  1364 +加固。主干有劈裂倾倒隐患或树冠上有断裂隐患的分枝间可
  1365 +采用抱箍和相互拉纤进行加固。
  1366 +4.3.4 针对偏冠、树冠过大的行道树开展缩冠修剪
  1367 +行道树的修剪以冬春季(休眠期)修剪为主,夏季(生
  1368 +长期)修剪为辅,结合大风汛期前的应急修剪进行。
  1369 +针对存在偏冠的行道树,适当进行缩冠修剪。修剪时,
  1370 +应对生长势较弱一方的枝条适当长放或轻剪,对生长势较强
  1371 +一侧适当回缩,以此达到平衡生长势。
  1372 +树冠过大的行道树需进行缩冠修剪,尤其是针对处于风
  1373 +口树冠过大的行道树,必须进行缩冠修剪。按照“由外及里、
  1374 +由上到下”的顺序进行修剪,注意保留大部分枝条顶芽,避
  1375 +免出现截干、重修剪等现象。修剪时应保持树木冠幅及树冠
  1376 +高度与树干适当比例,冠幅宜占全树高度的 1/3~1/2,树冠
  1377 +高度宜占全树高度的 1/2~2/3。
  1378 +4.3.5 针对枝条过密的行道树进行疏枝
  1379 +过密枝条的疏枝按照“一知、二看、三剪、四拿、五处
  1380 +理、六保护”的程序进行操作:
  1381 +一知:参加修剪的全体人员,应明确修剪原则,知道操
  1382 +作规程、技术规范及特殊要求;
  1383 +28
  1384 +
  1385 + 第四部分 风险控制
  1386 +
  1387 +二看:修剪前先绕树观察,对树木的修剪方法做到心中
  1388 +有数;
  1389 +三剪:根据因地制宜,因树修剪的原则,合理修剪;
  1390 +四拿:修剪下来的枝条,及时清运,保证环境整洁;
  1391 +五处理:剪下的枝条要及时处理,防止病虫害蔓延;
  1392 +六保护:疏除大枝、粗枝时,应保护树体。
  1393 +4.3.6 针对枝干结合部异常的修剪
  1394 +针对枝干结合部存在异常,导致连接部位脆弱易发生断
  1395 +折的行道树,应及时去异常部位的枝条,避免产生大枝劈裂
  1396 +的风险。修剪时应注意避开枝领,降低剪切面受到病虫害侵
  1397 +染的几率,修剪后及时涂抹愈合剂。
  1398 +4.3.7 针对蛀干害虫的防治措施
  1399 +常见的蛀干害虫包括鞘翅目的吉丁虫、天牛、小蠹、象
  1400 +甲等,鳞翅目的木蠹蛾、小卷蛾、松梢螟、透翅蛾等,膜翅
  1401 +目的树蜂等。
  1402 +按照“预防为主,综合防治”的原则,做到安全、经济、
  1403 +及时、有效。及时采取物理防治手段,包括诱杀、阻止上树、
  1404 +人工捕捉、摘除网幕、剪除病虫枝等。宜采用生物防治手段,
  1405 +保护和利用天敌。采用化学防治措施时,选择符合环保要求
  1406 +的低毒农药。交替使用不同的药剂,减少喷药次数。
  1407 +
  1408 +29
  1409 +
  1410 + 行道树安全风险评估指南(试行)
  1411 +
  1412 +4.4 应急抢险
  1413 +管理单位应建立组织全面、职能明确、运行有效的应急
  1414 +抢险组织架构(见图 7),确保行道树安全风险管理有效。
  1415 +组织架构设立应急抢险领导小组,下设应急抢险办公室、后
  1416 +勤保障组、现场调查组和应急抢险组。组织架构中相关人员
  1417 +各司其职,保障应急信息、应急指令的及时传递,并定期开
  1418 +展培训和演练。
  1419 +
  1420 +图 7 应急抢险组织架构
  1421 +
  1422 +行道树安全风险事故发生后,应根据险情不同及时启动
  1423 +相应的应急抢险,以保障抢险工作高效、有序进行。当树木
  1424 +完全倒伏至地面或高度较低,采用普通锯除,再将大枝、主
  1425 +枝、主干截段,进行运输处理;当树木倒伏或断枝的位置过
  1426 +高时,采用高空移除,先将中级枝以上分枝去除,再将大枝、
  1427 +主枝、主干截断,再截段运输;若发生倾斜或挤压建筑的树
  1428 +木过大过高,为了防止二次破坏,采用搭建脚手架辅助移除。
  1429 +
  1430 +30
  1431 +
  1432 + 第五部分 附录
  1433 +
  1434 +第五部分 附录
  1435 +附录 1 风险指标判断示例
  1436 +5.1.1 根部病害示例
  1437 +
  1438 +蜜环菌
  1439 +
  1440 +多孔菌
  1441 +
  1442 +鬼伞
  1443 +
  1444 +鬼伞
  1445 +
  1446 +图 8 根部病害示例
  1447 +
  1448 +5.1.2 根系下扎不良示例
  1449 +
  1450 +31
  1451 +
  1452 + 行道树安全风险评估指南(试行)
  1453 +根部隆起
  1454 +
  1455 +根部隆起
  1456 +
  1457 +盘根
  1458 +
  1459 +盘根
  1460 +
  1461 +图 9 根系下扎不良示例
  1462 +
  1463 +5.1.3 工程切根示例
  1464 +
  1465 +图 10 施工切根示例
  1466 +
  1467 +32
  1468 +
  1469 + 第五部分 附录
  1470 +
  1471 +5.1.4 根颈木质部受损示例
  1472 +
  1473 +图 11 根颈木质部受损示例
  1474 +
  1475 +5.1.5 根颈树皮受损示例
  1476 +
  1477 +图 12 根颈树皮受损示例
  1478 +
  1479 +33
  1480 +
  1481 + 行道树安全风险评估指南(试行)
  1482 +
  1483 +5.1.6 主干木质部受损示例
  1484 +
  1485 +图 13 主干部位木质部受损示例
  1486 +
  1487 +34
  1488 +
  1489 + 第五部分 附录
  1490 +
  1491 +5.1.7 主干倾斜示例
  1492 +
  1493 +图 14 主干倾斜示例
  1494 +
  1495 +5.1.8 主干树皮受损示例
  1496 +
  1497 +图 15 主干部位树皮受损示例
  1498 +
  1499 +5.1.9 易落枝示例
  1500 +
  1501 +图 16 易落枝示例
  1502 +
  1503 +35
  1504 +
  1505 + 行道树安全风险评估指南(试行)
  1506 +
  1507 +5.1.10 枝干结合部异常示例
  1508 +
  1509 +图 17 枝干结合部异常示例
  1510 +
  1511 +5.1.11 树冠透风情况及平衡性示例
  1512 +
  1513 +36
  1514 +
  1515 + 第五部分 附录
  1516 +
  1517 +树冠密不透风且冠幅大
  1518 +
  1519 +偏冠
  1520 +
  1521 +图 18 树冠透风情况及平衡性示例
  1522 +
  1523 +37
  1524 +
  1525 + 行道树安全风险评估指南(试行)
  1526 +
  1527 +38
  1528 +
  1529 +
0 1530 \ No newline at end of file
... ...
script/convert_gis_road_coords.py 0 → 100644
  1 +#!/usr/bin/env python3
  2 +"""
  3 +WGS84 → GCJ02(高德坐标系)坐标转换脚本
  4 +目标表: garden_gis_road
  5 +转换字段: starting_latitude/longitude, end_latitude/longitude, gis_polygon_coords
  6 +
  7 +使用方法:
  8 + python3 convert_gis_road_coords.py
  9 +
  10 +前提: pip install pymysql
  11 +"""
  12 +import pymysql
  13 +import math
  14 +import re
  15 +
  16 +# ========== 数据库配置 ==========
  17 +DB_CONFIG = {
  18 + 'host': '172.17.16.15',
  19 + 'port': 3306,
  20 + 'user': 'root',
  21 + 'password': 'mysql2025!',
  22 + 'database': 'urban_ops_agent',
  23 + 'charset': 'utf8mb4',
  24 +}
  25 +
  26 +# ========== WGS84 → GCJ02 算法 ==========
  27 +PI = math.pi
  28 +A = 6378245.0 # 长半轴
  29 +EE = 0.00669342162296594323 # 偏心率平方
  30 +
  31 +
  32 +def wgs84_to_gcj02(lng, lat):
  33 + """WGS84 转 GCJ02(高德坐标系)"""
  34 + if lng < 72.004 or lng > 137.8347 or lat < 0.8293 or lat > 55.8271:
  35 + return lng, lat
  36 +
  37 + x, y = lng - 105.0, lat - 35.0
  38 +
  39 + dlat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x))
  40 + dlat += (20.0 * math.sin(6.0 * x * PI) + 20.0 * math.sin(2.0 * x * PI)) * 2.0 / 3.0
  41 + dlat += (20.0 * math.sin(y * PI) + 40.0 * math.sin(y / 3.0 * PI)) * 2.0 / 3.0
  42 + dlat += (160.0 * math.sin(y / 12.0 * PI) + 320.0 * math.sin(y * PI / 30.0)) * 2.0 / 3.0
  43 +
  44 + dlng = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x))
  45 + dlng += (20.0 * math.sin(6.0 * x * PI) + 20.0 * math.sin(2.0 * x * PI)) * 2.0 / 3.0
  46 + dlng += (20.0 * math.sin(x * PI) + 40.0 * math.sin(x / 3.0 * PI)) * 2.0 / 3.0
  47 + dlng += (150.0 * math.sin(x / 12.0 * PI) + 300.0 * math.sin(x / 30.0 * PI)) * 2.0 / 3.0
  48 +
  49 + rad_lat = lat / 180.0 * PI
  50 + magic = math.sin(rad_lat)
  51 + magic = 1 - EE * magic * magic
  52 + sqrt_magic = math.sqrt(magic)
  53 +
  54 + dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrt_magic) * PI)
  55 + dlng = (dlng * 180.0) / (A / sqrt_magic * math.cos(rad_lat) * PI)
  56 +
  57 + return lng + dlng, lat + dlat
  58 +
  59 +
  60 +def transform_wkt(wkt):
  61 + """转换 WKT 字符串中的所有坐标对(支持 POLYGON / MULTIPOLYGON)"""
  62 + if not wkt or not wkt.strip():
  63 + return wkt
  64 +
  65 + def convert_pair(m):
  66 + nlng, nlat = wgs84_to_gcj02(float(m.group(1)), float(m.group(2)))
  67 + return f"{nlng:.6f} {nlat:.6f}"
  68 +
  69 + return re.sub(r'(\d+\.?\d*)\s+(\d+\.?\d*)', convert_pair, wkt)
  70 +
  71 +
  72 +def main():
  73 + conn = pymysql.connect(**DB_CONFIG)
  74 + cur = conn.cursor()
  75 +
  76 + # 1. 转换起点/终点坐标(如果有的话)
  77 + cur.execute(
  78 + "SELECT id, starting_latitude, starting_longitude, end_latitude, end_longitude "
  79 + "FROM garden_gis_road "
  80 + "WHERE (starting_latitude IS NOT NULL AND starting_latitude != '') "
  81 + " OR (end_latitude IS NOT NULL AND end_latitude != '')"
  82 + )
  83 + rows = cur.fetchall()
  84 + updated = 0
  85 + for row in rows:
  86 + id_, slat, slng, elat, elng = row
  87 + updates = []
  88 + params = []
  89 +
  90 + if slat and slng and slat.strip():
  91 + try:
  92 + nlng, nlat = wgs84_to_gcj02(float(slng), float(slat))
  93 + updates.append('starting_longitude = %s')
  94 + updates.append('starting_latitude = %s')
  95 + params.append(str(round(nlng, 6)))
  96 + params.append(str(round(nlat, 6)))
  97 + except ValueError:
  98 + pass
  99 +
  100 + if elat and elng and elat.strip():
  101 + try:
  102 + nlng, nlat = wgs84_to_gcj02(float(elng), float(elat))
  103 + updates.append('end_longitude = %s')
  104 + updates.append('end_latitude = %s')
  105 + params.append(str(round(nlng, 6)))
  106 + params.append(str(round(nlat, 6)))
  107 + except ValueError:
  108 + pass
  109 +
  110 + if updates:
  111 + sql = 'UPDATE garden_gis_road SET ' + ', '.join(updates) + ' WHERE id = %s'
  112 + params.append(id_)
  113 + cur.execute(sql, params)
  114 + updated += 1
  115 +
  116 + conn.commit()
  117 + print(f'起点/终点坐标: 转换 {updated} 条')
  118 +
  119 + # 2. 转换围栏坐标
  120 + cur.execute(
  121 + "SELECT COUNT(*) FROM garden_gis_road "
  122 + "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
  123 + )
  124 + total = cur.fetchone()[0]
  125 + print(f'围栏坐标记录: {total} 条')
  126 +
  127 + batch_size = 500
  128 + updated = 0
  129 + for offset in range(0, total, batch_size):
  130 + cur.execute(
  131 + "SELECT id, gis_polygon_coords FROM garden_gis_road "
  132 + "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != '' "
  133 + "LIMIT %s OFFSET %s",
  134 + (batch_size, offset)
  135 + )
  136 + for id_, wkt in cur.fetchall():
  137 + new_wkt = transform_wkt(wkt)
  138 + if new_wkt != wkt:
  139 + cur.execute(
  140 + "UPDATE garden_gis_road SET gis_polygon_coords = %s WHERE id = %s",
  141 + (new_wkt, id_)
  142 + )
  143 + updated += 1
  144 + conn.commit()
  145 + print(f' 进度: {min(offset + batch_size, total)}/{total}, 已更新: {updated}')
  146 +
  147 + print(f'围栏坐标: 转换 {updated} 条')
  148 +
  149 + # 3. 验证
  150 + cur.execute(
  151 + "SELECT COUNT(*) FROM garden_gis_road "
  152 + "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
  153 + )
  154 + total = cur.fetchone()[0]
  155 + unconverted = 0
  156 + cur.execute(
  157 + "SELECT id, gis_polygon_coords FROM garden_gis_road "
  158 + "WHERE gis_polygon_coords IS NOT NULL AND gis_polygon_coords != ''"
  159 + )
  160 + for id_, wkt in cur.fetchall():
  161 + m = re.search(r'(\d+\.?\d*)\s+(\d+\.?\d*)', wkt)
  162 + if m:
  163 + wlng, wlat = float(m.group(1)), float(m.group(2))
  164 + nlng, nlat = wgs84_to_gcj02(wlng, wlat)
  165 + if abs(wlng - nlng) < 0.00001:
  166 + unconverted += 1
  167 +
  168 + print(f'\n验证: {unconverted}/{total} 条未转换')
  169 + if unconverted == 0:
  170 + print('所有坐标已成功转换为 GCJ02(高德坐标系)')
  171 +
  172 + cur.close()
  173 + conn.close()
  174 +
  175 +
  176 +if __name__ == '__main__':
  177 + main()
... ...
script/shell/deploy.sh
1 1 #!/bin/bash
2   -set -e
  2 +set -eo pipefail
3 3  
4 4 DATE=$(date +%Y%m%d%H%M)
5 5 # 基础路径
... ... @@ -9,9 +9,9 @@ SOURCE_PATH=$BASE_PATH/build
9 9 # 服务名称。同时约定部署服务的 jar 包名字也为它。
10 10 SERVER_NAME=urbanops-server
11 11 # 环境
12   -PROFILES_ACTIVE=development
  12 +PROFILES_ACTIVE=dev
13 13 # 健康检查 URL
14   -HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/
  14 +HEALTH_CHECK_URL=http://127.0.0.1:48081/actuator/health/
15 15  
16 16 # heapError 存放路径
17 17 HEAP_ERROR_PATH=$BASE_PATH/heapError
... ... @@ -33,7 +33,8 @@ function backup() {
33 33 # 如果存在,则备份到 backup 目录下,使用时间作为后缀
34 34 else
35 35 echo "[backup] 开始备份 $SERVER_NAME ..."
36   - cp $BASE_PATH/$SERVER_NAME.jar $BASE_PATH/backup/$SERVER_NAME-$DATE.jar
  36 + mkdir -p "$BASE_PATH/backup"
  37 + cp "$BASE_PATH/$SERVER_NAME.jar" "$BASE_PATH/backup/$SERVER_NAME-$DATE.jar"
37 38 echo "[backup] 备份 $SERVER_NAME 完成"
38 39 fi
39 40 }
... ... @@ -47,12 +48,12 @@ function transfer() {
47 48 echo "[transfer] $BASE_PATH/$SERVER_NAME.jar 不存在,跳过删除"
48 49 else
49 50 echo "[transfer] 移除 $BASE_PATH/$SERVER_NAME.jar 完成"
50   - rm $BASE_PATH/$SERVER_NAME.jar
  51 + rm "$BASE_PATH/$SERVER_NAME.jar"
51 52 fi
52 53  
53 54 # 复制新 jar 包
54 55 echo "[transfer] 从 $SOURCE_PATH 中获取 $SERVER_NAME.jar 并迁移至 $BASE_PATH ...."
55   - cp $SOURCE_PATH/$SERVER_NAME.jar $BASE_PATH
  56 + cp "$SOURCE_PATH/$SERVER_NAME.jar" "$BASE_PATH"
56 57  
57 58 echo "[transfer] 转移 $SERVER_NAME.jar 完成"
58 59 }
... ... @@ -60,7 +61,7 @@ function transfer() {
60 61 # 停止:优雅关闭之前已经启动的服务
61 62 function stop() {
62 63 echo "[stop] 开始停止 $BASE_PATH/$SERVER_NAME"
63   - PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
  64 + PID=$(ps -ef | grep "$BASE_PATH/$SERVER_NAME.jar" | grep -v "grep" | awk '{print $2}')
64 65 # 如果 Java 服务启动中,则进行关闭
65 66 if [ -n "$PID" ]; then
66 67 # 正常关闭
... ... @@ -70,7 +71,7 @@ function stop() {
70 71 for ((i = 0; i < 120; i++))
71 72 do
72 73 sleep 1
73   - PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
  74 + PID=$(ps -ef | grep "$BASE_PATH/$SERVER_NAME.jar" | grep -v "grep" | awk '{print $2}')
74 75 if [ -n "$PID" ]; then
75 76 echo -e ".\c"
76 77 else
... ... @@ -99,7 +100,8 @@ function start() {
99 100 echo "[start] PROFILES: $PROFILES_ACTIVE"
100 101  
101 102 # 开始启动
102   - BUILD_ID=dontKillMe nohup java -server $JAVA_OPS $JAVA_AGENT -jar $BASE_PATH/$SERVER_NAME.jar --spring.profiles.active=$PROFILES_ACTIVE &
  103 + mkdir -p "$HEAP_ERROR_PATH"
  104 + nohup java -server $JAVA_OPS $JAVA_AGENT -jar "$BASE_PATH/$SERVER_NAME.jar" --spring.profiles.active=$PROFILES_ACTIVE >> "$BASE_PATH/nohup.out" 2>&1 &
103 105 echo "[start] 启动 $BASE_PATH/$SERVER_NAME 完成"
104 106 }
105 107  
... ... @@ -112,7 +114,7 @@ function healthCheck() {
112 114 for ((i = 0; i < 120; i++))
113 115 do
114 116 # 请求健康检查地址,只获取状态码。
115   - result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo "000"`
  117 + result=$(curl -I -m 10 -o /dev/null -s -w %{http_code} "$HEALTH_CHECK_URL" || echo "000")
116 118 # 如果状态码为 200,则说明健康检查通过
117 119 if [ "$result" == "200" ]; then
118 120 echo "[healthCheck] 健康检查通过";
... ... @@ -127,24 +129,29 @@ function healthCheck() {
127 129 # 健康检查未通过,则异常退出 shell 脚本,不继续部署。
128 130 if [ ! "$result" == "200" ]; then
129 131 echo "[healthCheck] 健康检查不通过,可能部署失败。查看日志,自行判断是否启动成功";
130   - tail -n 10 nohup.out
  132 + tail -n 10 "$BASE_PATH/nohup.out"
131 133 exit 1;
132 134 # 健康检查通过,打印最后 10 行日志,可能部署的人想看下日志。
133 135 else
134   - tail -n 10 nohup.out
  136 + tail -n 10 "$BASE_PATH/nohup.out"
135 137 fi
136 138 # 如果未配置健康检查,则 sleep 120 秒,人工看日志是否部署成功。
137 139 else
138 140 echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 120 秒";
139 141 sleep 120
140 142 echo "[healthCheck] sleep 120 秒完成,查看日志,自行判断是否启动成功";
141   - tail -n 50 nohup.out
  143 + tail -n 50 "$BASE_PATH/nohup.out"
142 144 fi
143 145 }
144 146  
145 147 # 部署
146 148 function deploy() {
147   - cd $BASE_PATH
  149 + cd "$BASE_PATH" || { echo "[deploy] 错误:$BASE_PATH 目录不存在"; exit 1; }
  150 + if [ ! -d "$SOURCE_PATH" ]; then
  151 + echo "[deploy] 错误:构建目录 $SOURCE_PATH 不存在"
  152 + exit 1
  153 + fi
  154 + mkdir -p "$BASE_PATH/backup" "$HEAP_ERROR_PATH"
148 155 # 备份原 jar
149 156 backup
150 157 # 停止 Java 服务
... ...
sql/dis-check-menu.sql 0 → 100644
  1 +-- =====================================================
  2 +-- dis核对 完整部署脚本
  3 +-- 数据库: urban_ops_agent
  4 +-- 包含: 表结构变更 + 菜单创建 + 角色授权
  5 +-- 执行方式: 在对应环境的数据库中执行此 SQL
  6 +-- =====================================================
  7 +
  8 +-- ==========================================
  9 +-- Part 0: garden_gis_road 表结构变更
  10 +-- ==========================================
  11 +-- 新增多边形围栏边界坐标字段(MySQL 8.x 兼容写法)
  12 +SET @col_exists = (
  13 + SELECT COUNT(*) FROM information_schema.COLUMNS
  14 + WHERE TABLE_SCHEMA = DATABASE()
  15 + AND TABLE_NAME = 'garden_gis_road'
  16 + AND COLUMN_NAME = 'gis_polygon_coords'
  17 +);
  18 +
  19 +SET @sql = IF(@col_exists = 0,
  20 + 'ALTER TABLE garden_gis_road ADD COLUMN gis_polygon_coords LONGTEXT NOT NULL COMMENT ''多边形/矩形围栏边界坐标,格式:POLYGON((x1 y1, x2 y2, ..., xn yn, x1 y1)) 存储的是WGS84坐标系''',
  21 + 'SELECT ''COLUMN gis_polygon_coords already exists'' AS info'
  22 +);
  23 +PREPARE stmt FROM @sql;
  24 +EXECUTE stmt;
  25 +DEALLOCATE PREPARE stmt;
  26 +
  27 +-- ==========================================
  28 +-- Part 1: 菜单创建
  29 +-- ==========================================
  30 +
  31 +-- 1. 新增「dis核对」子菜单(位于电子围栏下)
  32 +INSERT INTO system_menu
  33 +(name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
  34 +VALUES
  35 +('dis核对', '', 2, 1, 5698, 'disCheck', 'ep:view', 'fence/dis-check/index', 'DisCheck', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
  36 +
  37 +-- 2. 获取上一步插入的菜单ID,用于后续按钮权限插入
  38 +SET @dis_check_menu_id = LAST_INSERT_ID();
  39 +
  40 +-- 3. 新增「导出」按钮权限(挂在新菜单下)
  41 +INSERT INTO system_menu
  42 +(name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
  43 +VALUES
  44 +('导出', 'garden:gis-road:export', 3, 0, @dis_check_menu_id, '', '', '', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
  45 +
  46 +-- 4. 新增「查询」按钮权限
  47 +INSERT INTO system_menu
  48 +(name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
  49 +VALUES
  50 +('查询', 'garden:gis-road:query', 3, 1, @dis_check_menu_id, '', '', '', 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
  51 +
  52 +-- 5. 给超级管理员(role_id=1)分配菜单权限
  53 +INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time)
  54 +VALUES
  55 +(1, @dis_check_menu_id, '1', NOW(), '1', NOW());
  56 +
  57 +INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time)
  58 +SELECT 1, id, '1', NOW(), '1', NOW()
  59 +FROM system_menu
  60 +WHERE parent_id = @dis_check_menu_id AND deleted = 0;
  61 +
  62 +-- =====================================================
  63 +-- 验证插入结果
  64 +-- =====================================================
  65 +SELECT id, name, type, parent_id, path, component, permission
  66 +FROM system_menu
  67 +WHERE parent_id = 5698 OR id = @dis_check_menu_id
  68 +ORDER BY parent_id, sort;
  69 +
  70 +SELECT rm.role_id, rm.menu_id, m.name, m.permission
  71 +FROM system_role_menu rm
  72 +JOIN system_menu m ON rm.menu_id = m.id
  73 +WHERE rm.menu_id = @dis_check_menu_id
  74 + OR rm.menu_id IN (SELECT id FROM system_menu WHERE parent_id = @dis_check_menu_id);
... ...
sql/garden_road_add_gis_fields.sql 0 → 100644
  1 +-- garden_road 表新增 GIS 关联字段
  2 +ALTER TABLE garden_road
  3 + ADD COLUMN gis_plot_name VARCHAR(200) DEFAULT NULL COMMENT 'GIS 图斑名称',
  4 + ADD COLUMN gis_plot_area FLOAT DEFAULT NULL COMMENT 'GIS 图斑面积',
  5 + ADD COLUMN gis_is_related TINYINT DEFAULT 0 COMMENT '是否关联GIS (0=未关联, 1=已关联)';
... ...
sql/mysql/garden_tree_inspection.sql 0 → 100644
  1 +-- 行道树巡检与安全风险评估表
  2 +CREATE TABLE IF NOT EXISTS `garden_tree_inspection` (
  3 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  4 + `tree_id` bigint NOT NULL COMMENT '一树一档案ID',
  5 + `treenumber` varchar(64) DEFAULT NULL COMMENT '树木编号',
  6 + `inspection_time` datetime NOT NULL COMMENT '巡检时间',
  7 + `inspector_id` bigint DEFAULT NULL COMMENT '巡检人ID',
  8 + `inspector_name` varchar(64) DEFAULT NULL COMMENT '巡检人姓名',
  9 + `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  10 +
  11 + -- 缺陷评估指标得分 (数值直接存对应选项的分数)
  12 + `root_disease` int DEFAULT '0' COMMENT '根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽',
  13 + `root_anchorage` int DEFAULT '0' COMMENT '根系下扎情况:0-良好,7-存在隆起或盘根',
  14 + `root_cutting` int DEFAULT '0' COMMENT '工程切根:0-无,5-存在',
  15 + `collar_wood_damage` int DEFAULT '0' COMMENT '根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%',
  16 + `collar_bark_damage` int DEFAULT '0' COMMENT '根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%',
  17 + `collar_loosening` int DEFAULT '0' COMMENT '根颈松动:0-不存在,100-存在',
  18 + `trunk_wood_damage` int DEFAULT '0' COMMENT '主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%',
  19 + `trunk_tilt` int DEFAULT '0' COMMENT '主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度',
  20 + `trunk_bark_damage` int DEFAULT '0' COMMENT '主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%',
  21 + `crown_loose_branch` int DEFAULT '0' COMMENT '易落枝:0-未发现,2-<1/10, 3->=1/10',
  22 + `crown_collar_abnormal` int DEFAULT '0' COMMENT '枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹',
  23 + `crown_ventilation_balance` int DEFAULT '0' COMMENT '树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠',
  24 +
  25 + -- 权重因子指标值和赋权值
  26 + `tree_species_type` varchar(32) DEFAULT NULL COMMENT '树种类型:深根性树种, 浅根性树种',
  27 + `tree_species_weight` double DEFAULT '1.0' COMMENT '树种类型权重:深根性-1.0, 浅根性-1.1',
  28 + `planting_years` varchar(32) DEFAULT NULL COMMENT '栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上',
  29 + `planting_years_weight` double DEFAULT '1.0' COMMENT '栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2',
  30 + `is_wind_corridor` tinyint DEFAULT '0' COMMENT '是否处于风口:0-否, 1-是',
  31 + `wind_corridor_weight` double DEFAULT '1.0' COMMENT '风口权重:否-1.0, 是-2.0',
  32 + `tree_pool_type` varchar(32) DEFAULT NULL COMMENT '树池类型:联通树池, 独立树池, 树池硬化',
  33 + `tree_pool_weight` double DEFAULT '1.0' COMMENT '树池类型权重:联通树池-1.0, 独立树池-1.2, 树池硬化-1.5',
  34 + `tree_pool_width_dbh_ratio` varchar(32) DEFAULT NULL COMMENT '树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下',
  35 + `tree_pool_ratio_weight` double DEFAULT '1.0' COMMENT '树池比权重:7倍及以上-1.0, 5-7倍-1.1, 3-5倍-1.2, 3倍以下-1.3',
  36 +
  37 + -- 是否展开应急评估及应急权重
  38 + `is_emergency` tinyint DEFAULT '0' COMMENT '是否进行应急评估:0-否, 1-是',
  39 + `wind_power` varchar(32) DEFAULT NULL COMMENT '应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上',
  40 + `wind_power_weight` double DEFAULT '1.0' COMMENT '风力权重:7级及以下-1.0, 8-9级-1.5, 10级-2.0, 10级以上-3.0',
  41 +
  42 + -- 评估结果
  43 + `defect_score` int DEFAULT '0' COMMENT '树木缺陷得分',
  44 + `normal_score` double DEFAULT '0' COMMENT '常规情况下安全风险得分',
  45 + `normal_level` varchar(64) DEFAULT NULL COMMENT '常规情况下风险等级',
  46 + `emergency_score` double DEFAULT NULL COMMENT '应急情况下安全风险得分',
  47 + `emergency_level` varchar(64) DEFAULT NULL COMMENT '应急情况下风险等级',
  48 +
  49 + -- 现状及处理措施
  50 + `treatment_suggestion` varchar(512) DEFAULT NULL COMMENT '建议处置措施',
  51 + `is_treated` tinyint DEFAULT '0' COMMENT '是否已处置:0-未处置, 1-已处置',
  52 + `photos` varchar(1024) DEFAULT NULL COMMENT '现场照片链接(JSON 数组)',
  53 + `estimator_company` varchar(255) DEFAULT NULL COMMENT '评估单位(公司)',
  54 + `estimator` varchar(100) DEFAULT NULL COMMENT '评估人',
  55 +
  56 +
  57 + -- 基础审计与租户字段(继承 BaseDO / TenantBaseDO)
  58 + `creator` varchar(64) DEFAULT NULL COMMENT '创建者',
  59 + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  60 + `updater` varchar(64) DEFAULT NULL COMMENT '更新者',
  61 + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  62 + `deleted` tinyint DEFAULT '0' COMMENT '是否删除:0-未删除, 1-已删除',
  63 + `tenant_id` bigint DEFAULT '0' COMMENT '租户ID',
  64 + PRIMARY KEY (`id`),
  65 + KEY `idx_tree_id` (`tree_id`)
  66 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='行道树巡检与安全评估记录';
... ...
sql/sync_land_forest_to_road.sql 0 → 100644
  1 +-- ============================================================
  2 +-- 将 garden_gis_land_forest_data 数据同步到 garden_gis_road
  3 +-- 按 greenname + greentype 分组聚合
  4 +-- - xbmj 求和 → gis_plot_area
  5 +-- - geometry 所有 MULTIPOLYGON 环拼接 → gis_polygon_coords (WKT)
  6 +-- - 其他 gis_ 字段取分组内 MAX 值
  7 +--
  8 +-- 注意:MySQL 不支持 ST_Union 聚合函数,geometry 并集通过
  9 +-- GROUP_CONCAT 拼接各 MULTIPOLYGON 的内部环来实现,不做空间融合。
  10 +-- 如需真正融合重叠区域,建议在 Java 层用 JTS 处理。
  11 +-- ============================================================
  12 +
  13 +-- 避免 GROUP_CONCAT 默认 1024 字节限制导致 WKT 截断
  14 +SET SESSION group_concat_max_len = 10485760; -- 10MB
  15 +
  16 +INSERT INTO garden_gis_road (
  17 + gis_plot_name,
  18 + gis_green_type_name,
  19 + gis_plot_area,
  20 + gis_polygon_coords,
  21 + gis_owner_unit,
  22 + gis_property_unit,
  23 + gis_manage_unit,
  24 + gis_street,
  25 + gis_plot_code,
  26 + gis_green_type_code,
  27 + street_id,
  28 + company_id,
  29 + dept_id
  30 +)
  31 +SELECT
  32 + greenname AS gis_plot_name,
  33 + greentype AS gis_green_type_name,
  34 + ROUND(SUM(xbmj), 2) AS gis_plot_area,
  35 + CONCAT(
  36 + 'MULTIPOLYGON(',
  37 + GROUP_CONCAT(
  38 + SUBSTR(
  39 + ST_AsText(geometry),
  40 + 14, -- 跳过 'MULTIPOLYGON('(13字符)
  41 + LENGTH(ST_AsText(geometry)) - 14 -- 去掉首尾各1个括号
  42 + )
  43 + SEPARATOR ','
  44 + ),
  45 + ')'
  46 + ) AS gis_polygon_coords,
  47 + MAX(cqdw) AS gis_owner_unit,
  48 + MAX(cqdw) AS gis_property_unit,
  49 + MAX(gl_dw) AS gis_manage_unit,
  50 + MAX(CONCAT_WS('-', sheng, shi, xian, xiang)) AS gis_street,
  51 + '' AS gis_plot_code,
  52 + '' AS gis_green_type_code,
  53 + '' AS street_id,
  54 + 0 AS company_id,
  55 + 0 AS dept_id
  56 +FROM garden_gis_land_forest_data
  57 +WHERE greenname IS NOT NULL
  58 + AND greentype IS NOT NULL
  59 +GROUP BY greenname, greentype;
... ...
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/BusinessLineConvert.java 0 → 100644
  1 +package com.zteits.urbanops.framework.excel.core.convert;
  2 +
  3 +import cn.idev.excel.converters.Converter;
  4 +import cn.idev.excel.enums.CellDataTypeEnum;
  5 +import cn.idev.excel.metadata.GlobalConfiguration;
  6 +import cn.idev.excel.metadata.data.ReadCellData;
  7 +import cn.idev.excel.metadata.data.WriteCellData;
  8 +import cn.idev.excel.metadata.property.ExcelContentProperty;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +@Slf4j
  12 +public class BusinessLineConvert implements Converter<Object> {
  13 +
  14 + @Override
  15 + public Class<?> supportJavaTypeKey() {
  16 + throw new UnsupportedOperationException("暂不支持,也不需要");
  17 + }
  18 +
  19 + @Override
  20 + public CellDataTypeEnum supportExcelTypeKey() {
  21 + throw new UnsupportedOperationException("暂不支持,也不需要");
  22 + }
  23 +
  24 + /**
  25 + * 导入:业务线名称(园林,市政) → 编码(yl,sz)
  26 + */
  27 + @Override
  28 + public Object convertToJavaData(ReadCellData<?> readCellData, ExcelContentProperty contentProperty,
  29 + GlobalConfiguration globalConfiguration) {
  30 + String cellValue = readCellData.getStringValue();
  31 + if (cellValue == null || cellValue.isBlank()) {
  32 + return null;
  33 + }
  34 +
  35 + try {
  36 + // 按逗号切割
  37 + String[] lineArray = cellValue.split(",");
  38 + StringBuilder codeSb = new StringBuilder();
  39 +
  40 + for (String line : lineArray) {
  41 + String code = convertLineToCode(line.trim());
  42 + if (codeSb.length() > 0) {
  43 + codeSb.append(",");
  44 + }
  45 + codeSb.append(code);
  46 + }
  47 +
  48 + return codeSb.toString();
  49 + } catch (Exception e) {
  50 + log.error("[convertToJavaData][业务线转换异常] {}", cellValue, e);
  51 + return null;
  52 + }
  53 + }
  54 +
  55 + /**
  56 + * 导出:业务线编码(yl,sz) → 名称(园林,市政)
  57 + */
  58 + @Override
  59 + public WriteCellData<?> convertToExcelData(Object value, ExcelContentProperty contentProperty,
  60 + GlobalConfiguration globalConfiguration) {
  61 + if (value == null) {
  62 + return new WriteCellData<>("");
  63 + }
  64 +
  65 + try {
  66 + String codeValue = String.valueOf(value);
  67 + String[] codeArray = codeValue.split(",");
  68 + StringBuilder nameSb = new StringBuilder();
  69 +
  70 + for (String code : codeArray) {
  71 + String name = convertCodeToLine(code.trim());
  72 + if (nameSb.length() > 0) {
  73 + nameSb.append(",");
  74 + }
  75 + nameSb.append(name);
  76 + }
  77 +
  78 + return new WriteCellData<>(nameSb.toString());
  79 + } catch (Exception e) {
  80 + log.error("[convertToExcelData][业务线转换异常] {}", value, e);
  81 + return new WriteCellData<>("");
  82 + }
  83 + }
  84 +
  85 + /**
  86 + * 名称 → 编码
  87 + */
  88 + private String convertLineToCode(String line) {
  89 + return switch (line) {
  90 + case "园林" -> "yl";
  91 + case "市政" -> "sz";
  92 + case "物业" -> "wy";
  93 + case "秩序管理" -> "zx";
  94 + case "环境卫生" -> "hj";
  95 + default -> "";
  96 + };
  97 + }
  98 +
  99 + /**
  100 + * 编码 → 名称
  101 + */
  102 + private String convertCodeToLine(String code) {
  103 + return switch (code) {
  104 + case "yl" -> "园林";
  105 + case "sz" -> "市政";
  106 + case "wy" -> "物业";
  107 + case "zx" -> "秩序管理";
  108 + case "hj" -> "环境卫生";
  109 + default -> "";
  110 + };
  111 + }
  112 +}
... ...
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/convert/ImageListConverter.java 0 → 100644
  1 +package com.zteits.urbanops.framework.excel.core.convert;
  2 +
  3 +import cn.idev.excel.converters.Converter;
  4 +import cn.idev.excel.enums.CellDataTypeEnum;
  5 +import cn.idev.excel.metadata.GlobalConfiguration;
  6 +import cn.idev.excel.metadata.data.ImageData;
  7 +import cn.idev.excel.metadata.data.ReadCellData;
  8 +import cn.idev.excel.metadata.data.WriteCellData;
  9 +import cn.idev.excel.metadata.property.ExcelContentProperty;
  10 +import cn.idev.excel.util.FileUtils;
  11 +import cn.idev.excel.util.ListUtils;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.util.StringUtils;
  14 +
  15 +import java.io.ByteArrayOutputStream;
  16 +import java.io.File;
  17 +import java.io.InputStream;
  18 +import java.net.HttpURLConnection;
  19 +import java.net.URL;
  20 +import java.util.List;
  21 +
  22 +@Slf4j
  23 +public class ImageListConverter implements Converter<List<String>> {
  24 +
  25 + // 横向排列:每张图片宽度 + 间距
  26 + private static final int IMAGE_WIDTH = 120;
  27 + private static final int IMAGE_GAP = 15;
  28 + private static final int MAX_IMAGE_SIZE = 10 * 1024 * 1024;
  29 + private static final int CONNECT_TIMEOUT = 5000;
  30 + private static final int READ_TIMEOUT = 10000;
  31 +
  32 + @Override
  33 + public Class<?> supportJavaTypeKey() {
  34 + return List.class;
  35 + }
  36 +
  37 + @Override
  38 + public CellDataTypeEnum supportExcelTypeKey() {
  39 + return CellDataTypeEnum.EMPTY;
  40 + }
  41 +
  42 + @Override
  43 + public List<String> convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
  44 + GlobalConfiguration globalConfiguration) {
  45 + return null;
  46 + }
  47 +
  48 + @Override
  49 + public WriteCellData<?> convertToExcelData(List<String> imageList, ExcelContentProperty contentProperty,
  50 + GlobalConfiguration globalConfiguration) {
  51 + try {
  52 + if (imageList == null || imageList.isEmpty()) {
  53 + return new WriteCellData<>("无图片");
  54 + }
  55 +
  56 + List<ImageData> imageDataList = ListUtils.newArrayList();
  57 + int leftOffset = 0; // 横向偏移量
  58 +
  59 + for (String path : imageList) {
  60 + if (!StringUtils.hasText(path)) continue;
  61 +
  62 + byte[] imageBytes = path.startsWith("http") ? loadNetworkImage(path) : loadLocalImage(path);
  63 + if (imageBytes == null || imageBytes.length == 0 || imageBytes.length > MAX_IMAGE_SIZE) {
  64 + log.warn("跳过无效图片:{}", path);
  65 + continue;
  66 + }
  67 +
  68 + ImageData imageData = new ImageData();
  69 + imageData.setImage(imageBytes);
  70 + imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);
  71 +
  72 + // ====================== 核心:横向偏移 ======================
  73 + imageData.setLeft(leftOffset);
  74 + // =============================================================
  75 +
  76 + imageDataList.add(imageData);
  77 + leftOffset += IMAGE_WIDTH + IMAGE_GAP;
  78 + }
  79 +
  80 + if (imageDataList.isEmpty()) {
  81 + return new WriteCellData<>("无有效图片");
  82 + }
  83 +
  84 + WriteCellData<?> writeCellData = new WriteCellData<>();
  85 + writeCellData.setType(CellDataTypeEnum.EMPTY);
  86 + writeCellData.setImageDataList(imageDataList);
  87 +
  88 + return writeCellData;
  89 +
  90 + } catch (Exception e) {
  91 + log.error("图片导出失败", e);
  92 + return new WriteCellData<>("图片加载失败");
  93 + }
  94 + }
  95 +
  96 + private byte[] loadLocalImage(String imagePath) throws Exception {
  97 + File file = new File(imagePath);
  98 + if (!file.exists() || !file.isFile()) {
  99 + log.warn("本地图片不存在:{}", imagePath);
  100 + return null;
  101 + }
  102 + return FileUtils.readFileToByteArray(file);
  103 + }
  104 +
  105 + private byte[] loadNetworkImage(String imageUrl) throws Exception {
  106 + URL url = new URL(imageUrl);
  107 + HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  108 + conn.setConnectTimeout(CONNECT_TIMEOUT);
  109 + conn.setReadTimeout(READ_TIMEOUT);
  110 + try {
  111 + if (conn.getResponseCode() != 200) return null;
  112 + try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
  113 + byte[] buffer = new byte[4096];
  114 + int len;
  115 + while ((len = in.read(buffer)) != -1) out.write(buffer, 0, len);
  116 + return out.toByteArray();
  117 + }
  118 + } finally {
  119 + conn.disconnect();
  120 + }
  121 + }
  122 +}
... ...
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/handler/ImageColumnWidthHandler.java 0 → 100644
  1 +package com.zteits.urbanops.framework.excel.core.handler;
  2 +
  3 +import cn.idev.excel.write.handler.SheetWriteHandler;
  4 +import cn.idev.excel.write.handler.context.SheetWriteHandlerContext;
  5 +import lombok.RequiredArgsConstructor;
  6 +import org.apache.poi.ss.usermodel.Sheet;
  7 +
  8 +/**
  9 + * 图片列自动加宽处理器
  10 + */
  11 +@RequiredArgsConstructor
  12 +public class ImageColumnWidthHandler implements SheetWriteHandler {
  13 +
  14 + // 需要加宽的列索引(从0开始),比如你的图片列在第3列,就传2
  15 + private final int imageColumnIndex;
  16 + // 加宽后的列宽(单位:字符宽度,Excel默认1个字符宽度≈256像素)
  17 + private final int columnWidth;
  18 +
  19 + @Override
  20 + public void afterSheetCreate(SheetWriteHandlerContext context) {
  21 + Sheet sheet = context.getWriteSheetHolder().getSheet();
  22 + // 设置指定列的宽度
  23 + sheet.setColumnWidth(imageColumnIndex, columnWidth);
  24 + }
  25 +}
... ...
urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java
... ... @@ -44,9 +44,27 @@ public class ExcelUtils {
44 44 }
45 45  
46 46 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
47   - return FastExcelFactory.read(file.getInputStream(), head, null)
48   - .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
49   - .doReadAllSync();
  47 + List<T> list = FastExcelFactory.read(file.getInputStream(), head, null)
  48 + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
  49 + .doReadAllSync();
  50 +
  51 + // 过滤:对象不为 null + 不是全字段 null
  52 + if (list != null) {
  53 + list.removeIf(item -> {
  54 + if (item == null) return true;
  55 + // 判断所有字段是否都为 null
  56 + for (java.lang.reflect.Field field : item.getClass().getDeclaredFields()) {
  57 + field.setAccessible(true);
  58 + try {
  59 + if (field.get(item) != null) {
  60 + return false;
  61 + }
  62 + } catch (Exception e) { }
  63 + }
  64 + return true;
  65 + });
  66 + }
  67 + return list;
50 68 }
51 69  
52 70 public static <T> List<T> read(MultipartFile file, Class<T> head, Integer startIndex) throws IOException {
... ...
urbanops-module-api/urbanops-module-workorder-api/src/main/java/com/zteits/urbanops/module/workorder/dto/AppGardenWorkOrderInspectorReqVO.java
... ... @@ -63,6 +63,12 @@ public class AppGardenWorkOrderInspectorReqVO {
63 63 @Schema(description = "工单名称", example = "绿地卫生")
64 64 private String orderName;
65 65  
  66 + @Schema(description = "三级编码")
  67 + private String orderCode;
  68 +
  69 + @Schema(description = "业务类型(一级编码)")
  70 + private String busiType;
  71 +
66 72 @Schema(description = "来源ID 工单来源 1、巡查,2、游客居民,3、12345,4、网格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
67 73 //@NotNull(message = "来源ID不能为空")
68 74 private Integer sourceId;
... ...
urbanops-module-api/urbanops-module-workorder-api/src/main/java/com/zteits/urbanops/module/workorder/dto/MainInfoRespVO.java
... ... @@ -31,6 +31,13 @@ public class MainInfoRespVO {
31 31 @ExcelProperty("工单名称")
32 32 private String orderName;
33 33  
  34 + @Schema(description = "三级编码")
  35 + private String orderCode;
  36 +
  37 + @Schema(description = "业务类型(一级编码)")
  38 + private String busiType;
  39 +
  40 +
34 41 /**
35 42 * 工单类型 Q:快速工单,C:普通工单,O:其他工单
36 43 */
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/emergencytask/vo/EmergencyTaskExcleRespVO.java
... ... @@ -2,8 +2,9 @@ package com.zteits.urbanops.module.garden.controller.admin.emergencytask.vo;
2 2  
3 3 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
4 4 import cn.idev.excel.annotation.ExcelProperty;
  5 +import cn.idev.excel.annotation.write.style.ColumnWidth;
  6 +import com.zteits.urbanops.framework.excel.core.convert.ImageListConverter;
5 7 import com.zteits.urbanops.framework.excel.core.convert.ListToStringConverter;
6   -import com.zteits.urbanops.module.garden.dal.dataobject.emergencytask.EmergencyMachineryDO;
7 8 import io.swagger.v3.oas.annotations.media.Schema;
8 9 import lombok.Data;
9 10  
... ... @@ -52,7 +53,9 @@ public class EmergencyTaskExcleRespVO {
52 53 private LocalDateTime rescueStartTime;
53 54  
54 55 @Schema(description = "抢险中照片", requiredMode = Schema.RequiredMode.REQUIRED)
55   - @ExcelProperty(value="抢险中照片", converter = ListToStringConverter.class)
  56 + //@ExcelProperty(value="抢险中照片", converter = ListToStringConverter.class)
  57 + @ExcelProperty(value = "抢险中照片", converter = ImageListConverter.class)
  58 + @ColumnWidth(70)
56 59 private List<String> emergencyImg;
57 60  
58 61 @Schema(description = "树种(如:国槐)")
... ... @@ -72,7 +75,9 @@ public class EmergencyTaskExcleRespVO {
72 75 private Integer personnelCount;
73 76  
74 77 @Schema(description = "抢险结束照片", requiredMode = Schema.RequiredMode.REQUIRED)
75   - @ExcelProperty(value="抢险结束照片", converter = ListToStringConverter.class)
  78 + //@ExcelProperty(value="抢险结束照片", converter = ListToStringConverter.class)
  79 + @ExcelProperty(value = "抢险结束照片", converter = ImageListConverter.class)
  80 + @ColumnWidth(70)
76 81 private List<String> emergencyEndImg;
77 82  
78 83 @Schema(description = "备注")
... ... @@ -111,4 +116,4 @@ public class EmergencyTaskExcleRespVO {
111 116 @ExcelProperty("归属公司")
112 117 private String companyName;
113 118  
114   -}
115 119 \ No newline at end of file
  120 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisRoadController.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis;
  2 +
  3 +import com.zteits.urbanops.framework.common.pojo.PageParam;
  4 +import org.slf4j.Logger;
  5 +import org.slf4j.LoggerFactory;
  6 +import org.springframework.web.bind.annotation.*;
  7 +import jakarta.annotation.Resource;
  8 +import org.springframework.validation.annotation.Validated;
  9 +import org.springframework.security.access.prepost.PreAuthorize;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +
  14 +import jakarta.validation.*;
  15 +import jakarta.servlet.http.*;
  16 +import java.io.IOException;
  17 +import java.time.LocalDate;
  18 +import java.time.format.DateTimeFormatter;
  19 +import java.util.*;
  20 +
  21 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  22 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
  23 +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  24 +
  25 +import com.zteits.urbanops.framework.excel.core.util.ExcelUtils;
  26 +import com.zteits.urbanops.framework.apilog.core.annotation.ApiAccessLog;
  27 +import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.*;
  28 +
  29 +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*;
  30 +import com.zteits.urbanops.module.garden.service.gis.GisRoadService;
  31 +
  32 +@Tag(name = "管理后台 - GIS 道路管理")
  33 +@RestController
  34 +@RequestMapping("/garden/gis/road")
  35 +@Validated
  36 +public class GisRoadController {
  37 + private static final Logger log = LoggerFactory.getLogger(GisRoadController.class);
  38 +
  39 + @Resource
  40 + private GisRoadService gisRoadService;
  41 +
  42 + @GetMapping("/list")
  43 + @Operation(summary = "获得 GIS 道路分页列表")
  44 + @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
  45 + public CommonResult<PageResult<GisRoadRespVO>> getGisRoadList(@Valid GisRoadPageReqVO pageReqVO) {
  46 + PageResult<GisRoadRespVO> pageResult = gisRoadService.getGisRoadPage(pageReqVO);
  47 + return success(pageResult);
  48 + }
  49 +
  50 + @GetMapping("/list-all")
  51 + @Operation(summary = "获得全部 GIS 道路列表(不分页,用于地图渲染)")
  52 + @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
  53 + public CommonResult<List<GisRoadRespVO>> getGisRoadAllList(GisRoadPageReqVO reqVO) {
  54 + return success(gisRoadService.getGisRoadAllList(reqVO));
  55 + }
  56 +
  57 + @GetMapping("/get")
  58 + @Operation(summary = "获得 GIS 道路详情")
  59 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  60 + @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
  61 + public CommonResult<GisRoadRespVO> getGisRoad(@RequestParam("id") Long id) {
  62 + return success(gisRoadService.getGisRoad(id));
  63 + }
  64 +
  65 + @GetMapping("/export-excel")
  66 + @Operation(summary = "导出 GIS 道路 Excel")
  67 + @PreAuthorize("@ss.hasPermission('garden:gis-road:export')")
  68 + @ApiAccessLog(operateType = EXPORT)
  69 + public void exportGisRoadExcel(@Valid GisRoadPageReqVO pageReqVO,
  70 + HttpServletResponse response) throws IOException {
  71 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
  72 + List<GisRoadRespVO> list = gisRoadService.getGisRoadPage(pageReqVO).getList();
  73 + List<GisRoadExportVO> exportList = new ArrayList<>();
  74 + for (GisRoadRespVO vo : list) {
  75 + GisRoadExportVO exportVO = new GisRoadExportVO();
  76 + exportVO.setGisPlotName(vo.getGisPlotName());
  77 + exportVO.setRoadName(vo.getRoadName());
  78 + exportVO.setGisPlotArea(vo.getGisPlotArea());
  79 + exportVO.setTotalArea(vo.getTotalArea());
  80 + exportVO.setGisIsRelatedStr(vo.getGisIsRelated() != null && vo.getGisIsRelated() == 1 ? "已关联" : "未关联");
  81 + exportVO.setGisDiffArea(vo.getGisDiffArea());
  82 + exportVO.setCompanyName(vo.getCompanyName());
  83 + exportVO.setDeptName(vo.getDeptName());
  84 + // 位置信息:起点描述 ~ 终点描述
  85 + String positionInfo = "";
  86 + if (vo.getStartingRemark() != null) {
  87 + positionInfo += vo.getStartingRemark();
  88 + }
  89 + if (vo.getEndRemark() != null) {
  90 + if (!positionInfo.isEmpty()) {
  91 + positionInfo += " ~ ";
  92 + }
  93 + positionInfo += vo.getEndRemark();
  94 + }
  95 + exportVO.setPositionInfo(positionInfo.isEmpty() ? null : positionInfo);
  96 + exportList.add(exportVO);
  97 + }
  98 + String filename = "十谱绿地核对" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".xls";
  99 + ExcelUtils.write(response, filename, "数据", GisRoadExportVO.class, exportList);
  100 + }
  101 +
  102 + @PostMapping("/associate")
  103 + @Operation(summary = "关联道路到 GIS 图斑")
  104 +// @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
  105 + public CommonResult<Boolean> associateRoad(@Valid @RequestBody GisRoadAssociateReqVO reqVO) {
  106 + gisRoadService.associateRoad(reqVO);
  107 + return success(true);
  108 + }
  109 +
  110 + @PostMapping("/disassociate")
  111 + @Operation(summary = "取消 GIS 图斑与道路的关联")
  112 +// @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
  113 + public CommonResult<Boolean> disassociateRoad(@Valid @RequestBody GisRoadDisassociateReqVO reqVO) {
  114 + gisRoadService.disassociateRoad(reqVO);
  115 + return success(true);
  116 + }
  117 +
  118 + @PostMapping("/convert-coordinates")
  119 + @Operation(summary = "将全部道路坐标从 WGS84 转换为 GCJ02(高德坐标系)")
  120 +// @PreAuthorize("@ss.hasPermission('garden:gis-road:query')")
  121 + public CommonResult<String> convertCoordinates() {
  122 + int count = gisRoadService.convertCoordinatesToGcj02();
  123 + log.info("坐标转换完成,共转换 {} 条记录", count);
  124 + return success("坐标转换完成,共转换 " + count + " 条记录");
  125 + }
  126 +
  127 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/GisTypeController.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis;
  2 +
  3 +import io.swagger.v3.oas.annotations.tags.Tag;
  4 +import io.swagger.v3.oas.annotations.Operation;
  5 +import org.springframework.web.bind.annotation.*;
  6 +import jakarta.annotation.Resource;
  7 +import jakarta.validation.Valid;
  8 +import java.util.*;
  9 +
  10 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
  11 +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  12 +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO;
  13 +import com.zteits.urbanops.module.garden.service.gis.GisTypeService;
  14 +
  15 +@Tag(name = "管理后台 - GIS 绿地类型")
  16 +@RestController
  17 +@RequestMapping("/garden/gis/type")
  18 +public class GisTypeController {
  19 +
  20 + @Resource
  21 + private GisTypeService gisTypeService;
  22 +
  23 + @GetMapping("/list-by-parent")
  24 + @Operation(summary = "按父级编码查子类型(级联下拉用)")
  25 + public CommonResult<List<Map<String, Object>>> listByParentCode(
  26 + @RequestParam(required = false) String parentCode) {
  27 + List<GisTypeDO> list = gisTypeService.listByParentCode(parentCode);
  28 + List<Map<String, Object>> result = new ArrayList<>();
  29 + for (GisTypeDO t : list) {
  30 + Map<String, Object> m = new HashMap<>();
  31 + m.put("code", t.getTypeCode());
  32 + m.put("name", t.getTypeName());
  33 + m.put("level", t.getLevel());
  34 + m.put("parentCode", t.getParentCode());
  35 + result.add(m);
  36 + }
  37 + return success(result);
  38 + }
  39 +
  40 + @GetMapping("/list-by-level")
  41 + @Operation(summary = "按层级查类型列表")
  42 + public CommonResult<List<Map<String, Object>>> listByLevel(@RequestParam Integer level) {
  43 + List<GisTypeDO> list = gisTypeService.listByLevel(level);
  44 + List<Map<String, Object>> result = new ArrayList<>();
  45 + for (GisTypeDO t : list) {
  46 + Map<String, Object> m = new HashMap<>();
  47 + m.put("code", t.getTypeCode());
  48 + m.put("name", t.getTypeName());
  49 + m.put("level", t.getLevel());
  50 + m.put("parentCode", t.getParentCode());
  51 + result.add(m);
  52 + }
  53 + return success(result);
  54 + }
  55 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadAssociateReqVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import jakarta.validation.constraints.NotNull;
  6 +
  7 +@Schema(description = "管理后台 - GIS 道路关联 Request VO")
  8 +@Data
  9 +public class GisRoadAssociateReqVO {
  10 +
  11 + @Schema(description = "GIS道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  12 + @NotNull(message = "GIS道路ID不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "关联的道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  16 + @NotNull(message = "道路ID不能为空")
  17 + private Long roadId;
  18 +
  19 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadDisassociateReqVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import jakarta.validation.constraints.NotNull;
  6 +
  7 +@Schema(description = "管理后台 - GIS 道路取消关联 Request VO")
  8 +@Data
  9 +public class GisRoadDisassociateReqVO {
  10 +
  11 + @Schema(description = "GIS道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  12 + @NotNull(message = "GIS道路ID不能为空")
  13 + private Long id;
  14 +
  15 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadExportVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis.vo;
  2 +
  3 +import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  4 +import com.zteits.urbanops.module.garden.util.TwoDecimalFloatSerializer;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  8 +import cn.idev.excel.annotation.ExcelProperty;
  9 +
  10 +@Schema(description = "十谱绿地核对导出 VO")
  11 +@Data
  12 +@ExcelIgnoreUnannotated
  13 +public class GisRoadExportVO {
  14 +
  15 + @Schema(description = "十谱绿地名称")
  16 + @ExcelProperty("十谱绿地名称")
  17 + private String gisPlotName;
  18 +
  19 + @Schema(description = "道路名称")
  20 + @ExcelProperty("道路名称")
  21 + private String roadName;
  22 +
  23 + @Schema(description = "十谱绿地面积")
  24 + @ExcelProperty("十谱绿地面积(㎡)")
  25 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  26 + private Float gisPlotArea;
  27 +
  28 + @Schema(description = "道路面积")
  29 + @ExcelProperty("道路面积(㎡)")
  30 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  31 + private Float totalArea;
  32 +
  33 + @Schema(description = "是否关联")
  34 + @ExcelProperty("是否关联")
  35 + private String gisIsRelatedStr;
  36 +
  37 + @Schema(description = "差异面积")
  38 + @ExcelProperty("差异面积(㎡)")
  39 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  40 + private Float gisDiffArea;
  41 +
  42 + @Schema(description = "归属单位")
  43 + @ExcelProperty("归属单位")
  44 + private String companyName;
  45 +
  46 + @Schema(description = "归属班组")
  47 + @ExcelProperty("归属班组")
  48 + private String deptName;
  49 +
  50 + @Schema(description = "位置信息")
  51 + @ExcelProperty("位置信息")
  52 + private String positionInfo;
  53 +
  54 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadPageReqVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import com.zteits.urbanops.framework.common.pojo.PageParam;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +import java.time.LocalDateTime;
  8 +
  9 +import static com.zteits.urbanops.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  10 +
  11 +@Schema(description = "管理后台 - GIS 道路核对分页 Request VO")
  12 +@Data
  13 +public class GisRoadPageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "道路名称")
  16 + private String roadName;
  17 +
  18 + @Schema(description = "业务线")
  19 + private String busiLine;
  20 +
  21 + @Schema(description = "街道ID")
  22 + private String streetId;
  23 +
  24 + @Schema(description = "街道名称")
  25 + private String streetName;
  26 +
  27 + @Schema(description = "养护级别")
  28 + private Integer levelId;
  29 +
  30 + @Schema(description = "道路属性")
  31 + private String roadAttr;
  32 +
  33 + @Schema(description = "道路方位")
  34 + private String direction;
  35 +
  36 + @Schema(description = "GIS 图斑编号")
  37 + private String gisPlotCode;
  38 +
  39 + @Schema(description = "GIS 图斑名称")
  40 + private String gisPlotName;
  41 +
  42 + @Schema(description = "是否已关联 (0=未关联, 1=已关联)")
  43 + private Integer gisIsRelated;
  44 +
  45 + @Schema(description = "归属公司ID")
  46 + private Long companyId;
  47 +
  48 + @Schema(description = "部门ID")
  49 + private Long deptId;
  50 +
  51 + @Schema(description = "GIS绿地类型编码(级联查询用)")
  52 + private String gisTypeCode;
  53 +
  54 + @Schema(description = "创建时间")
  55 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  56 + private LocalDateTime[] createTime;
  57 +
  58 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/gis/vo/GisRoadRespVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.gis.vo;
  2 +
  3 +import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  4 +import com.zteits.urbanops.module.garden.util.TwoDecimalFloatSerializer;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.*;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +import java.time.LocalDateTime;
  9 +import cn.idev.excel.annotation.*;
  10 +
  11 +@Schema(description = "管理后台 - GIS 道路核对 Response VO")
  12 +@Data
  13 +@ExcelIgnoreUnannotated
  14 +public class GisRoadRespVO {
  15 +
  16 + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + @ExcelProperty("ID")
  18 + private Long id;
  19 +
  20 + @Schema(description = "关联道路ID")
  21 + @ExcelProperty("关联道路ID")
  22 + private Long gisRoadId;
  23 +
  24 + @Schema(description = "道路名称")
  25 + @ExcelProperty("道路名称")
  26 + private String roadName;
  27 +
  28 + @Schema(description = "业务线")
  29 + @ExcelProperty("业务线")
  30 + private String busiLine;
  31 +
  32 + @Schema(description = "街道ID")
  33 + @ExcelProperty("街道ID")
  34 + private String streetId;
  35 +
  36 + @Schema(description = "街道名称")
  37 + @ExcelProperty("街道名称")
  38 + private String streetName;
  39 +
  40 + @Schema(description = "养护级别")
  41 + @ExcelProperty("养护级别")
  42 + private Integer levelId;
  43 +
  44 + @Schema(description = "道路属性")
  45 + @ExcelProperty("道路属性")
  46 + private String roadAttr;
  47 +
  48 + @Schema(description = "道路方位")
  49 + @ExcelProperty("道路方位")
  50 + private String direction;
  51 +
  52 + @Schema(description = "行道树绿化面积")
  53 + @ExcelProperty("行道树绿化面积")
  54 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  55 + private Float greenBeltTreeArea;
  56 +
  57 + @Schema(description = "绿化带面积")
  58 + @ExcelProperty("绿化带面积")
  59 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  60 + private Float greenBeltArea;
  61 +
  62 + @Schema(description = "总绿化带面积")
  63 + @ExcelProperty("总绿化带面积")
  64 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  65 + private Float totalArea;
  66 +
  67 + @Schema(description = "行道树面积")
  68 + @ExcelProperty("行道树面积")
  69 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  70 + private Float treeArea;
  71 +
  72 + @Schema(description = "起点经度")
  73 + @ExcelProperty("起点经度")
  74 + private String startingLatitude;
  75 +
  76 + @Schema(description = "起点纬度")
  77 + @ExcelProperty("起点纬度")
  78 + private String startingLongitude;
  79 +
  80 + @Schema(description = "起点描述")
  81 + @ExcelProperty("起点描述")
  82 + private String startingRemark;
  83 +
  84 + @Schema(description = "终点经度")
  85 + @ExcelProperty("终点经度")
  86 + private String endLatitude;
  87 +
  88 + @Schema(description = "终点纬度")
  89 + @ExcelProperty("终点纬度")
  90 + private String endLongitude;
  91 +
  92 + @Schema(description = "终点描述")
  93 + @ExcelProperty("终点描述")
  94 + private String endRemark;
  95 +
  96 + @Schema(description = "备注")
  97 + @ExcelProperty("备注")
  98 + private String remark;
  99 +
  100 + @Schema(description = "归属公司ID")
  101 + @ExcelProperty("归属公司ID")
  102 + private Long companyId;
  103 +
  104 + @Schema(description = "部门ID")
  105 + @ExcelProperty("部门ID")
  106 + private Long deptId;
  107 +
  108 + @Schema(description = "创建者名称")
  109 + @ExcelProperty("创建者")
  110 + private String creatorName;
  111 +
  112 + @Schema(description = "创建时间")
  113 + @ExcelProperty("创建时间")
  114 + private LocalDateTime createTime;
  115 +
  116 + @Schema(description = "更新者名称")
  117 + @ExcelProperty("更新者")
  118 + private String updaterName;
  119 +
  120 + // ========== GIS 核对相关字段 ==========
  121 +
  122 + @Schema(description = "GIS 图斑编号")
  123 + @ExcelProperty("GIS图斑编号")
  124 + private String gisPlotCode;
  125 +
  126 + @Schema(description = "GIS 图斑名称")
  127 + @ExcelProperty("GIS图斑名称")
  128 + private String gisPlotName;
  129 +
  130 + @Schema(description = "GIS 图斑面积")
  131 + @ExcelProperty("GIS图斑面积")
  132 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  133 + private Float gisPlotArea;
  134 +
  135 + @Schema(description = "GIS 绿地类型编码")
  136 + @ExcelProperty("GIS绿地类型编码")
  137 + private String gisGreenTypeCode;
  138 +
  139 + @Schema(description = "GIS 绿地类型名称")
  140 + @ExcelProperty("GIS绿地类型名称")
  141 + private String gisGreenTypeName;
  142 +
  143 + @Schema(description = "GIS 所属街道")
  144 + @ExcelProperty("GIS所属街道")
  145 + private String gisStreet;
  146 +
  147 + @Schema(description = "GIS 权属单位")
  148 + @ExcelProperty("GIS权属单位")
  149 + private String gisOwnerUnit;
  150 +
  151 + @Schema(description = "GIS 物业单位")
  152 + @ExcelProperty("GIS物业单位")
  153 + private String gisPropertyUnit;
  154 +
  155 + @Schema(description = "GIS 养护单位")
  156 + @ExcelProperty("GIS养护单位")
  157 + private String gisManageUnit;
  158 +
  159 + @Schema(description = "GIS 差异面积")
  160 + @ExcelProperty("GIS差异面积")
  161 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  162 + private Float gisDiffArea;
  163 +
  164 + @Schema(description = "GIS 差异备注")
  165 + @ExcelProperty("GIS差异备注")
  166 + private String gisDiffRemark;
  167 +
  168 + @Schema(description = "是否已关联 (0=未关联, 1=已关联)")
  169 + @ExcelProperty("是否已关联")
  170 + private Integer gisIsRelated;
  171 +
  172 + @Schema(description = "关联人")
  173 + @ExcelProperty("关联人")
  174 + private String gisRelatedUser;
  175 +
  176 + @Schema(description = "关联时间")
  177 + @ExcelProperty("关联时间")
  178 + private LocalDateTime gisRelatedTime;
  179 +
  180 + @Schema(description = "多边形围栏边界坐标 WKT格式")
  181 + @ExcelProperty("围栏边界坐标")
  182 + private String gisPolygonCoords;
  183 +
  184 + // ========== 翻译字段 ==========
  185 +
  186 + @Schema(description = "公司名称")
  187 + private String companyName;
  188 +
  189 + @Schema(description = "部门名称")
  190 + private String deptName;
  191 +
  192 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadPageReqVO.java
... ... @@ -86,6 +86,9 @@ public class RoadPageReqVO extends PageParam {
86 86 @Schema(description = "更新者", example = "芋艿")
87 87 private String updaterName;
88 88  
  89 + @Schema(description = "是否关联GIS (0=未关联, 1=已关联)")
  90 + private Integer gisIsRelated;
  91 +
89 92 //对应前端排序字段
90 93 public static final Map<String, SFunction<RoadDO, ?>> SORT_FIELD_MAP = Map.of(
91 94 "id", RoadDO::getId,
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/vo/RoadRespVO.java
... ... @@ -122,4 +122,17 @@ public class RoadRespVO {
122 122 @ExcelProperty("部门(道路负责部门)")
123 123 private String deptName;
124 124  
  125 + @Schema(description = "GIS 图斑名称")
  126 + @ExcelProperty("GIS图斑名称")
  127 + private String gisPlotName;
  128 +
  129 + @Schema(description = "GIS 图斑面积")
  130 + @ExcelProperty("GIS图斑面积")
  131 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  132 + private Float gisPlotArea;
  133 +
  134 + @Schema(description = "是否关联GIS (0=未关联, 1=已关联)")
  135 + @ExcelProperty("是否关联GIS")
  136 + private Integer gisIsRelated;
  137 +
125 138 }
126 139 \ No newline at end of file
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treeinspection/TreeInspectionController.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.admin.treeinspection;
  2 +
  3 +import io.swagger.v3.oas.annotations.Operation;
  4 +import io.swagger.v3.oas.annotations.Parameter;
  5 +import io.swagger.v3.oas.annotations.tags.Tag;
  6 +import org.springframework.validation.annotation.Validated;
  7 +import org.springframework.web.bind.annotation.*;
  8 +import org.springframework.security.access.prepost.PreAuthorize;
  9 +import jakarta.annotation.Resource;
  10 +import jakarta.validation.Valid;
  11 +
  12 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
  13 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  14 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  15 +import com.zteits.urbanops.module.garden.service.treeinspection.TreeInspectionService;
  16 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*;
  17 +import com.zteits.urbanops.module.garden.convert.treeinspection.TreeInspectionConvert;
  18 +
  19 +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - 行道树巡检记录与评估")
  22 +@RestController("adminTreeInspectionController")
  23 +@RequestMapping("/garden/tree-inspection")
  24 +@Validated
  25 +public class TreeInspectionController {
  26 +
  27 + @Resource
  28 + private TreeInspectionService treeInspectionService;
  29 +
  30 + @GetMapping("/get")
  31 + @Operation(summary = "获得巡检评估详情")
  32 + @Parameter(name = "id", description = "巡检记录编号", required = true, example = "12")
  33 + @PreAuthorize("@ss.hasPermission('garden:tree-inspection:query')")
  34 + public CommonResult<TreeInspectionRespVO> getTreeInspection(@RequestParam("id") Long id) {
  35 + return success(treeInspectionService.getTreeInspection(id));
  36 + }
  37 +
  38 + @GetMapping("/page")
  39 + @Operation(summary = "分页查询单株树木的历史巡检记录")
  40 + @PreAuthorize("@ss.hasPermission('garden:tree-inspection:query')")
  41 + public CommonResult<PageResult<TreeInspectionRespVO>> getTreeInspectionPage(@Valid TreeInspectionPageReqVO pageReqVO) {
  42 + PageResult<TreeInspectionDO> pageResult = treeInspectionService.getTreeInspectionPage(pageReqVO);
  43 + PageResult<TreeInspectionRespVO> respPageResult = new PageResult<>(
  44 + TreeInspectionConvert.INSTANCE.convertList(pageResult.getList()),
  45 + pageResult.getTotal()
  46 + );
  47 + return success(respPageResult);
  48 + }
  49 +
  50 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java
... ... @@ -2,10 +2,7 @@ package com.zteits.urbanops.module.garden.controller.app.homepage;
2 2  
3 3 import com.zteits.urbanops.framework.common.pojo.CommonResult;
4 4 import com.zteits.urbanops.framework.common.pojo.PageResult;
5   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryPageReqVO;
6   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryReqVO;
7   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo;
8   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo;
  5 +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*;
9 6 import com.zteits.urbanops.module.garden.service.homepage.HomepageSummaryService;
10 7 import io.swagger.v3.oas.annotations.Operation;
11 8 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -15,7 +12,6 @@ import jakarta.validation.Valid;
15 12 import org.springframework.web.bind.annotation.*;
16 13  
17 14 import java.util.List;
18   -import java.util.Map;
19 15  
20 16 import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
21 17  
... ... @@ -51,4 +47,9 @@ public class AppHomepageSummaryController {
51 47 homepageSummaryService.countTaskNumberByUserId(busiDate);
52 48 }
53 49  
  50 + @PostMapping("/iWorkOrderSummary")
  51 + @Operation(summary = "工单情况统计")
  52 + public CommonResult<AppWorkOrderSummaryRspVo> iWorkOrderSummary(@Valid @RequestBody AppWorkOrderSummaryReqVo req) {
  53 + return success(homepageSummaryService.iWorkOrderSummary(req));
  54 + }
54 55 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppWorkOrderSummaryReqVo.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.homepage.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * @Classname AppWorkOrderSummaryReqVo
  9 + * @Description 全域工单统计
  10 + * @Date 2026/5/26 23:42
  11 + * @Created by wangqian
  12 + */
  13 +@Data
  14 +public class AppWorkOrderSummaryReqVo {
  15 +
  16 + @NotEmpty
  17 + @Schema(description = "查询类型", example = "0:全部工单 1:全域工单")
  18 + public String queryType;
  19 + @NotEmpty
  20 + @Schema(description = "开始时间", example = "2025-01-01")
  21 + public String beginTime;
  22 + /*结束时间*/
  23 + @NotEmpty
  24 + @Schema(description = "结束时间", example = "2025-12-10")
  25 + public String endTime;
  26 +
  27 +
  28 + // ===================== 自动拼接时间成完整格式 =====================
  29 + /**
  30 + * 获取 开始时间 yyyy-MM-dd 00:00:00
  31 + */
  32 + public String getBeginTime() {
  33 + if (beginTime != null && beginTime.length() == 10) {
  34 + return beginTime + " 00:00:00";
  35 + }
  36 + return beginTime;
  37 + }
  38 +
  39 + /**
  40 + * 获取 结束时间 yyyy-MM-dd 23:59:59
  41 + */
  42 + public String getEndTime() {
  43 + if (endTime != null && endTime.length() == 10) {
  44 + return endTime + " 23:59:59";
  45 + }
  46 + return endTime;
  47 + }
  48 +
  49 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppWorkOrderSummaryRspVo.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.homepage.vo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * @Classname TaskCompletionStatusRspVo
  9 + * @Description 任务完成情况
  10 + * @Date 2026/1/7 9:44
  11 + * @Created by wangqian
  12 + */
  13 +@Data
  14 +public class AppWorkOrderSummaryRspVo {
  15 + /*单位名称列表*/
  16 + private List<String> legend;
  17 + /*总条数*/
  18 + private String totalNum;
  19 + /*占比*/
  20 + private List<Integer> percents;
  21 + /*单位工单统计*/
  22 + private List<AppWorkOrderSummaryVo> table;
  23 +
  24 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppWorkOrderSummaryVo.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.homepage.vo;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Classname TaskCompletionStatusRspVo
  7 + * @Description 任务完成情况
  8 + * @Date 2026/1/7 9:44
  9 + * @Created by wangqian
  10 + */
  11 +@Data
  12 +public class AppWorkOrderSummaryVo {
  13 +
  14 + /*公司ID*/
  15 + private Long companyId;
  16 + /*公司名称*/
  17 + private String companyName;
  18 + /*总工单数量*/
  19 + private Integer total;
  20 + /* 已完成数量*/
  21 + private Integer finish;
  22 + /*待完成数量*/
  23 + private Integer ongoing;
  24 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/AppProblemTypeController.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.problemtype;
  2 +
  3 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
  4 +import com.zteits.urbanops.module.garden.controller.app.problemtype.vo.ProblemTypeCascadeRespVO;
  5 +import com.zteits.urbanops.module.garden.service.problemtype.GardenProblemTypeService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import jakarta.annotation.Resource;
  9 +import org.springframework.validation.annotation.Validated;
  10 +import org.springframework.web.bind.annotation.GetMapping;
  11 +import org.springframework.web.bind.annotation.RequestMapping;
  12 +import org.springframework.web.bind.annotation.RequestParam;
  13 +import org.springframework.web.bind.annotation.RestController;
  14 +
  15 +import java.util.List;
  16 +
  17 +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  18 +
  19 +@Tag(name = "APP - 问题类型")
  20 +@RestController
  21 +@RequestMapping("/app/problem-type")
  22 +@Validated
  23 +public class AppProblemTypeController {
  24 +
  25 + @Resource
  26 + private GardenProblemTypeService gardenProblemTypeService;
  27 +
  28 + @GetMapping("/list-by-parent")
  29 + @Operation(summary = "查询问题类型级联列表(级联下拉用)")
  30 + public CommonResult<List<ProblemTypeCascadeRespVO>> getProblemTypeListByParent(
  31 + @RequestParam(value = "parentCode", required = false) String parentCode) {
  32 + return success(gardenProblemTypeService.getProblemTypeTree(parentCode));
  33 + }
  34 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/problemtype/vo/ProblemTypeCascadeRespVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.problemtype.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.util.ArrayList;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "APP - 问题类型级联响应 VO")
  10 +@Data
  11 +public class ProblemTypeCascadeRespVO {
  12 +
  13 + @Schema(description = "问题类型名称", example = "树木倒伏")
  14 + private String label;
  15 +
  16 + @Schema(description = "问题类型编码", example = "PT001")
  17 + private String value;
  18 +
  19 + @Schema(description = "子类型列表")
  20 + private List<ProblemTypeCascadeRespVO> children = new ArrayList<>();
  21 +
  22 +}
0 23 \ No newline at end of file
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/AppTreeInspectionController.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.treeinspection;
  2 +
  3 +import io.swagger.v3.oas.annotations.Operation;
  4 +import io.swagger.v3.oas.annotations.Parameter;
  5 +import io.swagger.v3.oas.annotations.tags.Tag;
  6 +import org.springframework.validation.annotation.Validated;
  7 +import org.springframework.web.bind.annotation.*;
  8 +import jakarta.annotation.Resource;
  9 +import jakarta.validation.Valid;
  10 +
  11 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
  12 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  13 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  14 +import com.zteits.urbanops.module.garden.service.treeinspection.TreeInspectionService;
  15 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*;
  16 +import com.zteits.urbanops.module.garden.convert.treeinspection.TreeInspectionConvert;
  17 +
  18 +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "App - 行道树巡检与风险评估")
  21 +@RestController
  22 +@RequestMapping({"/garden/tree-inspection", "/business/tree-inspection"})
  23 +@Validated
  24 +public class AppTreeInspectionController {
  25 +
  26 + @Resource
  27 + private TreeInspectionService treeInspectionService;
  28 +
  29 + @PostMapping("/create")
  30 + @Operation(summary = "提交巡检记录并评估")
  31 + public CommonResult<Long> createTreeInspection(@Valid @RequestBody TreeInspectionSaveReqVO createReqVO) {
  32 + return success(treeInspectionService.createTreeInspection(createReqVO));
  33 + }
  34 +
  35 + @GetMapping("/get")
  36 + @Operation(summary = "获得巡检评估详情")
  37 + @Parameter(name = "id", description = "巡检记录编号", required = true, example = "12")
  38 + public CommonResult<TreeInspectionRespVO> getTreeInspection(@RequestParam("id") Long id) {
  39 + return success(treeInspectionService.getTreeInspection(id));
  40 + }
  41 +
  42 + @GetMapping("/page")
  43 + @Operation(summary = "分页查询单株树木的历史巡检记录")
  44 + public CommonResult<PageResult<TreeInspectionRespVO>> getTreeInspectionPage(@Valid TreeInspectionPageReqVO pageReqVO) {
  45 + PageResult<TreeInspectionDO> pageResult = treeInspectionService.getTreeInspectionPage(pageReqVO);
  46 + PageResult<TreeInspectionRespVO> respPageResult = new PageResult<>(
  47 + TreeInspectionConvert.INSTANCE.convertList(pageResult.getList()),
  48 + pageResult.getTotal()
  49 + );
  50 + return success(respPageResult);
  51 + }
  52 +
  53 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionPageReqVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.treeinspection.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import lombok.ToString;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import com.zteits.urbanops.framework.common.pojo.PageParam;
  9 +
  10 +@Schema(description = "小程序端 - 行道树巡检历史分页 Request VO")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +public class TreeInspectionPageReqVO extends PageParam {
  15 +
  16 + @Schema(description = "一树一档案ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @NotNull(message = "关联树木ID不能为空")
  18 + private Long treeId;
  19 +
  20 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionRespVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.treeinspection.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import org.springframework.format.annotation.DateTimeFormat;
  6 +import com.fasterxml.jackson.annotation.JsonFormat;
  7 +import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  8 +import com.zteits.urbanops.framework.common.util.json.databind.LocalDateTimeStringSerializer;
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "小程序端 - 行道树巡检记录与评估详情 Response VO")
  13 +@Data
  14 +public class TreeInspectionRespVO {
  15 +
  16 + @Schema(description = "主键ID", example = "12")
  17 + private Long id;
  18 +
  19 + @Schema(description = "一树一档案ID", example = "1024")
  20 + private Long treeId;
  21 +
  22 + @Schema(description = "树木编号", example = "D0001-P1-0001")
  23 + private String treenumber;
  24 +
  25 + @Schema(description = "巡检时间")
  26 + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  27 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  28 + @JsonSerialize(using = LocalDateTimeStringSerializer.class)
  29 + private LocalDateTime inspectionTime;
  30 +
  31 + @Schema(description = "巡检人ID", example = "10001")
  32 + private Long inspectorId;
  33 +
  34 + @Schema(description = "巡检人姓名", example = "张三")
  35 + private String inspectorName;
  36 +
  37 + @Schema(description = "树根部位指标组")
  38 + private RootRespDTO root;
  39 +
  40 + @Schema(description = "根颈部位指标组")
  41 + private CollarRespDTO collar;
  42 +
  43 + @Schema(description = "主干部位指标组")
  44 + private TrunkRespDTO trunk;
  45 +
  46 + @Schema(description = "树冠部位指标组")
  47 + private CrownRespDTO crown;
  48 +
  49 + @Schema(description = "权重因子评估组")
  50 + private WeightRespDTO weight;
  51 +
  52 + @Schema(description = "风险评估结果组 (折叠面板)")
  53 + private ResultRespDTO result;
  54 +
  55 +
  56 + @Schema(description = "现状及处理措施组")
  57 + private StatusRespDTO status;
  58 +
  59 + @Data
  60 + @Schema(description = "树根部位指标详情")
  61 + public static class RootRespDTO {
  62 + @Schema(description = "根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽", example = "0")
  63 + private Integer disease;
  64 +
  65 + @Schema(description = "根系下扎情况:0-良好,7-存在隆起或盘根", example = "0")
  66 + private Integer anchorage;
  67 +
  68 + @Schema(description = "工程切根:0-无,5-存在", example = "0")
  69 + private Integer cutting;
  70 + }
  71 +
  72 + @Data
  73 + @Schema(description = "根颈部位指标详情")
  74 + public static class CollarRespDTO {
  75 + @Schema(description = "根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%", example = "5")
  76 + private Integer woodDamage;
  77 +
  78 + @Schema(description = "根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%", example = "0")
  79 + private Integer barkDamage;
  80 +
  81 + @Schema(description = "根颈松动:0-不存在,100-存在", example = "0")
  82 + private Integer loosening;
  83 + }
  84 +
  85 + @Data
  86 + @Schema(description = "主干部位指标详情")
  87 + public static class TrunkRespDTO {
  88 + @Schema(description = "主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%", example = "0")
  89 + private Integer woodDamage;
  90 +
  91 + @Schema(description = "主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度", example = "0")
  92 + private Integer tilt;
  93 +
  94 + @Schema(description = "主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%", example = "0")
  95 + private Integer barkDamage;
  96 + }
  97 +
  98 + @Data
  99 + @Schema(description = "树冠部位指标详情")
  100 + public static class CrownRespDTO {
  101 + @Schema(description = "易落枝:0-未发现,2-<1/10, 3->=1/10", example = "0")
  102 + private Integer looseBranch;
  103 +
  104 + @Schema(description = "枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹", example = "0")
  105 + private Integer collarAbnormal;
  106 +
  107 + @Schema(description = "树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠", example = "0")
  108 + private Integer ventilationBalance;
  109 + }
  110 +
  111 + @Data
  112 + @Schema(description = "权重因子评估详情")
  113 + public static class WeightRespDTO {
  114 + @Schema(description = "树种类型:深根性树种, 浅根性树种", example = "深根性树种")
  115 + private String treeSpeciesType;
  116 +
  117 + @Schema(description = "树种类型权重常数", example = "1.0")
  118 + private Double treeSpeciesWeight;
  119 +
  120 + @Schema(description = "栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上", example = "栽植 10-30 年")
  121 + private String plantingYears;
  122 +
  123 + @Schema(description = "栽植年限权重常数", example = "1.1")
  124 + private Double plantingYearsWeight;
  125 +
  126 + @Schema(description = "是否处于风口", example = "false")
  127 + private Boolean isWindCorridor;
  128 +
  129 + @Schema(description = "风口权重常数", example = "1.0")
  130 + private Double windCorridorWeight;
  131 +
  132 + @Schema(description = "树池类型:联通树池, 独立树池, 树池硬化", example = "联通树池")
  133 + private String treePoolType;
  134 +
  135 + @Schema(description = "树池类型权重常数", example = "1.0")
  136 + private Double treePoolWeight;
  137 +
  138 + @Schema(description = "树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下", example = "7 倍及以上")
  139 + private String treePoolWidthDbhRatio;
  140 +
  141 + @Schema(description = "树池比权重常数", example = "1.0")
  142 + private Double treePoolRatioWeight;
  143 + }
  144 +
  145 + @Data
  146 + @Schema(description = "风险评估结果详情 (折叠面板)")
  147 + public static class ResultRespDTO {
  148 + @Schema(description = "是否进行应急评估", example = "true")
  149 + private Boolean isEmergency;
  150 +
  151 + @Schema(description = "应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上", example = "8-9 级")
  152 + private String windPower;
  153 +
  154 + @Schema(description = "风力权重常数", example = "1.5")
  155 + private Double windPowerWeight;
  156 +
  157 + @Schema(description = "树木缺陷得分", example = "9")
  158 + private Integer defectScore;
  159 +
  160 + @Schema(description = "常规安全风险评估结果")
  161 + private EvaluationResultDTO normalResult;
  162 +
  163 + @Schema(description = "应急安全风险评估结果")
  164 + private EvaluationResultDTO emergencyResult;
  165 + }
  166 +
  167 + @Data
  168 + @Schema(description = "安全风险评估得分与等级结果")
  169 + public static class EvaluationResultDTO {
  170 + @Schema(description = "风险得分", example = "87.12")
  171 + private Double score;
  172 +
  173 + @Schema(description = "风险等级", example = "重度风险")
  174 + private String level;
  175 + }
  176 +
  177 +
  178 +
  179 + @Data
  180 + @Schema(description = "现状及处理措施详情")
  181 + public static class StatusRespDTO {
  182 + @Schema(description = "现场照片(最多5张)")
  183 + private List<String> photos;
  184 + }
  185 +
  186 +
  187 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/treeinspection/vo/TreeInspectionSaveReqVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.controller.app.treeinspection.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import jakarta.validation.Valid;
  6 +import jakarta.validation.constraints.*;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +import com.fasterxml.jackson.annotation.JsonFormat;
  9 +import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
  10 +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
  11 +import java.time.LocalDateTime;
  12 +import java.util.List;
  13 +
  14 +@Schema(description = "小程序端 - 行道树巡检记录与评估新增/修改 Request VO")
  15 +@Data
  16 +public class TreeInspectionSaveReqVO {
  17 +
  18 + @Schema(description = "一树一档案ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + @NotNull(message = "关联树木ID不能为空")
  20 + private Long treeId;
  21 +
  22 + @Schema(description = "巡检时间", requiredMode = Schema.RequiredMode.REQUIRED)
  23 + @NotNull(message = "巡检时间不能为空")
  24 + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  25 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  26 + @JsonDeserialize(using = LocalDateTimeDeserializer.class)
  27 + private LocalDateTime inspectionTime;
  28 +
  29 + @Schema(description = "树根部位指标组", requiredMode = Schema.RequiredMode.REQUIRED)
  30 + @NotNull(message = "树根部位指标不能为空")
  31 + @Valid
  32 + private RootDTO root;
  33 +
  34 + @Schema(description = "根颈部位指标组", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + @NotNull(message = "根颈部位指标不能为空")
  36 + @Valid
  37 + private CollarDTO collar;
  38 +
  39 + @Schema(description = "主干部位指标组", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + @NotNull(message = "主干部位指标不能为空")
  41 + @Valid
  42 + private TrunkDTO trunk;
  43 +
  44 + @Schema(description = "树冠部位指标组", requiredMode = Schema.RequiredMode.REQUIRED)
  45 + @NotNull(message = "树冠部位指标不能为空")
  46 + @Valid
  47 + private CrownDTO crown;
  48 +
  49 + @Schema(description = "权重因子评估组", requiredMode = Schema.RequiredMode.REQUIRED)
  50 + @NotNull(message = "权重因子评估组不能为空")
  51 + @Valid
  52 + private WeightDTO weight;
  53 +
  54 + @Schema(description = "风险评估结果组", requiredMode = Schema.RequiredMode.REQUIRED)
  55 + @NotNull(message = "风险评估结果组不能为空")
  56 + @Valid
  57 + private ResultDTO result;
  58 +
  59 +
  60 + @Schema(description = "现状及处理措施组", requiredMode = Schema.RequiredMode.REQUIRED)
  61 + @NotNull(message = "现状及处理措施组不能为空")
  62 + @Valid
  63 + private StatusDTO status;
  64 +
  65 + @Data
  66 + @Schema(description = "树根部位指标 DTO")
  67 + public static class RootDTO {
  68 + @Schema(description = "根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  69 + @NotNull(message = "根部病害指标不能为空")
  70 + private Integer disease;
  71 +
  72 + @Schema(description = "根系下扎情况:0-良好,7-存在隆起或盘根", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  73 + @NotNull(message = "根系下扎情况指标不能为空")
  74 + private Integer anchorage;
  75 +
  76 + @Schema(description = "工程切根:0-无,5-存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  77 + @NotNull(message = "工程切根指标不能为空")
  78 + private Integer cutting;
  79 + }
  80 +
  81 + @Data
  82 + @Schema(description = "根颈部位指标 DTO")
  83 + public static class CollarDTO {
  84 + @Schema(description = "根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  85 + @NotNull(message = "根颈木质部受损指标不能为空")
  86 + private Integer woodDamage;
  87 +
  88 + @Schema(description = "根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  89 + @NotNull(message = "根颈树皮受损指标不能为空")
  90 + private Integer barkDamage;
  91 +
  92 + @Schema(description = "根颈松动:0-不存在,100-存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  93 + @NotNull(message = "根颈松动指标不能为空")
  94 + private Integer loosening;
  95 + }
  96 +
  97 + @Data
  98 + @Schema(description = "主干部位指标 DTO")
  99 + public static class TrunkDTO {
  100 + @Schema(description = "主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  101 + @NotNull(message = "主干木质部受损指标不能为空")
  102 + private Integer woodDamage;
  103 +
  104 + @Schema(description = "主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  105 + @NotNull(message = "主干倾斜指标不能为空")
  106 + private Integer tilt;
  107 +
  108 + @Schema(description = "主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  109 + @NotNull(message = "主干树皮受损指标不能为空")
  110 + private Integer barkDamage;
  111 + }
  112 +
  113 + @Data
  114 + @Schema(description = "树冠部位指标 DTO")
  115 + public static class CrownDTO {
  116 + @Schema(description = "易落枝:0-未发现,2-<1/10, 3->=1/10", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  117 + @NotNull(message = "易落枝指标不能为空")
  118 + private Integer looseBranch;
  119 +
  120 + @Schema(description = "枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  121 + @NotNull(message = "枝干结合部异常指标不能为空")
  122 + private Integer collarAbnormal;
  123 +
  124 + @Schema(description = "树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  125 + @NotNull(message = "树冠透风及平衡性指标不能为空")
  126 + private Integer ventilationBalance;
  127 + }
  128 +
  129 + @Data
  130 + @Schema(description = "权重因子评估 DTO")
  131 + public static class WeightDTO {
  132 + @Schema(description = "树种类型:深根性树种, 浅根性树种", requiredMode = Schema.RequiredMode.REQUIRED, example = "深根性树种")
  133 + @NotBlank(message = "树种类型不能为空")
  134 + private String treeSpeciesType;
  135 +
  136 + @Schema(description = "栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上", requiredMode = Schema.RequiredMode.REQUIRED, example = "栽植 10-30 年")
  137 + @NotBlank(message = "栽植年限不能为空")
  138 + private String plantingYears;
  139 +
  140 + @Schema(description = "是否处于风口", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  141 + @NotNull(message = "是否处于风口标识不能为空")
  142 + private Boolean isWindCorridor;
  143 +
  144 + @Schema(description = "树池类型:联通树池, 独立树池, 树池硬化", requiredMode = Schema.RequiredMode.REQUIRED, example = "联通树池")
  145 + @NotBlank(message = "树池类型不能为空")
  146 + private String treePoolType;
  147 +
  148 + @Schema(description = "树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下", requiredMode = Schema.RequiredMode.REQUIRED, example = "7 倍及以上")
  149 + @NotBlank(message = "树池宽度与胸径比不能为空")
  150 + private String treePoolWidthDbhRatio;
  151 + }
  152 +
  153 + @Data
  154 + @Schema(description = "风险评估结果 DTO (折叠面板)")
  155 + public static class ResultDTO {
  156 + @Schema(description = "是否进行应急评估", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  157 + @NotNull(message = "是否应急评估标识不能为空")
  158 + private Boolean isEmergency;
  159 +
  160 + @Schema(description = "应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上", example = "8-9 级")
  161 + private String windPower;
  162 +
  163 + @Schema(description = "树木缺陷得分", example = "9")
  164 + private Integer defectScore;
  165 +
  166 + @Schema(description = "常规安全风险评估结果")
  167 + private EvaluationResultDTO normalResult;
  168 +
  169 + @Schema(description = "应急安全风险评估结果")
  170 + private EvaluationResultDTO emergencyResult;
  171 + }
  172 +
  173 + @Data
  174 + @Schema(description = "安全风险评估得分与等级结果")
  175 + public static class EvaluationResultDTO {
  176 + @Schema(description = "风险得分", example = "87.12")
  177 + private Double score;
  178 +
  179 + @Schema(description = "风险等级", example = "重度风险")
  180 + private String level;
  181 + }
  182 +
  183 +
  184 +
  185 + @Data
  186 + @Schema(description = "现状及处理措施 DTO")
  187 + public static class StatusDTO {
  188 + @Schema(description = "现场照片(最多5张)")
  189 + @Size(max = 5, message = "最多只能上传5张现场照片")
  190 + private List<String> photos;
  191 + }
  192 +
  193 +
  194 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/convert/treeinspection/TreeInspectionConvert.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.convert.treeinspection;
  2 +
  3 +import org.mapstruct.Mapper;
  4 +import org.mapstruct.Mapping;
  5 +import org.mapstruct.Mappings;
  6 +import org.mapstruct.factory.Mappers;
  7 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  8 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.TreeInspectionSaveReqVO;
  9 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.TreeInspectionRespVO;
  10 +import java.util.List;
  11 +
  12 +@Mapper
  13 +public interface TreeInspectionConvert {
  14 +
  15 + TreeInspectionConvert INSTANCE = Mappers.getMapper(TreeInspectionConvert.class);
  16 +
  17 + @Mappings({
  18 + @Mapping(source = "root.disease", target = "rootDisease"),
  19 + @Mapping(source = "root.anchorage", target = "rootAnchorage"),
  20 + @Mapping(source = "root.cutting", target = "rootCutting"),
  21 + @Mapping(source = "collar.woodDamage", target = "collarWoodDamage"),
  22 + @Mapping(source = "collar.barkDamage", target = "collarBarkDamage"),
  23 + @Mapping(source = "collar.loosening", target = "collarLoosening"),
  24 + @Mapping(source = "trunk.woodDamage", target = "trunkWoodDamage"),
  25 + @Mapping(source = "trunk.tilt", target = "trunkTilt"),
  26 + @Mapping(source = "trunk.barkDamage", target = "trunkBarkDamage"),
  27 + @Mapping(source = "crown.looseBranch", target = "crownLooseBranch"),
  28 + @Mapping(source = "crown.collarAbnormal", target = "crownCollarAbnormal"),
  29 + @Mapping(source = "crown.ventilationBalance", target = "crownVentilationBalance"),
  30 + @Mapping(source = "weight.treeSpeciesType", target = "treeSpeciesType"),
  31 + @Mapping(source = "weight.plantingYears", target = "plantingYears"),
  32 + @Mapping(source = "weight.isWindCorridor", target = "isWindCorridor"),
  33 + @Mapping(source = "weight.treePoolType", target = "treePoolType"),
  34 + @Mapping(source = "weight.treePoolWidthDbhRatio", target = "treePoolWidthDbhRatio"),
  35 + @Mapping(source = "result.isEmergency", target = "isEmergency"),
  36 + @Mapping(source = "result.windPower", target = "windPower"),
  37 + @Mapping(source = "status.photos", target = "photos"),
  38 + @Mapping(source = "result.defectScore", target = "defectScore"),
  39 + @Mapping(source = "result.normalResult.score", target = "normalScore"),
  40 + @Mapping(source = "result.normalResult.level", target = "normalLevel"),
  41 + @Mapping(source = "result.emergencyResult.score", target = "emergencyScore"),
  42 + @Mapping(source = "result.emergencyResult.level", target = "emergencyLevel")
  43 + })
  44 + TreeInspectionDO convert(TreeInspectionSaveReqVO bean);
  45 +
  46 + @Mappings({
  47 + @Mapping(source = "rootDisease", target = "root.disease"),
  48 + @Mapping(source = "rootAnchorage", target = "root.anchorage"),
  49 + @Mapping(source = "rootCutting", target = "root.cutting"),
  50 + @Mapping(source = "collarWoodDamage", target = "collar.woodDamage"),
  51 + @Mapping(source = "collarBarkDamage", target = "collar.barkDamage"),
  52 + @Mapping(source = "collarLoosening", target = "collar.loosening"),
  53 + @Mapping(source = "trunkWoodDamage", target = "trunk.woodDamage"),
  54 + @Mapping(source = "trunkTilt", target = "trunk.tilt"),
  55 + @Mapping(source = "trunkBarkDamage", target = "trunk.barkDamage"),
  56 + @Mapping(source = "crownLooseBranch", target = "crown.looseBranch"),
  57 + @Mapping(source = "crownCollarAbnormal", target = "crown.collarAbnormal"),
  58 + @Mapping(source = "crownVentilationBalance", target = "crown.ventilationBalance"),
  59 + @Mapping(source = "treeSpeciesType", target = "weight.treeSpeciesType"),
  60 + @Mapping(source = "treeSpeciesWeight", target = "weight.treeSpeciesWeight"),
  61 + @Mapping(source = "plantingYears", target = "weight.plantingYears"),
  62 + @Mapping(source = "plantingYearsWeight", target = "weight.plantingYearsWeight"),
  63 + @Mapping(source = "isWindCorridor", target = "weight.isWindCorridor"),
  64 + @Mapping(source = "windCorridorWeight", target = "weight.windCorridorWeight"),
  65 + @Mapping(source = "treePoolType", target = "weight.treePoolType"),
  66 + @Mapping(source = "treePoolWeight", target = "weight.treePoolWeight"),
  67 + @Mapping(source = "treePoolWidthDbhRatio", target = "weight.treePoolWidthDbhRatio"),
  68 + @Mapping(source = "treePoolRatioWeight", target = "weight.treePoolRatioWeight"),
  69 + @Mapping(source = "isEmergency", target = "result.isEmergency"),
  70 + @Mapping(source = "windPower", target = "result.windPower"),
  71 + @Mapping(source = "windPowerWeight", target = "result.windPowerWeight"),
  72 + @Mapping(source = "defectScore", target = "result.defectScore"),
  73 + @Mapping(source = "normalScore", target = "result.normalResult.score"),
  74 + @Mapping(source = "normalLevel", target = "result.normalResult.level"),
  75 + @Mapping(source = "emergencyScore", target = "result.emergencyResult.score"),
  76 + @Mapping(source = "emergencyLevel", target = "result.emergencyResult.level"),
  77 + @Mapping(source = "photos", target = "status.photos")
  78 + })
  79 + TreeInspectionRespVO convert(TreeInspectionDO bean);
  80 +
  81 +
  82 + List<TreeInspectionRespVO> convertList(List<TreeInspectionDO> list);
  83 +
  84 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisRoadDO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.dataobject.gis;
  2 +
  3 +import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  4 +import com.zteits.urbanops.module.garden.util.TwoDecimalFloatSerializer;
  5 +import lombok.*;
  6 +import com.baomidou.mybatisplus.annotation.*;
  7 +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO;
  8 +
  9 +/**
  10 + * GIS 道路核对 DO - garden_gis_road 表
  11 + *
  12 + * @author UrbanOps
  13 + */
  14 +@TableName("garden_gis_road")
  15 +@KeySequence("garden_gis_road_seq")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +@ToString(callSuper = true)
  19 +@Builder
  20 +@NoArgsConstructor
  21 +@AllArgsConstructor
  22 +public class GisRoadDO extends BaseDO {
  23 +
  24 + @TableId
  25 + private Long id;
  26 +
  27 + /** 关联道路ID */
  28 + private Long gisRoadId;
  29 +
  30 + /** 道路名称 */
  31 + private String roadName;
  32 +
  33 + /** 业务线:yl-园林;wy-物业;sz-市政 */
  34 + private String busiLine;
  35 +
  36 + /** 街道ID */
  37 + private String streetId;
  38 +
  39 + /** 街道名称 */
  40 + private String streetName;
  41 +
  42 + /** 养护级别: 10:特级;11:一级:12:二级;13:三级 */
  43 + private Integer levelId;
  44 +
  45 + /** 道路属性 */
  46 + private String roadAttr;
  47 +
  48 + /** 道路方位 W-西 E-东 N-北 S-南 */
  49 + private String direction;
  50 +
  51 + /** 行道树绿化面积 */
  52 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  53 + private Float greenBeltTreeArea;
  54 +
  55 + /** 绿化带面积 */
  56 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  57 + private Float greenBeltArea;
  58 +
  59 + /** 总绿化带面积 */
  60 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  61 + private Float totalArea;
  62 +
  63 + /** 行道树(棵)面积 */
  64 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  65 + private Float treeArea;
  66 +
  67 + /** 起点经度 */
  68 + private String startingLatitude;
  69 +
  70 + /** 起点纬度 */
  71 + private String startingLongitude;
  72 +
  73 + /** 起点描述 */
  74 + private String startingRemark;
  75 +
  76 + /** 终点经度 */
  77 + private String endLatitude;
  78 +
  79 + /** 终点纬度 */
  80 + private String endLongitude;
  81 +
  82 + /** 终点描述 */
  83 + private String endRemark;
  84 +
  85 + /** 备注 */
  86 + private String remark;
  87 +
  88 + /** 归属公司ID */
  89 + private Long companyId;
  90 +
  91 + /** 部门ID */
  92 + private Long deptId;
  93 +
  94 + /** 创建者名称 */
  95 + private String creatorName;
  96 +
  97 + /** 更新者名称 */
  98 + private String updaterName;
  99 +
  100 + // ========== GIS 核对相关字段 ==========
  101 +
  102 + /** GIS 图斑编号 */
  103 + private String gisPlotCode;
  104 +
  105 + /** GIS 图斑名称 */
  106 + private String gisPlotName;
  107 +
  108 + /** GIS 图斑面积 */
  109 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  110 + private Float gisPlotArea;
  111 +
  112 + /** GIS 绿地类型编码 */
  113 + private String gisGreenTypeCode;
  114 +
  115 + /** GIS 绿地类型名称 */
  116 + private String gisGreenTypeName;
  117 +
  118 + /** GIS 所属街道 */
  119 + private String gisStreet;
  120 +
  121 + /** GIS 权属单位 */
  122 + private String gisOwnerUnit;
  123 +
  124 + /** GIS 物业单位 */
  125 + private String gisPropertyUnit;
  126 +
  127 + /** GIS 养护单位 */
  128 + private String gisManageUnit;
  129 +
  130 + /** GIS 差异面积 */
  131 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  132 + private Float gisDiffArea;
  133 +
  134 + /** GIS 差异备注 */
  135 + private String gisDiffRemark;
  136 +
  137 + /** 是否已关联 (0=未关联, 1=已关联) */
  138 + private Integer gisIsRelated;
  139 +
  140 + /** 关联人 */
  141 + private String gisRelatedUser;
  142 +
  143 + /** 关联时间 */
  144 + private java.time.LocalDateTime gisRelatedTime;
  145 +
  146 + /** 多边形围栏边界坐标 WKT格式: POLYGON((x1 y1, x2 y2, ...)) WGS84坐标系 */
  147 + private String gisPolygonCoords;
  148 +
  149 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/gis/GisTypeDO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.dataobject.gis;
  2 +
  3 +import lombok.*;
  4 +import com.baomidou.mybatisplus.annotation.*;
  5 +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO;
  6 +
  7 +/**
  8 + * 公园绿地类型字典 DO
  9 + */
  10 +@TableName("garden_gis_type")
  11 +@Data
  12 +@EqualsAndHashCode(callSuper = true)
  13 +@ToString(callSuper = true)
  14 +@Builder
  15 +@NoArgsConstructor
  16 +@AllArgsConstructor
  17 +public class GisTypeDO extends BaseDO {
  18 +
  19 + @TableId
  20 + private Long id;
  21 +
  22 + /** 类型编码 */
  23 + private String typeCode;
  24 +
  25 + /** 类型名称 */
  26 + private String typeName;
  27 +
  28 + /** 父级编码 */
  29 + private String parentCode;
  30 +
  31 + /** 层级 */
  32 + private Integer level;
  33 +
  34 + /** 排序 */
  35 + private Integer sort;
  36 +
  37 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/road/RoadDO.java
... ... @@ -122,5 +122,16 @@ public class RoadDO extends BaseDO {
122 122 */
123 123 private String updaterName;
124 124  
  125 + // ========== GIS 关联字段 ==========
  126 +
  127 + /** GIS 图斑名称 */
  128 + private String gisPlotName;
  129 +
  130 + /** GIS 图斑面积 */
  131 + @JsonSerialize(using = TwoDecimalFloatSerializer.class)
  132 + private Float gisPlotArea;
  133 +
  134 + /** 是否关联GIS (0=未关联, 1=已关联) */
  135 + private Integer gisIsRelated;
125 136  
126 137 }
127 138 \ No newline at end of file
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treeinspection/TreeInspectionDO.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.dataobject.treeinspection;
  2 +
  3 +import lombok.*;
  4 +import java.time.LocalDateTime;
  5 +import java.util.List;
  6 +import com.baomidou.mybatisplus.annotation.*;
  7 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
  8 +import com.zteits.urbanops.framework.tenant.core.db.TenantBaseDO;
  9 +
  10 +/**
  11 + * 行道树巡检与安全风险评估记录 DO
  12 + *
  13 + * @author Antigravity
  14 + */
  15 +@TableName(value = "garden_tree_inspection", autoResultMap = true)
  16 +@KeySequence("garden_tree_inspection_seq")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +@ToString(callSuper = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class TreeInspectionDO extends TenantBaseDO {
  24 +
  25 + /**
  26 + * 主键
  27 + */
  28 + @TableId(type = IdType.AUTO)
  29 + private Long id;
  30 +
  31 + /**
  32 + * 一树一档案ID
  33 + */
  34 + private Long treeId;
  35 +
  36 + /**
  37 + * 树木编号
  38 + */
  39 + private String treenumber;
  40 +
  41 + /**
  42 + * 巡检时间
  43 + */
  44 + private LocalDateTime inspectionTime;
  45 +
  46 + /**
  47 + * 巡检人ID
  48 + */
  49 + private Long inspectorId;
  50 +
  51 + /**
  52 + * 巡检人姓名
  53 + */
  54 + private String inspectorName;
  55 +
  56 + /**
  57 + * 部门ID
  58 + */
  59 + private Long deptId;
  60 +
  61 + // ========== 缺陷评估指标得分 ==========
  62 +
  63 + /**
  64 + * 根部病害:0-无真菌危害或腐朽,8-存在真菌危害或腐朽
  65 + */
  66 + private Integer rootDisease;
  67 +
  68 + /**
  69 + * 根系下扎情况:0-良好,7-存在隆起或盘根
  70 + */
  71 + private Integer rootAnchorage;
  72 +
  73 + /**
  74 + * 工程切根:0-无,5-存在
  75 + */
  76 + private Integer rootCutting;
  77 +
  78 + /**
  79 + * 根颈木质部受损:0-无,5-<10%, 15-10%-30%, 25-30%-50%, 70->=50%
  80 + */
  81 + private Integer collarWoodDamage;
  82 +
  83 + /**
  84 + * 根颈树皮受损:0-<10%, 2-10%-30%, 4-30%-50%, 6->=50%
  85 + */
  86 + private Integer collarBarkDamage;
  87 +
  88 + /**
  89 + * 根颈松动:0-不存在,100-存在
  90 + */
  91 + private Integer collarLoosening;
  92 +
  93 + /**
  94 + * 主干木质部受损:0-无,5-<10%, 12-10%-30%, 20-30%-50%, 70->=50%
  95 + */
  96 + private Integer trunkWoodDamage;
  97 +
  98 + /**
  99 + * 主干倾斜:0-<10度, 3-10-20度, 8-20-30度, 70->=30度
  100 + */
  101 + private Integer trunkTilt;
  102 +
  103 + /**
  104 + * 主干树皮受损:0-<10%, 1-10%-30%, 3-30%-50%, 5->=50%
  105 + */
  106 + private Integer trunkBarkDamage;
  107 +
  108 + /**
  109 + * 易落枝:0-未发现,2-<1/10, 3->=1/10
  110 + */
  111 + private Integer crownLooseBranch;
  112 +
  113 + /**
  114 + * 枝干结合部异常:0-无,3-龟裂卷皮,5-腐烂未成洞,70-明显空洞或蛀干痕迹
  115 + */
  116 + private Integer crownCollarAbnormal;
  117 +
  118 + /**
  119 + * 树冠透风及平衡性:0-较好不偏冠, 1-偏冠或透风差不偏冠, 2-透风差明显偏冠但冠幅适中, 5-透风差冠幅大不偏冠, 8-透风差冠幅大且明显偏冠
  120 + */
  121 + private Integer crownVentilationBalance;
  122 +
  123 + // ========== 权重因子指标值和赋权值 ==========
  124 +
  125 + /**
  126 + * 树种类型:深根性树种, 浅根性树种
  127 + */
  128 + private String treeSpeciesType;
  129 +
  130 + /**
  131 + * 树种类型权重:深根性-1.0, 浅根性-1.1
  132 + */
  133 + private Double treeSpeciesWeight;
  134 +
  135 + /**
  136 + * 栽植年限:栽植 10 年以内, 栽植 10-30 年, 栽植 30 年以上
  137 + */
  138 + private String plantingYears;
  139 +
  140 + /**
  141 + * 栽植年限权重:10年内-1.0, 10-30年-1.1, 30年以上-1.2
  142 + */
  143 + private Double plantingYearsWeight;
  144 +
  145 + /**
  146 + * 是否处于风口:0-否, 1-是
  147 + */
  148 + private Boolean isWindCorridor;
  149 +
  150 + /**
  151 + * 风口权重:否-1.0, 是-2.0
  152 + */
  153 + private Double windCorridorWeight;
  154 +
  155 + /**
  156 + * 树池类型:联通树池, 独立树池, 树池硬化
  157 + */
  158 + private String treePoolType;
  159 +
  160 + /**
  161 + * 树池类型权重:联通树池-1.0, 独立树池-1.2, 树池硬化-1.5
  162 + */
  163 + private Double treePoolWeight;
  164 +
  165 + /**
  166 + * 树池宽度与胸径比:7 倍及以上, 5 倍-7 倍, 3 倍-5 倍, 3 倍以下
  167 + */
  168 + private String treePoolWidthDbhRatio;
  169 +
  170 + /**
  171 + * 树池比权重:7倍及以上-1.0, 5-7倍-1.1, 3-5倍-1.2, 3倍以下-1.3
  172 + */
  173 + private Double treePoolRatioWeight;
  174 +
  175 + // ========== 应急评估及加权 ==========
  176 +
  177 + /**
  178 + * 是否进行应急评估:0-否, 1-是
  179 + */
  180 + private Boolean isEmergency;
  181 +
  182 + /**
  183 + * 应急评估极端天气风力:7 级及以下, 8-9 级, 10 级, 10 级以上
  184 + */
  185 + private String windPower;
  186 +
  187 + /**
  188 + * 风力权重:7级及以下-1.0, 8-9级-1.5, 10级-2.0, 10级以上-3.0
  189 + */
  190 + private Double windPowerWeight;
  191 +
  192 + // ========== 评估计算结果 ==========
  193 +
  194 + /**
  195 + * 树木缺陷得分
  196 + */
  197 + private Integer defectScore;
  198 +
  199 + /**
  200 + * 常规情况下安全风险得分
  201 + */
  202 + private Double normalScore;
  203 +
  204 + /**
  205 + * 常规情况下风险等级
  206 + */
  207 + private String normalLevel;
  208 +
  209 + /**
  210 + * 应急情况下安全风险得分
  211 + */
  212 + private Double emergencyScore;
  213 +
  214 + /**
  215 + * 应急情况下风险等级
  216 + */
  217 + private String emergencyLevel;
  218 +
  219 + // ========== 现状及处理措施 ==========
  220 +
  221 + /**
  222 + * 建议处置措施
  223 + */
  224 + private String treatmentSuggestion;
  225 +
  226 + /**
  227 + * 是否已处置:0-未处置, 1-已处置
  228 + */
  229 + private Boolean isTreated;
  230 +
  231 + /**
  232 + * 现场照片链接(JSON 数组)
  233 + */
  234 + @TableField(typeHandler = JacksonTypeHandler.class)
  235 + private List<String> photos;
  236 +
  237 + /**
  238 + * 评估单位(公司)
  239 + */
  240 + private String estimatorCompany;
  241 +
  242 + /**
  243 + * 评估人
  244 + */
  245 + private String estimator;
  246 +
  247 +
  248 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisRoadMapper.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.mysql.gis;
  2 +
  3 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  4 +import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
  6 +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisRoadDO;
  7 +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +import java.util.*;
  10 +
  11 +/**
  12 + * GIS 道路核对 Mapper
  13 + */
  14 +@Mapper
  15 +public interface GisRoadMapper extends BaseMapperX<GisRoadDO> {
  16 +
  17 + /** XG 大类的子代码(不以 XG 开头,需特殊处理) */
  18 + List<String> XG_CHILD_CODES = Arrays.asList("RG", "AG", "BG", "MG", "WG", "SG", "UG");
  19 +
  20 + /** 构建公共查询条件 */
  21 + private LambdaQueryWrapperX<GisRoadDO> buildQueryWrapper(GisRoadPageReqVO reqVO) {
  22 + LambdaQueryWrapperX<GisRoadDO> wrapper = new LambdaQueryWrapperX<GisRoadDO>()
  23 + .likeIfPresent(GisRoadDO::getRoadName, reqVO.getRoadName())
  24 + .eqIfPresent(GisRoadDO::getBusiLine, reqVO.getBusiLine())
  25 + .eqIfPresent(GisRoadDO::getStreetId, reqVO.getStreetId())
  26 + .likeIfPresent(GisRoadDO::getStreetName, reqVO.getStreetName())
  27 + .eqIfPresent(GisRoadDO::getLevelId, reqVO.getLevelId())
  28 + .eqIfPresent(GisRoadDO::getRoadAttr, reqVO.getRoadAttr())
  29 + .eqIfPresent(GisRoadDO::getDirection, reqVO.getDirection())
  30 + .likeIfPresent(GisRoadDO::getGisPlotCode, reqVO.getGisPlotCode())
  31 + .likeIfPresent(GisRoadDO::getGisPlotName, reqVO.getGisPlotName())
  32 + .eqIfPresent(GisRoadDO::getGisIsRelated, reqVO.getGisIsRelated())
  33 + .eqIfPresent(GisRoadDO::getCompanyId, reqVO.getCompanyId())
  34 + .eqIfPresent(GisRoadDO::getDeptId, reqVO.getDeptId());
  35 +
  36 + if (reqVO.getGisTypeCode() != null) {
  37 + if ("XG".equals(reqVO.getGisTypeCode())) {
  38 + wrapper.in(GisRoadDO::getGisGreenTypeCode, XG_CHILD_CODES);
  39 + } else {
  40 + wrapper.likeRight(GisRoadDO::getGisGreenTypeCode, reqVO.getGisTypeCode());
  41 + }
  42 + }
  43 + return wrapper.orderByDesc(GisRoadDO::getId);
  44 + }
  45 +
  46 + default PageResult<GisRoadDO> selectPage(GisRoadPageReqVO reqVO) {
  47 + return selectPage(reqVO, buildQueryWrapper(reqVO));
  48 + }
  49 +
  50 + /** 不分页查询全部记录(用于地图渲染) */
  51 + default List<GisRoadDO> selectAllList(GisRoadPageReqVO reqVO) {
  52 + return selectList(buildQueryWrapper(reqVO));
  53 + }
  54 +
  55 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/gis/GisTypeMapper.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.mysql.gis;
  2 +
  3 +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
  4 +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +@Mapper
  8 +public interface GisTypeMapper extends BaseMapperX<GisTypeDO> {
  9 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java
... ... @@ -6,6 +6,7 @@ import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.CommitTask
6 6 import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.CommitWorkOrderRespVo;
7 7 import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.CommitWorkOrderSourceRespVo;
8 8 import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.WorkOrderTrendRespVo;
  9 +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppWorkOrderSummaryVo;
9 10 import com.zteits.urbanops.module.garden.controller.app.homepage.vo.CommonTaskStatusVo;
10 11 import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo;
11 12 import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo;
... ... @@ -206,4 +207,13 @@ public interface HomepageSummaryMapper extends BaseMapperX&lt;DepartmentCostDO&gt; {
206 207 * @Date 2026/1/12 0:45
207 208 */
208 209 List<TaskStatisticsDO> getUserAll(@Param("busiDate") String busiDate);
  210 +
  211 + /**
  212 + * @Author wangqian
  213 + * @Description 按单位+时间统计工单数量 总量 | 已完成|
  214 + * @Date 2026/5/26 22:46
  215 + * @Param null
  216 + * @Return
  217 + */
  218 + List<AppWorkOrderSummaryVo> countWorkOrder(@Param("queryType") String queryType,@Param("userId") Long userId, @Param("beginTime") String beginTime, @Param("endTime") String endTime);
209 219 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/problemtype/GardenProblemTypeMapper.java
... ... @@ -6,6 +6,8 @@ import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
6 6 import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypePageReqVO;
7 7 import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO;
8 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Param;
  10 +import org.apache.ibatis.annotations.Select;
9 11  
10 12 import java.util.ArrayList;
11 13 import java.util.List;
... ... @@ -63,25 +65,55 @@ public interface GardenProblemTypeMapper extends BaseMapperX&lt;GardenProblemTypeDO
63 65 }
64 66  
65 67 /**
  68 + * 根据名称、父级编码、层级查询记录(校验名称在同级下唯一)
  69 + * 使用 LIMIT 1 避免已有脏数据导致 TooManyResultsException
  70 + */
  71 + default GardenProblemTypeDO selectByNameAndParentAndLevel(String typeName, String parentCode, Integer level) {
  72 + return selectOne(new LambdaQueryWrapperX<GardenProblemTypeDO>()
  73 + .eq(GardenProblemTypeDO::getTypeName, typeName)
  74 + .eq(GardenProblemTypeDO::getParentCode, parentCode)
  75 + .eq(GardenProblemTypeDO::getLevel, level)
  76 + .last("LIMIT 1"));
  77 + }
  78 +
  79 + /**
  80 + * 绕过逻辑删除,直接查 typeCode 是否物理存在(用于编码生成时避免唯一键冲突)
  81 + */
  82 + @Select("SELECT COUNT(1) FROM garden_problem_type WHERE type_code = #{code}")
  83 + int countByTypeCodeRaw(@Param("code") String code);
  84 +
  85 + /**
66 86 * 生成下一个编码:父级编码 + 5位递增序号
67 87 * 如 parentCode=BIZ_GARDEN → BIZ_GARDEN00001, BIZ_GARDEN00002
  88 + * 注意:加 LENGTH 条件避免匹配到子级编码;绕过逻辑删除检查确保唯一键可用
68 89 */
69 90 default String generateNextCode(String parentCode) {
  91 + int expectedLength = parentCode.length() + 5;
70 92 List<GardenProblemTypeDO> list = selectList(new LambdaQueryWrapperX<GardenProblemTypeDO>()
71 93 .likeRight(GardenProblemTypeDO::getTypeCode, parentCode)
  94 + .apply("LENGTH(type_code) = {0}", expectedLength)
72 95 .orderByDesc(GardenProblemTypeDO::getTypeCode)
73 96 .last("LIMIT 1"));
  97 + int seq;
74 98 if (list == null || list.isEmpty()) {
75   - return parentCode + "00001";
76   - }
77   - String maxCode = list.get(0).getTypeCode();
78   - String seqStr = maxCode.substring(parentCode.length());
79   - try {
80   - int seq = Integer.parseInt(seqStr) + 1;
81   - return parentCode + String.format("%05d", seq);
82   - } catch (NumberFormatException e) {
83   - return parentCode + "00001";
  99 + seq = 1;
  100 + } else {
  101 + String maxCode = list.get(0).getTypeCode();
  102 + String seqStr = maxCode.substring(parentCode.length());
  103 + try {
  104 + seq = Integer.parseInt(seqStr) + 1;
  105 + } catch (NumberFormatException e) {
  106 + seq = 1;
  107 + }
84 108 }
  109 + // 跳过被逻辑删除记录占用的编码
  110 + int maxAttempts = 100;
  111 + String nextCode;
  112 + do {
  113 + nextCode = parentCode + String.format("%05d", seq);
  114 + seq++;
  115 + } while (countByTypeCodeRaw(nextCode) > 0 && --maxAttempts > 0);
  116 + return nextCode;
85 117 }
86 118  
87 119 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/road/RoadMapper.java
... ... @@ -42,6 +42,7 @@ public interface RoadMapper extends BaseMapperX&lt;RoadDO&gt; {
42 42 .likeIfPresent(RoadDO::getCreatorName, reqVO.getCreatorName())
43 43 .betweenIfPresent(RoadDO::getCreateTime, reqVO.getCreateTime())
44 44 .likeIfPresent(RoadDO::getUpdaterName, reqVO.getUpdaterName())
  45 + .eqIfPresent(RoadDO::getGisIsRelated, reqVO.getGisIsRelated())
45 46 .orderByDesc(RoadDO::getId));
46 47 }
47 48  
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treeinspection/TreeInspectionMapper.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.dal.mysql.treeinspection;
  2 +
  3 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  4 +import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
  6 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  7 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.TreeInspectionPageReqVO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +/**
  11 + * 行道树巡检与安全风险评估记录 Mapper
  12 + *
  13 + * @author Antigravity
  14 + */
  15 +@Mapper
  16 +public interface TreeInspectionMapper extends BaseMapperX<TreeInspectionDO> {
  17 +
  18 + default PageResult<TreeInspectionDO> selectPage(TreeInspectionPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<TreeInspectionDO>()
  20 + .eqIfPresent(TreeInspectionDO::getTreeId, reqVO.getTreeId())
  21 + .orderByDesc(TreeInspectionDO::getInspectionTime)
  22 + .orderByDesc(TreeInspectionDO::getId)
  23 + );
  24 + }
  25 +
  26 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/ErrorCodeConstants.java
... ... @@ -150,5 +150,9 @@ public interface ErrorCodeConstants {
150 150 // ========== 问题类型配置 1-100-009-000 ==========
151 151 ErrorCode PROBLEM_TYPE_NOT_EXISTS = new ErrorCode(1100009000, "问题类型不存在");
152 152 ErrorCode PROBLEM_TYPE_CODE_EXISTS = new ErrorCode(1100009001, "问题类型编码已存在");
  153 + ErrorCode PROBLEM_TYPE_NAME_DUPLICATE = new ErrorCode(1100009002, "同级下问题类型名称已存在,一级、二级、三级类型名称不能同时重复");
  154 +
  155 + // ========== 一树一档案巡检与风险评估 1-100-008-000 ==========
  156 + ErrorCode TREE_INSPECTION_NOT_EXISTS = new ErrorCode(1100008001, "巡检评估记录不存在");
153 157  
154 158 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadService.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.gis;
  2 +
  3 +import java.util.*;
  4 +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*;
  5 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  6 +
  7 +/**
  8 + * GIS 道路 Service 接口
  9 + *
  10 + * @author UrbanOps
  11 + */
  12 +public interface GisRoadService {
  13 +
  14 + /**
  15 + * 获得 GIS 道路分页
  16 + *
  17 + * @param pageReqVO 分页查询
  18 + * @return GIS 道路分页
  19 + */
  20 + PageResult<GisRoadRespVO> getGisRoadPage(GisRoadPageReqVO pageReqVO);
  21 +
  22 + /**
  23 + * 获得 GIS 道路
  24 + *
  25 + * @param id 编号
  26 + * @return GIS 道路
  27 + */
  28 + GisRoadRespVO getGisRoad(Long id);
  29 +
  30 + /**
  31 + * 获得全部 GIS 道路列表(不分页,用于地图渲染)
  32 + *
  33 + * @param reqVO 查询条件
  34 + * @return GIS 道路列表
  35 + */
  36 + List<GisRoadRespVO> getGisRoadAllList(GisRoadPageReqVO reqVO);
  37 +
  38 + /**
  39 + * 关联道路到 GIS 图斑
  40 + *
  41 + * @param reqVO 关联请求
  42 + */
  43 + void associateRoad(GisRoadAssociateReqVO reqVO);
  44 +
  45 + /**
  46 + * 取消 GIS 图斑与道路的关联
  47 + *
  48 + * @param reqVO 取消关联请求
  49 + */
  50 + void disassociateRoad(GisRoadDisassociateReqVO reqVO);
  51 +
  52 + /**
  53 + * 将 garden_gis_road 表中所有坐标从 WGS84 转换为 GCJ02(高德坐标系)
  54 + *
  55 + * @return 转换记录数
  56 + */
  57 + int convertCoordinatesToGcj02();
  58 +
  59 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisRoadServiceImpl.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.gis;
  2 +
  3 +import com.zteits.urbanops.module.system.api.dept.DeptApi;
  4 +import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO;
  5 +import org.springframework.stereotype.Service;
  6 +import org.springframework.transaction.annotation.Transactional;
  7 +import jakarta.annotation.Resource;
  8 +import org.springframework.util.CollectionUtils;
  9 +import org.springframework.validation.annotation.Validated;
  10 +
  11 +import java.time.LocalDateTime;
  12 +import java.util.*;
  13 +import java.util.stream.Collectors;
  14 +
  15 +import com.zteits.urbanops.module.garden.controller.admin.gis.vo.*;
  16 +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisRoadDO;
  17 +import com.zteits.urbanops.module.garden.dal.dataobject.road.RoadDO;
  18 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  19 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  20 +import com.zteits.urbanops.framework.common.util.object.BeanUtils;
  21 +import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils;
  22 +import com.zteits.urbanops.module.garden.dal.mysql.gis.GisRoadMapper;
  23 +import com.zteits.urbanops.module.garden.dal.mysql.road.RoadMapper;
  24 +import com.zteits.urbanops.module.garden.util.GisCoordinateUtil;
  25 +
  26 +/**
  27 + * GIS 道路 Service 实现类
  28 + *
  29 + * @author UrbanOps
  30 + */
  31 +@Service
  32 +@Validated
  33 +public class GisRoadServiceImpl implements GisRoadService {
  34 +
  35 + @Resource
  36 + private GisRoadMapper gisRoadMapper;
  37 +
  38 + @Resource
  39 + private RoadMapper roadMapper;
  40 +
  41 + @Resource
  42 + private DeptApi deptApi;
  43 +
  44 + @Override
  45 + public PageResult<GisRoadRespVO> getGisRoadPage(GisRoadPageReqVO pageReqVO) {
  46 + PageResult<GisRoadDO> pageResult = gisRoadMapper.selectPage(pageReqVO);
  47 + PageResult<GisRoadRespVO> page = BeanUtils.toBean(pageResult, GisRoadRespVO.class);
  48 + fillDeptAndCompanyName(page.getList());
  49 + return page;
  50 + }
  51 +
  52 + @Override
  53 + public GisRoadRespVO getGisRoad(Long id) {
  54 + GisRoadDO gisRoadDO = gisRoadMapper.selectById(id);
  55 + if (gisRoadDO == null) {
  56 + return null;
  57 + }
  58 + GisRoadRespVO respVO = BeanUtils.toBean(gisRoadDO, GisRoadRespVO.class);
  59 + List<GisRoadRespVO> list = new ArrayList<>();
  60 + list.add(respVO);
  61 + fillDeptAndCompanyName(list);
  62 + return respVO;
  63 + }
  64 +
  65 + @Override
  66 + public List<GisRoadRespVO> getGisRoadAllList(GisRoadPageReqVO reqVO) {
  67 + List<GisRoadDO> list = gisRoadMapper.selectAllList(reqVO);
  68 + List<GisRoadRespVO> voList = BeanUtils.toBean(list, GisRoadRespVO.class);
  69 + fillDeptAndCompanyName(voList);
  70 + return voList;
  71 + }
  72 +
  73 + @Override
  74 + @Transactional(rollbackFor = Exception.class)
  75 + public void associateRoad(GisRoadAssociateReqVO reqVO) {
  76 + GisRoadDO gisRoad = gisRoadMapper.selectById(reqVO.getId());
  77 + if (gisRoad == null) {
  78 + throw new IllegalArgumentException("GIS道路不存在: id=" + reqVO.getId());
  79 + }
  80 + RoadDO road = roadMapper.selectById(reqVO.getRoadId());
  81 + if (road == null) {
  82 + throw new IllegalArgumentException("道路不存在: id=" + reqVO.getRoadId());
  83 + }
  84 +
  85 + gisRoad.setGisRoadId(road.getId());
  86 + gisRoad.setRoadName(road.getRoadName());
  87 + gisRoad.setTotalArea(road.getTotalArea());
  88 + gisRoad.setCompanyId(road.getCompanyId());
  89 + gisRoad.setDeptId(road.getDeptId());
  90 +
  91 + // 差异面积 = GIS面积 - 道路面积
  92 + float gisArea = gisRoad.getGisPlotArea() != null ? gisRoad.getGisPlotArea() : 0;
  93 + float roadArea = road.getTotalArea() != null ? road.getTotalArea() : 0;
  94 + gisRoad.setGisDiffArea(gisArea - roadArea);
  95 +
  96 + gisRoad.setGisIsRelated(1);
  97 + gisRoad.setGisRelatedUser(SecurityFrameworkUtils.getLoginUserNickname());
  98 + gisRoad.setGisRelatedTime(LocalDateTime.now());
  99 +
  100 + gisRoadMapper.updateById(gisRoad);
  101 +
  102 + // 同步更新 garden_road 的 GIS 关联字段
  103 + road.setGisPlotName(gisRoad.getGisPlotName());
  104 + road.setGisPlotArea(gisRoad.getGisPlotArea());
  105 + road.setGisIsRelated(1);
  106 + roadMapper.updateById(road);
  107 + }
  108 +
  109 + @Override
  110 + @Transactional(rollbackFor = Exception.class)
  111 + public void disassociateRoad(GisRoadDisassociateReqVO reqVO) {
  112 + GisRoadDO gisRoad = gisRoadMapper.selectById(reqVO.getId());
  113 + if (gisRoad == null) {
  114 + throw new IllegalArgumentException("GIS道路不存在: id=" + reqVO.getId());
  115 + }
  116 +
  117 + // 同步清空 garden_road 的 GIS 关联字段(LambdaUpdateWrapper 才能将字段设为 null)
  118 + if (gisRoad.getGisRoadId() != null) {
  119 + roadMapper.update(null,
  120 + new LambdaUpdateWrapper<RoadDO>()
  121 + .eq(RoadDO::getId, gisRoad.getGisRoadId())
  122 + .set(RoadDO::getGisPlotName, null)
  123 + .set(RoadDO::getGisPlotArea, null)
  124 + .set(RoadDO::getGisIsRelated, 0));
  125 + }
  126 +
  127 + // 取消关联:仅将关联状态置为 0,其他字段因数据库 NOT NULL 约束保留原值
  128 + gisRoadMapper.update(null,
  129 + new LambdaUpdateWrapper<GisRoadDO>()
  130 + .eq(GisRoadDO::getId, gisRoad.getId())
  131 + .set(GisRoadDO::getGisIsRelated, 0));
  132 + }
  133 +
  134 + @Override
  135 + @Transactional(rollbackFor = Exception.class)
  136 + public int convertCoordinatesToGcj02() {
  137 + List<GisRoadDO> all = gisRoadMapper.selectList();
  138 + if (CollectionUtils.isEmpty(all)) {
  139 + return 0;
  140 + }
  141 + int count = 0;
  142 + for (GisRoadDO road : all) {
  143 + boolean updated = false;
  144 +
  145 + // 转换起点坐标
  146 + if (road.getStartingLatitude() != null && !road.getStartingLatitude().isBlank()
  147 + && road.getStartingLongitude() != null && !road.getStartingLongitude().isBlank()) {
  148 + try {
  149 + double lng = Double.parseDouble(road.getStartingLongitude());
  150 + double lat = Double.parseDouble(road.getStartingLatitude());
  151 + double[] gcj = GisCoordinateUtil.wgs84ToGcj02(lng, lat);
  152 + road.setStartingLongitude(String.format("%.6f", gcj[0]));
  153 + road.setStartingLatitude(String.format("%.6f", gcj[1]));
  154 + updated = true;
  155 + } catch (NumberFormatException ignored) {
  156 + }
  157 + }
  158 +
  159 + // 转换终点坐标
  160 + if (road.getEndLatitude() != null && !road.getEndLatitude().isBlank()
  161 + && road.getEndLongitude() != null && !road.getEndLongitude().isBlank()) {
  162 + try {
  163 + double lng = Double.parseDouble(road.getEndLongitude());
  164 + double lat = Double.parseDouble(road.getEndLatitude());
  165 + double[] gcj = GisCoordinateUtil.wgs84ToGcj02(lng, lat);
  166 + road.setEndLongitude(String.format("%.6f", gcj[0]));
  167 + road.setEndLatitude(String.format("%.6f", gcj[1]));
  168 + updated = true;
  169 + } catch (NumberFormatException ignored) {
  170 + }
  171 + }
  172 +
  173 + // 转换多边形围栏坐标
  174 + if (road.getGisPolygonCoords() != null && !road.getGisPolygonCoords().isBlank()) {
  175 + String converted = GisCoordinateUtil.transformPolygonWkt(road.getGisPolygonCoords());
  176 + if (!converted.equals(road.getGisPolygonCoords())) {
  177 + road.setGisPolygonCoords(converted);
  178 + updated = true;
  179 + }
  180 + }
  181 +
  182 + if (updated) {
  183 + gisRoadMapper.updateById(road);
  184 + count++;
  185 + }
  186 + }
  187 + return count;
  188 + }
  189 +
  190 + /**
  191 + * 回填部门/公司名称
  192 + */
  193 + private void fillDeptAndCompanyName(List<GisRoadRespVO> list) {
  194 + if (CollectionUtils.isEmpty(list)) {
  195 + return;
  196 + }
  197 + // 收集所有公司ID和部门ID
  198 + Set<Long> companyIds = list.stream()
  199 + .map(GisRoadRespVO::getCompanyId)
  200 + .filter(Objects::nonNull)
  201 + .collect(Collectors.toSet());
  202 + Set<Long> deptIds = list.stream()
  203 + .map(GisRoadRespVO::getDeptId)
  204 + .filter(Objects::nonNull)
  205 + .collect(Collectors.toSet());
  206 + deptIds.addAll(companyIds);
  207 +
  208 + if (deptIds.isEmpty()) {
  209 + return;
  210 + }
  211 +
  212 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
  213 +
  214 + list.forEach(vo -> {
  215 + if (vo.getDeptId() != null) {
  216 + DeptRespDTO dept = deptMap.get(vo.getDeptId());
  217 + if (dept != null) {
  218 + vo.setDeptName(dept.getName());
  219 + }
  220 + }
  221 + if (vo.getCompanyId() != null) {
  222 + DeptRespDTO company = deptMap.get(vo.getCompanyId());
  223 + if (company != null) {
  224 + vo.setCompanyName(company.getName());
  225 + }
  226 + }
  227 + });
  228 + }
  229 +
  230 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeService.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.gis;
  2 +
  3 +import java.util.List;
  4 +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO;
  5 +
  6 +public interface GisTypeService {
  7 +
  8 + /** 按父级编码查子类型列表 */
  9 + List<GisTypeDO> listByParentCode(String parentCode);
  10 +
  11 + /** 按层级查类型列表 */
  12 + List<GisTypeDO> listByLevel(Integer level);
  13 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/gis/GisTypeServiceImpl.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.gis;
  2 +
  3 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4 +import com.zteits.urbanops.module.garden.dal.dataobject.gis.GisTypeDO;
  5 +import com.zteits.urbanops.module.garden.dal.mysql.gis.GisTypeMapper;
  6 +import org.springframework.stereotype.Service;
  7 +import jakarta.annotation.Resource;
  8 +import java.util.List;
  9 +
  10 +@Service
  11 +public class GisTypeServiceImpl implements GisTypeService {
  12 +
  13 + @Resource
  14 + private GisTypeMapper gisTypeMapper;
  15 +
  16 + @Override
  17 + public List<GisTypeDO> listByParentCode(String parentCode) {
  18 + return gisTypeMapper.selectList(
  19 + new LambdaQueryWrapper<GisTypeDO>()
  20 + .eq(GisTypeDO::getParentCode, parentCode == null ? "" : parentCode)
  21 + .orderByAsc(GisTypeDO::getSort)
  22 + );
  23 + }
  24 +
  25 + @Override
  26 + public List<GisTypeDO> listByLevel(Integer level) {
  27 + return gisTypeMapper.selectList(
  28 + new LambdaQueryWrapper<GisTypeDO>()
  29 + .eq(GisTypeDO::getLevel, level)
  30 + .orderByAsc(GisTypeDO::getSort)
  31 + );
  32 + }
  33 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java
1 1 package com.zteits.urbanops.module.garden.service.homepage;
2 2  
3 3 import com.zteits.urbanops.framework.common.pojo.PageResult;
4   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryPageReqVO;
5   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryReqVO;
6   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo;
7   -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo;
  4 +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*;
8 5  
9 6 import java.util.List;
10   -import java.util.Map;
11 7  
12 8 /**
13 9 * @Classname HomepageSummaryService
... ... @@ -42,4 +38,14 @@ public interface HomepageSummaryService {
42 38 * @Return void
43 39 */
44 40 public void countTaskNumberByUserId(String busiDate);
  41 + /**
  42 + * @Author wangqian
  43 + * @Description 工单情况统计
  44 + * @Date 2026/5/26 22:34
  45 + * @Param req
  46 + * @Return java.util.List<WorkOrderStatisticsRspVo>
  47 + */
  48 + AppWorkOrderSummaryRspVo iWorkOrderSummary(AppWorkOrderSummaryReqVo req);
  49 +
  50 +
45 51 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java
... ... @@ -5,9 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
5 5 import com.baomidou.mybatisplus.core.metadata.IPage;
6 6 import com.esotericsoftware.minlog.Log;
7 7 import com.zteits.urbanops.framework.common.pojo.PageResult;
8   -import com.zteits.urbanops.framework.common.util.date.DateUtils;
9 8 import com.zteits.urbanops.framework.common.util.date.LocalDateTimeUtils;
10   -import com.zteits.urbanops.framework.mybatis.core.query.QueryWrapperX;
11 9 import com.zteits.urbanops.framework.mybatis.core.util.MyBatisUtils;
12 10 import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils;
13 11 import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*;
... ... @@ -17,7 +15,7 @@ import com.zteits.urbanops.module.garden.dal.mysql.taskstatistics.TaskStatistics
17 15 import com.zteits.urbanops.module.system.api.dept.DeptApi;
18 16 import com.zteits.urbanops.module.system.api.permission.RoleApi;
19 17 import com.zteits.urbanops.module.system.api.user.AdminUserApi;
20   -import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO;
  18 +import com.zteits.urbanops.module.system.enums.common.CommonConstants;
21 19 import jakarta.annotation.Resource;
22 20 import org.apache.commons.collections4.CollectionUtils;
23 21 import org.apache.commons.lang3.StringUtils;
... ... @@ -28,11 +26,12 @@ import java.text.DateFormat;
28 26 import java.text.ParseException;
29 27 import java.text.SimpleDateFormat;
30 28 import java.time.LocalDateTime;
31   -import java.time.LocalTime;
32 29 import java.time.format.DateTimeFormatter;
33 30 import java.util.*;
34 31 import java.util.stream.Collectors;
35 32  
  33 +import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  34 +
36 35 /**
37 36 * @Classname HomepageSummaryServiceImpl
38 37 * @Description 首页统计实现
... ... @@ -60,7 +59,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
60 59  
61 60 @Override
62 61 public List<TaskCompletionStatusRspVo> taskCompletionSummary(AppHomePageSummaryReqVO req) {
63   - Long userId = SecurityFrameworkUtils.getLoginUserId();
  62 + Long userId = getLoginUserId();
64 63 //巡查任务和养护任务已完成,未完成数量统计
65 64 //返回数据初始化
66 65 List<String> days = getDays(req.getBeginTime(), req.getEndTime());
... ... @@ -106,7 +105,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
106 105  
107 106 @Override
108 107 public PageResult<TaskDetailsRspVo> queryTaskDetails(AppHomePageSummaryPageReqVO req) {
109   - Long userId = SecurityFrameworkUtils.getLoginUserId();
  108 + Long userId = getLoginUserId();
110 109 IPage<TaskDetailsRspVo> mpPage = MyBatisUtils.buildPage(req);
111 110 if (req.getQueryType() == 2) {
112 111 //已完成
... ... @@ -114,7 +113,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
114 113 } else {
115 114 //待办
116 115 Long deptId = SecurityFrameworkUtils.getDeptId();
117   - List<Long> roleIds = roleApi.getRoleIdsByUserId(SecurityFrameworkUtils.getLoginUserId());
  116 + List<Long> roleIds = roleApi.getRoleIdsByUserId(getLoginUserId());
118 117 mpPage = homepageSummaryMapper.queryPendingTaskDetails(mpPage, roleIds, userId, deptId, LocalDateTime.now());
119 118 }
120 119 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
... ... @@ -287,6 +286,66 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
287 286 //批量保存
288 287 taskStatisticsMapper.insertBatch(taskStatisticsDOList);
289 288 }
  289 +
  290 + @Override
  291 + public AppWorkOrderSummaryRspVo iWorkOrderSummary(AppWorkOrderSummaryReqVo req) {
  292 + // 1. 创建返回对象
  293 + AppWorkOrderSummaryRspVo respVo = new AppWorkOrderSummaryRspVo();
  294 +
  295 + Long userId = null;
  296 + //部派工单
  297 + if ("1".equals(req.getQueryType())) {
  298 + // 全域督察员查询自己发起的工单
  299 + List<Long> userIds = roleApi.getUserIdsByRoleCode(CommonConstants.INSPECTOR_ROLE_KEY);
  300 + if (!userIds.contains(getLoginUserId())) {
  301 + userId = getLoginUserId();
  302 + }
  303 + }
  304 + // 2. 查询工单列表
  305 + List<AppWorkOrderSummaryVo> workOrderList = homepageSummaryMapper.countWorkOrder(
  306 + req.getQueryType(),
  307 + userId,
  308 + req.getBeginTime(),
  309 + req.getEndTime()
  310 + );
  311 +
  312 + // 3. 提取公司名称列表(修复:toList() 或 toList())
  313 + List<String> companyNameList = workOrderList.stream()
  314 + .map(AppWorkOrderSummaryVo::getCompanyName)
  315 + .collect(Collectors.toList());
  316 +
  317 + // 4. 总数量求和
  318 + int totalNum = workOrderList.stream()
  319 + .mapToInt(AppWorkOrderSummaryVo::getTotal)
  320 + .sum();
  321 +
  322 + List<Integer> percents;
  323 + if (totalNum <= 0) {
  324 + percents = Collections.singletonList(0);
  325 + } else {
  326 + // 3. 先计算四舍五入占比
  327 + List<Integer> tempPercents = workOrderList.stream()
  328 + .map(vo -> (int) Math.round((vo.getTotal() * 100.0) / totalNum))
  329 + .collect(Collectors.toList());
  330 +
  331 + // 4. 核心:修正总和 = 100%(把差值补到最后一项)
  332 + int sum = tempPercents.stream().mapToInt(Integer::intValue).sum();
  333 + int diff = 100 - sum;
  334 + if (diff != 0 && !tempPercents.isEmpty()) {
  335 + int lastIndex = tempPercents.size() - 1;
  336 + tempPercents.set(lastIndex, tempPercents.get(lastIndex) + diff);
  337 + }
  338 + percents = tempPercents;
  339 + }
  340 + // 6. 封装返回数据
  341 + respVo.setLegend(companyNameList);
  342 + respVo.setTable(workOrderList);
  343 + respVo.setTotalNum(String.valueOf(totalNum)); // 转字符串
  344 + respVo.setPercents(percents); //占比
  345 + // 7. 包装成 List 返回(因为方法声明返回 List)
  346 + return respVo;
  347 + }
  348 +
290 349 /**
291 350 * @Author wangqian
292 351 * @Description 初始化对象集合
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeService.java
... ... @@ -2,7 +2,9 @@ package com.zteits.urbanops.module.garden.service.problemtype;
2 2  
3 3 import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypePageReqVO;
4 4 import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypeSaveReqVO;
  5 +import com.zteits.urbanops.module.garden.controller.app.problemtype.vo.ProblemTypeCascadeRespVO;
5 6 import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO;
  7 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
6 8 import com.zteits.urbanops.framework.common.pojo.PageResult;
7 9 import jakarta.validation.Valid;
8 10  
... ... @@ -63,6 +65,14 @@ public interface GardenProblemTypeService {
63 65 List<GardenProblemTypeDO> getProblemTypeByParentCode(String parentCode, Integer level);
64 66  
65 67 /**
  68 + * 获取问题类型级联树
  69 + *
  70 + * @param parentCode 父级编码(为空则返回顶级树)
  71 + * @return 级联树列表
  72 + */
  73 + List<ProblemTypeCascadeRespVO> getProblemTypeTree(String parentCode);
  74 +
  75 + /**
66 76 * 根据父级编码生成下一个类型编码
67 77 *
68 78 * @param parentCode 父级编码
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/problemtype/GardenProblemTypeServiceImpl.java
... ... @@ -3,13 +3,18 @@ package com.zteits.urbanops.module.garden.service.problemtype;
3 3 import com.zteits.urbanops.framework.common.enums.CommonStatusEnum;
4 4 import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypePageReqVO;
5 5 import com.zteits.urbanops.module.garden.controller.admin.problemtype.vo.ProblemTypeSaveReqVO;
  6 +import com.zteits.urbanops.module.garden.controller.app.problemtype.vo.ProblemTypeCascadeRespVO;
6 7 import com.zteits.urbanops.module.system.dal.dataobject.dict.DictDataDO;
7 8 import com.zteits.urbanops.module.system.service.dict.DictDataService;
8 9 import org.springframework.stereotype.Service;
9 10 import org.springframework.validation.annotation.Validated;
10 11 import jakarta.annotation.Resource;
11 12  
  13 +import java.util.ArrayList;
12 14 import java.util.List;
  15 +import java.util.Map;
  16 +import java.util.Objects;
  17 +import java.util.stream.Collectors;
13 18  
14 19 import com.zteits.urbanops.module.garden.dal.dataobject.problemtype.GardenProblemTypeDO;
15 20 import com.zteits.urbanops.framework.common.pojo.PageResult;
... ... @@ -39,6 +44,9 @@ public class GardenProblemTypeServiceImpl implements GardenProblemTypeService {
39 44 public Long createProblemType(ProblemTypeSaveReqVO createReqVO) {
40 45 // 校验编码唯一性
41 46 validateTypeCodeUnique(createReqVO.getTypeCode(), null);
  47 + // 校验名称在同级父节点下唯一
  48 + validateTypeNameUniqueUnderParent(createReqVO.getTypeName(),
  49 + createReqVO.getParentCode(), createReqVO.getLevel(), null);
42 50 // 插入
43 51 GardenProblemTypeDO problemType = BeanUtils.toBean(createReqVO, GardenProblemTypeDO.class);
44 52 gardenProblemTypeMapper.insert(problemType);
... ... @@ -77,6 +85,15 @@ public class GardenProblemTypeServiceImpl implements GardenProblemTypeService {
77 85 }
78 86 }
79 87  
  88 + private void validateTypeNameUniqueUnderParent(String typeName, String parentCode,
  89 + Integer level, Long excludeId) {
  90 + GardenProblemTypeDO exist = gardenProblemTypeMapper.selectByNameAndParentAndLevel(
  91 + typeName, parentCode, level);
  92 + if (exist != null && (excludeId == null || !exist.getId().equals(excludeId))) {
  93 + throw exception(PROBLEM_TYPE_NAME_DUPLICATE);
  94 + }
  95 + }
  96 +
80 97 @Override
81 98 public GardenProblemTypeDO getProblemType(Long id) {
82 99 return gardenProblemTypeMapper.selectById(id);
... ... @@ -93,6 +110,74 @@ public class GardenProblemTypeServiceImpl implements GardenProblemTypeService {
93 110 }
94 111  
95 112 @Override
  113 + public List<ProblemTypeCascadeRespVO> getProblemTypeTree(String parentCode) {
  114 + List<GardenProblemTypeDO> allList;
  115 +
  116 + if (parentCode == null) {
  117 + allList = gardenProblemTypeMapper.selectList(
  118 + new com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX<GardenProblemTypeDO>()
  119 + .eq(GardenProblemTypeDO::getStatus, 1)
  120 + .orderByAsc(GardenProblemTypeDO::getSort)
  121 + .orderByAsc(GardenProblemTypeDO::getId));
  122 + } else {
  123 + List<GardenProblemTypeDO> level2List = gardenProblemTypeMapper.selectByParentCodeAndLevel(parentCode, 2);
  124 +
  125 + List<String> level2Codes = level2List.stream()
  126 + .map(GardenProblemTypeDO::getTypeCode)
  127 + .collect(Collectors.toList());
  128 +
  129 + List<GardenProblemTypeDO> level3List = new ArrayList<>();
  130 + if (!level2Codes.isEmpty()) {
  131 + level3List = gardenProblemTypeMapper.selectList(
  132 + new com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX<GardenProblemTypeDO>()
  133 + .eq(GardenProblemTypeDO::getStatus, 1)
  134 + .in(GardenProblemTypeDO::getParentCode, level2Codes));
  135 + }
  136 +
  137 + allList = new ArrayList<>();
  138 + allList.addAll(level2List);
  139 + allList.addAll(level3List);
  140 + }
  141 +
  142 + Map<String, List<GardenProblemTypeDO>> childrenMap = allList.stream()
  143 + .filter(item -> item.getParentCode() != null && !item.getParentCode().equals(item.getTypeCode()))
  144 + .collect(Collectors.groupingBy(GardenProblemTypeDO::getParentCode));
  145 +
  146 + List<ProblemTypeCascadeRespVO> result = new ArrayList<>();
  147 + if (parentCode == null) {
  148 + List<GardenProblemTypeDO> rootList = allList.stream()
  149 + .filter(item -> item.getParentCode() == null || item.getParentCode().equals(item.getTypeCode()))
  150 + .collect(Collectors.toList());
  151 + for (GardenProblemTypeDO root : rootList) {
  152 + ProblemTypeCascadeRespVO vo = buildTreeNode(root, childrenMap);
  153 + result.add(vo);
  154 + }
  155 + } else {
  156 + List<GardenProblemTypeDO> childrenList = allList.stream()
  157 + .filter(item -> parentCode.equals(item.getParentCode()))
  158 + .collect(Collectors.toList());
  159 + for (GardenProblemTypeDO child : childrenList) {
  160 + ProblemTypeCascadeRespVO vo = buildTreeNode(child, childrenMap);
  161 + result.add(vo);
  162 + }
  163 + }
  164 + return result;
  165 + }
  166 +
  167 + private ProblemTypeCascadeRespVO buildTreeNode(GardenProblemTypeDO item, Map<String, List<GardenProblemTypeDO>> childrenMap) {
  168 + ProblemTypeCascadeRespVO vo = new ProblemTypeCascadeRespVO();
  169 + vo.setLabel(item.getTypeName());
  170 + vo.setValue(item.getTypeCode());
  171 + List<GardenProblemTypeDO> children = childrenMap.get(item.getTypeCode());
  172 + if (children != null && !children.isEmpty()) {
  173 + for (GardenProblemTypeDO child : children) {
  174 + vo.getChildren().add(buildTreeNode(child, childrenMap));
  175 + }
  176 + }
  177 + return vo;
  178 + }
  179 +
  180 + @Override
96 181 public String generateNextCode(String parentCode) {
97 182 return gardenProblemTypeMapper.generateNextCode(parentCode);
98 183 }
... ... @@ -114,6 +199,10 @@ public class GardenProblemTypeServiceImpl implements GardenProblemTypeService {
114 199 entity.setStatus(CommonStatusEnum.ENABLE.getStatus());
115 200 gardenProblemTypeMapper.insert(entity);
116 201 count++;
  202 + } else if (!Objects.equals(exist.getTypeName(), dict.getLabel())) {
  203 + exist.setTypeName(dict.getLabel());
  204 + gardenProblemTypeMapper.updateById(exist);
  205 + count++;
117 206 }
118 207 }
119 208 return count;
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionService.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.treeinspection;
  2 +
  3 +import jakarta.validation.Valid;
  4 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  5 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  6 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*;
  7 +
  8 +/**
  9 + * 行道树巡检与安全风险评估业务 Service 接口
  10 + *
  11 + * @author Antigravity
  12 + */
  13 +public interface TreeInspectionService {
  14 +
  15 + /**
  16 + * 创建行道树巡检与评估记录
  17 + *
  18 + * @param createReqVO 创建信息
  19 + * @return 记录ID
  20 + */
  21 + Long createTreeInspection(@Valid TreeInspectionSaveReqVO createReqVO);
  22 +
  23 + /**
  24 + * 获得行道树巡检与评估详情
  25 + *
  26 + * @param id 编号
  27 + * @return 嵌套结构的详情 VO
  28 + */
  29 + TreeInspectionRespVO getTreeInspection(Long id);
  30 +
  31 + /**
  32 + * 分页查询单株树木的历史巡检记录
  33 + *
  34 + * @param pageReqVO 分页查询请求
  35 + * @return 巡检历史记录分页
  36 + */
  37 + PageResult<TreeInspectionDO> getTreeInspectionPage(TreeInspectionPageReqVO pageReqVO);
  38 +
  39 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImpl.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.treeinspection;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.springframework.stereotype.Service;
  5 +import org.springframework.validation.annotation.Validated;
  6 +import org.springframework.transaction.annotation.Transactional;
  7 +import jakarta.annotation.Resource;
  8 +
  9 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  10 +import com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil;
  11 +import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils;
  12 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  13 +import com.zteits.urbanops.module.garden.dal.dataobject.tree.TreeDO;
  14 +import com.zteits.urbanops.module.garden.dal.mysql.treeinspection.TreeInspectionMapper;
  15 +import com.zteits.urbanops.module.garden.dal.mysql.tree.TreeMapper;
  16 +import com.zteits.urbanops.module.garden.enums.ErrorCodeConstants;
  17 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*;
  18 +import com.zteits.urbanops.module.garden.convert.treeinspection.TreeInspectionConvert;
  19 +
  20 +/**
  21 + * 行道树巡检与安全风险评估业务 Service 实现类
  22 + *
  23 + * @author Antigravity
  24 + */
  25 +@Service
  26 +@Validated
  27 +@Slf4j
  28 +public class TreeInspectionServiceImpl implements TreeInspectionService {
  29 +
  30 + @Resource
  31 + private TreeInspectionMapper treeInspectionMapper;
  32 +
  33 + @Resource
  34 + private TreeMapper treeMapper;
  35 +
  36 + @Override
  37 + @Transactional(rollbackFor = Exception.class)
  38 + public Long createTreeInspection(TreeInspectionSaveReqVO createReqVO) {
  39 + // 1. 校验树木档案是否存在
  40 + TreeDO tree = treeMapper.selectById(createReqVO.getTreeId());
  41 + if (tree == null) {
  42 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.TREE_NOT_EXISTS);
  43 + }
  44 +
  45 + // 2. 转换数据并组装 DO 实体
  46 + TreeInspectionDO entity = TreeInspectionConvert.INSTANCE.convert(createReqVO);
  47 + entity.setTreenumber(tree.getTreenumber());
  48 +
  49 + // 3. 填充用户信息与部门信息
  50 + Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
  51 + String nickname = SecurityFrameworkUtils.getLoginUserNickname();
  52 + Long deptId = SecurityFrameworkUtils.getLoginUserDeptId();
  53 +
  54 + entity.setInspectorId(loginUserId);
  55 + entity.setInspectorName(nickname != null ? nickname : "巡检员");
  56 + if (entity.getDeptId() == null) {
  57 + entity.setDeptId(deptId != null ? deptId : tree.getDeptId());
  58 + }
  59 +
  60 + // 4. 补齐各因子的权重指标(即使由前端打分,后端依然补齐数据库里各个细节因子权重以支持底层的统计与分析)
  61 + double treeSpeciesWeight = "浅根性树种".equals(entity.getTreeSpeciesType()) ? 1.1 : 1.0;
  62 + entity.setTreeSpeciesWeight(treeSpeciesWeight);
  63 +
  64 + double plantingYearsWeight = getPlantingYearsWeight(entity.getPlantingYears());
  65 + entity.setPlantingYearsWeight(plantingYearsWeight);
  66 +
  67 + double windCorridorWeight = Boolean.TRUE.equals(entity.getIsWindCorridor()) ? 2.0 : 1.0;
  68 + entity.setWindCorridorWeight(windCorridorWeight);
  69 +
  70 + double treePoolWeight = getTreePoolWeight(entity.getTreePoolType());
  71 + entity.setTreePoolWeight(treePoolWeight);
  72 +
  73 + double treePoolRatioWeight = getTreePoolRatioWeight(entity.getTreePoolWidthDbhRatio());
  74 + entity.setTreePoolRatioWeight(treePoolRatioWeight);
  75 +
  76 + if (Boolean.TRUE.equals(entity.getIsEmergency())) {
  77 + double windPowerWeight = getWindPowerWeight(entity.getWindPower());
  78 + entity.setWindPowerWeight(windPowerWeight);
  79 + } else {
  80 + entity.setWindPowerWeight(1.0);
  81 + }
  82 +
  83 + // 5. 风险打分计算(前端计算结果存储优先,后端自动补齐防空兜底)
  84 + if (entity.getDefectScore() == null
  85 + || entity.getNormalScore() == null
  86 + || entity.getNormalLevel() == null) {
  87 +
  88 + // 后端兜底自动缺陷打分
  89 + int calculatedSum = entity.getRootDisease()
  90 + + entity.getRootAnchorage()
  91 + + entity.getRootCutting()
  92 + + entity.getCollarWoodDamage()
  93 + + entity.getCollarBarkDamage()
  94 + + entity.getCollarLoosening()
  95 + + entity.getTrunkWoodDamage()
  96 + + entity.getTrunkTilt()
  97 + + entity.getTrunkBarkDamage()
  98 + + entity.getCrownLooseBranch()
  99 + + entity.getCrownCollarAbnormal()
  100 + + entity.getCrownVentilationBalance();
  101 +
  102 + int defectScore = calculatedSum;
  103 + // 一票否决规则
  104 + if (entity.getCollarLoosening() == 100) {
  105 + defectScore = 100;
  106 + } else if (entity.getCollarWoodDamage() == 70
  107 + || entity.getTrunkWoodDamage() == 70
  108 + || entity.getTrunkTilt() == 70
  109 + || entity.getCrownCollarAbnormal() == 70) {
  110 + defectScore = Math.max(calculatedSum, 70);
  111 + }
  112 + entity.setDefectScore(defectScore);
  113 +
  114 + // 计算常规安全得分
  115 + double normalScore = defectScore * treeSpeciesWeight * plantingYearsWeight * windCorridorWeight * treePoolWeight * treePoolRatioWeight;
  116 + normalScore = Math.round(normalScore * 100.0) / 100.0;
  117 + entity.setNormalScore(normalScore);
  118 + entity.setNormalLevel(getRiskLevelName(normalScore));
  119 + }
  120 +
  121 + // 6. 应急打分补齐(若前端已计算则信任前端结果,否则后端自动补齐应急兜底)
  122 + if (Boolean.TRUE.equals(entity.getIsEmergency())) {
  123 + if (entity.getEmergencyScore() == null || entity.getEmergencyLevel() == null) {
  124 + double windPowerWeight = entity.getWindPowerWeight();
  125 + double emergencyScore = entity.getNormalScore() * windPowerWeight;
  126 + emergencyScore = Math.round(emergencyScore * 100.0) / 100.0;
  127 + entity.setEmergencyScore(emergencyScore);
  128 + entity.setEmergencyLevel(getRiskLevelName(emergencyScore));
  129 + }
  130 + } else {
  131 + entity.setEmergencyScore(null);
  132 + entity.setEmergencyLevel(null);
  133 + }
  134 +
  135 + // 7. 保存到数据库
  136 + treeInspectionMapper.insert(entity);
  137 + return entity.getId();
  138 + }
  139 +
  140 + @Override
  141 + public TreeInspectionRespVO getTreeInspection(Long id) {
  142 + TreeInspectionDO entity = treeInspectionMapper.selectById(id);
  143 + if (entity == null) {
  144 + throw ServiceExceptionUtil.exception(ErrorCodeConstants.TREE_INSPECTION_NOT_EXISTS);
  145 + }
  146 + return TreeInspectionConvert.INSTANCE.convert(entity);
  147 + }
  148 +
  149 + @Override
  150 + public PageResult<TreeInspectionDO> getTreeInspectionPage(TreeInspectionPageReqVO pageReqVO) {
  151 + return treeInspectionMapper.selectPage(pageReqVO);
  152 + }
  153 +
  154 + // ========== 权重因子规则辅助类 ==========
  155 +
  156 + private double getPlantingYearsWeight(String years) {
  157 + if (years == null) return 1.0;
  158 + if (years.contains("30年")) return 1.2;
  159 + if (years.contains("10-30")) return 1.1;
  160 + if (years.contains("10年")) return 1.0;
  161 + return 1.0;
  162 + }
  163 +
  164 + private double getTreePoolWeight(String type) {
  165 + if (type == null) return 1.0;
  166 + if (type.contains("硬化")) return 1.5;
  167 + if (type.contains("独立")) return 1.2;
  168 + if (type.contains("联通")) return 1.0;
  169 + return 1.0;
  170 + }
  171 +
  172 + private double getTreePoolRatioWeight(String ratio) {
  173 + if (ratio == null) return 1.0;
  174 + if (ratio.contains("3倍以下") || ratio.contains("3 倍以下") || ratio.contains("小于3")) {
  175 + return 1.3;
  176 + }
  177 + if (ratio.contains("3-5") || ratio.contains("3至5") || ratio.contains("3倍") || ratio.contains("3 倍")) {
  178 + return 1.2;
  179 + }
  180 + if (ratio.contains("5-7") || ratio.contains("5至7") || ratio.contains("5倍") || ratio.contains("5 倍")) {
  181 + return 1.1;
  182 + }
  183 + if (ratio.contains("7倍") || ratio.contains("7 倍") || ratio.contains("大于7")) {
  184 + return 1.0;
  185 + }
  186 + return 1.0;
  187 + }
  188 +
  189 + private double getWindPowerWeight(String power) {
  190 + if (power == null) return 1.0;
  191 + if (power.contains("10级以上") || power.contains("10 级以上")) return 3.0;
  192 + if (power.contains("10级") || power.contains("10 级")) return 2.0;
  193 + if (power.contains("8-9")) return 1.5;
  194 + if (power.contains("7级") || power.contains("7 级")) return 1.0;
  195 + return 1.0;
  196 + }
  197 +
  198 + private String getRiskLevelName(double score) {
  199 + if (score < 10.0) {
  200 + return "I级 (基本无风险)";
  201 + } else if (score < 30.0) {
  202 + return "II级 (轻度风险)";
  203 + } else if (score < 70.0) {
  204 + return "III级 (中度风险)";
  205 + } else if (score < 100.0) {
  206 + return "IV级 (重度风险)";
  207 + } else {
  208 + return "V级 (极度风险)";
  209 + }
  210 + }
  211 +
  212 +}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GisCoordinateUtil.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.util;
  2 +
  3 +import java.util.regex.Matcher;
  4 +import java.util.regex.Pattern;
  5 +
  6 +/**
  7 + * GIS 坐标转换工具 — WGS84 ↔ GCJ02(高德坐标系)
  8 + */
  9 +public class GisCoordinateUtil {
  10 +
  11 + private static final double PI = Math.PI;
  12 + private static final double A = 6378245.0; // 长半轴
  13 + private static final double EE = 0.00669342162296594323; // 偏心率平方
  14 +
  15 + /**
  16 + * WGS84 转 GCJ02(高德)
  17 + */
  18 + public static double[] wgs84ToGcj02(double lng, double lat) {
  19 + if (outOfChina(lng, lat)) {
  20 + return new double[]{lng, lat};
  21 + }
  22 + double dLat = transformLat(lng - 105.0, lat - 35.0);
  23 + double dLng = transformLng(lng - 105.0, lat - 35.0);
  24 + double radLat = lat / 180.0 * PI;
  25 + double magic = Math.sin(radLat);
  26 + magic = 1 - EE * magic * magic;
  27 + double sqrtMagic = Math.sqrt(magic);
  28 + dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI);
  29 + dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI);
  30 + return new double[]{lng + dLng, lat + dLat};
  31 + }
  32 +
  33 + /**
  34 + * GCJ02 转 WGS84
  35 + */
  36 + public static double[] gcj02ToWgs84(double lng, double lat) {
  37 + if (outOfChina(lng, lat)) {
  38 + return new double[]{lng, lat};
  39 + }
  40 + double[] gcj = wgs84ToGcj02(lng, lat);
  41 + return new double[]{lng * 2 - gcj[0], lat * 2 - gcj[1]};
  42 + }
  43 +
  44 + /** 匹配 WKT 中的坐标对:数字 空格 数字 */
  45 + private static final Pattern COORD_PAIR = Pattern.compile("(\\d+\\.?\\d*)\\s+(\\d+\\.?\\d*)");
  46 +
  47 + /**
  48 + * 转换 WKT 字符串中的所有坐标对(WGS84 → GCJ02)
  49 + * 支持 POLYGON、MULTIPOLYGON 等任意 WKT 格式
  50 + */
  51 + public static String transformPolygonWkt(String wkt) {
  52 + if (wkt == null || wkt.isBlank()) {
  53 + return wkt;
  54 + }
  55 + Matcher m = COORD_PAIR.matcher(wkt);
  56 + StringBuilder sb = new StringBuilder();
  57 + while (m.find()) {
  58 + double lng = Double.parseDouble(m.group(1));
  59 + double lat = Double.parseDouble(m.group(2));
  60 + double[] gcj = wgs84ToGcj02(lng, lat);
  61 + m.appendReplacement(sb, String.format("%.6f %.6f", gcj[0], gcj[1]));
  62 + }
  63 + m.appendTail(sb);
  64 + return sb.toString();
  65 + }
  66 +
  67 + private static boolean outOfChina(double lng, double lat) {
  68 + return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
  69 + }
  70 +
  71 + private static double transformLat(double x, double y) {
  72 + double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
  73 + ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
  74 + ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
  75 + ret += (160.0 * Math.sin(y / 12.0 * PI) + 320.0 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
  76 + return ret;
  77 + }
  78 +
  79 + private static double transformLng(double x, double y) {
  80 + double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
  81 + ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
  82 + ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
  83 + ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
  84 + return ret;
  85 + }
  86 +}
... ...
urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml
... ... @@ -587,4 +587,33 @@
587 587 INNER JOIN dept_recursive dr ON u.dept_id = dr.id
588 588 GROUP BY u.id, u.nickname, u.dept_id, u.busi_line, dr.companyId, dr.companyName;
589 589 </select>
  590 +
  591 + <!--app工单数量汇总-->
  592 + <select id="countWorkOrder" resultType="com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppWorkOrderSummaryVo">
  593 + SELECT
  594 + w.worker_company_id AS companyId,
  595 + d.name AS companyName,
  596 + COUNT(*) AS total,
  597 + SUM(CASE WHEN w.status = 2 THEN 1 ELSE 0 END) AS finish,
  598 + SUM(CASE WHEN w.status != 2 THEN 1 ELSE 0 END) AS ongoing
  599 + FROM system_dept d
  600 + LEFT JOIN workorder_main_info w ON w.worker_company_id = d.id
  601 + WHERE w.order_type !='Q'
  602 + <!-- queryType = 1 时,才拼接 督察工单(IWO开头)条件 -->
  603 + <if test='queryType != null and queryType == "1"'>
  604 + AND w.order_no LIKE 'IWO%'
  605 + </if>
  606 + <if test="userId != null">
  607 + AND w.user_id = #{userId}
  608 + </if>
  609 + <if test="beginTime != null and beginTime != ''">
  610 + AND w.create_time >= #{beginTime}
  611 + </if>
  612 + <if test="endTime != null and endTime != ''">
  613 + AND w.create_time &lt;= #{endTime}
  614 + </if>
  615 + GROUP BY w.worker_company_id, d.name
  616 + </select>
  617 +
  618 +
590 619 </mapper>
... ...
urbanops-module-garden/src/test/java/com/zteits/urbanops/module/garden/service/treeinspection/TreeInspectionServiceImplTest.java 0 → 100644
  1 +package com.zteits.urbanops.module.garden.service.treeinspection;
  2 +
  3 +import org.junit.jupiter.api.Test;
  4 +import org.mockito.InjectMocks;
  5 +import org.mockito.Mock;
  6 +import java.time.LocalDateTime;
  7 +import java.util.Collections;
  8 +
  9 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  10 +import com.zteits.urbanops.framework.test.core.ut.BaseMockitoUnitTest;
  11 +import com.zteits.urbanops.module.garden.dal.dataobject.treeinspection.TreeInspectionDO;
  12 +import com.zteits.urbanops.module.garden.dal.dataobject.tree.TreeDO;
  13 +import com.zteits.urbanops.module.garden.dal.mysql.treeinspection.TreeInspectionMapper;
  14 +import com.zteits.urbanops.module.garden.dal.mysql.tree.TreeMapper;
  15 +import com.zteits.urbanops.module.garden.controller.app.treeinspection.vo.*;
  16 +import com.zteits.urbanops.module.garden.enums.ErrorCodeConstants;
  17 +
  18 +import static com.zteits.urbanops.framework.test.core.util.AssertUtils.assertServiceException;
  19 +import static com.zteits.urbanops.framework.test.core.util.RandomUtils.randomLongId;
  20 +import static org.junit.jupiter.api.Assertions.*;
  21 +import static org.mockito.ArgumentMatchers.any;
  22 +import static org.mockito.Mockito.*;
  23 +
  24 +public class TreeInspectionServiceImplTest extends BaseMockitoUnitTest {
  25 +
  26 + @InjectMocks
  27 + private TreeInspectionServiceImpl treeInspectionService;
  28 +
  29 + @Mock
  30 + private TreeInspectionMapper treeInspectionMapper;
  31 +
  32 + @Mock
  33 + private TreeMapper treeMapper;
  34 +
  35 + private TreeInspectionSaveReqVO createReqVOBase() {
  36 + TreeInspectionSaveReqVO req = new TreeInspectionSaveReqVO();
  37 + req.setTreeId(1024L);
  38 + req.setInspectionTime(LocalDateTime.now());
  39 +
  40 + // 树根
  41 + TreeInspectionSaveReqVO.RootDTO root = new TreeInspectionSaveReqVO.RootDTO();
  42 + root.setDisease(0);
  43 + root.setAnchorage(0);
  44 + root.setCutting(0);
  45 + req.setRoot(root);
  46 +
  47 + // 根颈
  48 + TreeInspectionSaveReqVO.CollarDTO collar = new TreeInspectionSaveReqVO.CollarDTO();
  49 + collar.setWoodDamage(5); // 扣 5 分
  50 + collar.setBarkDamage(0);
  51 + collar.setLoosening(0);
  52 + req.setCollar(collar);
  53 +
  54 + // 主干
  55 + TreeInspectionSaveReqVO.TrunkDTO trunk = new TreeInspectionSaveReqVO.TrunkDTO();
  56 + trunk.setWoodDamage(0);
  57 + trunk.setTilt(3); // 扣 3 分
  58 + trunk.setBarkDamage(0);
  59 + req.setTrunk(trunk);
  60 +
  61 + // 树冠
  62 + TreeInspectionSaveReqVO.CrownDTO crown = new TreeInspectionSaveReqVO.CrownDTO();
  63 + crown.setLooseBranch(0);
  64 + crown.setCollarAbnormal(0);
  65 + crown.setVentilationBalance(1); // 扣 1 分
  66 + req.setCrown(crown);
  67 +
  68 + // 权重
  69 + TreeInspectionSaveReqVO.WeightDTO weight = new TreeInspectionSaveReqVO.WeightDTO();
  70 + weight.setTreeSpeciesType("深根性树种"); // 1.0
  71 + weight.setPlantingYears("栽植 10-30 年"); // 1.1
  72 + weight.setIsWindCorridor(false); // 1.0
  73 + weight.setTreePoolType("联通树池"); // 1.0
  74 + weight.setTreePoolWidthDbhRatio("5 倍(含)-7 倍(不含)"); // 1.1
  75 + req.setWeight(weight);
  76 +
  77 + // 风险评估结果 (展开应急评估)
  78 + TreeInspectionSaveReqVO.ResultDTO result = new TreeInspectionSaveReqVO.ResultDTO();
  79 + result.setIsEmergency(true);
  80 + result.setWindPower("8-9 级"); // 1.5
  81 + req.setResult(result);
  82 +
  83 + // 现状及建议
  84 + TreeInspectionSaveReqVO.StatusDTO status = new TreeInspectionSaveReqVO.StatusDTO();
  85 + status.setPhotos(Collections.singletonList("http://example.com/tree.jpg"));
  86 + req.setStatus(status);
  87 +
  88 + return req;
  89 + }
  90 +
  91 + @Test
  92 + public void testCreateTreeInspection_success_normal() {
  93 + // 准备参数
  94 + TreeInspectionSaveReqVO req = createReqVOBase();
  95 + // 模拟一树一档案数据
  96 + TreeDO tree = new TreeDO();
  97 + tree.setId(1024L);
  98 + tree.setTreenumber("D0001-P1-0001");
  99 + tree.setDeptId(200L);
  100 + when(treeMapper.selectById(eq(1024L))).thenReturn(tree);
  101 +
  102 + // 调用
  103 + Long resultId = treeInspectionService.createTreeInspection(req);
  104 +
  105 + // 验证数据库插入
  106 + verify(treeInspectionMapper, times(1)).insert(argThat((TreeInspectionDO entity) -> {
  107 + assertNotNull(entity);
  108 + assertEquals("D0001-P1-0001", entity.getTreenumber());
  109 + assertEquals(1024L, entity.getTreeId());
  110 + // 基础累加得分: collar.woodDamage (5) + trunk.tilt (3) + crown.ventilationBalance (1) = 9 分
  111 + assertEquals(9, entity.getDefectScore());
  112 + // 权重乘积: 9 * 1.0 (深根) * 1.1 (10-30年) * 1.0 (非风口) * 1.0 (联通树池) * 1.1 (5-7倍比) = 10.89 分
  113 + assertEquals(10.89, entity.getNormalScore());
  114 + assertEquals("II级 (轻度风险)", entity.getNormalLevel());
  115 + // 应急乘积 (isEmergency = true, windPower = 8-9级 即 1.5): 10.89 * 1.5 = 16.34 分
  116 + assertEquals(16.34, entity.getEmergencyScore());
  117 + assertEquals("II级 (轻度风险)", entity.getEmergencyLevel());
  118 + assertEquals(1, entity.getPhotos().size());
  119 + assertEquals("http://example.com/tree.jpg", entity.getPhotos().get(0));
  120 + return true;
  121 + }));
  122 + }
  123 +
  124 + @Test
  125 + public void testCreateTreeInspection_success_oneVoteVetoLoosening() {
  126 + // 根颈松动一票否决 -> 直接归为 V级极度风险
  127 + TreeInspectionSaveReqVO req = createReqVOBase();
  128 + req.getCollar().setLoosening(100); // 存在根颈松动
  129 +
  130 + TreeDO tree = new TreeDO();
  131 + tree.setId(1024L);
  132 + tree.setTreenumber("D0001-P1-0001");
  133 + when(treeMapper.selectById(eq(1024L))).thenReturn(tree);
  134 +
  135 + treeInspectionService.createTreeInspection(req);
  136 +
  137 + verify(treeInspectionMapper, times(1)).insert(argThat((TreeInspectionDO entity) -> {
  138 + assertEquals(100, entity.getDefectScore());
  139 + // 权重乘积: 100 * 1.0 * 1.1 * 1.0 * 1.0 * 1.1 = 121.0
  140 + assertEquals(121.0, entity.getNormalScore());
  141 + assertEquals("V级 (极度风险)", entity.getNormalLevel());
  142 + return true;
  143 + }));
  144 + }
  145 +
  146 + @Test
  147 + public void testCreateTreeInspection_success_oneVoteVetoTilt() {
  148 + // 主干倾斜>=30度一票否决 -> 缺陷分基准为 70,归为 IV级重度风险及以上
  149 + TreeInspectionSaveReqVO req = createReqVOBase();
  150 + req.getTrunk().setTilt(70); // 主干倾斜度>=30度
  151 +
  152 + TreeDO tree = new TreeDO();
  153 + tree.setId(1024L);
  154 + tree.setTreenumber("D0001-P1-0001");
  155 + when(treeMapper.selectById(eq(1024L))).thenReturn(tree);
  156 +
  157 + treeInspectionService.createTreeInspection(req);
  158 +
  159 + verify(treeInspectionMapper, times(1)).insert(argThat((TreeInspectionDO entity) -> {
  160 + // 70 (一票否决) + 5 (collar) + 1 (crown) = 76 分
  161 + assertEquals(76, entity.getDefectScore());
  162 + // 权重乘积: 76 * 1.0 * 1.1 * 1.0 * 1.0 * 1.1 = 91.96
  163 + assertEquals(91.96, entity.getNormalScore());
  164 + assertEquals("IV级 (重度风险)", entity.getNormalLevel());
  165 + return true;
  166 + }));
  167 + }
  168 +
  169 + @Test
  170 + public void testCreateTreeInspection_treeNotExists() {
  171 + TreeInspectionSaveReqVO req = createReqVOBase();
  172 + when(treeMapper.selectById(eq(1024L))).thenReturn(null);
  173 +
  174 + assertServiceException(() -> treeInspectionService.createTreeInspection(req),
  175 + ErrorCodeConstants.TREE_NOT_EXISTS);
  176 + }
  177 +
  178 + @Test
  179 + public void testGetTreeInspection_success() {
  180 + Long id = randomLongId();
  181 + LocalDateTime now = LocalDateTime.now();
  182 + TreeInspectionDO entity = new TreeInspectionDO();
  183 + entity.setId(id);
  184 + entity.setTreeId(1024L);
  185 + entity.setTreenumber("D0001-P1-0001");
  186 + entity.setInspectionTime(now);
  187 + entity.setRootDisease(0);
  188 + entity.setRootAnchorage(0);
  189 + entity.setRootCutting(0);
  190 + entity.setCollarWoodDamage(5);
  191 + entity.setCollarBarkDamage(0);
  192 + entity.setCollarLoosening(0);
  193 + entity.setTrunkWoodDamage(0);
  194 + entity.setTrunkTilt(3);
  195 + entity.setTrunkBarkDamage(0);
  196 + entity.setCrownLooseBranch(0);
  197 + entity.setCrownCollarAbnormal(0);
  198 + entity.setCrownVentilationBalance(1);
  199 + entity.setTreeSpeciesType("深根性树种");
  200 + entity.setTreeSpeciesWeight(1.0);
  201 + entity.setPlantingYears("栽植 10-30 年");
  202 + entity.setPlantingYearsWeight(1.1);
  203 + entity.setIsWindCorridor(false);
  204 + entity.setWindCorridorWeight(1.0);
  205 + entity.setTreePoolType("联通树池");
  206 + entity.setTreePoolWeight(1.0);
  207 + entity.setTreePoolWidthDbhRatio("5 倍(含)-7 倍(不含)");
  208 + entity.setTreePoolRatioWeight(1.1);
  209 + entity.setIsEmergency(true);
  210 + entity.setWindPower("8-9 级");
  211 + entity.setWindPowerWeight(1.5);
  212 + entity.setDefectScore(9);
  213 + entity.setNormalScore(10.89);
  214 + entity.setNormalLevel("II级 (轻度风险)");
  215 + entity.setEmergencyScore(16.34);
  216 + entity.setEmergencyLevel("II级 (轻度风险)");
  217 + entity.setPhotos(Collections.singletonList("http://example.com/tree.jpg"));
  218 +
  219 + when(treeInspectionMapper.selectById(eq(id))).thenReturn(entity);
  220 +
  221 + TreeInspectionRespVO resp = treeInspectionService.getTreeInspection(id);
  222 +
  223 + assertNotNull(resp);
  224 + assertEquals(id, resp.getId());
  225 + assertEquals("D0001-P1-0001", resp.getTreenumber());
  226 + assertEquals(now, resp.getInspectionTime());
  227 + assertEquals(5, resp.getCollar().getWoodDamage());
  228 + assertEquals(3, resp.getTrunk().getTilt());
  229 + assertEquals(1, resp.getCrown().getVentilationBalance());
  230 + assertEquals(1.1, resp.getWeight().getPlantingYearsWeight());
  231 + assertEquals(1.5, resp.getResult().getWindPowerWeight());
  232 + assertEquals(10.89, resp.getResult().getNormalResult().getScore());
  233 + assertEquals("II级 (轻度风险)", resp.getResult().getNormalResult().getLevel());
  234 + assertEquals(1, resp.getStatus().getPhotos().size());
  235 + }
  236 +
  237 + @Test
  238 + public void testGetTreeInspection_notExists() {
  239 + Long id = randomLongId();
  240 + when(treeInspectionMapper.selectById(eq(id))).thenReturn(null);
  241 +
  242 + assertServiceException(() -> treeInspectionService.getTreeInspection(id),
  243 + ErrorCodeConstants.TREE_INSPECTION_NOT_EXISTS);
  244 + }
  245 +
  246 +}
... ...
urbanops-module-report/src/main/java/com/zteits/urbanops/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java
... ... @@ -158,4 +158,33 @@ public class JmReportTokenServiceImpl implements JmReportTokenServiceI {
158 158 return StrUtil.toStringOrNull(loginUser.getTenantId());
159 159 }
160 160  
  161 + @Override
  162 + public String[] getPermissions(String token) {
  163 + // 设置租户上下文
  164 + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
  165 + if (loginUser == null) {
  166 + return null;
  167 + }
  168 + TenantContextHolder.setTenantId(loginUser.getTenantId());
  169 +
  170 + // 参见文档 https://help.jimureport.com/prodSafe/ 文档
  171 + // 适配:如果是本系统的管理员,则返回积木报表(仪表盘/大屏设计器)的所有权限指令
  172 + // 如果不处理,会碰到 https://t.zsxq.com/yzlkA 反馈的问题
  173 + Long userId = SecurityFrameworkUtils.getLoginUserId();
  174 + if (permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode())) {
  175 + return new String[]{
  176 + "drag:datasource:testConnection", // 数据库连接测试
  177 + "drag:datasource:saveOrUpate", // 数据源保存
  178 + "drag:datasource:delete", // 数据源删除
  179 + "drag:analysis:sql", // SQL解析
  180 + "drag:design:getTotalData", // 展示Online表单数据
  181 + "drag:dataset:save", // 数据集保存
  182 + "drag:dataset:delete", // 数据集删除
  183 + "onl:drag:clear:recovery", // 清空回收站
  184 + "onl:drag:page:delete" // 数据删除
  185 + };
  186 + }
  187 + return null;
  188 + }
  189 +
161 190 }
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/UserController.java
... ... @@ -18,11 +18,13 @@ import com.zteits.urbanops.module.system.service.dept.DeptService;
18 18 import com.zteits.urbanops.module.system.service.permission.PermissionService;
19 19 import com.zteits.urbanops.module.system.service.permission.RoleService;
20 20 import com.zteits.urbanops.module.system.service.user.AdminUserService;
  21 +import com.zteits.urbanops.module.system.util.dept.DeptCacheHelper;
21 22 import io.swagger.v3.oas.annotations.Operation;
22 23 import io.swagger.v3.oas.annotations.Parameter;
23 24 import io.swagger.v3.oas.annotations.Parameters;
24 25 import io.swagger.v3.oas.annotations.tags.Tag;
25 26 import jakarta.annotation.Resource;
  27 +import jakarta.annotation.security.PermitAll;
26 28 import jakarta.servlet.http.HttpServletResponse;
27 29 import jakarta.validation.Valid;
28 30 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -40,6 +42,7 @@ import java.util.stream.Collectors;
40 42 import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
41 43 import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
42 44 import static com.zteits.urbanops.framework.common.util.collection.CollectionUtils.convertList;
  45 +import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getDeptId;
43 46  
44 47 @Tag(name = "管理后台 - 用户")
45 48 @RestController
... ... @@ -213,11 +216,20 @@ public class UserController {
213 216 public void importTemplate(HttpServletResponse response) throws IOException {
214 217 // 手动创建导出 demo
215 218 List<UserImportExcelVO> list = Arrays.asList(
216   - UserImportExcelVO.builder().username("yunai").deptId(1L).email("yunai@iocoder.cn").mobile("15601691300")
217   - .nickname("全域").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
218   - UserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300")
219   - .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
  219 + UserImportExcelVO.builder()
  220 + .username("示例:zhangshan")
  221 + .nickname("张山")
  222 + .mobile("15601691300")
  223 + .sex(SexEnum.MALE.getSex())
  224 + .email("yunai@iocoder.cn")
  225 + .deptId(255L)
  226 + // .status(CommonStatusEnum.ENABLE.getStatus())
  227 + .busiLine("yl,sz") // 简洁示例
  228 + .isInner(1)
  229 + .build()
220 230 );
  231 + //初始化部门
  232 + DeptCacheHelper.init(deptService, getDeptId());
221 233 // 输出
222 234 ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list);
223 235 }
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/user/vo/user/UserImportExcelVO.java
... ... @@ -2,8 +2,12 @@ package com.zteits.urbanops.module.system.controller.admin.user.vo.user;
2 2  
3 3 import cn.idev.excel.annotation.ExcelProperty;
4 4 import com.zteits.urbanops.framework.excel.core.annotations.DictFormat;
  5 +import com.zteits.urbanops.framework.excel.core.annotations.ExcelColumnSelect;
5 6 import com.zteits.urbanops.framework.excel.core.convert.DictConvert;
6 7 import com.zteits.urbanops.module.system.enums.DictTypeConstants;
  8 +import com.zteits.urbanops.framework.excel.core.convert.BusinessLineConvert;
  9 +import com.zteits.urbanops.module.system.framework.excel.convert.DeptConvert;
  10 +import com.zteits.urbanops.module.system.framework.excel.core.DeptExcelColumnSelectFunction;
7 11 import lombok.AllArgsConstructor;
8 12 import lombok.Builder;
9 13 import lombok.Data;
... ... @@ -18,18 +22,12 @@ import lombok.NoArgsConstructor;
18 22 @NoArgsConstructor
19 23 public class UserImportExcelVO {
20 24  
21   - @ExcelProperty("登录名称")
  25 + @ExcelProperty("用户账户")
22 26 private String username;
23 27  
24 28 @ExcelProperty("用户名称")
25 29 private String nickname;
26 30  
27   - @ExcelProperty("部门编号")
28   - private Long deptId;
29   -
30   - @ExcelProperty("用户邮箱")
31   - private String email;
32   -
33 31 @ExcelProperty("手机号码")
34 32 private String mobile;
35 33  
... ... @@ -37,11 +35,24 @@ public class UserImportExcelVO {
37 35 @DictFormat(DictTypeConstants.USER_SEX)
38 36 private Integer sex;
39 37  
40   - @ExcelProperty(value = "账号状态", converter = DictConvert.class)
41   - @DictFormat(DictTypeConstants.COMMON_STATUS)
42   - private Integer status;
  38 + @ExcelProperty("邮箱")
  39 + private String email;
43 40  
44   - @ExcelProperty("业务线")
  41 + @ExcelProperty(value = "归属部门名称", converter = DeptConvert.class)
  42 + @ExcelColumnSelect(functionName = DeptExcelColumnSelectFunction.NAME)
  43 + private Long deptId;
  44 +
  45 + //@ExcelProperty(value = "账号状态", converter = DictConvert.class)
  46 + //@DictFormat(DictTypeConstants.COMMON_STATUS)
  47 + //private Integer status;
  48 +
  49 + @ExcelProperty(value = "业务线", converter = BusinessLineConvert.class)
45 50 private String busiLine;
46 51  
  52 + @ExcelProperty(value = "是否是内部员工", converter = DictConvert.class)
  53 + @DictFormat(DictTypeConstants.SYSTEM_IS_INNER)
  54 + private Integer isInner;
  55 +
  56 + @ExcelProperty(value = "备注")
  57 + private String remark;
47 58 }
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java
... ... @@ -30,6 +30,8 @@ public interface DictTypeConstants {
30 30  
31 31 String WORKORDER_TYPE = "work_order_type"; //工单类型
32 32  
  33 + String SYSTEM_IS_INNER = "system_is_inner";//是否是内部员工
  34 +
33 35  
34 36  
35 37  
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/common/CommonConstants.java 0 → 100644
  1 +package com.zteits.urbanops.module.system.enums.common;
  2 +
  3 +/**
  4 + * @Classname CommonConstants
  5 + * @Description 公共静态参数
  6 + * @Date 2026/1/3 10:15
  7 + * @Created by wangqian
  8 + */
  9 +public interface CommonConstants {
  10 +
  11 + /*全域督察员组长*/
  12 + public static final String INSPECTOR_ROLE_KEY = "Inspector_global_leader";
  13 +
  14 +}
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/convert/DeptConvert.java 0 → 100644
  1 +package com.zteits.urbanops.module.system.framework.excel.convert;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.idev.excel.converters.Converter;
  6 +import cn.idev.excel.enums.CellDataTypeEnum;
  7 +import cn.idev.excel.metadata.GlobalConfiguration;
  8 +import cn.idev.excel.metadata.data.ReadCellData;
  9 +import cn.idev.excel.metadata.data.WriteCellData;
  10 +import cn.idev.excel.metadata.property.ExcelContentProperty;
  11 +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO;
  12 +import com.zteits.urbanops.module.system.service.dept.DeptService;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +import java.util.stream.Collectors;
  18 +
  19 +/**
  20 + * 若依框架 部门Excel转换器(官方规范版)
  21 + * 全程走缓存,绝不重复查询数据库
  22 + *
  23 + * @author ruoyi
  24 + */
  25 +@Slf4j
  26 +public class DeptConvert implements Converter<Object> {
  27 +
  28 + /**
  29 + * 若依 部门Service(走系统缓存)
  30 + */
  31 + private static final DeptService deptService = SpringUtil.getBean(DeptService.class);
  32 +
  33 + /**
  34 + * 若依系统全局缓存:ID -> 部门名称
  35 + */
  36 + private static final Map<Long, String> DEPT_CACHE;
  37 +
  38 + /**
  39 + * 若依系统全局缓存:名称 -> 部门ID
  40 + */
  41 + private static final Map<String, Long> DEPT_NAME_CACHE;
  42 +
  43 + // ===================== 静态初始化:只加载一次 =====================
  44 + static {
  45 + // 从若依缓存获取所有部门(只查一次库,后面全走Redis)
  46 + List<DeptDO> deptList = deptService.getChildDeptList(0L);
  47 +
  48 + // ID -> 名称(导出用)
  49 + DEPT_CACHE = deptList.stream()
  50 + .collect(Collectors.toMap(DeptDO::getId, DeptDO::getName));
  51 +
  52 + // 名称 -> ID(导入用)
  53 + DEPT_NAME_CACHE = deptList.stream()
  54 + .collect(Collectors.toMap(DeptDO::getName, DeptDO::getId));
  55 +
  56 + log.info("【若依部门Excel转换器】初始化缓存成功,共 {} 个部门", deptList.size());
  57 + }
  58 +
  59 + @Override
  60 + public Class<?> supportJavaTypeKey() {
  61 + throw new UnsupportedOperationException("暂不支持,也不需要");
  62 + }
  63 +
  64 + @Override
  65 + public CellDataTypeEnum supportExcelTypeKey() {
  66 + throw new UnsupportedOperationException("暂不支持,也不需要");
  67 + }
  68 +
  69 + // ===================== 导入:Excel部门名称 → ID =====================
  70 + @Override
  71 + public Object convertToJavaData(ReadCellData<?> readCellData, ExcelContentProperty contentProperty,
  72 + GlobalConfiguration globalConfiguration) {
  73 + String deptName = readCellData.getStringValue();
  74 +
  75 + // 从缓存获取(0 查库)
  76 + Long deptId = DEPT_NAME_CACHE.get(deptName);
  77 + if (deptId == null) {
  78 + log.error("[convertToJavaData][部门名称({}) 不存在]", deptName);
  79 + return null;
  80 + }
  81 +
  82 + // 类型转换
  83 + Class<?> fieldClazz = contentProperty.getField().getType();
  84 + return Convert.convert(fieldClazz, deptId);
  85 + }
  86 +
  87 + // ===================== 导出:ID → 部门名称 =====================
  88 + @Override
  89 + public WriteCellData<?> convertToExcelData(Object value, ExcelContentProperty contentProperty,
  90 + GlobalConfiguration globalConfiguration) {
  91 + if (value == null) {
  92 + return new WriteCellData<>("");
  93 + }
  94 +
  95 + Long deptId = Convert.toLong(value);
  96 + // 从缓存获取名称(0 查库)
  97 + String deptName = DEPT_CACHE.get(deptId);
  98 +
  99 + if (deptName == null) {
  100 + log.error("[convertToExcelData][部门ID({}) 不存在]", deptId);
  101 + return new WriteCellData<>("");
  102 + }
  103 +
  104 + return new WriteCellData<>(deptName);
  105 + }
  106 +}
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/framework/excel/core/DeptExcelColumnSelectFunction.java 0 → 100644
  1 +package com.zteits.urbanops.module.system.framework.excel.core;
  2 +
  3 +import com.zteits.urbanops.framework.excel.core.function.ExcelColumnSelectFunction;
  4 +import com.zteits.urbanops.module.system.util.dept.DeptCacheHelper;
  5 +import org.springframework.stereotype.Service;
  6 +import java.util.List;
  7 +
  8 +@Service
  9 +public class DeptExcelColumnSelectFunction implements ExcelColumnSelectFunction {
  10 +
  11 + public static final String NAME = "deptNameList";
  12 +
  13 + @Override
  14 + public String getName() {
  15 + return NAME;
  16 + }
  17 +
  18 + /**
  19 + * 从缓存获取部门名称列表,作为Excel下拉选项
  20 + */
  21 + @Override
  22 + public List<String> getOptions() {
  23 + return DeptCacheHelper.getNameList(); // 直接走缓存!
  24 + }
  25 +
  26 +}
... ...
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/util/dept/DeptCacheHelper.java 0 → 100644
  1 +package com.zteits.urbanops.module.system.util.dept;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO;
  5 +import com.zteits.urbanops.module.system.service.dept.DeptService;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import java.util.*;
  8 +import java.util.function.Function;
  9 +import java.util.stream.Collectors;
  10 +
  11 +/**
  12 + * 部门全局缓存工具
  13 + * 给 Excel 导入导出、下拉框、转换器统一使用
  14 + * 只加载一次,不重复查询
  15 + */
  16 +@Slf4j
  17 +public class DeptCacheHelper {
  18 +
  19 + private static List<DeptDO> DEPT_LIST;
  20 + private static Map<Long, DeptDO> DEPT_ID_MAP;
  21 + private static Map<String, DeptDO> DEPT_NAME_MAP;
  22 + private static List<String> DEPT_NAME_LIST;
  23 +
  24 + /**
  25 + * 初始化部门缓存(在导入模板接口中主动调用)
  26 + */
  27 + public static void init(DeptService deptService, Long rootDeptId) {
  28 +
  29 + //每次调用都使用传入的 deptId,每次可刷新
  30 + if (DEPT_LIST != null) {
  31 + // 清空旧缓存
  32 + DEPT_LIST = null;
  33 + DEPT_ID_MAP = null;
  34 + DEPT_NAME_MAP = null;
  35 + DEPT_NAME_LIST = null;
  36 + }
  37 +
  38 + try {
  39 +
  40 + // 1. 批量查询部门(只查一次)
  41 + DEPT_LIST = deptService.getLastDeptList(rootDeptId);
  42 +
  43 + if (CollUtil.isEmpty(DEPT_LIST)) {
  44 + log.warn("[DeptCacheHelper] 未查询到部门数据");
  45 + return;
  46 + }
  47 +
  48 + // 3. 构建各种缓存
  49 + DEPT_ID_MAP = DEPT_LIST.stream()
  50 + .collect(Collectors.toMap(DeptDO::getId, Function.identity()));
  51 +
  52 + DEPT_NAME_MAP = DEPT_LIST.stream()
  53 + .collect(Collectors.toMap(DeptDO::getName, Function.identity()));
  54 +
  55 + DEPT_NAME_LIST = DEPT_LIST.stream()
  56 + .map(DeptDO::getName)
  57 + .collect(Collectors.toList());
  58 +
  59 + log.info("[DeptCacheHelper] 部门缓存初始化成功,共 {} 个部门", DEPT_LIST.size());
  60 + } catch (Exception e) {
  61 + log.error("[DeptCacheHelper] 部门缓存初始化失败", e);
  62 + }
  63 + }
  64 +
  65 + // ===================== 提供给外部使用 =====================
  66 + public static DeptDO getById(Long id) {
  67 + return DEPT_ID_MAP == null ? null : DEPT_ID_MAP.get(id);
  68 + }
  69 +
  70 + public static DeptDO getByName(String name) {
  71 + return DEPT_NAME_MAP == null ? null : DEPT_NAME_MAP.get(name);
  72 + }
  73 +
  74 + public static List<String> getNameList() {
  75 + return DEPT_NAME_LIST == null ? Collections.emptyList() : DEPT_NAME_LIST;
  76 + }
  77 +
  78 + public static boolean isEmpty() {
  79 + return CollUtil.isEmpty(DEPT_LIST);
  80 + }
  81 +}
... ...
urbanops-module-system/src/test/java/com/zteits/urbanops/module/system/service/user/AdminUserServiceImplTest.java
... ... @@ -482,7 +482,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
482 482 public void testImportUserList_02() {
483 483 // 准备参数
484 484 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {
485   - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  485 + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
486 486 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围
487 487 o.setEmail(randomEmail());
488 488 o.setMobile(randomMobile());
... ... @@ -517,7 +517,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
517 517 userMapper.insert(dbUser);
518 518 // 准备参数
519 519 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {
520   - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  520 + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
521 521 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围
522 522 o.setUsername(dbUser.getUsername());
523 523 o.setEmail(randomEmail());
... ... @@ -549,7 +549,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
549 549 userMapper.insert(dbUser);
550 550 // 准备参数
551 551 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {
552   - o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
  552 + //o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
553 553 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围
554 554 o.setUsername(dbUser.getUsername());
555 555 o.setEmail(randomEmail());
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/api/constant/BpmCommonConstant.java
... ... @@ -175,6 +175,11 @@ public class BpmCommonConstant {
175 175 * 督察员 角色编码
176 176 */
177 177 public static final String INSPECTOR_GLOBAL = "Inspector_global";
  178 +
  179 + /**
  180 + * 督察员组长 角色编码
  181 + */
  182 + public static final String INSPECTOR_GLOBAL_LEADER = "Inspector_global_leader";
178 183 /**
179 184 * ai 工单派发人
180 185 */
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/admin/maininfo/vo/MainInfoPageReqVO.java
... ... @@ -130,4 +130,5 @@ public class MainInfoPageReqVO extends PageParam {
130 130 @Schema(description = "部门id")
131 131 private Set<Long> deptIds;
132 132  
  133 +
133 134 }
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/AppGardenWorkOrderInspectorController.java
... ... @@ -6,7 +6,6 @@ import com.zteits.urbanops.framework.idempotent.core.annotation.Idempotent;
6 6 import com.zteits.urbanops.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;
7 7 import com.zteits.urbanops.module.garden.api.road.RoadApi;
8 8 import com.zteits.urbanops.module.workorder.annotation.AutoUpdateWorkerOrderInspectorBuzStatus;
9   -import com.zteits.urbanops.module.workorder.annotation.AutoUpdateWorkerOrderUniversalBuzStatus;
10 9 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.common.AppUniversalApprovalReqVO;
11 10 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.operator.AppGardenTaskReturnExtReqVO;
12 11 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.operator.AppGardenTaskTeamLeaderAssignReqVO;
... ... @@ -16,12 +15,9 @@ import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGar
16 15 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskRegionMgrApproveReqVO;
17 16 import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderInspectorReqVO;
18 17 import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderReqVO;
19   -import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderUniversalReqVO;
20   -import com.zteits.urbanops.module.workorder.enums.InspectorOperateTypeEnum;
21 18 import com.zteits.urbanops.module.workorder.enums.InspectorOperateTypeEnum;
22 19 import com.zteits.urbanops.module.workorder.service.garden.BpmGardenService;
23 20 import com.zteits.urbanops.module.workorder.service.garden.BpmInspectorService;
24   -import com.zteits.urbanops.module.workorder.service.garden.BpmUniversalService;
25 21 import io.swagger.v3.oas.annotations.Operation;
26 22 import io.swagger.v3.oas.annotations.tags.Tag;
27 23 import jakarta.annotation.Resource;
... ... @@ -76,6 +72,15 @@ public class AppGardenWorkOrderInspectorController {
76 72 return success(inspectorService.createWorkOrder(createReqVO));
77 73 }
78 74  
  75 + @PostMapping("/createBatch")
  76 + //@PreAuthorize("@ss.hasPermission('bpm:garden-workorder:create')")
  77 + @Operation(summary = "app-全域巡查员创建工单信息")
  78 + public CommonResult<List<Long>> createMainInfoBatch(@Valid @RequestBody List<AppGardenWorkOrderInspectorReqVO> createReqVOList) {
  79 + return success(inspectorService.batchCreateWorkOrder(createReqVOList));
  80 + }
  81 +
  82 +
  83 +
79 84 @Operation(summary = "app-统一审批入口")
80 85 @PostMapping("/universalApproval")
81 86 @AutoUpdateWorkerOrderInspectorBuzStatus
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/aop/InspectorBuzStatusUpdateAspect.java
... ... @@ -96,6 +96,14 @@ public class InspectorBuzStatusUpdateAspect {
96 96 targeVO.setId(workerDataId);
97 97 targeVO.setOrderNo(dbData.getOrderNo());
98 98 targeVO.setBusiLine(dbData.getBusiLine());
  99 + //三级级编码
  100 + if(StringUtil.isNotEmpty(reqVO.getOrderCode())){
  101 + targeVO.setOrderCode(reqVO.getOrderCode());
  102 + }
  103 + //业务类型(一级编码)
  104 + if(StringUtil.isNotEmpty(reqVO.getBusiType())){
  105 + targeVO.setBusiType(reqVO.getBusiType());
  106 + }
99 107 //大区经理分配
100 108 if(InspectorOperateTypeEnum.SH_REGION_MANAGER_ASSIGN.getCode().equals(reqVO.getOperateType())){
101 109 RoadStreetRespDTO roadStreetRespDTO = roadApi.getRoadInfoById(reqVO.getRoadId());
... ... @@ -163,6 +171,7 @@ public class InspectorBuzStatusUpdateAspect {
163 171 busiLine = dbData.getBusiLine();
164 172 }
165 173 targeVO.setBusiLine(busiLine);
  174 +
166 175 targeVO.setUserId(SecurityFrameworkUtils.getLoginUserId());
167 176 targeVO.setUserName(SecurityFrameworkUtils.getLoginUserNickname());
168 177 if(ObjectUtils.isNotAllEmpty(reqVO.getExpectedFinishDate())){
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/garden/vo/common/AppUniversalApprovalReqVO.java
... ... @@ -123,6 +123,12 @@ public class AppUniversalApprovalReqVO {
123 123 @Schema(description = "工单名称", example = "绿地卫生")
124 124 private String orderName;
125 125  
  126 + @Schema(description = "三级编码")
  127 + private String orderCode;
  128 +
  129 + @Schema(description = "业务类型(一级编码)")
  130 + private String busiType;
  131 +
126 132 @Schema(description = "来源ID 工单来源 1、巡查,2、游客居民,3、12345,4、网格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
127 133 //@NotNull(message = "来源ID不能为空")
128 134 private Integer sourceId;
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/maininfo/AppMainInfoController.java 0 → 100644
  1 +package com.zteits.urbanops.module.workorder.controller.app.maininfo;
  2 +
  3 +import com.zteits.urbanops.framework.apilog.core.annotation.ApiAccessLog;
  4 +import com.zteits.urbanops.framework.common.pojo.CommonResult;
  5 +import com.zteits.urbanops.framework.common.pojo.PageParam;
  6 +import com.zteits.urbanops.framework.common.pojo.PageResult;
  7 +import com.zteits.urbanops.framework.common.util.object.BeanUtils;
  8 +import com.zteits.urbanops.framework.excel.core.util.ExcelUtils;
  9 +import com.zteits.urbanops.module.system.api.user.AdminUserApi;
  10 +import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO;
  11 +import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO;
  12 +import com.zteits.urbanops.module.system.service.dept.DeptService;
  13 +import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.AdminMainInfoRespVO;
  14 +import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.MainInfoPageReqVO;
  15 +import com.zteits.urbanops.module.workorder.controller.app.maininfo.vo.AppMainInfoPageReqVO;
  16 +import com.zteits.urbanops.module.workorder.controller.app.maininfo.vo.AppMainInfoRespVO;
  17 +import com.zteits.urbanops.module.workorder.convert.maininfo.MainInfoConvert;
  18 +import com.zteits.urbanops.module.workorder.dal.dataobject.attachment.AttachmentDO;
  19 +import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
  20 +import com.zteits.urbanops.module.workorder.dto.MainInfoRespVO;
  21 +import com.zteits.urbanops.module.workorder.service.attachment.AttachmentService;
  22 +import com.zteits.urbanops.module.workorder.service.maininfo.MainInfoService;
  23 +import io.swagger.v3.oas.annotations.Operation;
  24 +import io.swagger.v3.oas.annotations.Parameter;
  25 +import io.swagger.v3.oas.annotations.tags.Tag;
  26 +import jakarta.annotation.Resource;
  27 +import jakarta.servlet.http.HttpServletResponse;
  28 +import jakarta.validation.Valid;
  29 +import org.apache.commons.collections4.CollectionUtils;
  30 +import org.springframework.security.access.prepost.PreAuthorize;
  31 +import org.springframework.validation.annotation.Validated;
  32 +import org.springframework.web.bind.annotation.GetMapping;
  33 +import org.springframework.web.bind.annotation.RequestMapping;
  34 +import org.springframework.web.bind.annotation.RequestParam;
  35 +import org.springframework.web.bind.annotation.RestController;
  36 +
  37 +import java.io.IOException;
  38 +import java.util.*;
  39 +import java.util.stream.Collectors;
  40 +
  41 +import static com.zteits.urbanops.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
  42 +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  43 +
  44 +@Tag(name = "app - 工单信息")
  45 +@RestController
  46 +@RequestMapping("/workorder/main-info")
  47 +@Validated
  48 +public class AppMainInfoController {
  49 +
  50 + @Resource
  51 + private MainInfoService mainInfoService;
  52 +
  53 + @Resource
  54 + private AttachmentService attachmentService;
  55 +
  56 + @Resource
  57 + private DeptService deptService;
  58 + @Resource
  59 + private AdminUserApi adminUserApi;
  60 +
  61 + @GetMapping("/get")
  62 + @Operation(summary = "获得工单信息")
  63 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  64 + public CommonResult<AppMainInfoRespVO> getMainInfo(@RequestParam("id") Long id) {
  65 + MainInfoDO mainInfo = mainInfoService.getMainInfo(id);
  66 + // 若订单号列表非空,查询附件;否则跳过
  67 + Map<String, List<String>> attachmentMap = Collections.emptyMap();
  68 + if (mainInfo != null) {
  69 + List<AttachmentDO> attachmentDOList = attachmentService.getAttachmentByOrderNo(Arrays.asList(mainInfo.getOrderNo()));
  70 + if (!CollectionUtils.isEmpty(attachmentDOList)) {
  71 + attachmentMap = attachmentDOList.stream()
  72 + .collect(Collectors.groupingBy(
  73 + AttachmentDO::getBusiType,
  74 + Collectors.mapping(
  75 + AttachmentDO::getFileNames,
  76 + Collectors.toList()
  77 + )
  78 + ));
  79 + }
  80 + }
  81 +
  82 + String companyName = "";
  83 + if (mainInfo.getWorkerCompanyId() != null) {
  84 + DeptDO dept = deptService.getDept(mainInfo.getWorkerCompanyId());
  85 + if (dept != null) {
  86 + companyName = dept.getName();
  87 + }
  88 + }
  89 +
  90 + Set<Long> userIds = new HashSet<>();
  91 + if (mainInfo.getCoHandlers() != null) {
  92 + mainInfo.getCoHandlers().stream()
  93 + .filter(Objects::nonNull) // 过滤null ID
  94 + .forEach(userIds::add);
  95 + }
  96 + userIds.add(mainInfo.getUserId());
  97 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  98 + return success(MainInfoConvert.INSTANCE.appBuildMainInfo(mainInfo, attachmentMap, companyName, userMap));
  99 + }
  100 +
  101 + @GetMapping("/page")
  102 + @Operation(summary = "获得工单信息分页")
  103 + public CommonResult<PageResult<AdminMainInfoRespVO>> getMainInfoPage(@Valid AppMainInfoPageReqVO pageReqVO) {
  104 +
  105 + PageResult<MainInfoDO> pageResult = mainInfoService.selectPageExcludeQuickOrder(pageReqVO);
  106 + // 6. 转换并返回结果
  107 + return success(MainInfoConvert.INSTANCE.buildMainInfoPage(pageResult, null, null, null));
  108 + }
  109 +
  110 + @GetMapping("/export-excel")
  111 + @Operation(summary = "导出工单信息 Excel")
  112 + @ApiAccessLog(operateType = EXPORT)
  113 + public void exportMainInfoExcel(@Valid MainInfoPageReqVO pageReqVO,
  114 + HttpServletResponse response) throws IOException {
  115 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
  116 + List<MainInfoDO> list = mainInfoService.getMainInfoPage(pageReqVO).getList();
  117 + // 导出 Excel
  118 + ExcelUtils.write(response, "工单信息.xls", "数据", MainInfoRespVO.class,
  119 + BeanUtils.toBean(list, MainInfoRespVO.class));
  120 + }
  121 +
  122 +}
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/maininfo/vo/AppMainInfoPageReqVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.workorder.controller.app.maininfo.vo;
  2 +
  3 +import com.zteits.urbanops.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotEmpty;
  6 +import lombok.Data;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDate;
  10 +import java.time.LocalDateTime;
  11 +import java.time.LocalTime;
  12 +
  13 +import static com.zteits.urbanops.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
  14 +
  15 +@Schema(description = "APP - 工单信息分页 Request VO")
  16 +@Data
  17 +public class AppMainInfoPageReqVO extends PageParam {
  18 +
  19 + @NotEmpty
  20 + @Schema(description = "查询类型", example = "0:全部工单 1:全域工单")
  21 + public String queryType;
  22 +
  23 + @Schema(description = "搜索类型:1 位置 2 工单名称 3 情况描述 4 工单编号", example = "1")
  24 + private String type;
  25 +
  26 + @Schema(description = "搜索输入内容", example = "祭旁")
  27 + private String searchContent;
  28 +
  29 + @Schema(description = "工单号")
  30 + private String orderNo;
  31 +
  32 + @Schema(description = "工单名称", example = "张三")
  33 + private String orderName;
  34 +
  35 + @Schema(description = "审批状态", example = "2")
  36 + private Integer status;
  37 +
  38 + @Schema(description = "经纬度地址")
  39 + private String lonLatAddress;
  40 +
  41 + @Schema(description = "工单描述", example = "你说的对")
  42 + private String remark;
  43 + /**
  44 + * 时间数组:[开始时间, 结束时间]
  45 + * 由 beginTime 和 endTime 自动组装
  46 + */
  47 + @Schema(description = "创建时间-范围查询", hidden = true)
  48 + private LocalDateTime[] createTime;
  49 +
  50 + @Schema(description = "归属单位", example = "12")
  51 + private Long workerCompanyId;
  52 +
  53 + @Schema(description = "实施人员部门id", example = "12")
  54 + private Long companyId;
  55 +
  56 + @Schema(description = "开始日期(yyyy-MM-dd)", example = "2025-01-01")
  57 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
  58 + private String beginTime;
  59 +
  60 + @Schema(description = "结束日期(yyyy-MM-dd)", example = "2025-12-10")
  61 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
  62 + private String endTime;
  63 +
  64 + @Schema(description = "用户id", example = "12" , hidden = true)
  65 + private Long userId;
  66 +
  67 + /**
  68 + * 组装时间:beginTime + endTime → createTime
  69 + */
  70 + public LocalDateTime[] getCreateTime() {
  71 + // 已经手动设置过,直接返回
  72 + if (this.createTime != null) {
  73 + return this.createTime;
  74 + }
  75 + // 没有传时间,直接返回 null
  76 + if (beginTime == null && endTime == null) {
  77 + return null;
  78 + }
  79 +
  80 + LocalDateTime startTime = null;
  81 + LocalDateTime endTimeDate = null;
  82 +
  83 + // 开始时间:yyyy-MM-dd 00:00:00
  84 + if (beginTime != null && !beginTime.isEmpty()) {
  85 + startTime = LocalDate.parse(beginTime).atTime(LocalTime.MIN);
  86 + }
  87 + // 结束时间:yyyy-MM-dd 23:59:59
  88 + if (endTime != null && !endTime.isEmpty()) {
  89 + endTimeDate = LocalDate.parse(endTime).atTime(LocalTime.MAX);
  90 + }
  91 +
  92 + return new LocalDateTime[]{startTime, endTimeDate};
  93 + }
  94 +
  95 + // 防止覆盖
  96 + public void setCreateTime(LocalDateTime[] createTime) {
  97 + this.createTime = createTime;
  98 + }
  99 +}
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/controller/app/maininfo/vo/AppMainInfoRespVO.java 0 → 100644
  1 +package com.zteits.urbanops.module.workorder.controller.app.maininfo.vo;
  2 +
  3 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  4 +import cn.idev.excel.annotation.ExcelProperty;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +import java.util.Set;
  12 +
  13 +@Schema(description = "管理后台 - 工单信息 Response VO")
  14 +@Data
  15 +@ExcelIgnoreUnannotated
  16 +public class AppMainInfoRespVO {
  17 +
  18 + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "7114")
  19 + @ExcelProperty("ID")
  20 + private Long id;
  21 +
  22 + @Schema(description = "业务线:yl 园林,wy 物业,sz 市政 ", requiredMode = Schema.RequiredMode.REQUIRED)
  23 + @ExcelProperty("业务线:yl 园林,wy 物业,sz 市政 ")
  24 + private String busiLine;
  25 +
  26 + @Schema(description = "工单号", requiredMode = Schema.RequiredMode.REQUIRED)
  27 + @ExcelProperty("工单号")
  28 + private String orderNo;
  29 +
  30 + @Schema(description = "工单名称", example = "张三")
  31 + @ExcelProperty("工单名称")
  32 + private String orderName;
  33 +
  34 + /**
  35 + * 工单类型 Q:快速工单,C:普通工单,O:其他工单
  36 + */
  37 + private String orderType;
  38 +
  39 + @Schema(description = "来源ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1828")
  40 + @ExcelProperty("来源ID 问题来源 1、巡查,2、游客居民,3、12345,4、网格 5、大区经理")
  41 + private Integer sourceId;
  42 +
  43 + @Schema(description = "来源名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  44 + @ExcelProperty("来源名称")
  45 + private String sourceName;
  46 +
  47 + @Schema(description = "道路ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21128")
  48 + @ExcelProperty("道路ID")
  49 + private Long roadId;
  50 +
  51 + @Schema(description = "道路名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  52 + @ExcelProperty("道路名称")
  53 + private String roadName;
  54 +
  55 + @Schema(description = "街道ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "27852")
  56 + @ExcelProperty("街道ID")
  57 + private String streetId;
  58 +
  59 + @Schema(description = "街道名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  60 + @ExcelProperty("街道名称")
  61 + private String streetName;
  62 +
  63 + @Schema(description = "养护级别ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28278")
  64 + @ExcelProperty("养护级别ID")
  65 + private Integer curingLevelId;
  66 +
  67 + @Schema(description = "养护级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  68 + @ExcelProperty("养护级别")
  69 + private String curingLevelName;
  70 +
  71 + @Schema(description = "提交日期", requiredMode = Schema.RequiredMode.REQUIRED)
  72 + @ExcelProperty("提交日期")
  73 + private LocalDateTime commitDate;
  74 +
  75 + @Schema(description = "希望完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
  76 + @ExcelProperty("希望完成时间")
  77 + private LocalDateTime expectedFinishDate;
  78 +
  79 + @Schema(description = "完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
  80 + @ExcelProperty("完成时间")
  81 + private LocalDateTime finishDate;
  82 +
  83 + @Schema(description = "紧急程度:1:特急;2:紧急;3:一般", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  84 + @ExcelProperty("紧急程度:1:特急;2:紧急;3:一般")
  85 + private Integer pressingType;
  86 +
  87 + @Schema(description = "提交用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "12757")
  88 + private Long userId;
  89 +
  90 + @Schema(description = "提交用户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  91 + @ExcelProperty("提交用户名称")
  92 + private String userName;
  93 +
  94 + @Schema(description = "部门id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26936")
  95 + private Long companyId;
  96 +
  97 + @Schema(description = "单位名称")
  98 + @ExcelProperty("单位名称")
  99 + private String companyName;
  100 +
  101 + @Schema(description = "经纬度类型: 1:国标; 2:百度;3:高德;4:腾讯", example = "2")
  102 + @ExcelProperty("经纬度类型: 1:国标; 2:百度;3:高德;4:腾讯")
  103 + private Integer latLonType;
  104 +
  105 + @Schema(description = "经度", requiredMode = Schema.RequiredMode.REQUIRED)
  106 + @ExcelProperty("经度")
  107 + private BigDecimal lat;
  108 +
  109 + @Schema(description = "维度", requiredMode = Schema.RequiredMode.REQUIRED)
  110 + @ExcelProperty("维度")
  111 + private BigDecimal lon;
  112 +
  113 + @Schema(description = "经纬度地址", requiredMode = Schema.RequiredMode.REQUIRED)
  114 + @ExcelProperty("经纬度地址")
  115 + private String lonLatAddress;
  116 +
  117 + @Schema(description = "三方工单ID")
  118 + @ExcelProperty("三方工单ID")
  119 + private String thirdWorkNo;
  120 +
  121 + @Schema(description = "三方工单结果上报状态:1:待推送;2:推送成功;3:推送失败")
  122 + @ExcelProperty("三方工单结果上报状态:1:待推送;2:推送成功;3:推送失败")
  123 + private Integer thirdPushState;
  124 +
  125 + @Schema(description = "业务状态", example = "1")
  126 + @ExcelProperty("业务状态")
  127 + private String buzStatus;
  128 +
  129 + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  130 + @ExcelProperty("审批状态")
  131 + private Integer status;
  132 +
  133 + @Schema(description = "流程实例的编号", example = "9184")
  134 + @ExcelProperty("流程实例的编号")
  135 + private String processInstanceId;
  136 +
  137 + @Schema(description = "工单描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
  138 + @ExcelProperty("工单描述")
  139 + private String remark;
  140 +
  141 + @Schema(description = "工单完成结果描述", example = "已完成")
  142 + @ExcelProperty("工单完成结果描述")
  143 + private String handleResult;
  144 +
  145 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  146 + @ExcelProperty("创建时间")
  147 + private LocalDateTime createTime;
  148 +
  149 + @Schema(description = "文件名称")
  150 + private List<String> fileNames;
  151 +
  152 + @Schema(description = "共同处理人")
  153 + private Set<Long> coHandlers;
  154 + @Schema(description = "共同处理人名称,多个为数组")
  155 + private List<String> coHandlersName;
  156 +
  157 + @Schema(description = "提交人电话")
  158 + private String userMobile;
  159 +
  160 + @Schema(description = "养护员")
  161 + private String workerName;
  162 +
  163 + @Schema(description = "实施人员部门id")
  164 + private Long workerCompanyId;
  165 +
  166 + @Schema(description = "处理人")
  167 + private String assigneeName;
  168 +
  169 + @Schema(description = "审批节点")
  170 + private String approvalNode;
  171 +
  172 + @Schema(description = "问题图片")
  173 + private List<String> problemsImgs;
  174 + @Schema(description = "开始图片")
  175 + private List<String> startImgs;
  176 + @Schema(description = "处理中图片")
  177 + private List<String> processingImgs;
  178 + @Schema(description = "结束图片")
  179 + private List<String> endImgs;
  180 + @Schema(description = "人员图片")
  181 + private List<String> personImgs;
  182 + @Schema(description = "物料图片")
  183 + private List<String> materialImgs;
  184 +}
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/convert/maininfo/MainInfoConvert.java
... ... @@ -6,6 +6,7 @@ import com.zteits.urbanops.framework.common.util.object.BeanUtils;
6 6 import com.zteits.urbanops.module.bpm.api.task.dto.BpmUserSimpleReqDTO;
7 7 import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO;
8 8 import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.AdminMainInfoRespVO;
  9 +import com.zteits.urbanops.module.workorder.controller.app.maininfo.vo.AppMainInfoRespVO;
9 10 import com.zteits.urbanops.module.workorder.dal.dataobject.attachment.AttachmentDO;
10 11 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
11 12 import org.mapstruct.Mapper;
... ... @@ -90,4 +91,36 @@ public interface MainInfoConvert {
90 91 }
91 92 return mainInfoVo;
92 93 }
  94 +
  95 + default AppMainInfoRespVO appBuildMainInfo(MainInfoDO mainInfoDO, Map<String, List<String>> attachmentMap, String deptName, Map<Long, AdminUserRespDTO> userMap) {
  96 + AppMainInfoRespVO mainInfoVo = BeanUtils.toBean(mainInfoDO, AppMainInfoRespVO.class);
  97 + if (mainInfoDO != null) {
  98 + if (attachmentMap != null && !attachmentMap.isEmpty()){
  99 + //01 问题图片,02 开始图片,03 进行中图片,04 已完成图片,05 人员图片:06 材料图片
  100 + mainInfoVo.setProblemsImgs(attachmentMap.getOrDefault("01", List.of()));
  101 + mainInfoVo.setStartImgs(attachmentMap.getOrDefault("02", List.of()));
  102 + mainInfoVo.setProcessingImgs(attachmentMap.getOrDefault("03", List.of()));
  103 + mainInfoVo.setEndImgs(attachmentMap.getOrDefault("04", List.of()));
  104 + mainInfoVo.setPersonImgs(attachmentMap.getOrDefault("05", List.of()));
  105 + mainInfoVo.setMaterialImgs(attachmentMap.getOrDefault("06", List.of()));
  106 + }
  107 + mainInfoVo.setCompanyName(deptName);
  108 + if (mainInfoVo.getCoHandlers() != null && !mainInfoVo.getCoHandlers().isEmpty()) {
  109 + List<String> coHandlersNameList = Optional.ofNullable(mainInfoVo.getCoHandlers())
  110 + .orElse(Set.of())
  111 + .stream()
  112 + .filter(Objects::nonNull)
  113 + .map(userMap::get)
  114 + .filter(Objects::nonNull)
  115 + .map(AdminUserRespDTO::getNickname)
  116 + .filter(nickname -> nickname != null && !nickname.isEmpty())
  117 + .collect(Collectors.toList());
  118 + mainInfoVo.setCoHandlersName(coHandlersNameList);
  119 + }else{
  120 + mainInfoVo.setCoHandlersName(new ArrayList<>());
  121 + }
  122 + mainInfoVo.setUserMobile(userMap.get(mainInfoVo.getUserId()).getMobile());
  123 + }
  124 + return mainInfoVo;
  125 + }
93 126 }
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/dataobject/maininfo/MainInfoDO.java
1 1 package com.zteits.urbanops.module.workorder.dal.dataobject.maininfo;
2 2  
  3 +import com.baomidou.mybatisplus.annotation.KeySequence;
  4 +import com.baomidou.mybatisplus.annotation.TableField;
  5 +import com.baomidou.mybatisplus.annotation.TableId;
  6 +import com.baomidou.mybatisplus.annotation.TableName;
3 7 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
4   -import io.swagger.v3.oas.annotations.media.Schema;
  8 +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO;
5 9 import lombok.*;
6   -import java.util.*;
7   -import java.time.LocalDateTime;
8   -import java.math.BigDecimal;
  10 +
9 11 import java.math.BigDecimal;
10 12 import java.time.LocalDateTime;
11   -import java.time.LocalDateTime;
12   -import com.baomidou.mybatisplus.annotation.*;
13   -import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO;
14   -import org.hibernate.validator.constraints.Length;
  13 +import java.util.Set;
15 14  
16 15 /**
17 16 * 工单信息 DO
... ... @@ -46,6 +45,14 @@ public class MainInfoDO extends BaseDO {
46 45 */
47 46 private String orderName;
48 47 /**
  48 + * 三级编码
  49 + */
  50 + private String orderCode;
  51 + /**
  52 + * 业务类型
  53 + */
  54 + private String busiType;
  55 + /**
49 56 * 工单类型 Q:快速工单,C:普通工单,O:其他工单
50 57 */
51 58 private String orderType;
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/dataobject/maininfo/MainInfoExtDO.java
1 1 package com.zteits.urbanops.module.workorder.dal.dataobject.maininfo;
2 2  
3   -import com.baomidou.mybatisplus.annotation.KeySequence;
4 3 import com.baomidou.mybatisplus.annotation.TableField;
5   -import com.baomidou.mybatisplus.annotation.TableName;
6 4 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
7 5 import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO;
8 6 import lombok.*;
... ... @@ -47,6 +45,15 @@ public class MainInfoExtDO extends BaseDO {
47 45 * 工单名称
48 46 */
49 47 private String orderName;
  48 +
  49 + /**
  50 + * 三级编码
  51 + */
  52 + private String orderCode;
  53 + /**
  54 + * 业务类型
  55 + */
  56 + private String busiType;
50 57 /**
51 58 * 来源ID
52 59 */
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/mysql/maininfo/MainInfoExtMapper.java
1 1 package com.zteits.urbanops.module.workorder.dal.mysql.maininfo;
2 2  
3   -import com.baomidou.mybatisplus.core.conditions.Wrapper;
4 3 import com.baomidou.mybatisplus.core.metadata.IPage;
5 4 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
6 5 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
... ... @@ -11,10 +10,8 @@ import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
11 10 import com.zteits.urbanops.framework.mybatis.core.util.MyBatisUtils;
12 11 import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO;
13 12 import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.MainInfoHasTaskPageReqVO;
14   -import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.MainInfoPageReqVO;
15 13 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
16 14 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoExtDO;
17   -import jakarta.validation.constraints.NotNull;
18 15 import org.apache.ibatis.annotations.*;
19 16 import org.springframework.lang.NonNull;
20 17  
... ... @@ -112,6 +109,8 @@ public interface MainInfoExtMapper extends BaseMapperX&lt;MainInfoDO&gt; {
112 109 @Result(column = "busi_line", property = "busiLine"),
113 110 @Result(column = "order_no", property = "orderNo"),
114 111 @Result(column = "order_name", property = "orderName"),
  112 + @Result(column = "order_code", property = "orderCode"),
  113 + @Result(column = "busi_type", property = "busiType"),
115 114 @Result(column = "source_id", property = "sourceId"),
116 115 @Result(column = "source_name", property = "sourceName"),
117 116 @Result(column = "road_id", property = "roadId"),
... ... @@ -230,6 +229,8 @@ public interface MainInfoExtMapper extends BaseMapperX&lt;MainInfoDO&gt; {
230 229 @Result(column = "busi_line", property = "busiLine"),
231 230 @Result(column = "order_no", property = "orderNo"),
232 231 @Result(column = "order_name", property = "orderName"),
  232 + @Result(column = "order_code", property = "orderCode"),
  233 + @Result(column = "busi_type", property = "busiType"),
233 234 @Result(column = "source_id", property = "sourceId"),
234 235 @Result(column = "source_name", property = "sourceName"),
235 236 @Result(column = "road_id", property = "roadId"),
... ... @@ -345,6 +346,8 @@ public interface MainInfoExtMapper extends BaseMapperX&lt;MainInfoDO&gt; {
345 346 @Result(column = "busi_line", property = "busiLine"),
346 347 @Result(column = "order_no", property = "orderNo"),
347 348 @Result(column = "order_name", property = "orderName"),
  349 + @Result(column = "order_code", property = "orderCode"),
  350 + @Result(column = "busi_type", property = "busiType"),
348 351 @Result(column = "source_id", property = "sourceId"),
349 352 @Result(column = "source_name", property = "sourceName"),
350 353 @Result(column = "road_id", property = "roadId"),
... ... @@ -471,6 +474,8 @@ public interface MainInfoExtMapper extends BaseMapperX&lt;MainInfoDO&gt; {
471 474 @Result(column = "busi_line", property = "busiLine"),
472 475 @Result(column = "order_no", property = "orderNo"),
473 476 @Result(column = "order_name", property = "orderName"),
  477 + @Result(column = "order_code", property = "orderCode"),
  478 + @Result(column = "busi_type", property = "busiType"),
474 479 @Result(column = "source_id", property = "sourceId"),
475 480 @Result(column = "source_name", property = "sourceName"),
476 481 @Result(column = "road_id", property = "roadId"),
... ... @@ -584,6 +589,8 @@ public interface MainInfoExtMapper extends BaseMapperX&lt;MainInfoDO&gt; {
584 589 @Result(column = "busi_line", property = "busiLine"),
585 590 @Result(column = "order_no", property = "orderNo"),
586 591 @Result(column = "order_name", property = "orderName"),
  592 + @Result(column = "order_code", property = "orderCode"),
  593 + @Result(column = "busi_type", property = "busiType"),
587 594 @Result(column = "source_id", property = "sourceId"),
588 595 @Result(column = "source_name", property = "sourceName"),
589 596 @Result(column = "road_id", property = "roadId"),
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/dal/mysql/maininfo/MainInfoMapper.java
... ... @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 6 import com.zteits.urbanops.framework.common.pojo.PageResult;
7 7 import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX;
8 8 import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX;
  9 +import com.zteits.urbanops.module.workorder.controller.app.maininfo.vo.AppMainInfoPageReqVO;
9 10 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
10 11 import org.apache.ibatis.annotations.Mapper;
11 12 import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.*;
... ... @@ -135,4 +136,32 @@ public interface MainInfoMapper extends BaseMapperX&lt;MainInfoDO&gt; {
135 136 return selectList(new QueryWrapper<MainInfoDO>().in(field, values));
136 137 }
137 138  
  139 + /**
  140 + * 获得工单信息分页 不查询快速工单
  141 + *
  142 + * @param reqVO 分页查询
  143 + * @return 工单信息分页
  144 + */
  145 + default PageResult<MainInfoDO> selectPageExcludeQuickOrder(AppMainInfoPageReqVO reqVO) {
  146 +
  147 + LambdaQueryWrapperX<MainInfoDO> queryWrapper = new LambdaQueryWrapperX<>();
  148 + // 不查询快速工单 → 订单类型 != "Q"
  149 + queryWrapper.ne(MainInfoDO::getOrderType, "Q");
  150 + // 部派工单 → 工单编号以 "IWO" 开头
  151 + if ("1".equals(reqVO.getQueryType()) ) {
  152 + queryWrapper.likeRight(MainInfoDO::getOrderNo, "IWO");
  153 + }
  154 + queryWrapper.eqIfPresent(MainInfoDO::getWorkerCompanyId, reqVO.getWorkerCompanyId())
  155 + .eqIfPresent(MainInfoDO::getCompanyId, reqVO.getCompanyId())
  156 + .likeIfPresent(MainInfoDO::getOrderNo, reqVO.getOrderNo())
  157 + .likeIfPresent(MainInfoDO::getOrderName, reqVO.getOrderName())
  158 + .eqIfPresent(MainInfoDO::getStatus, reqVO.getStatus())
  159 + .betweenIfPresent(MainInfoDO::getCreateTime, reqVO.getCreateTime())
  160 + .likeIfPresent(MainInfoDO::getLonLatAddress, reqVO.getLonLatAddress())
  161 + .likeIfPresent(MainInfoDO::getRemark, reqVO.getRemark())
  162 + .eqIfPresent(MainInfoDO::getUserId, reqVO.getUserId())
  163 + .orderByDesc(MainInfoDO::getId);
  164 + return selectPage(reqVO, queryWrapper);
  165 + }
  166 +
138 167 }
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/enums/ErrorCodeConstants.java
... ... @@ -48,7 +48,7 @@ public interface ErrorCodeConstants {
48 48  
49 49 ErrorCode EVENT_MAPPING_INFO_NOT_EXISTS = new ErrorCode(1-900-004-001, "工单事件映射不存在");
50 50  
51   - ErrorCode APP_INSPECTOR_USER_ROLE_ERROR = new ErrorCode(1-900-004-001, "该登录人不是 全域督查员,无法发起流程!");
  51 + ErrorCode APP_INSPECTOR_USER_ROLE_ERROR = new ErrorCode(1-900-004-001, "该登录人不是 全域督查员或者组长,无法发起流程!");
52 52  
53 53 ErrorCode APP_WORK_INSPECTOR_ROLE_ILLEGLE = new ErrorCode(1-900-004-003, "该登录人即是 大区经理 角色,又是 全域督察员,无法发起流程!");
54 54  
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorService.java
1 1 package com.zteits.urbanops.module.workorder.service.garden;
2 2  
3   -import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskApproveReqVO;
4 3 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskInspectorApproveReqVO;
5 4 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskRegionMgrApproveReqVO;
6 5 import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderInspectorReqVO;
7   -import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderUniversalReqVO;
8 6 import jakarta.validation.Valid;
9 7  
  8 +import java.util.List;
  9 +
10 10 /**
11 11 * 类描述:全域督察员工单处理server
12 12 * 创建人:yanhuiqing
... ... @@ -27,6 +27,14 @@ public interface BpmInspectorService {
27 27 */
28 28 Long createWorkOrder(@Valid AppGardenWorkOrderInspectorReqVO createRequestVo);
29 29  
  30 +
  31 + /**
  32 + * 批量创建工单
  33 + * @param reqVOList 工单列表
  34 + * @return 工单ID集合
  35 + */
  36 + List<Long> batchCreateWorkOrder(List<AppGardenWorkOrderInspectorReqVO> reqVOList);
  37 +
30 38 /**
31 39 * 全域督察员工单分配
32 40 * @param regionPassReqVO
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/garden/BpmInspectorServiceImpl.java
... ... @@ -11,6 +11,7 @@ import com.zteits.urbanops.module.bpm.controller.admin.task.vo.task.BpmTaskAppro
11 11 import com.zteits.urbanops.module.bpm.enums.task.BpmTaskStatusEnum;
12 12 import com.zteits.urbanops.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
13 13 import com.zteits.urbanops.module.bpm.service.definition.BpmProcessDefinitionService;
  14 +import com.zteits.urbanops.module.bpm.service.task.BpmProcessInstanceCopyService;
14 15 import com.zteits.urbanops.module.bpm.service.task.BpmProcessInstanceService;
15 16 import com.zteits.urbanops.module.bpm.service.task.BpmTaskService;
16 17 import com.zteits.urbanops.module.garden.api.road.RoadApi;
... ... @@ -21,7 +22,6 @@ import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO;
21 22 import com.zteits.urbanops.module.system.controller.admin.permission.vo.role.RoleRespVO;
22 23 import com.zteits.urbanops.module.workorder.api.WorkOrderApi;
23 24 import com.zteits.urbanops.module.workorder.api.constant.BpmCommonConstant;
24   -import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskApproveReqVO;
25 25 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskInspectorApproveReqVO;
26 26 import com.zteits.urbanops.module.workorder.controller.app.garden.vo.task.AppGardenTaskRegionMgrApproveReqVO;
27 27 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
... ... @@ -29,7 +29,6 @@ import com.zteits.urbanops.module.workorder.dal.mysql.attachment.AttachmentMappe
29 29 import com.zteits.urbanops.module.workorder.dal.mysql.maininfo.MainInfoExtMapper;
30 30 import com.zteits.urbanops.module.workorder.dal.mysql.maininfo.MainInfoMapper;
31 31 import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderInspectorReqVO;
32   -import com.zteits.urbanops.module.workorder.dto.AppGardenWorkOrderUniversalReqVO;
33 32 import com.zteits.urbanops.module.workorder.enums.BusiLineTeamLeaderRoleCodeEnum;
34 33 import com.zteits.urbanops.module.workorder.enums.EventSourceEnum;
35 34 import com.zteits.urbanops.module.workorder.util.RoleListUtils;
... ... @@ -78,6 +77,8 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{
78 77 private BpmProcessInstanceApi processInstanceApi;
79 78  
80 79 @Resource
  80 + private BpmProcessInstanceCopyService copyService;
  81 + @Resource
81 82 private BpmProcessDefinitionService processDefinitionService;
82 83 @Resource
83 84 private WorkOrderApi workOrderApi;
... ... @@ -113,7 +114,7 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{
113 114 throw exception(APP_USER_ROLE_NOT_EXISTS);
114 115 }
115 116  
116   - if(!RoleListUtils.containsCode(roleList,INSPECTOR_GLOBAL)){
  117 + if(!(RoleListUtils.containsCode(roleList,INSPECTOR_GLOBAL) || RoleListUtils.containsCode(roleList,INSPECTOR_GLOBAL_LEADER))){
117 118 throw exception(APP_INSPECTOR_USER_ROLE_ERROR);
118 119 }
119 120 //校验人员不能有 双重角色
... ... @@ -213,6 +214,11 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{
213 214 String processInstanceId = processInstanceApi.createProcessInstance(userId,
214 215 new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(BpmCommonConstant.BPM_UNIVERSE_INSPECTOR_WO)
215 216 .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(workOrder.getId())));
  217 + List<Long> userIds = roleApi.getUserIdsByRoleCode("Inspector_global_leader");
  218 + //抄送
  219 + if(null != userIds && !userIds.isEmpty()){
  220 + copyService.createProcessInstanceCopy(userIds,"APP巡查工单创建,自动抄送",processInstanceId,"StartEvent","发起流程",null);
  221 + }
216 222  
217 223 // 将工作流的编号,更新到 工单中
218 224 workOrderMapper.updateById(new MainInfoDO().setId(workOrder.getId()).setProcessInstanceId(processInstanceId));
... ... @@ -220,6 +226,18 @@ public class BpmInspectorServiceImpl implements BpmInspectorService{
220 226 }
221 227  
222 228 @Override
  229 + @Transactional(rollbackFor = Exception.class)
  230 + public List<Long> batchCreateWorkOrder(List<AppGardenWorkOrderInspectorReqVO> reqVOList) {
  231 + List<Long> idList = new ArrayList<>();
  232 + for (AppGardenWorkOrderInspectorReqVO reqVO : reqVOList) {
  233 + // 调用你原来的单个创建方法
  234 + Long id = createWorkOrder(reqVO);
  235 + idList.add(id);
  236 + }
  237 + return idList;
  238 + }
  239 +
  240 + @Override
223 241 public void approveTask2TeamLeader(AppGardenTaskRegionMgrApproveReqVO reqVO) {
224 242 BpmTaskApproveReqVO targetVO = BeanUtils.toBean(reqVO,BpmTaskApproveReqVO.class);
225 243 Map<String, Object> variables = new HashMap<>();
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/maininfo/MainInfoService.java
1 1 package com.zteits.urbanops.module.workorder.service.maininfo;
2 2  
3 3 import java.util.*;
  4 +
  5 +import com.zteits.urbanops.module.workorder.controller.app.maininfo.vo.AppMainInfoPageReqVO;
4 6 import jakarta.validation.*;
5 7 import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.*;
6 8 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
... ... @@ -67,4 +69,12 @@ public interface MainInfoService {
67 69 */
68 70 MainInfoDO getMainInfoByOrderNo(String orderNo);
69 71  
  72 + /**
  73 + * 获得工单信息分页
  74 + *
  75 + * @param pageReqVO 分页查询
  76 + * @return 工单信息分页
  77 + */
  78 + PageResult<MainInfoDO> selectPageExcludeQuickOrder(AppMainInfoPageReqVO pageReqVO);
  79 +
70 80 }
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/service/maininfo/MainInfoServiceImpl.java
... ... @@ -2,8 +2,12 @@ package com.zteits.urbanops.module.workorder.service.maininfo;
2 2  
3 3 import com.zteits.urbanops.framework.common.pojo.PageResult;
4 4 import com.zteits.urbanops.framework.common.util.object.BeanUtils;
  5 +import com.zteits.urbanops.framework.common.util.object.ObjectUtils;
  6 +import com.zteits.urbanops.module.system.api.permission.RoleApi;
  7 +import com.zteits.urbanops.module.system.enums.common.CommonConstants;
5 8 import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.MainInfoPageReqVO;
6 9 import com.zteits.urbanops.module.workorder.controller.admin.maininfo.vo.MainInfoSaveReqVO;
  10 +import com.zteits.urbanops.module.workorder.controller.app.maininfo.vo.AppMainInfoPageReqVO;
7 11 import com.zteits.urbanops.module.workorder.dal.dataobject.maininfo.MainInfoDO;
8 12 import com.zteits.urbanops.module.workorder.dal.mysql.maininfo.MainInfoMapper;
9 13 import jakarta.annotation.Resource;
... ... @@ -13,6 +17,7 @@ import org.springframework.validation.annotation.Validated;
13 17 import java.util.List;
14 18  
15 19 import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
16 21 import static com.zteits.urbanops.module.workorder.enums.ErrorCodeConstants.MAIN_INFO_NOT_EXISTS;
17 22  
18 23 /**
... ... @@ -27,6 +32,10 @@ public class MainInfoServiceImpl implements MainInfoService {
27 32 @Resource
28 33 private MainInfoMapper mainInfoMapper;
29 34  
  35 + @Resource
  36 + private RoleApi roleApi;
  37 +
  38 +
30 39 @Override
31 40 public Long createMainInfo(MainInfoSaveReqVO createReqVO) {
32 41 // 插入
... ... @@ -82,4 +91,38 @@ public class MainInfoServiceImpl implements MainInfoService {
82 91 return mainInfoMapper.selectOne(MainInfoDO::getOrderNo, orderNo);
83 92 }
84 93  
  94 + public PageResult<MainInfoDO> selectPageExcludeQuickOrder(AppMainInfoPageReqVO pageReqVO) {
  95 + String type = pageReqVO.getType();
  96 + String content = pageReqVO.getSearchContent();
  97 + if (ObjectUtils.isNotAllEmpty(type)){
  98 + if("1".equals(type)){
  99 + if(ObjectUtils.isNotAllEmpty(content)){pageReqVO.setLonLatAddress(content);}
  100 + } else if("2".equals(type)){
  101 + if(ObjectUtils.isNotAllEmpty(content)){pageReqVO.setOrderName(content);}
  102 + } else if("3".equals(type)){
  103 + if(ObjectUtils.isNotAllEmpty(content)){pageReqVO.setRemark(content);}
  104 + } else if("4".equals(type)){
  105 + if(ObjectUtils.isNotAllEmpty(content)){pageReqVO.setOrderNo(content);}
  106 + } else {
  107 + if (ObjectUtils.isNotAllEmpty(content)) {
  108 + pageReqVO.setOrderName(content);
  109 + }
  110 + }
  111 + } else {//默认是工单名称
  112 + if (ObjectUtils.isNotAllEmpty(content)) {
  113 + pageReqVO.setOrderName(content);
  114 + }
  115 + }
  116 + //部派工单
  117 + if ("1".equals(pageReqVO.getQueryType())) {
  118 + //全域督察员查询自己发起的工单
  119 + List<Long> userIds = roleApi.getUserIdsByRoleCode(CommonConstants.INSPECTOR_ROLE_KEY);
  120 + if (!userIds.contains(getLoginUserId())) {
  121 + pageReqVO.setUserId(getLoginUserId());
  122 + pageReqVO.setWorkerCompanyId(null);
  123 + }
  124 + }
  125 + return mainInfoMapper.selectPageExcludeQuickOrder(pageReqVO);
  126 + }
  127 +
85 128 }
... ...
urbanops-module-workorder/src/main/java/com/zteits/urbanops/module/workorder/util/RoleListUtils.java
... ... @@ -64,7 +64,7 @@ public class RoleListUtils {
64 64 * @param code2 第二个要校验的编码(允许为 null,此时要求列表中存在 null 的 code)
65 65 * @return true=同时包含code1和code2;false=不同时包含/列表为空/列表为null
66 66 */
67   - public static boolean containsBothCodes(List<RoleRespVO> roleList, String code1, String code2) {
  67 + public static boolean containsBothCodes(List<RoleRespVO> roleList, String code1, String code2 ) {
68 68 // 1. 列表为null/空,直接返回false
69 69 if (roleList == null || roleList.isEmpty()) {
70 70 return false;
... ... @@ -82,6 +82,7 @@ public class RoleListUtils {
82 82 return containsCode1 && containsCode2;
83 83 }
84 84  
  85 +
85 86 public static void teamLeaderByRoleAssign(List<AdminUserRespDTO> userList, Map<String, Object> processInstanceVariables){
86 87 //养护组长指派为多人
87 88 String assigneeIdStr = userList.stream()
... ...
urbanops-server/pom.xml
... ... @@ -146,6 +146,17 @@
146 146 <artifactId>urbanops-spring-boot-starter-protection</artifactId>
147 147 </dependency>
148 148  
  149 + <!-- 强制固定 BouncyCastle 版本为 1.80,禁止自动升级 -->
  150 + <dependency>
  151 + <groupId>org.bouncycastle</groupId>
  152 + <artifactId>bcprov-jdk18on</artifactId>
  153 + <version>1.80</version>
  154 + </dependency>
  155 + <dependency>
  156 + <groupId>org.bouncycastle</groupId>
  157 + <artifactId>bcutil-jdk18on</artifactId>
  158 + <version>1.80</version>
  159 + </dependency>
149 160 </dependencies>
150 161  
151 162 <build>
... ...
urbanops-server/src/main/resources/application-local.yaml
... ... @@ -186,6 +186,9 @@ urbanops:
186 186 refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
187 187 transfer-notify-url: https://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
188 188 demo: false # 开启演示模式
  189 + security:
  190 + mock-enable: true # 本地调试启用 Mock 认证,Token 格式: test{userId}(如 test1 表示用户ID=1)
  191 + mock-secret: test
189 192 tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
190 193  
191 194 justauth:
... ...
北京市行道树安全风险评估指南 2024.11.15.pdf 0 → 100644
No preview for this file type