Archived
feat: fill annual report table cells with generated investment data
This commit is contained in:
+3
-3
@@ -5,7 +5,7 @@ info:
|
|||||||
contact:
|
contact:
|
||||||
name: 吕冬
|
name: 吕冬
|
||||||
servers:
|
servers:
|
||||||
- url: https://closein.site
|
- url: https://closein.cn
|
||||||
description: 生产服务器(HTTPS)
|
description: 生产服务器(HTTPS)
|
||||||
- url: http://localhost:10086
|
- url: http://localhost:10086
|
||||||
description: 本地开发服务器(HTTP)
|
description: 本地开发服务器(HTTP)
|
||||||
@@ -42,7 +42,7 @@ paths:
|
|||||||
code: 0
|
code: 0
|
||||||
msg: "success"
|
msg: "success"
|
||||||
data:
|
data:
|
||||||
url: "https://closein.site/api/docx/download?id=d8d4122719c8465ea75ab62b37f6ce10"
|
url: "https://closein.cn/api/docx/download?id=d8d4122719c8465ea75ab62b37f6ce10"
|
||||||
fileName: "annualReport.docx"
|
fileName: "annualReport.docx"
|
||||||
"400":
|
"400":
|
||||||
description: 参数校验失败(exportId 为空 / businessData 为空)
|
description: 参数校验失败(exportId 为空 / businessData 为空)
|
||||||
@@ -177,7 +177,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
description: 文件下载 URL,由 origin + 路径组成
|
description: 文件下载 URL,由 origin + 路径组成
|
||||||
example: "https://closein.site/api/docx/download?id=d8d4122719c8465ea75ab62b37f6ce10"
|
example: "https://closein.cn/api/docx/download?id=d8d4122719c8465ea75ab62b37f6ce10"
|
||||||
fileName:
|
fileName:
|
||||||
type: string
|
type: string
|
||||||
description: 文件名
|
description: 文件名
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package com.gmp.mock.template.annualReport;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||||
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
||||||
@@ -16,24 +18,38 @@ import com.deepoove.poi.XWPFTemplate;
|
|||||||
import com.gmp.mock.template.TemplateProvider;
|
import com.gmp.mock.template.TemplateProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* poi-tl based template provider.
|
* 年度报告模板填充器。
|
||||||
*
|
*
|
||||||
* Simple text placeholders ({{title}}, {{companyName}}, etc.) are handled
|
* 模板含一个固定表格,行结构如下:
|
||||||
* by poi-tl directly. Table rows are built programmatically with POI API
|
* Row 0: 表头(序号 | 专业类别 | 合计 | yearA | yearB | yearC)
|
||||||
* after poi-tl rendering, because poi-tl's table loop syntax can be
|
* Row 1: 总计(汇总行)
|
||||||
* finicky with raw XML templates.
|
* Row 2~11: 各投资分类(电网基建/小型基建/技改/科技/信息化)
|
||||||
*
|
*
|
||||||
* To edit the template: open the .docx in Word, write {{placeholders}}.
|
* postProcess() 遍历现有行,按行名填充生成的投资数据到单元格。
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class AnnualReportProvider implements TemplateProvider {
|
public class AnnualReportProvider implements TemplateProvider {
|
||||||
|
|
||||||
private static final List<Map<String, String>> METRICS = List.of(
|
/** 行索引 → 生成策略 */
|
||||||
Map.of("name", "营业收入", "value", "¥ 1,234,567", "change", "+15.3%"),
|
private static final List<RowConfig> ROW_CONFIGS = List.of(
|
||||||
Map.of("name", "净利润", "value", "¥ 321,098", "change", "+8.7%"),
|
new RowConfig(1, "总计", true), // 汇总行:自动求和
|
||||||
Map.of("name", "毛利率", "value", "45.2%", "change", "+2.1%")
|
new RowConfig(2, "电网基建", true), // 分类标题行
|
||||||
|
new RowConfig(3, "主网基建", false),
|
||||||
|
new RowConfig(4, "配网基建", false),
|
||||||
|
new RowConfig(5, "小型基建", true), // 分类标题行(只有一项)
|
||||||
|
new RowConfig(6, "技改项目", true), // 分类标题行
|
||||||
|
new RowConfig(7, "生产技改", false),
|
||||||
|
new RowConfig(8, "营销技改", false),
|
||||||
|
new RowConfig(9, "其他技改", false),
|
||||||
|
new RowConfig(10, "科技项目", true), // 分类标题行(只有一项)
|
||||||
|
new RowConfig(11, "信息化项目", true) // 分类标题行(只有一项)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 非标题行的行索引列表(需要实际填充数据) */
|
||||||
|
private static final List<Integer> DATA_ROW_INDEXES = List.of(3, 4, 7, 8, 9, 10, 11);
|
||||||
|
|
||||||
|
private record RowConfig(int index, String name, boolean isHeader) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTemplateId() {
|
public String getTemplateId() {
|
||||||
return "annualReport";
|
return "annualReport";
|
||||||
@@ -59,27 +75,90 @@ public class AnnualReportProvider implements TemplateProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getDataModel() {
|
public Map<String, Object> getDataModel() {
|
||||||
|
int yearA = 2024;
|
||||||
|
int yearB = 2025;
|
||||||
|
int yearC = 2026;
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"title", "2025 年度工作报告",
|
"title", "2025 年度工作报告",
|
||||||
"companyName", "示例科技有限公司",
|
"companyName", "示例科技有限公司",
|
||||||
"reportPeriod", "2025年1月 - 2025年12月",
|
"reportPeriod", "2025年1月 - 2025年12月",
|
||||||
"generatedDate", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))
|
"generatedDate", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")),
|
||||||
|
"yearA", String.valueOf(yearA),
|
||||||
|
"yearB", String.valueOf(yearB),
|
||||||
|
"yearC", String.valueOf(yearC),
|
||||||
|
"snhj", "300",
|
||||||
|
"dwjjtz", "200"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postProcess(XWPFDocument document) {
|
public void postProcess(XWPFDocument document) {
|
||||||
List<XWPFTable> tables = document.getTables();
|
List<XWPFTable> tables = document.getTables();
|
||||||
if (tables.isEmpty()) {
|
if (tables.isEmpty()) return;
|
||||||
return; // 模板中无表格,跳过
|
|
||||||
}
|
|
||||||
// Add data rows to the first table in the document
|
|
||||||
XWPFTable table = tables.get(0);
|
XWPFTable table = tables.get(0);
|
||||||
for (Map<String, String> metric : METRICS) {
|
|
||||||
XWPFTableRow row = table.createRow();
|
// 1. 为每个数据行生成随机投资数据
|
||||||
row.getCell(0).setText(metric.get("name"));
|
Map<Integer, long[]> rowData = new LinkedHashMap<>();
|
||||||
row.getCell(1).setText(metric.get("value"));
|
long grandTotal = 0;
|
||||||
row.getCell(2).setText(metric.get("change"));
|
long grandYearA = 0;
|
||||||
|
long grandYearB = 0;
|
||||||
|
long grandYearC = 0;
|
||||||
|
|
||||||
|
// 分类与子项分配比例
|
||||||
|
Map<Integer, List<Integer>> categoryChildren = Map.of(
|
||||||
|
2, List.of(3, 4), // 电网基建 → 主网基建 + 配网基建
|
||||||
|
5, List.of(5), // 小型基建(自身就是数据行)
|
||||||
|
6, List.of(7, 8, 9), // 技改项目 → 生产技改 + 营销技改 + 其他技改
|
||||||
|
10, List.of(10), // 科技项目
|
||||||
|
11, List.of(11) // 信息化项目
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var entry : categoryChildren.entrySet()) {
|
||||||
|
int catRowIdx = entry.getKey();
|
||||||
|
List<Integer> childIndexes = entry.getValue();
|
||||||
|
|
||||||
|
// 为子项生成随机数据
|
||||||
|
long catTotal = 0, catA = 0, catB = 0, catC = 0;
|
||||||
|
for (int childIdx : childIndexes) {
|
||||||
|
long a = randomInvest();
|
||||||
|
long b = randomInvest();
|
||||||
|
long c = randomInvest();
|
||||||
|
long total = a + b + c;
|
||||||
|
rowData.put(childIdx, new long[]{total, a, b, c});
|
||||||
|
catTotal += total; catA += a; catB += b; catC += c;
|
||||||
}
|
}
|
||||||
|
// 分类标题行 = 子项之和
|
||||||
|
rowData.put(catRowIdx, new long[]{catTotal, catA, catB, catC});
|
||||||
|
grandTotal += catTotal; grandYearA += catA; grandYearB += catB; grandYearC += catC;
|
||||||
|
}
|
||||||
|
// 总计行
|
||||||
|
rowData.put(1, new long[]{grandTotal, grandYearA, grandYearB, grandYearC});
|
||||||
|
|
||||||
|
// 2. 填充表格
|
||||||
|
for (var entry : rowData.entrySet()) {
|
||||||
|
int rowIdx = entry.getKey();
|
||||||
|
long[] vals = entry.getValue();
|
||||||
|
XWPFTableRow row = table.getRow(rowIdx);
|
||||||
|
if (row == null) continue;
|
||||||
|
setCellText(row, 2, fmtLong(vals[0])); // 合计
|
||||||
|
setCellText(row, 3, fmtLong(vals[1])); // yearA
|
||||||
|
setCellText(row, 4, fmtLong(vals[2])); // yearB
|
||||||
|
setCellText(row, 5, fmtLong(vals[3])); // yearC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCellText(XWPFTableRow row, int col, String text) {
|
||||||
|
if (row.getCell(col) != null) {
|
||||||
|
row.getCell(col).setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long randomInvest() {
|
||||||
|
// 生成 500~50000 万元之间的随机投资额
|
||||||
|
return ThreadLocalRandom.current().nextLong(500, 50001);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fmtLong(long v) {
|
||||||
|
return String.format("%,d", v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ spring:
|
|||||||
name: gmp-mock-service
|
name: gmp-mock-service
|
||||||
|
|
||||||
gmp:
|
gmp:
|
||||||
download-origin: https://closein.site
|
download-origin: https://closein.cn
|
||||||
|
|||||||
Reference in New Issue
Block a user