fix: 8条整改落地——数据查看滚动加载/规则阈值联动通用化/旅客运政导入兼容.xls/审核月报弹窗删企业解释列/汇总大表导出无筛选自适应/报表Tab重组/筛选栏与月份年份顺序 + 测试报告与工作日结
| New file |
| | |
| | | # åè½æµè¯æ¥å_æ´æ¹8项åå½éªè¯_2026-09-08 |
| | | |
| | | ## åè½è¯´æ |
| | | éå¯¹ç¨æ· 2026-09-08 æåºç 8 æ¡çé¢ä¸åè½æ´æ¹ï¼â䏿¬¡è¯´æ¹ç没æ¹âï¼éæ¡è½å°å¹¶åå½ï¼ |
| | | 1. æ°æ®æ¥ç页æ»å¨å°åºèªå¨å è½½æ´å¤æ°æ®è¡ï¼ä¸ååªæ¾ç¤ºä¸é¡µï¼ï¼ |
| | | 2. å®¡æ ¸è§åé弿¹å¨åï¼è§ååç§°/è§å说æä¸çç¾åæ¯ééå¼èå¨ï¼å¦ ±10%â±20%â±30%âåéï¼ï¼å¹¶å¯¹å
¨é¨è§åçæãæä¹
åï¼ |
| | | 3. å
¬è·¯æ
客âè¿æ¿è½¦è¾æ°â导å
¥å
¼å®¹æ°çè¿æ¿åºå¯¼åºæä»¶ï¼.xlsï¼è¡¨å¤´ OWNERNAME/æ»è½¦è¾æ°/客使°ï¼æ ä¼ä¸ä»£ç åï¼ï¼åæ¶å
¼å®¹æ§ä¸æååè¡¨å¤´ï¼ |
| | | 4. å®¡æ ¸ç»æï¼å é¤âä¼ä¸è§£éâååè§£éè¾å
¥æ¡ï¼ç¹å»âæ°æ®âå¼¹åºè¯¥ä¼ä¸è¯¥æ¥è¡¨æçææ¥æ ·å¼æ¥çï¼ |
| | | 5. çææ±æ»å¤§è¡¨ï¼æ´æ¬ï¼ï¼å¯¼åºæ¶åæ¶å
¨é¨é¡µçéï¼ç线å
车ç页ä¸ååç¼ºæ°æ®ï¼ãè¿åéèè¡ãææ°æ®èªå¨å 宽åå®½ãæ°å¼ä¿ç两ä½å°æ°æ¾ç¤ºï¼ä¸ä¸¢å¤±å
¨ç²¾åº¦ï¼ãå
¬å¼ä¿ç䏿å¼èªå¨éç®ï¼ |
| | | 6. æ¥è¡¨çæé¡µï¼åå¸å®¢è¿æ¥è¡¨æ ç¾ä¸ç tag å é¤ãåç§°ä¸å
¶å®ä¸è´ï¼âéè·¯è¿è¾éæ±æ»è¡¨ï¼æ´æ¬ï¼âç§»å
¥é¦ä¸ªç¬ç« Tabâæ±æ»å¤§è¡¨âï¼ |
| | | 7. å®¡æ ¸ç»æé¡µçéæ é¡ºåºï¼æ¥è¡¨æå¨æ¥è¡¨ç±»åå·¦è¾¹ï¼ |
| | | 8. æ¥è¡¨çææä»½ä¸æï¼é»è®¤æå
å±ç¤ºå½å年份ï¼2026 å¹´ 1â12 æï¼ï¼åå±ç¤º 2025 å¹´ä¸ 2027 å¹´ã |
| | | |
| | | ## æµè¯ç¯å¢ |
| | | - æ¬æºï¼Windowsï¼å端 Spring Boot :8090ï¼å端 Vue2 :8080ï¼é¡µé¢ç»éæ°ç¼è¯å è½½ææ°ä»£ç ï¼å端已éå¯ï¼ã |
| | | - ç»å½ï¼adminï¼app.captcha.required=false æµè¯æ¨¡å¼ï¼ã |
| | | - æ°æ®åºï¼æ¬æº traffic-audit å¼ååºï¼å« 2026-08 è´§è¿ææ¥ 13056 æ¡çæ¢ææ°æ®ï¼ã |
| | | - æ¥æï¼2026-09-08ã |
| | | |
| | | ## ç¨ä¾è¡¨ï¼è¾å
¥ / 颿 / å®é
/ ç»è®ºï¼ |
| | | |
| | | | # | è¾å
¥ | 颿 | å®é
| ç»è®º | |
| | | |---|------|------|------|------| |
| | | | 1 | æ°æ®æ¥ç页ï¼H203-2ï¼å
± 13056 æ¡ï¼ï¼å°è¡¨æ ¼ç«åæ»å¨å°åº | èªå¨è¿½å ä¸ä¸é¡µï¼è¡æ° 20â40ï¼é¡µç¾ä»â第 1 页âåâ第 2 页â | åå§ 20 è¡ï¼å®¹å¨é« 299ãå
å®¹é« 796ï¼ï¼æ»å¨å 40 è¡ï¼å
å®¹é« 1592ï¼scrollTop 496.8ï¼ï¼âå
± 13056 æ¡è®°å½ï¼å½å第 2 页â | éè¿ | |
| | | | 2 | è§å管çï¼å° P005 éå¼ 20â30â20 åå¤ä¿å | åç§°/说æä¸ç ±N% éå¨ï¼Â±20%â±30%ï¼ï¼å
¶å®æ å
³æ°åä¸å¨ï¼éå¼é¡µé¢ä»ä¸è´ | P005 以å B001ï¼å¢éï¼ãE005ï¼Â±%ï¼ãRULE_003ï¼é«åº%ï¼ãT001ï¼å·®è·%ï¼å宿µéè¿ï¼æ¾å¼ï¼0.2â0.1ï¼ä¸æ¯å¼ï¼1.3â1.5ï¼å£å¾åæ£ç¡®å¹¶æä¹
å | éè¿ | |
| | | | 3 | 导å
¥ 2026-08 å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°ï¼æ° .xlsï¼OWNERNAME/æ»è½¦è¾æ°/客使°ï¼ | 750 å®¶ä¼ä¸å
¨é¨å
¥åºãæ½æ ·æ°å¼ä¸æºæä»¶ä¸è´ï¼æ§ä¸æè¡¨å¤´æä»¶ä»å¯å¯¼å
¥ | æ°æ¨¡æ¿æ¾å
¥ docs/å
¬è·¯æ
客/模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xls 导å
¥æåï¼æ½æ¥ç§å½å¿å¿å° 5/35ãå®é叿¶¢å® 26/668 䏿ºæä»¶ä¸è´ï¼ä¼ä¸ä»£ç åä¸ºç©ºå±æ£å¸¸ï¼æ°æ¨¡æ¿æ æ¤åï¼ | éè¿ | |
| | | | 4 | å®¡æ ¸ç»æé¡µï¼â æ£æ¥è¡å
æä½åï¼â¡ç¹å»âæ°æ®â | â æ âä¼ä¸è§£éâå/è¾å
¥æ¡ï¼åªæ æ°æ®/确认/忽ç¥ï¼â¡ å¼¹åºè¯¥ä¼ä¸è¯¥æ¥è¡¨æçææ¥æ ·å¼ï¼æ é¢=ä¼ä¸å+æ¥è¡¨æ ææ¥æ°æ®ï¼ååº=è¿åæ
åµâè¿è¾ç产æ
åµâ平忿 âå¡«æ¥äººä¿¡æ¯ï¼åºé¨å
³éå¯ç¨ï¼ | 宿µæ 4 åâä¼ä¸è§£éâæ®çï¼âæ°æ®âå¼¹çªå
容ä¸ååºé¡ºåºç¬¦å颿ï¼å
³éæ£å¸¸ | éè¿ | |
| | | | 5 | 导åºâéè·¯è¿è¾éæ±æ»è¡¨ï¼æ´æ¬ï¼â2026-08 å¹¶èæ¬æ ¡éª 19 个 sheet | å
¨é¨é¡µæ autoFilterãæ éèè¡ï¼æ°å¼åå
æ ¼ä¿ç两ä½å°æ°æ¾ç¤ºä¸å
¨ç²¾åº¦ï¼ç³»ç»æ±å/忝å
¬å¼ä¿çï¼æå¼å³éç® | æ ¡éªæä»¶ï¼docs/çææ±æ»å¤§è¡¨/è¾åº/çæ_éè·¯è¿è¾éæ±æ»è¡¨_2026-08_æ¸
çéªè¯.xlsxï¼19 页 filter=Noneãhidden=[]ï¼C4/D4 çé¿æ±åä¸åæ¯å
¬å¼ä¿ç | éè¿ | |
| | | | 6 | æ¥è¡¨çæé¡µæ£æ¥ Tab 䏿 ç¾ | 顺åºï¼æ±æ»å¤§è¡¨âè´§è¿æ¥è¡¨âæ
客æ¥è¡¨âåå¸å®¢è¿æ¥è¡¨âæèµæ¥è¡¨âè½èæ¥è¡¨ï¼åå¸å®¢è¿æ tagï¼æ´æ¬ä»åå¸å®¢è¿ç§»åº | æ±æ»å¤§è¡¨ç¬ç«ä¸ºé¦ç»ï¼åå¸å®¢è¿æ ç¾æ âå
¬äº¤/åºç§/轨é轮渡/æ±æ»âtag | éè¿ | |
| | | | 7 | å®¡æ ¸ç»æé¡µçéæ | æ¥è¡¨æå¨æ¥è¡¨ç±»å左边 | çéæ é¡ºåºä¸ºâæ¥è¡¨æ â æ¥è¡¨ç±»å â æ§è¡å®¡æ ¸æé®â | éè¿ | |
| | | | 8 | æ¥è¡¨çæé¡µç¹å¼æä»½ä¸æ | é»è®¤å±å¼å½å年份 2026 å¹´ 1â12 æï¼å
¶æ¬¡ 2025 å¹´ï¼å次 2027 å¹´ | 宿µä¸æé¡ºåºï¼2026å¹´1æâ¦2026å¹´12æã2025å¹´1æâ¦2025å¹´12æã2027å¹´1æâ¦2027å¹´12æ | éè¿ | |
| | | |
| | | ## åç°çé®é¢ä¸å¤ç½® |
| | | - åå²é®é¢ï¼è§ååç§°/说æéé¤éå¼å¤è¿æ··æä¸é弿 å
³çæ°åï¼å¦è¿è·åºé´ [10,3000]ï¼ï¼æ§å®ç°ä¼è¯¯æ¹ãæ¬è½®æ¹ä¸ºâææ§éå¼å¹é
对å¾ä¸çç¾åæ¯çæ®µææ¿æ¢âï¼å¹¶å¯¹è¯»å表åå¹çèªæï¼å®æµæªå误伤æ å
³æ°åã |
| | | - åå²é®é¢ï¼æ±æ»å¤§è¡¨æ¯çæ¬èº«å¸¦çéä¸éèè¡ï¼æ§å¯¼åºä¼åæ ·å¸¦åºï¼çèµ·æ¥åç¼ºæ°æ®ãæ¬è½®å¯¼åºåç»ä¸æ¸
çå¹¶éªè¯ 19 页å
¨å¹²åã |
| | | - åå²é®é¢ï¼å
¬è·¯æ
å®¢è¿æ¿å¯¼å
¥æ¤ååªè®¤âä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°â䏿ååï¼åäºä¸è½½çæ°æ¨¡æ¿å°ä¸¤å导è´å¯¼ä¸è¿å»ãæ¬è½®æå«å+表头大åå
¼å®¹æ°æ§ä¸¤ç§ç»æã |
| | | - 夿³¨ï¼docs/å®¡æ ¸çé¢ææ¥æ¨¡æ¿/HTML_å®¡æ ¸ç»æ_Aug_2026.html ä¸ºæ¬æ¬¡å®¡æ ¸ç»æé¡µä¸å¼¹çªéªè¯çæ¡£ã |
| | | |
| | | ## ç»è®º |
| | | 8 æ¡æ´æ¹å
¨é¨å®æå¹¶éè¿åå½éªè¯ï¼ç¸å
³æ¹å¨è§ä»£ç æäº¤ï¼DataImportService/ReportExportService/RuleManageController/AuditResult.vue/DataView.vue/ReportExport.vue/DataImport.vueï¼ã |
| | |
| | | - `mvn` ç¼è¯éè¿ï¼åä¸äº¤æ¥å·²å®æï¼ï¼å端çç¼è¯ 3 次æåï¼15:19/15:24ï¼ã |
| | | ### éçä¸ä¸ä¸æ¥ |
| | | - 8 æå½ææ°æ®å¯¼å
¥å M2/M3 å¡«å¼å¤éªï¼ä¸´æ¶èæ¬ `_verify_*.py/.sql`ã`_do_patch1.py` ä¸å ä¸å
¥åºã |
| | | --- |
| | | |
| | | ## è¡¥å
ï¼2026-09-08 åæï¼åä¸ä¼è¯ç»ï¼ï¼ç¨æ· 8 æ¡æ´æ¹å
¨é¨è½å°å¹¶åå½ |
| | | ### 仿¥æ°å¢ |
| | | 1. **æ°æ®æ¥çæ»å¨å è½½ï¼çæºå¤æµï¼**ï¼è¡¨æ ¼åºå®é«åº¦ãæ»å¨å®¹å¨ç»å®å° el-table å
é¨ body-wrapperï¼å®æµ 20â40 è¡è¿½å ã页ç¾ç¬¬1页â第2页ï¼13056 æ¡æ»æ°ä¸åï¼ã |
| | | 2. **è§åéå¼è卿®åå°å
¨é¨è§å**ï¼`RuleManageController.syncThresholdText` æ¹ä¸ºæâæ§éå¼å¯¹åºçæ®µâæ¿æ¢ï¼æ¯æ ±N%ã>N%ã0.5â50%ã1.3âé«åº30% çå£å¾ï¼ï¼è¯»å表å¹çèªæï¼å®æµ P005/B001/E005/RULE_003/T001 æ¾å¼ä¸æ¯å¼å£å¾åæ£ç¡®å¹¶æä¹
åï¼è¯´æä¸ä¸é弿 å
³çæ°åä¸å被误æ¹ã |
| | | 3. **å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°å¯¼å
¥å
¼å®¹æ°æ¨¡æ¿**ï¼åäºä¸è½½çè¿æ¿åºå¯¼åº .xlsï¼è¡¨å¤´ OWNERNAME/æ»è½¦è¾æ°/客使°ãæ ä¼ä¸ä»£ç åï¼ä¸æ§ä¸æåååå¯å¯¼å
¥ï¼`docs/å
¬è·¯æ
客/模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsx` æ¢ä¸º `.xls`ï¼2026-08 å
± 750 å®¶å
¥åºãæ½æ ·ä¸æºæä»¶ä¸è´ã |
| | | 4. **å®¡æ ¸ç»æâæ°æ®âæ¹ææ¥æ ·å¼å¼¹çª + å é¤ä¼ä¸è§£éå**ï¼è¡å
ä»
å© æ°æ®/确认/忽ç¥ï¼å¼¹çªæ è¿åæ
åµâè¿è¾ç产æ
åµâ平忿 âå¡«æ¥äººä¿¡æ¯ ååºï¼æ é¢=ä¼ä¸å+æ¥è¡¨æã |
| | | 5. **æ±æ»å¤§è¡¨å¯¼åºæ¸
ç**ï¼`cleanSummarySheetPresentation` åæ¶å页çéå¹¶è¿åéèè¡ï¼ç线å
车ç页ä¸ååç¼ºæ°æ®ï¼ï¼ä¿ç两ä½å°æ°æ¾ç¤º/å
¨ç²¾åº¦ãå
¬å¼ä¸æå¼èªå¨éç®ï¼æå
容èªå¨å 宽å宽ï¼åªå 宽ä¸ç¼©çªãè·³è¿åå¹¶æ é¢ï¼ï¼19 页导åºèæ¬æ ¡éªéè¿ã |
| | | 6. **æ¥è¡¨çæ Tab éç»**ï¼âæ±æ»å¤§è¡¨ï¼æ´æ¬ï¼âç¬ç«ä¸ºé¦ä¸ª Tabï¼åå¸å®¢è¿æ¥è¡¨å»æ tagãå½åä¸å
¶å®ä¸è´ã |
| | | 7. **å®¡æ ¸é¡µçéæ é¡ºåº**ï¼æ¥è¡¨æç§»å°æ¥è¡¨ç±»å左边ã |
| | | 8. **æä»½ä¸æå¹´ä»½é¡ºåº**ï¼é»è®¤å½åå¹´ 2026 卿åï¼å
¶å 2025ã2027ã |
| | | ### æµè¯ç»ææè¦ |
| | | - 详è§ãåè½æµè¯æ¥å_æ´æ¹8项åå½éªè¯_2026-09-08.mdãï¼8/8 éè¿ï¼åç«¯å·²éæ°ç¼è¯ãå端已éå¯å è½½æ°ä»£ç ã |
| | | ### éçä¸ä¸ä¸æ¥ |
| | | - 8 æå½ææ°æ® M2/M3 å¡«å¼å¤éªï¼9 æèµ·çéæå¯¼å
¥âæ©åâå®¡æ ¸âçæå®æ´é¾å¾
ä¸å¡æ°æ®å°ä½åç»§ç»ã |
| | | - æ¬å°ä¸´æ¶æä»¶ï¼_*.ps1/_*.sql/*.bak/docs.zip/çæäº§ç©xlsx çï¼æªå
¥åºï¼å¾
æ´çæ¸
çã |
| | |
| | | } |
| | | } |
| | | |
| | | /** å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°ï¼ç¬ç«æ¨¡æ¿ï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼ */ |
| | | /** å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°ï¼å
¼å®¹ä¸¤ç§è¡¨å¤´ï¼â ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼â¡ è¿æ¿åºå¯¼åº OWNERNAME/æ»è½¦è¾æ°/客使°ï¼æ ä¼ä¸ä»£ç åï¼ */ |
| | | public ImportResult importPassengerAuth(MultipartFile file, String period) throws Exception { |
| | | List<PassengerAuthVehicle> authList = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | requireHeaderCols(sheet, "æ
å®¢è¿æ¿è½¦è¾æ°", "ä¼ä¸åç§°", "è½¦è¾æ°", "è½½å®¢ä½æ°"); |
| | | Map<String, Integer> colMap = buildHeaderMapCompat(sheet.getRow(0)); |
| | | Integer nameCol = firstColIdx(colMap, "ä¼ä¸åç§°", "OWNERNAME"); |
| | | Integer vehicleCol = firstColIdx(colMap, "è½¦è¾æ°", "æ»è½¦è¾æ°"); |
| | | Integer seatCol = firstColIdx(colMap, "è½½å®¢ä½æ°", "客使°"); |
| | | Integer codeCol = firstColIdx(colMap, "ä¼ä¸ä»£ç "); |
| | | if (nameCol == null || vehicleCol == null || seatCol == null) { |
| | | throw new RuntimeException("æéæ°æ®ç±»åãæ
å®¢è¿æ¿è½¦è¾æ°ã䏿件å
容ä¸ç¬¦ï¼è¡¨å¤´éå«ãä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ãæãOWNERNAME/æ»è½¦è¾æ°/客使°ãï¼è¯·ç¡®è®¤æ¯å¦ééäºæ°æ®ç±»åæä½¿ç¨äºæ§æ¨¡æ¿"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = gs(row, colMap, "ä¼ä¸åç§°"); |
| | | String name = getString(row, nameCol); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | PassengerAuthVehicle auth = new PassengerAuthVehicle(); |
| | | auth.setReportPeriod(period); |
| | | auth.setEnterpriseCode(gs(row, colMap, "ä¼ä¸ä»£ç ")); |
| | | auth.setEnterpriseCode(codeCol == null ? null : getString(row, codeCol)); |
| | | auth.setEnterpriseName(name.trim()); |
| | | auth.setVehicleCount(gi(row, colMap, "è½¦è¾æ°")); |
| | | auth.setSeatCount(gi(row, colMap, "è½½å®¢ä½æ°")); |
| | | auth.setVehicleCount(getInt(row, vehicleCol)); |
| | | auth.setSeatCount(getInt(row, seatCol)); |
| | | authList.add(auth); |
| | | } |
| | | } catch (Exception e) { |
| | |
| | | return colMap; |
| | | } |
| | | |
| | | /** å
¼å®¹è¡¨å¤´æ å°ï¼å»å¼å·/å»ç©ºç½ï¼çº¯è±æè¡¨å¤´ç»ä¸å¤§åï¼å¦ 'OWNERNAME' â OWNERNAMEï¼ï¼ä¾¿äºæ°æ§æ¨¡æ¿æ··ç¨ */ |
| | | private Map<String, Integer> buildHeaderMapCompat(Row header) { |
| | | Map<String, Integer> colMap = new HashMap<>(); |
| | | if (header == null) return colMap; |
| | | for (Cell cell : header) { |
| | | if (cell == null) continue; |
| | | String h = FORMATTER.formatCellValue(cell).replace("'", "").replace("\"", "").trim(); |
| | | if (h.isEmpty()) continue; |
| | | boolean ascii = true; |
| | | for (int i = 0; i < h.length(); i++) { |
| | | if (h.charAt(i) > 127) { ascii = false; break; } |
| | | } |
| | | if (ascii) h = h.toUpperCase(); |
| | | if (!colMap.containsKey(h)) colMap.put(h, cell.getColumnIndex()); |
| | | } |
| | | return colMap; |
| | | } |
| | | |
| | | /** æå«å顺åºå第ä¸ä¸ªåå¨çåå·ï¼å
¨é¨ç¼ºå¤±è¿å null */ |
| | | private Integer firstColIdx(Map<String, Integer> colMap, String... aliases) { |
| | | for (String a : aliases) { |
| | | Integer idx = colMap.get(a); |
| | | if (idx != null) return idx; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private int colIdx(Map<String, Integer> colMap, String header) { |
| | | Integer idx = colMap.get(header); |
| | | return idx == null ? -1 : idx; |
| | |
| | | log.info("æ±æ»å·¥ä½ç°¿è£åªè³ {} æå®æï¼æ¸
空/æ¹ååå
æ ¼æ°={}", toMonth, trimmed); |
| | | } |
| | | dynamicSummaryYear(wb, year); |
| | | applyTwoDecimalFormat(wb); // æ±æ»å¤§è¡¨ä¿ç人工æ¯çå宽/çå¼ï¼ä¸å autoFitï¼é¿å
¬å¼ä¼æå宽顶çï¼ |
| | | cleanSummarySheetPresentation(wb); // åæ¶å页çéãè¿åéèè¡ï¼å¦åâç线å
车âç页çèµ·æ¥åç¼ºæ°æ®ï¼ |
| | | applyTwoDecimalFormat(wb); // æ°å¼æ¾ç¤ºä¸¤ä½å°æ°ï¼ä¿çå
¨ç²¾åº¦ï¼ï¼% / æ¥æçæ¢ææ ·å¼ä¸æ¹å |
| | | autoFitContentColumns(wb); // ææ°æ®èªå¨å 宽å宽ï¼åªå 宽ä¸ç¼©çªï¼è·³è¿åå¹¶æ 颿 ¼ï¼é²é¿ææ¬æååæçï¼ |
| | | if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr(); |
| | | wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // è¿å/æ¹åå
¬å¼åæå¼å³éç® |
| | | return toBytes(wb); |
| | |
| | | } |
| | | |
| | | /** æ±æ»è¡¨å¯¼åºåæå
容èªå¨å 宽å宽ï¼åªå 宽ä¸ç¼©çªï¼è·³è¿åå¹¶åå
æ ¼æ é¢ï¼é²é¿æ 颿ååæçï¼ */ |
| | | /** æ±æ»å¤§è¡¨è¾åºåæ¸
çæ¯çæ®ç䏿¥ççéä¸è¡éèï¼çéç®å¤´/éèè¡ä¼è®©æ´é¡µçèµ·æ¥âç¼ºæ°æ®â */ |
| | | private void cleanSummarySheetPresentation(org.apache.poi.ss.usermodel.Workbook wb) { |
| | | if (wb == null) return; |
| | | for (int s = 0; s < wb.getNumberOfSheets(); s++) { |
| | | org.apache.poi.ss.usermodel.Sheet sh = wb.getSheetAt(s); |
| | | if (sh == null) continue; |
| | | if (sh instanceof org.apache.poi.xssf.usermodel.XSSFSheet) { |
| | | org.apache.poi.xssf.usermodel.XSSFSheet xs = (org.apache.poi.xssf.usermodel.XSSFSheet) sh; |
| | | if (xs.getCTWorksheet().isSetAutoFilter()) xs.getCTWorksheet().unsetAutoFilter(); |
| | | for (org.apache.poi.ss.usermodel.Row row : xs) { |
| | | if (row == null) continue; |
| | | org.apache.poi.xssf.usermodel.XSSFRow xr = (org.apache.poi.xssf.usermodel.XSSFRow) row; |
| | | if (xr.getCTRow().isSetHidden()) xr.getCTRow().unsetHidden(); |
| | | } |
| | | } else { |
| | | sh.setAutoFilter(null); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void autoFitContentColumns(org.apache.poi.ss.usermodel.Workbook wb) { |
| | | if (wb == null) return; |
| | | org.apache.poi.ss.usermodel.DataFormatter dfmt = new org.apache.poi.ss.usermodel.DataFormatter(); |
| | |
| | | if (body.getThreshold() != null) rule.setThreshold(body.getThreshold()); |
| | | if (body.getAlertLevel() != null) rule.setAlertLevel(body.getAlertLevel()); |
| | | if (body.getDescription() != null) rule.setDescription(body.getDescription()); |
| | | // éå¼å忶忥è§ååç§°ä¸è¯´æï¼åç§°/说æéç ±N% ææ°é弿¹åï¼ï¼å¹¶éä¿åæä¹
å |
| | | // éå¼å忶忥è§ååç§°ä¸è¯´æï¼åç§°/说æé䏿§éå¼å¯¹åºç N% çæ®µææ°é弿¹åï¼ï¼å¹¶éä¿åæä¹
å |
| | | if (body.getThreshold() != null && !body.getThreshold().equals(oldThreshold)) { |
| | | syncThresholdText(rule); |
| | | syncThresholdText(rule, oldThreshold); |
| | | } |
| | | rule.setUpdatedAt(LocalDateTime.now()); |
| | | ruleMapper.updateById(rule); |
| | | return Result.ok("å·²ä¿å"); |
| | | } |
| | | |
| | | /** ±N% åæ ·å¹é
ï¼åç§°/说æä¸ééå¼å¨æååççæ®µï¼ */ |
| | | private static final Pattern PCT_TOKEN = Pattern.compile("±\\s*\\d+(?:\\.\\d+)?\\s*%"); |
| | | /** % åçæ°å¼ï¼åç§°/说æä¸ééå¼å¨æååççæ®µï¼% åå¯è½æ¯ ±ã>ãè¶
è¿ãé«åº çæè¿°è¯ï¼ä»
æ¿æ¢æ°åæ¬èº«ï¼ */ |
| | | private static final Pattern PCT_NUM = Pattern.compile("\\d+(?:\\.\\d+)?(?=%)"); |
| | | |
| | | /** æææ¬ä¸ç ±N% æ¿æ¢ä¸º ±threshold%ï¼å«å·²å¸¦ ± çåç¼ï¼é¿å
å Â±ï¼ */ |
| | | private String decorateThresholdText(String text, String threshold) { |
| | | if (text == null || text.trim().isEmpty() || threshold == null || threshold.trim().isEmpty()) return text; |
| | | String token = "±" + threshold.trim() + "%"; |
| | | return PCT_TOKEN.matcher(text).replaceAll(Matcher.quoteReplacement(token)); |
| | | /** è§£æéå¼å符串ä¸ç¬¬ä¸ä¸ªæ°å¼ï¼æ æ³è§£æè¿å null */ |
| | | private Double parseThresholdNum(String threshold) { |
| | | if (threshold == null || threshold.trim().isEmpty()) return null; |
| | | java.util.regex.Matcher m = Pattern.compile("\\d+(?:\\.\\d+)?").matcher(threshold.trim()); |
| | | if (!m.find()) return null; |
| | | try { |
| | | return Double.parseDouble(m.group()); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** åç§°/说æééå¼åæ¥ï¼è¯»å表æ¶ä¹å¨ææ ¡æ£ï¼DB 䏿§ææ¬ä¸é»å¡æ¾ç¤ºï¼ */ |
| | | private void syncThresholdText(AuditRule rule) { |
| | | /** æ°å¼å±ç¤ºï¼æ´æ°å»æå°æ°ä½ï¼å
¶ä½ä¿çå精度 */ |
| | | private String fmtNum(double v) { |
| | | long l = Math.round(v); |
| | | if (Math.abs(v - l) < 1e-9) return Long.toString(l); |
| | | return new java.math.BigDecimal(String.valueOf(v)).stripTrailingZeros().toPlainString(); |
| | | } |
| | | |
| | | /** ç±é弿°å¼æ¨å¯¼â说ææåéåçæ¯ç¾åä¹å âçåéå£å¾ï¼ |
| | | * â é弿¬èº«å°±æ¯ç¾åæ°ï¼å¦ 20 â ±20%ï¼ï¼ |
| | | * â¡ é弿¯å°æ°æ¯ä¾ï¼å¦ 0.02 â 2%ã0.5 â 50%ï¼ï¼æ¤æ¶ <1 æå¯ç¨ï¼ |
| | | * ⢠é弿¯æ¯å¼ï¼å¦ 1.3 â é«åº30%ï¼ï¼æ¤æ¶ >1 æå¯ç¨ã */ |
| | | private double[] thresholdPercentCandidates(double v) { |
| | | double[] cands; |
| | | if (v > 0 && v < 1) { |
| | | cands = new double[]{v, v * 100}; |
| | | } else if (v > 1) { |
| | | cands = new double[]{v, (v - 1) * 100}; |
| | | } else { |
| | | cands = new double[]{v}; |
| | | } |
| | | return cands; |
| | | } |
| | | |
| | | /** ææ§éå¼å¹é
ææ¬ä¸å·²æçç¾åæ¯ç段ï¼ä»
æ¿æ¢â䏿§éå¼å¯¹å¾ä¸âçæ°å为æ°éå¼å¯¹åºæ°åï¼å¯¹ä¸ä¸çæ°åï¼å¦è¯´æéä¸é弿 å
³çæ°å¼ï¼ä¿æä¸å¨ */ |
| | | private String decorateThresholdText(String text, String oldThreshold, String newThreshold) { |
| | | if (text == null || text.trim().isEmpty()) return text; |
| | | Double oldNum = parseThresholdNum(oldThreshold); |
| | | Double newNum = parseThresholdNum(newThreshold); |
| | | if (oldNum == null || newNum == null) return text; |
| | | double[] oldCands = thresholdPercentCandidates(oldNum); |
| | | double[] newCands = thresholdPercentCandidates(newNum); |
| | | if (oldCands.length != newCands.length) return text; |
| | | java.util.regex.Matcher m = PCT_NUM.matcher(text); |
| | | StringBuffer sb = new StringBuffer(); |
| | | while (m.find()) { |
| | | double token; |
| | | try { |
| | | token = Double.parseDouble(m.group()); |
| | | } catch (Exception e) { |
| | | continue; |
| | | } |
| | | String repl = null; |
| | | for (int i = 0; i < oldCands.length; i++) { |
| | | if (Math.abs(oldCands[i] - token) < 1e-9) { |
| | | repl = fmtNum(newCands[i]); |
| | | break; |
| | | } |
| | | } |
| | | if (repl == null) continue; |
| | | m.appendReplacement(sb, Matcher.quoteReplacement(repl)); |
| | | } |
| | | m.appendTail(sb); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** åç§°/说æééå¼åæ¥ï¼update æ¶æ æ§âæ° æ¹åå¹¶æä¹
åï¼ */ |
| | | private void syncThresholdText(AuditRule rule, String oldThreshold) { |
| | | if (rule == null || rule.getThreshold() == null) return; |
| | | rule.setRuleName(decorateThresholdText(rule.getRuleName(), rule.getThreshold())); |
| | | rule.setDescription(decorateThresholdText(rule.getDescription(), rule.getThreshold())); |
| | | rule.setRuleName(decorateThresholdText(rule.getRuleName(), oldThreshold, rule.getThreshold())); |
| | | rule.setDescription(decorateThresholdText(rule.getDescription(), oldThreshold, rule.getThreshold())); |
| | | } |
| | | |
| | | /** 读å表æ¶èªæï¼ä»
彿æ¬ç段ä¸å½åéå¼å¯¹åºæ¶ææ¹åï¼å¹çï¼ä¸è¯¯ä¼¤ä¸é弿 å
³çæ°åï¼ */ |
| | | private void syncThresholdText(AuditRule rule) { |
| | | syncThresholdText(rule, rule == null ? null : rule.getThreshold()); |
| | | } |
| | | } |
| | |
| | | <el-main :class="{ 'main-focus': focusMode }" style="display:flex;flex-direction:column;min-height:0"> |
| | | <h3 v-show="!focusMode" style="flex:none;margin:0 0 10px">å®¡æ ¸ç»æ</h3> |
| | | <el-form :inline="true" style="flex:none"> |
| | | <el-form-item label="æ¥è¡¨æ"> |
| | | <el-date-picker v-model="period" type="month" placeholder="éæ©æä»½" value-format="yyyy-MM" @change="onPeriodChange"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨ç±»å"> |
| | | <el-radio-group v-model="reportType" @change="onTypeChange"> |
| | | <el-radio label="H2032">è´§è¿ææ¥</el-radio> |
| | |
| | | |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨æ"> |
| | | <el-date-picker v-model="period" type="month" placeholder="éæ©æä»½" value-format="yyyy-MM" @change="onPeriodChange"></el-date-picker> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="executeAudit" :loading="loading">æ§è¡å®¡æ ¸</el-button> |
| | | <el-button type="success" plain @click="markAudited" :loading="marking">æ è®°å·²å®¡æ ¸</el-button> |
| | |
| | | <i :class="statusIcon(row.status)"></i> {{ statusText(row.status) }} |
| | | </el-tag> |
| | | <span class="spacer"></span> |
| | | <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="goData(row)">æ°æ®</el-link> |
| | | <el-tag v-if="explainType(reportType)" size="mini" :type="row.verifyExplanation ? 'success' : 'warning'" |
| | | style="cursor:pointer" :title="expandedExplain === row.reportId ? 'æ¶èµ·è§£é' : 'å±å¼å¡«å/æ¥çè§£é'" |
| | | @click="toggleExplain(row.reportId)"> |
| | | {{ row.verifyExplanation ? 'è§£éï¼å·²å¡«å' : 'è§£éï¼æªå¡«å' }} |
| | | </el-tag> |
| | | <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="openMonthReport(row)">æ°æ®</el-link> |
| | | <el-button v-if="row.status === 'PENDING'" size="mini" type="success" icon="el-icon-check" round @click="review(row, 'CONFIRMED')">确认</el-button> |
| | | <el-button v-if="row.status === 'PENDING'" size="mini" type="info" icon="el-icon-close" round plain @click="review(row, 'IGNORED')">忽ç¥</el-button> |
| | | <span v-else class="done-text">å·²å¤ç</span> |
| | |
| | | <div class="detail-line"><span class="dt-label">å®é
</span><span class="dt-val">{{ row.actualValue }}</span></div> |
| | | <div class="detail-line"><span class="dt-label">åºå</span><span class="dt-val">{{ row.thresholdValue }}</span></div> |
| | | <div class="detail-line"><span class="dt-label">å·®å¼</span><span class="dt-val dt-warn">{{ row.deviation }}</span></div> |
| | | </div> |
| | | </div> |
| | | <div v-if="explainType(reportType) && expandedExplain === row.reportId" class="explain-side" style="margin:0 12px 10px"> |
| | | <div class="explain-head"> |
| | | <span class="explain-title">ä¼ä¸è§£é</span> |
| | | <el-tag size="mini" :type="row.verifyExplanation ? 'success' : 'warning'">{{ row.verifyExplanation ? '已填å' : 'æªå¡«å' }}</el-tag> |
| | | <span style="flex:1"></span> |
| | | <el-button size="mini" text @click="expandedExplain = null">æ¶èµ·</el-button> |
| | | </div> |
| | | <span v-if="row.verifyExplanation" class="explain-cur">{{ row.verifyExplanation }}</span> |
| | | <el-input v-model="explainDrafts[row.reportId]" type="textarea" :rows="2" resize="none" |
| | | placeholder="å¡«å/ä¿®æ¹è¯¥ä¼ä¸çè§£é说æâ¦"></el-input> |
| | | <div class="explain-actions"> |
| | | <el-button size="mini" type="primary" plain :loading="savingExplainId === row.reportId" @click="saveExplain(row)">ä¿åè§£é</el-button> |
| | | <el-button v-if="(explainDrafts[row.reportId] || '') !== (row.verifyExplanation || '')" size="mini" text @click="resetDraft(row.reportId)">éç½®</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <b>{{ item.ruleName }}</b> |
| | | <el-tag :type="item.alertLevel === 'RED' ? 'danger' : 'warning'" size="mini">{{ item.alertLevel }}</el-tag> |
| | | <el-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</el-tag> |
| | | <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="goData(item)">æ°æ®</el-link> |
| | | <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="openMonthReport(item)">æ°æ®</el-link> |
| | | <span style="flex:1"></span> |
| | | <el-button v-if="item.status === 'PENDING'" size="mini" type="success" @click="review(item, 'CONFIRMED')">确认</el-button> |
| | | <el-button v-if="item.status === 'PENDING'" size="mini" type="info" @click="review(item, 'IGNORED')">忽ç¥</el-button> |
| | |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ä¼ä¸è§£é" width="340"> |
| | | <template slot-scope="scope"> |
| | | <div v-if="explainType(reportType) && scope.row.items[0]" class="explain-cell"> |
| | | <div v-if="expandedExplain !== scope.row.items[0].reportId" class="explain-compact"> |
| | | <el-tag size="mini" :type="scope.row.items[0].verifyExplanation ? 'success' : 'warning'">{{ scope.row.items[0].verifyExplanation ? '已填å' : 'æªå¡«å' }}</el-tag> |
| | | <div v-if="scope.row.items[0].verifyExplanation" class="explain-preview">{{ scope.row.items[0].verifyExplanation }}</div> |
| | | <el-button size="mini" text type="primary" @click="expandedExplain = scope.row.items[0].reportId">å¡«å/ä¿®æ¹</el-button> |
| | | </div> |
| | | <div v-else class="explain-side"> |
| | | <div class="explain-head"> |
| | | <span class="explain-title">ä¼ä¸è§£é</span> |
| | | <span style="flex:1"></span> |
| | | <el-button size="mini" text @click="expandedExplain = null">æ¶èµ·</el-button> |
| | | </div> |
| | | <span v-if="scope.row.items[0].verifyExplanation" class="explain-cur">{{ scope.row.items[0].verifyExplanation }}</span> |
| | | <el-input v-model="explainDrafts[scope.row.items[0].reportId]" type="textarea" :rows="2" resize="none" |
| | | placeholder="å¡«å/ä¿®æ¹è¯¥ä¼ä¸çè§£é说æâ¦"></el-input> |
| | | <div class="explain-actions"> |
| | | <el-button size="mini" type="primary" plain :loading="savingExplainId === scope.row.items[0].reportId" @click="saveExplain(scope.row.items[0])">ä¿åè§£é</el-button> |
| | | <el-button v-if="(explainDrafts[scope.row.items[0].reportId] || '') !== (scope.row.items[0].verifyExplanation || '')" size="mini" text @click="resetDraft(scope.row.items[0].reportId)">éç½®</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <span v-else style="color:#C0C4CC;font-size:12px">â</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="130"> |
| | | <template slot-scope="scope"> |
| | | <el-button v-if="hasPending(scope.row)" size="mini" type="success" plain @click="reviewAll(scope.row, 'CONFIRMED')">å
¨é¨ç¡®è®¤</el-button> |
| | |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | | |
| | | <el-dialog title="æ°æ®æ¥çï¼ææ¥æ ·å¼ï¼" :visible.sync="monthVisible" width="880px" top="4vh" custom-class="mr-dialog"> |
| | | <div v-if="monthLoading" style="padding:40px 0;text-align:center;color:#909399">å è½½ä¸â¦</div> |
| | | <div v-else-if="!monthRecords.length" style="padding:30px 0;text-align:center;color:#909399"> |
| | | æªæ¥è¯¢å°è¯¥ä¼ä¸ã该æ¥è¡¨æçå¡«æ¥æ°æ®ï¼å¯è½è¿æªå¯¼å
¥ï¼ |
| | | </div> |
| | | <div v-else class="mr-body"> |
| | | <div v-for="(rec, ri) in monthRecords" :key="ri" class="mr-card"> |
| | | <div class="mr-title">{{ monthTitle(rec) }}</div> |
| | | <table class="mr-meta"> |
| | | <tbody> |
| | | <tr v-for="(m, mi) in monthMeta(rec)" :key="mi"> |
| | | <td class="mr-meta-k">{{ m.label }}</td> |
| | | <td class="mr-meta-v">{{ fmtVal(m.value) }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | <div v-for="sec in monthSections(rec)" :key="sec.title" class="mr-sec"> |
| | | <div class="mr-sec-title">{{ sec.title }}</div> |
| | | <table class="mr-sec-tab" border="1"> |
| | | <thead> |
| | | <tr> |
| | | <th style="width:34%">ææ åç§°</th> |
| | | <th v-for="(h, hi) in sec.head" :key="hi" style="width:16.5%">{{ h }}</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(rw, rwi) in sec.rows" :key="rwi"> |
| | | <td>{{ rw.label }}</td> |
| | | <td v-for="(c, ci) in rw.cells" :key="ci">{{ fmtVal(c.v) }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | <div v-if="rec.verifyExplanation || rec.reportNotes" class="mr-note"> |
| | | <div v-if="rec.verifyExplanation" class="mr-note-line"><b>æ ¸å®è§£éï¼</b>{{ rec.verifyExplanation }}</div> |
| | | <div v-if="rec.reportNotes" class="mr-note-line"><b>夿³¨ï¼</b>{{ rec.reportNotes }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <span slot="footer"> |
| | | <el-button size="small" @click="monthVisible = false">å
³é</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | </el-container> |
| | | </template> |
| | | <script> |
| | |
| | | import api from '@/api' |
| | | const DATA_TYPE_MAP = { H2032: 'h2032', H2031: 'h2031', CITY_BUS: 'cityBus', CITY_TAXI: 'cityTaxi' } |
| | | const EXPLAIN_TYPES = ['H2032', 'H2031', 'CITY_BUS', 'CITY_TAXI'] |
| | | |
| | | const MONTH_CFG = { |
| | | h2032: { |
| | | meta: [['æ¥è¡¨æ', 'reportPeriod'], ['ä¼ä¸åç§°', 'enterpriseName'], ['ç»ä¸ç¤¾ä¼ä¿¡ç¨ä»£ç ', 'unifiedCreditCode'], ['å¡«æ¥åä½', 'reportUnit']], |
| | | sections: [ |
| | | { title: 'ä¸ãè¿åæ
åµ', head: ['è½¦è¾æ°ï¼è¾ï¼', 'æ è®°å¨ä½æ°ï¼å¨ï¼'], rows: [ |
| | | ['æ»è®¡', ['vehicleTotal', 'tonsTotal']], |
| | | ['å
¶ä¸ï¼çµå¼è½¦', ['vehicleTractor', null]], |
| | | ['æè½¦', ['vehicleTrailer', 'tonsTrailer']], |
| | | ['å
¶ä¸ï¼éè£
箱车', ['vehicleContainer', 'tonsContainer']], |
| | | ['æ´è½¦', ['vehicleWhole', 'tonsWhole']] |
| | | ]}, |
| | | { title: 'äºãè¿è¾ç产æ
åµ', head: ['è´§è¿éï¼å¨ï¼', 'è´§ç©å¨è½¬éï¼å¨å
¬éï¼'], rows: [ |
| | | ['è¿è¾é', ['freightTotal', 'turnoverTotal']], |
| | | ['å
¶ä¸ï¼éè£
ç®±', ['freightContainer', null]], |
| | | ['å
¶ä¸ï¼ç
¤çåå¶å', ['freightCoal', null]], |
| | | ['ç³æ²¹ãå¤©ç¶æ°åå¶å', ['freightOilGas', null]], |
| | | ['å
¶ä¸ï¼åæ²¹', ['freightCrudeOil', null]], |
| | | ['éå±ç¿ç³', ['freightMetalOre', null]], |
| | | ['å
¶ä¸ï¼éç¿ç³', ['freightIronOre', null]], |
| | | ['ç¿å»ºæ§å»ºçææ', ['freightBuilding', null]], |
| | | ['ç²®é£', ['freightGrain', null]] |
| | | ]}, |
| | | { title: '平忿 ', head: [], rows: [ |
| | | ['货车平åå¨ä½', ['avgTonnage', null]], |
| | | ['货车平åè¿è·', ['avgDistance', null]] |
| | | ]}, |
| | | { title: 'å¡«æ¥äººä¿¡æ¯', head: [], rows: [ |
| | | ['åä½è´è´£äºº', ['unitLeader', null]], ['ç»è®¡è´è´£äºº', ['statsLeader', null]], |
| | | ['填表人', ['contactPerson', null]], ['èç³»çµè¯', ['contactPhone', null]], ['æ¥åºæ¥æ', ['reportDate', null]] |
| | | ]} |
| | | ] |
| | | }, |
| | | h2031: { |
| | | meta: [['æ¥è¡¨æ', 'reportPeriod'], ['ä¼ä¸åç§°', 'enterpriseName']], |
| | | sections: [ |
| | | { title: 'ä¸ãè¿åæ
åµ', head: ['è½¦è¾æ°ï¼è¾ï¼', 'æ ¸å®è½½å®¢ä½ï¼å®¢ä½ï¼'], rows: [ |
| | | ['æ»è®¡', ['vehicleTotal', 'seatTotal']], |
| | | ['客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼', ['vehicleSchedule', 'seatSchedule']], |
| | | ['客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼', ['vehicleCharter', 'seatCharter']] |
| | | ]}, |
| | | { title: 'äºãè¿è¾ç产æ
åµ', head: ['客è¿éï¼äººæ¬¡ï¼', 'æ
客å¨è½¬éï¼äººå
¬éï¼'], rows: [ |
| | | ['æ»è®¡', ['passengerTotal', 'turnoverTotal']], |
| | | ['ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', ['passengerClass1', 'turnoverClass1']], |
| | | ['äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', ['passengerClass2', 'turnoverClass2']], |
| | | ['ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', ['passengerClass3', 'turnoverClass3']], |
| | | ['å类客è¿ç线ï¼å«å®çº¿æ
游ï¼', ['passengerClass4', 'turnoverClass4']], |
| | | ['客è¿å
车ï¼å«éå®çº¿æ
游ï¼', ['passengerCharter', 'turnoverCharter']] |
| | | ]}, |
| | | { title: '客è¿å¹³åè¿è·', head: [], rows: [ |
| | | ['客è¿å¹³åè¿è·', ['avgDistanceTotal', null]], |
| | | ['ä¸ç±»å®¢è¿ç线平åè¿è·', ['avgDistanceClass1', null]], |
| | | ['äºç±»å®¢è¿ç线平åè¿è·', ['avgDistanceClass2', null]], |
| | | ['ä¸ç±»å®¢è¿ç线平åè¿è·', ['avgDistanceClass3', null]], |
| | | ['å类客è¿ç线平åè¿è·', ['avgDistanceClass4', null]], |
| | | ['客è¿å
车平åè¿è·', ['avgDistanceCharter', null]] |
| | | ]} |
| | | ] |
| | | }, |
| | | cityBus: { |
| | | meta: [['æ¥è¡¨æ', 'reportPeriod'], ['å¸å·', 'city'], ['ä¼ä¸åç§°', 'enterpriseName']], |
| | | sections: [ |
| | | { title: 'ä¸ãå
Œ
±æ±½çµè½¦è¿è¥', head: [], rows: [ |
| | | ['è¿è¥è½¦æ°ï¼è¾ï¼', ['opVehicles', null]], ['è¿è¥ç«ç¹æ»ä¸ªæ°ï¼ä¸ªï¼', ['stopTotal', null]], |
| | | ['å
¶ä¸ï¼åå¸è¿è¥ç«ç¹', ['stopCity', null]], ['客è¿éï¼ä¸äººæ¬¡ï¼', ['passengerVolume', null]], |
| | | ['å
¶ä¸ï¼åå¸å®¢è¿é', ['passengerCity', null]], ['æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', ['turnover', null]], |
| | | ['å
¶ä¸ï¼å叿
客å¨è½¬é', ['turnoverCity', null]] |
| | | ]}, |
| | | { title: 'äºãæ´¾çææ ä¸å¢é', head: [], rows: [ |
| | | ['å¹³åè¿è·ï¼å
¬éï¼', ['avgDistance', null]], ['åå¸å
å¹³åè¿è·', ['avgDistanceCity', null]], |
| | | ['åé
å乡客è¿é', ['passengerChengxiang', null]], ['åé
å乡å¨è½¬é', ['turnoverChengxiang', null]], |
| | | ['åé
å乡平åè¿è·', ['avgDistanceChengxiang', null]], ['客è¿éç¯æ¯', ['ratePassengerMom', null]], |
| | | ['åå¸å
客è¿éç¯æ¯', ['ratePassengerCityMom', null]], ['åé
å乡客è¿éç¯æ¯', ['rateChengxiangMom', null]], |
| | | ['客è¿é忝', ['ratePassengerYoy', null]], ['åå¸å
客è¿éå æ¯', ['passengerCityRatio', null]], |
| | | ['åé
å乡客è¿éå æ¯', ['chengxiangRatio', null]], ['填表人', ['contactPerson', null]], |
| | | ['æ¥åºæ¥æ', ['reportDate', null]] |
| | | ]} |
| | | ] |
| | | }, |
| | | cityTaxi: { |
| | | meta: [['æ¥è¡¨æ', 'reportPeriod'], ['å¸å·', 'city'], ['ä¼ä¸åç§°', 'enterpriseName']], |
| | | sections: [ |
| | | { title: 'ä¸ã巡游åºç§è¿è¥æå¡', head: [], rows: [ |
| | | ['è½½å®¢è½¦æ¬¡æ»æ°ï¼ä¸è½¦æ¬¡ï¼', ['tripTotal', null]], ['å
¶ä¸ï¼åå¸è½½å®¢è½¦æ¬¡', ['tripCity', null]], |
| | | ['客è¿éï¼ä¸äººæ¬¡ï¼', ['passengerVolume', null]], ['å
¶ä¸ï¼åå¸å®¢è¿é', ['passengerCity', null]], |
| | | ['æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', ['turnover', null]], ['å
¶ä¸ï¼å叿
客å¨è½¬é', ['turnoverCity', null]], |
| | | ['è¿è¥è½¦è¾æ°ï¼è¾ï¼', ['opVehicles', null]] |
| | | ]}, |
| | | { title: 'äºãæ´¾çææ ä¸å¢é', head: [], rows: [ |
| | | ['å¹³åè¿è·ï¼å
¬éï¼', ['avgDistance', null]], ['åå¸å
å¹³åè¿è·', ['avgDistanceCity', null]], |
| | | ['å车次载客人æ°', ['passengersPerTrip', null]], ['è½½å®¢è½¦æ¬¡ç¯æ¯', ['rateTripMom', null]], |
| | | ['è½½å®¢è½¦æ¬¡åæ¯', ['rateTripYoy', null]], ['客è¿éç¯æ¯', ['ratePassengerMom', null]], |
| | | ['客è¿é忝', ['ratePassengerYoy', null]], ['åå¸å
客è¿éå æ¯', ['passengerCityRatio', null]], |
| | | ['åé
å乡平åè¿è·', ['avgDistanceChengxiang', null]] |
| | | ]} |
| | | ] |
| | | } |
| | | } |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'AuditResult', |
| | |
| | | return { |
| | | period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false, |
| | | enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', batchSelection: [], focusMode: false, |
| | | explainDrafts: {}, savingExplainId: null, expandedExplain: null |
| | | explainDrafts: {}, savingExplainId: null, expandedExplain: null, |
| | | monthVisible: false, monthLoading: false, monthType: '', monthPeriod: '', monthKeyword: '', monthRecords: [], monthColumns: [] |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | methods: { |
| | | dataType(t) { return DATA_TYPE_MAP[t] || '' }, |
| | | explainType(t) { return EXPLAIN_TYPES.indexOf(t) >= 0 }, |
| | | goData(row) { |
| | | openMonthReport(row) { |
| | | const type = DATA_TYPE_MAP[this.reportType] |
| | | if (!type) return |
| | | this.$router.push({ path: '/data', query: { type, period: row.reportPeriod, keyword: row.enterpriseName || row.city || '' } }) |
| | | this.monthType = type |
| | | this.monthPeriod = row.reportPeriod |
| | | this.monthKeyword = row.enterpriseName || row.city || '' |
| | | this.monthRecords = [] |
| | | this.monthColumns = [] |
| | | this.monthVisible = true |
| | | this.monthLoading = true |
| | | api.get('/data/list', { |
| | | params: { |
| | | type, period: row.reportPeriod, |
| | | keyword: row.enterpriseName || row.city || '', |
| | | page: 1, size: 50 |
| | | }, timeout: 60000 |
| | | }).then(res => { |
| | | const d = res.data || {} |
| | | this.monthRecords = d.records || [] |
| | | this.monthColumns = d.columns || [] |
| | | if (!this.monthRecords.length) this.$message.warning('æªæ¥è¯¢å°è¯¥ä¼ä¸è¯¥æ¥è¡¨æçå¡«æ¥æ°æ®ï¼å¯è½å°æªå¯¼å
¥ï¼') |
| | | }).catch(e => { |
| | | if (e && e._toast) return |
| | | this.$message.error('ææ¥æ°æ®æ¥è¯¢å¤±è´¥') |
| | | }).finally(() => { this.monthLoading = false }) |
| | | }, |
| | | monthTitle(rec) { |
| | | const cfg = MONTH_CFG[this.monthType] || null |
| | | if (!cfg) return (this.monthKeyword || '') + 'ï¼' + this.monthPeriod + 'ï¼' |
| | | const name = rec.enterpriseName || rec.city || this.monthKeyword || '' |
| | | return name + ' ' + (rec.reportPeriod || this.monthPeriod || '') + ' ææ¥æ°æ®' |
| | | }, |
| | | monthMeta(rec) { |
| | | const cfg = MONTH_CFG[this.monthType] || null |
| | | if (!cfg) return [] |
| | | return cfg.meta.map(m => ({ label: m[0], value: rec[m[1]] })).filter(m => m.value !== undefined) |
| | | }, |
| | | monthSections(rec) { |
| | | const cfg = MONTH_CFG[this.monthType] |
| | | if (!cfg) return [] |
| | | return cfg.sections.map(sec => ({ |
| | | title: sec.title, head: sec.head || [], |
| | | rows: sec.rows.map(r => ({ label: r[0], cells: (r[1] || []).map(prop => ({ prop, v: prop ? rec[prop] : null })) })) |
| | | .filter(rw => rw.cells.some(c => c.v !== null && c.v !== undefined && c.v !== '')) |
| | | })).filter(sec => sec.rows.length) |
| | | }, |
| | | fmtVal(v) { |
| | | if (v === null || v === undefined || v === '') return 'â' |
| | | if (typeof v === 'number') return String(v) |
| | | return String(v) |
| | | }, |
| | | goExplain(row) { |
| | | this.$router.push({ path: '/explain', query: { reportType: this.reportType, keyword: row.enterpriseName || row.city || '', period: row.reportPeriod } }) |
| | |
| | | .stat-num { color: #909399; } |
| | | .stat-num b { font-weight: 700; } |
| | | .hit-num { color: #E6A23C; } |
| | | |
| | | .mr-dialog .el-dialog__body { padding: 12px 18px; } |
| | | .mr-body { max-height: 66vh; overflow-y: auto; } |
| | | .mr-card { border: 1px solid #DCDFE6; border-radius: 6px; margin-bottom: 14px; overflow: hidden; } |
| | | .mr-title { background: #409EFF; color: #fff; font-weight: 600; padding: 8px 12px; font-size: 15px; } |
| | | .mr-meta { width: 100%; border-collapse: collapse; background: #F7F9FC; } |
| | | .mr-meta td { border: 1px solid #EBEEF5; padding: 6px 10px; font-size: 13px; } |
| | | .mr-meta-k { width: 120px; color: #909399; background: #F0F4FA; } |
| | | .mr-meta-v { color: #303133; font-weight: 600; } |
| | | .mr-sec { padding: 10px 12px 4px; } |
| | | .mr-sec-title { font-weight: 600; color: #303133; margin: 4px 0 8px; } |
| | | .mr-sec-tab { width: 100%; border-collapse: collapse; font-size: 13px; } |
| | | .mr-sec-tab th, .mr-sec-tab td { border: 1px solid #DCDFE6; padding: 5px 8px; text-align: left; color: #303133; } |
| | | .mr-sec-tab th { background: #F5F7FA; font-weight: 600; } |
| | | .mr-note { margin: 6px 12px 12px; background: #FDF6EC; border: 1px solid #FAECD8; border-radius: 4px; padding: 8px 10px; font-size: 12px; color: #8a6d3b; white-space: pre-wrap; word-break: break-all; } |
| | | .mr-note-line { margin: 2px 0; } |
| | | </style> |
| | |
| | | scaleSplit: '模æ¿_è§ä¸è§ä¸æåè¿è¾é.xlsx', |
| | | freightTurnover: '模æ¿_è´§è¿éå¨è½¬é.xlsx', |
| | | passengerMonthly: 'å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsxï¼å«è¿æ¿è½¦è¾æ°sheetï¼', |
| | | passengerAuth: '模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsxï¼åï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼', |
| | | passengerAuth: '模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsï¼æ¯æ ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ° ä¸ è¿æ¿åºå¯¼åº OWNERNAME/æ»è½¦è¾æ°/客使°ï¼', |
| | | passengerIndividual: '个ä½å®¢è¿éæ±æ».xlsxï¼åï¼æå±å°åº/ä¼ä¸ä»£ç /ä¼ä¸åç§°/个ä½å®¢è¿é/ä¸ªä½æ
客å¨è½¬éï¼', |
| | | energyMonthly: 'é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼.xlsxï¼è½èæç»+è¿æ¿è½¦è¾ä¿¡æ¯ä¸¤sheetï¼', |
| | | energyAuth: '模æ¿_è½è车è¾è¿æ¿ä¿¡æ¯.xlsxï¼åï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼', |
| | |
| | | scaleSplit: 'è´§è¿/模æ¿_è§ä¸è§ä¸æåè¿è¾é.xlsx', |
| | | freightTurnover: 'è´§è¿/模æ¿_è´§è¿éå¨è½¬é.xlsx', |
| | | passengerMonthly: 'å
¬è·¯æ
客/模æ¿_交ä¼ç»H203-1表_å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsx', |
| | | passengerAuth: 'å
¬è·¯æ
客/模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsx', |
| | | passengerAuth: 'å
¬è·¯æ
客/模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xls', |
| | | passengerIndividual: 'å
¬è·¯æ
客/个ä½å®¢è¿éæ±æ».xlsx', |
| | | energyMonthly: 'è½è/模æ¿_é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026ï¼.xlsx', |
| | | energyAuth: 'è½è/模æ¿_è½è车è¾è¿æ¿ä¿¡æ¯.xlsx', |
| | |
| | | this.$message.error('æ°æ®æ¥è¯¢å¤±è´¥') |
| | | }).finally(() => { |
| | | this.loading = false |
| | | this.$nextTick(() => this.attachBodyScroll()) |
| | | }) |
| | | }, |
| | | // el-table åºå®é«åº¦åï¼çæ£çç«åæ»å¨å®¹å¨æ¯å
é¨ç body-wrapperï¼é¡µé¢æ¬èº«ä¸æ»å¨ï¼ |
| | | attachBodyScroll() { |
| | | const wrap = this.$refs.tableWrap |
| | | if (!wrap) return |
| | | const bw = wrap.querySelector('.el-table__body-wrapper') |
| | | if (bw && bw._vwScrollHandler !== this.onScroll) { |
| | | if (bw._vwScrollHandler) bw.removeEventListener('scroll', bw._vwScrollHandler) |
| | | bw.addEventListener('scroll', this.onScroll, { passive: true }) |
| | | bw._vwScrollHandler = this.onScroll |
| | | } |
| | | }, |
| | | // æ»å¨å°åºé¨èªå¨è¿½å ä¸ä¸é¡µï¼ä¿çæå¨å页跳页è½åï¼ |
| | | loadMore() { |
| | |
| | | this.$message.error('å è½½æ´å¤å¤±è´¥') |
| | | }).finally(() => { |
| | | this.loadingMore = false |
| | | this.$nextTick(() => this.attachBodyScroll()) |
| | | }) |
| | | }, |
| | | onScroll(e) { |
| | |
| | | // capture=trueï¼el-table å
é¨ body-wrapper çæ»å¨å¨æè·é¶æ®µä¹è½æ¶å° |
| | | this.$refs.tableWrap.addEventListener('scroll', this.onScroll, { passive: true, capture: true }) |
| | | } |
| | | this.$nextTick(() => this.attachBodyScroll()) |
| | | }, |
| | | beforeDestroy() { |
| | | window.removeEventListener('scroll', this.onScroll) |
| | | if (this.$refs.tableWrap) { |
| | | this.$refs.tableWrap.removeEventListener('scroll', this.onScroll, { capture: true }) |
| | | const bw = this.$refs.tableWrap.querySelector('.el-table__body-wrapper') |
| | | if (bw && bw._vwScrollHandler) bw.removeEventListener('scroll', bw._vwScrollHandler) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | #app .table-wrap { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: calc(100vh - 330px); |
| | | min-height: 300px; |
| | | } |
| | | #app .table-area { |
| | | flex: 1 1 auto; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | #app .table-area .el-table { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | #app .table-wrap .el-pagination { |
| | | flex: none; |
| | | margin-top: 8px; |
| | | } |
| | | #app .table-area:fullscreen { |
| | | padding: 20px; |
| | | background: #fff; |
| | |
| | | overflow: auto; |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | } |
| | | #app .table-wrap:fullscreen .table-area { |
| | | flex: 1 1 auto; |
| | |
| | | noteDraft: '', |
| | | modules: [ |
| | | { |
| | | key: 'summary', title: 'æ±æ»å¤§è¡¨', items: [ |
| | | { key: 'summaryWorkbook', label: 'éè·¯è¿è¾éæ±æ»è¡¨ï¼æ´æ¬ï¼', fileName: 'çæ_éè·¯è¿è¾éæ±æ»è¡¨.xlsx', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | }, |
| | | { |
| | | key: 'freight', title: 'è´§è¿æ¥è¡¨', items: [ |
| | | { key: 'cityDetail', label: 'è´§è¿éåå¸å·æç»', fileName: 'çæ_è´§è¿éåå¸å·æç».xlsx', status: null, statusNote: '' }, |
| | | { key: 'freightRank', label: 'è´§è¿éæå', fileName: 'çæ_è´§è¿éæå.xlsx', status: null, statusNote: '' }, |
| | |
| | | { key: 'passengerMidAnalysis', label: 'ä¸å£å¾åæ', fileName: 'çæ_å
¬è·¯æ
客ä¸å£å¾åæ.xlsx', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | }, { |
| | | key: 'cityPassenger', title: 'åå¸å®¢è¿æ¥è¡¨', tag: 'å
¬äº¤/åºç§/轨é轮渡/æ±æ»', tagType: 'success', items: [ |
| | | key: 'cityPassenger', title: 'åå¸å®¢è¿æ¥è¡¨', items: [ |
| | | { key: 'cityBusDetail', label: 'åå¸å
¬äº¤å®¢è¿éåå¸å·æç»', fileName: 'çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsx', status: null, statusNote: '' }, |
| | | { key: 'cityTaxiDetail', label: '巡游åºç§å®¢è¿éåå¸å·æç»', fileName: 'çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsx', status: null, statusNote: '' }, |
| | | { key: 'cityRailFerryDetail', label: '轨é轮渡客è¿éåå¸å·æç»', fileName: 'çæ_轨é轮渡客è¿éåå¸å·æç».xlsx', status: null, statusNote: '' }, |
| | | { key: 'cityPassengerCitySum', label: 'åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡', fileName: 'çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsx', status: null, statusNote: '' }, |
| | | { key: 'cityPassengerDetailSum', label: 'åå¸å®¢è¿åå¸å·æç»è¡¨', fileName: 'çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx', status: null, statusNote: '' }, |
| | | { key: 'cityPassengerSummary', label: 'åå¸å®¢è¿æ±æ»', fileName: 'çæ_åå¸å®¢è¿æ±æ».xlsx', status: null, statusNote: '' }, |
| | | { key: 'wycSplit', label: 'ç½çº¦è½¦å®¢è¿éæå表', fileName: 'çæ_ç½çº¦è½¦å®¢è¿éæå表.xlsx', status: null, statusNote: '' }, |
| | | { key: 'summaryWorkbook', label: 'éè·¯è¿è¾éæ±æ»è¡¨ï¼æ´æ¬ï¼', fileName: 'çæ_éè·¯è¿è¾éæ±æ»è¡¨.xlsx', status: null, statusNote: '' } |
| | | { key: 'wycSplit', label: 'ç½çº¦è½¦å®¢è¿éæå表', fileName: 'çæ_ç½çº¦è½¦å®¢è¿éæå表.xlsx', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | }, |
| | | { |
| | |
| | | // æ¥è¡¨æä¸æåç»ï¼å¹´æ¥æå¹´ä»½ï¼ææ¥/壿¥æå¹´åç»å 1-12 æï¼è½èåªåå£åº¦æ«æï¼ |
| | | periodGroups() { |
| | | const year = new Date().getFullYear() |
| | | const years = [year - 1, year, year + 1] |
| | | const years = [year, year - 1, year + 1] |
| | | if (this.cycle === 'year') { |
| | | return [{ label: '鿩年份', options: years.map(y => ({ label: y + '年', value: String(y) })) }] |
| | | } |