8个文件已添加
5个文件已修改
448 ■■■■■ 已修改文件
HANDOFF.md 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/公路旅客/个体客运量汇总.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/城市客运/导入模板_出租车信息(运政车辆信息).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
docs/年报/模板_公共汽电车运营情况.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/年报/模板_公路旅客运输能源消耗情况.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/年报/模板_单位基本情况.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/年报/模板_城市轨道交通人员及运营情况.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/年报/模板_城市轨道交通车辆及线路情况.xls 补丁 | 查看 | 原始文档 | blame | 历史
docs/年报/模板_法人企业财务状况.xls 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/DataImport.vue 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md
@@ -4,7 +4,26 @@
> **每天结束时**:把当天进展、踩过的坑、新需求、未完成事项更新到本文件,然后可以放心开新对话。
> **协作约定(2026-08-31)**:说「提交」= æœ¬åœ° commit + æŽ¨é€åˆ°è¿œç«¯ git(origin main,Gitblit),不再只做本地 commit。
## ä»Šæ—¥ï¼ˆ2026-08-31,含 08-28 èµ·ç§¯ç´¯è®°å½•)
## ä»Šæ—¥ï¼ˆ2026-09-01,含 08-31 èµ·ç§¯ç´¯è®°å½•)
### 9. 2026-09-01:同事年报功能开发中 + æ–°ç‰ˆæœ¬æ‰“包发布(已发布到用户环境)
- **git è¿›å±•(今天 4 ä¸ªæ–°æäº¤ï¼‰**:
  - `92528d9`(同事):数据导入页模板名/下载路径对齐实际模板文件(.xls→.xlsx、补个体客运模板路径)
  - `b1be169`(我方):2 ä¸ªæ¨¡æ¿æ–‡ä»¶å…¥åº“推送(docs/城市客运/导入模板_出租车信息(运政车辆信息).xlsx、docs/公路旅客/个体客运量汇总.xlsx;同事本地放好后我方核对后按惯例入库)
  - `053ebfe`(同事):数据导入页分组改名(道路货运/站场投资/货运能耗季报)+ æ–°å¢žã€Œå¹´æŠ¥ã€åˆ†ç»„ 11 ç±»é€‰é¡¹
  - `0c16e56`(同事):6 ä¸ªå¹´æŠ¥æ¨¡æ¿å…¥åº“(docs/年报/,.xls)
- **⚠️ å¹´æŠ¥åŠŸèƒ½åªæœ‰ UI å£³ï¼ŒåŒäº‹å¼€å‘中,我方约定不动:**前端 groups æœ‰ 11 ä¸ª annual* é€‰é¡¹ï¼Œä½† importApiMap æ—  URL、后端 DataImportController æ—  annual* ç«¯ç‚¹ã€templateName/templateUrl æ— æ˜ å°„、模板只有 6/11 ç±» â†’ é€‰äº†å¹´æŠ¥ç±»åž‹æ— æ³•上传/下载。同事继续开发,后续拉取即可。
- **打包发布(已完成,用户已发布到用户环境)**:后端 mvn clean package(jar 57MB)+ å‰ç«¯ npm run build(dist 38 æ–‡ä»¶ï¼‰ï¼›éƒ¨ç½²åˆ° D:\trafficAudit:app.jar æ›¿æ¢ï¼ˆæ—§åŒ…备份 app.jar.bak_20260901)、web/ é‡å»ºã€docs å•向同步(新增 docs/年报 + 2 ä¸ªæ–°æ¨¡æ¿ï¼Œä¿ç•™ ai_demo/usage_screenshots);本机启动验证 8090:Started TrafficAuditApplication、首页 200、/api/auth/captcha-config required=false,验证后已停。
- **待办不变**:正式发布前 app.captcha.required æ”¹å›ž true;浏览器崩溃如在新包再现看 logs/access.*.log + å®¢æˆ·ç«¯ F12;态势图/AI RAG/经济指标试算仅记录未实现。
### 10. å…³æœºäº¤æŽ¥å¿«ç…§ï¼ˆ09-01 æ™šï¼‰
- **当前运行状态**:dev å‰åŽç«¯æœªè¿è¡Œï¼›D:\trafficAudit çš„ app.jar(PID 28976,14:40 ç”¨æˆ·è‡ªå·±å¯åŠ¨ï¼‰åœ¨ 8090 è¿è¡Œï¼Œå…³æœºè‡ªç„¶åœæ­¢ã€ä¸éœ€å¤„理;MySQL80(3305)、MySQL57(3306) å‡ Running,按惯例保留。
- **git**:工作区干净,HEAD=`9b97b92`,已同步 origin/main。
- **今日交付**:拉取同事代码(92528d9/053ebfe/0c16e56)、模板入库(b1be169)、打包发布到 D:\trafficAudit å¹¶æœ¬æœºéªŒè¯é€šè¿‡ï¼ˆæ—§åŒ…备份 app.jar.bak_20260901),用户已发布到用户环境;版本号咨询已答复(SemVer,首次正式发布定 1.0.0)。
- **待办(不变)**:①正式发布前 app.captcha.required æ”¹å›ž true;②浏览器崩溃如再现看 logs/access.*.log + å®¢æˆ·ç«¯ F12;③同事年报功能开发中(我方不动);④态势图/AI RAG/经济指标试算仅记录未实现;⑤每日反思自动化未创建。
- **⚠️ å‘布后又拉到同事 2 ä¸ªæ–°æäº¤**(关机前发现):`fbc1c15` æ•°æ®å¯¼å…¥ä¸ŽæŠ¥è¡¨ç”Ÿæˆå¤šé¡¹æ”¹è¿›ã€`91d7eee` æŠ•资系统基准导入兼容查询结果表头(行号/单位/时期/计划总投资)。今天 11:43 æ‰“包发布的版本不含这两项,用户环境当前版本落后 HEAD 2 ä¸ªæäº¤ï¼›ä¸‹æ¬¡å‘布前需重新拉取 + é‡æ–°æ‰“包。
## åŽ†å²ï¼ˆ2026-08-31,含 08-28 èµ·ç§¯ç´¯è®°å½•)
### 1. ç™»å½•验证码:测试阶段可跳过(已完成并验证)
- åŽç«¯æ–°å¢ž `app.captcha.required` å¼€å…³ï¼ˆç¼ºçœ `true`=启用);本机 application.yml ä¸Ž example çŽ°ä¸º `false`(测试阶段跳过,**正式发布前改回 `true`**)。
docs/¹«Â·ÂÿÍ/¸öÌå¿ÍÔËÁ¿»ã×Ü.xlsx
Binary files differ
docs/³ÇÊпÍÔË/µ¼ÈëÄ£°å_³ö×â³µÐÅÏ¢(ÔËÕþ³µÁ¾ÐÅÏ¢).xlsx
Binary files differ
docs/Ä걨/Ä£°å_¹«¹²Æûµç³µÔËÓªÇé¿ö.xls
Binary files differ
docs/Ä걨/Ä£°å_¹«Â·ÂÿÍÔËÊäÄÜÔ´ÏûºÄÇé¿ö.xls
Binary files differ
docs/Ä걨/Ä£°å_µ¥Î»»ù±¾Çé¿ö.xls
Binary files differ
docs/Ä걨/Ä£°å_³ÇÊйìµÀ½»Í¨ÈËÔ±¼°ÔËÓªÇé¿ö.xls
Binary files differ
docs/Ä걨/Ä£°å_³ÇÊйìµÀ½»Í¨³µÁ¾¼°Ïß·Çé¿ö.xls
Binary files differ
docs/Ä걨/Ä£°å_·¨ÈËÆóÒµ²ÆÎñ×´¿ö.xls
Binary files differ
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java
@@ -267,6 +267,20 @@
        }
    }
    /** å¤šæ–‡ä»¶æ‰¹é‡å¯¼å…¥ï¼šå‰ç«¯ä¸€æ¬¡é€‰æ‹©å¤šä¸ª Excel ä¸Šä¼ ï¼ˆtype ä¸ºæ•°æ®ç±»åž‹ï¼ŒæŠ•资类型按市州单表解析) */
    @PostMapping("/batch")
    public Result<Object> importBatch(@RequestParam("type") String type,
                                      @RequestParam("period") String period,
                                      @RequestParam("files") List<MultipartFile> files) {
        try {
            Map<String, Object> result = importService.importBatchFiles(type, period, files);
            return Result.ok(result);
        } catch (Exception e) {
            log.error("batch import error", e);
            return Result.error("批量导入失败: " + e.getMessage());
        }
    }
    /** æŠ•资市州单表批量导入(M2):扫描 docs/投资/输入/{类别}/{X月} ç›®å½•全部市州报表,category=客运站场/物流园区 */
    @PostMapping("/investCityBatch")
    public Result<Object> importInvestCityBatch(@RequestParam("period") String period,
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -168,6 +168,7 @@
        Map<String, Integer> colMap;
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderCols(sheet, "道路货物运输月度生产情况", "企业名称", "货运量_总计");
            colMap = buildHeaderMap(sheet.getRow(0));
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
@@ -284,6 +285,7 @@
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            // sheet0: å…¬è·¯æ—…客6月报
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderCols(sheet, "公路旅客运输月度生产情况", "企业名称", "客运量_总计");
            Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
@@ -476,6 +478,7 @@
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
            requireHeaderCols(sheet, "旅客运政车辆数", "企业名称", "车辆数", "载客位数");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -530,6 +533,7 @@
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            // sheet0: ç¬¬äºŒå­£åº¦è´§è½¦èƒ½è€—明细(每行左右各5组车辆,每组固定18列)
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderKeyword(sheet, "道路货运车辆能源消耗情况", "企业名称", "车牌号");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -648,6 +652,7 @@
        List<EnergyAuthVehicle> authList = new ArrayList<>();
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderKeyword(sheet, "能耗车辆运政信息", "车牌号");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -716,6 +721,7 @@
        int failRows = 0;
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderKeyword(sheet, "运政车辆", "企业名称");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -763,6 +769,7 @@
        int failRows = 0;
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderKeyword(sheet, "轨迹里程", "企业名称");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -806,6 +813,14 @@
        List<ScaleSplitTransport> list = new ArrayList<>();
        int failRows = 0;
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            boolean hasPeriodSheet = false;
            for (int i = 0; i < wb.getNumberOfSheets(); i++) {
                String sn = wb.getSheetAt(i).getSheetName();
                if (sn != null && (sn.contains("当月") || sn.contains("累计"))) { hasPeriodSheet = true; break; }
            }
            if (!hasPeriodSheet) {
                throw new RuntimeException("所选数据类型【规上规下拆分】与文件内容不符:未找到含 å½“月/累计 çš„ sheet,请确认是否选错了数据类型");
            }
            for (int i = 0; i < wb.getNumberOfSheets(); i++) {
                Sheet sheet = wb.getSheetAt(i);
                String sheetName = sheet.getSheetName();
@@ -896,6 +911,18 @@
        int monthCount = Integer.parseInt(period.split("-")[1]);
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            boolean cityRowFound = false;
            for (int r = 1; r <= Math.min(sheet.getLastRowNum(), 30); r++) {
                Row rr = sheet.getRow(r);
                if (rr == null) continue;
                String nm = getString(rr, 0);
                if (nm == null || nm.trim().isEmpty()) continue;
                String region = RegionUtil.normalizeCityName(nm);
                if (RegionUtil.cityList().contains(region) || "湖北省".equals(region)) { cityRowFound = true; break; }
            }
            if (!cityRowFound) {
                throw new RuntimeException("所选数据类型【货运量周转量】与文件内容不符:未识别到市州行(首列应为市州名称),请确认是否选错了数据类型");
            }
            Map<String, FreightTurnoverImport> freightMap = new HashMap<>();
            Map<String, FreightTurnoverImport> turnoverMap = new HashMap<>();
            Set<String> seen = new HashSet<>();
@@ -1152,6 +1179,33 @@
        }
    }
    /** ç±»åž‹-表头校验:按必需列名(解析器实际依赖的列),缺失即抛"类型与文件不符" */
    private void requireHeaderCols(Sheet sheet, String typeLabel, String... cols) {
        Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0));
        List<String> missing = new ArrayList<>();
        for (String c : cols) {
            if (!colMap.containsKey(c)) missing.add(c);
        }
        if (!missing.isEmpty()) {
            throw new RuntimeException("所选数据类型【" + typeLabel + "】与文件内容不符:表头缺少必需列【" + String.join("】【", missing) + "】,请确认是否选错了数据类型");
        }
    }
    /** ç±»åž‹-表头校验:扫描表头行,命中任一关键字即通过 */
    private void requireHeaderKeyword(Sheet sheet, String typeLabel, String... keywords) {
        Row header = sheet == null ? null : sheet.getRow(0);
        if (header != null) {
            for (Cell cell : header) {
                String v = FORMATTER.formatCellValue(cell);
                if (v == null || v.isEmpty()) continue;
                for (String k : keywords) {
                    if (v.contains(k)) return;
                }
            }
        }
        throw new RuntimeException("所选数据类型【" + typeLabel + "】与文件内容不符:表头未包含【" + String.join("】【", keywords) + "】等列,请确认是否选错了数据类型");
    }
    /** æŒ‰è¡¨å¤´åç§°å»ºç«‹åˆ—号映射,兼容新旧模板列位差异 */
    private Map<String, Integer> buildHeaderMap(Row header) {
        Map<String, Integer> colMap = new HashMap<>();
@@ -1398,7 +1452,7 @@
                }
            }
            if (headerRow < 0) {
                throw new RuntimeException("未找到表头行(需包含 åºå·/项目名称 åˆ—)");
                throw new RuntimeException("所选数据类型【" + (passenger ? "投资月报(客运站场)" : "投资月报(物流园区)") + "】与文件内容不符:未找到表头行(需包含 åºå·/项目名称 åˆ—),请确认是否选错了数据类型");
            }
            for (int r = headerRow + 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
@@ -1553,11 +1607,19 @@
            }
            fileResults.add(fr);
        }
        // å¹‚等清理:同类别同报表期月度数据 + INVEST å®¡æ ¸ç»“果(项目主档跨月保留,按名 upsert)
        cleanInvestMonthly(category, period);
        // å¹‚等清理:按本次文件涉及市州清理月度数据 + INVEST å®¡æ ¸ç»“果(项目主档跨月保留,按名 upsert)
        cleanInvestMonthlyByCity(category, period, rows);
        Map<String, Set<String>> knownCities = loadInvestKnownCities(category);
        Map<String, String> fileFirstMismatch = new LinkedHashMap<>();
        int success = 0;
        List<String> failDetails = new ArrayList<>();
        for (InvestCityRow item : rows) {
            String mismatch = investCityMismatchReason(item, knownCities);
            if (mismatch != null) {
                failDetails.add(mismatch);
                fileFirstMismatch.putIfAbsent(item.fileName, mismatch);
                continue;
            }
            try {
                saveInvestProjectRow(item.project, item.monthly);
                success++;
@@ -1569,6 +1631,11 @@
                failDetails.add(item.fileName + " ç¬¬ " + item.excelRow + " è¡Œï¼ˆ" + item.project.getProjectName() + "):" + reason);
                log.error("Invest city insert error: file {}, row {}", item.fileName, item.excelRow, e);
            }
        }
        // æŠŠé¦–个市州错位提示挂到对应文件结果上,便于页面弹窗直接看到
        for (Map<String, Object> fr2 : fileResults) {
            String msg = fileFirstMismatch.get(fr2.get("fileName"));
            if (msg != null && fr2.get("error") == null) fr2.put("error", msg);
        }
        String errorDetail = String.join("\n", failDetails);
        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n……";
@@ -1587,6 +1654,119 @@
        result.put("fiveYearTemplateProjects", fiveYearNames.size());
        result.put("fiveYearMatched", fiveYearMatched);
        log.info("Invest city {} {} imported: {} rows (fiveYear matched {}/{})",
            category, period, success, fiveYearMatched, fiveYearNames.size());
        return result;
    }
    /**
     * å¤šæ–‡ä»¶æ‰¹é‡å¯¼å…¥ï¼šå‰ç«¯ä¸€æ¬¡é€‰æ‹©å¤šä¸ª Excel ä¸Šä¼ ã€‚
     * æŠ•资类型(investment/investmentLogistics)按市州单表解析(文件名/填报单位识别市州,十五五自动标记);
     * å…¶ä»–类型逐个走现有解析逻辑(importDirFile)。
     */
    public Map<String, Object> importBatchFiles(String type, String period, List<MultipartFile> files) throws Exception {
        if (files == null || files.isEmpty()) {
            throw new RuntimeException("未接收到任何文件");
        }
        if ("investment".equals(type) || "investmentLogistics".equals(type)) {
            String category = "investment".equals(type) ? "客运站场" : "物流园区";
            return importInvestCityFiles(period, category, files);
        }
        List<Map<String, Object>> fileResults = new ArrayList<>();
        int totalSuccess = 0, totalFail = 0;
        for (MultipartFile f : files) {
            Map<String, Object> fr = new LinkedHashMap<>();
            fr.put("fileName", f.getOriginalFilename());
            try {
                ImportResult r = importDirFile(type, f, period);
                fr.put("success", r.getSuccess());
                fr.put("fail", r.getFail());
                if (r.getFail() > 0 && r.getFailDetails() != null && !r.getFailDetails().isEmpty()) {
                    fr.put("error", r.getFailDetails().get(0));
                }
                totalSuccess += r.getSuccess();
                totalFail += r.getFail();
            } catch (Exception e) {
                fr.put("success", 0);
                fr.put("fail", 0);
                fr.put("error", e.getMessage() == null ? e.toString() : e.getMessage());
                totalFail++;
            }
            fileResults.add(fr);
        }
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("type", type);
        result.put("total", files.size());
        result.put("success", totalSuccess);
        result.put("fail", totalFail);
        result.put("files", fileResults);
        return result;
    }
    /** æŠ•资多文件批量:逐文件按市州单表解析,统一幂等清理落库,批次汇总 */
    public Map<String, Object> importInvestCityFiles(String period, String category, List<MultipartFile> files) throws Exception {
        boolean passenger = "客运站场".equals(category);
        Set<String> fiveYearNames = loadFiveYearProjectNames();
        List<InvestCityRow> rows = new ArrayList<>();
        List<Map<String, Object>> fileResults = new ArrayList<>();
        for (MultipartFile f : files) {
            Map<String, Object> fr = new LinkedHashMap<>();
            fr.put("fileName", f.getOriginalFilename());
            try (Workbook wb = WorkbookFactory.create(f.getInputStream())) {
                parseInvestCityWorkbook(wb, f.getOriginalFilename(), category, period, fiveYearNames, rows, fr);
            } catch (Exception e) {
                fr.put("success", 0);
                fr.put("error", e.getMessage() == null ? e.toString() : e.getMessage());
                log.error("invest city file parse error: {}", f.getOriginalFilename(), e);
            }
            fileResults.add(fr);
        }
        String catDir = passenger ? "客运(客运站)" : "物流(物流园区)";
        cleanInvestMonthlyByCity(category, period, rows);
        Map<String, Set<String>> knownCities = loadInvestKnownCities(category);
        Map<String, String> fileFirstMismatch = new LinkedHashMap<>();
        int success = 0;
        List<String> failDetails = new ArrayList<>();
        for (InvestCityRow item : rows) {
            String mismatch = investCityMismatchReason(item, knownCities);
            if (mismatch != null) {
                failDetails.add(mismatch);
                fileFirstMismatch.putIfAbsent(item.fileName, mismatch);
                continue;
            }
            try {
                saveInvestProjectRow(item.project, item.monthly);
                success++;
            } catch (Exception e) {
                Throwable cause = e;
                while (cause.getCause() != null) cause = cause.getCause();
                String reason = cause.getMessage();
                if (reason == null || reason.trim().isEmpty()) reason = e.getMessage();
                failDetails.add(item.fileName + " ç¬¬ " + item.excelRow + " è¡Œï¼ˆ" + item.project.getProjectName() + "):" + reason);
                log.error("Invest city insert error: file {}, row {}", item.fileName, item.excelRow, e);
            }
        }
        // æŠŠé¦–个市州错位提示挂到对应文件结果上,便于页面弹窗直接看到
        for (Map<String, Object> fr2 : fileResults) {
            String msg = fileFirstMismatch.get(fr2.get("fileName"));
            if (msg != null && fr2.get("error") == null) fr2.put("error", msg);
        }
        String errorDetail = String.join("\n", failDetails);
        if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\n……";
        recordBatch("多文件批量导入(" + catDir + "/" + period + ")", "INVEST_" + category + "_CITY",
            period, rows.size(), success, failDetails.size(), errorDetail);
        int fiveYearMatched = 0;
        for (InvestCityRow item : rows) {
            if (Integer.valueOf(1).equals(item.project.getIsFiveYear())) fiveYearMatched++;
        }
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("total", rows.size());
        result.put("success", success);
        result.put("fail", failDetails.size());
        result.put("failDetails", failDetails);
        result.put("files", fileResults);
        result.put("fiveYearTemplateProjects", fiveYearNames.size());
        result.put("fiveYearMatched", fiveYearMatched);
        log.info("Invest city multi-file {} {} imported: {} rows (fiveYear matched {}/{})",
            category, period, success, fiveYearMatched, fiveYearNames.size());
        return result;
    }
@@ -1627,6 +1807,7 @@
        }
        String city = cityFromFileName(fileName);
        if (city == null) city = cityFromSheetHeader(best);
        fr.put("city", city == null ? "" : city);
        String currentCity = city;
        Map<String, Integer> col = bestCol;
        int nameCol = col.get("name");
@@ -1637,6 +1818,15 @@
            Row row = best.getRow(r);
            if (row == null) continue;
            String name = cellStr(row, nameCol);
            String seq = hasSeq ? cellStr(row, seqCol) : null;
            // å¸‚州小节行:部分汇总表把市州名写在序号列(如十堰汇总表 A åˆ—写"武汉"),需同样识别
            if (seq != null && !seq.trim().isEmpty()) {
                String seqCity = investmentCity(seq.trim());
                if (seqCity != null) {
                    currentCity = seqCity;
                    continue;
                }
            }
            if (name == null || name.trim().isEmpty()) continue;
            String nt = name.trim().replaceAll("[\r\n]+", "");
            if (nt.contains("合计") || nt.contains("总计")) continue;
@@ -1645,7 +1835,6 @@
            if (nt.replace(" ", "").matches(".*小\\s*计.*")) continue;
            if (nt.startsWith("填报") || nt.startsWith("单位负责人") || nt.matches("^\\d+、.*")) continue;
            if (hasSeq) {
                String seq = cellStr(row, seqCol);
                if (seq == null || seq.trim().isEmpty()) {
                    // å¸‚州小节行(如 "武汉" "天 é—¨"),更新当前市州
                    String c = investmentCity(nt);
@@ -1884,7 +2073,7 @@
        return null;
    }
    /** åŒç±»åˆ«åŒæŠ¥è¡¨æœŸæœˆåº¦æ•°æ® + INVEST å®¡æ ¸ç»“果清理(幂等) */
    /** åŒç±»åˆ«åŒæŠ¥è¡¨æœŸæœˆåº¦æ•°æ® + INVEST å®¡æ ¸ç»“果清理(幂等,全省汇总大表全量清理) */
    private void cleanInvestMonthly(String category, String period) {
        List<InvestmentProject> sameCat = investProjectMapper.selectList(
            new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category));
@@ -1895,15 +2084,60 @@
                .eq(InvestmentMonthly::getReportPeriod, period)
                .in(InvestmentMonthly::getProjectId, catProjectIds));
        }
        deleteInvestAuditResults(period, null);
    }
    /** æŒ‰å¸‚州清理:只删除本次文件涉及市州(含未识别市州的 null é¡¹ç›®ï¼‰çš„æœˆåº¦æ•°æ® + INVEST å®¡æ ¸ç»“æžœ */
    private void cleanInvestMonthlyByCity(String category, String period, List<InvestCityRow> rows) {
        Set<String> citySet = new HashSet<>();
        boolean hasNullCity = false;
        for (InvestCityRow item : rows) {
            String c = item.project.getCity();
            if (c == null || c.trim().isEmpty()) hasNullCity = true;
            else citySet.add(c.trim());
        }
        if (citySet.isEmpty() && !hasNullCity) return;
        boolean cleanNullCity = hasNullCity;
        LambdaQueryWrapper<InvestmentProject> pw = new LambdaQueryWrapper<InvestmentProject>()
            .eq(InvestmentProject::getCategory, category);
        pw.and(w -> {
            if (!citySet.isEmpty()) w.in(InvestmentProject::getCity, citySet);
            if (cleanNullCity) {
                if (!citySet.isEmpty()) w.or();
                w.isNull(InvestmentProject::getCity);
            }
        });
        List<InvestmentProject> cityProjects = investProjectMapper.selectList(pw);
        java.util.Set<Long> catProjectIds = new HashSet<>();
        for (InvestmentProject pc : cityProjects) catProjectIds.add(pc.getId());
        if (catProjectIds.isEmpty()) return;
        List<InvestmentMonthly> oldMonthlies = investMonthlyMapper.selectList(
            new LambdaQueryWrapper<InvestmentMonthly>()
                .eq(InvestmentMonthly::getReportPeriod, period)
                .in(InvestmentMonthly::getProjectId, catProjectIds));
        java.util.Set<Long> oldMonthlyIds = new HashSet<>();
        for (InvestmentMonthly m : oldMonthlies) oldMonthlyIds.add(m.getId());
        investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>()
            .eq(InvestmentMonthly::getReportPeriod, period)
            .in(InvestmentMonthly::getProjectId, catProjectIds));
        deleteInvestAuditResults(period, oldMonthlyIds);
    }
    /** æ¸…理 INVEST å®¡æ ¸ç»“果(monthlyIds ä¸º null è¡¨ç¤ºè¯¥æœŸé—´å…¨é‡æ¸…理) */
    private void deleteInvestAuditResults(String period, java.util.Set<Long> monthlyIds) {
        List<Long> investRuleIds = new ArrayList<>();
        for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) {
            if ("INVEST".equals(rule.getReportType())) investRuleIds.add(rule.getId());
        }
        if (!investRuleIds.isEmpty()) {
            auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
                .eq(AuditResult::getReportPeriod, period)
                .in(AuditResult::getRuleId, investRuleIds));
        if (investRuleIds.isEmpty()) return;
        LambdaQueryWrapper<AuditResult> w = new LambdaQueryWrapper<AuditResult>()
            .eq(AuditResult::getReportPeriod, period)
            .in(AuditResult::getRuleId, investRuleIds);
        if (monthlyIds != null) {
            if (monthlyIds.isEmpty()) return;
            w.in(AuditResult::getReportId, monthlyIds);
        }
        auditResultMapper.delete(w);
    }
    /** é¡¹ç›®ä¸»æ¡£ upsert(按 é¡¹ç›®å+类别)+ æœˆåº¦å†™å…¥ */
@@ -1922,6 +2156,32 @@
            monthly.setProjectId(exist.getId());
        }
        investMonthlyMapper.insert(monthly);
    }
    /** è½½å…¥æŸç±»åˆ«ã€Œè§„范化项目名 -> å·²å…¥åº“市州集合」,用于市州一致性校验(名称在多个市州重复时放弃校验) */
    private Map<String, Set<String>> loadInvestKnownCities(String category) {
        Map<String, Set<String>> map = new HashMap<>();
        List<InvestmentProject> list = investProjectMapper.selectList(
            new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category));
        for (InvestmentProject p : list) {
            if (p.getCity() == null || p.getCity().trim().isEmpty()) continue;
            map.computeIfAbsent(normalizeProjectName(p.getProjectName()), k -> new HashSet<>())
               .add(p.getCity().trim());
        }
        return map;
    }
    /** å¸‚州一致性校验:库中唯一归属市州与本次解析不一致时返回错误说明,否则返回 null */
    private String investCityMismatchReason(InvestCityRow item, Map<String, Set<String>> knownCities) {
        String parsed = item.project.getCity();
        if (parsed == null || parsed.trim().isEmpty()) return null;
        Set<String> known = knownCities.get(normalizeProjectName(item.project.getProjectName()));
        if (known == null || known.size() != 1) return null;
        String knownCity = known.iterator().next();
        if (knownCity.equals(parsed.trim())) return null;
        return item.fileName + " ç¬¬ " + item.excelRow + " è¡Œï¼ˆ" + item.project.getProjectName()
            + "):市州一致性校验未通过——库中该项目归属「" + knownCity + "」,本次文件解析为「" + parsed.trim()
            + "」,疑似市州错位,已跳过导入,请核对源文件";
    }
    private String cellStr(Row row, int idx) {
@@ -1959,6 +2219,7 @@
        int failRows = 0;
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderKeyword(sheet, "投资系统导出", "项目名称", "计划总投资", "单位");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
@@ -2198,6 +2459,7 @@
        List<CityTaxiAuth> list = new ArrayList<>();
        try (Workbook wb = WorkbookFactory.create(file.getInputStream())) {
            Sheet sheet = wb.getSheetAt(0);
            requireHeaderKeyword(sheet, "出租车运政车辆", "车牌");
            for (int r = 1; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) continue;
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -3221,11 +3221,11 @@
    }
    /** è¡¨1 é¡¹ç›®è¡Œå¡«å……(模板行/插入行共用;cityText ä¸Žæ¨¡æ¿ B åˆ—格式一致) */
    private void fillInvestPlanProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq, String cityText) {
    private void fillInvestPlanProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq, String cityText, String displayName) {
        hssfNum(row, 0, (double) seq);
        hssfText(row, 1, cityText);
        hssfText(row, 2, valueOf(p.getCounty()));
        hssfText(row, 3, p.getProjectName());
        hssfText(row, 3, displayName != null && !displayName.trim().isEmpty() ? displayName.trim() : p.getProjectName());
        hssfText(row, 4, valueOf(p.getConstructNature()));
        hssfNum(row, 5, parseYearNum(p.getStartTime()));
        hssfNum(row, 6, parseYearNum(p.getEndTime()));
@@ -3332,6 +3332,7 @@
        // åŽŸä½é€è¡Œæ›¿æ¢ï¼šåˆè®¡/块标题/市州小节/分组/项目行;未匹配的模板行清空样例数据(结构/合并/列宽不变)
        int seq = 0;
        int si = -1;
        java.util.Set<Long> usedProjectIds = new HashSet<>();
        for (int r = 5; r <= lastRow; r++) {
            HSSFRow row = sheet.getRow(r);
            if (row == null) continue;
@@ -3391,11 +3392,12 @@
            // é¡¹ç›®è¡Œï¼šæŒ‰ æ¨¡æ¿åç§°+市州+块类别 åŒ¹é… DB é¡¹ç›®ï¼ŒæœªåŒ¹é…æ•´è¡Œæ¸…空
            blankInvestRow(row, 25);
            if (blockCategory == null) continue; // è€æ—§è´§è½¦æ®µæ— æ•°æ®æº
            InvestmentProject p = matchInvest(projects, d, b, blockCategory);
            InvestmentProject p = matchInvest(projects, d, b, blockCategory, usedProjectIds);
            if (p == null) continue;
            usedProjectIds.add(p.getId());
            if (sec != null) sec.matched.add(p.getId());
            InvestmentMonthly m = mByProject.get(p.getId());
            fillInvestPlanProjectRow(row, p, m, ++seq, b);
            fillInvestPlanProjectRow(row, p, m, ++seq, b, d);
        }
        // åŠ¨æ€æ’è¡Œï¼šDB é¡¹ç›®æ•° > æ¨¡æ¿é¢„留行数时,在市州小节末尾补齐(自底向上插行,避免行号错位)
        for (int i = sections.size() - 1; i >= 0; i--) {
@@ -3415,7 +3417,7 @@
                HSSFRow row = sheet.getRow(first + j);
                if (row == null) continue;
                InvestmentProject p = extra.get(j);
                fillInvestPlanProjectRow(row, p, mByProject.get(p.getId()), 0, p.getCity());
                fillInvestPlanProjectRow(row, p, mByProject.get(p.getId()), 0, p.getCity(), p.getProjectName());
            }
        }
        renumberInvestSeq(sheet, "investPlan");
@@ -3725,6 +3727,11 @@
    /** åœ¨å€™é€‰é¡¹ç›®æ± ä¸­æŒ‰åç§°åŒ¹é… DB é¡¹ç›®ï¼ˆå¯é€‰ å¸‚å·ž/类别 è¿‡æ»¤ + åŽ¿åŒºå‰ç¼€æ¶ˆæ­§ï¼‰ï¼Œæœªå‘½ä¸­è¿”å›ž null */
    private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category) {
        return matchInvest(pool, name, cityFull, category, null);
    }
    /** å¸¦å·²å ç”¨æŽ’除的匹配:同一 DB é¡¹ç›®åªå…è®¸å¡«å……一行(避免模板一期/二期同名行重复输出) */
    private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category, Set<Long> excludeIds) {
        if (name == null || name.trim().isEmpty()) return null;
        String norm = normInvestName(name);
        if (norm.isEmpty()) return null;
@@ -3732,6 +3739,7 @@
        String county = extractInvestCounty(name);
        List<InvestmentProject> cands = new java.util.ArrayList<>();
        for (InvestmentProject p : pool) {
            if (excludeIds != null && excludeIds.contains(p.getId())) continue;
            if (category != null && !category.equals(p.getCategory())) continue;
            if (cityCanon != null && !cityCanon.equals(RegionUtil.normalizeCityName(p.getCity()))) continue;
            cands.add(p);
traffic-audit-web/src/views/DataImport.vue
@@ -51,15 +51,15 @@
                  <el-button size="mini" type="primary" icon="el-icon-upload2" :loading="cityDirLoading"
                             @click="importCityDir(moduleSel[g.key])">从目录批量导入</el-button>
                </div>
                <div v-else-if="moduleSel[g.key]" class="module-file">
                  <div class="file-name" :title="fileNames[moduleSel[g.key]]">{{ fileNames[moduleSel[g.key]] || '未选择文件' }}</div>
                <div v-else-if="moduleSel[g.key] && !isDirOnlyType(moduleSel[g.key])" class="module-file">
                  <div class="file-name" :title="fileNamesText(moduleSel[g.key])">{{ fileNamesText(moduleSel[g.key]) || '未选择文件' }}</div>
                  <div class="file-btns">
                    <el-upload action="#" :auto-upload="false" :show-file-list="false" accept=".xlsx,.xls"
                    <el-upload action="#" :auto-upload="false" :show-file-list="false" multiple accept=".xlsx,.xls,.et"
                               :ref="'up' + g.key" :on-change="f => onFilePicked(g.key, f)" style="display:inline-block">
                      <el-button size="mini" type="primary" plain icon="el-icon-folder-opened">选择文件</el-button>
                    </el-upload>
                    <el-button size="mini" type="primary" icon="el-icon-upload2" :disabled="!files[moduleSel[g.key]]"
                               @click="importNow(g.key)">立即导入</el-button>
                    <el-button size="mini" type="primary" icon="el-icon-upload2" :disabled="!fileCount(moduleSel[g.key])"
                               @click="importNow(g.key)">{{ fileCount(moduleSel[g.key]) > 1 ? '批量导入 ' + fileCount(moduleSel[g.key]) + ' ä¸ªæ–‡ä»¶' : '立即导入' }}</el-button>
                  </div>
                </div>
              </div>
@@ -167,8 +167,8 @@
      overview: {},
      groups: [
        {
          key: 'freight', name: '货运', types: [
            { value: 'h2032', label: 'H203-2月报' },
          key: 'freight', name: '道路货运', types: [
            { value: 'h2032', label: '道路货物运输月度生产情况' },
            { value: 'transportAuth', label: '运政车辆' },
            { value: 'trackMileage', label: '轨迹里程' },
            { value: 'scaleSplit', label: '规上规下拆分' },
@@ -177,20 +177,20 @@
        },
        {
          key: 'passenger', name: '公路旅客', types: [
            { value: 'passengerMonthly', label: 'H203-1旅客月报' },
            { value: 'passengerMonthly', label: '公路旅客运输月度生产情况' },
            { value: 'passengerAuth', label: '运政车辆数' },
            { value: 'passengerIndividual', label: '个体客运量/周转量' }
          ]
        },
        {
          key: 'city', name: '城市客运', types: [
            { value: 'cityBus', label: '城市公交月报' },
            { value: 'cityTaxi', label: '巡游出租月报' },
            { value: 'cityBus', label: '城市公共交通月度运营情况' },
            { value: 'cityTaxi', label: '巡游出租汽车运营服务情况月报' },
            { value: 'cityTaxiAuth', label: '出租车运政车辆' }
          ]
        },
        {
          key: 'invest', name: '投资', types: [
          key: 'invest', name: '站场投资', types: [
            { value: 'investment', label: '投资月报(客运站场)' },
            { value: 'investmentLogistics', label: '投资月报(物流园区)' },
            { value: 'investmentSystem', label: '投资系统导出' },
@@ -199,9 +199,24 @@
          ]
        },
        {
          key: 'energy', name: '能耗', types: [
            { value: 'energyMonthly', label: 'H204能耗季报' },
          key: 'energy', name: '货运能耗季报', types: [
            { value: 'energyMonthly', label: '道路货运车辆能源消耗情况' },
            { value: 'energyAuth', label: '能耗车辆运政信息' }
          ]
        },
        {
          key: 'annual', name: '年报', types: [
            { value: 'annualCompanyBasic', label: '单位基本情况' },
            { value: 'annualEnterpriseFinance', label: '法人企业财务状况' },
            { value: 'annualPassengerEnergy', label: '公路旅客运输能源消耗情况' },
            { value: 'annualBusOperation', label: '公共汽电车运营情况' },
            { value: 'annualMetroVehicles', label: '城市轨道交通车辆及线路情况' },
            { value: 'annualMetroStaff', label: '城市轨道交通人员及运营情况' },
            { value: 'annualFerry', label: '城市客运轮渡运营情况' },
            { value: 'annualPublicEnergy', label: '公共交通能源消耗情况' },
            { value: 'annualCityInfrastructureOperators', label: '城市客运基础设施情况、城市客运经营业户情况' },
            { value: 'annualTaxiOwnership', label: '巡游出租汽车营运车辆拥有情况' },
            { value: 'annualTaxiProduction', label: '巡游出租汽车运输生产情况' }
          ]
        }
      ]
@@ -240,7 +255,7 @@
        investCityLogistics: '—(批量扫描目录,无需模板)',
        cityBus: '导入_城市公共交通月度运营情况(YYYYå¹´M月).xls/.xlsx(企业级明细,取公共汽电车字段)',
        cityTaxi: '导入_巡游出租汽车运营服务情况月报(YYYYå¹´M月).xls(市州级17行)',
        cityTaxiAuth: '导入_出租车信息(运政车辆信息).xls(车辆级明细,用于运营车数对比)'
        cityTaxiAuth: '导入模板_出租车信息(运政车辆信息).xlsx(车辆级明细,用于运营车数对比)'
      }
      return map[this.importType] || ''
    },
@@ -253,12 +268,12 @@
        freightTurnover: '货运/模板_货运量周转量.xlsx',
        passengerMonthly: '公路旅客/模板_交企统H203-1表_公路旅客运输月度生产情况.xlsx',
        passengerAuth: '公路旅客/模板_公路旅客运政车辆数.xlsx',
        passengerIndividual: '',
        passengerIndividual: '公路旅客/个体客运量汇总.xlsx',
        energyMonthly: '能耗/模板_道路货运车辆能源消耗情况(2026).xlsx',
        energyAuth: '能耗/模板_能耗车辆运政信息.xlsx',
        cityBus: '城市客运/导入模板_城市公交.xlsx',
        cityTaxi: '城市客运/导入模板_巡游出租.xlsx',
        cityTaxiAuth: '城市客运/导入模板_出租车信息(运政车辆信息).xls',
        cityTaxiAuth: '城市客运/导入模板_出租车信息(运政车辆信息).xlsx',
        investment: '投资/输入/客运(客运站)/更新_7月客运站投资明细表(市州汇总大表,给领导).xlsx',
        investmentLogistics: '投资/输入/物流(物流园区)/更新_7月物流站场投资明细.xls',
        investmentSystem: '投资/模板_查询结果(投资系统-客运+物流 2026.7).xls'
@@ -301,22 +316,31 @@
      return map[t] || ''
    },
    onFilePicked(key, file) {
      this.$set(this.files, this.moduleSel[key], file.raw)
      this.$set(this.fileNames, this.moduleSel[key], file.name)
      const type = this.moduleSel[key]
      if (!type) return
      const list = (this.files[type] || []).filter(f => f.name !== file.name)
      list.push(file.raw)
      this.$set(this.files, type, list)
      this.$set(this.fileNames, type, list.map(f => f.name))
      const up = this.$refs['up' + key]
      if (up && up.clearFiles) up.clearFiles()
    },
    importNow(key) {
      const type = this.moduleSel[key]
      if (!type) return
      if (this.isCityDirType(type)) {
      if (this.isDirOnlyType(type)) {
        this.importCityDir(type)
        this.moduleSel[key] = ''
        this.importType = ''
        return
      }
      const file = this.files[type]
      if (!file) { this.$message.warning('请先为该类型选择文件'); return }
      const fileList = this.files[type] || []
      if (!fileList.length) { this.$message.warning('请先为该类型选择文件'); return }
      if (fileList.length > 1 || type === 'investment' || type === 'investmentLogistics') {
        this.batchImport(type, key, fileList)
        return
      }
      const file = fileList[0]
      if (!this.period) { this.$message.warning('请先选择报表期'); return }
      const g = this.groups.find(x => x.key === key)
      const t = g.types.find(x => x.value === type)
@@ -342,10 +366,57 @@
        this.$delete(this.fileNames, type)
      })
    },
    fileCount(type) { return (this.files[type] || []).length },
    fileNamesText(type) {
      const list = this.fileNames[type] || []
      return list.length ? '已选 ' + list.length + ' ä¸ªæ–‡ä»¶ï¼š' + list.join('、') : ''
    },
    batchImport(type, key, fileList) {
      if (!this.period) { this.$message.warning('请先选择报表期'); return }
      const fd = new FormData()
      fd.append('type', type)
      fd.append('period', this.period)
      fileList.forEach(f => fd.append('files', f))
      const g = this.groups.find(x => x.key === key)
      const label = g ? g.name : type
      this.$message({ message: '正在批量导入 ' + fileList.length + ' ä¸ªæ–‡ä»¶â€¦', duration: 3000 })
      api.post('/import/batch', fd, { timeout: 600000 })
        .then(res => {
          const d = (res && res.data) || {}
          if (!d.files) {
            this.$message.error((res && res.message) || '批量导入失败')
            return
          }
          const files = d.files || []
          const errCount = files.filter(f => f.error || f.success === 0).length
          const html = files.map(f =>
            '<div style="margin:6px 0;line-height:1.6"><b>' + (f.fileName || '') + '</b>' +
            (f.city ? '(' + f.city + ')' : '') + ':解析 ' + (f.success || 0) + ' æ¡' +
            (f.error ? ' <span style="color:#F56C6C">' + f.error + '</span>' : '') + '</div>'
          ).join('')
          this.$alert(html, (label || type) + ' æ‰¹é‡å¯¼å…¥å®Œæˆï¼šå…± ' + d.total + ' ä¸ªæ–‡ä»¶' +
            (d.fail > 0 ? ',失败 ' + d.fail + ' æ¡' : '') + '(成功 ' + d.success + ' æ¡ï¼‰', {
            dangerouslyUseHTMLString: true,
            type: errCount > 0 ? 'warning' : 'success',
            confirmButtonText: '知道了'
          })
          if (type === 'investment' || type === 'investmentLogistics') {
            this.checkInvest(this.period)
          }
          this.afterImport()
          this.files[type] = null
          this.$delete(this.fileNames, type)
        })
        .catch(e => {
          if (e && e._toast) return
          this.$message.error((e.response && e.response.data && e.response.data.message) || '批量导入失败')
        })
    },
    isCityDirType(t) { return t === 'investCityPassenger' || t === 'investCityLogistics' },
    isDirOnlyType(t) { return t === 'investCityPassenger' || t === 'investCityLogistics' },
    importCityDir(type) {
      if (!this.period) { this.$message.warning('请先选择报表期'); return }
      const category = type === 'investCityPassenger' ? '客运站场' : '物流园区'
      const category = (type === 'investCityPassenger' || type === 'investment') ? '客运站场' : '物流园区'
      const fd = new FormData()
      fd.append('period', this.period)
      fd.append('category', category)