feat: 数据导入页新增清空按钮——按报表期清空数据库业务数据/审核结果/已审核标记/导入批次,支持整月重导(含功能测试报告与工作日结)
3个文件已修改
2个文件已添加
170 ■■■■■ 已修改文件
docs/功能测试报告/功能测试报告_数据导入清空按钮按报表期清库_2026-09-10.md 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-10.md 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-web/src/views/DataImport.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_Êý¾Ýµ¼ÈëÇå¿Õ°´Å¥°´±¨±íÆÚÇå¿â_2026-09-10.md
New file
@@ -0,0 +1,41 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Šï¼šæ•°æ®å¯¼å…¥é¡µâ€œå¯¼å…¥è¿›åº¦Â·æ¸…ç©ºâ€æŒ‰é’®ï¼ˆæŒ‰æŠ¥è¡¨æœŸæ¸…ç©ºæ•°æ®åº“ï¼‰ 2026-09-10
## åŠŸèƒ½è¯´æ˜Ž
- æ•°æ®å¯¼å…¥é¡µå³ä¾§â€œå¯¼å…¥è¿›åº¦â€ä¾§æ æ ‡é¢˜æ—æ–°å¢žçº¢è‰²â€œæ¸…空”按钮(与“刷新”并列),随侧栏展开可见。
- ä½œç”¨ï¼šä¸€é”®æ¸…空当前所选报表期在数据库中的全部已导入业务数据,并同步删除该报表期的审核结果、审核通过标记(audit_run)与导入批次记录(import_batch),用于整月重新导入;投资项目主档(investment_project)保留不动。
- äº¤äº’:点击 â†’ äºŒæ¬¡ç¡®è®¤å¼¹çª—(标题含报表期,提示不可撤销)→ ç¡®è®¤åŽè°ƒç”¨ `POST /api/import/clear?period=yyyy-MM`;清空过程中按钮进入 loading å¹¶ç¦ç”¨ï¼›æœªé€‰æŠ¥è¡¨æœŸæˆ–正在导入时按钮禁用;成功后提示“已清空 xxx æŠ¥è¡¨æœŸçš„æ•°æ®ï¼Œå¯ä»¥é‡æ–°å¯¼å…¥â€å¹¶è‡ªåŠ¨åˆ·æ–°å¯¼å…¥è¿›åº¦ã€‚
## æ¶‰åŠæ”¹åЍ
- åŽç«¯ `DataImportService.clearPeriod(period)`:`@Transactional`,先校验报表期格式,再按报表期删除 h2032/运政车辆/轨迹里程/规上规下/货运量周转量/旅客月报/个体/旅客运政/能耗/能耗运政/投资月度/投资系统/城市公交/巡游出租/出租车运政/网约车总数/网约车订单各表数据,并删除该期 audit_result、audit_run、import_batch;返回各表删除行数统计。
- åŽç«¯ `DataImportController` æ–°å¢ž `POST /api/import/clear`(参数 period)。
- å‰ç«¯ `DataImport.vue`:新增 `clearing` çŠ¶æ€ã€`clearPeriodData()` æ–¹æ³•与侧栏按钮/样式。
## æµ‹è¯•环境
- åŽç«¯ï¼štraffic-audit-server 8090(含本次改动,`start-dev.ps1` å¯åŠ¨æˆåŠŸï¼Œbackend.log å‡ºçް `Started TrafficAuditApplication`)。
- å‰ç«¯ï¼štraffic-audit-web 8080 dev(Compiled successfully),浏览器为 Codex åº”用内浏览器,`admin` ç™»å½•。
- æ•°æ®åº“:本机 MySQL `localhost:3308/traffic_audit`,用 mysql CLI ç›´æŸ¥æ ¸å¯¹ã€‚
- æµ‹è¯•数据:`docs/货运需导入的数据/货运/2026å¹´1-7月报/道路货物运输月度生产情况(2026å¹´6月).xls`(636 è¡Œï¼‰ï¼Œå¯¼å…¥åˆ°ä¸€æ¬¡æ€§æµ‹è¯•期 `2099-01`,全程不触碰任何真实月份。
## ç”¨ä¾‹è¡¨
| # | ç”¨ä¾‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
|---|---|---|---|---|---|
| 1 | åŸºçº¿æ£€æŸ¥ | æŸ¥è¯¢ 2099-01 çš„æ‰¹æ¬¡ä¸Žå®¡æ ¸ç»“æžœ | å‡ä¸ºç©º | æ‰¹æ¬¡ 0 æ¡ã€å®¡æ ¸ç»“æžœ 0 æ¡ | é€šè¿‡ |
| 2 | æ­£å‘-导入 | ä¸Šä¼  6 æœˆè´§è¿ xls,period=2099-01 | æˆåŠŸä¸”è¡Œæ•° 636 | è¿”回“H2032月报导入成功,共 636 æ¡è®°å½•”,批次 1 æ¡ | é€šè¿‡ |
| 3 | æ­£å‘-审核 | `POST /audit/execute?period=2099-01&reportType=H2032` | ç”Ÿæˆå®¡æ ¸ç»“果与已审核标记 | ç”Ÿæˆ audit_result 31 è¡Œã€audit_run 1 è¡Œ | é€šè¿‡ |
| 4 | æ­£å‘-清空(接口) | `POST /import/clear?period=2099-01` | è¿”回删除统计,库内该期清空 | è¿”回 dataRows=636、auditRows=31、auditRunRows=1、batchRows=1;DB ç›´æŸ¥ h2032/audit_result/audit_run/import_batch å‡ä¸º 0 | é€šè¿‡ |
| 5 | æ­£å‘-清空后重导 | å†æ¬¡ä¸Šä¼ åŒä¸€æ–‡ä»¶ period=2099-01 | å¯æ­£å¸¸é‡æ–°å¯¼å…¥ | æˆåŠŸ 636 æ¡ï¼›DB h2032=636、import_batch=1 | é€šè¿‡ |
| 6 | è´Ÿå‘-非法报表期 | `period=abc` | æ‹’绝并提示格式 | code=500,“清空失败: æŠ¥è¡¨æœŸæ ¼å¼ä¸æ­£ç¡®ï¼Œåº”为 yyyy-MM” | é€šè¿‡ |
| 7 | è¾¹ç•Œ-空报表期 | `period=2098-12`(无数据) | æˆåŠŸä¸”å„é¡¹ä¸º 0 | dataRows/auditRows/auditRunRows/batchRows å‡ä¸º 0 | é€šè¿‡ |
| 8 | UI-按钮存在 | æ‰“开数据导入页,选 2099-01,展开“导入进度” | æ ‡é¢˜æ—å‡ºçŽ°â€œåˆ·æ–°/清空”,进度显示 H203-2 å·²å¯¼å…¥ 636 è¡Œ | ä¸Žé¢„期一致(可访问性树读到“清空”按钮与“已导入 636 è¡Œâ€ï¼‰ | é€šè¿‡ |
| 9 | UI-二次确认 | ç‚¹â€œæ¸…空” | å¼¹å‡ºç¡®è®¤æ¡†ï¼Œæ ‡é¢˜å«æŠ¥è¡¨æœŸ | å¼¹çª—标题“清空 2099-01 æ•°æ®â€ï¼Œæ­£æ–‡æç¤ºä¸å¯æ’¤é”€ï¼ŒæŒ‰é’®â€œå–消/确认清空” | é€šè¿‡ |
| 10 | UI-确认清空 | ç‚¹â€œç¡®è®¤æ¸…空” | æç¤ºæˆåŠŸã€è¿›åº¦å…¨éƒ¨å›žâ€œæœªå¯¼å…¥â€ã€åº“å†…æ¸…ç©º | æç¤ºâ€œå·²æ¸…空 2099-01 æŠ¥è¡¨æœŸçš„æ•°æ®ï¼Œå¯ä»¥é‡æ–°å¯¼å…¥â€ï¼›è¿›åº¦å…¨éƒ¨æœªå¯¼å…¥ï¼›DB ç›´æŸ¥å››è¡¨å‡ä¸º 0 | é€šè¿‡ |
| 11 | å›žå½’-真实月份不受影响 | æ¸…空前后对比批次分布与库内行数 | çœŸå®žæœˆä»½æ— å˜åŒ– | æ‰¹æ¬¡åˆ†å¸ƒä¸Žæ”¹åŠ¨å‰å®Œå…¨ä¸€è‡´ï¼ˆ2025 å„月 1 æ¡ã€2026-07 45 æ¡ã€2026-08 48 æ¡ã€2026-09 7 æ¡ï¼‰ï¼›h2032:2025-07=664、2026-07=636、2026-09=636 | é€šè¿‡ |
## å‘现的问题与处置
- æœªå‘现功能缺陷。
- è¯´æ˜Žï¼šä¸€æ¬¡æ¸…空会删除该报表期“全部类型”的业务数据及审核结果/已审核标记,弹窗已明确提示不可撤销;若只想清单一类型,目前需整月清空后重导,可作为后续增强(按类型清空)。
- çŽ¯å¢ƒå¤‡æ³¨ï¼šæœ¬æœºåº“ `admin` å¯†ç ä¸º `123456`(见 docs/交接文档-20260831.md,8/31 å®žæµ‹ï¼‰ï¼Œ`admin123` åœ¨æœ¬æœºä¸åŒ¹é…ã€‚
## ç»“论
- é€šè¿‡ã€‚清空按钮在界面可用、确认弹窗语义清晰,接口按报表期完整清除业务数据、审核结果、审核标记与导入批次,清空后可立即整月重导;对真实月份数据零影响。
- å¾…用户实际浏览器验收:在自己工作机上点一次“清空”(建议先确认所选报表期与备份)确认弹窗文案与预期一致。
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-10.md
New file
@@ -0,0 +1,23 @@
# å·¥ä½œæ—¥ç»“ 2026-09-10
## ä¸Šåˆï¼ˆ2026-09-10)数据导入页新增“清空(按报表期清库)”并完成端到端验证
### å®Œæˆå†…容
- æ•°æ®å¯¼å…¥é¡µå³ä¾§â€œå¯¼å…¥è¿›åº¦â€ä¾§æ æ ‡é¢˜æ—æ–°å¢žçº¢è‰²â€œæ¸…空”按钮:一键清空所选报表期在数据库中的全部已导入数据,并同步删除该期审核结果、审核通过标记(audit_run)与导入批次(import_batch),用于整月重导;投资项目主档保留。
- åŽç«¯æ–°å¢ž `DataImportService.clearPeriod(period)`(`@Transactional` + æŠ¥è¡¨æœŸæ ¼å¼æ ¡éªŒ + åˆ é™¤è¡Œæ•°ç»Ÿè®¡ï¼‰ä¸Ž `POST /api/import/clear`;覆盖 h2032/运政车辆/轨迹里程/规上规下/货运量周转量/旅客月报/个体/旅客运政/能耗/能耗运政/投资月度/投资系统/城市公交/巡游出租/出租车运政/网约车总数/网约车订单 ç­‰è¡¨ã€‚
- å‰ç«¯äº¤äº’:二次确认弹窗(标题含报表期、提示不可撤销)、清空中 loading/禁用、未选报表期或导入中禁用、成功后自动刷新导入进度。
### æµ‹è¯•结果摘要
- ä¸€æ¬¡æ€§æµ‹è¯•期 2099-01 å…¨æµç¨‹ï¼šå¯¼å…¥ 636 è¡Œ â†’ å®¡æ ¸ç”Ÿæˆ audit_result 31 è¡Œ/audit_run 1 è¡Œ â†’ ç•Œé¢ç‚¹â€œæ¸…空”确认 â†’ è¿”回 dataRows=636/auditRows=31/auditRunRows=1/batchRows=1,DB ç›´æŸ¥å››è¡¨å…¨ 0 â†’ å†å¯¼å…¥ä» 636 è¡ŒæˆåŠŸã€‚
- UI å®žæµ‹ï¼ˆCodex åº”用内浏览器,admin ç™»å½•):按钮显示正常,弹窗标题“清空 2099-01 æ•°æ®â€ï¼Œç¡®è®¤åŽæç¤ºâ€œå·²æ¸…空 2099-01 æŠ¥è¡¨æœŸçš„æ•°æ®â€ï¼Œè¿›åº¦å…¨éƒ¨å›žâ€œæœªå¯¼å…¥â€ã€‚
- åå‘用例:非法报表期 `abc` è¢«æ‹’并提示格式;空报表期 `2098-12` è¿”回全 0 æˆåŠŸã€‚
- å›žå½’:清空前后真实月份批次分布与库内行数完全一致(2025-07=664、2026-07=636、2026-09=636 ç­‰ï¼‰ï¼Œé›¶å½±å“ã€‚
- åŽç«¯ `start-dev.ps1` å¯åŠ¨å°±ç»ªï¼ˆbackend.log `Started TrafficAuditApplication`),前端 dev Compiled successfully。
- æµ‹è¯•报告:docs/功能测试报告/功能测试报告_数据导入清空按钮按报表期清库_2026-09-10.md。
### é—留问题与待办
- å¾…用户实际浏览器验收“清空”按钮(建议先确认所选报表期与备份);如需只清某一数据类型,可后续做“按类型清空”。
- æ²¿ç”¨æ˜¨æ—¥æœªåŠ¨å¾…åŠžï¼šæ±‡æ€»å¤§è¡¨ M3(网约车台账取数、货运页 FreightCalc è§„上/规下、城市客运/中口径合成页)与“9 æœˆ+无母版自动扩列”;8 æœˆä»ç¼º 4 ç±»æºæ•°æ®è¡¨è¡Œï¼ˆfreight_turnover_import、scale_split_transport、passenger_individual_monthly、网约车拆分输入)。
## é™„:提交
- æœ¬æ¬¡æ”¹åЍ 3 ä¸ªä»£ç æ–‡ä»¶ + æœ¬æŠ¥å‘Šä¸Žæ—¥ç»“,按规范先 `git fetch gitea/origin` å¯¹æ¯”合并,再 commit å¹¶åŒç«¯æŽ¨é€ï¼ˆgitea main、个人 gitee master)。
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/controller/DataImportController.java
@@ -443,4 +443,16 @@
        return Result.ok(result);
    }
    /** æ¸…空指定报表期全部已导入数据与审核结果(数据导入页"清空"按钮调用),便于整月重新导入 */
    @PostMapping("/clear")
    public Result<Map<String, Object>> clearData(@RequestParam("period") String period) {
        try {
            Map<String, Object> stats = importService.clearPeriod(period);
            return Result.ok(stats);
        } catch (Exception e) {
            log.error("clear period {} error", period, e);
            return Result.error("清空失败: " + e.getMessage());
        }
    }
}
traffic-audit-server/src/main/java/com/trafficaudit/dataimport/service/DataImportService.java
@@ -2,7 +2,9 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trafficaudit.auditengine.entity.AuditResult;
import com.trafficaudit.auditengine.entity.AuditRun;
import com.trafficaudit.auditengine.mapper.AuditResultMapper;
import com.trafficaudit.auditengine.mapper.AuditRunMapper;
import com.trafficaudit.common.util.RegionUtil;
import com.trafficaudit.dataimport.entity.EnergyAuthVehicle;
import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
@@ -56,6 +58,7 @@
import org.apache.poi.ss.usermodel.WorkbookFactory;
import cn.hutool.core.io.IoUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
@@ -142,6 +145,8 @@
    private FreightTurnoverImportMapper freightTurnoverMapper;
    @Resource
    private AuditResultMapper auditResultMapper;
    @Resource
    private AuditRunMapper auditRunMapper;
    @Resource
    private PassengerEnterpriseMonthlyMapper passengerMapper;
    @Resource
@@ -1312,6 +1317,64 @@
        }
    }
    /** æ¸…空指定报表期全部已导入数据与审核结果(含导入批次、审核通过标记),用于整月重导;投资项目主档保留 */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> clearPeriod(String period) {
        if (period == null || !period.matches("\\d{4}-\\d{2}")) {
            throw new IllegalArgumentException("报表期格式不正确,应为 yyyy-MM");
        }
        int dataRows = 0;
        dataRows += h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
            .eq(H2032EnterpriseMonthly::getReportPeriod, period));
        dataRows += transportAuthMapper.delete(new LambdaQueryWrapper<TransportAuthVehicle>()
            .eq(TransportAuthVehicle::getReportPeriod, period));
        dataRows += trackMileageMapper.delete(new LambdaQueryWrapper<VehicleTrackMileage>()
            .eq(VehicleTrackMileage::getReportPeriod, period));
        dataRows += scaleSplitMapper.delete(new LambdaQueryWrapper<ScaleSplitTransport>()
            .eq(ScaleSplitTransport::getReportPeriod, period));
        dataRows += freightTurnoverMapper.delete(new LambdaQueryWrapper<FreightTurnoverImport>()
            .eq(FreightTurnoverImport::getReportPeriod, period));
        dataRows += passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
            .eq(PassengerEnterpriseMonthly::getReportPeriod, period));
        dataRows += passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>()
            .eq(PassengerIndividualMonthly::getReportPeriod, period));
        dataRows += passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>()
            .eq(PassengerAuthVehicle::getReportPeriod, period));
        dataRows += energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
            .eq(EnergyVehicleQuarterly::getReportPeriod, period));
        dataRows += energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>()
            .eq(EnergyAuthVehicle::getReportPeriod, period));
        dataRows += investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>()
            .eq(InvestmentMonthly::getReportPeriod, period));
        dataRows += investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>()
            .eq(InvestmentSystem::getReportPeriod, period));
        dataRows += cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>()
            .eq(CityBusMonthly::getReportPeriod, period));
        dataRows += cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>()
            .eq(CityTaxiMonthly::getReportPeriod, period));
        dataRows += cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>()
            .eq(CityTaxiAuth::getReportPeriod, period));
        dataRows += wycTotalMapper.delete(new LambdaQueryWrapper<WycTotalMonthly>()
            .eq(WycTotalMonthly::getReportPeriod, period));
        dataRows += wycOrderMapper.delete(new LambdaQueryWrapper<WycOrderMonthly>()
            .eq(WycOrderMonthly::getReportPeriod, period));
        int auditRows = auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>()
            .eq(AuditResult::getReportPeriod, period));
        int auditRunRows = auditRunMapper.delete(new LambdaQueryWrapper<AuditRun>()
            .eq(AuditRun::getReportPeriod, period));
        int batchRows = importBatchMapper.delete(new LambdaQueryWrapper<ImportBatch>()
            .eq(ImportBatch::getReportPeriod, period));
        Map<String, Object> stats = new LinkedHashMap<>();
        stats.put("period", period);
        stats.put("dataRows", dataRows);
        stats.put("auditRows", auditRows);
        stats.put("auditRunRows", auditRunRows);
        stats.put("batchRows", batchRows);
        log.info("清空报表期 {} å®Œæˆï¼šä¸šåŠ¡æ•°æ® {} è¡Œã€å®¡æ ¸ç»“æžœ {} è¡Œã€å®¡æ ¸æ ‡è®° {} è¡Œã€å¯¼å…¥æ‰¹æ¬¡ {} è¡Œ",
            period, dataRows, auditRows, auditRunRows, batchRows);
        return stats;
    }
    /** æ•°æ®å˜åŒ–后删除指定报表类型指定报表期的审核结果(需重新执行审核) */
    private void deleteRuleResults(String reportType, String period) {
        List<Long> ruleIds = new ArrayList<>();
traffic-audit-web/src/views/DataImport.vue
@@ -103,7 +103,11 @@
              <div v-show="sideOpen" class="side-content">
                <div class="side-head">
                  <span>本月导入进度({{ period || '未选择报表期' }})</span>
                  <el-button type="text" icon="el-icon-refresh" @click="loadData">刷新</el-button>
                  <span class="side-head-btns">
                    <el-button type="text" icon="el-icon-refresh" @click="loadData">刷新</el-button>
                    <el-button type="text" icon="el-icon-delete" style="color:#F56C6C" :loading="clearing"
                               :disabled="!period || clearing || importing" @click="clearPeriodData">清空</el-button>
                  </span>
                </div>
                <div v-if="!period" class="tip">请先在上方选择报表期</div>
                <div v-else class="overview-grid">
@@ -158,6 +162,7 @@
      period: (() => { const d = new Date(); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') })(),
      cityDirLoading: false,
      importing: false,
      clearing: false,
      sideOpen: false,
      result: '',
      error: '',
@@ -520,6 +525,28 @@
        .catch(e => { if (!e || !e._toast) this.$message.error('导入记录加载失败,请刷新重试') })
        .finally(() => { this.batchLoading = false })
    },
    clearPeriodData() {
      if (!this.period) { this.$message.warning('请先选择报表期'); return }
      if (this.importing) { this.$message.warning('正在导入数据,请等待导入完成后再清空'); return }
      if (this.clearing) return
      this.$confirm(
        '将清空 ' + this.period + ' æŠ¥è¡¨æœŸåœ¨æ•°æ®åº“中的全部已导入数据,同时删除该报表期的审核结果与导入批次记录(便于整月重新导入)。\n此操作不可撤销,是否继续?',
        '清空 ' + this.period + ' æ•°æ®',
        { type: 'warning', confirmButtonText: '确认清空', cancelButtonText: '取消' }
      ).then(() => {
        this.clearing = true
        api.post('/import/clear', null, { params: { period: this.period }, timeout: 600000 })
          .then(() => {
            this.$message.success('已清空 ' + this.period + ' æŠ¥è¡¨æœŸçš„æ•°æ®ï¼Œå¯ä»¥é‡æ–°å¯¼å…¥')
            this.loadData()
          })
          .catch(e => {
            if (e && e._toast) return
            this.$message.error((e.response && e.response.data && e.response.data.message) || '清空失败,请稍后重试')
          })
          .finally(() => { this.clearing = false })
      }).catch(() => {})
    },
    batchKeyToType(k) {
      if (BATCH_TYPE_MAP[k]) return BATCH_TYPE_MAP[k]
      if (k && k.indexOf('INVEST_') === 0) {
@@ -591,6 +618,8 @@
.import-side.side-open .side-toggle-text { writing-mode: horizontal-tb; letter-spacing: normal; margin-top: 0; }
.side-content { padding: 8px 12px 12px; max-height: calc(100vh - 220px); overflow-y: auto; overscroll-behavior: contain; }
.side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-weight: 600; margin-bottom: 6px; }
.side-head-btns { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.side-head-btns .el-button + .el-button { margin-left: 0; }
.import-side .overview-grid { display: block; }
.import-side .ov-row { display: block; margin-bottom: 6px; }
.import-side .ov-group { width: auto; }