From 0a3f49f3250424582ff0d9dcea1ef12d89c85e44 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期六, 19 九月 2026 00:23:12 +0800
Subject: [PATCH] feat(报表): 新增客运站/物流站场投资明细表导出 + 中口径同比口径修复 + 隐藏网约车订单入口
---
traffic-audit-web/src/views/ReportExport.vue | 13 +
docs/生成汇总大表/生成_道路运输量汇总表.xlsx | 0
traffic-audit-web/src/views/DataImport.vue | 12
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java | 30 ++
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | 494 +++++++++++++++++++++++++++++++++++++++++++++---
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java | 16 +
docs/城市客运/模板_网约车订单及全省总量.xlsx | 0
7 files changed, 516 insertions(+), 49 deletions(-)
diff --git "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\347\275\221\347\272\246\350\275\246\350\256\242\345\215\225\345\217\212\345\205\250\347\234\201\346\200\273\351\207\217.xlsx" "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\347\275\221\347\272\246\350\275\246\350\256\242\345\215\225\345\217\212\345\205\250\347\234\201\346\200\273\351\207\217.xlsx"
index 4291040..2528a5d 100644
--- "a/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\347\275\221\347\272\246\350\275\246\350\256\242\345\215\225\345\217\212\345\205\250\347\234\201\346\200\273\351\207\217.xlsx"
+++ "b/docs/\345\237\216\345\270\202\345\256\242\350\277\220/\346\250\241\346\235\277_\347\275\221\347\272\246\350\275\246\350\256\242\345\215\225\345\217\212\345\205\250\347\234\201\346\200\273\351\207\217.xlsx"
Binary files differ
diff --git "a/docs/\347\224\237\346\210\220\346\261\207\346\200\273\345\244\247\350\241\250/\347\224\237\346\210\220_\351\201\223\350\267\257\350\277\220\350\276\223\351\207\217\346\261\207\346\200\273\350\241\250.xlsx" "b/docs/\347\224\237\346\210\220\346\261\207\346\200\273\345\244\247\350\241\250/\347\224\237\346\210\220_\351\201\223\350\267\257\350\277\220\350\276\223\351\207\217\346\261\207\346\200\273\350\241\250.xlsx"
index f850836..0c755dd 100644
--- "a/docs/\347\224\237\346\210\220\346\261\207\346\200\273\345\244\247\350\241\250/\347\224\237\346\210\220_\351\201\223\350\267\257\350\277\220\350\276\223\351\207\217\346\261\207\346\200\273\350\241\250.xlsx"
+++ "b/docs/\347\224\237\346\210\220\346\261\207\346\200\273\345\244\247\350\241\250/\347\224\237\346\210\220_\351\201\223\350\267\257\350\277\220\350\276\223\351\207\217\346\261\207\346\200\273\350\241\250.xlsx"
Binary files differ
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java
index 947eff3..903f103 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/WycSplitCalc.java
@@ -4,8 +4,10 @@
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
import com.trafficaudit.dataimport.entity.WycOrderMonthly;
+import com.trafficaudit.dataimport.entity.WycTotalMonthly;
import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
import com.trafficaudit.dataimport.mapper.WycOrderMonthlyMapper;
+import com.trafficaudit.dataimport.mapper.WycTotalMonthlyMapper;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
@@ -80,6 +82,8 @@
private CityTaxiMonthlyMapper cityTaxiMapper;
@Resource
private WycOrderMonthlyMapper wycOrderMapper;
+ @Resource
+ private WycTotalMonthlyMapper wycTotalMapper;
@Value("${city-passenger.template-dir:docs/鍩庡競瀹㈣繍}")
private String templateDir;
@@ -254,14 +258,30 @@
if (sum <= 0) return null;
double orderSum = sum;
if (row.getOrderSum() != null && row.getOrderSum() > 0) orderSum = row.getOrderSum();
+ double pinK = row.getPinK() == null ? 0.0 : row.getPinK();
+ double pinCK = row.getPinCK() == null ? 0.0 : row.getPinCK();
+ double pinZ = row.getPinZ() == null ? 0.0 : row.getPinZ();
+ double pinCZ = row.getPinCZ() == null ? 0.0 : row.getPinCZ();
+ boolean pinFix = false;
+ // pin 鍏滃簳锛氳鍗曡〃 pin 鍒椾负绌�/涓� 0 鏃跺洖钀� wyc_total_monthly锛坧in 涓撹〃锛�
+ if (pinK <= 0 || pinCK <= 0 || pinZ <= 0 || pinCZ <= 0) {
+ WycTotalMonthly tot = wycTotalMapper.selectOne(new LambdaQueryWrapper<WycTotalMonthly>()
+ .eq(WycTotalMonthly::getReportPeriod, period).last("LIMIT 1"));
+ if (tot != null) {
+ if (pinK <= 0 && tot.getPinK() != null && tot.getPinK() > 0) { pinK = tot.getPinK(); pinFix = true; }
+ if (pinCK <= 0 && tot.getPinCK() != null && tot.getPinCK() > 0) { pinCK = tot.getPinCK(); pinFix = true; }
+ if (pinZ <= 0 && tot.getPinZ() != null && tot.getPinZ() > 0) { pinZ = tot.getPinZ(); pinFix = true; }
+ if (pinCZ <= 0 && tot.getPinCZ() != null && tot.getPinCZ() > 0) { pinCZ = tot.getPinCZ(); pinFix = true; }
+ }
+ }
Map<String, Object> map = new LinkedHashMap<>();
- map.put("pinK", row.getPinK() == null ? 0.0 : row.getPinK());
- map.put("pinCK", row.getPinCK() == null ? 0.0 : row.getPinCK());
- map.put("pinZ", row.getPinZ() == null ? 0.0 : row.getPinZ());
- map.put("pinCZ", row.getPinCZ() == null ? 0.0 : row.getPinCZ());
+ map.put("pinK", pinK);
+ map.put("pinCK", pinCK);
+ map.put("pinZ", pinZ);
+ map.put("pinCZ", pinCZ);
map.put("orders", orders);
map.put("orderSum", orderSum);
- map.put("source", "db");
+ map.put("source", pinFix ? "db+wyc_total" : "db");
return map;
}
private File findInput() throws Exception {
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
index efe7696..b955d53 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
@@ -155,6 +155,22 @@
return buildResponse(reportService.investCounty(period, mode), "鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls");
}
+ /** 鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx锛堟姇璧勬ā鍧楁槑缁嗚〃1锛氬杩愮珯鍦猴級 */
+ @GetMapping("/export/investPassengerDetail")
+ public ResponseEntity<byte[]> exportInvestPassengerDetail(@RequestParam("period") String period,
+ @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+ logExport("鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx", period);
+ return buildResponse(reportService.exportInvestPassengerDetail(period, mode), "鐢熸垚_瀹㈣繍绔欐姇璧勬槑缁嗚〃.xlsx");
+ }
+
+ /** 鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls锛堟姇璧勬ā鍧楁槑缁嗚〃2锛氱墿娴佸洯鍖猴級 */
+ @GetMapping("/export/investLogisticsDetail")
+ public ResponseEntity<byte[]> exportInvestLogisticsDetail(@RequestParam("period") String period,
+ @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+ logExport("鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls", period);
+ return buildResponse(reportService.exportInvestLogisticsDetail(period, mode), "鐢熸垚_鐗╂祦绔欏満鎶曡祫鏄庣粏.xls");
+ }
+
/** 鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愶紝杞ㄩ亾/杞浮鍙栬嚜鍏氦婧愭枃浠讹級 */
@GetMapping("/export/cityRailFerryDetail")
public ResponseEntity<byte[]> exportCityRailFerryDetail(@RequestParam("period") String period,
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 a0a48f7..f2bdde7 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
@@ -1189,18 +1189,9 @@
/** 涓彛寰勬湀鍚屾瘮锛歬=-1 琛ㄧず鎬昏锛堢彮绾�+鍏氦+鍑虹鍩庝埂+缃戠害杞﹀煄涔� 鍥涢」涔嬪拰锛夛紱鏃犲幓骞存暟鎹暀绌� */
private void fillMidYoyCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month,
String area, int k, boolean volume) {
- double v = 0.0, lv = 0.0;
- if (k == -1) {
- for (int kk = 1; kk <= 4; kk++) {
- v += midClassVal(mid, year, month, area, kk, volume);
- lv += midClassVal(mid, year - 1, month, area, kk, volume);
- }
- } else {
- v = midClassVal(mid, year, month, area, k, volume);
- lv = midClassVal(mid, year - 1, month, area, k, volume);
- }
+ Double y = midMonthYoy(mid, year, month, area, k, volume);
c.setBlank();
- if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4));
+ if (y != null) c.setCellValue(y);
}
/** 涓彛寰勭疮璁″悓姣斿垪锛�1..months 鏈堢疮璁★紱鍥涚淮榻愬叏锛� */
@@ -1221,19 +1212,13 @@
if (blockOffset < 0 || blockOffset % 5 == 0) continue;
int k = blockOffset % 5;
String area = areas.get(blockOffset / 5);
- double v = 0.0, lv = 0.0;
- for (int m = 1; m <= months; m++) {
- {
- v += midClassVal(mid, year, m, area, k, volume);
- lv += midClassVal(mid, year - 1, m, area, k, volume);
- }
- }
+ Double y = midCumYoy(mid, year, area, months, k, volume);
Row row = sheet.getRow(r0);
if (row == null) continue;
Cell c = row.getCell(colIdx);
if (c == null) c = row.createCell(colIdx);
c.setBlank();
- if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4));
+ if (y != null) c.setCellValue(y);
}
}
@@ -2326,6 +2311,56 @@
return volume ? arr[k][0] : arr[k][1];
}
+ /** B3锛氱櫥璁般�岃鏈熻甯傚窞璇ョ淮搴︽湁婧愯銆嶃�傚垽鎹槸婧愯〃鏈夋病鏈夎繖涓�琛岋紝鑰屼笉鏄畻鍑烘潵鐨勫�兼槸鍚﹂潪 0
+ * 鈥斺�斿洜涓恒�屽�间负 0銆嶅彲鑳芥槸鐪熷疄涓氬姟锛堝姝︽眽甯傚贰娓稿嚭绉熷叏閮ㄤ负鍩庡競鍐咃紝鍩庨檯鍩庝埂鎭掍负 0锛夈�� */
+ private void markMid(Map<Integer, Map<String, boolean[]>> pres, int key, String city, int k) {
+ pres.computeIfAbsent(key, kk -> new HashMap<>())
+ .computeIfAbsent(city, kk -> new boolean[5])[k] = true;
+ }
+
+ /**
+ * B3锛氳鏈堣缁村害鏄惁銆屾湁婧愩�嶏紙鍚屾瘮瀹屽鎬ф牎楠岀敤锛夈��
+ * k <= 0 琛ㄧず銆屾�婚噺琛屻�嶏紝瑕佹眰鍥涚淮锛堢彮绾�/鍏氦/鍑虹/缃戠害杞︼級閮芥湁婧愭墠绠楅綈鍏ㄣ��
+ */
+ private boolean midMonthHas(Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) {
+ Map<String, boolean[]> pm = midPresence.get().get(year * 100 + month);
+ if (pm == null) return false;
+ boolean[] b = pm.get(area);
+ if (b == null) return false;
+ if (k <= 0) {
+ for (int kk = 1; kk <= 4; kk++) {
+ if (!b[kk]) return false;
+ }
+ return true;
+ }
+ return k < b.length && b[k];
+ }
+
+ /** B3锛氬幓骞� 1..monthCount 鏈堢殑鍛戒腑鏈堜唤鏁帮紙鐢ㄤ簬鍒ゅ畾鍚屾瘮鍩烘暟鏄惁榻愬叏锛� */
+ private int midCumHitMonths(Map<Integer, Map<String, double[][]>> mid, int year, String area, int monthCount, int k, boolean volume) {
+ int n = 0;
+ for (int m = 1; m <= monthCount; m++) {
+ if (midMonthHas(mid, year, m, area, k, volume)) n++;
+ }
+ return n;
+ }
+
+ /** B3锛氬綋鏈堝悓姣斺�斺�斿幓骞村悓鏈堝熀鏁扮己澶卞垯鐣欑┖锛堢己鏁版嵁涓嶅啀褰� 0 璁$畻锛� */
+ private Double midMonthYoy(Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) {
+ if (!midMonthHas(mid, year - 1, month, area, k, volume)) return null;
+ double v = midClassVal(mid, year, month, area, k, volume);
+ if (v == 0.0) return null;
+ return yoyOf(v, midClassVal(mid, year - 1, month, area, k, volume));
+ }
+
+ /** B3锛氱疮璁″悓姣斺�斺�斿幓骞� 1..monthCount 鍚勬湀榻愬叏鎵嶈绠楋紝缂轰换浣曚竴鏈堜竴寰嬬暀绌� */
+ private Double midCumYoy(Map<Integer, Map<String, double[][]>> mid, int year, String area, int monthCount, int k, boolean volume) {
+ if (midCumHitMonths(mid, year - 1, area, monthCount, k, volume) < monthCount) return null;
+ double v = midCumClass(mid, year, area, monthCount, k, volume);
+ if (v == 0.0) return null;
+ return yoyOf(v, midCumClass(mid, year - 1, area, monthCount, k, volume));
+ }
+
/** 鍏紡鍗曞厓鏍兼浛鎹负缂撳瓨鏁板�硷紙閬垮厤璺ㄧ翱寮曠敤鏂摼锛涢潪鏁板�肩紦瀛樼疆绌猴級 */
private void keepCached(Cell c) {
try {
@@ -2346,7 +2381,20 @@
* 涓彛寰勫垎绫绘湀搴︽暟鎹細骞�*100+鏈� -> (甯傚窞/鍏ㄧ渷 -> double[5][2])
* 缁村害 0=鎬婚噺 1=鍏矾鐝嚎(h2031+涓綋) 2=鍩庨檯鍩庝埂鍏氦(cityBus) 3=宸℃父鍑虹 4=缃戠害杞︼紱[瀹㈣繍閲�(涓囦汉), 鍛ㄨ浆閲�(涓囦汉鍏噷)]
*/
+ /** B3锛氭渶杩戜竴娆� loadMidClassMap 鐨勩�岀淮搴︽槸鍚︽湁婧愯銆嶇櫥璁拌〃锛坘ey -> city -> boolean[5]锛夛紝鐢ㄤ簬鍚屾瘮瀹屽鎬ф牎楠� */
+ private final ThreadLocal<Map<Integer, Map<String, boolean[]>>> midPresence =
+ ThreadLocal.withInitial(HashMap::new);
+
+ /** B4/B5锛氭渶杩戜竴娆� loadMidClassMap 鐨勬暟鎹棶棰樻竻鍗曪紙绾跨▼鍐咃紝闅忓鍑轰綋妫�杈撳嚭/鍐欑敓鎴愪欢锛� */
+ private final ThreadLocal<java.util.List<String>> midIssues = ThreadLocal.withInitial(java.util.ArrayList::new);
+
+ /** B4/B5锛氬彇鏈�杩戜竴娆′腑鍙e緞鏁版嵁鍔犺浇鐨勯棶棰樻竻鍗曪紙鍙鍓湰锛� */
+ public java.util.List<String> getLastMidIssues() { return new java.util.ArrayList<>(midIssues.get()); }
+
private Map<Integer, Map<String, double[][]>> loadMidClassMap() {
+ midIssues.get().clear(); // B5锛氭湰娆″姞杞界殑闂娓呭崟
+ Map<Integer, Map<String, boolean[]>> pres = new HashMap<>();
+ midPresence.set(pres); // B3锛氭湰娆″姞杞界殑缁村害鏈夋簮鐧昏
Map<Integer, Map<String, double[][]>> mid = new HashMap<>();
for (PassengerEnterpriseMonthly e : passengerMapper.selectList(null)) {
if (e.getReportPeriod() == null) continue;
@@ -2368,6 +2416,8 @@
+ nz(e.getTurnoverClass4()) + nz(e.getTurnoverCharter())) / 10000.0;
addMidClass(mid, key, city, 1, pass, turn);
addMidClass(mid, key, "婀栧寳鐪�", 1, pass, turn);
+ markMid(pres, key, city, 1);
+ markMid(pres, key, "婀栧寳鐪�", 1);
}
for (PassengerIndividualMonthly e : passengerIndividualMapper.selectList(null)) { // 涓綋骞跺叆鍏矾鐝嚎/鎬婚噺锛�09-03 鍙e緞锛�
if (e.getReportPeriod() == null) continue;
@@ -2387,6 +2437,8 @@
double turn = nz(e.getTurnover()) / 10000.0;
addMidClass(mid, key, city, 1, pass, turn);
addMidClass(mid, key, "婀栧寳鐪�", 1, pass, turn);
+ markMid(pres, key, city, 1);
+ markMid(pres, key, "婀栧寳鐪�", 1);
}
for (CityBusMonthly b : cityBusMapper.selectList(null)) {
if (b.getReportPeriod() == null || b.getCity() == null) continue;
@@ -2402,8 +2454,17 @@
String city = RegionUtil.normalizeCityName(b.getCity());
if (city == null) continue;
int key = y * 100 + m;
- addMidClass(mid, key, city, 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang()));
- addMidClass(mid, key, "婀栧寳鐪�", 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang()));
+ // B1 鍏滃簳锛氬瓨鍌ㄧ殑銆屽煄闄呭煄涔°�嶇己澶�(<=0)鏃剁敤銆屾�诲杩愰噺 鈭� 鍩庡競鍐呫�嶇幇绠楋紝閬垮厤 2025 鍏ㄥ勾鍏氦缁村害琚綋鎴� 0
+ double cxPass = nz(b.getPassengerChengxiang());
+ if (cxPass <= 0) cxPass = nz(b.getPassengerVolume()) - nz(b.getPassengerCity());
+ double cxTurn = nz(b.getTurnoverChengxiang());
+ if (cxTurn <= 0) cxTurn = nz(b.getTurnover()) - nz(b.getTurnoverCity());
+ if (cxPass < 0) cxPass = 0.0;
+ if (cxTurn < 0) cxTurn = 0.0;
+ addMidClass(mid, key, city, 2, cxPass, cxTurn);
+ addMidClass(mid, key, "婀栧寳鐪�", 2, cxPass, cxTurn);
+ markMid(pres, key, city, 2);
+ markMid(pres, key, "婀栧寳鐪�", 2);
}
// 缁村害3锛氬煄闄呭煄涔″贰娓稿嚭绉� = 鍙拌处椤点�屽嚭绉�!鎬婚噺 鈭� 鍑虹!鍩庡競鍐呫�嶏紙婧� city_taxi_monthly锛�
for (CityTaxiMonthly t : cityTaxiMapper.selectList(null)) {
@@ -2416,6 +2477,8 @@
double turn = nz(t.getTurnover()) - nz(t.getTurnoverCity());
addMidClass(mid, key, city, 3, pass, turn);
addMidClass(mid, key, "婀栧寳鐪�", 3, pass, turn);
+ markMid(pres, key, city, 3);
+ markMid(pres, key, "婀栧寳鐪�", 3);
}
// 缁村害4锛氬煄闄呭煄涔$綉绾﹁溅 = 缃戠害杞︽媶鍒嗙粨鏋滐紙涓庡彴璐︺�岀綉绾﹁溅銆嶉〉鍚屾簮鍚屾硶锛�
for (WycOrderMonthly o : wycOrderMapper.selectList(null)) {
@@ -2427,12 +2490,18 @@
try {
wr = wycSplitCalc.calc(per);
} catch (Exception e) {
- continue; // 璇ユ湡缂鸿鍗�/pin 鎴栧嚭绉熸簮鏁版嵁锛岃烦杩囷紙涓嶅奖鍝嶅叾瀹冩湡锛�
+ // B5锛氫笉鍐嶉潤榛樿烦杩団�斺�旇褰曘�屾湡 + 鍘熷洜銆嶅苟鍐欐棩蹇楋紝闅忓鍑轰綋妫�涓�骞惰緭鍑�
+ String msg = per + " 缃戠害杞﹀煄闄呭煄涔$淮搴︾己澶憋細" + e.getMessage();
+ midIssues.get().add(msg);
+ log.warn("loadMidClassMap {}", msg);
+ continue;
}
for (Map.Entry<String, WycSplitCalc.WycMetrics> en : wr.getByCity().entrySet()) {
WycSplitCalc.WycMetrics wm = en.getValue();
addMidClass(mid, key, en.getKey(), 4, wm.getSuburbanPax(), wm.getSuburbanTurnover());
addMidClass(mid, key, "婀栧寳鐪�", 4, wm.getSuburbanPax(), wm.getSuburbanTurnover());
+ markMid(pres, key, en.getKey(), 4);
+ markMid(pres, key, "婀栧寳鐪�", 4);
}
}
return mid;
@@ -2586,15 +2655,11 @@
title.getCell(10).setCellValue(currentYear + "骞�" + maxMonth + "鏈堝叏鐪佸垎甯傚窞瀹屾垚閬撹矾瀹㈣繍鐢熶骇鎯呭喌");
}
// 鍏ㄧ渷琛� r3锛欱3/F3 涓� SUM 鍏紡淇濈暀锛汥3/H3 鍚屾瘮濉�硷紙0-based 鍒� 3/7锛�
- setValOrBlank(sheet, 2, 3, yoyOf(midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, true),
- midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, 0, true)));
- setValOrBlank(sheet, 2, 7, yoyOf(midCumClass(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, false),
- midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, 0, false)));
+ setValOrBlank(sheet, 2, 3, midCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, true));
+ setValOrBlank(sheet, 2, 7, midCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, 0, false));
// 鍙冲潡鍏ㄧ渷琛� N3/R3锛氬綋鏈堝悓姣旓紙L3/P3 涓� SUM 鍏紡淇濈暀锛�
- setValOrBlank(sheet, 2, 13, yoyOf(midClassVal(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, true),
- midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", 0, true)));
- setValOrBlank(sheet, 2, 17, yoyOf(midClassVal(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, false),
- midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", 0, false)));
+ setValOrBlank(sheet, 2, 13, midMonthYoy(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, true));
+ setValOrBlank(sheet, 2, 17, midMonthYoy(mid, currentYear, maxMonth, "婀栧寳鐪�", 0, false));
// 甯傚窞琛� r4-20锛欱/F 绱鍊笺�丏/H 鍚屾瘮锛汣/E/G/I 鎺掑悕鍏紡淇濈暀
List<String> cities = RegionUtil.CITY_LIST;
for (int i = 0; i < cities.size(); i++) {
@@ -2605,18 +2670,18 @@
double lastPass = midCumClass(mid, currentYear - 1, city, maxMonth, 0, true);
double lastTurn = midCumClass(mid, currentYear - 1, city, maxMonth, 0, false);
setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
- setValOrBlank(sheet, r0, 3, yoyOf(pass, lastPass));
+ setValOrBlank(sheet, r0, 3, midCumYoy(mid, currentYear, city, maxMonth, 0, true));
setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
- setValOrBlank(sheet, r0, 7, yoyOf(turn, lastTurn));
+ setValOrBlank(sheet, r0, 7, midCumYoy(mid, currentYear, city, maxMonth, 0, false));
// 鍙冲潡褰撴湀锛歀/P 鍊笺�丯/R 褰撴湀鍚屾瘮锛圡/O/Q/S 鎺掑悕鍏紡淇濈暀锛�
double mPass = midClassVal(mid, currentYear, maxMonth, city, 0, true);
double mTurn = midClassVal(mid, currentYear, maxMonth, city, 0, false);
double mLastPass = midClassVal(mid, currentYear - 1, maxMonth, city, 0, true);
double mLastTurn = midClassVal(mid, currentYear - 1, maxMonth, city, 0, false);
setValOrBlank(sheet, r0, 11, mPass == 0 ? null : round(mPass, 4));
- setValOrBlank(sheet, r0, 13, mPass == 0 ? null : yoyOf(mPass, mLastPass));
+ setValOrBlank(sheet, r0, 13, mPass == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, city, 0, true));
setValOrBlank(sheet, r0, 15, mTurn == 0 ? null : round(mTurn, 4));
- setValOrBlank(sheet, r0, 17, mTurn == 0 ? null : yoyOf(mTurn, mLastTurn));
+ setValOrBlank(sheet, r0, 17, mTurn == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, city, 0, false));
}
recalc(wb);
return toBytes(wb);
@@ -2678,9 +2743,9 @@
double lastPass = midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, dims[i], true);
double lastTurn = midCumClass(mid, currentYear - 1, "婀栧寳鐪�", maxMonth, dims[i], false);
setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
- setValOrBlank(sheet, r0, 2, yoyOf(pass, lastPass));
+ setValOrBlank(sheet, r0, 2, midCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, dims[i], true));
setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
- setValOrBlank(sheet, r0, 6, yoyOf(turn, lastTurn));
+ setValOrBlank(sheet, r0, 6, midCumYoy(mid, currentYear, "婀栧寳鐪�", maxMonth, dims[i], false));
}
// 涓嬪崐鍧楋紙褰撴湀锛夛細鏍囬琛�9銆佹暟鎹11-15锛�0-based 8銆�10-14锛�
Row bottomTitle = sheet.getRow(8);
@@ -2694,9 +2759,9 @@
double lastPass = midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", dims[i], true);
double lastTurn = midClassVal(mid, currentYear - 1, maxMonth, "婀栧寳鐪�", dims[i], false);
setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4));
- setValOrBlank(sheet, r0, 2, pass == 0 ? null : yoyOf(pass, lastPass));
+ setValOrBlank(sheet, r0, 2, pass == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, "婀栧寳鐪�", dims[i], true));
setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4));
- setValOrBlank(sheet, r0, 6, turn == 0 ? null : yoyOf(turn, lastTurn));
+ setValOrBlank(sheet, r0, 6, turn == 0 ? null : midMonthYoy(mid, currentYear, maxMonth, "婀栧寳鐪�", dims[i], false));
}
// 涓嬪崐鍧楀崰姣斿叕寮忕敱鏍蜂緥鎸囧悜涓婂崐鍧楋紙B$3/F$3锛夛紝鏀瑰啓涓烘湰鍧楁�昏锛圔$11/F$11锛夛紝琛�12-15
for (int i = 1; i < names.length; i++) {
@@ -4823,6 +4888,8 @@
case "investFiveYearLogistics": return investFiveYearLogistics(period, mode);
case "investBillion": return investBillion(period, mode);
case "investCounty": return investCounty(period, mode);
+ case "investPassengerDetail": return exportInvestPassengerDetail(period, mode);
+ case "investLogisticsDetail": return exportInvestLogisticsDetail(period, mode);
case "cityBusDetail": return exportCityBusDetail(period, mode);
case "cityTaxiDetail": return exportCityTaxiDetail(period, mode);
case "cityRailFerryDetail": return exportCityRailFerryDetail(period, mode);
@@ -4848,6 +4915,8 @@
REPORT_FILE_NAMES.put("investFiveYearLogistics", "鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls");
REPORT_FILE_NAMES.put("investBillion", "鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls");
REPORT_FILE_NAMES.put("investCounty", "鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls");
+ REPORT_FILE_NAMES.put("investPassengerDetail", "鐢熸垚_瀹㈣繍绔欐姇璧勬槑缁嗚〃.xlsx");
+ REPORT_FILE_NAMES.put("investLogisticsDetail", "鐢熸垚_鐗╂祦绔欏満鎶曡祫鏄庣粏.xls");
REPORT_FILE_NAMES.put("cityBusDetail", "鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
REPORT_FILE_NAMES.put("cityTaxiDetail", "鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
REPORT_FILE_NAMES.put("cityRailFerryDetail", "鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
@@ -5399,6 +5468,355 @@
}
+
+ // ==================== 鎶曡祫妯″潡锛氬杩愮珯鎶曡祫鏄庣粏琛� / 鐗╂祦绔欏満鎶曡祫鏄庣粏锛堟ā鏉垮簳绋垮師浣嶆浛鎹級 ====================
+
+ /** 鐗╂祦绔欏満鏄庣粏琛ㄦā鏉夸腑鐨勫競宸炲皬鑺傜煭鍚� -> 搴撳唴瑙勮寖鍏ㄥ悕 */
+ private static final Map<String, String> LOGI_CITY_LABELS = new LinkedHashMap<>();
+ static {
+ LOGI_CITY_LABELS.put("姝︽眽", "姝︽眽甯�");
+ LOGI_CITY_LABELS.put("榛勭煶", "榛勭煶甯�");
+ LOGI_CITY_LABELS.put("鍗佸牥", "鍗佸牥甯�");
+ LOGI_CITY_LABELS.put("瀹滄槍", "瀹滄槍甯�");
+ LOGI_CITY_LABELS.put("瑗勯槼", "瑗勯槼甯�");
+ LOGI_CITY_LABELS.put("閯傚窞", "閯傚窞甯�");
+ LOGI_CITY_LABELS.put("鑽嗛棬", "鑽嗛棬甯�");
+ LOGI_CITY_LABELS.put("瀛濇劅", "瀛濇劅甯�");
+ LOGI_CITY_LABELS.put("鑽嗗窞", "鑽嗗窞甯�");
+ LOGI_CITY_LABELS.put("榛勫唸", "榛勫唸甯�");
+ LOGI_CITY_LABELS.put("鍜稿畞", "鍜稿畞甯�");
+ LOGI_CITY_LABELS.put("闅忓窞", "闅忓窞甯�");
+ LOGI_CITY_LABELS.put("鎭╂柦", "鎭╂柦宸�");
+ LOGI_CITY_LABELS.put("浠欐", "浠欐甯�");
+ LOGI_CITY_LABELS.put("娼滄睙", "娼滄睙甯�");
+ LOGI_CITY_LABELS.put("澶╅棬", "澶╅棬甯�");
+ LOGI_CITY_LABELS.put("鏋楀尯", "绁炲啘鏋舵灄鍖�");
+ }
+
+ /** 璇ユ湡鍚勯」鐩湀搴︽暟鎹細projectId -> InvestmentMonthly */
+ private Map<Long, InvestmentMonthly> investMonthlyOf(String period) {
+ Map<Long, InvestmentMonthly> map = new HashMap<>();
+ for (InvestmentMonthly m : investMonthlyMapper.selectList(
+ new LambdaQueryWrapper<InvestmentMonthly>().eq(InvestmentMonthly::getReportPeriod, period))) {
+ map.put(m.getProjectId(), m);
+ }
+ return map;
+ }
+
+ /** 鍐欏崟鍏冩牸鏁板�硷紙淇濈暀鏍峰紡/鍚堝苟锛夛細null 娓呯┖ */
+ private void putNum(Row row, int idx, Double v) {
+ if (row == null) return;
+ Cell c = row.getCell(idx);
+ if (c == null) c = row.createCell(idx);
+ c.setBlank();
+ if (v != null) c.setCellValue(v);
+ }
+
+ /** 鍐欏崟鍏冩牸鏂囨湰锛堜繚鐣欐牱寮�/鍚堝苟锛夛細绌轰覆娓呯┖ */
+ private void putText(Row row, int idx, String v) {
+ if (row == null) return;
+ Cell c = row.getCell(idx);
+ if (c == null) c = row.createCell(idx);
+ c.setBlank();
+ if (v != null && !v.trim().isEmpty()) c.setCellValue(v.trim());
+ }
+
+ /** 璇诲崟鍏冩牸鏁板�硷紙绌�/闈炴暟鍊�/鍏紡鏃犵紦瀛樹竴寰� 0锛� */
+ private double numOf(Cell c) {
+ if (c == null) return 0.0;
+ try {
+ if (c.getCellType() == CellType.NUMERIC || c.getCellType() == CellType.FORMULA) {
+ return c.getNumericCellValue();
+ }
+ String s = cellText(c);
+ if (s == null) return 0.0;
+ s = s.replace(",", "").trim();
+ return s.isEmpty() ? 0.0 : Double.parseDouble(s);
+ } catch (Exception e) {
+ return 0.0;
+ }
+ }
+
+ /** 妯℃澘鏍囬閲岀殑銆岋紙N鏈堟湀鎶ワ級銆嶃�岋紙N鏈堬級銆嶆敼涓烘姤琛ㄦ湡鏈堜唤 */
+ private void fixInvestMonthTitle(Sheet sheet, int rowIdx, int colIdx, int month) {
+ Row row = sheet.getRow(rowIdx);
+ if (row == null) return;
+ String t = cellText(row.getCell(colIdx));
+ if (t == null) return;
+ String nv = t.replaceAll("锛圽\d+鏈堟湀鎶ワ級", "锛�" + month + "鏈堟湀鎶ワ級")
+ .replaceAll("锛圽\d+鏈堬級", "锛�" + month + "鏈堬級");
+ if (!nv.equals(t)) putText(row, colIdx, nv);
+ }
+
+ /** 妯℃澘琛� A 鍒楁槸鍚︿负銆屼竴浜屼笁鍥涖�併�嶆笭閬撳垎缁勬爣棰� */
+ private boolean isInvestChannelLabel(String a) {
+ return a != null && a.matches("^[涓�浜屼笁鍥涗簲鍏竷鍏節鍗乚+銆�.*");
+ }
+
+ /**
+ * 鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx锛堟ā鏉匡細docs/鎶曡祫/妯℃澘/妯℃澘_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx锛宻heet锛氭槑缁� + 姹囨�伙級銆�
+ * 鍙e緞锛氭ā鏉垮嵆涓婃湀鍙拌处搴曠锛涙湰鏈堝彧瑕嗙洊銆屽簱鍐呮湁璇ユ湡鏁版嵁銆嶇殑椤圭洰琛�
+ * 锛圚鑷紑濮嬪缓璁剧疮璁� / I鏈勾璁″垝 / J鑷勾鍒濈疮璁� / K鏈湀瀹屾垚 / L寤鸿闃舵 / M褰㈣薄杩涘害锛夛紝
+ * 搴撳唴鏃犺鏈熺殑椤圭洰琛屼繚鐣欐瘝鐗堝師鍊硷紙绛夊悓浜哄伐銆屼笂鏈堢粨杞� + 鏈湀鏇存柊銆嶏級锛�
+ * 鍩庡競/娓犻亾/鍚堣琛屾部鐢ㄦā鏉� SUM 鍏紡锛岄殢琛ㄥ唴鏁板�艰嚜鍔ㄩ噸绠椼��
+ */
+ public byte[] exportInvestPassengerDetail(String period, String mode) throws Exception {
+ int year = Integer.parseInt(period.split("-")[0]);
+ int month = monthOf(period, mode);
+ List<InvestmentProject> projects = investProjectMapper.selectList(
+ new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, "瀹㈣繍绔欏満"));
+ Map<Long, InvestmentMonthly> mByProject = investMonthlyOf(period);
+ File tpl = resolveAnyTemplate(investTemplateDir, "妯℃澘_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx");
+ try (InputStream in = new FileInputStream(tpl);
+ XSSFWorkbook wb = new XSSFWorkbook(in)) {
+ Sheet sheet = wb.getSheet("鏄庣粏");
+ if (sheet == null) throw new RuntimeException("妯℃澘銆婃ā鏉縚X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx銆嬬己灏戙�屾槑缁嗐�嶅伐浣滆〃");
+ fixInvestMonthTitle(sheet, 0, 1, month);
+ String curCity = null;
+ Set<Long> used = new HashSet<>();
+ int filled = 0, kept = 0, dup = 0;
+ for (int r = 8; r <= sheet.getLastRowNum(); r++) {
+ Row row = sheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ String b = nvl(cellText(row.getCell(1)));
+ if (isCityLabel(a)) { curCity = templateCityFull(a); continue; }
+ if (isInvestChannelLabel(a)) continue;
+ if (a.contains("鍚堣") || a.contains("鎬昏")) continue;
+ if (!a.matches("\\d+(\\.\\d+)?")) continue;
+ if (b.isEmpty() || curCity == null) continue;
+ InvestmentProject p = matchInvest(projects, b, curCity, "瀹㈣繍绔欏満", used);
+ InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
+ if (p == null || m == null) {
+ kept++;
+ // 鏈夊簱鍐呴」鐩絾褰撴湀鏃犳暟鎹� 鈫� 璁颁负寰呮牳瀵癸紱鏃犲尮閰嶉」鐩� 鈫� 缁撹浆涓婃湀
+ if (p != null) dup++;
+ continue;
+ }
+ used.add(p.getId());
+ // 鍙e緞锛堢敤鎴� 2026-09-18 纭锛夛細鍙鐩栦汉宸ヤ細闅忔湀鎶ユ洿鏂扮殑鍥涘垪鈥斺��
+ // H 鑷紑濮嬪缓璁剧疮璁� / J 鑷勾鍒濈疮璁� / K 鏈湀瀹屾垚 / M 褰㈣薄杩涘害锛�
+ // I 鏈勾璁″垝鎶曡祫銆丩 寤鸿闃舵銆丟 璁″垝鎬绘姇璧勪负鐪佸畾/姣嶇増鍙e緞锛屼竴寰嬩笉鍔紱搴撳唴涓虹┖鍒欎繚鐣欐瘝鐗堝師鍊�
+ if (m.getStartCum() != null) putNum(row, 7, m.getStartCum());
+ if (m.getYearCum() != null) putNum(row, 9, m.getYearCum());
+ if (m.getMonthDone() != null) putNum(row, 10, m.getMonthDone());
+ if (m.getProgressDesc() != null && !m.getProgressDesc().trim().isEmpty()) putText(row, 12, m.getProgressDesc());
+ filled++;
+ }
+ Sheet sumSheet = wb.getSheet("姹囨��");
+ if (sumSheet != null) {
+ Row r2 = sumSheet.getRow(1);
+ if (r2 != null) {
+ Cell c = r2.getCell(0);
+ if (c == null) c = r2.createCell(0);
+ c.setBlank();
+ c.setCellValue(java.time.LocalDate.of(year, month, 1).atStartOfDay());
+ }
+ }
+ try {
+ wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+ } catch (Exception e) {
+ log.warn("瀹㈣繍绔欐姇璧勬槑缁嗚〃鍏紡姹傚�煎け璐�: {}", e.getMessage());
+ }
+ log.info("瀹㈣繍绔欐姇璧勬槑缁嗚〃 {}锛氬啓鍏� {} 琛岋紝缁撹浆姣嶇増鍘熷�� {} 琛岋紙鍏朵腑搴撳唴鏈夐」鐩絾褰撴湀鏃犳暟鎹� {} 琛岋級",
+ period, filled, kept, dup);
+ return toBytes(wb);
+ }
+ }
+
+ /**
+ * 鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls锛堟ā鏉匡細docs/鎶曡祫/妯℃澘/妯℃澘_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls锛夈��
+ * 瑕嗙洊銆� 鍒嗛」鐩姇璧勫畬鎴愭儏鍐点�嶉」鐩锛堝彛寰勫悓瀹㈣繍绔欐槑缁嗚〃锛夛紝骞舵寜琛ㄥ唴鏁板�奸噸绠�
+ * 娓犻亾琛� / 甯傚窞琛� / 鍚堣琛岋紝鍚屾鍒锋柊銆屽垎甯傚窞瑙勫垝鍐呭椤圭洰瀹屾垚鎯呭喌銆嶃�屽競宸炴眹鎬汇�嶄袱寮犳眹鎬婚〉銆�
+ */
+ public byte[] exportInvestLogisticsDetail(String period, String mode) throws Exception {
+ int year = Integer.parseInt(period.split("-")[0]);
+ int month = monthOf(period, mode);
+ List<InvestmentProject> projects = investProjectMapper.selectList(
+ new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, "鐗╂祦鍥尯"));
+ Map<Long, InvestmentMonthly> mByProject = investMonthlyOf(period);
+ HSSFWorkbook wb = loadInvestTemplate("妯℃澘_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls");
+ HSSFSheet sheet = wb.getSheet(" 鍒嗛」鐩姇璧勫畬鎴愭儏鍐�");
+ if (sheet == null) throw new RuntimeException("妯℃澘銆婃ā鏉縚X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls銆嬬己灏戙�� 鍒嗛」鐩姇璧勫畬鎴愭儏鍐点�嶅伐浣滆〃");
+ fixInvestMonthTitle(sheet, 0, 0, month);
+ Row head = sheet.getRow(3);
+ if (head != null) {
+ String t = cellText(head.getCell(0));
+ if (t != null) {
+ String nv = t.replaceAll("\\d{4}骞碶\d+鏈�", year + "骞�" + month + "鏈�");
+ if (!nv.equals(t)) putText(head, 0, nv);
+ }
+ }
+ // 1) 缁撴瀯鎵弿锛氬競宸炲皬鑺傝
+ List<Integer> cityRows = new ArrayList<>();
+ for (int r = 7; r <= sheet.getLastRowNum(); r++) {
+ HSSFRow row = sheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ String b = nvl(cellText(row.getCell(1)));
+ if (LOGI_CITY_LABELS.containsKey(a) && b.isEmpty()) cityRows.add(r);
+ }
+ Map<String, double[]> citySums = new LinkedHashMap<>(); // 鍏ㄥ悕 -> G,H,I,J,K
+ Map<String, double[]> cityStats = new LinkedHashMap<>(); // 鍏ㄥ悕 -> {鎬绘暟, 绱, 瑙勫垝鍐呮暟, 瑙勫垝鍐呯疮璁�, 瑙勫垝澶栨暟, 瑙勫垝澶栫疮璁
+ Set<Long> used = new HashSet<>();
+ int filled = 0, kept = 0;
+ for (int i = 0; i < cityRows.size(); i++) {
+ int cr = cityRows.get(i);
+ String cityLabel = nvl(cellText(sheet.getRow(cr).getCell(0)));
+ String city = LOGI_CITY_LABELS.getOrDefault(cityLabel, cityLabel);
+ int start = cr + 1;
+ int end = (i + 1 < cityRows.size() ? cityRows.get(i + 1) : sheet.getLastRowNum() + 1) - 1;
+ List<Integer> chRows = new ArrayList<>();
+ for (int r = start; r <= end; r++) {
+ HSSFRow row = sheet.getRow(r);
+ if (row == null) continue;
+ if (isInvestChannelLabel(nvl(cellText(row.getCell(0))))) chRows.add(r);
+ }
+ // (1) 鍏堟寜椤圭洰鍚�+甯傚窞 瑕嗙洊鏈夊簱鍐呭綋鏈熸暟鎹殑椤圭洰琛�
+ for (int r = start; r <= end; r++) {
+ HSSFRow row = sheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ String b = nvl(cellText(row.getCell(1)));
+ if (isInvestChannelLabel(a)) continue;
+ if (!a.matches("\\d+(\\.\\d+)?")) continue;
+ if (b.isEmpty()) continue;
+ InvestmentProject p = matchInvest(projects, b, city, "鐗╂祦鍥尯", used);
+ InvestmentMonthly m = p == null ? null : mByProject.get(p.getId());
+ if (p == null || m == null) { kept++; continue; }
+ used.add(p.getId());
+ // 鍙e緞鍚屽杩愮珯鏄庣粏琛細鍙鐩� H 鑷紑濮嬪缓璁剧疮璁� / J 鑷勾鍒濈疮璁� / K 鏈湀瀹屾垚 / M 褰㈣薄杩涘害
+ if (m.getStartCum() != null) putNum(row, 7, m.getStartCum());
+ if (m.getYearCum() != null) putNum(row, 9, m.getYearCum());
+ if (m.getMonthDone() != null) putNum(row, 10, m.getMonthDone());
+ if (m.getProgressDesc() != null && !m.getProgressDesc().trim().isEmpty()) putText(row, 12, m.getProgressDesc());
+ filled++;
+ }
+ // (2) 鍐嶆寜娓犻亾鍧楁眹鎬伙紝骞跺啓鍥炴笭閬撹 / 甯傚窞琛�
+ double[] citySum = new double[5];
+ double[] stat = new double[6];
+ for (int j = 0; j < chRows.size(); j++) {
+ int r0 = chRows.get(j);
+ int r1 = (j + 1 < chRows.size() ? chRows.get(j + 1) : end + 1) - 1;
+ boolean planned = !nvl(cellText(sheet.getRow(r0).getCell(0))).startsWith("鍥涖��");
+ double[] s = new double[5];
+ for (int r = r0 + 1; r <= r1; r++) {
+ HSSFRow row = sheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ String b = nvl(cellText(row.getCell(1)));
+ if (isInvestChannelLabel(a) || !a.matches("\\d+(\\.\\d+)?")) continue;
+ if (b.isEmpty()) continue;
+ for (int k = 0; k < 5; k++) s[k] += numOf(row.getCell(6 + k));
+ stat[0] += 1;
+ stat[1] += numOf(row.getCell(9));
+ if (planned) { stat[2] += 1; stat[3] += numOf(row.getCell(9)); }
+ else { stat[4] += 1; stat[5] += numOf(row.getCell(9)); }
+ }
+ HSSFRow crow = sheet.getRow(r0);
+ for (int k = 0; k < 5; k++) putNum(crow, 6 + k, s[k] == 0 ? null : s[k]);
+ for (int k = 0; k < 5; k++) citySum[k] += s[k];
+ }
+ // 娓犻亾鍧椾箣澶栫殑娓哥椤圭洰琛岋紙妯℃澘鏈垎缁勬椂锛変篃璁″叆甯傚窞鍚堣
+ for (int r = start; r <= end; r++) {
+ HSSFRow row = sheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ String b = nvl(cellText(row.getCell(1)));
+ if (isInvestChannelLabel(a) || !a.matches("\\d+(\\.\\d+)?")) continue;
+ if (b.isEmpty()) continue;
+ boolean inChannel = false;
+ for (int j = 0; j < chRows.size(); j++) {
+ int r0 = chRows.get(j);
+ int r1 = (j + 1 < chRows.size() ? chRows.get(j + 1) : end + 1) - 1;
+ if (r > r0 && r <= r1) { inChannel = true; break; }
+ }
+ if (inChannel) continue;
+ for (int k = 0; k < 5; k++) citySum[k] += numOf(row.getCell(6 + k));
+ stat[0] += 1;
+ stat[1] += numOf(row.getCell(9));
+ }
+ HSSFRow cityRow = sheet.getRow(cr);
+ for (int k = 0; k < 5; k++) putNum(cityRow, 6 + k, citySum[k] == 0 ? null : citySum[k]);
+ citySums.put(city, citySum);
+ cityStats.put(city, stat);
+ }
+ // 2) 鍚堣琛岋紙绗� 7 琛岋級
+ double[] total = new double[5];
+ for (double[] s : citySums.values()) {
+ for (int k = 0; k < 5; k++) total[k] += s[k];
+ }
+ for (int k = 0; k < 5; k++) putNum(sheet.getRow(6), 6 + k, total[k] == 0 ? null : total[k]);
+
+ // 3) 鍒嗗競宸炶鍒掑唴澶栭」鐩畬鎴愭儏鍐�
+ HSSFSheet citySheet = wb.getSheet("鍒嗗競宸炶鍒掑唴澶栭」鐩畬鎴愭儏鍐�");
+ if (citySheet != null) {
+ Row t1 = citySheet.getRow(0);
+ if (t1 != null) {
+ String t = cellText(t1.getCell(0));
+ if (t != null) {
+ String nv = t.replaceAll("\\d{4}骞�", year + "骞�");
+ if (!nv.equals(t)) putText(t1, 0, nv);
+ }
+ }
+ double[] st = new double[6];
+ for (int r = 3; r <= citySheet.getLastRowNum(); r++) {
+ HSSFRow row = citySheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ String b = nvl(cellText(row.getCell(1)));
+ if (a.contains("鍚堣") || b.contains("鍚堣")) {
+ for (int k = 0; k < 6; k++) putNum(row, 2 + k, st[k] == 0 ? null : st[k]);
+ continue;
+ }
+ double[] s = cityStats.get(LOGI_CITY_LABELS.getOrDefault(b, b));
+ if (s == null) continue;
+ for (int k = 0; k < 6; k++) {
+ putNum(row, 2 + k, s[k] == 0 ? null : s[k]);
+ st[k] += s[k];
+ }
+ }
+ }
+
+ // 4) 甯傚窞姹囨�伙紙B 骞村害璁″垝鎶曡祫涓哄畼鏂瑰彛寰勶紝淇濇寔妯℃澘鍘熷�硷紱C/D/E 鎸夊垎椤圭洰椤甸噸绠楋級
+ HSSFSheet sumSheet = wb.getSheet("甯傚窞姹囨��");
+ if (sumSheet != null) {
+ HSSFRow r2 = sumSheet.getRow(1);
+ if (r2 != null) {
+ putNum(r2, 0, (double) org.apache.poi.ss.usermodel.DateUtil.getExcelDate(
+ java.time.LocalDate.of(year, month, 1).atStartOfDay()));
+ }
+ double cT = 0.0, dT = 0.0;
+ for (int r = 7; r <= 23; r++) {
+ HSSFRow row = sumSheet.getRow(r);
+ if (row == null) continue;
+ String a = nvl(cellText(row.getCell(0)));
+ double[] s = citySums.get(LOGI_CITY_LABELS.getOrDefault(a, a));
+ if (s == null) continue;
+ putNum(row, 2, s[3] == 0 ? null : s[3]);
+ putNum(row, 3, s[4] == 0 ? null : s[4]);
+ double b = numOf(row.getCell(1));
+ putNum(row, 4, b > 0 ? round(s[3] / b * 100, 4) : null);
+ cT += s[3];
+ dT += s[4];
+ }
+ HSSFRow tot = sumSheet.getRow(6);
+ if (tot != null) {
+ putNum(tot, 2, cT == 0 ? null : cT);
+ putNum(tot, 3, dT == 0 ? null : dT);
+ double b = numOf(tot.getCell(1));
+ putNum(tot, 4, b > 0 ? round(cT / b * 100, 4) : null);
+ }
+ }
+ try {
+ wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+ } catch (Exception e) {
+ log.warn("鐗╂祦绔欏満鎶曡祫鏄庣粏鍏紡姹傚�煎け璐�: {}", e.getMessage());
+ }
+ log.info("鐗╂祦绔欏満鎶曡祫鏄庣粏 {}锛氬啓鍏� {} 琛岋紝缁撹浆姣嶇増鍘熷�� {} 琛�", period, filled, kept);
+ return toBytesHssf(wb);
+ }
+
private static class InvestAgg {
double total, startCum, yearPlan, yearCum;
diff --git a/traffic-audit-web/src/views/DataImport.vue b/traffic-audit-web/src/views/DataImport.vue
index f7f6638..caa322a 100644
--- a/traffic-audit-web/src/views/DataImport.vue
+++ b/traffic-audit-web/src/views/DataImport.vue
@@ -37,7 +37,7 @@
<div class="module-sel-row">
<el-select v-model="moduleSel[g.key]" placeholder="閫夋嫨鏁版嵁绫诲瀷" style="width:260px"
@change="onModuleChange(g.key)" clearable>
- <el-option v-for="t in g.types" :key="t.value" :label="t.label" :value="t.value"></el-option>
+ <el-option v-for="t in typeList(g)" :key="t.value" :label="t.label" :value="t.value"></el-option>
</el-select>
<div v-if="importType" class="tpl-box">
<span class="tpl-name">妯℃澘锛歿{ templateName }}</span>
@@ -114,7 +114,7 @@
<div v-else class="overview-grid">
<div class="ov-row" v-for="g in visibleGroups" :key="g.key">
<div class="ov-group">{{ g.name }}</div>
- <div class="ov-item" v-for="t in g.types" :key="t.value" :class="overviewState(t.value).cls">
+ <div class="ov-item" v-for="t in typeList(g)" :key="t.value" :class="overviewState(t.value).cls">
<div class="ov-name">{{ t.label }}</div>
<div class="ov-status">{{ overviewState(t.value).text }}</div>
</div>
@@ -134,7 +134,7 @@
<div v-if="clearScope === 'types'" class="clear-types">
<el-select v-model="clearTypes" multiple filterable collapse-tags placeholder="閫夋嫨瑕佹竻绌虹殑鏁版嵁绫诲瀷" style="width:100%">
<el-option-group v-for="g in clearableGroups()" :key="g.key" :label="g.name">
- <el-option v-for="t in g.types" :key="t.value" :value="t.value"
+ <el-option v-for="t in typeList(g)" :key="t.value" :value="t.value"
:label="t.label + '锛�' + overviewState(t.value).text + '锛�'"></el-option>
</el-option-group>
</el-select>
@@ -226,7 +226,7 @@
{ value: 'cityTaxi', label: '宸℃父鍑虹姹借溅杩愯惀鏈嶅姟鎯呭喌鏈堟姤' },
{ value: 'cityTaxiAuth', label: '鍑虹杞﹁繍鏀胯溅杈�' },
{ value: 'wycTotal', label: '缃戠害杞︽�绘暟' },
- { value: 'wycOrder', label: '缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺' },
+ { value: 'wycOrder', label: '缃戠害杞﹁鍗曞強鍏ㄧ渷鎬婚噺', hidden: true },
{ value: 'wycOrderReport', label: '缃戠害杞﹁鍗曟姤鍛�' }
]
},
@@ -584,6 +584,10 @@
clearableGroups() {
return this.groups.filter(g => !g.hidden)
},
+ // 鍗曚釜鏁版嵁绫诲瀷涔熷彲闅愯棌锛坔idden: true锛夛細鍏ュ彛銆佹瑙堛�佹竻绌轰笅鎷夌粺涓�杩囨护
+ typeList(g) {
+ return (g && g.types ? g.types : []).filter(t => !t.hidden)
+ },
confirmClear() {
if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鎶ヨ〃鏈�'); return }
if (this.clearing) return
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index bf645b9..8448ec1 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -225,7 +225,9 @@
{ key: 'investPlan', label: '鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽眹鎬�', fileName: '鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls', status: null, statusNote: '' },
{ key: 'investFiveYearLogistics', label: '鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞�', fileName: '鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls', status: null, statusNote: '' },
{ key: 'investBillion', label: '浜垮厓鎶曡祫椤圭洰', fileName: '鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls', status: null, statusNote: '' },
- { key: 'investCounty', label: '缁忔祹寮哄幙椤圭洰鎶曡祫缁熻', fileName: '鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls', status: null, statusNote: '' }
+ { key: 'investCounty', label: '缁忔祹寮哄幙椤圭洰鎶曡祫缁熻', fileName: '鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls', status: null, statusNote: '' },
+ { key: 'investPassengerDetail', label: '瀹㈣繍绔欐姇璧勬槑缁嗚〃', fileName: '鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx', status: null, statusNote: '' },
+ { key: 'investLogisticsDetail', label: '鐗╂祦绔欏満鎶曡祫鏄庣粏', fileName: '鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls', status: null, statusNote: '' }
], checked: []
},
{
@@ -476,7 +478,7 @@
const doExport = () => {
if (items.length === 1) {
api.get('/report/export/' + items[0].key, { params, responseType: 'blob', timeout: 120000 })
- .then(res => { this.downloadBlob(res, items[0].fileName); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
+ .then(res => { this.downloadBlob(res, this.fileNameOf(items[0])); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
.catch(err => this.failWith(err))
return
}
@@ -547,6 +549,13 @@
}
return text.length > 200 ? text.slice(0, 200) + '鈥�' : text
},
+ // 鏂囦欢鍚嶅崰浣嶇 X鏈� -> 褰撳墠鎶ヨ〃鏈熸湀浠斤紙濡傘�岀敓鎴恄X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx銆�->銆岀敓鎴恄8鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx銆嶏級
+ fileNameOf(item) {
+ const name = (item && item.fileName) || ''
+ const mm = parseInt((this.period || '').split('-')[1], 10)
+ if (!mm) return name
+ return name.replace(/X鏈�/g, mm + '鏈�')
+ },
downloadBlob(res, fileName) {
const isZip = /\.zip$/.test(fileName)
const isXls = /\.xls$/.test(fileName)
--
Gitblit v1.9.1