From a76a8cce476ceccbc061a3f51681b6090a7d3741 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期五, 11 九月 2026 17:32:41 +0800
Subject: [PATCH] feat: 汇总大表排名页随报表期刷新 + 同比列统一百分数显示
---
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 214 insertions(+), 2 deletions(-)
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
index c8ae866..ceea4f1 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -53,6 +53,7 @@
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -3351,10 +3352,11 @@
int dbFilled = summaryWorkbookFiller.fillMonthlyLedger(wb, effPeriod, year, effMonth, dbProblems);
int yoyFilled = summaryWorkbookFiller.ensureMonthYoyFormulas(wb, year, effMonth);
int monthFormulaFilled = summaryWorkbookFiller.ensureMonthFormulas(wb, year, effMonth);
+ int rankRefreshed = refreshSummaryRankSheets(wb, effPeriod, "month");
int widthApplied = applySummaryReferenceLayout(wb);
log.info("姹囨�诲伐浣滅翱鏁版嵁椹卞姩鍥炲~瀹屾垚锛氬啓鍏ュ綋鏈堝�兼牸鏁�={}锛岃ˉ鍐欏綋鏈堝悓姣斿叕寮忔牸鏁�={}锛岃ˉ鍐欏綋鏈堝悎璁″叕寮忔牸鏁�={}锛�"
- + "鎸夈�婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪榻愬垪瀹藉垪鏁�={}锛岀己鏁版彁绀�={}",
- dbFilled, yoyFilled, monthFormulaFilled, widthApplied, dbProblems);
+ + "鍒锋柊鎺掑悕椤垫暟鍊兼牸鏁�={}锛屾寜銆婄敓鎴恄閬撹矾杩愯緭閲忔眹鎬昏〃銆嬪榻愬垪瀹藉垪鏁�={}锛岀己鏁版彁绀�={}",
+ dbFilled, yoyFilled, monthFormulaFilled, rankRefreshed, widthApplied, dbProblems);
applyTwoDecimalFormat(wb); // 鏁板�兼樉绀轰袱浣嶅皬鏁帮紙淇濈暀鍏ㄧ簿搴︼級锛�% / 鏃ユ湡绛夋棦鏈夋牱寮忎笉鏀瑰彉
if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr();
wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // 杩樺師/鏀瑰啓鍏紡鍚庢墦寮�鍗抽噸绠�
@@ -3441,6 +3443,216 @@
return max;
}
+ // ==================== 姹囨�诲伐浣滅翱鍐呯殑鎺掑悕椤靛埛鏂� ====================
+
+ /**
+ * 姹囨�诲伐浣滅翱閲岀殑銆岃揣杩愰噺鎺掑悕 / 璐ц繍鍛ㄨ浆閲忔帓鍚� / 涓彛寰勬帓鍚嶃�嶅湪姣嶇増閲屾槸涓婁竴鎶ヨ〃鏈熺殑闈欐�佸�硷紝
+ * 瀵煎嚭鏃剁敤鐙珛鎺掑悕琛紙銆婄敓鎴恄璐ц繍閲忔帓鍚嶃�嬨�婄敓鎴恄鍛ㄨ浆閲忔帓鍚嶃�嬨�婄敓鎴恄涓彛寰勬帓鍚嶃�嬶紝涓庣敤鎴锋棩甯告牳瀵圭殑鍙e緞涓�鑷达級
+ * 鐨勭粨鏋滃埛鏂版暟鍊硷紝閬垮厤姹囨�昏〃閲岀殑鎺掑悕椤靛仠鐣欏湪鏃ф湀浠姐��
+ * 鏁板�兼牸鎼�笺�佸叕寮忔牸鎸夊亸绉诲钩绉诲叕寮忓悗鍐欏叆锛涚洰鏍囨牸鏈韩宸叉槸鍏紡鐨勶紙SUM/RANK/鍗犳瘮/琛ユ暟锛変繚鐣欎笉鍔紝鎵撳紑鏃舵寜鏂版暟鎹噸绠椼��
+ */
+ private int refreshSummaryRankSheets(XSSFWorkbook wb, String period, String mode) throws Exception {
+ int n = 0;
+ n += copyRankValues(wb, "璐ц繍閲忔帓鍚�", exportFreightRank(period, mode), new int[][]{{0, 0}, {22, 0}});
+ n += copyRankValues(wb, "璐ц繍鍛ㄨ浆閲忔帓鍚�", exportTurnoverRank(period, mode), new int[][]{{0, 0}, {22, 0}});
+ n += copyRankValues(wb, "涓彛寰勬帓鍚�", exportPassengerMidRank(period, mode), new int[][]{{0, 0}, {0, 10}});
+ return n;
+ }
+
+ /**
+ * 鎶婄嫭绔嬫帓鍚嶈〃鐨勫悇涓潡鎸夋爣棰樺榻愭惉杩涙眹鎬诲ぇ琛ㄥ搴旈〉銆�
+ * srcBlocks[i] = 婧愬潡 i 鐨勮捣濮� {琛�, 鍒梷锛�0 鍩猴級锛涚洰鏍囧潡鎸夋爣棰橀『搴忥紙绱鍧楀湪鍓嶃�佸綋鏈堝潡鍦ㄥ悗锛夊搴斻��
+ * 鏁板�兼牸鐩存帴鎼�硷紱婧愪晶鏄叕寮忕殑锛堟帓鍚�/鍗犳瘮绛夛級鎸夌洰鏍囧潡鍋忕Щ鏁翠綋骞崇Щ鍚庡啓鍏ュ叕寮忥紝鎵撳紑鏃舵寜姹囨�昏〃鑷韩鏁版嵁閲嶇畻銆�
+ */
+ private int copyRankValues(XSSFWorkbook dstWb, String dstSheetName, byte[] srcBytes, int[][] srcBlocks) throws Exception {
+ XSSFSheet dst = dstWb.getSheet(dstSheetName);
+ if (dst == null) return 0;
+ List<XSSFCell> dstTitles = rankTitleCells(dst);
+ if (dstTitles.isEmpty()) return 0;
+ int copied = 0;
+ FormulaEvaluator dstEv = dstWb.getCreationHelper().createFormulaEvaluator();
+ try (InputStream sin = new ByteArrayInputStream(srcBytes);
+ XSSFWorkbook srcWb = new XSSFWorkbook(sin)) {
+ XSSFSheet src = srcWb.getSheetAt(0);
+ FormulaEvaluator ev = srcWb.getCreationHelper().createFormulaEvaluator();
+ int blocks = Math.min(srcBlocks.length, dstTitles.size());
+ for (int b = 0; b < blocks; b++) {
+ int sr0 = srcBlocks[b][0];
+ int sc0 = srcBlocks[b][1];
+ XSSFCell dstTitle = dstTitles.get(b);
+ int rowOff = dstTitle.getRowIndex() - sr0;
+ int colOff = dstTitle.getColumnIndex() - sc0;
+ int h = rankBlockHeight(src, sr0, sc0);
+ int w = rankBlockWidth(src, sr0, sc0);
+ for (int r = 0; r < h; r++) {
+ Row srcRow = src.getRow(sr0 + r);
+ if (srcRow == null) continue;
+ Row dstRow = dst.getRow(sr0 + r + rowOff);
+ if (dstRow == null) dstRow = dst.createRow(sr0 + r + rowOff);
+ for (int c = 0; c < w; c++) {
+ Cell sc = srcRow.getCell(sc0 + c);
+ if (sc == null) continue;
+ int dc = sc0 + c + colOff;
+ if (r == 0) {
+ // 鏍囬琛岋細鍚屾鍧楁爣棰樻枃妗堛�傛瘝鐗堟爣棰樻槸 inlineStr锛孭OI 鐨� setCellValue 鍙啓 <v>銆�
+ // 涓嶆洿鏂� <is>锛孍xcel 浼氱户缁樉绀烘棫鏈堜唤锛屽繀椤诲厛 setBlank 娓呮帀鍐嶅啓銆�
+ Cell dt = dstRow.getCell(dc);
+ if (dt == null) dt = rankCell(dstRow, dc);
+ if (sc.getCellType() == CellType.STRING && dt.getCellType() != CellType.FORMULA) {
+ String text = sc.getStringCellValue();
+ String old = dt.getCellType() == CellType.STRING ? dt.getStringCellValue() : null;
+ if (text != null && !text.equals(old)) {
+ dt.setBlank();
+ dt.setCellValue(text);
+ }
+ }
+ continue;
+ }
+ Cell dcCell = dstRow.getCell(dc);
+ if (dcCell != null && dcCell.getCellType() == CellType.FORMULA) continue; // 淇濈暀姣嶇増鑷繁鐨勫叕寮�
+ if (sc.getCellType() == CellType.FORMULA) {
+ // 婧愪晶鍏紡锛堟帓鍚�/鍗犳瘮锛夛細鎸夌洰鏍囧潡鍋忕Щ骞崇Щ鍚庡師鏍峰啓鍏ワ紝鍐嶆寜姹囨�昏〃鑷韩鏁版嵁姹傚�肩紦瀛樸��
+ // 涓嶇洿鎺ユ惉 POI 瀵规簮琛ㄧ殑姹傚�肩粨鏋滐紙濡� RANK 瀵规墜宸ョ暀绌虹殑鍚屾瘮涓�寰嬭繑鍥� 1锛夈��
+ String f = sc.getCellFormula();
+ if (f == null || f.isEmpty()) continue;
+ String shifted = shiftFormula(f, colOff, rowOff);
+ if (dcCell == null) dcCell = rankCell(dstRow, dc);
+ dcCell.setCellFormula(shifted);
+ try {
+ dstEv.evaluateFormulaCell(dcCell);
+ } catch (Exception ignore) {
+ // 姹傚�煎け璐ヤ笉闃诲瀵煎嚭锛氭墦寮�鏃剁敱 Excel/WPS 閲嶇畻
+ }
+ copied++;
+ continue;
+ }
+ Double v = rankNumeric(sc, ev);
+ if (v == null) continue;
+ if (dcCell == null) dcCell = rankCell(dstRow, dc);
+ dcCell.setCellValue(v);
+ copied++;
+ }
+ }
+ }
+ }
+ return copied;
+ }
+
+ /** 鐩爣鍧楃己鏍兼椂鏂板缓锛屽苟娌跨敤鍚岃宸﹂偦鏍兼牱寮忥紙闃叉柊鏍间涪杈规/鐧惧垎鏁版牸寮忥級 */
+ private Cell rankCell(Row row, int col) {
+ Cell c = row.createCell(col);
+ Cell left = col > 0 ? row.getCell(col - 1) : null;
+ if (left != null) c.setCellStyle(left.getCellStyle());
+ return c;
+ }
+
+ /** 鍏紡鍐呮墍鏈� A1 寮曠敤鏁翠綋骞崇Щ锛氬垪 +dCol銆佽 +dRow锛圫UM/RANK 绛夊嚱鏁板悕鍚庢帴鎷彿涓嶄細琚尮閰嶏級 */
+ private String shiftFormula(String formula, int dCol, int dRow) {
+ if (formula == null || (dCol == 0 && dRow == 0)) return formula;
+ java.util.regex.Matcher m = java.util.regex.Pattern
+ .compile("(?<![A-Za-z0-9_$])([$]?)([A-Z]{1,3})([$]?)([0-9]+)")
+ .matcher(formula);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String col = m.group(2);
+ int idx = 0;
+ for (int i = 0; i < col.length(); i++) idx = idx * 26 + (col.charAt(i) - 'A' + 1);
+ int nidx = Math.max(1, idx + dCol);
+ StringBuilder nc = new StringBuilder();
+ while (nidx > 0) {
+ int rem = (nidx - 1) % 26;
+ nc.insert(0, (char) ('A' + rem));
+ nidx = (nidx - 1) / 26;
+ }
+ int nrow = Math.max(1, Integer.parseInt(m.group(4)) + dRow);
+ m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(m.group(1) + nc + m.group(3) + nrow));
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+
+ /** 鎺掑悕椤甸噷鐨勨�滃潡鏍囬鈥濆崟鍏冩牸锛堝惈鈥滃叏鐪佸垎甯傚窞鈥濈殑璇存槑鏂囧瓧锛夛紝鎸夎銆佸垪椤哄簭杩斿洖 */
+ private List<XSSFCell> rankTitleCells(Sheet sh) {
+ List<XSSFCell> out = new ArrayList<>();
+ for (int r = sh.getFirstRowNum(); r <= sh.getLastRowNum(); r++) {
+ Row row = sh.getRow(r);
+ if (row == null) continue;
+ for (int c = row.getFirstCellNum(); c < row.getLastCellNum(); c++) {
+ Cell cell = row.getCell(c);
+ if (cell == null || cell.getCellType() != CellType.STRING) continue;
+ String t = cell.getStringCellValue();
+ if (t != null && t.contains("鍏ㄧ渷鍒嗗競宸�")) out.add((XSSFCell) cell);
+ }
+ }
+ return out;
+ }
+
+ /** 鍧楅珮搴︼細浠庤捣濮嬭鍚戜笅鐩村埌鏁磋涓虹┖锛堝湪鍧楀垪鑼冨洿鍐咃級 */
+ private int rankBlockHeight(Sheet sh, int r0, int c0) {
+ int h = 0;
+ for (int r = r0; r <= sh.getLastRowNum(); r++) {
+ Row row = sh.getRow(r);
+ boolean any = false;
+ if (row != null) {
+ int last = row.getLastCellNum();
+ for (int c = c0; c < last; c++) {
+ if (rankHasContent(row.getCell(c))) { any = true; break; }
+ }
+ }
+ if (!any) break;
+ h++;
+ }
+ return h;
+ }
+
+ /** 鍧楀搴︼細浠庤捣濮嬪垪鍚戝彸鐩村埌鏁村垪涓虹┖锛堝湪鍧楄鑼冨洿鍐咃紝鎵弿涓婇檺 40 鍒楋級 */
+ private int rankBlockWidth(Sheet sh, int r0, int c0) {
+ int w = 0;
+ for (int c = c0; c < c0 + 40; c++) {
+ boolean any = false;
+ for (int r = r0; r < r0 + 30; r++) {
+ Row row = sh.getRow(r);
+ if (row != null && rankHasContent(row.getCell(c))) { any = true; break; }
+ }
+ if (!any) break;
+ w++;
+ }
+ return w;
+ }
+
+ private boolean rankHasContent(Cell c) {
+ if (c == null) return false;
+ CellType t = c.getCellType();
+ if (t == CellType.BLANK) return false;
+ if (t == CellType.STRING) {
+ String v = c.getStringCellValue();
+ return v != null && !v.trim().isEmpty();
+ }
+ return true;
+ }
+
+ /** 鍙栧崟鍏冩牸鏁板�硷紙鍏紡鍙栨眰鍊肩粨鏋滐紝绾暟瀛楁枃鏈寜鏁板�艰В鏋愶級锛屽彇涓嶅埌杩斿洖 null */
+ private Double rankNumeric(Cell c, FormulaEvaluator ev) {
+ try {
+ CellType t = c.getCellType();
+ if (t == CellType.NUMERIC) return c.getNumericCellValue();
+ if (t == CellType.FORMULA) {
+ org.apache.poi.ss.usermodel.CellValue cv = ev.evaluate(c);
+ return cv != null && cv.getCellType() == CellType.NUMERIC ? cv.getNumberValue() : null;
+ }
+ if (t == CellType.STRING) {
+ String v = c.getStringCellValue();
+ if (v == null) return null;
+ String x = v.replace(",", "").trim();
+ if (x.isEmpty()) return null;
+ try { return Double.parseDouble(x); } catch (NumberFormatException ignore) { return null; }
+ }
+ } catch (Exception ignore) {
+ return null;
+ }
+ return null;
+ }
+
/** 瀹氫綅鐗堝紡鍙傜収浠� docs/鐢熸垚姹囨�诲ぇ琛�/鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛堜紭鍏堢骇鍚屾瘝鐗堬細閰嶇疆鐩綍 鈫� user.dir 鐩稿 鈫� 涓婄骇锛� */
private File resolveSummaryLayoutRef() {
String rel = summaryTemplateDir;
--
Gitblit v1.9.1