feat(数据查看): 隐藏报表期/所属地区+冻结收窄为市州与企业名称、网约车按市州竖排、市州月报显示项目名称、列名补单位、数值千分位与同比按百分比显示
2个文件已修改
285 ■■■■■ 已修改文件
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/DataView.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataViewController.java
@@ -23,6 +23,7 @@
import com.trafficaudit.dataimport.entity.WycOrderMonthly;
import com.trafficaudit.dataimport.entity.WycTotalMonthly;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.reportexport.calc.WycSplitCalc;
import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper;
import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper;
@@ -97,6 +98,8 @@
    private WycOrderMonthlyMapper wycOrderMapper;
    @Resource
    private WycTotalMonthlyMapper wycTotalMapper;
    @Resource
    private WycSplitCalc wycSplitCalc;
    @GetMapping("/list")
    public Result<Map<String, Object>> list(@RequestParam("type") String type,
@@ -120,7 +123,9 @@
                        .orderByDesc(H2032EnterpriseMonthly::getId);
                Page<H2032EnterpriseMonthly> p = new Page<>(safePage, safeSize);
                Page<H2032EnterpriseMonthly> r = h2032Mapper.selectPage(p, w);
                result.put("records", toMaps(r.getRecords(), propsOf(H2032_COLUMNS)));
                List<Map<String, Object>> rows = toMaps(r.getRecords(), propsOf(H2032_COLUMNS));
                fillCity(rows);
                result.put("records", rows);
                fillPage(result, r);
                break;
            }
@@ -173,7 +178,9 @@
                        .orderByDesc(PassengerEnterpriseMonthly::getId);
                Page<PassengerEnterpriseMonthly> p = new Page<>(safePage, safeSize);
                Page<PassengerEnterpriseMonthly> r = passengerMapper.selectPage(p, w);
                result.put("records", toMaps(r.getRecords(), propsOf(H2031_COLUMNS)));
                List<Map<String, Object>> rows = toMaps(r.getRecords(), propsOf(H2031_COLUMNS));
                fillCity(rows);
                result.put("records", rows);
                fillPage(result, r);
                break;
            }
@@ -254,7 +261,9 @@
                        .orderByAsc(InvestmentMonthly::getId);
                Page<InvestmentMonthly> p = new Page<>(safePage, safeSize);
                Page<InvestmentMonthly> r = investmentMonthlyMapper.selectPage(p, w);
                result.put("records", toMaps(r.getRecords(), propsOf(INVEST_MONTHLY_COLUMNS)));
                List<Map<String, Object>> rows = toMaps(r.getRecords(), propsOf(INVEST_MONTHLY_COLUMNS));
                fillProjectInfo(rows);
                result.put("records", rows);
                fillPage(result, r);
                break;
            }
@@ -287,7 +296,9 @@
                        .orderByDesc(EnergyVehicleQuarterly::getId);
                Page<EnergyVehicleQuarterly> p = new Page<>(safePage, safeSize);
                Page<EnergyVehicleQuarterly> r = energyVehicleMapper.selectPage(p, w);
                result.put("records", toMaps(r.getRecords(), propsOf(ENERGY_VEHICLE_COLUMNS)));
                List<Map<String, Object>> rows = toMaps(r.getRecords(), propsOf(ENERGY_VEHICLE_COLUMNS));
                fillCity(rows);
                result.put("records", rows);
                fillPage(result, r);
                break;
            }
@@ -310,11 +321,7 @@
                Page<PassengerIndividualMonthly> p = new Page<>(safePage, safeSize);
                Page<PassengerIndividualMonthly> r = passengerIndividualMapper.selectPage(p, w);
                List<Map<String, Object>> rows = toMaps(r.getRecords(), propsOf(PAX_INDIVIDUAL_COLUMNS));
                for (Map<String, Object> row : rows) {
                    Object rc = row.get("regionCode");
                    String cityName = rc == null ? null : RegionUtil.cityByCode(String.valueOf(rc));
                    row.put("city", cityName == null ? "" : cityName);
                }
                fillCity(rows);
                result.put("records", rows);
                fillPage(result, r);
                break;
@@ -332,11 +339,16 @@
            case "wycOrder": {
                LambdaQueryWrapper<WycOrderMonthly> w = new LambdaQueryWrapper<WycOrderMonthly>()
                        .eq(notEmpty(period), WycOrderMonthly::getReportPeriod, period)
                        .orderByDesc(WycOrderMonthly::getId);
                Page<WycOrderMonthly> p = new Page<>(safePage, safeSize);
                Page<WycOrderMonthly> r = wycOrderMapper.selectPage(p, w);
                result.put("records", toMaps(r.getRecords(), propsOf(WYC_ORDER_COLUMNS)));
                fillPage(result, r);
                        .orderByDesc(WycOrderMonthly::getReportPeriod);
                List<WycOrderMonthly> list = wycOrderMapper.selectList(w);
                List<Map<String, Object>> rows = new ArrayList<>();
                for (WycOrderMonthly o : list) {
                    rows.addAll(wycOrderCityRows(o));
                }
                result.put("records", rows);
                result.put("total", (long) rows.size());
                result.put("current", 1L);
                result.put("size", (long) rows.size());
                break;
            }
            default:
@@ -346,6 +358,95 @@
                result.put("size", safeSize);
        }
        return Result.ok(result);
    }
    /** 区划代码 -> 市州名(无法识别返回空串) */
    private String cityOf(String regionCode) {
        if (regionCode == null || regionCode.trim().isEmpty()) return "";
        String city = RegionUtil.cityByCode(regionCode.trim());
        return city == null ? "" : city;
    }
    /** 按 regionCode 派生"市州"列(企业类表通用) */
    private void fillCity(List<Map<String, Object>> rows) {
        for (Map<String, Object> row : rows) {
            Object rc = row.get("regionCode");
            row.put("city", cityOf(rc == null ? null : String.valueOf(rc)));
        }
    }
    /** 投资月报:补项目名称与市州(原表只有 projectId,页面无法辨识项目) */
    private void fillProjectInfo(List<Map<String, Object>> rows) {
        java.util.Set<Long> ids = new java.util.LinkedHashSet<>();
        for (Map<String, Object> row : rows) {
            Object pid = row.get("projectId");
            if (pid instanceof Number) ids.add(((Number) pid).longValue());
        }
        if (ids.isEmpty()) return;
        Map<Long, InvestmentProject> byId = new LinkedHashMap<>();
        for (InvestmentProject ip : investmentProjectMapper.selectBatchIds(ids)) {
            byId.put(ip.getId(), ip);
        }
        for (Map<String, Object> row : rows) {
            Object pid = row.get("projectId");
            InvestmentProject ip = pid instanceof Number ? byId.get(((Number) pid).longValue()) : null;
            row.put("projectName", ip == null ? null : ip.getProjectName());
            row.put("city", ip == null ? null : ip.getCity());
        }
    }
    private static double nz(Double v) {
        return v == null ? 0.0 : v;
    }
    /** 网约车订单台账 -> 按市州竖排:全省 + 17 市州 × 指标(客运量/周转量由 WycSplitCalc 拆分口径计算) */
    private List<Map<String, Object>> wycOrderCityRows(WycOrderMonthly o) {
        List<Map<String, Object>> rows = new ArrayList<>();
        List<String> cities = RegionUtil.cityList();
        double orderSum = nz(o.getOrderSum());
        double[] orders = { nz(o.getOrderWuhan()), nz(o.getOrderHuangshi()), nz(o.getOrderShiyan()),
                nz(o.getOrderYichang()), nz(o.getOrderXiangyang()), nz(o.getOrderEzhou()), nz(o.getOrderJingmen()),
                nz(o.getOrderXiaogan()), nz(o.getOrderJingzhou()), nz(o.getOrderHuanggang()), nz(o.getOrderXianning()),
                nz(o.getOrderSuizhou()), nz(o.getOrderEnshi()), nz(o.getOrderXiantao()), nz(o.getOrderQianjiang()),
                nz(o.getOrderTianmen()), nz(o.getOrderShennong()) };
        Map<String, Object> province = new LinkedHashMap<>();
        province.put("reportPeriod", o.getReportPeriod());
        province.put("period", o.getReportPeriod());
        province.put("region", "全省");
        province.put("orderCount", orderSum);
        province.put("orderShare", orderSum > 0 ? 1.0 : null);
        province.put("pax", o.getPinK());
        province.put("turnover", o.getPinZ());
        province.put("cityPax", o.getPinCK());
        province.put("cityTurnover", o.getPinCZ());
        province.put("note", "全省口径直接取台账 pin 值");
        rows.add(province);
        Map<String, WycSplitCalc.WycMetrics> byCity = null;
        String calcError = null;
        try {
            byCity = wycSplitCalc.calc(o.getReportPeriod()).getByCity();
        } catch (Exception e) {
            calcError = e.getMessage() == null ? "拆分计算失败" : e.getMessage();
        }
        for (int i = 0; i < cities.size() && i < orders.length; i++) {
            String city = cities.get(i);
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("reportPeriod", o.getReportPeriod());
            row.put("period", o.getReportPeriod());
            row.put("region", city);
            row.put("orderCount", orders[i]);
            row.put("orderShare", orderSum > 0 ? orders[i] / orderSum : null);
            WycSplitCalc.WycMetrics m = byCity == null ? null : byCity.get(city);
            row.put("pax", m == null ? null : m.getTotalPax());
            row.put("turnover", m == null ? null : m.getTotalTurnover());
            row.put("cityPax", m == null ? null : m.getCityPax());
            row.put("cityTurnover", m == null ? null : m.getCityTurnover());
            row.put("note", m == null ? (calcError == null ? "拆分未计算" : calcError) : "");
            rows.add(row);
        }
        return rows;
    }
    private boolean notEmpty(String s) {
@@ -434,12 +535,13 @@
    static {
        H2032_COLUMNS.add(col("reportPeriod", "报表期", 100));
        H2032_COLUMNS.add(col("regionCode", "所属地区", 100));
        H2032_COLUMNS.add(col("city", "市州", 110));
        H2032_COLUMNS.add(col("enterpriseCode", "企业代码", 130));
        H2032_COLUMNS.add(col("enterpriseName", "企业名称", 220));
        H2032_COLUMNS.add(col("unifiedCreditCode", "统一社会信用代码", 180));
        H2032_COLUMNS.add(col("reportUnit", "填报单位", 160));
        H2032_COLUMNS.add(col("vehicleTotal", "车辆数合计", 110));
        H2032_COLUMNS.add(col("tonsTotal", "标记吨位数合计", 130));
        H2032_COLUMNS.add(col("tonsTotal", "标记吨位数合计(吨)", 150));
        H2032_COLUMNS.add(col("vehicleTractor", "牵引车", 90));
        H2032_COLUMNS.add(col("vehicleTrailer", "挂车", 90));
        H2032_COLUMNS.add(col("tonsTrailer", "吨位-挂车", 110));
@@ -447,8 +549,8 @@
        H2032_COLUMNS.add(col("tonsContainer", "吨位-集装箱", 120));
        H2032_COLUMNS.add(col("vehicleWhole", "整车", 90));
        H2032_COLUMNS.add(col("tonsWhole", "吨位-整车", 110));
        H2032_COLUMNS.add(col("freightTotal", "货运量合计", 110));
        H2032_COLUMNS.add(col("turnoverTotal", "周转量合计", 110));
        H2032_COLUMNS.add(col("freightTotal", "货运量合计(吨)", 130));
        H2032_COLUMNS.add(col("turnoverTotal", "周转量合计(吨公里)", 150));
        H2032_COLUMNS.add(col("freightContainer", "货运量-集装箱", 130));
        H2032_COLUMNS.add(col("freightCoal", "货运量-煤炭", 120));
        H2032_COLUMNS.add(col("freightOilGas", "货运量-油气", 120));
@@ -458,7 +560,7 @@
        H2032_COLUMNS.add(col("freightBuilding", "货运量-建材", 120));
        H2032_COLUMNS.add(col("freightGrain", "货运量-粮食", 120));
        H2032_COLUMNS.add(col("avgTonnage", "平均吨位", 100));
        H2032_COLUMNS.add(col("avgDistance", "平均运距", 100));
        H2032_COLUMNS.add(col("avgDistance", "平均运距(公里)", 120));
        H2032_COLUMNS.add(col("unitLeader", "单位负责人", 110));
        H2032_COLUMNS.add(col("statsLeader", "统计负责人", 110));
        H2032_COLUMNS.add(col("contactPerson", "填表人", 100));
@@ -483,16 +585,16 @@
        SPLIT_COLUMNS.add(col("reportPeriod", "报表期", 100));
        SPLIT_COLUMNS.add(col("periodType", "口径", 90));
        SPLIT_COLUMNS.add(col("regionName", "市州", 130));
        SPLIT_COLUMNS.add(col("aboveScaleFreight", "规上货运量", 120));
        SPLIT_COLUMNS.add(col("aboveScaleTurnover", "规上周转量", 120));
        SPLIT_COLUMNS.add(col("aboveScaleFreight", "规上货运量(来源文件未提供该列)", 220));
        SPLIT_COLUMNS.add(col("aboveScaleTurnover", "规上周转量(万吨公里)", 170));
        SPLIT_COLUMNS.add(col("aboveScaleRank", "规上排名", 100));
        SPLIT_COLUMNS.add(col("aboveScaleYoy", "规上同比", 100));
        SPLIT_COLUMNS.add(col("belowScaleFreight", "规下货运量", 120));
        SPLIT_COLUMNS.add(col("belowScaleTurnover", "规下周转量", 120));
        SPLIT_COLUMNS.add(col("belowScaleFreight", "规下货运量(来源文件未提供该列)", 220));
        SPLIT_COLUMNS.add(col("belowScaleTurnover", "规下周转量(万吨公里)", 170));
        SPLIT_COLUMNS.add(col("belowScaleRank", "规下排名", 100));
        SPLIT_COLUMNS.add(col("belowScaleYoy", "规下同比", 100));
        SPLIT_COLUMNS.add(col("totalFreight", "合计货运量", 120));
        SPLIT_COLUMNS.add(col("totalTurnover", "合计周转量", 120));
        SPLIT_COLUMNS.add(col("totalFreight", "合计货运量(来源文件未提供该列)", 220));
        SPLIT_COLUMNS.add(col("totalTurnover", "合计周转量(万吨公里)", 170));
        SPLIT_COLUMNS.add(col("totalRank", "合计排名", 100));
        SPLIT_COLUMNS.add(col("totalYoy", "合计同比", 100));
@@ -513,6 +615,7 @@
        H2031_COLUMNS.add(col("reportPeriod", "报表期", 100));
        H2031_COLUMNS.add(col("regionCode", "所属地区", 100));
        H2031_COLUMNS.add(col("city", "市州", 110));
        H2031_COLUMNS.add(col("enterpriseCode", "企业代码", 130));
        H2031_COLUMNS.add(col("enterpriseName", "企业名称", 220));
        H2031_COLUMNS.add(col("unifiedCreditCode", "统一社会信用代码", 180));
@@ -523,7 +626,7 @@
        H2031_COLUMNS.add(col("seatSchedule", "班线载客位", 110));
        H2031_COLUMNS.add(col("vehicleCharter", "包车车辆数", 110));
        H2031_COLUMNS.add(col("seatCharter", "包车载客位", 110));
        H2031_COLUMNS.add(col("passengerTotal", "客运量合计", 110));
        H2031_COLUMNS.add(col("passengerTotal", "客运量合计(人)", 130));
        H2031_COLUMNS.add(col("turnoverTotal", "周转量合计", 110));
        H2031_COLUMNS.add(col("passengerClass1", "客运量一类", 110));
        H2031_COLUMNS.add(col("passengerClass2", "客运量二类", 110));
@@ -548,15 +651,15 @@
        BUS_COLUMNS.add(col("opVehicles", "运营车数", 100));
        BUS_COLUMNS.add(col("stopTotal", "站点总个数", 110));
        BUS_COLUMNS.add(col("stopCity", "城市内站点", 110));
        BUS_COLUMNS.add(col("passengerVolume", "客运量", 100));
        BUS_COLUMNS.add(col("passengerCity", "城市内客运量", 120));
        BUS_COLUMNS.add(col("turnover", "旅客周转量", 110));
        BUS_COLUMNS.add(col("turnoverCity", "城市内周转量", 120));
        BUS_COLUMNS.add(col("avgDistance", "平均运距", 100));
        BUS_COLUMNS.add(col("avgDistanceCity", "城市内平均运距", 130));
        BUS_COLUMNS.add(col("passengerChengxiang", "城际城乡客运量", 130));
        BUS_COLUMNS.add(col("turnoverChengxiang", "城际城乡周转量", 130));
        BUS_COLUMNS.add(col("avgDistanceChengxiang", "城际城乡平均运距", 140));
        BUS_COLUMNS.add(col("passengerVolume", "客运量(万人次)", 120));
        BUS_COLUMNS.add(col("passengerCity", "城市内客运量(万人次)", 150));
        BUS_COLUMNS.add(col("turnover", "旅客周转量(万人公里)", 170));
        BUS_COLUMNS.add(col("turnoverCity", "城市内周转量(万人公里)", 180));
        BUS_COLUMNS.add(col("avgDistance", "平均运距(公里)", 120));
        BUS_COLUMNS.add(col("avgDistanceCity", "城市内平均运距(公里)", 150));
        BUS_COLUMNS.add(col("passengerChengxiang", "城际城乡客运量(万人次)", 160));
        BUS_COLUMNS.add(col("turnoverChengxiang", "城际城乡周转量(万人公里)", 190));
        BUS_COLUMNS.add(col("avgDistanceChengxiang", "城际城乡平均运距(公里)", 160));
        BUS_COLUMNS.add(col("ratePassengerMom", "客运量环比", 100));
        BUS_COLUMNS.add(col("ratePassengerCityMom", "城市内环比", 100));
        BUS_COLUMNS.add(col("rateChengxiangMom", "城际城乡环比", 110));
@@ -571,20 +674,20 @@
        TAXI_COLUMNS.add(col("city", "市州", 110));
        TAXI_COLUMNS.add(col("tripTotal", "载客车次总数", 120));
        TAXI_COLUMNS.add(col("tripCity", "载客车次_城市内", 130));
        TAXI_COLUMNS.add(col("passengerVolume", "客运量", 100));
        TAXI_COLUMNS.add(col("passengerCity", "城市内客运量", 120));
        TAXI_COLUMNS.add(col("turnover", "旅客周转量", 110));
        TAXI_COLUMNS.add(col("turnoverCity", "城市内周转量", 120));
        TAXI_COLUMNS.add(col("passengerVolume", "客运量(万人次)", 120));
        TAXI_COLUMNS.add(col("passengerCity", "城市内客运量(万人次)", 150));
        TAXI_COLUMNS.add(col("turnover", "旅客周转量(万人公里)", 170));
        TAXI_COLUMNS.add(col("turnoverCity", "城市内周转量(万人公里)", 180));
        TAXI_COLUMNS.add(col("opVehicles", "运营车辆数", 110));
        TAXI_COLUMNS.add(col("avgDistance", "平均运距", 100));
        TAXI_COLUMNS.add(col("avgDistanceCity", "城市内平均运距", 130));
        TAXI_COLUMNS.add(col("avgDistance", "平均运距(公里)", 120));
        TAXI_COLUMNS.add(col("avgDistanceCity", "城市内平均运距(公里)", 150));
        TAXI_COLUMNS.add(col("passengersPerTrip", "单车次载客人数", 130));
        TAXI_COLUMNS.add(col("rateTripMom", "载客车次环比", 110));
        TAXI_COLUMNS.add(col("rateTripYoy", "载客车次同比", 110));
        TAXI_COLUMNS.add(col("ratePassengerMom", "客运量环比", 100));
        TAXI_COLUMNS.add(col("ratePassengerYoy", "客运量同比", 100));
        TAXI_COLUMNS.add(col("passengerCityRatio", "城市内客运量占比", 140));
        TAXI_COLUMNS.add(col("avgDistanceChengxiang", "城际城乡平均运距", 140));
        TAXI_COLUMNS.add(col("avgDistanceChengxiang", "城际城乡平均运距(公里)", 160));
        TAXI_COLUMNS.add(col("verifyExplanation", "核实解释", 220));
        TAXI_AUTH_COLUMNS.add(col("reportPeriod", "报表期", 100));
        TAXI_AUTH_COLUMNS.add(col("city", "市州", 110));
@@ -605,6 +708,8 @@
        PAUTH_COLUMNS.add(col("seatCount", "载客位数", 100));
        INVEST_MONTHLY_COLUMNS.add(col("reportPeriod", "报表期", 100));
        INVEST_MONTHLY_COLUMNS.add(col("projectId", "项目ID", 80));
        INVEST_MONTHLY_COLUMNS.add(col("projectName", "项目名称", 280));
        INVEST_MONTHLY_COLUMNS.add(col("city", "市州", 90));
        INVEST_MONTHLY_COLUMNS.add(col("yearPlan", "本年计划投资(万元)", 140));
        INVEST_MONTHLY_COLUMNS.add(col("yearCum", "自年初累计完成(万元)", 160));
        INVEST_MONTHLY_COLUMNS.add(col("monthDone", "本月完成投资(万元)", 150));
@@ -634,6 +739,7 @@
        INVEST_SYSTEM_COLUMNS.add(col("monthDone", "本月完成投资(万元)", 150));
        ENERGY_VEHICLE_COLUMNS.add(col("reportPeriod", "报表期", 100));
        ENERGY_VEHICLE_COLUMNS.add(col("regionCode", "所属地区", 100));
        ENERGY_VEHICLE_COLUMNS.add(col("city", "市州", 110));
        ENERGY_VEHICLE_COLUMNS.add(col("enterpriseName", "企业名称", 220));
        ENERGY_VEHICLE_COLUMNS.add(col("plateNo", "车牌号", 110));
        ENERGY_VEHICLE_COLUMNS.add(col("vehicleType", "车辆类型", 130));
@@ -674,20 +780,15 @@
        WYC_TOTAL_COLUMNS.add(col("pinZ", "全省网约车周转量(万人公里)", 220));
        WYC_TOTAL_COLUMNS.add(col("pinCZ", "其中城市内周转量(万人公里)", 220));
        WYC_ORDER_COLUMNS.add(col("reportPeriod", "报表期", 100));
        WYC_ORDER_COLUMNS.add(col("pinK", "全省客运量(万人次)", 170));
        WYC_ORDER_COLUMNS.add(col("pinCK", "其中城市内客运量", 170));
        WYC_ORDER_COLUMNS.add(col("pinZ", "全省周转量(万人公里)", 180));
        WYC_ORDER_COLUMNS.add(col("pinCZ", "其中城市内周转量", 170));
        WYC_ORDER_COLUMNS.add(col("orderSum", "订单合计(单)", 130));
        String[] wycOrderCityProps = { "orderWuhan", "orderHuangshi", "orderShiyan", "orderYichang", "orderXiangyang",
                "orderEzhou", "orderJingmen", "orderXiaogan", "orderJingzhou", "orderHuanggang", "orderXianning",
                "orderSuizhou", "orderEnshi", "orderXiantao", "orderQianjiang", "orderTianmen", "orderShennong" };
        String[] wycOrderCityNames = { "武汉", "黄石", "十堰", "宜昌", "襄阳", "鄂州", "荆门", "孝感", "荆州", "黄冈",
                "咸宁", "随州", "恩施", "仙桃", "潜江", "天门", "神农架" };
        for (int i = 0; i < wycOrderCityProps.length; i++) {
            WYC_ORDER_COLUMNS.add(col(wycOrderCityProps[i], wycOrderCityNames[i] + "订单(单)", 120));
        }
        WYC_ORDER_COLUMNS.add(col("region", "地区", 110));
        WYC_ORDER_COLUMNS.add(col("period", "期间", 100));
        WYC_ORDER_COLUMNS.add(col("orderCount", "订单数(单)", 130));
        WYC_ORDER_COLUMNS.add(col("orderShare", "订单占比", 100));
        WYC_ORDER_COLUMNS.add(col("pax", "客运量(万人次)", 140));
        WYC_ORDER_COLUMNS.add(col("turnover", "旅客周转量(万人公里)", 180));
        WYC_ORDER_COLUMNS.add(col("cityPax", "其中城市内客运量(万人次)", 190));
        WYC_ORDER_COLUMNS.add(col("cityTurnover", "其中城市内旅客周转量(万人公里)", 230));
        WYC_ORDER_COLUMNS.add(col("note", "说明", 200));
    }
}
traffic-audit-web/src/views/DataView.vue
@@ -22,7 +22,7 @@
          <span class="toolbar-sep"></span>
          <span class="toolbar-label">数据类型</span>
          <el-select v-model="type" size="small" style="width:300px" placeholder="选择数据类型" filterable
                     :title="currentTypeLabel" @change="onTypeSelect">
                     popper-class="data-type-select" :title="currentTypeLabel" @change="onTypeSelect">
            <el-option-group v-for="g in modules" :key="g.key" :label="g.name">
              <el-option v-for="t in g.types" :key="t.value" :label="t.label" :value="t.value"></el-option>
            </el-option-group>
@@ -37,7 +37,7 @@
            <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%">
              <el-table-column v-for="c in renderColumns" :key="c.prop" :prop="c.prop" :label="c.label" :min-width="c.width" :fixed="c.fixed ? 'left' : false" show-overflow-tooltip>
                <template slot-scope="scope">
                  <span>{{ formatCell(scope.row[c.prop]) }}</span>
                  <span>{{ formatCell(scope.row[c.prop], c.prop) }}</span>
                </template>
              </el-table-column>
            </el-table>
@@ -122,33 +122,29 @@
      return g
    },
    visibleColumns() {
      const hiddenProps = ['enterpriseCode', 'unifiedCreditCode', 'reportUnit']
      const hiddenLabels = ['企业代码', '统一社会信用代码', '填报单位']
      return (this.columns || []).filter(c =>
        hiddenProps.indexOf(c.prop) < 0 && hiddenLabels.indexOf(c.label) < 0)
      return (this.columns || []).filter(c => this.keepColumn(c))
    },
    // 左侧冻结"这是谁的数据"所需列:横向滚动时企业名称/市州不会跑出视野。
    // 只冻结从第 1 列开始的一段连续列,避免冻结列之间被普通列打断。
    // 左侧冻结"这是谁的数据"的列:市州/地区 + 企业(项目)名称/车牌号,最多 3 列。
    // 报表期与所属地区不参与冻结(报表期是筛选条件,所属地区是区划代码,已由市州列替代)。
    renderColumns() {
      const stickyProps = ['reportPeriod', 'regionCode', 'city', 'regionName', 'periodType',
        'enterpriseName', 'projectName', 'plateNo']
      const nameProps = ['enterpriseName', 'projectName', 'plateNo']
      const stickyProps = ['city', 'regionName', 'region', 'periodType', 'enterpriseName', 'projectName', 'plateNo']
      const cols = this.visibleColumns
      let limit = -1
      for (let i = 0; i < cols.length && i < 6; i++) {
        if (nameProps.indexOf(cols[i].prop) >= 0) { limit = Math.min(i, 3); break }
      let count = 0
      while (count < cols.length && count < 3 && stickyProps.indexOf(cols[count].prop) >= 0) {
        count++
      }
      if (limit < 0) {
        // 表里没有名称列(如货运量周转量)时,冻结开头的连续标识列
        limit = 0
        while (limit + 1 < cols.length && limit < 2 && stickyProps.indexOf(cols[limit + 1].prop) >= 0) {
          limit++
        }
      }
      return cols.map((c, i) => Object.assign({}, c, { fixed: i <= limit && stickyProps.indexOf(c.prop) >= 0 }))
      return cols.map((c, i) => Object.assign({}, c, { fixed: i < count }))
    }
  },
  methods: {
    // 数据查看统一列过滤:内部标识列不展示;报表期(由筛选条件体现)与所属地区(区划代码,已被市州列替代)不展示
    keepColumn(c) {
      if (!c) return false
      const hiddenProps = ['enterpriseCode', 'unifiedCreditCode', 'reportUnit', 'projectId', 'regionCode', 'reportPeriod']
      const hiddenLabels = ['企业代码', '统一社会信用代码', '填报单位', '项目ID', '所属地区', '报表期']
      if (hiddenProps.indexOf(c.prop) >= 0 || hiddenLabels.indexOf(c.label) >= 0) return false
      return true
    },
    onModuleChange(key) {
      const g = this.modules.find(x => x.key === key)
      if (!g || !g.types.length) return
@@ -267,9 +263,21 @@
        document.exitFullscreen()
      }
    },
    formatCell(v) {
    // 显示格式化(不改动底层数据,导出仍为全精度):
    //  - 比率类列(环比/同比/增速/占比)库内是小数(0.045 = 4.5%),显示为百分比;
    //  - 排名类列显示整数;
    //  - 其余数值千分位、保留两位小数;整数原样。
    formatCell(v, prop) {
      if (v === null || v === undefined || v === '') return '-'
      return v
      const s = typeof v === 'string' ? v.trim() : v
      if (typeof v !== 'number' && !/^-?\d+(\.\d+)?$/.test(String(s))) return v
      const n = Number(s)
      if (!isFinite(n)) return v
      const key = String(prop || '').toLowerCase()
      if (/rank/.test(key)) return Math.round(n).toLocaleString('zh-CN')
      if (/(rate|yoy|mom|growth|ratio|percent|share)/.test(key)) return (n * 100).toFixed(2) + '%'
      if (Number.isInteger(n)) return n.toLocaleString('zh-CN')
      return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
    }
  },
  mounted() {
@@ -310,4 +318,20 @@
#app .table-area:fullscreen { padding:20px; background:#fff; overflow:auto; }
#app .table-wrap:fullscreen { padding:20px; background:#fff; overflow:auto; display:flex; flex-direction:column; height:100vh; }
#app .table-wrap:fullscreen .table-area { flex:1 1 auto; min-height:0; }
</style>
</style>
<style>
/* 数据类型下拉:Element UI 默认把分组标题做成 12px 灰色小字,看起来"比选项更小更淡"。
   这里把它改成明确的组标题样式(加深、加粗、略大、带底色),与选项形成有意的层级。 */
.data-type-select .el-select-group__title {
  font-size: 13px;
  font-weight: 700;
  color: #303133;
  line-height: 28px;
  background: #F5F7FA;
  padding-left: 12px;
  letter-spacing: .5px;
}
.data-type-select .el-select-group__title::after { display: none; }
.data-type-select .el-select-group { padding-bottom: 6px; }
.data-type-select .el-select-group .el-select-dropdown__item { padding-left: 20px; }
</style>