Sfoglia il codice sorgente

农村三资数据填报

wwh 1 settimana fa
parent
commit
a467ac5084

+ 77 - 6
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/LivestockOwnershipHouseholdExcelExporter.java

@@ -78,6 +78,8 @@ public class LivestockOwnershipHouseholdExcelExporter
78 78
             updateTitleYearMonth(sheet, statYear, statMonth);
79 79
             updateFiller(sheet, fillerName);
80 80
             int dataStartRow = headerRowIndex + 2;
81
+            CellStyle[] dataRowStyles = captureDataRowStyles(sheet, dataStartRow);
82
+            Float dataRowHeight = captureRowHeight(sheet, dataStartRow);
81 83
             clearTemplateDataRegion(sheet, dataStartRow);
82 84
             Totals totals = new Totals();
83 85
             int rowIndex = dataStartRow;
@@ -95,6 +97,7 @@ public class LivestockOwnershipHouseholdExcelExporter
95 97
                     setIntegerCell(row, LivestockOwnershipHouseholdExcelParser.COL_SEQ, seq++);
96 98
                     setStringCell(row, LivestockOwnershipHouseholdExcelParser.COL_TOWN, report.getTownName());
97 99
                     fillDataRow(row, report);
100
+                    applyDataRowStyles(row, dataRowStyles, dataRowHeight);
98 101
                     totals.add(report);
99 102
                     rowIndex++;
100 103
                 }
@@ -102,8 +105,9 @@ public class LivestockOwnershipHouseholdExcelExporter
102 105
             Row totalRow = getOrCreateRow(sheet, rowIndex);
103 106
             clearDataCells(totalRow);
104 107
             setStringCell(totalRow, LivestockOwnershipHouseholdExcelParser.COL_SEQ, "合计");
105
-            mergeTotalLabelCells(sheet, rowIndex);
106 108
             totals.writeToRow(totalRow);
109
+            applyDataRowStyles(totalRow, dataRowStyles, dataRowHeight);
110
+            mergeTotalLabelCells(sheet, rowIndex);
107 111
             removeRowsAfter(sheet, rowIndex);
108 112
             workbook.write(out);
109 113
             return out.toByteArray();
@@ -186,7 +190,8 @@ public class LivestockOwnershipHouseholdExcelExporter
186 190
                     continue;
187 191
                 }
188 192
                 String text = formatter.formatCellValue(cell);
189
-                if (StringUtils.isEmpty(text) || !text.contains("年"))
193
+                // 跳过填表人/填表时间行,避免把「XXXX年X月」先改成统计年导致填表日期无法落到当月
194
+                if (StringUtils.isEmpty(text) || !text.contains("年") || isFillerMetaText(text))
190 195
                 {
191 196
                     continue;
192 197
                 }
@@ -210,6 +215,9 @@ public class LivestockOwnershipHouseholdExcelExporter
210 215
         }
211 216
     }
212 217
 
218
+    /**
219
+     * 将填表人、填表时间/日期写为当前登录用户与<strong>系统当月</strong>(非导出统计年月)。
220
+     */
213 221
     static void updateFiller(Sheet sheet, String fillerName)
214 222
     {
215 223
         java.time.LocalDate today = java.time.LocalDate.now();
@@ -236,7 +244,7 @@ public class LivestockOwnershipHouseholdExcelExporter
236 244
                     continue;
237 245
                 }
238 246
                 String text = formatter.formatCellValue(cell);
239
-                if (StringUtils.isEmpty(text) || (!text.contains("填表人") && !text.contains("填表时间")))
247
+                if (StringUtils.isEmpty(text) || !isFillerMetaText(text))
240 248
                 {
241 249
                     continue;
242 250
                 }
@@ -245,9 +253,12 @@ public class LivestockOwnershipHouseholdExcelExporter
245 253
                 {
246 254
                     updated = updated.replace("登录用户名称", fillerName);
247 255
                 }
248
-                updated = updated.replaceAll("填表时间:\\s*XXXX年X月", "填表时间:" + fillTime);
249
-                updated = updated.replaceAll("填表时间:\\s*\\d{4}年\\d{1,2}月", "填表时间:" + fillTime);
250
-                if (updated.contains("XXXX年") && updated.contains("填表时间"))
256
+                // 兼容「填表时间」「填表日期」及占位 XXXX年X月 / 已被误替换的 yyyy年X月
257
+                updated = updated.replaceAll("(填表时间|填表日期):\\s*XXXX年X月", "$1:" + fillTime);
258
+                updated = updated.replaceAll("(填表时间|填表日期):\\s*\\d{4}年X月", "$1:" + fillTime);
259
+                updated = updated.replaceAll("(填表时间|填表日期):\\s*\\d{4}年\\d{1,2}月", "$1:" + fillTime);
260
+                if (updated.contains("XXXX年")
261
+                        && (updated.contains("填表时间") || updated.contains("填表日期")))
251 262
                 {
252 263
                     updated = updated.replaceFirst("XXXX年X?月?", fillTime);
253 264
                 }
@@ -256,6 +267,66 @@ public class LivestockOwnershipHouseholdExcelExporter
256 267
         }
257 268
     }
258 269
 
270
+    private static boolean isFillerMetaText(String text)
271
+    {
272
+        return text.contains("填表人") || text.contains("填表时间") || text.contains("填表日期");
273
+    }
274
+
275
+    private static CellStyle[] captureDataRowStyles(Sheet sheet, int styleSourceRow)
276
+    {
277
+        CellStyle[] styles = new CellStyle[LivestockOwnershipHouseholdExcelParser.COL_LAST + 1];
278
+        Row src = sheet.getRow(styleSourceRow);
279
+        if (src == null)
280
+        {
281
+            return styles;
282
+        }
283
+        for (int col = 0; col < styles.length; col++)
284
+        {
285
+            Cell cell = src.getCell(col);
286
+            if (cell != null)
287
+            {
288
+                styles[col] = cell.getCellStyle();
289
+            }
290
+        }
291
+        return styles;
292
+    }
293
+
294
+    private static Float captureRowHeight(Sheet sheet, int styleSourceRow)
295
+    {
296
+        Row src = sheet.getRow(styleSourceRow);
297
+        if (src == null)
298
+        {
299
+            return null;
300
+        }
301
+        return src.getHeightInPoints();
302
+    }
303
+
304
+    private static void applyDataRowStyles(Row row, CellStyle[] styles, Float rowHeight)
305
+    {
306
+        if (row == null || styles == null)
307
+        {
308
+            return;
309
+        }
310
+        if (rowHeight != null && rowHeight > 0)
311
+        {
312
+            row.setHeightInPoints(rowHeight);
313
+        }
314
+        for (int col = 0; col < styles.length; col++)
315
+        {
316
+            CellStyle style = styles[col];
317
+            if (style == null)
318
+            {
319
+                continue;
320
+            }
321
+            Cell cell = row.getCell(col);
322
+            if (cell == null)
323
+            {
324
+                cell = row.createCell(col);
325
+            }
326
+            cell.setCellStyle(style);
327
+        }
328
+    }
329
+
259 330
     private static void mergeTotalLabelCells(Sheet sheet, int rowIndex)
260 331
     {
261 332
         int firstCol = LivestockOwnershipHouseholdExcelParser.COL_SEQ;

+ 81 - 6
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/VaccinationDataExcelExporter.java

@@ -6,6 +6,7 @@ import java.util.List;
6 6
 import java.util.regex.Matcher;
7 7
 import java.util.regex.Pattern;
8 8
 import org.apache.poi.ss.usermodel.Cell;
9
+import org.apache.poi.ss.usermodel.CellStyle;
9 10
 import org.apache.poi.ss.usermodel.DataFormatter;
10 11
 import org.apache.poi.ss.usermodel.Row;
11 12
 import org.apache.poi.ss.usermodel.Sheet;
@@ -73,8 +74,11 @@ public class VaccinationDataExcelExporter
73 74
             }
74 75
             updateTitleYearMonth(sheet, statYear, statMonth);
75 76
             updateFiller(sheet, fillerName);
76
-            // 官方模板:主表头 + 子表头,数据从 header+HEADER_SPAN 起
77
+            // 官方模板:主表头 + 子表头,数据从 header+HEADER_SPAN 起(Excel 约第 5 行)
77 78
             int dataStartRow = headerRowIndex + VaccinationDataExcelParser.HEADER_SPAN;
79
+            // 模板样例行带边框;清空/新建单元格会丢样式,需先捕获再回写
80
+            CellStyle[] dataRowStyles = captureDataRowStyles(sheet, dataStartRow);
81
+            Float dataRowHeight = captureRowHeight(sheet, dataStartRow);
78 82
             clearTemplateDataRegion(sheet, dataStartRow);
79 83
             int rowIndex = dataStartRow;
80 84
             int seq = 1;
@@ -96,10 +100,12 @@ public class VaccinationDataExcelExporter
96 100
                     setIntegerCell(row, VaccinationDataExcelParser.COL_ACTUAL, report.getActualVaccinateCount());
97 101
                     setStringCell(row, VaccinationDataExcelParser.COL_RATE,
98 102
                             VaccinationDataDerivedSupport.formatImmunityRatePercent(report.getImmunityRate()));
103
+                    applyDataRowStyles(row, dataRowStyles, dataRowHeight);
99 104
                     rowIndex++;
100 105
                 }
101 106
             }
102
-            removeRowsAfter(sheet, Math.max(dataStartRow - 1, rowIndex - 1));
107
+            int lastKeepRow = rowIndex > dataStartRow ? rowIndex - 1 : dataStartRow - 1;
108
+            removeRowsAfter(sheet, lastKeepRow);
103 109
             workbook.write(out);
104 110
             return out.toByteArray();
105 111
         }
@@ -253,6 +259,63 @@ public class VaccinationDataExcelExporter
253 259
         return text.contains("填表人") || text.contains("填表时间") || text.contains("填表日期");
254 260
     }
255 261
 
262
+    /** 捕获模板数据行各列样式(边框等),供导出行复用。 */
263
+    private static CellStyle[] captureDataRowStyles(Sheet sheet, int styleSourceRow)
264
+    {
265
+        CellStyle[] styles = new CellStyle[VaccinationDataExcelParser.COL_RATE + 1];
266
+        Row src = sheet.getRow(styleSourceRow);
267
+        if (src == null)
268
+        {
269
+            return styles;
270
+        }
271
+        for (int col = 0; col < styles.length; col++)
272
+        {
273
+            Cell cell = src.getCell(col);
274
+            if (cell != null)
275
+            {
276
+                styles[col] = cell.getCellStyle();
277
+            }
278
+        }
279
+        return styles;
280
+    }
281
+
282
+    private static Float captureRowHeight(Sheet sheet, int styleSourceRow)
283
+    {
284
+        Row src = sheet.getRow(styleSourceRow);
285
+        if (src == null)
286
+        {
287
+            return null;
288
+        }
289
+        return src.getHeightInPoints();
290
+    }
291
+
292
+    /** 将模板数据行样式套到导出行(含空单元格),保证边框与模板一致。 */
293
+    private static void applyDataRowStyles(Row row, CellStyle[] styles, Float rowHeight)
294
+    {
295
+        if (row == null || styles == null)
296
+        {
297
+            return;
298
+        }
299
+        if (rowHeight != null && rowHeight > 0)
300
+        {
301
+            row.setHeightInPoints(rowHeight);
302
+        }
303
+        for (int col = 0; col < styles.length; col++)
304
+        {
305
+            CellStyle style = styles[col];
306
+            if (style == null)
307
+            {
308
+                continue;
309
+            }
310
+            Cell cell = row.getCell(col);
311
+            if (cell == null)
312
+            {
313
+                cell = row.createCell(col);
314
+            }
315
+            cell.setCellStyle(style);
316
+        }
317
+    }
318
+
256 319
     private static Row getOrCreateRow(Sheet sheet, int rowIndex)
257 320
     {
258 321
         Row row = sheet.getRow(rowIndex);
@@ -266,26 +329,38 @@ public class VaccinationDataExcelExporter
266 329
             Cell cell = row.getCell(c);
267 330
             if (cell != null)
268 331
             {
269
-                row.removeCell(cell);
332
+                cell.setBlank();
270 333
             }
271 334
         }
272 335
     }
273 336
 
274 337
     private static void setStringCell(Row row, int col, String value)
275 338
     {
276
-        if (value == null)
339
+        Cell cell = row.getCell(col);
340
+        if (cell == null)
277 341
         {
342
+            cell = row.createCell(col);
343
+        }
344
+        if (StringUtils.isEmpty(value))
345
+        {
346
+            cell.setBlank();
278 347
             return;
279 348
         }
280
-        row.createCell(col).setCellValue(value);
349
+        cell.setCellValue(value);
281 350
     }
282 351
 
283 352
     private static void setIntegerCell(Row row, int col, Integer value)
284 353
     {
354
+        Cell cell = row.getCell(col);
355
+        if (cell == null)
356
+        {
357
+            cell = row.createCell(col);
358
+        }
285 359
         if (value == null)
286 360
         {
361
+            cell.setBlank();
287 362
             return;
288 363
         }
289
-        row.createCell(col).setCellValue(value.doubleValue());
364
+        cell.setCellValue(value.doubleValue());
290 365
     }
291 366
 }

BIN
baqing-admin/src/main/resources/templates/livestock_ownership_household_import_template.xlsx


+ 86 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/LivestockOwnershipHouseholdExcelExporterTest.java

@@ -0,0 +1,86 @@
1
+package com.ruoyi.web.modules.industryservice.support;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
4
+import static org.junit.jupiter.api.Assertions.assertNotNull;
5
+import static org.junit.jupiter.api.Assertions.assertTrue;
6
+
7
+import java.io.ByteArrayInputStream;
8
+import java.util.Collections;
9
+import org.apache.poi.ss.usermodel.BorderStyle;
10
+import org.apache.poi.ss.usermodel.Cell;
11
+import org.apache.poi.ss.usermodel.Row;
12
+import org.apache.poi.ss.usermodel.Sheet;
13
+import org.apache.poi.ss.usermodel.Workbook;
14
+import org.apache.poi.ss.usermodel.WorkbookFactory;
15
+import org.junit.jupiter.api.DisplayName;
16
+import org.junit.jupiter.api.Test;
17
+import com.ruoyi.web.modules.industryservice.domain.BizLivestockOwnershipHousehold;
18
+
19
+@DisplayName("LivestockOwnershipHouseholdExcelExporter")
20
+class LivestockOwnershipHouseholdExcelExporterTest
21
+{
22
+    private final LivestockOwnershipHouseholdExcelExporter exporter = new LivestockOwnershipHouseholdExcelExporter();
23
+
24
+    @Test
25
+    @DisplayName("导出填表时间默认为系统当月,且标题统计月与填表月可不同")
26
+    void exportFillerDateDefaultsToCurrentMonth() throws Exception
27
+    {
28
+        java.time.LocalDate today = java.time.LocalDate.now();
29
+        String expectedFill = today.getYear() + "年" + today.getMonthValue() + "月";
30
+        int exportMonth = today.getMonthValue() == 1 ? 12 : today.getMonthValue() - 1;
31
+        int exportYear = today.getMonthValue() == 1 ? today.getYear() - 1 : today.getYear();
32
+
33
+        BizLivestockOwnershipHousehold row = new BizLivestockOwnershipHousehold();
34
+        row.setTownName("雅安镇");
35
+        row.setVillageDeptId(201L);
36
+        row.setVillageName("雅安镇荣嘎村");
37
+        row.setWithLivestockHouseholdCount(36);
38
+        row.setWithoutLivestockHouseholdCount(12);
39
+
40
+        byte[] bytes = exporter.export(exportYear, exportMonth, Collections.singletonList(row), "测试用户");
41
+        try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
42
+        {
43
+            Sheet sheet = LivestockOwnershipHouseholdExcelParser.resolveSheet(wb);
44
+            Row fillerRow = sheet.getRow(1);
45
+            assertNotNull(fillerRow);
46
+            String fillerText = LivestockOwnershipHouseholdExcelParser.cellString(fillerRow, 0);
47
+            assertNotNull(fillerText);
48
+            assertTrue(fillerText.contains("测试用户"), fillerText);
49
+            assertTrue(fillerText.contains(expectedFill), "填表时间应为当月: " + fillerText);
50
+            assertTrue(!fillerText.contains("X月"), fillerText);
51
+            String title = LivestockOwnershipHouseholdExcelParser.cellString(sheet.getRow(0), 0);
52
+            assertTrue(title.contains(exportYear + "年"), title);
53
+            assertTrue(title.contains("(" + exportMonth + "月)"), title);
54
+        }
55
+    }
56
+
57
+    @Test
58
+    @DisplayName("导出合计行应保留边框")
59
+    void exportTotalRowKeepsBorders() throws Exception
60
+    {
61
+        BizLivestockOwnershipHousehold row = new BizLivestockOwnershipHousehold();
62
+        row.setTownName("雅安镇");
63
+        row.setVillageDeptId(201L);
64
+        row.setVillageName("雅安镇荣嘎村");
65
+        row.setWithLivestockHouseholdCount(36);
66
+        row.setWithoutLivestockHouseholdCount(12);
67
+
68
+        byte[] bytes = exporter.export(2026, 6, Collections.singletonList(row), "测试用户");
69
+        try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
70
+        {
71
+            Sheet sheet = LivestockOwnershipHouseholdExcelParser.resolveSheet(wb);
72
+            int headerRowIndex = LivestockOwnershipHouseholdExcelParser.detectHeaderRow(sheet);
73
+            int totalRowIndex = headerRowIndex + 3;
74
+            Row totalRow = sheet.getRow(totalRowIndex);
75
+            assertNotNull(totalRow);
76
+            assertTrue("合计".equals(LivestockOwnershipHouseholdExcelParser.cellString(totalRow, 0)));
77
+            for (int col = 0; col <= LivestockOwnershipHouseholdExcelParser.COL_LAST; col++)
78
+            {
79
+                Cell cell = totalRow.getCell(col);
80
+                assertNotNull(cell, "合计行列 " + col + " 应存在");
81
+                assertNotEquals(BorderStyle.NONE, cell.getCellStyle().getBorderBottom(),
82
+                        "合计行列 " + col + " 应有下边框");
83
+            }
84
+        }
85
+    }
86
+}

+ 48 - 6
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/VaccinationDataExcelExporterTest.java

@@ -1,10 +1,13 @@
1 1
 package com.ruoyi.web.modules.industryservice.support;
2 2
 
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
3 4
 import static org.junit.jupiter.api.Assertions.assertNotNull;
4 5
 import static org.junit.jupiter.api.Assertions.assertTrue;
5 6
 
6 7
 import java.io.ByteArrayInputStream;
8
+import java.util.Arrays;
7 9
 import java.util.Collections;
10
+import org.apache.poi.ss.usermodel.BorderStyle;
8 11
 import org.apache.poi.ss.usermodel.Row;
9 12
 import org.apache.poi.ss.usermodel.Sheet;
10 13
 import org.apache.poi.ss.usermodel.Workbook;
@@ -27,12 +30,7 @@ class VaccinationDataExcelExporterTest
27 30
         int exportMonth = today.getMonthValue() == 1 ? 12 : today.getMonthValue() - 1;
28 31
         int exportYear = today.getMonthValue() == 1 ? today.getYear() - 1 : today.getYear();
29 32
 
30
-        BizVaccinationData row = new BizVaccinationData();
31
-        row.setTownName("雅安镇");
32
-        row.setVillageDeptId(201L);
33
-        row.setVillageName("雅安镇荣嘎村");
34
-        row.setShouldVaccinateCount(100);
35
-        row.setActualVaccinateCount(90);
33
+        BizVaccinationData row = sample("雅安镇", "雅安镇荣嘎村", 100, 90);
36 34
 
37 35
         byte[] bytes = exporter.export(exportYear, exportMonth, Collections.singletonList(row), "测试用户");
38 36
         try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
@@ -50,4 +48,48 @@ class VaccinationDataExcelExporterTest
50 48
             assertTrue(title.contains("(" + exportMonth + "月)"), title);
51 49
         }
52 50
     }
51
+
52
+    @Test
53
+    @DisplayName("导出第5、6行(数据行)保留模板边框")
54
+    void exportDataRowsKeepBorders() throws Exception
55
+    {
56
+        BizVaccinationData a = sample("雅安镇", "雅安镇荣嘎村", 12000, 11800);
57
+        BizVaccinationData b = sample("雅安镇", "雅安镇公随村", 13000, 12695);
58
+        byte[] bytes = exporter.export(2026, 7, Arrays.asList(a, b), "admin");
59
+        try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
60
+        {
61
+            Sheet sheet = VaccinationDataExcelParser.resolveSheet(wb);
62
+            int header = VaccinationDataExcelParser.detectHeaderRow(sheet);
63
+            int dataStart = header + VaccinationDataExcelParser.HEADER_SPAN;
64
+            // Excel 第 5、6 行(1-based)即 dataStart / dataStart+1
65
+            assertEquals(4, dataStart);
66
+            Row row5 = sheet.getRow(dataStart);
67
+            Row row6 = sheet.getRow(dataStart + 1);
68
+            assertNotNull(row5);
69
+            assertNotNull(row6);
70
+            assertHasBorder(row5, "第5行");
71
+            assertHasBorder(row6, "第6行");
72
+        }
73
+    }
74
+
75
+    private static void assertHasBorder(Row row, String label)
76
+    {
77
+        for (int col = 0; col <= VaccinationDataExcelParser.COL_RATE; col++)
78
+        {
79
+            assertNotNull(row.getCell(col), label + " 列" + col + " 单元格为空");
80
+            BorderStyle bottom = row.getCell(col).getCellStyle().getBorderBottom();
81
+            assertTrue(bottom != BorderStyle.NONE, label + " 列" + col + " 缺少下边框");
82
+        }
83
+    }
84
+
85
+    private static BizVaccinationData sample(String town, String village, int should, int actual)
86
+    {
87
+        BizVaccinationData row = new BizVaccinationData();
88
+        row.setTownName(town);
89
+        row.setVillageDeptId((long) village.hashCode());
90
+        row.setVillageName(village);
91
+        row.setShouldVaccinateCount(should);
92
+        row.setActualVaccinateCount(actual);
93
+        return row;
94
+    }
53 95
 }