wwh 1 месяц назад
Родитель
Сommit
5b830b5093

+ 9 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/controller/HomeScreenController.java

@@ -53,4 +53,13 @@ public class HomeScreenController extends BaseController
53 53
         List<HomeScreenStandardListItemVo> list = homeScreenService.listStandards(statYear, category);
54 54
         return getDataTable(list);
55 55
     }
56
+
57
+    /**
58
+     * 地图标注:各乡镇牦牛存栏(上一年填报)与出栏(当年填报)。
59
+     */
60
+    @GetMapping("/townMapMarkers")
61
+    public AjaxResult townMapMarkers()
62
+    {
63
+        return success(homeScreenService.loadTownMapMarkers());
64
+    }
56 65
 }

+ 8 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/mapper/HomeScreenStatsMapper.java

@@ -7,6 +7,7 @@ import com.ruoyi.web.modules.screen.domain.dto.HomeScreenAppointmentStatDto;
7 7
 import com.ruoyi.web.modules.screen.domain.dto.HomeScreenMonthCountDto;
8 8
 import com.ruoyi.web.modules.screen.domain.dto.HomeScreenSubsidyTypeSumDto;
9 9
 import com.ruoyi.web.modules.screen.domain.dto.HomeScreenTrainingMonthDto;
10
+import com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapMarkerVo;
10 11
 
11 12
 /**
12 13
  * 大屏首页聚合查询。
@@ -62,4 +63,11 @@ public interface HomeScreenStatsMapper
62 63
     List<Integer> selectDistinctTechPublishYears();
63 64
 
64 65
     List<Integer> selectDistinctTrainingYears();
66
+
67
+    /**
68
+     * 地图标注:全县填报乡镇 + 存栏(上一年)/出栏(当年)头数。
69
+     */
70
+    List<HomeScreenTownMapMarkerVo> selectTownMapMarkers(
71
+            @Param("herdStatYear") int herdStatYear,
72
+            @Param("outboundStatYear") int outboundStatYear);
65 73
 }

+ 6 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/service/IHomeScreenService.java

@@ -3,6 +3,7 @@ package com.ruoyi.web.modules.screen.service;
3 3
 import java.util.List;
4 4
 import com.ruoyi.web.modules.screen.domain.vo.HomeScreenDashboardVo;
5 5
 import com.ruoyi.web.modules.screen.domain.vo.HomeScreenStandardListItemVo;
6
+import com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapVo;
6 7
 
7 8
 /**
8 9
  * 大屏首页统计服务。
@@ -24,4 +25,9 @@ public interface IHomeScreenService
24 25
      * @param category {@link com.ruoyi.web.modules.screen.support.HomeScreenStandardCategory} 枚举名
25 26
      */
26 27
     List<HomeScreenStandardListItemVo> listStandards(String statYear, String category);
28
+
29
+    /**
30
+     * 地图标注:各乡镇存栏(上一年填报)与出栏(当年填报)。
31
+     */
32
+    HomeScreenTownMapVo loadTownMapMarkers();
27 33
 }

+ 17 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/screen/service/impl/HomeScreenServiceImpl.java

@@ -3,6 +3,7 @@ package com.ruoyi.web.modules.screen.service.impl;
3 3
 import java.math.BigDecimal;
4 4
 import java.math.RoundingMode;
5 5
 import java.time.LocalDate;
6
+import java.time.Year;
6 7
 import java.time.ZoneId;
7 8
 import java.util.Collections;
8 9
 import java.util.Date;
@@ -18,6 +19,8 @@ import com.ruoyi.web.modules.screen.domain.vo.HomeScreenDashboardVo;
18 19
 import com.ruoyi.web.modules.screen.domain.vo.HomeScreenIndustryOverviewVo;
19 20
 import com.ruoyi.web.modules.screen.domain.vo.HomeScreenInventoryChangeVo;
20 21
 import com.ruoyi.web.modules.screen.domain.vo.HomeScreenStandardListItemVo;
22
+import com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapMarkerVo;
23
+import com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapVo;
21 24
 import com.ruoyi.web.modules.screen.mapper.HomeScreenStandardMapper;
22 25
 import com.ruoyi.web.modules.screen.mapper.HomeScreenStatsMapper;
23 26
 import com.ruoyi.web.modules.screen.service.IHomeScreenService;
@@ -171,6 +174,20 @@ public class HomeScreenServiceImpl implements IHomeScreenService
171 174
         return rows;
172 175
     }
173 176
 
177
+    @Override
178
+    public HomeScreenTownMapVo loadTownMapMarkers()
179
+    {
180
+        int outboundStatYear = Year.now().getValue();
181
+        int herdStatYear = outboundStatYear - 1;
182
+        List<HomeScreenTownMapMarkerVo> markers = homeScreenStatsMapper.selectTownMapMarkers(herdStatYear,
183
+                outboundStatYear);
184
+        HomeScreenTownMapVo vo = new HomeScreenTownMapVo();
185
+        vo.setHerdStatYear(herdStatYear);
186
+        vo.setOutboundStatYear(outboundStatYear);
187
+        vo.setMarkers(markers != null ? markers : Collections.emptyList());
188
+        return vo;
189
+    }
190
+
174 191
     private List<Long> safeActivePastureIds()
175 192
     {
176 193
         List<Long> ids = breedingIndustryStatsMapper.selectActivePastureIds();

+ 18 - 0
baqing-admin/src/main/resources/mapper/screen/HomeScreenStatsMapper.xml

@@ -145,4 +145,22 @@
145 145
         where del_flag = '0' and publish_status = 1
146 146
         order by 1 desc
147 147
     </select>
148
+
149
+    <select id="selectTownMapMarkers"
150
+            resultType="com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapMarkerVo">
151
+        select d.dept_id as townDeptId,
152
+               d.dept_name as townName,
153
+               COALESCE(h.yak_total, 0) as yakInventoryCount,
154
+               COALESCE(o.yak_outbound_count, 0) as yakOutboundCount
155
+        from sys_dept d
156
+        left join biz_yak_herd_inventory h
157
+            on h.town_dept_id = d.dept_id and h.stat_year = #{herdStatYear}
158
+        left join biz_yak_outbound_report o
159
+            on o.town_dept_id = d.dept_id and o.stat_year = #{outboundStatYear}
160
+        where d.parent_id = 100
161
+          and d.order_num &gt; 12
162
+          and d.del_flag = '0'
163
+          and d.status = '0'
164
+        order by d.order_num asc, d.dept_id asc
165
+    </select>
148 166
 </mapper>

+ 32 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/screen/controller/HomeScreenControllerApiTest.java

@@ -112,6 +112,38 @@ class HomeScreenControllerApiTest
112 112
                 .andExpect(jsonPath("$.code").value(200));
113 113
     }
114 114
 
115
+    @Test
116
+    @DisplayName("DP-SY-API-038 GET townMapMarkers 地图标注")
117
+    void api024_townMapMarkers() throws Exception
118
+    {
119
+        when(homeScreenService.loadTownMapMarkers()).thenReturn(sampleTownMap());
120
+        mockMvc.perform(get("/bigScreen/home/townMapMarkers"))
121
+                .andExpect(status().isOk())
122
+                .andExpect(jsonPath("$.code").value(200))
123
+                .andExpect(jsonPath("$.data.herdStatYear").value(2025))
124
+                .andExpect(jsonPath("$.data.outboundStatYear").value(2026))
125
+                .andExpect(jsonPath("$.data.markers[0].townDeptId").value(101))
126
+                .andExpect(jsonPath("$.data.markers[0].townName").value("巴青乡"))
127
+                .andExpect(jsonPath("$.data.markers[0].yakInventoryCount").value(21587))
128
+                .andExpect(jsonPath("$.data.markers[0].yakOutboundCount").value(1523));
129
+    }
130
+
131
+    private static com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapVo sampleTownMap()
132
+    {
133
+        com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapMarkerVo marker =
134
+                new com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapMarkerVo();
135
+        marker.setTownDeptId(101L);
136
+        marker.setTownName("巴青乡");
137
+        marker.setYakInventoryCount(21587);
138
+        marker.setYakOutboundCount(1523);
139
+        com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapVo vo =
140
+                new com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapVo();
141
+        vo.setHerdStatYear(2025);
142
+        vo.setOutboundStatYear(2026);
143
+        vo.setMarkers(Collections.singletonList(marker));
144
+        return vo;
145
+    }
146
+
115 147
     private static HomeScreenDashboardVo sampleDashboard()
116 148
     {
117 149
         HomeScreenDashboardVo vo = new HomeScreenDashboardVo();

+ 2 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/screen/mapper/HomeScreenStatsMapperXmlTest.java

@@ -24,6 +24,8 @@ class HomeScreenStatsMapperXmlTest
24 24
         assertTrue(xml.contains("biz_yak_asset"));
25 25
         assertTrue(xml.contains("biz_service_appointment"));
26 26
         assertTrue(xml.contains("biz_subsidy_grant_record"));
27
+        assertTrue(xml.contains("biz_yak_herd_inventory"));
28
+        assertTrue(xml.contains("biz_yak_outbound_report"));
27 29
         assertTrue(xml.contains("status not in (2, 3)"));
28 30
     }
29 31
 }

+ 25 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/screen/service/impl/HomeScreenServiceImplTest.java

@@ -2,11 +2,13 @@ package com.ruoyi.web.modules.screen.service.impl;
2 2
 
3 3
 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4
 import static org.junit.jupiter.api.Assertions.assertNotNull;
5
+import static org.mockito.ArgumentMatchers.eq;
5 6
 import static org.mockito.ArgumentMatchers.any;
6 7
 import static org.mockito.ArgumentMatchers.anyList;
7 8
 import static org.mockito.Mockito.when;
8 9
 
9 10
 import java.math.BigDecimal;
11
+import java.time.Year;
10 12
 import java.util.Arrays;
11 13
 import java.util.Collections;
12 14
 import java.util.List;
@@ -20,6 +22,8 @@ import com.ruoyi.web.modules.industryservice.domain.dto.BreedingIndustryYakSnaps
20 22
 import com.ruoyi.web.modules.industryservice.mapper.BreedingIndustryStatsMapper;
21 23
 import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
22 24
 import com.ruoyi.web.modules.screen.domain.vo.HomeScreenDashboardVo;
25
+import com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapMarkerVo;
26
+import com.ruoyi.web.modules.screen.domain.vo.HomeScreenTownMapVo;
23 27
 import com.ruoyi.web.modules.screen.mapper.HomeScreenStandardMapper;
24 28
 import com.ruoyi.web.modules.screen.mapper.HomeScreenStatsMapper;
25 29
 
@@ -84,6 +88,27 @@ class HomeScreenServiceImplTest
84 88
         assertNotNull(vo.getTrainingTrend());
85 89
     }
86 90
 
91
+    @Test
92
+    @DisplayName("DP-SY-UT-026 地图标注年份与字段")
93
+    void ut011_townMapMarkers()
94
+    {
95
+        int currentYear = Year.now().getValue();
96
+        HomeScreenTownMapMarkerVo marker = new HomeScreenTownMapMarkerVo();
97
+        marker.setTownDeptId(101L);
98
+        marker.setTownName("巴青乡");
99
+        marker.setYakInventoryCount(100);
100
+        marker.setYakOutboundCount(50);
101
+        when(homeScreenStatsMapper.selectTownMapMarkers(eq(currentYear - 1), eq(currentYear)))
102
+                .thenReturn(Collections.singletonList(marker));
103
+
104
+        HomeScreenTownMapVo vo = service.loadTownMapMarkers();
105
+        assertEquals(currentYear - 1, vo.getHerdStatYear().intValue());
106
+        assertEquals(currentYear, vo.getOutboundStatYear().intValue());
107
+        assertEquals(1, vo.getMarkers().size());
108
+        assertEquals(100, vo.getMarkers().get(0).getYakInventoryCount().intValue());
109
+        assertEquals(50, vo.getMarkers().get(0).getYakOutboundCount().intValue());
110
+    }
111
+
87 112
     private void stubBreedingEmpty()
88 113
     {
89 114
         when(breedingIndustryStatsMapper.selectActivePastureIds()).thenReturn(Collections.singletonList(1L));

+ 17 - 1
doc/大屏/首页/大屏首页功能需求.md

@@ -182,12 +182,13 @@
182 182
 - **养殖标准**(分类 Tab + 分页列表,行内查阅);
183 183
 - **农技课堂**(课程视频、实战培训、培训内容合计);
184 184
 - **实战培训报名**(按月活动数与报名人数双曲线);
185
+- **地图标注**(全县乡镇牦牛存栏/出栏头数,供首页地图展示);
185 186
 - 无数据、加载中、加载失败等状态展示。
186 187
 
187 188
 ### 3.2 范围外
188 189
 
189 190
 - 各业务模块台账的增删改、发布下架、预约受理、补贴登记、标准维护;
190
-- 导出、打印;地图标点、下钻后台菜单(若后续增加外链须单独评审);
191
+- 导出、打印;地图下钻后台菜单(若后续增加外链须单独评审);
191 192
 - 按牧场、区域、自定义日期段筛选(见 **§8**);
192 193
 - 气象、疫病风险专页、共同富裕专页等**其他大屏子页**(各自独立需求);
193 194
 - 大屏端权限与网关策略(由部署约定,本文档不展开)。
@@ -377,6 +378,20 @@ flowchart TB
377 378
 
378 379
 ---
379 380
 
381
+### 5.12 区块十一:地图标注
382
+
383
+| 项 | 规则 |
384
+| --- | --- |
385
+| 展示位置 | 首页地图各乡镇标注(以原型为准) |
386
+| 乡镇范围 | `sys_dept` 中 `parent_id=100` 且 `order_num>12` 的有效乡镇 |
387
+| 标注字段 | **所属乡镇** `dept_id`、**乡镇名称**、**牦牛存栏数**(头)、**牦牛出栏数**(头) |
388
+| 存栏口径 | `biz_yak_herd_inventory.yak_total`;统计年 = **当前自然年 − 1** |
389
+| 出栏口径 | `biz_yak_outbound_report.yak_outbound_count`;统计年 = **当前自然年** |
390
+| 无填报 | 对应乡镇该年未填报时展示 **0** |
391
+| 与看板年份 | **不**随页面「统计年份 **Y**」切换;始终按服务器当前自然年取上一年存栏、当年出栏 |
392
+
393
+---
394
+
380 395
 ## 6. 图表与列表通用规则
381 396
 
382 397
 | 项 | 规则 |
@@ -401,6 +416,7 @@ flowchart TB
401 416
 | 某标准分类无已发布标准 | 该 Tab 列表空态 |
402 417
 | **Y** 年无视频课程且无实战培训 | 农技课堂空态 |
403 418
 | **Y** 年无已发布实战培训 | 实战培训报名趋势空态 |
419
+| 某乡镇无存栏/出栏填报 | 地图标注该乡镇对应头数为 **0** |
404 420
 
405 421
 ### 7.2 加载与失败
406 422
 

+ 48 - 2
doc/大屏/首页/大屏首页技术方案.md

@@ -36,6 +36,9 @@
36 36
 | `biz_breeding_standard` 等 5 张标准表 | 养殖标准分页列表 |
37 37
 | `biz_tech_resource` | 视频课程(`004008`) |
38 38
 | `biz_practical_training` | 实战培训场次与报名趋势 |
39
+| `biz_yak_herd_inventory` | 地图标注 — 乡镇牦牛存栏(**上一年** `yak_total`) |
40
+| `biz_yak_outbound_report` | 地图标注 — 乡镇牦牛出栏(**当年** `yak_outbound_count`) |
41
+| `sys_dept` | 地图标注 — 乡镇列表(`parent_id=100` 且 `order_num>12`) |
39 42
 
40 43
 ---
41 44
 
@@ -183,8 +186,9 @@
183 186
 | --- | --- | --- | --- | --- |
184 187
 | 3.1 | 看板主数据(除标准分页外全部区块) | GET | `/bigScreen/home/dashboard` | `bigScreen:home:query` |
185 188
 | 3.2 | 养殖标准分页列表 | GET | `/bigScreen/home/standards` | 同上 |
189
+| 3.3 | 地图标注(乡镇存栏/出栏) | GET | `/bigScreen/home/townMapMarkers` | 同上 |
186 190
 
187
-前端:切换 `statYear` → **3.1** + **3.2**(`pageNum=1`);仅翻页 / 切 Tab → **3.2**。
191
+前端:切换 `statYear` → **3.1** + **3.2**(`pageNum=1`);仅翻页 / 切 Tab → **3.2**;进入首页 / 地图刷新 → **3.3**(**不**随 `statYear` 参数变化,按服务器当前自然年计算)
188 192
 
189 193
 ---
190 194
 
@@ -380,6 +384,48 @@
380 384
 
381 385
 ---
382 386
 
387
+### 3.3 地图标注
388
+
389
+#### Query
390
+
391
+无参数。统计年份由服务器当前自然年 `Y` 推导:
392
+
393
+| 字段 | 年份 |
394
+| --- | --- |
395
+| `herdStatYear` | `Y - 1`(存栏取 `biz_yak_herd_inventory.stat_year`) |
396
+| `outboundStatYear` | `Y`(出栏取 `biz_yak_outbound_report.stat_year`) |
397
+
398
+#### 响应 `data`
399
+
400
+| 字段 | 类型 | 说明 |
401
+| --- | --- | --- |
402
+| `herdStatYear` | int | 存栏数据年份(上一年) |
403
+| `outboundStatYear` | int | 出栏数据年份(当年) |
404
+| `markers` | array | 乡镇标注列表,见下表 |
405
+
406
+`markers[]`:
407
+
408
+| 字段 | 类型 | 说明 |
409
+| --- | --- | --- |
410
+| `townDeptId` | long | 所属乡镇 `dept_id`(`sys_dept`) |
411
+| `townName` | string | 乡镇名称 |
412
+| `yakInventoryCount` | int | 牦牛存栏数(头),`biz_yak_herd_inventory.yak_total`;无填报为 **0** |
413
+| `yakOutboundCount` | int | 牦牛出栏数(头),`biz_yak_outbound_report.yak_outbound_count`;无填报为 **0** |
414
+
415
+#### 乡镇范围
416
+
417
+与牦牛存栏/出栏填报模块一致:`sys_dept` 中 `parent_id = 100`、`order_num > 12`、`del_flag = '0'`、`status = '0'`,按 `order_num`、`dept_id` 升序。
418
+
419
+#### 服务端逻辑(摘要)
420
+
421
+```
422
+1. outboundStatYear = Year.now(); herdStatYear = outboundStatYear - 1
423
+2. markers = HomeScreenStatsMapper.selectTownMapMarkers(herdStatYear, outboundStatYear)
424
+3. return AjaxResult.success(vo)
425
+```
426
+
427
+---
428
+
383 429
 ## 4. 菜单与权限(示例)
384 430
 
385 431
 | 类型 | 名称 | 权限标识 |
@@ -396,7 +442,7 @@ SQL 示例:`sql/big_screen_home_perm.sql`(挂载「大屏」父菜单下)
396 442
 - [ ] 复用 `BreedingIndustryStatsMapper` / `BreedingIndustryCalculator`(按 **Y** 与全县牧场)
397 443
 - [ ] 预约、补贴、标准、科技资源、实战培训 Mapper 扩展(**§2.6**)
398 444
 - [ ] 单元测试:历史年 `statDate`、12 月补 0、主数据不随 **Y** 变、补贴万元、无培训空态、标准 Tab 路由
399
-- [ ] 接口测试:`/bigScreen/home/dashboard`、`/standards`
445
+- [ ] 接口测试:`/bigScreen/home/dashboard`、`/standards`、`/townMapMarkers`
400 446
 - [ ] 菜单权限 SQL;依赖各业务表 DDL 已执行
401 447
 
402 448
 ---

+ 3 - 1
doc/大屏/首页/大屏首页测试用例.md

@@ -73,9 +73,10 @@
73 73
 | DP-SY-UT-020 | 养殖标准 | 纳入条件 | 单元测试 | JUnit5 | SY-10 | `STD-PUB`+`STD-DRAFT`+`STD-OUT` | `filterStandards(2026)` | 仅 `STD-PUB` 通过 |
74 74
 | DP-SY-UT-021 | 可选年份 | 并集含当前年 | 单元测试 | JUnit5 | §2.1 | Mock 出栏年 `[2025]`,其余空;当前 2026 | `buildAvailableYears` | 含 `2026`;降序 |
75 75
 | DP-SY-UT-022 | 约束 | 复用 Calculator | 单元测试 | JUnit5 | 技术§2.6 | 检查 HomeScreenService | 月末存栏调用 `BreedingIndustryCalculator` | 无重复实现月末判定 |
76
-| DP-SY-UT-023 | 约束 | 只读无写 | 单元测试 | JUnit5 | §3.2、SY-14 | 检查 Controller/Service | 无 insert/update/delete | 仅 `dashboard`、`standards` 查询 |
76
+| DP-SY-UT-023 | 约束 | 只读无写 | 单元测试 | JUnit5 | §3.2、SY-14 | 检查 Controller/Service | 无 insert/update/delete | 仅 `dashboard`、`standards`、`townMapMarkers` 查询 |
77 77
 | DP-SY-UT-024 | 约束 | 无标准详情接口 | 单元测试 | JUnit5 | §5.9 | 检查 Controller 映射 | 无 `GET .../standards/{id}` | 仅列表分页 |
78 78
 | DP-SY-UT-025 | 缓存 | forceRefresh | 单元测试 | JUnit5 | 技术§2.3 | 启用缓存未过期 | `loadDashboard(2026,true)` | 跳过缓存重新聚合 |
79
+| DP-SY-UT-026 | 地图标注 | 年份与字段 | 单元测试 | JUnit5 | §5.12 | Mock Mapper 返回 1 乡镇 | `loadTownMapMarkers()` | `herdStatYear=当前年-1`;`outboundStatYear=当前年`;含 `townDeptId`/`townName`/存栏/出栏 |
79 80
 
80 81
 ---
81 82
 
@@ -120,6 +121,7 @@
120 121
 | DP-SY-API-035 | 权限 | 无大屏权限 | 接口测试 | Postman | 越权 | 无 `bigScreen:home:query` | `GET dashboard`、`GET standards` | 403 或无权限 |
121 122
 | DP-SY-API-036 | 权限 | 有大屏无台账写 | 接口测试 | Postman | 只读 SY-14 | 仅有 query | `GET` 成功;`POST` 牦牛/标准维护接口 | 大屏可读;台账写权限独立 |
122 123
 | DP-SY-API-037 | 一致性 | 切年双请求 | 接口测试 | Postman | 技术§3 | 已登录 | 模拟前端切年同时调 `dashboard`+`standards` | 两接口 `statYear` 一致;列表为新年 `pageNum=1` |
124
+| DP-SY-API-038 | 地图标注 | 乡镇存栏出栏 | 接口测试 | Postman/curl | §5.12 | 乡镇填报表有样本 | `GET .../townMapMarkers` | `code` 成功;含 `herdStatYear`、`outboundStatYear`、`markers[]`;字段与 SQL 一致;无填报为 0 |
123 125
 
124 126
 ---
125 127