Merge partner-work: 合并双方开发结果(我方验证码/登出/布局 + 对方业务模块)
148个文件已添加
30个文件已修改
27个文件已删除
6 文件已重命名
| | |
| | | .DS_Store |
| | | Thumbs.db |
| | | desktop.ini |
| | | |
| | | # ==== ??/?????????==== |
| | | _shots/ |
| | | *.nb3 |
| | | *.dmp |
| | | backend.log* |
| | | energy-test.log |
| | | diag*.log |
| | | _*.py |
| | | _*.txt |
| | | _*.xlsx |
| | | _*.png |
| | | _*.json |
| | | _test_check*/ |
| | | _test_passenger/ |
| | | check_*.py |
| | | cmp*.py |
| | | dump_*.py |
| | | fix_*.py |
| | | inspect_*.py |
| | | patch_*.py |
| | | verify_*.py |
| | | write_readme.py |
| | | clear-bom.ps1 |
| | | verify_city*.txt |
| | |
| | | > **ç¨æ³**ï¼æ°å¯¹è¯å¼å§åï¼å
è¯»æ¬æä»¶ + é¡¹ç®æ ¹ç®å½ `AGENTS.md`ï¼å³å¯æ ç¼æ¥åã |
| | | > **æ¯å¤©ç»ææ¶**ï¼æå½å¤©è¿å±ã踩è¿çåãæ°éæ±ãæªå®æäºé¡¹æ´æ°å°æ¬æä»¶ï¼ç¶åå¯ä»¥æ¾å¿å¼æ°å¯¹è¯ã |
| | | |
| | | ## ä¸ãå½åç¶æï¼2026-08-27ï¼ |
| | | - åå端**è¿è¡ä¸**ï¼8090/8080 å·²çå¬ï¼start-dev.ps1 å¯å¨ï¼ï¼ç»å½é¡µå·²æ¥å
¥çå®è®¤è¯ + è¡ä¸ºéªè¯ç ã |
| | | - 08-27 è¡ä¸ºéªè¯ç æ¹å¨**æªæäº¤**ï¼å·¥ä½åºææ¹å¨ï¼è§ç¬¬åèï¼ï¼æ¤åç代ç å·²å
¥åº Gitee ç§æä»åºï¼è§ç¬¬ä¹èï¼ã |
| | | ## ä¸ãå½åç¶æï¼2026-08-27 åå¹¶åï¼ |
| | | - åå端**è¿è¡ä¸**ï¼8090/8080 å·²çå¬ï¼start-dev.ps1 å¯å¨ï¼ï¼ç»å½é¡µå·²æ¥å
¥çå®è®¤è¯ï¼æ°æ®åºæ ¡éªï¼+ è¡ä¸ºéªè¯ç ã |
| | | - **2026-08-27 宿忹å¼åç»æåå¹¶**ï¼ä»¥å¯¹æ¹ï¼éçæ´ï¼å¦ä¸å¼å人åï¼ç代ç 为åºç¡ï¼å¹¶å
¥ææ¹ä¸æ ·æ¹å¨ |
| | | ï¼æå¿å¾/AIå©æææ³è®°å½ãç»å½è¡ä¸ºéªè¯ç ãç»åºä¸ä¸ä¸å¸å±ï¼ãéªè¯ç éç¨**ææ¹å®ç°**ï¼Captcha3D + å端 captcha å
ï¼ |
| | | å
ååå¨ï¼æ Redis ä¾èµï¼ï¼å¯¹æ¹ç¬ç«å®ç°ç ShapeCaptchaï¼Canvas+Redisï¼å·²å¼ç¨ãä¸å
¥åºã |
| | | - æ° git ä»åºè¿ç§»è¿è¡ä¸ï¼æ§ Gitee ä»åºå¼ç¨ï¼åå¹¶ç»æå°æäº¤å°æ°ä»åºï¼å°åè§ç¬¬ä¹èï¼å¾
ç¨æ·æä¾åè¡¥å
¨ï¼ã |
| | | - æ°æ®åºå·²è¡¥é½å¯¹æ¹æ°å¢è¡¨ï¼MySQL 8ï¼åº traffic_auditï¼æ¬æº 3305ï¼ï¼h2031_enterprise_monthlyãpassenger_*ã |
| | | h204_*ãinvestment_*ãcity_bus_monthlyãcity_taxi_monthlyãcity_taxi_authãpassenger_individual_monthlyãaudit_runï¼ |
| | | sys_user å·²å modules åï¼admin å¯ç å·²é置为 BCryptï¼admin/admin123ï¼ã |
| | | - æ å·²ç¥é»å¡æ§é®é¢ã |
| | | |
| | | ## äºãå¿«éå¯å¨ |
| | |
| | | - å端ï¼Spring Boot 2.x + MyBatis Plus + EasyExcelï¼`traffic-audit-server`ï¼ç«¯å£ 8090ã |
| | | - æ°æ®åºï¼MySQLï¼æ¬æº 3305 端å£ï¼åºå `traffic_audit`ï¼è´¦å·å¯ç è§ `traffic-audit-server/src/main/resources/application.yml`ï¼ã |
| | | - LLMï¼DeepSeek APIï¼`deepseek-chat`ï¼é
ç½®åæ ·å¨ application.ymlï¼æ³¨æä¸è¦æ API Key åè¿ä»»ä½ææ¡£ï¼ã |
| | | - æ ¸å¿æ¨¡åï¼æ°æ®å¯¼å
¥ï¼5ç±»ï¼ãå®¡æ ¸å¼æï¼è§å驱å¨ï¼ãAI åæï¼è±æ+æ¥åº+è§£éçæï¼ãæ¥è¡¨çæï¼3ç±»ï¼ãç¨æ·æéï¼ä¸åå离ï¼ã |
| | | - æ ¸å¿æ¨¡åï¼æ°æ®å¯¼å
¥ï¼è´§è¿/æ
客/åå¸å®¢è¿/æèµ/è½èå¤ç±»ï¼ãå®¡æ ¸å¼æï¼è§å驱å¨ï¼ãAI åæï¼è±æ+æ¥åº+è§£éçæï¼ã |
| | | æ¥è¡¨çæï¼è´§è¿/æ
客/åå¸å®¢è¿/æèµ/è½è Tab å·¥ä½å°ï¼ãç¨æ·æéï¼ä¸åå离ï¼ãæä½æ¥å¿ã |
| | | - 对æ¹ï¼éçæ´ï¼08-26/08-27 ç宿´å¼åè®°å½è§ `docs/åäºå¼åè®°å½-20260827.md`ï¼å«æ¥è¡¨ Tab å·¥ä½å°ãç®å½å¯¼å
¥ã |
| | | è§£éæéå±å¼ã卿æ¥è¡¨æ é¢ãåå¸å®¢è¿/å
¬è·¯æ
客/æèµ/è½è模åçç»èä¸è¸©åï¼ã |
| | | |
| | | ## åã已宿åè½ï¼æªè³ 08-07ï¼ |
| | | - æ°æ®å¯¼å
¥ï¼H203-2ææ¥ãè¿æ¿è½¦è¾ã轨迹éç¨ãè§ä¸è§ä¸æåãè´§è¿éå¨è½¬éï¼å¯¼å
¥å¤±è´¥æ¾ç¤ºè¡å·ã |
| | |
| | | 1. æ°å¯¹è¯ç¬¬ä¸æ¥ï¼è¯» `AGENTS.md` + `HANDOFF.md`ï¼æ¶å AI åæ/å®¡æ ¸æ¶åè `docs/` 䏿°æ®åºè¡¨ç»æã |
| | | 2. æ¯å¤©ç»ææ¶ï¼æ´æ° `HANDOFF.md`ï¼è¿å±ãåãæ°éæ±ãå¾
åï¼ï¼åå
³å¯¹è¯ã |
| | | 3. 跨项ç®å¯å¤ç¨ç»éªæ²æ·å°æºå¨çº§é¡¶å± AGENTS.mdï¼æ¬é¡¹ç®ä¸åéå¤ï¼ã |
| | | ## ä¹ãGit ä»åºï¼2026-08-14 建ç«ï¼ |
| | | - è¿ç¨ï¼Gitee **ç§æ**ä»åº https://gitee.com/jichengxiong/trafficAudit ï¼åæ¯ `main`ï¼æäº¤èº«ä»½ xyc <jc_xiong@hotmail.com>ï¼ã |
| | | ## ä¹ãGit ä»åºï¼2026-08-27 èµ·è¿ç§»å°æ°ä»åºï¼ |
| | | - **æ§ä»åºï¼å¼ç¨ï¼**ï¼Gitee https://gitee.com/jichengxiong/trafficAuditï¼2026-08-14 建ç«ï¼ââä¸å使ç¨ã |
| | | - **æ°ä»åº**ï¼å°åå¾
è¡¥ï¼ç¨æ·æä¾è´¦å·/ä»åºååå¡«åï¼ãåå¹¶åç main 忝已å
å«ï¼ååå² + ææ¹ 08-27 æäº¤ + 对æ¹å¼åç»æåæ¯ + åå¹¶æäº¤ã |
| | | - å¯¹æ¹æªä½¿ç¨ gitï¼å
¶å¼åç»æä»¥ç¬ç«åæ¯ï¼partner-workï¼åå
¥ï¼åå¹¶æäº¤ `Merge partner-work`ã |
| | | - ææé
ç½®ä¸å
¥åºï¼`traffic-audit-server/src/main/resources/application.yml` å·²å¨ `.gitignore` ä¸ï¼ |
| | | ä»åºå
åªæäº¤å ä½ç¬¦æ¨¡æ¿ `application.yml.example`ï¼`${DEEPSEEK_API_KEY}` çï¼ãæ°ç¯å¢ï¼å¤å¶æ¨¡æ¿ä¸º application.yml å¹¶å¡«çå®å¼ã |
| | | - æ¥å¸¸æµç¨ï¼`git add -A` â `git commit -m "..."` â `git push`ï¼åæ®å·²å Windows åæ®ç®¡çå¨ï¼æ¬æº push æ éåè¾ä»¤çï¼æ¢æºå¨ééæ°æä¾ä»¤çï¼ã |
| | | - 注æï¼`backend.log`ã`frontend.log`ã`node_modules/`ã`dist/`ã`target/` çä¸å
¥åºã |
| | | - æ¥å¸¸æµç¨ï¼`git add -A` â `git commit -m "..."` â `git push`ï¼åæ®ææ°ä»åºè¦æ±é
ç½®ï¼ã |
| | | - 注æï¼`backend.log`ã`frontend.log`ã`node_modules/`ã`dist/`ã`target/` çä¸å
¥åºï¼`docs/sql_city_bus.sql`ã |
| | | `docs/sql_city_taxi.sql` å·²å
¥åºï¼å»ºè¡¨+å®¡æ ¸è§åï¼MySQL 8 å¯ç¨ï¼ã |
| | | |
| | | ## åãä¼è¯è®°å½ |
| | | - 2026-08-14ï¼å¯å¨åå端éªè¯æ£å¸¸ï¼åå已忢ï¼ï¼å»ºç« git ä»åºï¼æ°å¢ `.gitignore`ãè±ææ¨¡æ¿ `application.yml.example`ï¼çå® `application.yml` ä¸å
¥åºï¼ï¼å建 Gitee ç§æä»åº `jichengxiong/trafficAudit` å¹¶æ¨é `main`ï¼2 commitsï¼åå§æäº¤ + HANDOFF è¡¥å
git 说æï¼ã |
| | |
| | | - 2026-08-27ï¼ç»å½åè½æ¹é 宿ââè¡ä¸ºéªè¯ç ï¼ææç¤ºé¡ºåºç¹å»ç«ä½å¾å½¢ï¼ãå端 security/captcha/CaptchaService.java ç´æ¥çæçè½´æµå ä½é¢ï¼ååºä¸å«å¾å½¢ç±»ååï¼çæ¡åªå¨æå¡ç«¯ï¼ï¼/api/auth/captcha è·åã/captcha/verify æ ¡éªï¼é 3 次æ¢é¢ï¼ã/login æ¶è´¹ï¼ä¸æ¬¡æ§ï¼æªéªè¯ 4003/å·²è¿æ 4002/顺åºé 4001ï¼ï¼å端 Captcha3D.vue SVG 渲æï¼èæ¯éæºè²+6 ç§å¾æ¡ã8 ç§ç«ä½å¾å½¢éæºä½ç½®/大å°/æè½¬/é¢è²ï¼ï¼Login.vue æ¥å
¥çå®ç»å½ï¼axios+token+localStorageï¼ï¼router å ç»å½å®å«ã宿µéè¿ï¼æ£ç¡®é¡ºåºéªè¯æåãéè¯¯é¡ºåºæç¤ºéè¯ãæªéªè¯æ¦æªãéå¯ç 401ãç»å½è·³è½¬å·¥ä½å°ãåï¼Vue2 ç»ä»¶å大å°åææï¼ç»ä»¶æ³¨å Captcha3D æ¶æ¨¡æ¿å¿
é¡»å <captcha3-d>ï¼<captcha3d> ä¼è¢«è§£ææ Captcha3d å¹é
ä¸ä¸ï¼ã |
| | | - 2026-08-27ï¼ç»ï¼ï¼ç»å½é¡µè§è§ä¸ç»åºââéªè¯ç ç»å¸ 340x190 缩为 340x148ãå¾å½¢ size 0.8~1.2 缩为 0.6~0.95ãé´è·/æå¨æ¶ç´§ãæè½¬ ±14°ï¼ç»å½å¡ç 420â400 宽ãform é´è·æ¶ç´§ï¼å¡çé«çº¦ 559pxï¼ãç«æ¹ä½/é¿æ¹ä½æ¹ä¸ºæäºæµæç§ä¹¦ç»æ³ï¼å颿£æ¹å½¢+é¡¶/ä¾§æåºï¼æ´ç«ä½ï¼ãæ°å¢å
Œ
± components/AppHeader.vueï¼æ é¢+å½åç¨æ·+éåºç»å½ï¼éåºæ¸
token åç»å½é¡µï¼ï¼æ¿æ¢ 6 个ä¸å¡é¡µåæ el-headerãåï¼æ¹éæå
¥ components æ¶ ExplainReview åæ components: { AiChatDialog } 被è¦çï¼éå¤é®ï¼ï¼éå并为 { AppHeader, AiChatDialog }ãå¦ï¼PowerShell åå¼å·å符串éçåå¼å·ä¼è¢«åæï¼å markdown ä»£ç æ è®°è¦é¿å
ã |
| | | - 2026-08-27ï¼ç»2ï¼ï¼å
æ£±æ±æ¹ä¸ºæ åçè½´æµæ£å
边形顶é¢+3 ä¾§é¢ï¼æ¤åé¡¶é¢è¿æè®¤ä¸åºï¼ï¼çæ¹ä¸ºå¾åæ¸åï¼äº®âæï¼å端渲æå¨æ°å¢ radial æ¸åæ¯æï¼gradient id ç¨ shape id å»éï¼ãä¿®å¸å±åå½ï¼AppHeader æ¯èªå®ä¹ç»ä»¶ï¼el-container æ£æµä¸å° el-header åç»ä»¶å¯¼è´å¤å±å®¹å¨åææ¨ªåï¼ç³»ç»åç§°+ç»åºåªå 左侧 435pxï¼ââ6 个ä¸å¡é¡µå¤å±å®¹å¨æ¾å¼å direction="vertical" ä¿®å¤ï¼ä¸ï¼ç³»ç»æ å·¦æ é¢å³ç¨æ·+ç»åºï¼ä¸ï¼å·¦èåå³å·¥ä½åºï¼ãåï¼radialFaces æ¹æ³è¯¯æ¾è¿ computedï¼Vue computed 䏿¥ååæ°ï¼s 为 undefined æ¥éï¼ï¼é¡»æ¾ methodsï¼vue-loader ç¼è¯ç¼å/æµè§å¨å¯åå¼ç¼åï¼index.html æ ç¼å头ï¼å¯¼è´æ¹ä»£ç å页é¢ä»å è½½æ§ chunkââvue.config.js devServer.headers å Cache-Control: no-storeï¼æµè¯æ¶ç¨æªè®¿é®è¿ç host:portã |
| | | |
| | | - 2026-08-27ï¼ç»3ï¼åæ¹åå¹¶ï¼ï¼åå¹¶æµç¨âââ ææ¹æ¹å¨å
æäº¤ï¼éªè¯ç +ç»åº+å¸å±ï¼commit åç¼ feat:ï¼ï¼â¡ ä» 08-23 åºçº¿ |
| | | 建 `partner-work` 忝ï¼éå对æ¹ç®å½ `D:\03_å
¬å¸\02_项ç®\27_æ¹åçéè·¯è¿è¾äºä¸åå±ä¸å¿\05_å¼åæµè¯\00_å¼å\traffic-audit-project-20260827` |
| | | ï¼æé¤ .git/dist/æ¥å¿/对æ¹éªè¯ç ç¸å
³æä»¶ï¼ShapeCaptcha.vueãsecurity/captcha/*ãCAPTCHA.mdãcp.txtãt_test.jsï¼ï¼æäº¤ä¸ºå¯¹æ¹å¼åç»æï¼ |
| | | ⢠`git merge partner-work` è§£å³å²çªï¼LoginController=ææ¹éªè¯ç +å¯¹æ¹æ°æ®åºæ ¡éªï¼UserMapper+PasswordEncoder+modules/realNameï¼ï¼ |
| | | Login.vue=ææ¹ Captcha3D çï¼è¡¥å realName/modulesï¼ï¼router å®å«ç¨ææ¹çæ¬ï¼6 个ä¸å¡é¡µ=对æ¹åè½ç+ææ¹ AppHeader ä¸ä¸å¸å± |
| | | ï¼æ ¹å®¹å¨ direction="vertical"ãel-header æ¢ app-headerãå èåå
éåºç»å½ã注å AppHeader ç»ä»¶ï¼ï¼HANDOFF.md éå为åå¹¶çã |
| | | ⣠pom.xml 廿坹æ¹å ç spring-boot-starter-data-redisï¼éªè¯ç ç¨ææ¹å
åçï¼æ é Redisï¼ï¼application.yml æ¬å°åå¹¶ |
| | | ï¼ä¿çææ¹ 3305 æ°æ®åºåæ°ï¼å multipart 100MBãdocs 模æ¿ç®å½é
ç½®ï¼example 模æ¿åæ¥ï¼ãâ¤ æ°æ®åºè¡¥é½ï¼è§ç¬¬ä¸èï¼ï¼init.sql |
| | | è¡¥ sys_user.modules åãä¿®æ£ admin å¯ç 为çå® BCryptã追å passenger_individual_monthly ä¸ audit_run 表ï¼åå¸å®¢è¿ä¸¤ä¸ª |
| | | sql çè§å INSERT æ¹å为 INSERT IGNORE VALUESï¼å
¼å®¹ MySQL 5.7/8.0ï¼åæ´¾çè¡¨åæ³å¨ 8.0.29 æ¥ Unknown column t.rule_codeï¼ã |
| | | ⥠éªè¯ï¼å端 mvn compile â
ãå端 npm run build â
ãåï¼é¾æ¥å·¥ä½åº .git æ¯æä»¶ï¼robocopy /MIR 伿å®å æå¯¼è´"not a git |
| | | repository"ï¼éé建 gitdir æä»¶ï¼PowerShell ç -e "..." å符串é $ ä¼è¢«å½åéå±å¼ï¼å SQL å¯ç åå¸è¦ç¨ SQL æä»¶ã |
| | |
| | | # 交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - Demo 说æ |
| | | |
| | | > Demo èå´ï¼äº¤ä¼ç»H203-2表ï¼é路货ç©è¿è¾æåº¦ç产æ
åµï¼ |
| | | > å·²å®ç°ï¼äº¤ä¼ç»H203-2ï¼é路货ç©è¿è¾æåº¦ç产æ
åµï¼+ H203-1ï¼å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµï¼ |
| | | > éæ±ææ¡£ï¼`è´§è¿ç®å.docx`ã`database.md`ã`architecture.md`ï¼æ°æ®åºèæ¬ï¼`init.sql` |
| | | |
| | | ## ä¸ãç¯å¢è¦æ± |
| | |
| | | | JDK | 1.8 | |
| | | | Maven | 3.x | |
| | | | Node.js | 14+ï¼Vue CLIï¼ | |
| | | | MySQL | 5.7ï¼æ¬æº `localhost:3305`ï¼è´¦å· `root/root`ï¼ | |
| | | | MySQL | 8.0ï¼æ¬æº `localhost:3308`ï¼è´¦å· `root/123456`ï¼æå¡ MySQL80ï¼ | |
| | | |
| | | ## äºãåå§åæ°æ®åº |
| | | |
| | | ```bash |
| | | mysql --default-character-set=utf8mb4 -h localhost -P 3305 -u root -proot < docs/init.sql |
| | | mysql --default-character-set=utf8mb4 -h localhost -P 3308 -u root -p123456 < docs/init.sql |
| | | ``` |
| | | |
| | | `init.sql` ä¼é建 `traffic_audit` åºï¼å«æ§ç表æ¸
çï¼ï¼å¹¶é¢ç½®ï¼ |
| | |
| | | | import_batch_id | bigint | 导å
¥æ¹æ¬¡ | |
| | | | created_at | datetime | å建æ¶é´ | |
| | | |
| | | |
| | | ### h2031_enterprise_monthlyï¼äº¤ä¼ç»H203-1表 å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµï¼ |
| | | > æ°æ®æ¥æºï¼å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsx çãå
¬è·¯æ
客6ææ¥ãsheetï¼åæä»¶ãè¿æ¿è½¦è¾æ°ãsheet 导å
¥ passenger_auth_vehicle |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | report_period | varchar(20) | æ¥è¡¨æ 2026-06 | |
| | | | region_code | varchar(20) | æå±å°åºï¼è¡æ¿åºå代ç ï¼ | |
| | | | enterprise_code | varchar(50) | ä¼ä¸ä»£ç | |
| | | | enterprise_name | varchar(200) | ä¼ä¸åç§° | |
| | | | vehicle_total | int | 客è¿è½¦è¾_æ»è®¡ | |
| | | | seat_total | int | æ ¸å®è½½å®¢ä½_æ»è®¡ | |
| | | | vehicle_schedule | int | è½¦è¾æ°_客è¿ç车ï¼å«å®çº¿æ
æ¸¸ï¼ | |
| | | | seat_schedule | int | æ ¸å®è½½å®¢ä½_客è¿ç车ï¼å«å®çº¿æ
æ¸¸ï¼ | |
| | | | vehicle_charter | int | è½¦è¾æ°_客è¿å
车ï¼å«éå®çº¿æ
æ¸¸ï¼ | |
| | | | seat_charter | int | æ ¸å®è½½å®¢ä½_客è¿å
车ï¼å«éå®çº¿æ
æ¸¸ï¼ | |
| | | | passenger_total | double | 客è¿é_æ»è®¡ï¼äººï¼ | |
| | | | turnover_total | double | æ
客å¨è½¬é_æ»è®¡ï¼äººå
¬éï¼ | |
| | | | passenger_class1 ~ passenger_class4 | double | 客è¿é_ä¸~å类客è¿ç线ï¼å«å®çº¿æ
æ¸¸ï¼ | |
| | | | turnover_class1 ~ turnover_class4 | double | æ
客å¨è½¬é_ä¸~å类客è¿ç线ï¼å«å®çº¿æ
æ¸¸ï¼ | |
| | | | passenger_charter | double | 客è¿é_客è¿å
车ï¼å«éå®çº¿æ
æ¸¸ï¼ | |
| | | | turnover_charter | double | æ
客å¨è½¬é_客è¿å
车ï¼å«éå®çº¿æ
æ¸¸ï¼ | |
| | | | avg_distance_total | double | 客è¿å¹³åè¿è·ï¼å
¬éï¼ | |
| | | | avg_distance_class1 ~ avg_distance_class4 | double | ä¸~å类客è¿ç线平åè¿è·ï¼å
¬éï¼ | |
| | | | avg_distance_charter | double | æ
游ãå
车平åè¿è·ï¼å
¬éï¼ | |
| | | | verify_explanation | text | æ ¸å®æ§åå è§£é | |
| | | | import_batch_id | bigint | 导å
¥æ¹æ¬¡ | |
| | | | created_at | datetime | å建æ¶é´ | |
| | | |
| | | ### passenger_auth_vehicleï¼å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°æ®ï¼ |
| | | > æ°æ®æ¥æºï¼å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsx çãè¿æ¿è½¦è¾æ°ãsheetï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼ï¼éææ¥ä¸èµ·ææ¥è¡¨æå¯¼å
¥ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | report_period | varchar(20) | æ¥è¡¨æ | |
| | | | enterprise_code | varchar(50) | ä¼ä¸ä»£ç | |
| | | | enterprise_name | varchar(200) | ä¼ä¸åç§° | |
| | | | vehicle_count | int | è½¦è¾æ° | |
| | | | seat_count | int | è½½å®¢ä½æ° | |
| | | | import_batch_id | bigint | 导å
¥æ¹æ¬¡ | |
| | | | created_at | datetime | å建æ¶é´ | |
| | | |
| | | --- |
| | | |
| | | ### h204_vehicle_quarterlyï¼äº¤ä¼ç»H204表 é路货è¿è½¦è¾è½æºæ¶èæ
åµÂ·è½¦è¾ç²åº¦ï¼ |
| | | > æ°æ®æ¥æºï¼é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼.xlsx çã第äºå£åº¦è´§è½¦è½èæç»ãsheetï¼æ¯è¡å·¦å³å5ç»è½¦è¾ãæ¯ç»18åï¼ï¼åæä»¶ãè¿æ¿è½¦è¾ä¿¡æ¯ãsheet 导å
¥ h204_auth_vehicle |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | report_period | varchar(20) | æ¥è¡¨æ 2026-06ï¼å£åº¦æ«æï¼ | |
| | | | region_code | varchar(20) | æå±å°åºï¼è¡æ¿åºå代ç ï¼ | |
| | | | enterprise_code | varchar(50) | ä¼ä¸ä»£ç | |
| | | | enterprise_name | varchar(200) | ä¼ä¸åç§° | |
| | | | plate_no | varchar(30) | 车çå· | |
| | | | vehicle_type_code | varchar(10) | 车è¾ç±»å代ç | |
| | | | vehicle_type | varchar(50) | 车è¾ç±»åï¼ä¸æï¼ | |
| | | | fuel_type_code | varchar(10) | çæç±»å代ç | |
| | | | fuel_type | varchar(50) | çæç±»åï¼ä¸æï¼ | |
| | | | manufacture_year | int | åºå年份 | |
| | | | marked_tonnage | double | æ è®°å¨ä½ï¼å¨ï¼ | |
| | | | total_mileage | double | æ»è¡ç¨ï¼å
¬éï¼ | |
| | | | loaded_mileage | double | 载货éç¨ï¼å
¬éï¼ | |
| | | | empty_mileage | double | 空驶éç¨ï¼å
¬éï¼ | |
| | | | freight | double | è´§è¿éï¼å¨ï¼ | |
| | | | turnover | double | è´§ç©å¨è½¬éï¼å¨å
¬éï¼ | |
| | | | trip_count | int | è¶æ¬¡æ°ï¼ä¸ªï¼ | |
| | | | fuel_unit | varchar(20) | çæ1计éåä½ | |
| | | | fuel_consumption | double | çæ1æ¶èé | |
| | | | avg_distance | double | å¹³åè¿è·ï¼å
¬éï¼ | |
| | | | avg_loaded_tonnage | double | å¹³å载货å¨ä½ï¼å¨ï¼ | |
| | | | avg_trip_mileage | double | åè¶å¹³åéç¨ï¼å
¬éï¼ | |
| | | | fuel_per_100km | double | ç¾å
¬éåè | |
| | | | turnover_fuel | double | å¨è½¬éåè | |
| | | | import_batch_id | bigint | 导å
¥æ¹æ¬¡ | |
| | | | created_at | datetime | å建æ¶é´ | |
| | | |
| | | ### h204_auth_vehicleï¼é路货è¿è½è_è¿æ¿è½¦è¾ä¿¡æ¯ï¼ |
| | | > æ°æ®æ¥æºï¼é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼.xlsx çãè¿æ¿è½¦è¾ä¿¡æ¯ãsheetï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼ï¼é壿¥ä¸èµ·ææ¥è¡¨æå¯¼å
¥ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | report_period | varchar(20) | æ¥è¡¨æ | |
| | | | plate_no | varchar(30) | 车çå· | |
| | | | vehicle_type | varchar(50) | 车è¾ç±»åï¼ä¸æï¼ | |
| | | | fuel_type | varchar(50) | çæç±»åï¼ä¸æï¼ | |
| | | | marked_tonnage | double | æ è®°å¨ä½ï¼å¨ï¼ | |
| | | | traction_quality | double | åçµå¼è´¨éï¼çµå¼è½¦æ è®°å¨ä½ï¼ | |
| | | | import_batch_id | bigint | 导å
¥æ¹æ¬¡ | |
| | | | created_at | datetime | å建æ¶é´ | |
| | | |
| | | ## ä¸ãå®¡æ ¸è§å模å |
| | | |
| | |
| | | | sort_order | int | æåº | |
| | | | created_at | datetime | - | |
| | | |
| | | Demoé¢ç½®6æ¡è§åï¼ |
| | | Demoé¢ç½®14æ¡è§åï¼report_type åºå H2032 è´§è¿ / H2031 æ
客ï¼ï¼ |
| | | |
| | | è´§è¿ H2032 6æ¡ï¼ |
| | | 1. 车è¾è¿æ¿å¯¹æ¯ï¼DIFF>5 â REDï¼ |
| | | 2. å¨ä½è¿æ¿å¯¹æ¯ï¼DIFF>50 â REDï¼ |
| | | 3. å¨è½¬é轨迹对æ¯ï¼RATIO>1.3 â REDï¼ |
| | | 4. è´§è¿ç±»åç¯æ¯ï¼MOM â YELLOWï¼ |
| | | 5. éè£
ç®±å¼å¸¸ï¼NULL_CHECK â REDï¼ |
| | | 6. æ´è½¦å¨ä½èå´ï¼OUT_RANGE ä¸å¨4-40ä¹é´ â YELLOWï¼ |
| | | |
| | | æ
客 H2031 8æ¡ï¼compare_type æ°å¢ MOM_EQ_ZERO/MOM_PCT/MOM_EQ/EXISTENCEï¼ï¼ |
| | | 1. è½¦è¾æ°è¿æ¿å¯¹æ¯ï¼DIFF>3 â REDï¼ |
| | | 2. è½½å®¢ä½æ°è¿æ¿å¯¹æ¯ï¼DIFF>50 â REDï¼ |
| | | 3. 客è¿éç¯æ¯=0ï¼ä¸~åç±»+å
车ï¼å¼ä¸ä¸æç¸å â YELLOWï¼ |
| | | 4. æ
客å¨è½¬éç¯æ¯=0ï¼åä¸ â YELLOWï¼ |
| | | 5. å¹³åè¿è·ç¯æ¯è¶
±10%ï¼ä¸~åç±» â YELLOWï¼ |
| | | 6. å
车平åè¿è·çäºä¸æï¼â YELLOWï¼ |
| | | 7. æ ç线æç线客è¿éï¼EXISTENCE â REDï¼ |
| | | 8. æ å
车æå
车客è¿éï¼EXISTENCE â REDï¼ |
| | | |
| | | ## åãå®¡æ ¸ç»ææ¨¡å |
| | | |
| | |
| | | |------|------|------| |
| | | | çæ_è´§è¿éåå¸å·æç».xlsx | freight_turnover_import + h2032_enterprise_monthly + scale_split_transport | 1-Nææç»+忝ï¼è´§è¿éå计=模æ¿ãè§ä¸=H2032ãè§ä¸=å计-è§ä¸ï¼å¨è½¬é=æå表 | |
| | | | çæ_è´§è¿éæå.xlsx | freight_turnover_import + h2032_enterprise_monthly | è§ä¸=H2032累计ãå计=模æ¿ç´¯è®¡ãè§ä¸=å计-è§ä¸ | |
| | | | çæ_å
¬è·¯æ
客åå¸å·æç».xlsx | h2031_enterprise_monthlyï¼æå¸å·æ±æ»ï¼ | ææ¥ï¼éå¹´-æï¼ä»å¹´1-Næ+累计ï¼/å¹´æ¥ï¼é年份ï¼å
¨å¹´12个æ+累计ï¼ï¼æ¯æå«å½å¹´+è¿3å¹´åºåãåæåæ¯ãç´¯è®¡åæ¯ï¼åé
å乡å
¬äº¤/巡游åºç§/ç½çº¦è½¦åæç空 | |
| | | | çæ_å¨è½¬éæå.xlsx | scale_split_transport | è§ä¸/è§ä¸/å计å¨è½¬éæåï¼å«å¿ å®å¯¼å
¥çå
¨çè¡ï¼ | |
| | | |
| | | ## å
ãç³»ç»æ¥å¿ |
| | |
| | | | masked_value | varchar(200) | è±æå¼ | |
| | | | mask_type | varchar(20) | ENTERPRISE/REGION | |
| | | | call_id | varchar(50) | è°ç¨ID | |
| | | |
| | | ## å
«ãæèµæ¨¡åï¼å®¢è¿ç«åº / ç©æµååºï¼ |
| | | |
| | | > æ°æ®æºï¼å
¨çæ±æ»å¤§è¡¨ï¼`7æå®¢è¿ç«æèµæç»è¡¨.xlsx` æç»sheetã`6æç©æµç«åºæèµæç».xls` åé¡¹ç®æèµå®ææ
åµsheetï¼ï¼å®¡æ ¸æ¯å¯¹åºå为æèµç³»ç»å¯¼åºï¼`æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7).xls`ï¼ã |
| | | |
| | | ### investment_projectï¼æèµé¡¹ç®ä¸»æ¡£ï¼ |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | project_name | varchar(200) | 项ç®åç§°ï¼ä¸ category ç»æå¯ä¸é®ï¼ | |
| | | | category | varchar(20) | 客è¿ç«åº / ç©æµååº | |
| | | | city | varchar(50) | å¸å· | |
| | | | county | varchar(100) | å¿ï¼å¸ãåºï¼ | |
| | | | builder_name | varchar(200) | æèµå»ºè®¾åä½ | |
| | | | construct_nature | varchar(50) | 建设æ§è´¨ï¼æ°å»º/ç»å»º/æ¹æ©å»ºï¼ | |
| | | | start_time / end_time | varchar(20) | å¼å·¥ / å»ºææ¶é´ | |
| | | | total_investment | double | è®¡åæ»æèµï¼ä¸å
ï¼ | |
| | | | approval_gk / approval_cs | varchar(500) | å·¥å¯ / å设æ¹å¤ | |
| | | | building_area | double | 建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼ç©æµè¡¨ï¼ | |
| | | | is_billion / is_county / is_five_year | tinyint | 亿å
/ ç»æµå¼ºå¿ / åäºäºéç¹ åºå®ååæ è®° | |
| | | |
| | | ### investment_monthlyï¼æèµé¡¹ç®æåº¦å®ææ
åµï¼ |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | report_period | varchar(20) | æ¥è¡¨æ 2026-07 | |
| | | | project_id | bigint | å
³è investment_project.id | |
| | | | year_plan | double | æ¬å¹´è®¡åæèµï¼ä¸å
ï¼ | |
| | | | year_cum | double | èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼ | |
| | | | month_done | double | å
¶ä¸ï¼æ¬æå®ææèµï¼ä¸å
ï¼ | |
| | | | start_cum | double | èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµï¼ä¸å
ï¼ | |
| | | | progress_stage | varchar(100) | å»ºè®¾é¶æ®µ | |
| | | | progress_desc | varchar(2000) | 形象è¿åº¦ | |
| | | | building_area | double | 建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼ | |
| | | |
| | | ### investment_systemï¼æèµç³»ç»å¯¼åºÂ·å®¡æ ¸æ¯å¯¹åºåï¼ |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | bigint | PK | |
| | | | report_period | varchar(20) | æ¥è¡¨æ | |
| | | | project_name | varchar(200) | 项ç®åç§°ï¼åä½ï¼ | |
| | | | total_investment | double | è®¡åæ»æèµï¼ä¸å
ï¼ | |
| | | | start_cum | double | èªå¼å§å»ºè®¾è³æ¬æç´¯è®¡å®ææèµï¼ä¸å
ï¼ | |
| | | | year_plan | double | æ¬å¹´è®¡åæèµï¼ä¸å
ï¼ | |
| | | | year_cum | double | èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼ | |
| | | | month_done | double | 彿宿æèµï¼ä¸å
ï¼ | |
| | | |
| | | ### æèµå®¡æ ¸è§åï¼report_type=INVESTï¼ |
| | | | è§å | æ¯è¾ç±»å | 说æ | |
| | | |------|---------|------| |
| | | | RULE_I001 èªå¼å§ç´¯è®¡å¯¹æ¯ | CROSS_DIFF | ç»å¯¹å·®>100ä¸ ä¸ ç¸å¯¹å·®>5% | |
| | | | RULE_I002 èªå¹´åç´¯è®¡å¯¹æ¯ | CROSS_DIFF | ç»å¯¹å·®>50ä¸ ä¸ ç¸å¯¹å·®>5% | |
| | | | RULE_I003 æ¬æå®æå¯¹æ¯ | CROSS_DIFF | ç»å¯¹å·®>30ä¸ ä¸ ç¸å¯¹å·®>30% | |
| | | | RULE_I004 ç³»ç»ç¼ºå¤± | EXISTENCE_SYS | æ±æ»å¤§è¡¨é¡¹ç®å¨æèµç³»ç»å¯¼åºä¸ä¸åå¨ | |
| | | |
| | | ### æèµ 4 å¼ é¢å¯¼æ¥è¡¨ï¼ReportExportServiceï¼ |
| | | | ç«¯ç¹ | 表å | æ°æ® | |
| | | |------|------|------| |
| | | | /api/report/export/investPlan | å
¨çé¢å®æè®¡åè¿åº¦æ±æ» | 客è¿+ç©æµå¤§åã17å¸å·å°è+项ç®è¡ | |
| | | | /api/report/export/investFiveYearLogistics | "åäºäº"ç©æµé¡¹ç®è¿å± | åºå®ååè¿æ»¤ï¼å¸å·å°è+项ç®è¡ | |
| | | | /api/report/export/investBillion | 亿å
æèµé¡¹ç® | æèµæ»è¡¨ + è§ä¸é¡¹ç®è¡¨ä¸¤sheetï¼äº¿å
ååè¿æ»¤ | |
| | | | /api/report/export/investCounty | ç»æµå¼ºå¿æèµç»è®¡ | ç»æµå¼ºå¿ååè¿æ»¤ | |
| | |
| | | username VARCHAR(50) NOT NULL UNIQUE, |
| | | password VARCHAR(200) NOT NULL COMMENT 'BCrypt', |
| | | real_name VARCHAR(50), |
| | | modules VARCHAR(255) DEFAULT NULL COMMENT 'è´è´£æ¥è¡¨æ¨¡åï¼éå·åéï¼', |
| | | phone VARCHAR(20), |
| | | dept_id BIGINT, |
| | | status TINYINT DEFAULT 1 COMMENT '0ç¦ç¨ 1å¯ç¨', |
| | |
| | | ('è½¦è¾æ°è¿æ¿å¯¹æ¯', 'RULE_001', 'ä¼ä¸ä¸æ¥è½¦è¾æ°ä¸è¿æ¿æ°æ®å¯¹æ¯ï¼å·®è·>5å°éæ ¸å®', 'vehicleTotal', 'DIFF', '5', 'RED', 'H2032', 1, 1), |
| | | ('å¨ä½è¿æ¿å¯¹æ¯', 'RULE_002', 'ä¼ä¸ä¸æ¥å¨ä½ä¸è¿æ¿æ°æ®å¯¹æ¯ï¼å·®è·>50éæ ¸å®', 'tonsTotal', 'DIFF', '50', 'RED', 'H2032', 1, 2), |
| | | ('å¨è½¬é轨迹对æ¯', 'RULE_003', '䏿¥å¨è½¬éä¸è½¨è¿¹æµç®å¨è½¬é对æ¯ï¼é«åº30%éæ ¸å®', 'turnoverTotal,trackMileage', 'RATIO', '1.3', 'RED', 'H2032', 1, 3), |
| | | ('è´§è¿ç±»åç¯æ¯', 'RULE_004', 'æ¬ææ°å¢è´§è¿ç±»åéæ ¸å®', 'freightContainer,freightCoal', 'MOM', '0', 'YELLOW', 'H2032', 1, 4), |
| | | ('è´§è¿ç±»åç¯æ¯', 'RULE_004', 'æ¬ææ°å¢è´§è¿ç±»åéæ ¸å®ï¼ä¸æä¸º0ãæ¬ææéï¼', 'freightContainer,freightCoal,freightOilGas,freightCrudeOil,freightMetalOre,freightIronOre,freightBuilding,freightGrain', 'MOM', '0', 'YELLOW', 'H2032', 1, 4), |
| | | ('éè£
ç®±å¼å¸¸', 'RULE_005', 'æ éè£
ç®±è½¦ä½æéè£
箱货è¿ééæ ¸å®', 'freightContainer,vehicleContainer', 'NULL_CHECK', '0', 'RED', 'H2032', 1, 5), |
| | | ('æ´è½¦å¨ä½èå´', 'RULE_006', 'æ´è½¦å¨ä½ä¸å¨4-40å¨ä¹é´', 'tonsWhole', 'OUT_RANGE', '4,40', 'YELLOW', 'H2032', 1, 6); |
| | | |
| | |
| | | |
| | | -- é¢ç½®ç®¡çåè´¦å· admin / admin123 |
| | | INSERT INTO sys_user (username, password, real_name, dept_id) VALUES |
| | | ('admin', '.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', 'ç³»ç»ç®¡çå', 1); |
| | | ('admin', '$2b$10$aqN02Pwvzzuta/66V9MpGerfUH6favtq8av3cc/gueg6lQ9E4CH8u', 'ç³»ç»ç®¡çå', 1); |
| | | |
| | | INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1); |
| | | |
| | | |
| | | -- ============================================ |
| | | -- å
«ãå
¬è·¯æ
客è¿è¾ï¼äº¤ä¼ç»H203-1ï¼æ©å±æ¨¡å |
| | | -- ============================================ |
| | | |
| | | DROP TABLE IF EXISTS h2031_enterprise_monthly; |
| | | CREATE TABLE h2031_enterprise_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åº', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | vehicle_total INT COMMENT '客è¿è½¦è¾_æ»è®¡', |
| | | seat_total INT COMMENT 'æ ¸å®è½½å®¢ä½_æ»è®¡', |
| | | vehicle_schedule INT COMMENT 'è½¦è¾æ°_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | seat_schedule INT COMMENT 'æ ¸å®è½½å®¢ä½_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | vehicle_charter INT COMMENT 'è½¦è¾æ°_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | seat_charter INT COMMENT 'æ ¸å®è½½å®¢ä½_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼', |
| | | passenger_total DOUBLE COMMENT '客è¿é_æ»è®¡ï¼äººï¼', |
| | | turnover_total DOUBLE COMMENT 'æ
客å¨è½¬é_æ»è®¡ï¼äººå
¬éï¼', |
| | | passenger_class1 DOUBLE COMMENT '客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class1 DOUBLE COMMENT 'æ
客å¨è½¬é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_class2 DOUBLE COMMENT '客è¿é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class2 DOUBLE COMMENT 'æ
客å¨è½¬é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_class3 DOUBLE COMMENT '客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class3 DOUBLE COMMENT 'æ
客å¨è½¬é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_class4 DOUBLE COMMENT '客è¿é_å类客è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | turnover_class4 DOUBLE COMMENT 'æ
客å¨è½¬é_å类客è¿ç线ï¼å«å®çº¿æ
游ï¼', |
| | | passenger_charter DOUBLE COMMENT '客è¿é_客è¿å
车ï¼å«éå®çº¿æ
游ï¼', |
| | | turnover_charter DOUBLE COMMENT 'æ
客å¨è½¬é_客è¿å
车ï¼å«éå®çº¿æ
游ï¼', |
| | | avg_distance_total DOUBLE COMMENT '客è¿å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class1 DOUBLE COMMENT 'ä¸ç±»å®¢è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class2 DOUBLE COMMENT 'äºç±»å®¢è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class3 DOUBLE COMMENT 'ä¸ç±»å®¢è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_class4 DOUBLE COMMENT 'å类客è¿ç线平åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_charter DOUBLE COMMENT 'æ
游ãå
车平åè¿è·ï¼å
¬éï¼', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交ä¼ç»H203-1表_å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ'; |
| | | |
| | | DROP TABLE IF EXISTS passenger_auth_vehicle; |
| | | CREATE TABLE passenger_auth_vehicle ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | vehicle_count INT DEFAULT 0 COMMENT 'è½¦è¾æ°', |
| | | seat_count INT DEFAULT 0 COMMENT 'è½½å®¢ä½æ°', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°æ®'; |
| | | |
| | | -- H203-1 é¢ç½®å®¡æ ¸è§åï¼4ç»8æ¡ï¼report_type=H2031ï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('è½¦è¾æ°è¿æ¿å¯¹æ¯(æ
客)', 'RULE_P001', '客è¿è½¦è¾æ°ä¸è¿æ¿è½¦è¾æ°å¯¹æ¯ï¼å·®è·>3å°éæ ¸å®', 'vehicleTotal', 'DIFF', '3', 'RED', 'H2031', 1, 21), |
| | | ('è½½å®¢ä½æ°è¿æ¿å¯¹æ¯(æ
客)', 'RULE_P002', 'æ ¸å®è½½å®¢ä½æ°ä¸è¿æ¿è½½å®¢ä½æ°å¯¹æ¯ï¼å·®è·>50éæ ¸å®', 'seatTotal', 'DIFF', '50', 'RED', 'H2031', 1, 22), |
| | | ('客è¿éç¯æ¯=0æ ¸å®', 'RULE_P003', 'ä¸ç±»/äºç±»/ä¸ç±»/åç±»/å
车客è¿éç¯æ¯ä¸ä¸ªæçäº0éæ ¸å®', 'passengerClass1,passengerClass2,passengerClass3,passengerClass4,passengerCharter', 'MOM_EQ_ZERO', '0', 'YELLOW', 'H2031', 1, 23), |
| | | ('æ
客å¨è½¬éç¯æ¯=0æ ¸å®', 'RULE_P004', 'ä¸ç±»/äºç±»/ä¸ç±»/åç±»/å
车æ
客å¨è½¬éç¯æ¯ä¸ä¸ªæçäº0éæ ¸å®', 'turnoverClass1,turnoverClass2,turnoverClass3,turnoverClass4,turnoverCharter', 'MOM_EQ_ZERO', '0', 'YELLOW', 'H2031', 1, 24), |
| | | ('å¹³åè¿è·ç¯æ¯è¶
±10%æ ¸å®', 'RULE_P005', 'ä¸ç±»/äºç±»/ä¸ç±»/å类平åè¿è·ç¯æ¯ä¸ä¸ªæè¶
è¿Â±10%éæ ¸å®', 'avgDistanceClass1,avgDistanceClass2,avgDistanceClass3,avgDistanceClass4', 'MOM_PCT', '10', 'YELLOW', 'H2031', 1, 25), |
| | | ('å
车平åè¿è·çäºä¸ææ ¸å®', 'RULE_P006', 'æ
游å
车平åè¿è·çäºä¸ä¸ªæéæ ¸å®', 'avgDistanceCharter', 'MOM_EQ', '0', 'YELLOW', 'H2031', 1, 26), |
| | | ('æ ç线æç线客è¿éæ ¸å®', 'RULE_P007', 'æ 客è¿ç车ï¼ççº¿è½¦è¾æ°ä¸º0ï¼ä½æç线客è¿ééæ ¸å®', 'vehicleSchedule,passengerClass1,passengerClass2,passengerClass3,passengerClass4', 'EXISTENCE', '0', 'RED', 'H2031', 1, 27), |
| | | ('æ å
车æå
车客è¿éæ ¸å®', 'RULE_P008', 'æ 客è¿å
车ï¼å
è½¦è½¦è¾æ°ä¸º0ï¼ä½æå
车客è¿ééæ ¸å®', 'vehicleCharter,passengerCharter', 'EXISTENCE', '0', 'RED', 'H2031', 1, 28); |
| | | |
| | | |
| | | -- ============================================ |
| | | -- ä¹ãé路货è¿è½æºæ¶èï¼äº¤ä¼ç»H204ï¼æ©å±æ¨¡å |
| | | -- ============================================ |
| | | |
| | | -- é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼è½¦è¾ç²åº¦å£æ¥ï¼ |
| | | DROP TABLE IF EXISTS h204_vehicle_quarterly; |
| | | CREATE TABLE h204_vehicle_quarterly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-06ï¼å£åº¦æ«æï¼', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åº', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | plate_no VARCHAR(30) COMMENT '车çå·', |
| | | vehicle_type_code VARCHAR(10) COMMENT '车è¾ç±»å代ç ', |
| | | vehicle_type VARCHAR(50) COMMENT '车è¾ç±»åï¼ä¸æï¼', |
| | | fuel_type_code VARCHAR(10) COMMENT 'çæç±»å代ç ', |
| | | fuel_type VARCHAR(50) COMMENT 'çæç±»åï¼ä¸æï¼', |
| | | manufacture_year INT COMMENT 'åºå年份', |
| | | marked_tonnage DOUBLE COMMENT 'æ è®°å¨ä½ï¼å¨ï¼', |
| | | total_mileage DOUBLE COMMENT 'æ»è¡ç¨ï¼å
¬éï¼', |
| | | loaded_mileage DOUBLE COMMENT '载货éç¨ï¼å
¬éï¼', |
| | | empty_mileage DOUBLE COMMENT '空驶éç¨ï¼å
¬éï¼', |
| | | freight DOUBLE COMMENT 'è´§è¿éï¼å¨ï¼', |
| | | turnover DOUBLE COMMENT 'è´§ç©å¨è½¬éï¼å¨å
¬éï¼', |
| | | trip_count INT COMMENT 'è¶æ¬¡æ°ï¼ä¸ªï¼', |
| | | fuel_unit VARCHAR(20) COMMENT 'çæ1计éåä½', |
| | | fuel_consumption DOUBLE COMMENT 'çæ1æ¶èé', |
| | | avg_distance DOUBLE COMMENT 'å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_loaded_tonnage DOUBLE COMMENT 'å¹³å载货å¨ä½ï¼å¨ï¼', |
| | | avg_trip_mileage DOUBLE COMMENT 'åè¶å¹³åéç¨ï¼å
¬éï¼', |
| | | fuel_per_100km DOUBLE COMMENT 'ç¾å
¬éåè', |
| | | turnover_fuel DOUBLE COMMENT 'å¨è½¬éåè', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period_plate (report_period, plate_no), |
| | | KEY idx_period_ent (report_period, enterprise_code) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交ä¼ç»H204表_é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼è½¦è¾ç²åº¦ï¼'; |
| | | |
| | | -- é路货è¿è½è_è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦çç²åº¦ï¼ |
| | | DROP TABLE IF EXISTS h204_auth_vehicle; |
| | | CREATE TABLE h204_auth_vehicle ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ', |
| | | plate_no VARCHAR(30) NOT NULL COMMENT '车çå·', |
| | | vehicle_type VARCHAR(50) COMMENT '车è¾ç±»å', |
| | | fuel_type VARCHAR(50) COMMENT 'çæç±»å', |
| | | marked_tonnage DOUBLE COMMENT 'æ è®°å¨ä½ï¼å¨ï¼', |
| | | traction_quality DOUBLE COMMENT 'åçµå¼è´¨éï¼çµå¼è½¦æ è®°å¨ä½ï¼', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period_plate (report_period, plate_no) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='é路货è¿è½è_è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦çç²åº¦ï¼'; |
| | | |
| | | -- H204 é¢ç½®å®¡æ ¸è§åï¼4æ¡ï¼report_type=H204ï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('车è¾ç±»åä¸è¿æ¿å¯¹æ¯', 'RULE_E001', 'è½èæç»è½¦è¾ç±»åä¸è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸è´éæ ¸å®', 'vehicleType', 'CROSS_DIFF', '0', 'RED', 'H204', 1, 31), |
| | | ('çæç±»åä¸è¿æ¿å¯¹æ¯', 'RULE_E002', 'è½èæç»çæç±»åä¸è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸è´éæ ¸å®', 'fuelType', 'CROSS_DIFF', '0', 'RED', 'H204', 1, 32), |
| | | ('æ è®°å¨ä½ä¸è¿æ¿å¯¹æ¯', 'RULE_E003', 'è½èæç»æ è®°å¨ä½ä¸è¿æ¿å¯¹æ¯ï¼å·®è·>0.05å¨éæ ¸å®ï¼çµå¼è½¦å¯¹æ¯åçµå¼è´¨éï¼', 'markedTonnage', 'CROSS_DIFF', '0.05', 'YELLOW', 'H204', 1, 33), |
| | | ('è¿æ¿è½¦è¾ä¿¡æ¯ç¼ºå¤±', 'RULE_E004', 'è½èæç»è½¦çå·å¨è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸åå¨éæ ¸å®', 'plateNo', 'EXISTENCE_AUTH', '0', 'RED', 'H204', 1, 34), |
| | | ('载货éç¨å æ¯ä¸åè¶éç¨æ¯æ ¸å®', 'RULE_E005', '载货éç¨/æ»è¡ç¨ ä¸ å¹³åè¿è·/åè¶å¹³åéç¨ ç¸å·®Â±10%éæ ¸å®', 'loadedMileage,totalMileage,avgDistance,avgTripMileage', 'LOADED_RATIO_DIFF', '0.1', 'YELLOW', 'H204', 1, 35), |
| | | ('ç¾å
¬éåèè¶
èå´æ ¸å®', 'RULE_E006', 'ç¾å
¬éåèå离åçæç±»åä¸ä½æ°è¶
è¿50%éæ ¸å®', 'fuelPer100km,fuelTypeCode', 'FUEL_RANGE', '0.5', 'YELLOW', 'H204', 1, 36), |
| | | ('ç¾å
¬éåèç¯æ¯ååæ ¸å®', 'RULE_E007', 'åä¸è¾è½¦ç¾å
¬éåèç¯æ¯ååè¶
è¿Â±50%éæ ¸å®', 'fuelPer100km,plateNo', 'FUEL_MOM', '0.5', 'YELLOW', 'H204', 1, 37), |
| | | ('è½èéä¸è´§è¿ææ¥å¯¹æ¯æ ¸å®', 'RULE_E008', 'ä¼ä¸è½èè´§è¿é/å¨è½¬éä¹å大äºè´§è¿ææ¥50%éæ ¸å®', 'freight,turnover,enterpriseName', 'FREIGHT_VS_H2032', '0.5', 'RED', 'H204', 1, 38), |
| | | ('å¹³åè¿è·ä¸è´§è¿ææ¥å¯¹æ¯æ ¸å®', 'RULE_E009', '车è¾å¹³åè¿è·ä¸è´§è¿ææ¥å¹³åè¿è·ç¸å·®Â±60%éæ ¸å®', 'avgDistance,enterpriseName', 'AVG_DIST_VS_H2032', '0.6', 'YELLOW', 'H204', 1, 39), |
| | | ('å¹³å载货å¨ä½ä¸æ è®°å¨ä½æ¯å¼æ ¸å®', 'RULE_E010', 'å¹³å载货å¨ä½/æ è®°å¨ä½ å°äº0.5æå¤§äº1éæ ¸å®', 'avgLoadedTonnage,markedTonnage', 'LOADED_TON_RATIO', '0.5,1', 'YELLOW', 'H204', 1, 40); |
| | | |
| | | -- ============================================ |
| | | -- åãæèµæ¨¡åï¼å®¢è¿ç«åº/ç©æµååºï¼æ©å± |
| | | -- ============================================ |
| | | |
| | | -- æèµé¡¹ç®ä¸»æ¡£ï¼æ 项ç®å+ç±»å« å¯ä¸ï¼ |
| | | DROP TABLE IF EXISTS investment_project; |
| | | CREATE TABLE investment_project ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | project_name VARCHAR(200) NOT NULL COMMENT '项ç®åç§°', |
| | | category VARCHAR(20) NOT NULL COMMENT 'ç±»å«ï¼å®¢è¿ç«åº/ç©æµååº', |
| | | city VARCHAR(50) COMMENT 'å¸å·', |
| | | county VARCHAR(100) COMMENT 'å¿ï¼å¸ãåºï¼', |
| | | builder_name VARCHAR(200) COMMENT 'æèµå»ºè®¾åä½', |
| | | construct_nature VARCHAR(50) COMMENT '建设æ§è´¨', |
| | | start_time VARCHAR(20) COMMENT 'å¼å·¥æ¶é´', |
| | | end_time VARCHAR(20) COMMENT '建æ/竣工æ¶é´', |
| | | total_investment DOUBLE COMMENT 'è®¡åæ»æèµï¼ä¸å
ï¼', |
| | | approval_gk VARCHAR(500) COMMENT '工坿¹å¤', |
| | | approval_cs VARCHAR(500) COMMENT 'å设æ¹å¤', |
| | | building_area DOUBLE COMMENT '建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼', |
| | | is_billion TINYINT DEFAULT 0 COMMENT '亿å
æèµé¡¹ç®æ è®°', |
| | | is_county TINYINT DEFAULT 0 COMMENT 'ç»æµå¼ºå¿é¡¹ç®æ è®°', |
| | | is_five_year TINYINT DEFAULT 0 COMMENT 'åäºäºéç¹ç©æµé¡¹ç®æ è®°', |
| | | source VARCHAR(50) COMMENT 'æ°æ®æ¥æº', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | UNIQUE KEY uk_name_category (project_name, category) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æèµé¡¹ç®ä¸»æ¡£'; |
| | | |
| | | -- æèµé¡¹ç®æåº¦å®ææ
åµ |
| | | DROP TABLE IF EXISTS investment_monthly; |
| | | CREATE TABLE investment_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | project_id BIGINT NOT NULL COMMENT '项ç®ä¸»æ¡£ID', |
| | | year_plan DOUBLE COMMENT 'æ¬å¹´è®¡åæèµï¼ä¸å
ï¼', |
| | | year_cum DOUBLE COMMENT 'èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | month_done DOUBLE COMMENT 'å
¶ä¸ï¼æ¬æå®ææèµï¼ä¸å
ï¼', |
| | | start_cum DOUBLE COMMENT 'èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | progress_stage VARCHAR(100) COMMENT 'å»ºè®¾é¶æ®µ', |
| | | progress_desc VARCHAR(2000) COMMENT '形象è¿åº¦', |
| | | building_area DOUBLE COMMENT '建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period (report_period), |
| | | KEY idx_project (project_id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æèµé¡¹ç®æåº¦å®ææ
åµ'; |
| | | |
| | | -- æèµç³»ç»æ¥è¯¢å¯¼åºï¼å®¡æ ¸æ¯å¯¹åºåï¼ |
| | | DROP TABLE IF EXISTS investment_system; |
| | | CREATE TABLE investment_system ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | project_name VARCHAR(200) NOT NULL COMMENT '项ç®åç§°ï¼åä½ï¼', |
| | | total_investment DOUBLE COMMENT 'è®¡åæ»æèµï¼ä¸å
ï¼', |
| | | start_cum DOUBLE COMMENT 'èªå¼å§å»ºè®¾è³æ¬æç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | year_plan DOUBLE COMMENT 'æ¬å¹´è®¡åæèµï¼ä¸å
ï¼', |
| | | year_cum DOUBLE COMMENT 'èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼', |
| | | month_done DOUBLE COMMENT '彿宿æèµï¼ä¸å
ï¼', |
| | | import_batch_id BIGINT COMMENT '导å
¥æ¹æ¬¡', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period (report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æèµç³»ç»æ¥è¯¢å¯¼åºï¼å®¡æ ¸æ¯å¯¹åºåï¼'; |
| | | |
| | | -- INVEST é¢ç½®å®¡æ ¸è§åï¼4æ¡ï¼report_type=INVESTï¼ |
| | | INSERT INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('èªå¼å§ç´¯è®¡ä¸æèµç³»ç»å¯¹æ¯', 'RULE_I001', 'èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµä¸æèµç³»ç»å¯¼åºå·®å¼è¶
éå¼éæ ¸å®', 'startCum', 'CROSS_DIFF', '100|5', 'YELLOW', 'INVEST', 1, 41), |
| | | ('èªå¹´åç´¯è®¡ä¸æèµç³»ç»å¯¹æ¯', 'RULE_I002', 'èªå¹´åç´¯è®¡å®ææèµä¸æèµç³»ç»å¯¼åºå·®å¼è¶
éå¼éæ ¸å®', 'yearCum', 'CROSS_DIFF', '50|5', 'YELLOW', 'INVEST', 1, 42), |
| | | ('æ¬æå®æä¸æèµç³»ç»å¯¹æ¯', 'RULE_I003', 'æ¬æå®ææèµä¸æèµç³»ç»å¯¼åºå·®å¼è¶
éå¼éæ ¸å®', 'monthDone', 'CROSS_DIFF', '30|30', 'YELLOW', 'INVEST', 1, 43), |
| | | ('æèµç³»ç»é¡¹ç®ç¼ºå¤±æ ¸å®', 'RULE_I004', 'æ±æ»å¤§è¡¨é¡¹ç®å¨æèµç³»ç»å¯¼åºä¸ä¸åå¨éæ ¸å®', 'projectName', 'EXISTENCE_SYS', '0', 'YELLOW', 'INVEST', 1, 44); |
| | | |
| | | |
| | | -- ============================================ |
| | | -- ä¹ãå
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬éï¼ä¼ä¸çº¿ä¸æ±æ¥ï¼ |
| | | -- ============================================ |
| | | DROP TABLE IF EXISTS passenger_individual_monthly; |
| | | CREATE TABLE passenger_individual_monthly ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ 2026-07', |
| | | region_code VARCHAR(20) COMMENT 'æå±å°åºï¼è¡æ¿åºå代ç ï¼', |
| | | enterprise_code VARCHAR(50) COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) COMMENT 'ä¼ä¸åç§°', |
| | | passenger_count DOUBLE COMMENT 'å
¶ä¸ä¸ªä½å®¢è¿éï¼äººï¼', |
| | | turnover DOUBLE COMMENT 'å
¶ä¸ä¸ªä½æ
客å¨è½¬éï¼äººå
¬éï¼', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_period (report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬é'; |
| | | |
| | | -- ============================================ |
| | | -- åãæ¥è¡¨æå®¡æ ¸éè¿æ è®°ï¼audit_runï¼ |
| | | -- ============================================ |
| | | DROP TABLE IF EXISTS audit_run; |
| | | CREATE TABLE audit_run ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| | | report_type VARCHAR(50) COMMENT 'æ¥è¡¨ç±»å', |
| | | report_period VARCHAR(20) COMMENT 'æ¥è¡¨æ', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | KEY idx_type_period (report_type, report_period) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æ¥è¡¨æå®¡æ ¸éè¿æ è®°'; |
| New file |
| | |
| | | -- åå¸å®¢è¿ç¬¬1æ¥ï¼å
¬äº¤ï¼å»ºè¡¨ + å®¡æ ¸è§å |
| | | -- æ°æ®æºï¼å¯¼å
¥_åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼2026å¹´7æï¼.xlsï¼ä¼ä¸çº§æç»ï¼å«å
Œ
±æ±½çµè½¦/轨é/轮渡ä¸ç±»ï¼æ¬è¡¨åªåå
Œ
±æ±½çµè½¦åæ®µï¼ |
| | | |
| | | CREATE TABLE IF NOT EXISTS city_bus_monthly ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | region_code VARCHAR(20) DEFAULT NULL COMMENT 'æå±å°åºä»£ç ', |
| | | city VARCHAR(50) DEFAULT NULL COMMENT 'å¸å·ï¼ç±å°åºä»£ç æ å°ï¼', |
| | | enterprise_code VARCHAR(50) DEFAULT NULL COMMENT 'ä¼ä¸ä»£ç ', |
| | | enterprise_name VARCHAR(200) DEFAULT NULL COMMENT 'ä¼ä¸åç§°', |
| | | op_vehicles DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦è¿è¥è½¦æ°', |
| | | stop_total DOUBLE DEFAULT NULL COMMENT 'è¿è¥ç«ç¹æ»ä¸ªæ°', |
| | | stop_city DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸ï¼åå¸å
è¿è¥ç«ç¹', |
| | | passenger_volume DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦å®¢è¿éï¼ä¸äººæ¬¡ï¼', |
| | | passenger_city DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸ï¼åå¸å
客è¿éï¼ä¸äººæ¬¡ï¼', |
| | | turnover DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | turnover_city DOUBLE DEFAULT NULL COMMENT 'å
¶ä¸ï¼åå¸å
æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | avg_distance DOUBLE DEFAULT NULL COMMENT 'å
Œ
±æ±½çµè½¦å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_city DOUBLE DEFAULT NULL COMMENT 'åå¸å
å¹³åè¿è·ï¼å
¬éï¼', |
| | | passenger_chengxiang DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿éï¼ä¸äººæ¬¡ï¼', |
| | | turnover_chengxiang DOUBLE DEFAULT NULL COMMENT 'åé
å乡æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | avg_distance_chengxiang DOUBLE DEFAULT NULL COMMENT 'åé
å乡平åè¿è·ï¼å
¬éï¼', |
| | | rate_op_vehicles_mom DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°ç¯æ¯å¢é', |
| | | rate_passenger_mom DOUBLE DEFAULT NULL COMMENT '客è¿éç¯æ¯å¢é', |
| | | rate_passenger_city_mom DOUBLE DEFAULT NULL COMMENT 'åå¸å
客è¿éç¯æ¯', |
| | | rate_chengxiang_mom DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿éç¯æ¯å¢é', |
| | | rate_chengxiang_turnover_mom DOUBLE DEFAULT NULL COMMENT 'åé
å乡æ
客å¨è½¬éç¯æ¯å¢é', |
| | | rate_turnover_mom DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬éç¯æ¯å¢é', |
| | | avg_distance_mom_change DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼', |
| | | avg_distance_city_mom_change DOUBLE DEFAULT NULL COMMENT 'åå¸å
å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼', |
| | | daily_passenger_per_vehicle DOUBLE DEFAULT NULL COMMENT 'å车æ¥å客è¿é', |
| | | rate_op_vehicles_yoy DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°åæ¯å¢é', |
| | | rate_passenger_yoy DOUBLE DEFAULT NULL COMMENT '客è¿é忝å¢é', |
| | | rate_passenger_city_yoy DOUBLE DEFAULT NULL COMMENT 'åå¸å
客è¿é忝å¢é', |
| | | rate_chengxiang_yoy DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿é忝å¢é', |
| | | rate_chengxiang_turnover_yoy DOUBLE DEFAULT NULL COMMENT 'åé
å乡æ
客å¨è½¬é忝å¢é', |
| | | rate_turnover_yoy DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬é忝å¢é', |
| | | stop_city_ratio DOUBLE DEFAULT NULL COMMENT 'åå¸å
è¿è¥ç«ç¹å æ¯', |
| | | passenger_city_ratio DOUBLE DEFAULT NULL COMMENT 'åå¸å
客è¿éå æ¯', |
| | | chengxiang_ratio DOUBLE DEFAULT NULL COMMENT 'åé
å乡客è¿éå æ¯', |
| | | contact_person VARCHAR(100) DEFAULT NULL COMMENT '填表人', |
| | | contact_phone VARCHAR(50) DEFAULT NULL COMMENT 'èç³»çµè¯', |
| | | report_date VARCHAR(20) DEFAULT NULL COMMENT 'æ¥åºæ¥æ', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | report_note TEXT COMMENT '䏿¥è¯´æ', |
| | | modify_record TEXT COMMENT 'ä¿®æ¹è®°å½', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_period (report_period), |
| | | KEY idx_city (city), |
| | | KEY idx_enterprise (enterprise_name) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='åå¸å
¬äº¤ä¼ä¸æåº¦æ°æ®ï¼åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼'; |
| | | |
| | | -- åå¸å
¬äº¤ 4 ç»å®¡æ ¸è§åï¼report_type=CITY_BUSï¼ |
| | | INSERT IGNORE INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('åå¸å
ä¸åé
å乡客è¿éè¶å¿ä¸è´æ ¸å®', 'RULE_B001', 'åå¸å
客è¿éç¯æ¯/忝ä¸åé
å乡客è¿éç¯æ¯/忝è¶å¿ä¸ä¸è´ä¸å¢é>2%éæ ¸å®', 'passengerCityMom,passengerChengxiangMom,passengerCityYoy,passengerChengxiangYoy', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_BUS', 1, 51), |
| | | ('客è¿é䏿
客å¨è½¬éè¶å¿ä¸è´æ ¸å®', 'RULE_B002', '客è¿é忝/ç¯æ¯ä¸æ
客å¨è½¬é忝/ç¯æ¯è¶å¿ä¸ä¸è´éæ ¸å®', 'passengerMom,turnoverMom,passengerYoy,turnoverYoy', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_BUS', 1, 52), |
| | | ('åå¸å
/åé
å乡客è¿éå æ¯ååæ ¸å®', 'RULE_B003', 'åå¸å
客è¿éå æ¯ååé
å乡客è¿éå æ¯ä¸ä¸æç¸æ¯åå>1%éæ ¸å®', 'passengerCityRatio,chengxiangRatio', 'RATIO_CHANGE', '0.01', 'YELLOW', 'CITY_BUS', 1, 53), |
| | | ('å¹³åè¿è·ååå¹
åº¦æ ¸å®', 'RULE_B004', 'å¹³åè¿è·ä¸ä¸æç¸æ¯ååå¹
度è¶
è¿3å
¬ééæ ¸å®ï¼ç¹å«æ¯åé
å乡ï¼', 'avgDistanceMomChange,avgDistanceCityMomChange,avgDistanceChengxiang', 'AVG_DIST_CHANGE', '3', 'YELLOW', 'CITY_BUS', 1, 54); |
| New file |
| | |
| | | -- åå¸å®¢è¿ç¬¬2æ¥ï¼å·¡æ¸¸åºç§ï¼å»ºè¡¨ + å®¡æ ¸è§å |
| | | -- æ°æ®æº1ï¼å¯¼å
¥_巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼2026å¹´7æï¼.xlsï¼å¸å·çº§ï¼17è¡ï¼ |
| | | -- æ°æ®æº2ï¼è¾å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xlsï¼è½¦è¾çº§æç» 42636 è¡ï¼ |
| | | |
| | | CREATE TABLE IF NOT EXISTS city_taxi_monthly ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | region_code VARCHAR(20) DEFAULT NULL COMMENT 'æå±å°åºä»£ç ', |
| | | city VARCHAR(50) DEFAULT NULL COMMENT 'å¸å·', |
| | | trip_total DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°', |
| | | trip_city DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°_åå¸å
', |
| | | passenger_volume DOUBLE DEFAULT NULL COMMENT '客è¿éï¼ä¸äººæ¬¡ï¼', |
| | | passenger_city DOUBLE DEFAULT NULL COMMENT '客è¿é_å
¶ä¸åå¸å
', |
| | | turnover DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬éï¼ä¸äººå
¬éï¼', |
| | | turnover_city DOUBLE DEFAULT NULL COMMENT 'æ
客å¨è½¬é_åå¸å
', |
| | | op_vehicles DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦è¾æ°', |
| | | avg_distance DOUBLE DEFAULT NULL COMMENT '巡游åºç§æ±½è½¦å¹³åè¿è·ï¼å
¬éï¼', |
| | | avg_distance_city DOUBLE DEFAULT NULL COMMENT 'åå¸å
å¹³åè¿è·ï¼å
¬éï¼', |
| | | daily_trips_per_vehicle DOUBLE DEFAULT NULL COMMENT 'å车æ¥å载客车次', |
| | | daily_passenger_per_vehicle DOUBLE DEFAULT NULL COMMENT 'å车æ¥å客è¿é', |
| | | passengers_per_trip DOUBLE DEFAULT NULL COMMENT 'å车次载客人æ°', |
| | | passengers_per_trip_city DOUBLE DEFAULT NULL COMMENT 'å车次载客人æ°_åå¸å
', |
| | | passengers_per_trip_chengxiang DOUBLE DEFAULT NULL COMMENT 'å车次载客人æ°_åé
å乡', |
| | | rate_op_vehicles_mom DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°ç¯æ¯å¢é', |
| | | rate_op_vehicles_yoy DOUBLE DEFAULT NULL COMMENT 'è¿è¥è½¦æ°åæ¯å¢é', |
| | | rate_trip_mom DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯å¢é', |
| | | rate_trip_yoy DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡åæ¯å¢é', |
| | | rate_passenger_mom DOUBLE DEFAULT NULL COMMENT '客è¿éç¯æ¯å¢é', |
| | | rate_passenger_yoy DOUBLE DEFAULT NULL COMMENT '客è¿é忝å¢é', |
| | | avg_distance_mom_change DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼', |
| | | avg_distance_city_mom_change DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·_åå¸å
ç¯æ¯å¢åï¼å
¬éï¼', |
| | | rate_trip_city_mom DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯å¢é_åå¸å
', |
| | | rate_passenger_city_mom DOUBLE DEFAULT NULL COMMENT '客è¿éç¯æ¯å¢é_åå¸å
', |
| | | rate_trip_chengxiang_mom DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°_åé
åä¹¡ç¯æ¯å¢é', |
| | | rate_passenger_chengxiang_mom DOUBLE DEFAULT NULL COMMENT '客è¿é_åé
åä¹¡ç¯æ¯å¢é', |
| | | avg_distance_chengxiang DOUBLE DEFAULT NULL COMMENT 'å¹³åè¿è·_åé
å乡ï¼å
¬éï¼', |
| | | trip_city_ratio DOUBLE DEFAULT NULL COMMENT 'è½½å®¢è½¦æ¬¡æ»æ°_åå¸å
å æ¯', |
| | | passenger_city_ratio DOUBLE DEFAULT NULL COMMENT '客è¿é_åå¸å
å æ¯', |
| | | contact_person VARCHAR(100) DEFAULT NULL COMMENT '填表人', |
| | | contact_phone VARCHAR(50) DEFAULT NULL COMMENT 'èç³»çµè¯', |
| | | report_date VARCHAR(20) DEFAULT NULL COMMENT 'æ¥åºæ¥æ', |
| | | verify_explanation TEXT COMMENT 'æ ¸å®æ§åå è§£é', |
| | | report_note TEXT COMMENT '䏿¥è¯´æ', |
| | | modify_record TEXT COMMENT 'ä¿®æ¹è®°å½', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_period (report_period), |
| | | KEY idx_city (city) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼å¸å·çº§ï¼'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS city_taxi_auth ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | report_period VARCHAR(20) NOT NULL COMMENT 'æ¥è¡¨æ 2026-07', |
| | | city VARCHAR(50) DEFAULT NULL COMMENT 'å¸å·ï¼ç±è¯å·/æ¡£æ¡å·/å°åæåï¼', |
| | | plate_no VARCHAR(30) DEFAULT NULL COMMENT '车çå·ç ', |
| | | vehicle_type VARCHAR(50) DEFAULT NULL COMMENT '车è¾ç±»å', |
| | | biz_type VARCHAR(50) DEFAULT NULL COMMENT 'ç»è¥ç±»å', |
| | | owner_name VARCHAR(100) DEFAULT NULL COMMENT '车主åç§°', |
| | | license_no VARCHAR(100) DEFAULT NULL COMMENT 'ç»è¥æå·', |
| | | archive_no VARCHAR(100) DEFAULT NULL COMMENT 'æ¡£æ¡å·', |
| | | road_cert_no VARCHAR(100) DEFAULT NULL COMMENT 'éè·¯è¿è¾è¯åå·', |
| | | fuel_type VARCHAR(50) DEFAULT NULL COMMENT 'çæç±»å', |
| | | operator_name VARCHAR(200) DEFAULT NULL COMMENT '䏿·åç§°', |
| | | operator_addr VARCHAR(300) DEFAULT NULL COMMENT '䏿·å°å', |
| | | op_status VARCHAR(20) DEFAULT NULL COMMENT 'è¥è¿ç¶æ', |
| | | cert_status VARCHAR(20) DEFAULT NULL COMMENT 'è¯ä»¶ç¶æ', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_period (report_period), |
| | | KEY idx_city (city), |
| | | KEY idx_period_city (report_period, city) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='åºç§è½¦è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦è¾çº§æç»ï¼'; |
| | | |
| | | -- åå¸å®¢è¿ç¬¬2æ¥ï¼å·¡æ¸¸åºç§ 7 ç»å®¡æ ¸è§åï¼report_type=CITY_TAXIï¼ |
| | | INSERT IGNORE INTO audit_rule (rule_name, rule_code, description, check_field, compare_type, threshold, alert_level, report_type, is_enabled, sort_order) VALUES |
| | | ('è¿è¥è½¦æ°ä¸è¿æ¿ç³»ç»å¯¹æ¯æ ¸å®', 'RULE_T001', 'ææ¥è¿è¥è½¦è¾æ°ä¸è¿æ¿ç³»ç»è½¦è¾æ°å·®è·>3%éæ ¸å®', 'opVehicles', 'CROSS_DIFF_PCT', '3', 'RED', 'CITY_TAXI', 1, 61), |
| | | ('åå¸å
ä¸åé
å乡客è¿éè¶å¿ä¸è´æ ¸å®', 'RULE_T002', 'åå¸å
客è¿éç¯æ¯ä¸åé
å乡客è¿éç¯æ¯è¶å¿ä¸ä¸è´ä¸å¢é>2%éæ ¸å®', 'passengerCityMom,passengerChengxiangMom', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_TAXI', 1, 62), |
| | | ('客è¿é䏿
客å¨è½¬éè¶å¿ä¸è´æ ¸å®', 'RULE_T003', '客è¿éç¯æ¯ä¸æ
客å¨è½¬éç¯æ¯ï¼ç±å¹³åè¿è·æ¨å¯¼ï¼è¶å¿ä¸ä¸è´éæ ¸å®', 'passengerMom,turnoverMomDerived', 'TREND_CONSISTENCY', '0.02', 'YELLOW', 'CITY_TAXI', 1, 63), |
| | | ('åå¸å
客è¿éå æ¯ååæ ¸å®', 'RULE_T004', 'åå¸å
客è¿éå æ¯ä¸ä¸æç¸æ¯åå>1%éæ ¸å®', 'passengerCityRatio', 'RATIO_CHANGE', '0.01', 'YELLOW', 'CITY_TAXI', 1, 64), |
| | | ('å¹³åè¿è·ååå¹
åº¦æ ¸å®', 'RULE_T005', 'å¹³åè¿è·ä¸ä¸æç¸æ¯ååè¶
è¿3å
¬ééæ ¸å®ï¼ç¹å«æ¯åé
å乡ï¼', 'avgDistanceMomChange,avgDistanceCityMomChange,avgDistanceChengxiang', 'AVG_DIST_CHANGE', '3', 'YELLOW', 'CITY_TAXI', 1, 65), |
| | | ('å车次载客人æ°ç³»æ°ååæ ¸å®', 'RULE_T006', 'å车次载客人æ°=客è¿é/è½½å®¢è½¦æ¬¡æ»æ°ï¼ä¸ä¸æç¸æ¯åå>0.2éæ ¸å®', 'passengersPerTrip', 'PAX_PER_TRIP_CHANGE', '0.2', 'YELLOW', 'CITY_TAXI', 1, 66), |
| | | ('è½½å®¢è½¦æ¬¡æ»æ°ä¸å®¢è¿éè¶å¿ä¸è´æ ¸å®', 'RULE_T007', 'è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯/忝ä¸å®¢è¿éç¯æ¯/忝è¶å¿ä¸ä¸è´éæ ¸å®', 'tripMom,passengerMom,tripYoy,passengerYoy', 'TREND_CONSISTENCY', '0.01', 'YELLOW', 'CITY_TAXI', 1, 67); |
| New file |
| | |
| | | # HANDOFF.md â æ¯æ¥äº¤æ¥ææ¡£ |
| | | |
| | | > **ç¨æ³**ï¼æ°å¯¹è¯å¼å§åï¼å
è¯»æ¬æä»¶ + é¡¹ç®æ ¹ç®å½ `AGENTS.md`ï¼å³å¯æ ç¼æ¥åã |
| | | > **æ¯å¤©ç»ææ¶**ï¼æå½å¤©è¿å±ã踩è¿çåãæ°éæ±ãæªå®æäºé¡¹æ´æ°å°æ¬æä»¶ï¼ç¶åå¯ä»¥æ¾å¿å¼æ°å¯¹è¯ã |
| | | |
| | | ## ä¸ãå½åç¶æï¼2026-08-27ï¼ |
| | | - **2026-08-27ï¼ç¨æ·ç¯å¢ä¿®å¤ï¼Chrome 渲æè¿ç¨å´©æº=è¿
é·æ©å±ï¼ä¸åºç¨æ å
³ï¼**ï¼ç¨æ·åé¦ãç»å½åç¹æ°æ®å¯¼å
¥æµè§å¨å´©æºããææ¥ç»è®ºï¼**éåºç¨ä»£ç é®é¢**ââæ å¤´ Chrome 宿´æµç¨å¤è½®ï¼çå®ç»å½âç¹å»èåâå tab/䏿/å·æ°ï¼+ Edge å 0 å´©æºãJS 0 é误ï¼å端 /import/batches æ limit ä¸éæ 大ååºãæ¬æº Crashpad æ 3 个**渲æè¿ç¨**å´©æº dumpï¼10:02/10:03/11:15ï¼ï¼å¼å¸¸ç¹å¾å®å
¨ä¸è´ï¼code 0x517a7ed / addr 0xb57d5567=æªåå§åå
å跳转ï¼ï¼é¡µé¢å为 localhost:8080ï¼ä¸æ¬¡å´©æºæ¶å¯ä¸ç¬¬ä¸æ¹æ©å±=ãè¿
é·ä¸è½½æ¯æãï¼id ncennffkjdiamlpmcbajkmaiiiddgiooï¼document_start å
¨é¡µé¢æ³¨å
¥èªå¸¦ Vue3 runtime + MutationObserver è§å¯æ´ä¸ª body DOMï¼ï¼Intel æ§é©±å¨ 27.20.100.9365 为å å å ç´ ãç¨æ·å·²å¨ Chrome ç§»é¤è¯¥æ©å±å¹¶æ¹ç¨ Edgeï¼åä¸åå´©æºã |
| | | - **åï¼å¡å¿
è®°ä½ï¼**ï¼â æ£å¼ç Chrome å·²ç¦æ¢å½ä»¤è¡å è½½æ©å±ï¼--load-extension/--disable-extensions-except 被忽ç¥ï¼æ¥å¿æç¤º not allowed in Google Chromeï¼ï¼headless æ æ³å¤ç°ã带æ©å±ãåºæ¯ï¼**Edge å
许å½ä»¤è¡å è½½æ©å±**ï¼msedge.exe --load-extension=... + --user-data-dir=ç¬ç«ç®å½ï¼ï¼å¯ç¨äºå¤ç°æ©å±ç¸å
³é®é¢ãâ¡ç¨æ· Chrome å´©æºè¯æ®ç %LOCALAPPDATA%\Google\Chrome\User Data\Crashpad\reports\*.dmpï¼minidump å¯æå·¥è§£æï¼å¼å¸¸æµ type=6ãæ¨¡å表 type=4ï¼æ³¨éå« gpu-driver/extension-1/é¡µé¢ URLï¼ãâ¢åéãæµè§å¨å´©æº/ç½å±ãå
æ¥ Crashpad æ¯å¦å«ç¬¬ä¸æ¹æ©å±ï¼åæ¥æ©å±/驱å¨ï¼ä¸è¦å
æçåºç¨ã |
| | | - 产ç©ï¼_shots/import_crash_repro*.jsï¼headless å¤ç°ï¼ã_shots/edge_repro.jsï¼Edge+æ©å±å¤ç°ï¼ã_shots/xunlei_ext/ï¼æ©å±å¯æ¬ï¼ã_shots/cft/ï¼Chrome for Testing ä¸è½½æ®çï¼ã |
| | | |
| | | - **2026-08-26ï¼æé´ç»8ï¼æ¥è¡¨çæé¡µ UI ä¼å为ãTab å·¥ä½å°ã+ 夿³¨å¼¹çªï¼e2e å
¨ç»¿ï¼**ï¼`ReportExport.vue`ï¼23:27:55 Compiledï¼ç»§ç»æ¹çï¼ |
| | | - æ¥è¡¨ç±»åæ¹å**é¡¶é¨ 5 个 Tab**ï¼è´§è¿/æ
客/åå¸å®¢è¿/æèµ/è½èï¼åå¸å®¢è¿å¸¦ãå
¬äº¤/åºç§/轨é轮渡/æ±æ»ãæ ç¾ï¼ï¼æ¿ä»£ç»7çä¸ææ¡æ¹æ¡ã |
| | | - æ¥è¯¢åºï¼å¨æåéï¼ææ¥/å¹´æ¥ï¼è½èåªæ¾ç¤ºå£æ¥ï¼+ æä»½/年份**åç»ä¸æ**ï¼è½èåªåå£åº¦æ«æ 3/6/9/12ï¼+ æ¥è¯¢æé®ï¼é 2026-07 åç¶æå·æ°ã |
| | | - è´§è¿æ¨¡åæ°å¢ãæºæ°æ®å½å
¥ä¸æ ¡éªãå¡çï¼200 è¡æºæ°æ®è¡¨æ ¼ï¼ä¼ä¸åç§°/è´§è¿é/è¿è¾æ¹å¼/ç»è®¡æä»½ï¼ï¼ãæ ¡éªæ°æ®ãâ é误å表 + åºéåå
æ ¼çº¢è²é«äº®ï¼cell-errorï¼ï¼ãèªå¨è®¡ç®ãâ æ±æ»è¡ï¼æ»è´§è¿é/å¹³åè´§è¿é/åä¼ä¸å æ¯ï¼ï¼å
¶ä½æ¨¡åæ¾ç¤ºãææªé
ç½®å¨çº¿æ ¡éªè§åãæç¤ºã |
| | | - æ¥è¡¨å表ï¼å¾é + æ¥è¡¨å + ç¶ææ ç¾ï¼æ°æ®å°±ç»ª/æªå®¡æ ¸/ç¼ºæ°æ®ï¼+ 夿³¨å + é¢è§/导åºï¼åºé¨å
¨é/æ¹éé¢è§/æ¹é导åºä¿çã |
| | | - **夿³¨å¼¹çª**ï¼`openNoteDialog`/`saveNote`ï¼ï¼ææ¥è¡¨ key æ¬å°è®°å¿ï¼`localStorage report_note_<key>`ï¼ï¼ä¿åååè¡¨å³æ¶æ¾ç¤ºã |
| | | - æ 头 e2e `_shots/report_ui_opt_e2e.js`ï¼5 Tabã卿/æä»½ä¸æã2026-07 ä¸å¼ æ¥è¡¨æ°æ®å°±ç»ªãæ ¡éªå¤±è´¥ 1 æ¡+红è²é«äº®ãèªå¨è®¡ç®æ±æ»ãæèµ 4 å¼ /è½èä»
壿¥+å£åº¦æ«æã夿³¨å¼¹çªä¿åå
¨é¨éè¿ï¼JS 0 éè¯¯ï¼æªå¾ `_shots/report_ui_opt.png`ã |
| | | - **åï¼å·²ä¿®å¤ï¼**ï¼å¤æ³¨å¼¹çª e2e æ¾è¯¯æ¥ã空é¢è§å¼¹çªæ é¢ãââ`querySelector('.el-dialog__title')` å½ä¸çæ¯ DOM ä¸ç¬¬ä¸ä¸ªï¼éèçï¼é¢è§å¼¹çªæ é¢ï¼æ¹ä¸ºå**å¯è§** `.el-dialog__wrapper` å
çæ é¢/textarea/æé®åéè¿ï¼å¦ localStorage æè¨é®åºä¸ºå½å Tab æ¥è¡¨ï¼energySummary èé cityDetailï¼ãåºç¨æ¬èº«æ 缺é·ï¼æ¢é `_shots/probe_note.js` 确认ç¹ç¼è¾å¾æ å³å¼ãç¼è¾å¤æ³¨ãå¼¹çªï¼ã |
| | | - éç/å¾
åä¸åï¼è§ç»5ï¼è´§è¿ 7 ææ±æ»è¡¨æªå¯¼å
¥ãæ
客 1-6 æ/2025 åæç¼ºå¤±ã8-12 æä¾èµæ°æ®æºãè½èæ°è§åéå¼å¾
确认ï¼ã |
| | | - **2026-08-26ï¼æé´ç»4åè¡¥ï¼è§£éæ¨¡åæ¹ãæéå±å¼ãï¼**ï¼ç¨æ·åé¦å·¦å³å¸å±ä»ä¸ç¾è§ãè®¾è®¡å¤æï¼é®é¢ä¸å¨ä½ç½®èå¨ã常驻æ¾ç¤ºãââæè§£éç¼è¾å¨ä¸ç´æå¨æ¯æ¡é®é¢æå¿
ç¶æ¾æ¤ãæ¹ä¸º**æµè§è½»ãç¼è¾æéå±å¼**ï¼å端 17:41:58 Compiledï¼ï¼æç»è§å¾å¡ç头æ¯ãè§£éï¼å·²å¡«å/æªå¡«åãå¯ç¹å»æ ç¾ï¼ç¹å¼æå¨å¡çå
å±å¼è§£éç¼è¾åºï¼æ¶èµ·æé®å¯å
³ï¼ï¼åå¹¶è§å¾ãä¼ä¸è§£éãåé»è®¤åªæ¾ç¤ºç¶ææ ç¾+2 è¡æè¦+ãå¡«å/ä¿®æ¹ãæé®ï¼ç¹å¼æå±å¼ textareaï¼ä¸¤æ¡è§å¾å
跳转ãè§£éã龿¥å·²ç§»é¤ï¼å goExplain 龿¥æ¹ç±å±å¼é¢æ¿æ¿æ
ï¼AI åæä»å¨ä¼ä¸è§£é页ï¼ãé»è®¤é¡µé¢åå°å¹²åç¶æã |
| | | - **2026-08-26ï¼æé´ç»4è¡¥ï¼è§£éæ¨¡åæ¹å·¦å³å¸å±ï¼**ï¼ç¨æ·åé¦ãè§£éæ¾é®é¢ä¸é¢ä¸å¥½çï¼æ¾å³è¾¹ããå·²æ¹ï¼å端 17:38:55 Compiledï¼ï¼æç»è§å¾å¡çå
`card-body` æ¹ flex 两æ ââå·¦ `issue-detail`ï¼è§å+å®é
/åºå/å·®å¼ï¼ãå³ `explain-side`ï¼åºå®å®½ 340 ç°åºé¢æ¿ï¼æ ç¾+å·²æè§£é+textarea+ä¿å/éç½®ï¼ï¼åå¹¶è§å¾æè§£éä»é®é¢æç»åå
æ ¼ç§»åºï¼**åç¬æ°å¢ãä¼ä¸è§£éãåï¼å®½ 360ï¼**ï¼ä½äºãå®¡æ ¸é®é¢æç»ãä¸ãæä½ãä¹é´ï¼éè§£éç±»æ¥è¡¨ï¼INVEST/H204ï¼æ¾ç¤ºãâãã |
| | | - **2026-08-26ï¼æé´ç»4ï¼å®¡æ ¸ç»æé¡µå
åµä¼ä¸è§£éï¼**ï¼ç¨æ·è¦æ±ãå®¡æ ¸é®é¢æç»åé¢ç´æ¥å è§£éæ¨¡åï¼ä¸ç¨è·³è½¬å°å¦ä¸ä¸ªçé¢ããç°ç¶ï¼å®¡æ ¸ç»æé¡µæ¯æ¡é®é¢å¡çåªæè·³è½¬é¾æ¥ï¼ä¸å端**åæ¬æ²¡æä¿åè§£éçæ¥å£**ï¼verifyExplanation åªè¯»ï¼ã |
| | | - å端 `ExplainController` æ°å¢ `POST /api/explain/save`ï¼reportType/reportId/explanationï¼H2032/H2031/CITY_BUS/CITY_TAXI åç±»ï¼æ reportId æ´æ°å¯¹åºæ¥æºè¡¨ verifyExplanationï¼é误è¿å Result.errorï¼ã |
| | | - å端 `AuditResult.vue`ï¼17:35:24 Compiledï¼ï¼æç»è§å¾æ¯æ¡é®é¢å¡çåºé¨ãåå¹¶è§å¾æ¯å®¶ä¼ä¸é®é¢æç»ä¸æ¹ï¼å
åµãä¼ä¸è§£éãæ¨¡åââå·²æè§£é/æªå¡«åæ ç¾ + è稿 textarea + ä¿åè§£é/éç½®ï¼è稿æ reportId å
±äº«ï¼åä¼ä¸å¤æ¡é®é¢å draftï¼ä¿åå忥ææè¡ï¼ï¼å è½½ç»æç»ä¸èµ° `applyResults` åå§åè稿ãä¼ä¸è§£é页ï¼ExplainReviewï¼ä¸å®¡æ ¸ç»æé¡µå
±ç¨å䏿¥æºè¡¨ï¼ä¿åå两边ä¸è´ï¼åãè§£éãè·³è½¬é¾æ¥ä¿çã |
| | | - éªè¯ï¼`POST /api/explain/save` ä¿åâDB æ´æ°âexplain/list å¯è§âå·²æ¸
æµè¯æ°æ®ï¼å端ç¼è¯éè¿+éå¯å°±ç»ªï¼PID 11732ï¼ã |
| | | - **2026-08-26ï¼æé´ç»3ï¼ç®å½å¯¼å
¥ç´è¿ç + æä»¶åèªå¨è¯å«æä»½ï¼**ï¼ç¨æ·åé¦ç®å½å¯¼å
¥å¤ªæ
¢/æä½ç»ï¼ä¸æç¡®ãæä»¶å¤¹éæ¯å¤ä¸ªæçæ°æ®ï¼è¦æ Excel æä»¶ååå«åå°æ¯ä¸ªæãã |
| | | - å端 `DataImport.vue`ï¼17:21:59 Compiledï¼ï¼å»æãéæ©æä»¶å¤¹ãæé®ï¼æ¹ä¸º**ç®å½è·¯å¾è¾å
¥æ¡ + ã导å
¥è¯¥ç®å½ã**ï¼å车ä¹å¯è§¦åï¼+ãæµè§â¦ãè¾
婿¥æ¾ +ãè·³è¿å·²å¯¼å
¥æä»¶ãå¾éï¼é»è®¤å¼ï¼è®°ä½ä¸æ¬¡ç®å½ localStorage `dirLast_<type>`ï¼ï¼éæä»¶å¯¼å
¥æ¾ç¤ºå®æ¶è¿åº¦ï¼å¾
导å
¥/导å
¥ä¸/æå/失败/å·²è·³è¿ + è¯å«å°çæä»½æ ç¾ï¼ï¼ç®å½å¯¼å
¥ä¸å强å¶è¦æ±å
éæ¥è¡¨æï¼æ¥è¡¨æä»
使 年份æä»¶çåå¤å¹´ä»½ï¼ã |
| | | - å端ï¼ç¼è¯éè¿+éå¯å°±ç»ªï¼PID 10932ï¼ï¼`importDirFileByPath`/`importFromDir` å¢å **`detectPeriod(fileName, period)` ææä»¶åè¯å«æ¥è¡¨æ**ï¼æ¯æ `2026å¹´7æ`/`2026å¹´07æ`/`2026.7`/`2026-07`/`202607`/`7æ`ï¼æ å¹´ä»½ç¨ period 年份æå½å年份ï¼ï¼è¯å«ä¸å°ä¸æªéæ¥è¡¨æåéæä»¶æ¥éï¼æ°å¢**è·³è¿å·²å¯¼å
¥**ï¼import_batch å period+fileName ä¸ failRows=0 åè·³è¿ï¼`skipImported` åæ°é»è®¤ trueï¼ï¼`fromDirFile`/`fromDir` ç period æ¹ä¸ºå¯éãè¯å«è§å注æï¼`202607` é 6 ä½è¿æ°åä¸å¹´ 2000-2100ãæ 1-12ï¼é¿å
误å¤ã |
| | | - éªè¯ï¼`2026.6.xlsxâ2026-06`ã`202607.xlsxâ2026-07`ã`2026å¹´5æ.xlsxâ2026-05`ã`7æ.xlsxâ2026-07`ï¼å½åå¹´ï¼ã`æªæ 注æä»½.xlsxâæ¥é`ï¼éè· skipImported=true å
¨é¨è·³è¿ï¼æµè¯æ¹æ¬¡å·²æ¸
ã |
| | | - è¯ä¹æéï¼åä¸ç±»åå䏿¥è¡¨æå¤ä¸ªæä»¶ä»ä¼ãå
æ¸
ååãäºç¸è¦çï¼ç³»ç»å¹çè§åï¼ï¼æèµå¸å·å¤æä»¶è¯·ç¨ä¸é¨çãå¸å·å表æ¹é导å
¥ãã |
| | | - **2026-08-26ï¼æé´ç»2ï¼æä»¶å¤¹å¯¼å
¥æ¹æå¡ç«¯ç®å½æµè§å¨ + åå»å³å¯¼å
¥ï¼**ï¼ç¨æ·åé¦åçæä»¶å¤¹éæ©æ¡ãåå»/åå»ä¼è·³è¿ä¸ä¸çº§ç®å½ãçä¸å°éæ©ç¡®è®¤ãã |
| | | - æ ¹å ä¸ï¼æµè§å¨åç `webkitdirectory` éæ©æ¡äº¤äºï¼Chrome éç¹å³ä¸è§ãéæ©æä»¶å¤¹ã确认ï¼ç¨æ·æ²¡çå°ï¼ï¼æ ¹å äºï¼`DataImport.vue` æ´ä½éåæ¶ä¸¢äº `endpoint()` æ¹æ³ï¼`importOne` è° `this.endpoint(row.type)` å¿
æ TypeErrorï¼**æææä»¶å¯¼å
¥å
¶å®é½æ¯åç**ï¼ââæ¬æ¬¡å·²è¡¥å宿´ typeâURL æ å°ã |
| | | - æ¹æ¡ï¼å¼ç¨æµè§å¨åçæä»¶å¤¹éæ©æ¡ï¼æ¹ä¸º**æå¡ç«¯ç®å½æµè§å¨**ï¼ååç«¯åæºï¼æ°æ®å¨ `docs/`ï¼ï¼å端æ°å¢ `GET /api/import/dirs?path=`ï¼path 空=é¡¹ç® docsï¼å«åç®å½å表+å½åç®å½ Excel æä»¶å/æ°éï¼docs å®ä½å䏿¥æ¾ 4 å±ï¼å
¼å®¹ IDEA 以 server 模åç®å½å¯å¨ï¼ä¸ `POST /api/import/fromDir`ï¼type/period/dirï¼æ«æç´æ¥å
å«ç .xlsx/.xlsï¼`ByteArrayMultipartFile` å
è£
åå¤ç¨åç±»åæ¢æè§£ææ¹æ³ï¼éæä»¶è¿åæå/失败/é误ï¼ã |
| | | - å端 `DataImport.vue`ï¼ãéæ©æä»¶å¤¹ãæå¼åºç¨å
å¯¹è¯æ¡ï¼å½åè·¯å¾/ä¸çº§ç®å½/æ°æ®ç®å½ docs/åç®å½å表/å½åç®å½ Excel é¢è§ï¼ï¼**åå»=è¿å
¥åæä»¶å¤¹ï¼åå»ç®æ æä»¶å¤¹=ç«å³å¯¼å
¥**ï¼ä¹å¯è¿å
¥åç¹ãéæ©æ¤æä»¶å¤¹å¹¶å¯¼å
¥ãï¼å¯¼å
¥ç»æéæä»¶å¼¹çªå±ç¤ºï¼éååæä»¶å¯¼å
¥æµç¨ä¿çã |
| | | - éªè¯ï¼å端ç¼è¯éè¿+éå¯å°±ç»ªï¼start-dev.ps1ï¼PID 32576ï¼ï¼`/api/import/dirs` è¿å docs 忍¡åç®å½ä¸ 15 个åå¸å®¢è¿ Excelï¼`/api/import/fromDir` 宿µæ¨¡æ¿_轨迹éç¨.xlsxâ628 è¡ï¼çå®è§£ææåï¼ï¼æµè¯äº§çç track_mileage 2026-03 æ°æ®å·²ç¨ç©ºæä»¶é导æ¸
é¶ãimport_batch æµè¯è®°å½(34/35/36)å·²å ï¼å端 17:03:58 Compiled successfullyã |
| | | - å¾
åï¼è¯·ç¨æ·å·æ°é¡µé¢ææµãéæ©æä»¶å¤¹âåå»ç®æ æä»¶å¤¹å³å¯¼å
¥ãï¼ä¸´æ¶æµè¯ç®å½ `%TEMP%\dirimport_test`ã`%TEMP%\dirimport_empty` ä¿çæªå ã |
| | | - **2026-08-26ï¼æé´ï¼æ¥è¡¨æ é¢å
¨å¨æåï¼**ï¼æç¨æ·è¦æ±ãæ é¢ä¸è¦åæ»ï¼æ ¹æ®å®¡è®¡åéæ©çæä»½/年份æ¾ç¤ºã宿æ¶å°¾ï¼æ¿åï¼åå¸å®¢è¿ 6 表ã导å
¥æ¨¡æ¿_*.xlsxãå·²ç±æ¢æãçæ_*.xlsxãå¤å¶è¡¥é½ï¼æ¤åå¼ç¨æä»¶ä¸åå¨å¯¼è´ 500ï¼ï¼ |
| | | - â `dynamicTitle` ä»ãå 6 è¡ãæ¹ä¸ºå
¨è¡¨æ«æï¼è¡¥ä¸åå¸å·ç¬¬ 2 åæ é¢ï¼r23ãåå¸å®¢è¿å®¢è¿å¨è½¬éåå¸å·1-Næç´¯è®¡æ
åµãï¼ï¼ã1-12æãæ´å¹´å¯¹æ¯åï¼2024/2025ï¼æ é¢ä¿æåæ ·ï¼ä¸åè¢«è¯¯æ¹æéä¸å¹´ã |
| | | - â¡ æ°å¢ `cumRange(month)`ï¼1 ææ¾ç¤ºã1æãä¸ååºç°ã1-1æãï¼åºç¨äºè´§è¿é/å¨è½¬éæå累计åãä¸å£å¾åæãæèµé¢å®æè¡¨å¤´ãåå¸å®¢è¿æ é¢ã |
| | | - ⢠æ°å¢ `fixPassengerHeaderYear`ï¼å
¬è·¯æ
客åå¸å·/ä¸å£å¾æç»è¡¨å¤´å¹´ä»½å¨æåï¼æ¤åé 2027 仿¾ç¤ºã2026å¹´1æ/2026年累计ãï¼ã |
| | | - ⣠轨é轮渡æç»è¡¥è° `dynamicTitle`ï¼æ¤åè¡¨å¤´å¹´ä»½åæ» 2026ï¼ã |
| | | - éªè¯ï¼2026-01/2026-07/2026-12ï¼å¹´æ¥ï¼/2027-01 à å
¨æ¨¡å 17 å¼ å¯¼åºæ é¢å
¨é¨æè¨éè¿ï¼æ ·ä¾è§ `%TEMP%\title_check\*FINAL*`ï¼ï¼æ±æ»è¡¨ G1/M1/G12 对æ¯åæ 颿£ç¡®ä¿ç 2025/2024ã |
| | | - éçï¼å±æ¨¡æ¿ç»æï¼æªæ¹ï¼ï¼æèµäº¿å
表 3 çå£åº¦åºå®åï¼1-3æå®é
/4æè®¡å/Q2/1-4æç´¯è®¡/5æ/6æçï¼ä¸ºæ¨¡æ¿åºå®åï¼å¹´ä»½å·²å¨æãæä»½åºå®æ¯æ¨¡æ¿è®¾è®¡ï¼æèµåäºäºæ¨¡æ¿ sheet0ãå¹´ æãå ä½ä¸å®æ¹æ ·ä¾ä¸è´ã |
| | | - **2026-08-26ï¼æé´ç»ï¼æ¥è¡¨æ°æ®é¢è§è½»éçï¼**ï¼å¯¼åºåå
çæ°æ®ææ²¡æç¼º/ä¸å¯¹ãå端æ°å¢ `GET /api/report/preview`ï¼period/mode/typesï¼å¤ç¨ checkExportReady ç就绪/å®¡æ ¸ç¶æï¼é模åç®æåº¦è¦ç + æ ¸å¿ææ æè¦ + é®é¢æ¸
åï¼ï¼è´§è¿=æ¨¡æ¿æ¹åçè¡ 1..N æåè¦ç + å
¨ç累计货è¿é/å¨è½¬éï¼å计/è§ä¸/è§ä¸ï¼ï¼æ
客=æºè¡¨éæè¦ç + ä¼ä¸/个ä½ç´¯è®¡ï¼åå¸å®¢è¿=å
¬äº¤/åºç§/轨é/è½®æ¸¡ç´¯è®¡ï¼æ±æ» 3 表è¦ç=å
¬äº¤âªåºç§ï¼å
¬äº¤æç»/轨é轮渡åªçå
¬äº¤è¡¨ï¼ï¼æèµ=彿æ¥è¡¨åªæç¤ºå½æç¼ºå¤± + é¡¹ç®æ°/æ¬æå®æ/èªå¹´å/èªå¼å§ç´¯è®¡ï¼è½è=å£åº¦æ°æ® + è½¦è¾æ°/çæ²¹æ¶èãå端 `ReportExport.vue` æ¯æ¨¡åå ãé¢è§æ°æ®ãæé® + å¼¹çªï¼ç¶ææ ç¾ å°±ç»ª/ç¼ºæ°æ®/æªå®¡æ ¸ãæåº¦è¦çãæè¦è¡¨ãé®é¢æ¸
åï¼ãéªè¯ï¼2026-07 å
¨æ¨¡å 18 å¼ ã2026-08ï¼æªå®¡æ ¸+ç¼ºæ°æ®ï¼ã2026-12 å¹´æ¥ã2027-01 å
¨é¨æ£ç¡®ï¼å端 Compiled successfullyã |
| | | - é¢è§é¡ºå¸¦æ´é²çæ°æ®ç°ç¶ï¼ä¾ç¨æ·æ ¸å¯¹ï¼ï¼è´§è¿æ¨¡æ¿ä»
2026-07 ä¸æä¸ 7 æå为空ï¼ç´¯è®¡åªæ 1-6 æ 9.7 ä¸å¨ï¼è§ä¸ 3754 ä¸å¨ â è§ä¸ä¸ºè´ï¼å£å¾åçï¼ï¼æ
客/åºç§ä»
7 æãæèµä»
6-7 æå¯¼å
¥ï¼å
¬äº¤ææ¥æªå¯¼å
¥ï¼è½¨é/轮渡 0 æ°æ®ï¼ã |
| | | - **2026-08-26ï¼ååï¼æ¥è¡¨çæ P0 ä¸é¡¹ + 模æ¿è§å梳çï¼**ï¼â 导åºåæ°æ®å°±ç»ªæ ¡éªââå端 `GET /api/report/ready`ï¼ææ¥è¡¨ç±»åæ£æ¥ä¾èµè¡¨è¡æ°ï¼è´§è¿=模æ¿_è´§è¿éå¨è½¬é+è§ä¸è§ä¸æå表+H203-2ãæ
客=H203-1+个ä½ãå
¬äº¤/åºç§ãæèµ=investment_monthlyãè½è=h204_vehicle_quarterlyï¼ï¼å端导åºåå¼¹ãæ°æ®æªå°±ç»ªã确认æ¡ï¼ååºå
·ä½ç¼ºåªå¼ 表ï¼å¯ç»§ç»å¯¼åºç©ºè¡¨ï¼ï¼â¡é误éåºââæ°å¢ `GlobalExceptionHandler`ï¼HTTP 500 + Result{code,message}ï¼ï¼å端 `failWith` æ¾ç¤ºå端å
·ä½åå ï¼â¢æä»¶åç»ä¸ââå端 4 个æ
客 fileName 对é½å端ï¼çæ_å
¬è·¯æ
客åå¸å·æç»/ä¸å£å¾æç»/ä¸å£å¾æå/ä¸å£å¾åæ.xlsxï¼ï¼`buildResponse` æ .xls/.xlsx 设置 MIMEï¼â£é¡ºæä¿® `ReportExport.vue` 缺 `activeMenu` å®ä¹ï¼ä¾§è¾¹æ é«äº®å¤±æï¼ãéªè¯ï¼ready æ¥å£æ°å¼ä¸ DB éè¡¨æ ¸å¯¹ä¸è´ï¼cityDetail=690=ft18+scale36+h2032=636ãæ
客 771ãåºç§ 17ãæèµ 179ãè½è 126ãå
¬äº¤ 0 å å
¬äº¤ææ¥æªå¯¼å
¥ï¼ï¼æ 头æµè§å¨ä¸¤åºæ¯ PASSï¼ç¼ºæ°æ®å¼¹ç¡®è®¤æ¡ãæ°æ®é½å
¨ç´æ¥ä¸è½½ï¼ï¼å端 12:55:10 Compiledãæªå¾ `_shots/report_ready_dialog.png`ã |
| | | ### 2026-08-26 æé´ä¼è¯ï¼æ¥è¡¨çæä¸å£å¾è½å°ï¼å
¬å¼ä¿ç/卿æè¡/å®¡æ ¸åç½®+1~N累计ï¼å·²å®æéªè¯ï¼ |
| | | **èæ¯**ï¼ç¨æ·ç¡®è®¤ä¸é¡¹å£å¾âââ æææ¨¡æ¿ä¿çå
¬å¼ãåªå¡«åå§éãå
¬å¼é¨åèªå¨ç®ï¼â¡å¨ææè¡å¹¶åæ¥å
¬å¼èå´ï¼â¢å
å®¡æ ¸ååºè¡¨ã1~Næç´¯è®¡=䏿å°Nææ±åã |
| | | **â å
¬å¼ä¿çï¼ç°ç¶æ ¸å¯¹ï¼æ éæ¹ï¼** |
| | | - è½è/åå¸å®¢è¿/æ
客 8 å¼ å¯¼åºå·²æ¯ã模æ¿åºç¨¿ + å
¬å¼åå
æ ¼ä¿ç + evaluateAll éç®ãï¼æ
客åå¸å·/ä¸å£å¾æå/åæãè½èæ±æ»ãåå¸å®¢è¿ 3 å¼ å计/忝/å æ¯å
¬å¼åå¨å¯¼åºåéç®ï¼æèµ 4 表ä¸è´§è¿æ¨¡æ¿æ¬èº« 0 å
¬å¼ï¼å计/忝ç±ç³»ç»ç®å¥½åå
¥ï¼ãæ
客模æ¿åå² `#REF!` å¼ç¨ä¸å½±åå¡«å
ã |
| | | - 顺æä¿®å¤ï¼ä¸ä¸åä¼è¯ï¼ï¼`application.yml` ç `passenger.template-dir` åæåä¸åå¨ç `docs/å
¬è·¯æ
客/è¾åº` å¯¼è´æ
å®¢å¯¼åº 500ï¼å·²æ¹ä¸º `docs/å
¬è·¯æ
客`ï¼`REPORT_FILE_NAMES` æ
客 zip å
æä»¶åæ¹å
¨åãæ
客 4 表导åºå·²æ¢å¤ HTTP 200ï¼sizeâ13.6KBï¼ã |
| | | **â¡å¨ææè¡ï¼æ¬æ¬¡æ°å¢ï¼æèµ 3 表ï¼** |
| | | - æ¹å¨éä¸å¨ `ReportExportService.java`ï¼`investPlan`/`investFiveYearLogistics`/`investCounty`ï¼ï¼ç»ææ«æï¼åâå¸å·å°èâ项ç®è¡ï¼â åä½å¡«å
模æ¿è¡ï¼è®°å½å·²å¹é
é¡¹ç® idï¼â èªåºå䏿å°èæè¡è¡¥é½ DB å¤ä½é¡¹ç® â æå/å¸å·éæåºå·ã`insertRowsDown`ï¼shiftRows + åå¹¶åºå
æé¤åææ°è¡å·æ¢å¤ + `createRowLike` å¤å¶æ ·å¼/è¡é«/åè¡åå¹¶ï¼ã`renumberInvestSeq`ï¼è¡¨1æåéç½®ã表2æå¸å·éç½®ã表4å
¨è¡¨è¿ç»ï¼ã`InvestSection` çè¾
婿¹æ³ä¸å¹¶å å
¥ã表3ï¼äº¿å
æèµï¼åæ¬å°±æ¯å¨æçæï¼æªå¨ã |
| | | - éªè¯ï¼2026-07ï¼ï¼æ¨¡æ¿ 表1 å®¢è¿ 45 è¡/ç©æµ 121 è¡ï¼DB 46/132 â è¾åº 179 è¡ï¼46+133ï¼å
¶ä¸ 1 æ¡ä¸ºæ¢ææ¨¡ç³å¹é
éå¤ãèå·å¸çæµ·å½é
ç©æµåãï¼éæ¬æ¬¡å¼å
¥ï¼ï¼åå ° ç©æµå°èæ¨¡æ¿ 7 è¡âDB 38 ä¸ªâæ 31 è¡ï¼DB å¡«æ¥åä½ä¸ºåå °ç 38 个项ç®å
¨é¨ä¸è¡¨ï¼å«åç§°å¸¦æ¦æ±/é»ç³çè·¨å¸é¡¹ç®ï¼å±æ°æ®ç°ç¶ï¼ã表2 åäºäº 35 è¡=DB 35 ä¸ªï¼æ¨¡æ¿ 63 è¡æ æè¡ï¼ï¼è¡¨4 ç»æµå¼ºå¿ 38 è¡ï¼36 个 DB + 2 æ¡æ¢æéå¤å¹é
ï¼ææ±/åºåï¼ãå计/å/å¸å·å°èæ°å¼ä¸ DB èåä¸è´ï¼å
¨ç 11429050.52ãåå °ç©æµ 5111586ï¼ï¼åå¹¶åº 111 个ã0 éå ãéæè¡æ£ç¡®ä¸ç§»ï¼åºå·è¿ç»ï¼å®¢è¿ 1-46ãç©æµ 1-133ï¼ã |
| | | - 说æï¼æèµæ¨¡æ¿æ å
¬å¼ï¼ãå
¬å¼èå´åæ¥ãæ éå¤çï¼æ
客/åå¸å®¢è¿/è½èæ¨¡æ¿æ°æ®åºåºå®ï¼17/18 å¸å·è¡ï¼ä¸ä¼è¶
è¡ï¼è¥æªæ¥éè¦æè¡ï¼shiftRows ä¼èªå¨åæ¥å
¬å¼å¼ç¨ã |
| | | **â¢å
å®¡æ ¸ååºè¡¨ + 1~N 累计ï¼å·²å°±ç»ªï¼** |
| | | - `checkExportReady` å·²æ°å¢ `auditRows`/`audited` åæ®µï¼æ `audit_rule.report_type` â `audit_result.report_period` ç»è®¡ï¼ï¼å端导åºåå¼¹çªåºåããç¼ºæ°æ®ã/ãæªå®¡æ ¸ãããéªè¯ï¼è· CITY_TAXI å®¡æ ¸ï¼`POST /api/audit/execute?period=2026-07&reportType=CITY_TAXI`ï¼4 æ¡ï¼å ready æ¥å£ `cityTaxiDetail auditRows=4 audited=true`ï¼ä¸åæç¤ºæªå®¡æ ¸ã |
| | | - 1~N æç´¯è®¡è¯ä¹ç¡®è®¤ï¼`freightCum`/`midCumClass`/`passengerCumSum` å为 1..N ææ±åï¼å¦ 2026-07 ææ¥ç´¯è®¡=1-7æä¹åï¼æ
客åå¸å·æ¨¡æ¿æåº¦ååºå® 1-6 æï¼æ
fillMonths=min(currentMonth,6)ï¼è¶
åºé¨å以模æ¿åæ°ä¸ºåï¼ã |
| | | **å¾
确认/éç** |
| | | - å
¶ä½ç±»åå®¡æ ¸å°æªéè·ï¼å
¨åºæ¸
空åä»
H2032=1002 æ¡ + æ¬æ¬¡ CITY_TAXI=4 æ¡ï¼ï¼å¦é ready ä¸åæç¤ºæªå®¡æ ¸ï¼è¯·å¨é¡µé¢æ§è¡ H2031/INVEST/H204 å®¡æ ¸ï¼æè®© AI 代è·ï¼ã |
| | | - æèµæ¨¡ç³å¹é
åå¨éå¤ï¼å模æ¿è¡å¹é
å°åä¸ DB 项ç®ï¼ï¼å¦éå¯ç¨ãå·²ç¨é¡¹ç®éåã约æå¹é
ï¼ä¼æ¹åç°æè¾åºï¼é确认ï¼ã |
| | | - 表1 åå °ç©æµ 38 个 DB 项ç®å«å¤§éåç§°å¸¦æ¦æ±/é»ç³ç项ç®ï¼å¡«æ¥åä½=åå °ï¼ï¼è¥æ°æ®å£å¾æè¯¯éå¨å¯¼å
¥æºå¤´æ ¸å¯¹ã |
| | | - éçä¸åï¼7-12 ææ¨¡æ¿å¾
ç¨æ·æä¾ï¼è½èæ° 6 æ¡è§åéå¼å£å¾å¾
确认ï¼ä»£ç 仿ªæäº¤ Gitã |
| | | |
| | | - **2026-08-26ï¼ä¸åï¼M2 宿ï¼**ï¼æèµå¸å·å表æ¹é导å
¥è½å°å¹¶å
¨ééªè¯ï¼`/api/import/investCityBatch`ï¼åæ° period/category=客è¿ç«åº|ç©æµååºï¼æ«æ `docs/æèµ/è¾å
¥/{客è¿ï¼å®¢è¿ç«ï¼|ç©æµï¼ç©æµååºï¼}/{Næ}`ï¼è·³è¿ pdfï¼ï¼å®¢è¿ 6æ35/7æ46ãç©æµ 6æ132/7æ133 æ¡ï¼0 失败ï¼`total_investment` ä¿®å¤çæï¼èªåæºæ
§ç©æµå=69876ï¼å¯¼åºå计 2038714.59 ä¸ DB èåä¸è´ï¼ï¼ä¿®å¤ãæ»æèµ vs è®¡åæ»æèµï¼è¡¨å¤´å¸¦ç©ºæ ¼ï¼ã+ãå
¬å¼åå
æ ¼è¯»ç¼åå¼ï¼.et 跨表å
¬å¼ï¼ã+ãå¤ sheet åå²æä»¶ææ¥è¡¨æé sheetï¼è¥é³ .et æ¤åéé 2æ sheetï¼ããåäºäºæ¨¡æ¿ 63 项è§èåå¹é
35 è¦çï¼28 个çå®ç¼ºå£æ¸
åå¨ `_shots/five_year_report.txt`ï¼æ°æ®æºä¸å¨å¸å·å表ï¼éç¨æ·æä¾ï¼ã |
| | | - **2026-08-26ï¼åæ¨ï¼**ï¼ç¨æ· 4 项æè§å
¨é¨å¤ç宿¯ï¼è¯¦è§æ«å°¾ã2026-08-26 忍ä¼è¯ãï¼ï¼â 模æ¿ä¸è´æ§ââä¿®æ£ `application.yml` 模æ¿è·¯å¾ï¼passengerâ`docs/å
¬è·¯æ
客/è¾åº`ï¼æ°å¢ `energy.template-dir: docs/è½è`ï¼ï¼è½èæ¨¡æ¿æä»¶åæ¹ä¸ºç¨æ·ææ°æ´æ°ç `çæ_è½è¿æ±æ»è¡¨.xlsx`ï¼è´§è¿/æ
客/è½è/åå¸å®¢è¿ 10 å¼ å¯¼åºç»æ+åå¹¶+å宽å
¨é¨ä¸æ¨¡æ¿ PASSï¼â¡å®¡æ ¸è§åââH204 è½èè¡¥ 6 æ¡è§åï¼RULE_E005~E010ï¼å
± 10 æ¡ï¼éæ± 7 æ¡å
¨è¦çï¼ï¼å®ç°è§å管ç页ï¼å端 `RuleManageController` + `RuleManage.vue`ï¼ææ¥è¡¨ç±»åæ¥ç 6 ç±»å
¨é¨è§åãå¯å¯å/æ¹éå¼ï¼ï¼CITY_TAXI 7 æ¡ç¡®è®¤å¼æä¸å
¥åºå®æ´ï¼â¢ä¼ä¸è§£éè¡é«å缩ï¼ExplainReview.vue ç´§åè¡é« + è§£éåé髿»å¨ï¼ï¼â£éå¤å¯¼å
¥ç¡®è®¤å端å
¨é¨å¹çï¼åæ¥è¡¨æå
å åæï¼+ å端 loading é²è¿ç¹ï¼ä¸ä¼å两份ã |
| | | - **注æ**ï¼æ°æ®åºå·²äº 2026-08-26 00:45 æç¨æ·è¦æ±å
¨åºæ¸
空ï¼26 å¼ è¡¨ TRUNCATEï¼å¤ä»½ `_shots/db_backup_20260826_004520.sql` 28MBï¼ï¼`audit_rule` å·²æ¢å¤ 33 æ§æ¡ + æ°å¢ 6 æ¡=39 æ¡ï¼H2032=6/H2031=8/H204=10/INVEST=4/CITY_BUS=4/CITY_TAXI=7ï¼ï¼`sys_role` çé
置表ä»ä¸ºç©ºééæ°åå§åï¼ä¸å¡æ°æ®é鿰坼å
¥ã |
| | | - **2026-08-25ï¼æä¸ç»ï¼**ï¼æç¨æ·ç¡®è®¤å£å¾å®æï¼æ¥è¡¨çæé¡µéæï¼è´§è¿âå
¬è·¯æ
客âåå¸å®¢è¿å¨æ
客å³è¾¹âè½èâæèµï¼ãçæå
容æ¹ãå¤é䏿+导åºãåæé®ãè´§è¿/æ
客/åå¸å®¢è¿/æèµå
¨é¨æ¯æææ¥/å¹´æ¥ï¼è½èä¿æå£åº¦æ«æï¼ãè½èæ±æ»è¡¨æ¹ä¸ºæ¨¡æ¿åºç¨¿çæï¼æ¨¡æ¿_è½è¿æ±æ»è¡¨ .xlsxï¼ãæèµ 4 表å宽䏿 ·ä¾å¯¹é½ï¼æ ·ä¾æ åå¹¶/æ èªå®ä¹å宽ï¼ãä¼ä¸è§£éæ¯æâæªå¡«åâ+âä»
æ¾ç¤ºå·²å¡«åâå¼å
³ãæ°æ®å¯¼å
¥é¡µç¾åï¼ç¶æå/ç±»å说æ/é»è®¤æï¼ãè¯¦è§æ«å°¾äº¤æ¥è®°å½ã |
| | | - **2026-08-25ï¼æä¸ç»ï¼**ï¼æç¨æ·ç¡®è®¤å£å¾å®æï¼æ¥è¡¨çæé¡µéæï¼è´§è¿âå
¬è·¯æ
客âåå¸å®¢è¿å¨æ
客å³è¾¹âè½èâæèµï¼ãçæå
容æ¹ãå¤é䏿+导åºãåæé®ãè´§è¿/æ
客/åå¸å®¢è¿/æèµå
¨é¨æ¯æææ¥/å¹´æ¥ï¼è½èä¿æå£åº¦æ«æï¼ãè½èæ±æ»è¡¨æ¹ä¸ºæ¨¡æ¿åºç¨¿çæï¼æ¨¡æ¿_è½è¿æ±æ»è¡¨ .xlsxï¼ãæèµ 4 表å宽䏿 ·ä¾å¯¹é½ï¼æ ·ä¾æ åå¹¶/æ èªå®ä¹å宽ï¼ãä¼ä¸è§£éæ¯æâæªå¡«åâ+âä»
æ¾ç¤ºå·²å¡«åâå¼å
³ãæ°æ®å¯¼å
¥é¡µç¾åï¼ç¶æå/ç±»å说æ/é»è®¤æï¼ãè¯¦è§æ«å°¾äº¤æ¥è®°å½ã |
| | | - **2026-08-25ï¼ä¸åï¼**ï¼æ¥è¡¨çæ/ä¼ä¸è§£é/æ°æ®å¯¼å
¥ä¸é¡µ UI éæï¼è¯¦è§æ«å°¾äº¤æ¥è®°å½ï¼ï¼åå¸å®¢è¿ä¸æ¨¡åå并为ä¸å¼ å¡ãäºæ¨¡åç»ä¸ãå¾é+ä¸é®å
¨é¨zipããæ¥è¡¨ææ¯æææ¥/å¹´æ¥ãä¼ä¸è§£éæ©å± H2031/CITY_BUS/CITY_TAXIãæ°æ®å¯¼å
¥åç»+æè¿æ¹æ¬¡ãæ¥è¡¨çæççï¼sys_operation_logï¼ã |
| | | - **2026-08-25ï¼ææ°ï¼**ï¼æ°æ®åºè¿æ¥æ ¹å ä¿®å¤ï¼JDBC URL 缺 `allowPublicKeyRetrieval=trueï¼MySQL8 caching_sha2_password å¯¼è´ææ DB æ¥è¯¢ 500ï¼ï¼å·²å åå¹¶éå¯éªè¯ï¼åå¸å®¢è¿å
¬äº¤/åºç§ 2026-07 å®¡æ ¸ç»æéè·æ¢å¤ï¼53/6 æ¡ï¼ä¸ 08-24 ä¸è´ï¼ï¼6 å¼ æ¥è¡¨éæ°çææ°å¼å
¨é¨æ ¸å¯¹éè¿ï¼æ±æ»è¡¨ç½çº¦è½¦è¡ä¿®å¤ä¸ºæ¾å¼ 0ï¼è¯¦è§æ«å°¾äº¤æ¥è®°å½ï¼ã |
| | | - **2026-08-24ï¼ææ°ï¼**ï¼åå¸å®¢è¿å
¬äº¤/巡游åºç§æ¥è¡¨æ¹ä¸º**æç®å½ä¸æ¨¡æ¿åºç¨¿çæ**ï¼`åå¸å
¬äº¤_模æ¿.xlsx`/`巡游åºç§_模æ¿.xlsx`ï¼ï¼ä¿çæ¨¡æ¿æ é¢/表头/åå¹¶åå
æ ¼/å
¬å¼/æ ·å¼ï¼ä»
å¡«æ°æ®åºï¼å
¨ç 7 æåææ ä¸æ¨¡æ¿ 0 å·®å¼ã |
| | | - **2026-08-24ï¼ææ°ï¼**ï¼åå¸å®¢è¿ç¬¬ 2 æ¥ï¼å·¡æ¸¸åºç§ï¼å·²è½å°ï¼å·¡æ¸¸åºç§ææ¥å¯¼å
¥ï¼`/api/import/cityTaxi`ï¼å¸å·çº§ 17 è¡ï¼ãåºç§è½¦è¿æ¿è½¦è¾å¯¼å
¥ï¼`/api/import/cityTaxiAuth`ï¼42636 è¡ï¼å¸å·ç±è¯å·/æ¡£æ¡å·/ç»è¥æ/å°åæ£åæåï¼ã7 ç»å®¡æ ¸è§åï¼RULE_T001~T007ï¼report_type=CITY_TAXIï¼7 æå®¡æ ¸ 6 æ¡ï¼ãåå¸å·æ¥è¡¨ï¼`/api/report/export/cityTaxiDetail`ï¼å
¨ç+17 å¸å·Ã4 ææ ï¼ä¸ `巡游åºç§_模æ¿.xlsx` 7 ææ°å¼ 0 å·®å¼ï¼5860.24/34236.60/5351.24/27422.13ï¼ã第 1 æ¥å
¬äº¤ï¼CITY_BUSï¼ä¿ææ£å¸¸ï¼53 æ¡ï¼ã |
| | | - åå端åå¨è·ï¼å端 8090ï¼start-dev.ps1 å¯å¨ï¼æ¥å¿ backend.logï¼ãå端 8080ï¼vue-cli serveï¼çæ´æ°å·²å è½½å
¬äº¤+巡游åºç§å
¥å£ï¼ã |
| | | - **2026-08-24ï¼ææ°ï¼**ï¼åå¸å®¢è¿æ¥è¡¨ç³»åï¼6 表 + zipï¼æç®å½æ¨¡æ¿çæè½å°ï¼å
¬äº¤/åºç§/轨é轮渡æç»ãåå¸å·ç´¯è®¡ãåå¸å·æç»è¡¨ãæ±æ»ï¼è§ `docs/åå¸å®¢è¿/è¾åº/`ï¼è½¨é/è½®æ¸¡æ°æ®ä»å
¬äº¤æºæä»¶æ©å±å
¥åºï¼è¯¦è§ç»5ï¼ã |
| | | - åå¸å®¢è¿ç¬¬ 3 æ¥ï¼ç½çº¦è½¦æåï¼å¾
é¨çº§æ°æ®æºï¼æèµ M2ï¼å¸å·å表解æï¼å·²å®æï¼è§ä¸æ¹æ¡ç®ï¼ã |
| | | - æèµ M2ï¼å¸å·å表æ¹é导å
¥ï¼å·²å®æå¹¶éªè¯ï¼è¯¦è§æ«å°¾ã2026-08-26 ä¸åä¼è¯ï¼M2 å¸å·å表æ¹é导å
¥ãï¼ï¼28 个åäºäºé¡¹ç®ç¼ºå£æ°æ®æºå¾
ç¨æ·æä¾ã |
| | | ## äºãå¿«éå¯å¨ |
| | | ç»ä¸ç¨ä¸é®èæ¬ï¼å¿
é¡»æ²ç®±å¤/ææè¿è¡ï¼ï¼ |
| | | ``` |
| | | powershell -NoProfile -ExecutionPolicy Bypass -File start-dev.ps1 |
| | | ``` |
| | | - èæ¬ä¼èªå¨æ£æµ 8090/8080 æ¯å¦å·²çå¬å¹¶è·³è¿éå¤å¯å¨ï¼çå¾
就绪åæå°ç»æã |
| | | - æåæ å¿ï¼`backend.log` åºç° `Started TrafficAuditApplication`ï¼`frontend.log` åºç° `Compiled successfully` + `App running at:`ã |
| | | - 访é®å°åï¼å端 `http://localhost:8080`ï¼`/api` 代çå°å端 8090ï¼ã |
| | | - å¯å¨çä¸ä¸ªåå²åå对çï¼è§ `AGENTS.md`ï¼Path/PATH éå¤âä¸ç¨ Start-Processï¼node æ²ç®± EPERMâå¿
é¡»ææï¼ä¸åµå¥ powershell -Commandï¼ã |
| | | |
| | | ## ä¸ãé¡¹ç®æ¦è§ |
| | | - ç³»ç»ï¼äº¤éè´§è¿ç»è®¡å®¡æ ¸ç³»ç»ï¼æ¹åï¼ï¼å
å"交ä¼ç»H203-2表_é路货ç©è¿è¾æåº¦ç产æ
åµ"æ¼ç¤ºã |
| | | - å端ï¼Vue 2 + Element UIï¼`traffic-audit-web`ï¼ç«¯å£ 8080ã |
| | | - å端ï¼Spring Boot 2.x + MyBatis Plus + EasyExcelï¼`traffic-audit-server`ï¼ç«¯å£ 8090ã |
| | | - æ°æ®åºï¼MySQLï¼æ¬æº 3305 端å£ï¼åºå `traffic_audit`ï¼è´¦å·å¯ç è§ `traffic-audit-server/src/main/resources/application.yml`ï¼ã |
| | | - LLMï¼DeepSeek APIï¼`deepseek-chat`ï¼é
ç½®åæ ·å¨ application.ymlï¼æ³¨æä¸è¦æ API Key åè¿ä»»ä½ææ¡£ï¼ã |
| | | - æ ¸å¿æ¨¡åï¼æ°æ®å¯¼å
¥ï¼5ç±»ï¼ãå®¡æ ¸å¼æï¼è§å驱å¨ï¼ãAI åæï¼è±æ+æ¥åº+è§£éçæï¼ãæ¥è¡¨çæï¼3ç±»ï¼ãç¨æ·æéï¼ä¸åå离ï¼ã |
| | | |
| | | ## åã已宿åè½ï¼æªè³ 08-07ï¼ |
| | | - æ°æ®å¯¼å
¥ï¼H203-2ææ¥ãè¿æ¿è½¦è¾ã轨迹éç¨ãè§ä¸è§ä¸æåãè´§è¿éå¨è½¬éï¼å¯¼å
¥å¤±è´¥æ¾ç¤ºè¡å·ã |
| | | - å®¡æ ¸ï¼è§å表驱å¨ï¼æ´è½¦å¨ä½èå´ 4â¤ä¸â¤40ãè´§è¿ç±»åç¯æ¯ä¸æ+æ°éçï¼ï¼ç»ææ¯ææä¼ä¸åç§°/è§åè¿æ»¤ã |
| | | - AI åæï¼`AiChatDialog.vue`ï¼ï¼ç»æå Markdown æçãéåæåå¨ç»ï¼çº¦70å符/ç§ï¼ãæåæ¶åæ¥å¹³æ»æ»å¨ãæèä¸è¹¦è·³å°äººï¼åéå乿¾ç¤ºï¼ãè¯é³è¾å
¥+è¯é³ææ¥ã对è¯å¯æ¥æ°æ®åºãå¼¹çªåºå®å±å¹å±
ä¸åè§ã |
| | | - ä¼ä¸è§£éï¼ç¬ç«èåï¼ExplainReviewï¼ï¼ä¸å®¡æ ¸ç»æå离ï¼AI éæ¡åæã |
| | | - æ¥è¡¨çæï¼è´§è¿éåå¸å·æç»ãè´§è¿éæåãå¨è½¬éæåã |
| | | - å¸å±ï¼é¡¶é¨æ é¢+左侧èååºå®ï¼å
容åºè¡¨æ ¼å
鍿»å¨ï¼æ¬æµ®æç¤ºæ¡å·²é宽ï¼App.vue å
¨å± `.el-tooltip__popper` max-widthï¼ã |
| | | - ææ¡£ï¼`AGENTS.md`ã`start-dev.ps1`ã`docs/Codexåè½æ¸
å.md`ã`docs/æ¯æ¥åæä»»å¡.md`ã`docs/architecture.md`ã`docs/database.md`ã`docs/init.sql`ã |
| | | |
| | | ## äºãç¨æ·å好 / ä¸å¡è§åï¼å¡å¿
éµå®ï¼ |
| | | 1. å
¨ç¨ä¸æäº¤æµï¼ä¸´æ¶èæ¬/æä»¶æªç»åæä¸å ã |
| | | 2. æ¥è¡¨æ**以页é¢éæ©ä¸ºå**ï¼éå æå°±æ¯å æï¼å¿½ç¥ Excel éçæä»½æåï¼ï¼ç´¯è®¡=1~N æï¼N=æéæã |
| | | 3. è§ä¸è§ä¸æå表"å
¨ç"è¡ï¼**å¿ å®å¯¼å
¥ï¼ä¸è¦èªå·±æ±å**ï¼å¨è½¬éã忝é½ä¸æ ·ï¼æ¯ä»ä¹å°±åä»ä¹ï¼ã |
| | | 4. æ°å¼å¤çï¼**ä¸ç¨ BigDecimal**ï¼å°æ°ç¨ double 读å
¥/è¿ç®/åå¨ï¼æ´æ°åæ´æ°ï¼å¯æ¥å尾差ã |
| | | 5. è´§è¿éå¨è½¬é表æ¯**è¾å
¥**ï¼æ¯æå¯¼å
¥ï¼ï¼ä¸æ¯è¾åºï¼è´§è¿é=è§ä¸+è§ä¸å计ï¼è§ä¸è´§è¿éä» H203-2 æ 17 å¸å·å计ï¼å¸å·å
³ç³»è§ `docs/åå¸å·æå±ç»ç»æºæ.xlsx`ï¼ï¼è§ä¸=å计âè§ä¸ã |
| | | 6. AI äº¤äºæ°æ®**å
è±æååé**ï¼éæ±è§ `requirement/è´§è¿ç®å.docx` 第78è¡ï¼è®¾è®¡è§ `docs/database.md` 第ä¸ç« `llm_desensitize_map`ï¼ã |
| | | 7. AI åæè¾åºè¦"ä¸ç¼çå°ç»æåéç¹"ï¼æ é¢å ç²ãç»è®º/建议/便®çæ ç¾åã |
| | | 8. å®¡æ ¸ç»æä¸"ä¼ä¸è§£é AI åæ"æ¯ä¸¤ä¸ªç¬ç«åè½ï¼ä¸è¦æ··å¨ä¸èµ·ã |
| | | |
| | | ## å
ãææ¡£ç´¢å¼ |
| | | - éæ±ï¼`requirement/è´§è¿ç®å.docx`ï¼+ `docs/` 坿¬ï¼ã`docs/01-交éè¿è¾é¨é¨ç»è®¡è°æ¥å¶åº¦.docx`ã`docs/02-交éè¿è¾ä¼ä¸ç»è®¡è°æ¥å¶åº¦.docx`ã |
| | | - 模æ¿/çæè¡¨ï¼`docs/模æ¿_*.xlsx`ã`docs/çæ_*.xlsx`ã |
| | | - 设计ï¼`docs/architecture.md`ã`docs/database.md`ã`docs/init.sql`ã |
| | | - è¿ç»´ï¼`AGENTS.md`ã`start-dev.ps1`ã`docs/æ¯æ¥åæä»»å¡.md`ï¼æ¯æ¥åæèªå¨åå¾
å¯ç¨ï¼ã |
| | | |
| | | ## ä¸ãå¾
å / å·²ç¥äºé¡¹ |
| | | - æ¯æ¥åæèªå¨åå°æªåå»ºï¼æç¤ºè¯å·²å¤å¥½ï¼è§ `docs/æ¯æ¥åæä»»å¡.md`ï¼éè¦ automation å·¥å
·å¯ç¨çä¼è¯ï¼ã |
| | | - 宿¹ Codex æåæ¬æºè®¿é® 403ï¼`docs/Codexåè½æ¸
å.md` å¾
ç½ç»æ¢å¤åè¡¥å®æ¹æ ¸å®çã |
| | | - åç»æ©å±ï¼H203-1 å
¬è·¯æ
客ãW203 æ°´è·¯ãP203 港å£ãH204 è½æºçï¼éæ±ææ¡£ v2/v3 计åï¼ï¼å
æ H203-2 æ¼ç¤ºå好ã |
| | | |
| | | ## å
«ãæ¯æ¥äº¤æ¥çº¦å® |
| | | 1. æ°å¯¹è¯ç¬¬ä¸æ¥ï¼è¯» `AGENTS.md` + `HANDOFF.md`ï¼æ¶å AI åæ/å®¡æ ¸æ¶åè `docs/` 䏿°æ®åºè¡¨ç»æã |
| | | 2. æ¯å¤©ç»ææ¶ï¼æ´æ° `HANDOFF.md`ï¼è¿å±ãåãæ°éæ±ãå¾
åï¼ï¼åå
³å¯¹è¯ã |
| | | 3. 跨项ç®å¯å¤ç¨ç»éªæ²æ·å°æºå¨çº§é¡¶å± AGENTS.mdï¼æ¬é¡¹ç®ä¸åéå¤ï¼ã |
| | | ## ä¹ãGit ä»åºï¼2026-08-14 建ç«ï¼ |
| | | - è¿ç¨ï¼Gitee **ç§æ**ä»åº https://gitee.com/jichengxiong/trafficAudit ï¼åæ¯ `main`ï¼æäº¤èº«ä»½ xyc <jc_xiong@hotmail.com>ï¼ã |
| | | - ææé
ç½®ä¸å
¥åºï¼`traffic-audit-server/src/main/resources/application.yml` å·²å¨ `.gitignore` ä¸ï¼ |
| | | ä»åºå
åªæäº¤å ä½ç¬¦æ¨¡æ¿ `application.yml.example`ï¼`${DEEPSEEK_API_KEY}` çï¼ãæ°ç¯å¢ï¼å¤å¶æ¨¡æ¿ä¸º application.yml å¹¶å¡«çå®å¼ã |
| | | - æ¥å¸¸æµç¨ï¼`git add -A` â `git commit -m "..."` â `git push`ï¼HTTPS push éè¦ Gitee ç§äººä»¤çæå·²ä¿ååæ®ï¼ã |
| | | - 注æï¼`backend.log`ã`frontend.log`ã`node_modules/`ã`dist/`ã`target/` çä¸å
¥åºã |
| | | |
| | | ## åã交æ¥è®°å½ï¼2026-08-22 ~ 08-23ï¼æ©å±æ¨¡åè°ç ï¼ |
| | | |
| | | ### èæ¯ |
| | | ç¨æ·æåºå¨è´§è¿ï¼H203-2ï¼åºç¡ä¸æ©å±æ°åè½ï¼ä¸å¡èå´å«ï¼å
¬è·¯æ
客è¿è¾ãè½èãåå¸å®¢è¿ãæèµã |
| | | |
| | | ### å·²å |
| | | 1. **é读项ç®**ï¼AGENTS.md / HANDOFF.md / docs éæ±ä¸è®¾è®¡ / requirement/è´§è¿ç®å.docxï¼ææ¡ç°ç¶ï¼ä»£ç ä»
è´§è¿ä¸æ¡é¾è·¯ï¼ã |
| | | 2. **模æ¿å®æ**ï¼docs/æ©å±è¡¨æ¨¡æ¿/ ç¨æ·æ°æ¾ï¼ï¼ |
| | | - å
¬è·¯æ
客 H203-1ï¼ä¼ä¸ææ¥æç»ï¼28 åï¼+ è¿æ¿å¯¹æ¯ + åå¸å·çæè¡¨ï¼å®¡æ ¸ 4 ç»è§å |
| | | - è½è H204ï¼å£æ¥ï¼ï¼è½¦è¾ç²åº¦æç»ï¼18 åï¼+ è¿æ¿ä¿¡æ¯ + æ±æ»è¡¨ï¼å®¡æ ¸ 7 ç»è§åï¼2 ç»é跨表å¼ç¨è´§è¿ææ¥ï¼ |
| | | - åå¸å®¢è¿ï¼å¸å·çº§çæè¡¨ + ç½çº¦è½¦é¨çº§ä¸åæåï¼å
¬äº¤ 4 ç» + åºç§ 7 ç»è§å |
| | | - æèµï¼è¾å
¥=17 å¸å· 6/7 æå®¢è¿ç«åº+ç©æµååºææ¥ï¼**æ ¼å¼ä¸ç»ä¸**ï¼xls/xlsx/et/pdf æ··åï¼20~26 åï¼ï¼è¾åº=4 å¼ åºå®æ ¼å¼é¢å¯¼è¡¨ï¼å¦ææèµç³»ç»æ¥è¯¢å¯¼åºå¯å¯¹æ¥ |
| | | 3. **äº§åºææ¡£**ï¼ |
| | | - docs/æ©å±æ¨¡åè¯ä¼°.mdï¼æ¨¡åÃå¤ç¨åº¦è¯ä¼°ã宿½é¡ºåºï¼H203-1âH204âåå¸å®¢è¿âæèµï¼ãæ¶æå»ºè®®ï¼æ¥è¡¨ç±»å注å表åãå®¡æ ¸å¼ææ°å¢æ¯è¾ç±»åï¼ãå·¥ä½éç²ä¼° |
| | | - docs/弿ºé¡¹ç®å¯¹æ¯.mdï¼GitHub æ£ç´¢ç»è®ºï¼æ ç´æ¥åç±»ï¼ãåç±»åç
§é¡¹ç®ï¼NIC MSR / RuoYi / JimuReport+DataEase / Griffin+GX+Sodaï¼å¯¹æ¯ä¸å¯ç¤º |
| | | 4. **ä¸´æ¶æä»¶**ï¼æ ¹ç®å½ .tmp_*.pyï¼è§£æèæ¬ï¼+ .tmp_è´§è¿ç®å.txtï¼æªå ï¼çº¦å®ï¼ï¼xlrd å·²è£
å
¥ bundled pythonï¼D:\...codex-runtimes\dependencies\pythonï¼ã |
| | | |
| | | ### å
³é®ç»è®º |
| | | - èªç æ¹åæ£ç¡®ï¼ä¸å¼å
¥å¤§æ¡æ¶éåï¼æå¼å¾åé´çæ¯ RuoYi 代ç çæå¨ï¼æ©å±æéï¼ä¸å®¡æ ¸è§å声æå¼è®¾è®¡ï¼Griffin ç念ï¼ã |
| | | - 建议å¼å·¥é¡ºåºï¼å
¬è·¯æ
客 H203-1ï¼é«å¤ç¨ãå¿«éè§æï¼â è½è H204ï¼è¡¥è·¨è¡¨å¼ç¨ï¼â åå¸å®¢è¿ï¼è§åæå¤æï¼â æèµï¼ç¬ç«æ¨¡åï¼å
å®"å¯¹æ¥æèµç³»ç» vs è§£æå¸å·æ¥è¡¨"ï¼ã |
| | | |
| | | ### å¾
å / ä¸ä¸æ¥ |
| | | - çç¨æ·ç¡®è®¤å¼å·¥æ¹åï¼ç¨æ·è¯´"å
讨论åå¼å·¥"ï¼ã |
| | | - è¥å¼å·¥ï¼å
å H203-1 è¡¨ç»æè®¾è®¡ + è§ååå§å SQL + å端èåã |
| | | - ææ¯åºç»è®°ï¼Vue2/SB2 å级ã代ç çæå¨ãæä½æ¥å¿æ³¨è§£åï¼è¿æï¼ã |
| | | - Gitee API æªè®¤è¯è¿å空ï¼å¯äººå·¥æµè§å¨è¡¥æ¥ gitee æç´¢ã |
| | | |
| | | |
| | | ## åä¸ã交æ¥è®°å½ï¼2026-08-23ï¼H203-1 å
¬è·¯æ
客模åå·²å¼å·¥å¹¶å®æä¸»ä½ï¼ |
| | | |
| | | ### ç¨æ·ç¡®è®¤ç 3 个å£å¾ï¼å¼å·¥å讨论ç»è®ºï¼ |
| | | 1. æ¥è¡¨æï¼ä¸»æ¨¡å¼æ²¿ç¨ãå¹´-æãç°ç®ç°å¯¼ï¼ä»»æå岿份坿¥ï¼ï¼å¦å ãæå¹´ã模å¼ï¼é年份âå
¨å¹´ 12 个æ+累计+忝ï¼ï¼ä¸åé¢çæã |
| | | 2. åå¸å·æç»éçåé
å乡å
¬äº¤/巡游åºç§/ç½çº¦è½¦ï¼**ä¿çææ è¡ä½ç空**ï¼è¡¨å°¾å 说æè¡ï¼æ°æ®å¾
åå¸å®¢è¿æ¨¡åæ¥å
¥ï¼ã |
| | | 3. è¿æ¿è½¦è¾æ°ï¼éæ
å®¢ææ¥ä¸èµ·å¯¼å
¥ï¼åæä»¶ç¬¬äº sheetï¼ï¼ææ¥è¡¨æåå¨ãéå¤å¯¼å
¥è¦çï¼å®¡æ ¸æåæ¥è¡¨æå¹é
ã |
| | | |
| | | ### æ¬è½®å·²å®æï¼å
¨é¨ç»æ ·ä¾æ°æ®èè°éªè¯ï¼ |
| | | 1. **æ°æ®åº**ï¼docs/init.sql 追å ãå
«ãå
¬è·¯æ
客è¿è¾ï¼H203-1ï¼ã段ï¼ï¼ |
| | | - æ°è¡¨ h2031_enterprise_monthlyï¼28 åæ®µï¼å¯¹é½æ ·ä¾åï¼ãpassenger_auth_vehicleï¼ä¼ä¸ä»£ç /åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼ |
| | | - é¢ç½® H2031 è§å 8 æ¡ï¼RULE_P001~P008ï¼report_type=H2031ï¼compare_type æ°å¢ MOM_EQ_ZERO/MOM_PCT/MOM_EQ/EXISTENCEï¼ |
| | | 2. **å端**ï¼ |
| | | - 导å
¥ï¼`DataImportService.importPassengerMonthly`ï¼sheet0 ææ¥æè¡¨å¤´è§£æ + sheet1 è¿æ¿åºå® 4 åï¼å¹çè¦çï¼åªæ¸
H2031 è§åå®¡æ ¸ç»æï¼ä¸è¯¯å è´§è¿ç»æï¼ï¼`/api/import/passengerMonthly` |
| | | - å®¡æ ¸ï¼`AuditEngineService.executePassengerAudit`ï¼4 ç»è§åï¼è¿æ¿ DIFF 车è¾Â±3/载客ä½Â±50ï¼å®¢è¿é+å¨è½¬éç¯æ¯=0ï¼å¹³åè¿è·ç¯æ¯Â±10%ï¼å
车平åè¿è·=ä¸æï¼æ ç线/æ å
车æéçç¾ï¼ï¼`/api/audit/execute|results` å¢å reportType åæ°ï¼H2032/H2031ï¼ |
| | | - æ¥è¡¨ï¼`ReportExportService.exportPassengerCityDetail(period, mode)`ï¼month/year ä¸¤ç§æ¨¡å¼ï¼å½å¹´ 1-N ææå
¨å¹´ 12 æ + 累计 + 忝ï¼èªå¨å¸¦åºè¿ 3 å¹´åºåï¼å
¨ç=17 å¸å·æ±æ»ï¼å®¢è¿é/å¨è½¬éæ¢ç®ä¸äººãå¹³åè¿è·=å¨è½¬é/客è¿éå æï¼ï¼`/api/report/export/passengerCityDetail?period=&mode=` |
| | | - æ°æ®æ¥çï¼`DataViewController` æ°å¢ h2031 / passengerAuth ç±»å |
| | | 3. **å端**ï¼æ°æ®å¯¼å
¥å ãH203-1æ
å®¢ææ¥ãï¼æ°æ®æ¥çå ãH203-1æ
å®¢ææ¥/æ
å®¢è¿æ¿è½¦è¾ãï¼å®¡æ ¸ç»æå æ¥è¡¨ç±»ååæ¢ï¼æ¥è¡¨çæå æ
客åºåï¼ææ¥/å¹´æ¥åæ¢ï¼ï¼å·¥ä½å°ææ¡æ´æ° |
| | | 4. **ææ¡£**ï¼docs/database.md è¡¥ H203-1 两表 + 14 æ¡è§å + æ
客æ¥è¡¨è¡ |
| | | |
| | | ### èè°ç»æï¼æ ·ä¾ 2026 å¹´ 6 æï¼769 å®¶ä¼ä¸ï¼ |
| | | - 导å
¥ï¼769 è¡æåï¼5 æ/6 æå䏿¬¡ï¼5 æä¸ºæ¨¡æä¸ææ°æ®ï¼ |
| | | - å®¡æ ¸ 2026-06ï¼å
± 2057 æ¡é®é¢ï¼6 ç±»è§åå
¨é¨è§¦åéªè¯éè¿ï¼è¿æ¿å¯¹æ¯ 67/110ãç¯æ¯=0 å 737ãå
车平åè¿è·=䏿 397ãæ å
车æé 4ãå¹³åè¿è·ç¯æ¯ 5ï¼ |
| | | - æ¥è¡¨ï¼ææ¥ 94 å / å¹´æ¥ 106 åï¼ç´¯è®¡=5+6 æã忝ä¸å»å¹´åºåæ£ç¡®ã说æè¡å¨ |
| | | - å端 4 个 chunk å·²å
嫿°é¡µé¢ï¼js/3,4,5,7.jsï¼ |
| | | |
| | | ### ç¯å¢éè¦ååï¼å¡å¿
è®°å½ï¼ |
| | | - æ¬æº MySQL 5.7ï¼D:\mysql\mysql-5.7.41-winx64ï¼åæ¥æ²¡å¨è·ï¼ä¸ root å¯ç ä¸é
ç½®ä¸ç¬¦ï¼å·²ç¨ --skip-grant-tables éç½® root å¯ç 为 `root` å¹¶æ£å¸¸å¯å¨å¨ **3305**ï¼ä¸ application.yml ä¸è´ï¼ã |
| | | - traffic_audit åºåæ¥æ¯æ å
³æ§ç»æï¼audit_results/reports ç 5 å¼ è¡¨ï¼ï¼å·²æ README æµç¨éè·å®æ´ docs/init.sql é建项ç®åºï¼éçæ§è¡¨ audit_results/audit_rules/report_data/reports æªå ï¼æ 害ï¼ã |
| | | - å¯å¨ï¼`powershell -NoProfile -ExecutionPolicy Bypass -File start-dev.ps1`ï¼æ²ç®±å
ç´æ¥ ProcessStartInfo èµ·åå°è¢«çç¥æ¦æªï¼å¿
须走 start-dev.ps1ï¼ã |
| | | |
| | | ### å·²ç¥è¾¹ç / å¾
å |
| | | - H203-1 ä¼ä¸è§£é页ï¼ExplainReviewï¼ä»åªå H2032ï¼æ
客 sheet æ æ ¸å®è§£éåï¼æä¸æ¥è§£éæµç¨ï¼éæ±æªè¦æ±ï¼ã |
| | | - å®¡æ ¸ç»ææ reportType è¿æ»¤å¨åç«¯åæ¢ï¼audit_result.report_id 忶坿å h2032/h2031ï¼attachInfo å·²å
¼å®¹å表ã |
| | | - ä¸ä¸æ¥ï¼è½è H204ï¼è½¦è¾ç²åº¦ï¼éå®¡æ ¸å¼æè¡¥ CROSS_TABLE 跨表å¼ç¨è´§è¿ææ¥ï¼â åå¸å®¢è¿ï¼TREND/RATIO_CHANGE è§å + é¨çº§ä¸åæ°æ®æºï¼â æèµï¼å
å®å¯¹æ¥æèµç³»ç» vs è§£æå¸å· Excelï¼ã |
| | | - å¾
ç¨æ·ç¡®è®¤åæäº¤ Gitï¼ä»åº Gitee ç§æï¼è§ç¬¬ä¹èï¼ã |
| | | |
| | | ### 2026-08-23 IDEA æå¨è¿è¡æ¥ãæ¾ä¸å°ææ æ³å 载主类 com.trafficaudit.TrafficAuditApplicationã |
| | | - æ ¹å ï¼ä¸æ¯ä»£ç é®é¢ãIDEA ç Maven 导å
¥/ç´¢å¼éå»ºå°æªå®ææ¶ç¹ Runï¼ raffic-audit-server\target\classes 䏿²¡æç¼è¯åºç .classï¼åªå© ymlï¼ï¼Spring Boot è¿è¡é
ç½®ç Make æ¥éª¤æ²¡äº§åºï¼classpath éæ²¡æä¸»ç±»ã |
| | | - ä¿®å¤ï¼å½ä»¤è¡æ§è¡ cmd /c mvn.cmd -q -f traffic-audit-server\pom.xml compile è¡¥åº 55 个 .classï¼å« TrafficAuditApplication.classï¼ï¼åæäºå½ä»¤è¡å端é¾ï¼cmd 33764 / mvn java 28228 / app java 15952ï¼ç± start-dev.ps1 å¯å¨ï¼ï¼éæ¾ 8090ã |
| | | - ç¨æ·åç¹ IDEA ç»¿è² â¶ å³å¯ï¼è¥ä»å¤±è´¥ï¼Build â Rebuild Projectï¼æ File â Invalidate Caches and Restartã |
| | | - 注æï¼ç¨æ·è´´çè¿è¡å½ä»¤é Java è·¯å¾æ¾ç¤º jdk1.8.0\_202ï¼å¸¦ä¸åçº¿ï¼æ¯å¤å¶æ¾ç¤ºè¯¯å·®ï¼å®é
JDK æ¯ C:\Program Files\Java\jdk1.8.0_202ï¼åå¨ï¼æ éæ¹ï¼ã |
| | | - å端 8080ï¼node 31196/24168ï¼ä»å¨è·ï¼ä»£çæå 8090ï¼IDEA å端ä¸èµ·å³å¯ç¨ã |
| | | |
| | | ### 2026-08-23 ä¸åä¼è¯ï¼IDEA è·é + æ°æ®æ¥è¯¢å¤±è´¥ææ¥ï¼å·²è§£å³ï¼ |
| | | **æç»ç¶æï¼é¡¹ç®å¯æ£å¸¸ä½¿ç¨ï¼æ
客è¿è¾æ°æ®å¯å¯¼å
¥** |
| | | - å端æç»å¨ **IntelliJ IDEA** éè·éï¼ç¨æ·æå¨å¨ IDEA æå¼é¡¹ç®åï¼éè¿ UI èªå¨å触åè¿è¡ï¼Shift+F10ï¼ï¼æåå¯å¨ `TrafficAuditApplication`ï¼æç»è¿ç¨ PID 9188ï¼14:42 å¯å¨ï¼8090 çå¬ï¼ãæ¤åç¨æ·å¯å¨ IDEA éç´æ¥ç¹ â¶/â 管çå端ã |
| | | - å端ï¼8080 ä¸éæçº¿ï¼å·²ç¨ `npm run serve` éæ°æèµ·ï¼`traffic-audit-web/frontend.log` è®°å½ï¼PID 3324ï¼ï¼`/api` ä»£çæ£å¸¸ã |
| | | - ç¯å¢ç¡®è®¤ï¼IDEA è·¯å¾ `D:\Development_Tool\IntelliJ IDEA 2020.1.1\bin\idea64.exe`ï¼æ³¨ææ¯ `Development_Tool` æ ä¸å线ï¼ï¼JDK `C:\Program Files\Java\jdk1.8.0_202`ï¼IDEA è¿è¡é
ç½® `TrafficAuditApplication`ï¼module=traffic-audit-serverï¼å·²åå¨äº `.idea/workspace.xml`ï¼æ éæ°å»ºã |
| | | |
| | | **æ°æ®æ¥è¯¢å¤±è´¥ææ¥è®°å½ï¼éè¦ï¼é¿å
ä¸ä¸ªä¼è¯éå¤è¸©åï¼** |
| | | - ç°è±¡ï¼å端 `DataView.vue` æ¥ãæ°æ®æ¥è¯¢å¤±è´¥ãï¼å端 `/api/data/list?type=h2032` çå
¨é¨ 500ï¼åªæ default 忝ç type è¿å 200ï¼ã |
| | | - ææ¥ç»è®ºï¼**䏿¯ä»£ç /æ°æ®åºé®é¢**ãå·²æ ¸å¯¹ï¼7 å¼ è¡¨å
¨é¨åå¨ä¸ä¸ºç©ºãå®ä½å段ä¸è¡¨åå®å
¨å¹é
ï¼èæ¬éä¸å¯¹æ¯ï¼ãSQL é»è¾æ£å¸¸ã |
| | | - éªè¯æ¹æ³ï¼ä¸´æ¶å½ä»¤è¡èµ· 8091 è¯æå®ä¾ï¼`mvn spring-boot:run` å `-Dspring-boot.run.arguments="--server.port=8091 ..."`ï¼åæ°å¿
é¡»ç¨å¼å·å
裹ãç©ºæ ¼åéï¼ï¼åä¸ä»½ä»£ç ææç±»åæ¥è¯¢å
¨é¨ 200 â æé¤ä»£ç /DBã |
| | | - ç»æï¼ä¹å IDEA å®ä¾ï¼åä¸ PID 9188 æªéå¯ï¼ææç±»å乿¢å¤ 200ï¼å¤æä¸º**å¯å¨åæç临æ¶å¼å¸¸**ï¼å¯è½ä¸ IDEA Make å¢éç¼è¯/classpath 就绪æ¶åºæå
³ï¼ï¼æ éä¿®å¤ä»£ç ã |
| | | - å·²éªè¯ç±»åï¼`h2032 / transportAuth / trackMileage / scaleSplit / freightTurnover / h2031 / passengerAuth`ï¼8090 ç´è¿å 8080 å端代çå 200ãè¯æå®ä¾å·²åï¼8091 æ æ®çè¿ç¨ï¼ã |
| | | - è¥åéå°åç±» 500ï¼å
ç´æ¥ curl `http://localhost:8090/api/data/list?type=h2032` 夿åç«¯ç¶æï¼å端没é®é¢å°±å·æ°é¡µé¢ï¼å¿
è¦æ¶è®©ç¨æ·å¨ IDEA é **Build â Rebuild Project** åé Runã |
| | | |
| | | **æ¬ä¼è¯å·²è®¨è®ºãå¾
ä¸ä¸ªä¼è¯å¼å·¥çå
容ï¼ç¨æ·å·²ç¡®è®¤æ¹åï¼** |
| | | 1. éæ±èå´ï¼è´§è¿å®¡æ ¸è§åï¼å·²æï¼+ **å
¬è·¯æ
客è¿è¾ï¼H203-1ï¼å·²å®ç°å¹¶å¯å¯¼å
¥ï¼** + **åå¸å
¬äº¤/åå¸å®¢è¿** + **æèµæ¨¡å**ï¼ç¨æ·å·²ææ¨¡æ¿æ¾å° `æ©å±è¡¨æ¨¡æ¿`ï¼ã |
| | | 2. æ¥è¡¨ä½éªéæ±ï¼ç¨æ·é®é¢ 1ï¼ï¼å¸æ**æå¹´/ææå¨æåºè¡¨**ââä¸é¢çæææè¡¨ï¼èæ¯ç¨æ·æ¥è¯¢åªå¹´åªæå°±çæåªç§è¡¨ï¼åèæ¥è¡¨å¯¼åºç°ææ¨¡å¼å卿忰åï¼ææ¥/å¹´æ¥å·²å¨æ
客模åå®ç° `passengerCityDetail?period=&mode=`ï¼å¯ä½æ ·æ¿ï¼ã |
| | | 3. åè½ 2/3ï¼ç¨æ·é®é¢ 2ã3ï¼ï¼æ´ä½æè·¯åèè´§è¿æ¨¡åçåæ³ï¼å¯¼å
¥âå®¡æ ¸âæ¥è¡¨ åé¾è·¯ï¼ï¼å
·ä½ç»èå¾
æ°ä¼è¯æ `docs/æ©å±æ¨¡åè¯ä¼°.md` å±å¼ã |
| | | 4. ç«å/弿ºå¯¹æ¯å·²äº§åºï¼`docs/弿ºé¡¹ç®å¯¹æ¯.md`ï¼ç»è®ºï¼æ ç´æ¥å¯å¤ç¨ç弿ºç«åï¼èªç +模å忩屿¯åç路线ï¼ã |
| | | 5. 代ç 仿ªæäº¤ Gitï¼Gitee ç§æä»åºï¼ââå¾
ç¨æ·ç¡®è®¤åæäº¤ã |
| | | |
| | | **ä¸ä¸ªä¼è¯å»ºè®®ä»åªå¼å§** |
| | | - å
è®©ç¨æ·ç¡®è®¤ä¼å
级ï¼a) 卿年/ææ¥è¡¨çæï¼ä½éªä¼åï¼æ¹å¨éä¸å¨æ¥è¡¨å¯¼åºï¼â b) åå¸å
¬äº¤/åå¸å®¢è¿æ¨¡å â c) æèµæ¨¡åã |
| | | - è¥ä¼è¯å次åé¿ï¼ç»§ç»æè¿å±è¿½å å°æ¬æä»¶æ«å°¾å¹¶æéç¨æ·å¼æ°ä¼è¯ã |
| | | |
| | | ### 2026-08-23 æé´ä¼è¯ï¼æ
客ä¸å£å¾ 3 表 + è½è H204 模åï¼å·²å®æå¹¶éªè¯ï¼ |
| | | **éæ±ï¼ç¨æ·åè¯ï¼** |
| | | 1. ãå
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµãæ ¹æ®ãå
¬è·¯æ
å®¢ææ¥ãçæï¼å
¬è·¯æ
客åå¸å·æç»ï¼å·²æï¼+ **ä¸å£å¾æç»ãä¸å£å¾æåãä¸å£å¾åæ**ï¼æ¬æ¬¡æ°å¢ï¼ã |
| | | 2. ãé路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼ãæ ¹æ®ã第äºå£åº¦è´§è½¦è½èæç»ãä¸ãè¿æ¿è½¦è¾ä¿¡æ¯ã对åºåæ®µæ ¡éªï¼**æè¯¯æ¥åºæé**ï¼æ 误åçæãè½è¿æ±æ»è¡¨ãã |
| | | |
| | | **已宿ï¼å端已ç¼è¯ï¼ç«¯å°ç«¯éªè¯éè¿ï¼** |
| | | - æ°æ®å¯¼å
¥ï¼`DataImportService.importEnergyMonthly`ï¼è½èæç» sheet0ï¼æ¯è¡ 5 ç»Ã18 åï¼åèµ·ç¹ 0-based 3/21/39/57/75ï¼è¿æ¿ sheet1 åºå® 5 åï¼ç è¡¨è½¬ä¸æï¼å¹çå é¤å period 两表+H204 å®¡æ ¸ç»æï¼å¤±è´¥å¸¦è¡å·ï¼ï¼ç«¯ç¹ `POST /api/import/energyMonthly`ãå®ä½ `EnergyVehicleQuarterly` ç `fuelPer100km` å äº `@TableField("fuel_per_100km")`ï¼è¡¨åå带ä¸åçº¿ï¼æ¾å¯¼è´å¯¼å
¥å
¨å¤±è´¥ï¼ã |
| | | - å®¡æ ¸ï¼`AuditEngineService.executeEnergyAudit`ï¼4 æ¡è§åå·²å
¥åºï¼RULE_E001/E002 车è¾ç±»å/çæç±»å CROSS_DIFFãRULE_E003 æ è®°å¨ä½ CROSS_DIFF threshold=0.05 ä¸çµå¼è½¦(ç 11)ç¨åçµå¼è´¨éãRULE_E004 EXISTENCE_AUTH 车ç缺失ï¼ï¼`AuditController` å·²å H204 åæ¯ãæ ·ä¾ç»æ 577 æ¡ï¼å¨ä½ 383ãè¿æ¿ç¼ºå¤± 192ã车è¾ç±»å 2ï¼ã |
| | | - æ±æ»è¡¨ï¼`ReportExportService.exportEnergySummary(period)` â å
¨ç + 17 å¸å·åä¸å¼ sheetï¼æçæç±»åå 6 è¡ + å计ï¼å¹³åå¨ä½=æ»å¨ä½/è½¦è¾æ°ãå¹³åè¿è·=å¨è½¬é/è´§è¿éãç¾å
¬éåè=æ¶èé/æ»è¡ç¨*100ãå¨è½¬éåè=æ¶èé/å¨è½¬é*100*ç³»æ°ï¼æ´æ²¹0.86*1.4571ãæ±½æ²¹0.73*1.4714ãLNG/CNG 1.7572ã纯çµ0.1229ãæ°¢0.3329*12.1951ï¼ãæ ·ä¾å¯¼åºä¸æ¨¡æ¿ä¸è´ï¼ä»
æ»è¡ç¨/载货éç¨æ¯æ¨¡æ¿ç¤ºä¾å° 34ï¼ç»æºæä»¶ç´æ¥æ±åç¡®è®¤æ¯æ¨¡æ¿æ§æ°æ®ï¼é代ç é®é¢ï¼ã |
| | | - æ
客ä¸å£å¾ï¼`exportPassengerMidDetail/Rank/Analysis(period, mode)`ï¼month/year 卿åºè¡¨ï¼æç»=客è¿éå+å¨è½¬éåå 18 å°åºÃ5 ææ ï¼æå=å·¦å彿+å³å 1-N æç´¯è®¡ï¼ååå为客è¿é/å¨è½¬é+æå/忝/å¢éæåï¼åæ=å
¨ç累计+忝+å æ¯ï¼ï¼ç«¯ç¹ `/api/report/export/passengerMidDetail|MidRank|MidAnalysis`ã |
| | | - å端ï¼`ReportExport.vue` æ
客å¡çå 3 个ä¸å£å¾æé® + H204 è½è¿æ±æ»å¡çï¼`DataImport.vue` å `energyMonthly` 导å
¥ç±»åï¼`AuditResult.vue` å H204 æ¥è¡¨ç±»åã |
| | | - ææ¡£ï¼`docs/init.sql` 追å ãä¹ãH204ãæ®µï¼ä¸¤è¡¨ DDL + 4 æ¡è§åï¼ã`docs/database.md` è¡¥ h204 两表说æï¼`HANDOFF.md` æ¬æ®µã |
| | | - ä¿®å¤ï¼`AuditEngineService.attachInfo` æ¹ä¸ºæè§å reportType å表åå¡«ä¼ä¸åç§°ï¼h2032/æ
客/H204 ä¸è¡¨ id åèªèªå¢ï¼åæ¥ putIfAbsent ä¼ä¸²ä¼ä¸åï¼ã |
| | | |
| | | **å½åè¿è¡ç¶æï¼23:31ï¼** |
| | | - å端ï¼å½ä»¤è¡ `start-dev.ps1` å¯å¨ï¼8090ï¼æ¥å¿ `backend.log`ï¼PID é¾ cmdâmvnâjavaï¼ã**IDEA å®ä¾å·²ä¸å¨**ï¼æ java è¿ç¨ï¼ï¼ç¨æ·è¥è¦å IDEA é **Build â Rebuild Project** åå Runã |
| | | - å端ï¼`npm run serve` éå¯ï¼8080ï¼PID 15900ï¼æ¥å¿ `traffic-audit-web\frontend.log`ï¼ã |
| | | - DB å·²æ H204 æ ·ä¾æ°æ®ï¼1284 è¾ + 1114 è¿æ¿ï¼period=2026-06ï¼ä¸ 577 æ¡å®¡æ ¸ç»æï¼é¡µé¢å¯ç´æ¥æ¥çã |
| | | - ä¸´æ¶æä»¶ï¼é¡¹ç®æ ¹ `energy-test.log`ï¼8091 è¯ææ¥å¿ï¼ä¿çæªå ï¼ä¸´æ¶ py èæ¬å¨ `%TEMP%\check_*.py`ã |
| | | |
| | | **å·²ç¥è¾¹ç/å³ç** |
| | | - ä¸å£å¾çåé
å乡å
¬äº¤/巡游åºç§/ç½çº¦è½¦æ æ°æ®æºï¼çåå¸å®¢è¿æ¨¡åï¼ï¼æç»/æå/åæä¸è¿äºè¡ç空ï¼å
¬è·¯ç线=æ
å®¢ææ¥ä¸~åç±»+å
车åè®¡ï¼æ»=åè
ä¹åï¼å½å=å
¬è·¯ç线ï¼ã |
| | | - è½è车è¾ç±»åç â䏿éç¨åæ¨ç 表ï¼01 æ®é/02 å¹³æ¿/03 仿
/04 å¢å¼/06 ç½/07 ç¹æ®ç»æ/08 èªå¸/09 车è¾è¿è¾è½¦/11 åæçµå¼è½¦ï¼ï¼å®¡æ ¸å¯¹æ¯æ¶å
å»æè¿æ¿"é/ä¸/è½»å"åç¼åæå
许éåå¹é
ï¼é¿å
误æ¥ã |
| | | - è½èæ±æ»åªç»è®¡è½èæç»å
¨é¨è½¦è¾ï¼å«è¿æ¿ç¼ºå¤±çï¼ï¼"è¿æ¿ç¼ºå¤±"ç±å®¡æ ¸æéèéåé¤ã |
| | | |
| | | **ä¸ä¸ªä¼è¯å»ºè®®** |
| | | - åå¸å®¢è¿ï¼å
¬äº¤/åºç§/ç½çº¦è½¦æ°æ®æºï¼â æèµæ¨¡åãè¥ç¨æ·å IDEA è¿è¡ï¼éå
Rebuildã |
| | | |
| | | ### 2026-08-24 ä¼è¯ï¼æèµæ¨¡å宿 + 宿½æ¹æ¡ï¼å¾
ç¨æ·ç¡®è®¤åå¼å·¥ï¼ |
| | | **产åº** |
| | | - 已宿 `docs/æ©å±è¡¨æ¨¡æ¿/æèµ/` å
¨é¨æ ·ä¾ï¼è¾åº 4 å¼ é¢å¯¼è¡¨ãæèµç³»ç»å¯¼åºãå¸å·ä¸æ¥å®¢è¿/ç©æµæ¥è¡¨ãå
¨çæ±æ»ä¸é´è¡¨ï¼ï¼ç»è®ºä¸æ¹æ¡è§ **`docs/æèµæ¨¡å宿½æ¹æ¡.md`**ï¼æ°å¢ï¼ã |
| | | - å
³é®åç°ï¼ |
| | | - æèµç³»ç»å¯¼åºï¼`æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7).xls`ï¼8 åæå¹²åï¼ä½**æ å¸å·ãæ è¿åº¦/æ¹å¤å段**ï¼åªè½åå®¡æ ¸æ¯å¯¹åºåã |
| | | - å¸å·ä¸æ¥è¡¨æ ¼å¼ä¸ç»ä¸ï¼åæ° 18~24ã.xls/.xlsx/.et/.pdf æ··åãæ é¢è¡ä½ç½®ä¸ä¸ï¼ï¼é容éè§£æã |
| | | - å
¨çæ±æ»ä¸é´è¡¨ï¼`6æ/7æå®¢è¿ç«æèµæç»è¡¨.xlsx`ã`6æç©æµç«åºæèµæç».xls`ï¼**æ ¼å¼ç»ä¸**ï¼å»ºè®®ä¸æä¸»å
¥å£ã |
| | | - 4 å¼ è¾åºè¡¨ = å䏿¹é¡¹ç® 4 ç§è§è§ï¼å
¨çé¢å®æè®¡å / åäºäºç©æµéç¹ / â¥1亿è§ä¸ / ç»æµå¼ºå¿ï¼ï¼æ½ä¸å¥é¡¹ç®ä¸»æ¡£å³å¯å¤ç¨ã |
| | | - æ¹æ¡è¦ç¹ï¼ä¸¤æè½å°ï¼M1 å
¨çæ±æ»å¤§è¡¨å¯¼å
¥+4 å¼ æ¥è¡¨ï¼M2 å¸å·å表容éè§£æ+ç³»ç»å¯¼åºæ¯å¯¹å®¡æ ¸ï¼ï¼æ°æ®æ¨¡å `investment_project` + `investment_monthly` ä¸¤å¼ è¡¨ï¼å®¡æ ¸è§å report_type=INVESTï¼CROSS_DIFF/RANGE/LOGIC/NULL_CHECKï¼ï¼4 个导åºç«¯ç¹ `investPlan/investFiveYearLogistics/investBillion/investCounty`ã |
| | | |
| | | **å¾
ç¨æ·ç¡®è®¤ï¼æå¤©å¼å·¥åï¼** |
| | | 1. ä¸»æ°æ®æºï¼å
"å
¨çæ±æ»å¤§è¡¨"ï¼M1ï¼è¿æ¯ç´æ¥"å¸å·å表解æ"ï¼ |
| | | 2. ç³»ç»å¯¼åºæ¯å¦æ¯æé½æãè½å¦æä¾é¡¹ç®-å¸å·å¯¹ç
§ï¼å¯¼åºæ å¸å·å段ï¼ï¼ |
| | | 3. 亿å
项ç®å£å¾ â¥10000 䏿¯å¦åç¡®ï¼ç»æµå¼ºå¿æ¯å¦æåºå®ååï¼ |
| | | |
| | | **å½åè¿è¡ç¶æ** |
| | | - å端 8090ï¼start-dev.ps1 å¯å¨ï¼è·å« H204 çæ°ä»£ç ï¼ãå端 8080ï¼npm run serveï¼PID 15900ï¼åå¨è·ï¼H204 æ ·ä¾æ°æ®ä¸ 577 æ¡å®¡æ ¸ç»æå¨åºã |
| | | - ä¸´æ¶æä»¶ï¼é¡¹ç®æ ¹ `energy-test.log` ä¿çï¼`%TEMP%\pyxlrd`ï¼xlrd 2.0.2 临æ¶å®è£
ï¼ã`%TEMP%\dump_xls*.py`ã`%TEMP%\check_*.py` ä¿çã |
| | | - ç¯å¢å¤æ³¨ï¼ç³»ç» python ç site-packages æ åæéï¼è£
åºé `pip install --target %TEMP%\pyxlrd` å `sys.path.insert`ã |
| | | |
| | | **ä¸ä¸ªä¼è¯å»ºè®®** |
| | | - å
è¯·ç¨æ·ç¡®è®¤ä¸é¢ 3 个é®é¢ â æ `docs/æèµæ¨¡å宿½æ¹æ¡.md` å¼å·¥ M1ï¼å
¨çæ±æ»å¤§è¡¨å¯¼å
¥ + 4 å¼ æ¥è¡¨ï¼ç¨ 6/7 ææ ·ä¾æ ¸å¯¹ï¼ã |
| | | - è¥ç¨æ·æ³å
ååå¸å®¢è¿ï¼åéå
确认ç½çº¦è½¦æåå£å¾ä¸é¨çº§æ°æ®æºï¼æ¤åç»è®ºï¼æèµå¨åãåå¸å®¢è¿å¨åï¼ã |
| | | |
| | | ### 2026-08-24 æä¼è¯ï¼æèµæ¨¡å M1 è½å°éªè¯ + å®¡æ ¸å¹é
误æ¥ä¿®å¤ |
| | | **èæ¯**ï¼ç¨æ·ç¡®è®¤ â ä¸»æ°æ®æºå
æâå
¨çæ±æ»å¤§è¡¨âåï¼â¡å¸å·å°è¡¨ä¸»è¦æ´æ° èªå¼å§å»ºè®¾ç´¯è®¡/èªå¹´å累计/æ¬æå®æ/å»ºè®¾é¶æ®µ/形象è¿åº¦ åæ®µï¼å¹¶ä¸ãæ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7)ãç¸åºåæ®µæ ¡éªï¼â¢4 å¼ è¾åºè¡¨ä¸ºåºå®æ ¼å¼é¡¹ç®ã |
| | | **已宿ï¼M1ï¼** |
| | | - æ°æ®æ¨¡åï¼`investment_project`(148) + `investment_monthly`(148, 2026-07) + `investment_system`(148) ä¸è¡¨ï¼å¯¼å
¥ç±»å=æèµææ¥ï¼å
¨çæ±æ»å¤§è¡¨ï¼+æèµç³»ç»å¯¼åºã |
| | | - 4 å¼ æ¥è¡¨å¯¼åºç«¯ç¹å·²è·éï¼`investPlan`(é¢å®æè®¡åè¿åº¦æ±æ» 247è¡)ã`investFive`(åäºäºç©æµ 54è¡)ã`investBillion`(亿å
é¡¹ç® 2sheetï¼æèµæ»è¡¨+è§ä¸é¡¹ç®è¡¨ 56项)ã`investCounty`(ç»æµå¼ºå¿ 45è¡)ï¼æ ·ä¾æä»¶å¨ `%TEMP%\invest_*.xlsx`ã |
| | | - å®¡æ ¸è§å 4 æ¡ï¼RULE_I001~I004ï¼report_type=INVESTï¼ã |
| | | **æ¥è¡¨å
å®¹æ ¸å¯¹ç»è®ºï¼ä¸ docs/æ©å±è¡¨æ¨¡æ¿/æèµ/è¾åº/ 7ææ ·ä¾æ¯å¯¹ï¼** |
| | | - ç»æµå¼ºå¿è¡¨ï¼41 项ç®éè¡ä¸æ ·ä¾ä¸è´ï¼æ»æèµ/èªå¼å§ç´¯è®¡/æ¬å¹´è®¡å/èªå¹´å累计/æ¬æå®æ 0 å·®å¼ï¼ã |
| | | - é¢å®æè®¡å表ï¼å
é¨èªæ´½ï¼29 å¸å·å°è®¡=æç»ï¼å®¢è¿+ç©æµ=å计ï¼ï¼83 个ååé¡¹ç®æ°å¼ä»
5 å¤â2026年计åâå·®å¼ï¼ä¸¹æ±å£å³å²¸/丹æ±å£ææ¡/åå±±ç¿ä¸/åæ´²æºæ
§/ç¥åæ¶ç©æµåï¼æºäºæ±æ»å¤§è¡¨ä¸å¸å·è¡¨æ°æ®ä¸åï¼ï¼çæ 149 é¡¹ç® vs æ ·ä¾ 167ï¼è¦çå·®å¼ï¼ã |
| | | - åäºäºç©æµè¡¨ï¼35 é¡¹ç®æ°å¼ä¸æ ·ä¾ 0 å·®å¼ï¼æ ·ä¾å¦æ 28 项ç®ä¸å¨æ±æ»å¤§è¡¨ä¸ï¼å¾
äºæå¸å·å表补ï¼ã |
| | | - 亿å
项ç®è¡¨ï¼56 项ç®å
¨é¨ â¥1亿 çéæ£ç¡®ãåè®¡èªæ´½ï¼ä¸æ ·ä¾ 7 夿°å¼å·®å¼å为**æ ·ä¾èªèº«å¡«é**ï¼æ ·ä¾æ èªå¼å§ç´¯è®¡/1-7æç´¯è®¡ è¯¯å¡«å° æ»æèµ/å
¨å¹´è®¡å åï¼çæè¡¨ç¨æ±æ»å¤§è¡¨æ£ç¡®å¼ï¼ï¼æåº¦è®¡å/å®æåæ ·ä¾æãçæè¡¨çç©ºï¼æºæ°æ®æ ï¼ã |
| | | **å®¡æ ¸å¹é
误æ¥ä¿®å¤ï¼AuditEngineService æèµé¨åï¼** |
| | | - ä¿®å¤å 15 æ¡é®é¢ä¸ 4 æ¡è·¨è¡¨å·®å¼ä¸ºè¯¯æ¥ï¼â åæä¸åå®¢è¿æ¢ä¹ä¸å¿(æ´å为â¦) 被模ç³å¹é
éé
å° é§è¥¿å¿å®¢è¿æ¢ä¹ä¸å¿ï¼â¡å¸ä¸°å¿ç»¼åç©æµå ä¸ ç½ç°å¿ç»¼åç©æµå å½ä¸åååå(â综åç©æµåâ)导è´éé
ã |
| | | - ä¿®å¤å
容ï¼sysMap æ¹ä¸º å½ä¸ååâåéå表ï¼ç²¾ç¡®å½ä¸æå¿åºæ¶æ§ï¼æ¨¡ç³å¹é
æ¹âè¯ååæä¼âï¼åç¼>ä¸åé¨å
å«>5å>ååºåï¼ï¼ä¸åé¦å½ä¸è¿åï¼å½ä¸åå¢å â
¡/II ç»ä¸ï¼æ°å¢ååºåå
åºï¼ç»¼åæ¢çº½â综åå®¢è¿æ¢çº½ãç©æµä¸å¿âç©æµåä¸å¿ãæºæ
§/äºçº§ç«çæå
¥è¯ï¼ã |
| | | - ä¿®å¤åï¼RULE_I001/I002/I003 å
¨é¨ 0 误æ¥ï¼ç¼ºå¤±æé 11â5ï¼ä¸¹æ±å£å³å²¸/æ±å·åç«/éå±±å西/å½èæºæ
§/å°æ²³æ¸¯åºâ
¡æ/å¤©é¨ 6 æ¡å·²æ£ç¡®å¹é
䏿°å¼å
¨åï¼å©ä½ 5 æ¡ä¸ºçå®ç¼ºå¤±éäººå·¥æ ¸å®ï¼éä¸é«éå·´ä¸ç»¼åå®¢è¿æ¢çº½ãåç½æ²³æ¸¯æ£è´§è½¬è¿ä¸å¿é¡¹ç®ãæ¹åé¿æ±ç°ä»£ç©æµäº§ä¸éè示èåºÂ·ç°ä»£ç©æµåãå¼ä¸ç°ä»£ç©æµäº§ä¸åãå´é³å¿å½å®¶çº§ç»¼åç©æµæ¢çº½åé
å¥è®¾æ½å»ºè®¾é¡¹ç®ï¼ã |
| | | **å½åè¿è¡ç¶æ** |
| | | - å端 8090ï¼start-dev.ps1 éå¯ï¼PID 7668ï¼æ¥å¿ backend.logï¼ãå端 8080ï¼npm run serveï¼åè¿ç¨ï¼åå¨è·ï¼H204 ä¸æèµæ¨¡åæ°æ®é½å¨åºã |
| | | - ä¸´æ¶æä»¶ï¼`%TEMP%\invest_*.xlsx`ï¼4 å¼ å¯¼åºéªè¯ä»¶ï¼ã`%TEMP%\invest_dump.txt`ã`%TEMP%\sample_dump.txt`ã`%TEMP%\sample_plan.txt`ã`%TEMP%\plan_diff.txt`ãæ ¹ç®å½ energy-test.log åä¿çã |
| | | **å¾
å** |
| | | - M2ï¼17 å¸å·å表容éè§£æï¼è¡¥å
åäºäºè¡¨ç¼ºç 28 个项ç®ï¼+ å¸å·å°è¡¨âæ¬æå®æ/å»ºè®¾é¶æ®µ/形象è¿åº¦âåæ®µæ´æ°å
¥åºã |
| | | - 4 å¼ æ¥è¡¨ä¸ 7 ææ ·ä¾å·®å¼ç¹ï¼5 å¤ 2026年计åãé¢å®æè¦çå·®å¼ã亿å
表æåº¦åç空ï¼è¯·ç¨æ·æ ¸å¯¹å£å¾ã |
| | | - 亿å
表âæèµæ»è¡¨âsheet ç®åä»
âï¼äºï¼å
¶ä»=106.65äº¿ï¼æ¬å¹´è®¡åå计ï¼âï¼å
¶ä½è¡ç©ºç½ï¼å¾
确认å£åº¦æåå£å¾ã |
| | | |
| | | ### 2026-08-24 æ·±å¤ä¼è¯ï¼åå¸å®¢è¿å¼å·¥è®¨è®ºï¼ç»è®ºï¼å¯ä»¥åï¼çæ°æ®æºï¼ |
| | | **ç¨æ·æé®**ï¼ç°å¨æ¯å¦å¯ä»¥å åå¸å®¢è¿æ¨¡åï¼ |
| | | **ç»è®º**ï¼å¯ä»¥ä¸ï¼ä¸æ£å¥½è¡¥å
¨æ
客ä¸å£å¾æ¥è¡¨ââH203-1 ä¸å£å¾æç»/æå/åæéä¸ç´ç©ºçç åé
å乡å
¬äº¤/巡游åºç§/ç½çº¦è½¦ ä¸è¡ï¼çåå¸å®¢è¿æ°æ®è¿æ¥å³å¯å¡«ä¸ãæ¡æ¶å·²ç±æèµæ¨¡åéªè¯ï¼å¤æ°æ®æºå¯¼å
¥+å®¡æ ¸+æ¥è¡¨å
¨é¾è·¯å¤ç¨ï¼ï¼æ éæ°æ¶æãæ¨¡æ¿ `docs/åå¸å®¢è¿/åå¸å®¢è¿æ°æ®æ¨¡æ¿.xlsx`ï¼11 sheetï¼å
¬äº¤/åºç§/ç½çº¦è½¦/轨é轮渡/åå¸åå¸å·/ä¸å£å¾çï¼å« 2026 å¹´ 1-7 æåå¸å·æ°å¼ï¼ã |
| | | **å¼å·¥åéç¨æ·ç¡®è®¤ï¼æä¼å
级ï¼** |
| | | 1. å
¬äº¤ã巡游åºç§åå¸å·æåº¦åå§æ°æ®æ¥æºï¼å¸å·ä¸æ¥ or ç³»ç»å¯æ¥ï¼ââéä¸ä»½ç宿°æ®æä»¶å导å
¥æ ·ä¾ï¼æå¤§åç½®ï¼ã |
| | | 2. ç½çº¦è½¦æåå£å¾ä¸é¨çº§ä¸åæä»¶ï¼æ¨¡æ¿æåå
¬å¼å¤å¤ #REF!ï¼æ¾æååï¼ã |
| | | 3. â客è¿éåæ¯ç¯æ¯ 0% éæ ¸å®âä¸å¡å£å¾ï¼æ¨¡æ¿ç®å½åå txtï¼ï¼ç¡®è®¤åªäºç®æ£å¸¸é¿å
误æ¥ã |
| | | **å»ºè®®å®æ½ä¸æ¥ï¼å
æåé¾ï¼**ï¼â å
¬äº¤ï¼4 ç»è§åï¼å
è·é导å
¥+å®¡æ ¸+åå¸å·æ¥è¡¨ï¼â¡å·¡æ¸¸åºç§ï¼7 ç»è§åï¼è¡¥ è¿è¥è½¦æ° vs è¿æ¿ 对æ¯ï¼å¤ç¨ H203-1 è¿æ¿å¯¹æ¯ï¼ï¼â¢ç½çº¦è½¦æåï¼é¨çº§æ°æ®å°ä½ååï¼é¡ºæè¡¥ä¸å£å¾ä¸è¡¨ç©ºè¡ï¼ã |
| | | **å·¥ä½é**ï¼å端 6~10 + å端 2~3 人天ï¼docs/æ©å±æ¨¡åè¯ä¼°.mdï¼ï¼æ¯æèµ M2 å°ã |
| | | **ä¸ä¸æ¥**ï¼ç¨æ·æä¾å
¬äº¤æåº¦æ°æ®æä»¶åå¼å·¥ç¬¬ 1 æ¥ã |
| | | |
| | | |
| | | ### 2026-08-24 æ·±å¤ä¼è¯ï¼ç»ï¼ï¼åå¸å®¢è¿ç¬¬ 1 æ¥ï¼å
¬äº¤ï¼è½å° |
| | | **èæ¯**ï¼ç¨æ·ç¡®è®¤å
¬äº¤æåº¦æ°æ®æº = `docs/åå¸å®¢è¿/导å
¥_åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼2026å¹´7æï¼.xls`ï¼å®ä¸º xlsx å
容ã.xls æ©å±åï¼ä¼ä¸çº§æç» 143 è¡ï¼å«å
Œ
±æ±½çµè½¦/轨é/轮渡ä¸ç±»ï¼ç¬¬ 1 æ¥åªåå
Œ
±æ±½çµè½¦å段ï¼ï¼è¿æ¿æ°æ®å¾
ç¨æ·åè¡¥ï¼å±åºç§æ¥éª¤ï¼ã |
| | | **已宿ï¼å
¬äº¤ M1ï¼** |
| | | - æ°æ®æ¨¡åï¼`city_bus_monthly` 表ï¼ä¼ä¸çº§æåº¦ï¼44 åæ®µï¼ï¼å»ºè¡¨+è§å SQL è§ `docs/sql_city_bus.sql`ã |
| | | - 导å
¥ï¼`/api/import/cityBus`ï¼period=2026-07 æå 142 æ¡ã0 失败ï¼å¹çï¼åæ¥è¡¨æå
æ¸
æ§æ°æ®ä¸ CITY_BUS å®¡æ ¸ç»æï¼å¤±è´¥æ¾ç¤º Excel è¡å·ï¼ãå¸å·ç± `RegionUtil.cityByCode(æå±å°åº)` æ å°å° 17 å¸å·ã |
| | | - å®¡æ ¸ï¼4 æ¡è§åï¼report_type=CITY_BUSï¼compare_type=TREND_CONSISTENCY / RATIO_CHANGE / AVG_DIST_CHANGEï¼å为 YELLOWï¼ï¼ |
| | | - RULE_B001 åå¸å
ä¸åé
å乡客è¿éè¶å¿ä¸è´ï¼ç¯æ¯/忝åä¸å¯¹ï¼è¶å¿ä¸ä¸è´ä¸å¢é>2%ï¼ |
| | | - RULE_B002 客è¿é䏿
客å¨è½¬éè¶å¿ä¸è´ï¼ç¯æ¯/忝åä¸å¯¹ï¼è¶å¿ä¸ä¸è´ä¸å¢é>2%ï¼ |
| | | - RULE_B003 åå¸å
/åé
å乡客è¿éå æ¯ä¸ä¸æåå>1%ï¼ç¨æä»¶å
ç¯æ¯å¢éåæ¨ä¸æå æ¯ï¼ |
| | | - RULE_B004 å¹³åè¿è·ç¯æ¯å¢å>3 å
¬éï¼æä»¶ç´è¯»ï¼æåé
å乡平åè¿è·åå>3 å
¬éï¼ç± å¨è½¬é/客è¿é ç¯æ¯åæ¨ï¼ |
| | | - 2026-07 å®¡æ ¸ç»æ 53 æ¡ï¼B001=25ãB002=12ãB003=15ãB004=1ï¼å鱼鸿æåé
å乡平åè¿è· 4.8 å
¬éï¼ã |
| | | - æ¥è¡¨ï¼`/api/report/export/cityBusDetail` çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsxï¼å
¨ç+17 å¸å· à 4 ææ Ã 1..N æ+æåæ¯+æ¬å¹´ç´¯è®¡+ä¸å¹´ 1-12 æ+累计ï¼ï¼å
¨ç 7 æ 12974.73/87147.68/11508.07/59265.28 䏿¨¡æ¿ 0 å·®å¼ã |
| | | - åç«¯ï¼æ°æ®å¯¼å
¥é¡µæ°å¢ãåå¸å
¬äº¤ææ¥ããæ°æ®æ¥ç页æ°å¢ãåå¸å
¬äº¤ææ¥ããå®¡æ ¸ç»æé¡µæ°å¢ãCITY_BUSããæ¥è¡¨çæé¡µæ°å¢ãåå¸å®¢è¿æ¥è¡¨ï¼å
¬äº¤ï¼ç¬¬1æ¥ï¼ãå¡çï¼åå·²çæ´æ°çæã |
| | | **踩åè®°å½** |
| | | - æ°æ®æä»¶æ©å±å .xls ä½å
å®¹æ¯ xlsxï¼ZIP éæ°ï¼ï¼openpyxl ææ©å±åæç»ââå¤å¶æ .xlsx å³å¯ï¼å端 POI `WorkbookFactory` æå
容èªå¨è¯å«ï¼ä¸åå½±åã |
| | | - `start-dev.ps1` ç» `powershell ... | Select-Object` 管éå¯å¨æ¶ï¼åå° cmd/mvn/java ç»§æ¿ç®¡é奿ä¼å¯¼è´ä¼è¯æèµ·ï¼æ¹ä¸º `*> start-script.log` è¾åºéå®ååèæ¬æ£å¸¸æ¶å°¾ï¼ä½ä¼è¯ä»ä¼æå°ç»æï¼å¿ Ctrl+C 以å
误æå端è¿ç¨æ ï¼ã妿³¨æï¼å端æ¾è¢« IntelliJ å¯å¨ï¼idea64 ä¸ç java åºç¨è¿ç¨ï¼ï¼æ PID åªæåºç¨é¾å³å¯ï¼å¿å¨ idea64ã |
| | | - MySQL 5.7 ç `INSERT ... SELECT ... WHERE NOT EXISTS(æ´¾ç表å)` æ¥ Unknown columnï¼æ¹éæ¡ `WHERE NOT EXISTS(SELECT 1 FROM audit_rule WHERE rule_code=...)`ã |
| | | **å¾
å/å£å¾å¾
确认** |
| | | - 客è¿éåæ¯ç¯æ¯ 0% éæ ¸å®ï¼`docs/åå¸å®¢è¿/客è¿éçåæ¯ç¯æ¯æ¯0% éæ ¸å®ï¼ä¸»è¦åºç°å¨å
¬äº¤ï¼.txt`ï¼ææªçº³å
¥è§åï¼çä¸å¡å£å¾ç¡®è®¤ã |
| | | - å
¬äº¤æ¥è¡¨ç 2025 å¹´åæåå¾
2025 æ°æ®å¯¼å
¥åèªå¨å¡«å
ï¼åæ¯åçç±åºå
ä¸å¹´æ°æ®è®¡ç®ï¼ã |
| | | - 第 2 æ¥å·¡æ¸¸åºç§å·²å®æï¼è§ä¸æ¹è¡¥å
è®°å½ï¼ï¼ç¬¬ 3 æ¥ç½çº¦è½¦æåçé¨çº§æ°æ®ã |
| | | **å½åè¿è¡ç¶æ** |
| | | - å端 8090ï¼start-dev.ps1 éå¯ï¼PID 12712ï¼æ¥å¿ backend.logï¼ãå端 8080 åå¨è·ï¼`city_bus_monthly` å·²æ 2026-07 æ°æ®ï¼142 è¡ï¼ã |
| | | - ä¸´æ¶æä»¶ï¼`%TEMP%\bus_data.xlsx / bus_dump.txt / tpl_dump.txt / city_bus_detail.xlsx / city_bus_report*.txt / dump*.py`ãé¡¹ç®æ ¹ `start-script.log`ã`docs/sql_city_bus.sql`ï¼å»ºè¡¨+è§åè¿ç§»èæ¬ï¼å»ºè®®ä¿çï¼åæªå ã |
| | | |
| | | ### 2026-08-24ï¼ç»2ï¼ï¼åå¸å®¢è¿ç¬¬ 2 æ¥ï¼å·¡æ¸¸åºç§ï¼è½å° |
| | | **èæ¯**ï¼ç¨æ·å·²æ¾å
¥è¿æ¿æ°æ®ï¼`è¾å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xls`ï¼ç OLE .xlsï¼28MBï¼42636 è¡ï¼ãææ¥æ°æ®æº = `导å
¥_巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼2026å¹´7æï¼.xls`ï¼ç .xlsï¼å¸å·çº§ 17 è¡ã41 åï¼ã |
| | | **已宿ï¼å·¡æ¸¸åºç§ï¼** |
| | | - æ°æ®æ¨¡åï¼`city_taxi_monthly`ï¼å¸å·ææ¥ 41 åæ®µï¼+ `city_taxi_auth`ï¼è¿æ¿è½¦è¾æç»ï¼ï¼å»ºè¡¨+è§å SQL è§ `docs/sql_city_taxi.sql`ï¼å«è§å INSERTï¼å¯é夿§è¡ï¼ã |
| | | - 导å
¥ï¼`/api/import/cityTaxi`ï¼17 è¡ 0 失败ï¼å¹çæ¸
æ§+å®¡æ ¸ç»æï¼ï¼`/api/import/cityTaxiAuth`ï¼42636 è¡ï¼çº¦ 6 åéï¼å¹çæ¸
æ§ï¼ãå¸å·å½å±ï¼ææ¥æåºå代ç ï¼`RegionUtil.cityByCode`ï¼æ©æ½å· 422800/ç¥åæ¶ 429021 æ£å¸¸è¯å«ï¼ï¼è¿æ¿æ è¯å·âæ¡£æ¡âç»è¥æâå°å æ£å `(4290\d{2}|42(?:0[1-9]|1[0-3]|28)\d{2})` æåï¼å®æµ 42636 è¡å
¨é¨å½å¸å·ï¼ã |
| | | - å®¡æ ¸ï¼7 æ¡è§åï¼report_type=CITY_TAXIï¼ï¼ |
| | | - RULE_T001 è¿è¥è½¦æ° vs è¿æ¿å¯¹æ¯ï¼CROSS_DIFF_PCTï¼3%ï¼REDï¼ |
| | | - RULE_T002 åå¸å
/åé
å乡客è¿éè¶å¿ä¸è´ï¼TREND_CONSISTENCYï¼ |
| | | - RULE_T003 客è¿é/å¨è½¬éè¶å¿ä¸è´ï¼TREND_CONSISTENCYï¼å¨è½¬éç¯æ¯ç± 客è¿éç¯æ¯Ãå¹³åè¿è·ç¯æ¯ æ¨å¯¼ï¼check_field=turnoverMomDerivedï¼ |
| | | - RULE_T004 åå¸å
客è¿éå æ¯åå>1%ï¼RATIO_CHANGEï¼ |
| | | - RULE_T005 å¹³åè¿è·åå>3å
¬éï¼AVG_DIST_CHANGEï¼åé
å乡ç±ä¸æå¹³åè¿è·=æ¬æâå¢ååæ¨ï¼ |
| | | - RULE_T006 å车次载客人æ°åå>0.2人ï¼PAX_PER_TRIP_CHANGEï¼ç±ä¸æ 载客车次/客è¿éç¯æ¯ åæ¨ï¼ |
| | | - RULE_T007 载客车次/客è¿éè¶å¿ä¸è´ï¼TREND_CONSISTENCYï¼éå¼ 0.01ï¼ |
| | | - 2026-07 å®¡æ ¸ 6 æ¡ï¼T001 é»å 1931vs2043(5.48%)ãç¥åæ¶ 59vs71(16.9%)ï¼T002 æ½æ±ï¼T004 åæ/å¸å®/æ½æ±ã |
| | | - æ¥è¡¨ï¼`/api/report/export/cityTaxiDetail` çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsxï¼å
¨ç+17å¸å·Ã4ææ Ã1..Næ+忝+累计+å»å¹´12æï¼ï¼å
¨ç 7 æ 5860.24/34236.60/5351.24/27422.13 ä¸ `巡游åºç§_模æ¿.xlsx` 0 å·®å¼ã |
| | | - åç«¯ï¼æ°æ®å¯¼å
¥é¡µæ°å¢ã巡游åºç§ææ¥ããåºç§è½¦è¿æ¿è½¦è¾ããæ°æ®æ¥ç页æ°å¢å两项ãå®¡æ ¸ç»æé¡µæ°å¢ãCITY_TAXIããæ¥è¡¨çæé¡µæ°å¢ãåå¸å®¢è¿æ¥è¡¨ï¼å·¡æ¸¸åºç§ï¼ç¬¬2æ¥ï¼ãï¼åå·²çæ´æ°çæã |
| | | **踩åè®°å½** |
| | | - Spring é»è®¤ä¸ä¼ ä¸é 10MBï¼28MB è¿æ¿æä»¶è¢«æ â `application.yml` å `spring.servlet.multipart.max-file-size/max-request-size=100MB`ã |
| | | - `audit_result` 表æ enterprise_name åï¼ç¬æå段ï¼ï¼`attachInfo` åå¡«æ¶ CITY_BUS/CITY_TAXI è½å° H2032 表导è´åç§°æ¾ç¤ºâåæ°æ®å·²éæ°å¯¼å
¥âå ä½ â å·²ä¿®ï¼æ report_type å表åå¡«å
¬äº¤ï¼ä¼ä¸åï¼/巡游åºç§ï¼å¸å·åï¼+æ ¸å®è¯´æï¼å
¬äº¤/åºç§ååçã |
| | | - ææ¥åå¸å段åç¨ `normalizeCityName(ä¼ä¸åç§°)`ï¼æ©æ½âæ©æ½åå®¶æèæèªæ²»å·âæ æ³è¯å« â æ¹ç¨ `cityOf(å°åºä»£ç ,åç§°)` ä¼å
æåºå代ç ï¼å¹¶ç» RegionUtil å¢å âæ©æ½åå®¶æèæèªæ²»å·âæ©æ½å·âå«åã |
| | | - è¿æ¿ 42636 è¡éæ¡ insert æ¾çº¦ 6 åéï¼å端 axios 30s è¶
æ¶å¯¼è´é¡µé¢"导å
¥å¤±è´¥"ï¼å·²æ¹ MyBatis BATCH æ¹éæå
¥ + JDBC `rewriteBatchedStatements=true` + å端 timeout 600000ï¼æ°æ®åºæå
¥é¶æ®µçº¦ 30 ç§ï¼æ»èæ¶çº¦ 5 åé主è¦å¨ 26.8MB xls ç POI è§£æï¼ï¼åæ¥è¡¨æé导å
å åæï¼å¹ç宿µæ¶æ 42636 è¡ã |
| | | **å½åè¿è¡ç¶æ** |
| | | - å端 8090ï¼start-dev.ps1 éå¯ï¼æ¥å¿ backend.logï¼ï¼å½åè¿ç¨é¾ cmd 31740 â mvn(java) 31652 â app(java) 31452ï¼å¿è¯¯æï¼é坿¶ææ¤é¾åï¼ï¼å端 8080ï¼ç¨æ·è¿ç¨ï¼å¿æï¼åå¨è·ï¼`city_taxi_monthly` 17 è¡ã`city_taxi_auth` 42636 è¡ï¼2026-07/2026-08 å 42636 è¡ï¼2026-08 ä¸ºä¿®å¤æ¹é导å
¥åç宿´é导ï¼éèæ°æ®ï¼ã |
| | | - ä¸´æ¶æä»¶ï¼`%TEMP%\taxi_data.xlsx / taxi_dump.txt / yz_stat2.txt / yz_full.txt / city_taxi_detail.xlsx`ãæ ¹ç®å½ `start-script.log`ã`docs/sql_city_taxi.sql` åä¿çæªå ã |
| | | - å¾
åï¼ç¬¬ 3 æ¥ç½çº¦è½¦æåï¼çé¨çº§æ°æ®ï¼ï¼å
¬äº¤/åºç§æ¥è¡¨ç 2025 å¹´åæåå¾
2025 æ°æ®å¯¼å
¥åèªå¨å¡«å
ã |
| | | |
| | | |
| | | ### 2026-08-24ï¼ç»3ï¼ï¼åå¸å®¢è¿æ¥è¡¨æ¹ä¸ºææ¨¡æ¿åºç¨¿çæ |
| | | **éæ±**ï¼åå¸å®¢è¿è¾åºè¦âæ ¹æ®ç®å½ä¸çæ¨¡æ¿æç
§æ¨¡æ¿çæå¯¹åºçè¾åºæ¨¡æ¿âã |
| | | **å®ç°**ï¼`ReportExportService`ï¼ |
| | | - `exportCityBusDetail` / `exportCityTaxiDetail` ä¸åä»é¶å»ºè¡¨ï¼æ¹ä¸º `exportCityByTemplate(æ¨¡æ¿æä»¶å, period, ä»å¹´åææ°æ®, å»å¹´åææ°æ®)`ï¼è¯» `docs/åå¸å®¢è¿/åå¸å
¬äº¤_模æ¿.xlsx` / `巡游åºç§_模æ¿.xlsx` 为åºç¨¿ï¼ä¿çæ é¢/表头/åå¹¶åå
æ ¼ï¼63 å¤ï¼/è¾¹æ¡/æ°åæ ¼å¼ï¼0.00_ï¼/å
¬å¼ï¼ä»
æ¿æ¢æ°æ®åºå¸å·æåº¦å¼ã |
| | | - 模æ¿ç»æï¼Sheet1ï¼row1 æ é¢ãrow3-4 è¡¨å¤´ãæ°æ®åº row5-76ï¼å
¨ç+17å¸å·Ã4ææ ï¼æ¯å°åº 4 è¡ï¼ï¼2026 æåº¦å 0-based col2,4,...,14ï¼2025 æåº¦å col19,21,...,41ï¼åæ¯/累计å为å
¬å¼ï¼å
¨çè¡æåº¦å=17å¸å·æ±åå
¬å¼ï¼ã |
| | | - å¡«å
è§åï¼å¸å·è¡ 2026 å¹´ 1..N ææå¼å¡« 2 ä½å°æ°ãæ 弿¸
ç©ºæ¨¡æ¿æ ·ä¾ï¼2025 å¹´ 1..12 æåºå
æå¼å¡«ãå¦åæ¸
空ï¼ç 2025 æ°æ®å¯¼å
¥åèªå¨å¡«å
并触å忝å
¬å¼ï¼ï¼å
¨çè¡ä¸åæ¯/累计å
¬å¼ä¸å¾ä¿çï¼ååå `FormulaEvaluator.evaluateAll()` éç®å
¬å¼ç¼åå¼ï¼Excel æå¼ä¹å¯éç®ï¼ã |
| | | - 模æ¿å®ä½ï¼`application.yml` æ°å¢ `city-passenger.template-dir: docs/åå¸å®¢è¿`ï¼`resolveTemplate` æ é
ç½®ç®å½âuser.dirâuser.dir/.. ä¸çº§åéï¼æ°æ®åºè¡æ°å®å«ï¼<75 è¡æ¥é鲿¨¡æ¿æ¹çéä½ï¼ã |
| | | **éªè¯**ï¼2026-07 导åºï¼å
¨ç+17å¸å·Ã4ææ ï¼å®¢è¿é/å¨è½¬é/åå¸å
客è¿é/åå¸å
å¨è½¬éï¼ä¸åºå
èå 0 å·®å¼ï¼å
¬äº¤å
¨ç 12974.73/87147.68/11508.07/59265.28ï¼å·¡æ¸¸åºç§ 5860.24/34236.60/5351.24/27422.13ï¼è¾åº sheet å/åå¹¶åå
æ ¼/è¡¨å¤´ä¸æ¨¡æ¿å®å
¨ä¸è´ã |
| | | **注æ**ï¼æ¨¡æ¿ 2026 æ®µåæ¯å
¬å¼å¼ç¨ 2025 åï¼å½å 2025 åæ æ°æ®ï¼Excel æå¼åæ¯æ¾ç¤º #DIV/0!ï¼æ¨¡æ¿åæ ·è¡ä¸ºï¼ï¼2025 æ°æ®å¯¼å
¥åèªå¨ç®å¯¹ã |
| | | |
| | | |
| | | ### 2026-08-24ï¼ç»4ï¼ï¼è¿æ¿è½¦è¾å¯¼å
¥"ä¸è½å¯¼å
¥"ä¿®å¤ |
| | | **ç°è±¡**ï¼`导å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xls`ï¼26.8MBï¼42636 è¡ï¼å¨æ°æ®å¯¼å
¥é¡µç¹"å¼å§å¯¼å
¥"ä¸ç´å¤±è´¥ã |
| | | **æ ¹å **ï¼ |
| | | - å端 `src/api/index.js` å
¨å± axios `timeout: 30000`ï¼å端æ§å®ç°éæ¡ `insert` 约 6 åé â 30 ç§å请æ±è¢«å端æå¼ï¼é¡µé¢æ¥"导å
¥å¤±è´¥"ã |
| | | - å端 `DataImport.vue` ç cityTaxiAuth æ¨¡æ¿æç¤ºå误å为 `è¾å
¥_...`ï¼å®é
æä»¶æ¯ `导å
¥_...`ï¼ã |
| | | **ä¿®å¤**ï¼åå·²çæï¼ï¼ |
| | | - `DataImportService.importCityTaxiAuth` æ¹ MyBatis BATCH 模å¼ï¼`openSession(ExecutorType.BATCH)` + `flushStatements()` + `commit()`ï¼ï¼æ¹æ³å `synchronized` é²å¹¶åéå¤å¯¼å
¥ï¼ |
| | | - `application.yml` JDBC URL å `rewriteBatchedStatements=true`ï¼æ¹é flush ç±çº¦ 4 åééè³çº¦ 30 ç§ï¼ï¼ |
| | | - `DataImport.vue` cityTaxiAuth 模æ¿åæ¹å `导å
¥_`ï¼å¯¼å
¥è¯·æ± `timeout: 600000`ã |
| | | **éªè¯**ï¼21:57 鿰坼å
¥ 2026-07ï¼`import_batch` id=31 为 42636/42636/0ï¼`city_taxi_auth` 2026-07 æ°å¥½ 42636 è¡ï¼å¹çï¼id=30 ä¸ id=31 两次导å
¥åæ¶æ 42636ï¼ï¼2026-08 æ§ä»£ç éå¤å¯¼å
¥äº§çç 77262 è¡èæ°æ®å·² DELETEã |
| | | **注æ**ï¼æ»èæ¶çº¦ 5.3 åéï¼ä¸»è¦æ¯ POI(HSSF) è§£æ 26.8MB/42636 è¡ï¼ä»å¨ 600s è¶
æ¶å
ï¼å¦éæ´å¿«å¯åç»æ¹äºä»¶æµè§£æï¼easyexcelï¼ææåæä»¶ã |
| | | ### 2026-08-24ï¼ç»5ï¼ï¼åå¸å®¢è¿æ¥è¡¨ç³»åææ¨¡æ¿çæï¼6 表 + zipï¼ |
| | | **éæ±**ï¼è¯å«ã导å
¥_åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼2026å¹´7æï¼ãã导å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯)ãã导å
¥_巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼2026å¹´7æï¼ãä¸ä»½æºæ°æ®ï¼æç®å½ä¸æ¨¡æ¿çæç³»åè¾åºè¡¨ï¼æ ¼å¼ï¼å宽/表头/åå¹¶è§åï¼ä¸æ¨¡æ¿å®å
¨ä¸è´ï¼ä»
æ¿æ¢æ°æ®ã |
| | | **ç®å½åå**ï¼æ¨¡æ¿ç®å½ç± `docs/æ©å±è¡¨æ¨¡æ¿/åå¸å®¢è¿` ç§»è³ `docs/åå¸å®¢è¿`ï¼`application.yml` ç `city-passenger.template-dir` 已忥æ¹ä¸º `docs/åå¸å®¢è¿`ï¼`ReportExportService` ç `@Value` é»è®¤åéå¼ä¸å¹¶ä¿®æ£ï¼ã |
| | | **å®ç°**ï¼`ReportExportService` + `ReportExportController` + `ReportExport.vue`ï¼ |
| | | - æ°æ®æ©å±ï¼å
¬äº¤æºæä»¶ï¼ä¼ä¸çº§ 142 è¡ï¼é¤å
Œ
±æ±½çµè½¦å¤è¿å«**轨é**ï¼æ¦æ± 11701.74/å¨è½¬ 96240.86ï¼é»ç³ 30.09/404.37ï¼ä¸**轮渡**ï¼æ¦æ± 46.05/156.57ï¼ï¼`city_bus_monthly` å 4 åï¼rail/ferry 客è¿é+å¨è½¬éï¼ï¼`importCityBus` æ å° 0 åºå 13/14/17/18ï¼N/O/R/Sï¼å 142 è¡é导æåã |
| | | - æ°å¯¼åºæ¥å£ï¼`/api/report/export/`ï¼ï¼ |
| | | - `cityRailFerryDetail`ï¼è½¨éè¡ Excel 4/6/8ï¼å
¨ç/æ¦æ±/é»ç³ï¼+1 为å¨è½¬éè¡ï¼ï¼è½®æ¸¡è¡ 13/15ï¼å
¨çæåº¦åä¿çæ±åå
¬å¼ã |
| | | - `cityPassengerCitySum`ï¼`åå¸åå¸å·_模æ¿` / `åå¸å®¢è¿åå¸å·æç»è¡¨_模æ¿`ï¼ï¼å®¢è¿éåº r5-22ãå¨è½¬éåº r27-44ï¼B=æ»ç´¯è®¡ãG=å
¬äº¤ãL=åºç§ãQ=ç½çº¦è½¦ï¼æ æºæ°æ®æ¸
空ï¼ãV=轨éãX=轮渡ï¼E/J/O/T/W/Y å¢éåæ å»å¹´åæ â æ¸
ç©ºæ¨¡æ¿æ ·ä¾ã |
| | | - `cityPassengerSummary`ï¼`å叿±æ»_模æ¿`ï¼ï¼2026 段 B/D å¡«åºå
累计ãC/E 忝æ¸
空ãç½çº¦è½¦è¡ç½® 0ï¼2025/2024 段ï¼G..Q åï¼ä¿ç模æ¿åå²åèå¼ä¸å¨ã |
| | | - `cityPassengerSeries`ï¼ä¸é® zipï¼å
¬äº¤/åºç§/轨é轮渡æç» + åå¸å·ç´¯è®¡ + åå¸å·æç»è¡¨ + æ±æ»ï¼ã |
| | | - å端 `ReportExport.vue` æ°å¢âåå¸å®¢è¿æ¥è¡¨ï¼ç³»åï¼âå¡çï¼4 个å表æé® + ä¸é®æå
zipï¼ã |
| | | **POI 0 åºæè®**ï¼POI è¡å·æ¯ 0 åºï¼ä¹åæ Excel 1 åºåå¯¼è´æ´åéä½ä¸è¡ï¼è½¨éè¯»å° 96645.23 çéä½å¼ï¼ï¼å·²æ¹ 0 åºï¼è½¨é 3/5/7ã轮渡 12/14ãåå¸å· 4/26ãæ±æ» 2..8ï¼ï¼éçæåæ°æ®å
¨é¨æ ¸å¯¹éè¿ã |
| | | **éªè¯ï¼2026-07ï¼**ï¼è½¨é æ¦æ± 11701.74/96240.86ãé»ç³ 30.09/404.37ï¼è½®æ¸¡ å
¨ç=æ¦æ± 46.05/156.57ï¼åå¸å·ç´¯è®¡ æ¦æ± B=17060.44ï¼=å
¬äº¤ 4218.74+åºç§ 1093.91+轨é 11701.74+轮渡 46.05ï¼ãé»ç³ B=1254.8ãæåº B=27.15ï¼æ±æ» æ»å®¢è¿é B3=30612.85ãå
¬äº¤ 12974.73ãåºç§/巡游 5860.24ã轨é 11731.83ã轮渡 46.05ï¼2025/2024 æ®µä¸æ¨¡æ¿ä¸è´ï¼è¡¨å¤´/åå¹¶/å宽å
¨é¨ MATCHï¼ä»
æ°æ®è¡æè®¾è®¡æ¿æ¢ï¼ã |
| | | **è¾åº**ï¼`docs/åå¸å®¢è¿/è¾åº/çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsx`ã`çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsx`ã`çæ_轨é轮渡客è¿éåå¸å·æç».xlsx`ã`çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsx`ã`çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx`ã`çæ_åå¸å®¢è¿æ±æ».xlsx` å
± 6 个æä»¶ã |
| | | **åï¼èæ¬ç¼ç ï¼**ï¼`%TEMP%\gen_series2.ps1` è¥ç¨æ BOM ç UTF-8 ä¿åï¼PowerShell 5.1 æ ANSI 读åä¼æä¸æè·¯å¾è¯»æä¹±ç ï¼å¤å¶å° `D:\éå¨ã\ChatGPT\traffic-audit\docs\...`ï¼æ¬æ¬¡è¯¯å»ºçä¹±ç ç®å½ï¼æªå ï¼ï¼å·²ç¨å¸¦ BOM çæ¬éåï¼ç°æ£ç¡®åå
¥ `docs\åå¸å®¢è¿\è¾åº`ã |
| | | |
| | | |
| | | |
| | | ### 2026-08-25 ä¸åä¼è¯ï¼åå¸å®¢è¿é¾è·¯æ¢å¤éªè¯ + æ°æ®åºè¿æ¥æ ¹å ä¿®å¤ |
| | | **ç°è±¡**ï¼æ°å¯¹è¯å¯å¨åå端ï¼start-dev.ps1 确认 8090/8080 就绪ï¼åï¼`/api/data/list` å
¨é¨ç±»å 500ã`/api/audit/results` 500ï¼ä»
default 忝ï¼investment*ï¼è¿å 200 ç©ºæ°æ®ã |
| | | **ææ¥**ï¼åç«¯æ¯ IDEA å¯å¨å®ä¾ï¼æ¥å¿ä¸è¿ backend.logï¼ï¼èµ· 8091 è¯æå®ä¾ï¼`mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8091`ï¼å¤ç°ï¼å æ æ ¹å ï¼ |
| | | - `java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed`ï¼HikariPool åå§å失败ï¼ã |
| | | - æ¬æº MySQL 8 root è®¤è¯æä»¶ä¸º `caching_sha2_password`ï¼mysql 客æ·ç«¯å¯è¿ï¼ï¼JDBC URL 缺 `allowPublicKeyRetrieval=true` â é SSL è¿æ¥æç»åå
¬é¥ã |
| | | **ä¿®å¤**ï¼`application.yml` / `application.yml.example` / `target/classes`ï¼åæ¥ï¼JDBC URL å `allowPublicKeyRetrieval=true`ï¼ä¿ç rewriteBatchedStatements=trueï¼ï¼å IDEA æ§å®ä¾ â start-dev.ps1 éå¯ â å
¨é¨ç±»å 200ã |
| | | **æ¢å¤éªè¯ï¼å
¨é¨ä¸ 08-24 è®°å½ä¸è´ï¼**ï¼ |
| | | - éè·å®¡æ ¸ï¼CITY_BUS 2026-07 = 53 æ¡ï¼B001=25/B002=12/B003=15/B004=1ï¼ï¼CITY_TAXI 2026-07 = 6 æ¡ï¼T001=2/T002=1/T004=3ï¼ãä¹åå
¬äº¤ 53 æ¡è¢« 23:21 ç 142 è¡é导ï¼å¹çæ¸
ç»æï¼å²æä¸æªéè·ï¼åºç§åºéæ®ççæ¯ 2026-08 ç 6 æ¡ã |
| | | - 6 å¼ æ¥è¡¨éæ°çæï¼`docs/åå¸å®¢è¿/è¾åº/`ï¼ï¼å
³é®æ°å¼å
¨é¨æ ¸å¯¹ï¼å
¬äº¤å
¨ç 12974.73/87147.68/11508.07/59265.28ï¼åºç§æ¦æ± 1093.91/8532.5ï¼è½¨éæ¦æ± 11701.74/96240.86ãé»ç³ 30.09/404.37ï¼è½®æ¸¡ 46.05/156.57ï¼åå¸å·ç´¯è®¡ æ¦æ± B=17060.44/é»ç³ 1254.8/æåº 27.15ï¼æ±æ» æ» 30612.85/å
¬äº¤ 12974.73/åºç§ 5860.24/轨é 11731.83/轮渡 46.05ï¼ç³»å zip 6 æä»¶é½å
¨ã |
| | | **å°ä¿®å¤ï¼ä¸ HANDOFF 08-24 设计ä¸ç¬¦å¤ï¼**ï¼`setDataCell` 对 `v==0` æ§è¡ `c.setBlank()`ï¼å¯¼è´æ±æ»è¡¨"ç½çº¦è½¦è¡ç½® 0"å®é
è¾åºä¸ºç©ºï¼æ°å¢ `setZeroCell`ï¼æ¾å¼å 0.0ï¼ç¨äº B7/D7ï¼éçæå B7=0ãD7=0ï¼å
¶ä½æ°å¼ä¸åã |
| | | **å¾
åä¸å**ï¼åå¸å®¢è¿ç¬¬ 3 æ¥ï¼ç½çº¦è½¦æåï¼çé¨çº§æ°æ®æºï¼å
¬äº¤ 2025 åæåç 2025 æ°æ®å¯¼å
¥ï¼"客è¿éåæ¯ç¯æ¯ 0% éæ ¸å®"ï¼txt 为空ï¼çä¸å¡å£å¾ï¼ä¸å£å¾ä¸è¡¨ï¼H203-1 æç»/æå/åæï¼ç åé
å乡å
¬äº¤/巡游åºç§ è¡å¯å¨åå¸å®¢è¿æ°æ®åºç¡ä¸æ¥å
¥ï¼å»ºè®®ä¸ä¸æ¥ï¼é确认å£å¾ï¼å
¬äº¤å passengerChengxiang åæ®µï¼åºç§æºæä»¶æ åé
å乡客è¿éåæ®µé确认æåå£å¾ï¼ã |
| | | **å**ï¼apply_patch 卿¬ç¯å¢å
¨é¨æ¥ "Access is denied"ï¼è¿æ ¹ç®å½æä»¶ä¹æï¼ï¼æ¹ç¨ PowerShell `[System.IO.File]::ReadAllText/WriteAllText`ï¼UTF-8 æ BOMï¼ç¼è¾ï¼`mvn spring-boot:run` ä¸ä¼èªå¨éæ· resourcesï¼æ¹ yml åéæå¨åæ¥ target/classes æè· `mvn compile`ã |
| | | |
| | | |
| | | ### 2026-08-25 ä¸åä¼è¯ï¼æ¥è¡¨çæ/ä¼ä¸è§£é/æ°æ®å¯¼å
¥ UI éæï¼ç¨æ·ç¡®è®¤å£å¾åå¼å·¥ï¼ |
| | | **éæ±**ï¼â åå¸å®¢è¿æ¥è¡¨å¨æ¥è¡¨çæé¡µå并为ä¸ä¸ªæ¨¡åï¼â¡é¡µé¢ç¾å + æ¥è¡¨å¯éçæ/ä¸é®å
¨é¨ + æ¯æä¸ä¸ªæ/å 个æ/ä¸å¹´ï¼â¢ä¼ä¸è§£éå å
¥å
¬è·¯æ
客ä¸åå¸å®¢è¿ï¼è½è/æèµæ è§£éï¼ä¸åï¼ï¼â£æ°æ®å¯¼å
¥çé¢ä¼åï¼â¤è´§è¿/æ
客/æèµç模å忥ç»ä¸ã |
| | | **å端æ°å¢/ä¿®æ¹**ï¼å·²ç¼è¯éå¯ï¼8090 å°±ç»ªï¼ |
| | | - `ReportExportService`ï¼æ°å¢ `exportFreightSeries` / `exportPassengerSeries` / `exportInvestSeries` / `exportSelectedSeries`ï¼å端ãçæéä¸ãå¤éæ¶æ type å表æå
zipï¼é¿å
æµè§å¨æ¦æªå¤ä¸ªä¸è½½ï¼ï¼`REPORT_FILE_NAMES` æ å° 18 å¼ è¡¨ã |
| | | - `ReportExportController`ï¼æ°å¢ `/export/freightSeries`ã`/export/passengerSeries`ã`/export/investSeries`ã`/export/selected?types=a,b,c&mode=` å个 zip 端ç¹ï¼å
¨é¨å¯¼åºç«¯ç¹å `logExport` ççã |
| | | - æ°å¢ `OperationLog` å®ä½ + `OperationLogMapper` + `OperationLogService`ï¼èªå¨ä» Authorization 头解æç¨æ·ãX-Forwarded-For å IPï¼ï¼åå
¥ `sys_operation_log`ï¼è¡¨åæ¬ä¸ºç©ºï¼ç°å·²æè®°å½ï¼ã |
| | | - `ExplainController`ï¼`/api/explain/list` å `reportType` åæ°ï¼æ¯æ H2032/H2031/CITY_BUS/CITY_TAXI åç±»ï¼è¿åç±»å对åºåã |
| | | - `DataImportController`ï¼æ°å¢ `GET /api/import/batches?type=&limit=`ï¼å¯¼å
¥æ¹æ¬¡åå²ï¼`import_batch` 表ï¼ã |
| | | - `DbQueryExecutor`ï¼ç½ååæ³¨å `h2031_enterprise_monthly` / `city_bus_monthly` / `city_taxi_monthly`ï¼AI 对è¯å¯æ¥ï¼ï¼`LLMService` ç³»ç»æç¤ºæ¯æ context ä¼ å
¥ `reportType`/`tableName` å¼å¯¼æ¥è¡¨ã |
| | | **å端ï¼vue-cli çæ´æ°å·²çæï¼** |
| | | - `ReportExport.vue` éåï¼äºä¸ªæ¨¡åï¼è´§è¿/æ
客/è½è/æèµ/åå¸å®¢è¿ï¼ç»ä¸å¡çå¸å±ï¼gridï¼ï¼åå¸å®¢è¿ 6 å¼ è¡¨å并为ä¸å¼ å¡ï¼æ¯æ¨¡å checkbox å¤é + ãçæéä¸ãï¼åéç´ä¸ãå¤éæå
zipï¼+ãä¸é®çæå
¨é¨(zip)ãï¼æ
客ä¿çææ¥/å¹´æ¥ä¸¤æ¡£ï¼å¹´æ¥é年份ï¼period=yyyy&mode=yearï¼ï¼åºé¨ä½¿ç¨æç¤ºè¯´æææ°å£å¾ï¼é 07=1~7 æãé 12=å
¨å¹´+ä¸å¹´åæ¯ï¼ã |
| | | - `ExplainReview.vue`ï¼æ¥è¡¨ç±»å䏿ï¼è´§è¿/å
¬è·¯æ
客/åå¸å
¬äº¤/巡游åºç§ï¼ï¼è¡¨æ ¼åæç±»å卿æ¾ç¤ºï¼å
³é®åæ ç¾éç±»å忢ï¼åºç§æå¸å·ï¼ï¼AI åæ context å¢å reportType/tableNameã |
| | | - `DataImport.vue`ï¼å¯¼å
¥ç±»åæ è´§è¿/å
¬è·¯æ
客/è½è/åå¸å®¢è¿/æèµ äºç»ååºå±ç¤ºï¼å³ä¾§å¯¼å
¥è®¾ç½®å¡ï¼ä¸æ¹ãæè¿å¯¼å
¥è®°å½ãè¡¨æ ¼ï¼ç±»å/æ/æä»¶å/æ»è¡æ°/æå/失败/æ¶é´ï¼æ¥èª /import/batchesï¼ï¼cityTaxiAuth æ¾ç¤ºçº¦ 5 åéèæ¶æç¤ºã |
| | | **éªè¯ï¼å
¨é¨éè¿ï¼** |
| | | - `/api/import/batches`ï¼æ£å¸¸è¿åæè¿ 10 æ¡ã |
| | | - `/api/explain/list`ï¼H2032=28 æ¡ï¼2026-07ï¼ãH2031=0ãCITY_BUS=109ãCITY_TAXI=8ï¼ç¥åæ¶ç 8 å¸å·æè§£éï¼ã |
| | | - zipï¼selectedï¼cityBusDetail+cityTaxiDetail+cityPassengerSummaryï¼ãfreightSeriesãpassengerSeriesï¼month/yearï¼ãinvestSeries æä»¶é½å
¨ï¼æ
å®¢å¹´æ¥ 104810/47498 åèï¼>ææ¥ 95824/43439ï¼æ£ç¡®ï¼ã |
| | | - ççï¼sys_operation_log å·²è®°å½ 6 æ¡ãæ¥è¡¨çæãï¼æªç»å½æ¶ userId 为空ï¼detail 为 UTF-8 䏿æ£å¸¸ï¼ã |
| | | - å端 chunk 7/4/1 å·²å
嫿°ä»£ç ï¼report/export/selectedãimport/batchesãreportType çæ è®°ï¼ï¼æ ç¼è¯é误ã |
| | | **说æ/è¾¹ç** |
| | | - è½è模ååªæ 1 å¼ è¡¨ï¼ä¸æ¾ç¤ºãä¸é®çæå
¨é¨ãã |
| | | - ãå å¹´ã维度ï¼åºå®æ¨¡æ¿è¡¨åªæ¯æå½å¹´ 1~12 æ + ä¸å¹´åæ¯ä¸¤æ®µï¼é¢å¯¼è¡¨æ ¼å¼ï¼ï¼å¤å¹´å¯¹æ¯å¦éå¯åç»å 鿍¡æ¿ãåºé´æç»å¯¼åºãã |
| | | - ä¼ä¸è§£é H2031 å½å 0 æ¡æ¯å ä¸ºæ ·ä¾æ°æ®è¯¥ææªå¡«è§£éï¼æ¥å
¥é»è¾æ¬èº«å·²éã |
| | | - apply_patch ä»ä¸å¯ç¨ï¼Access deniedï¼ï¼å端大æä»¶ç¨ Python æ´æä»¶éåï¼å°æ¹å¨ç¨åç¬¦ä¸²æ¿æ¢ã |
| | | |
| | | |
| | | |
| | | ### 2026-08-25 æä¸ä¼è¯ï¼ææ¥/å¹´æ¥å
¨æ¨¡å + è½è模æ¿å + ä¼ä¸è§£éæªå¡«å + æ¥è¡¨é¡µä¸æå¯¼åºï¼ç¨æ·ç¡®è®¤å£å¾å宿½ï¼ |
| | | **éæ±**ï¼â å¸å±æ¹ä¸º è´§è¿âå
¬è·¯æ
客âåå¸å®¢è¿ï¼æ
客å³è¾¹ï¼âè½èâæèµï¼â¡ææ¥/å¹´æ¥åæ¢å å° è´§è¿/æ
客/åå¸å®¢è¿/æèµï¼â¢çæå
容æ¹å¤éä¸ææ¡ + åæé®ã导åºãï¼â£æ°æ®å¯¼å
¥çé¢è¿ä¸æ¥ç¾åï¼â¤ä¼ä¸è§£éå âæªå¡«åâä¸è¿æ»¤å¼å
³ï¼â¥ç¡¬æ§è¦æ±ï¼è´§è¿/æ
客/æèµ/å®¢è¿æ°è¡¨å¿
é¡»ä¸ãæ ¼å¼æ¨¡æ¿ãå宽/表头/åå¹¶è§åå®å
¨ä¸è´ï¼ä»
æ¿æ¢æ°æ®ï¼å¯¼åº Excelã |
| | | **å端ï¼å·²ç¼è¯éå¯éªè¯ï¼** |
| | | - å¹´æ¥ mode æ©å±ï¼`monthOf(period, mode)`ï¼yearâ12ï¼ï¼`exportCityDetail/exportFreightRank/exportTurnoverRank`ã`exportCityBusDetail/exportCityTaxiDetail/exportCityRailFerryDetail/exportCityPassengerCitySum/exportCityPassengerSummary`ã`exportCityByTemplate`ã`investPlan/investFiveYearLogistics/investBillion/investCounty` åä¸ä¸ª series ä¸ `selectedReportData` å
¨é¨å mode éä¼ ï¼æ§å¶å¨ 16 个端ç¹å `mode` åæ°ãè´§è¿å¹´æ¥æ 颿¾ç¤ºãæ¥è¡¨æï¼2026å¹´å
¨å¹´ãã |
| | | - è½è `exportEnergySummary` æ¹ä¸ºæ¨¡æ¿åºç¨¿ï¼`docs/å
¬è·¯æ
客+è½è/模æ¿_è½è¿æ±æ»è¡¨ .xlsx`ï¼æ°å¢ `energy.template-dir` é
ç½® + `resolveAnyTemplate`ï¼ï¼ä¿çæ é¢/表头/åå¹¶(A1:M1ãA11:G11)/å
¬å¼(D/J/L/M ä¸å计 SUM)/å宽/æ ·å¼ï¼ä»
å¡«æ°å¼åï¼B/C/E/F/G/H/I/Kï¼ï¼å
¨ç + 17 å¸å· clone sheetï¼å£åº¦æ 颿¹ä¸ææ°åï¼2026年第äºå£åº¦ï¼ã |
| | | - æèµ 4 è¡¨å»æ `autoWidth`ï¼ä¸ 7 ææ ·ä¾ä¸è´ï¼æ ·ä¾ 0 åå¹¶ãæ èªå®ä¹å宽ï¼è¡¨å¤´/å
容 08-24 å·²éªè¯éè¡ä¸è´ï¼ã |
| | | - `ExplainController`ï¼é»è®¤è¿åå
¨é¨è®°å½ï¼å«ç©ºè§£éï¼ï¼æ°å¢ `hasExplanation=true` è¿æ»¤ï¼`DataImportService.importPassengerMonthly` 补解æãæ ¸å®æ§åå è§£éãåï¼å½åæºæä»¶ 28 åæ æ¤å â H2031 å
¨é¨æ¾ç¤ºâæªå¡«åâï¼å±æºæ°æ®é®é¢ï¼ã |
| | | **å端ï¼chunk 1/7 已嫿°ä»£ç ï¼** |
| | | - `ReportExport.vue` éåï¼æ¨¡åé¡ºåº è´§è¿âå
¬è·¯æ
客âåå¸å®¢è¿âè½èâæèµï¼gridï¼è´§è¿æ´è¡ï¼æ
客左/åå¸å®¢è¿å³ï¼è½èå·¦/æèµå³ï¼ï¼çæå
容 = el-select å¤é䏿ï¼å
¨é/æ¸
空ï¼+ åæé®ã导åºãï¼é 1 å¼ ç´ä¸ãå¤å¼ zipï¼æé®æ¾ç¤ºå¼ æ°ï¼ï¼è´§è¿/æ
客/åå¸å®¢è¿/æèµåæææ¥/å¹´æ¥ radioï¼è½èä¿æå£åº¦æ«æï¼ã |
| | | - `ExplainReview.vue`ï¼ãä»
æ¾ç¤ºå·²å¡«åãel-switchï¼â hasExplanationï¼+ ç©ºè§£éæ¾ç¤ºãæªå¡«åãwarning æ ç¾ã |
| | | - `DataImport.vue`ï¼é»è®¤æ¥è¡¨æ=å½åæï¼æ¹æ¬¡è¡¨å æå/失败çè²ä¸ãç¶æãåï¼æå/é¨å失败/æ æ°æ®ï¼ï¼æ¯ç±»åå ä¸å¥å¯¼å
¥è¯´æã |
| | | **éªè¯ï¼å
¨é¨éè¿ï¼** |
| | | - explainï¼H2031 æ è¿æ»¤ 771 / è¿æ»¤ 0ï¼H2032 è¿æ»¤ 28ï¼CITY_BUS 142ï¼CITY_TAXI 17ã |
| | | - è½èï¼18 sheetsï¼å
¨ç+17 å¸å·ï¼ï¼æ é¢ã2026年第äºå£åº¦è½èæ±æ»æ
åµè¡¨ãï¼å
¬å¼/åå¹¶ä¿çï¼æ¦æ±æ´æ²¹è½¦ 375 çæ°å¼æ£ç¡®ã |
| | | - å¹´æ¥ï¼è´§è¿ 28 åï¼12 æ+忝ï¼ãåå¸å
¬äº¤ 45 åï¼2026 12 æ+2025 12 æï¼ï¼`selected?period=2026&mode=year&types=...` zip 4 æä»¶æ£å¸¸ã |
| | | - å端ç¼è¯æåï¼Compiled successfullyï¼ï¼chunk 1/7 å« hasExplanation/select-wrap/exportChecked æ è®°ã |
| | | **å** |
| | | - åæ®µæ¿æ¢ï¼Pythonï¼æ¶ `c.find()` æ¾å°æ©äºç®æ çè¡å¯¼è´æä»¶å¤å¶åºä¸æ´æ®µåå¸å®¢è¿ä»£ç â ç¼è¯æ¥âé夿¹æ³â+ Lombok è¿éæ¥é â ç¨è¡ç´¢å¼ç²¾ç¡®å é¤éå¤å [2033..2560) å `mvn clean compile` éè¿ãæè®ï¼æ¹éæ¿æ¢åå¿
é¡» clean compile + æ£æ¥æ¹æ³å¯ä¸æ§ã |
| | | - start-dev.ps1 è¥ä¸æ¬¡ä¼è¯æªéåºï¼`*>` éå®å被åå°åè¿ç¨ææå¥æï¼ä¼å¹¶åéå¤å¯å¨ â å
确认 8090/8080 空端å£åå¯å¨ï¼backend.log å¤å°±ç»ªè¦ç¡®è®¤æ¯æ°åå
¥å
å®¹ï¼æ§æ¥å¿ä¼è¯¯å¤ OKï¼ã |
| | | **å¾
å / éç¨æ·æä¾** |
| | | - ãæ ¼å¼æ¨¡æ¿ãåºåæä»¶ï¼è´§è¿è¾åºè¡¨ï¼è´§è¿éåå¸å·æç»/è´§è¿éæå/å¨è½¬éæåï¼ä¸æ
客è¾åºè¡¨ï¼åå¸å·æç» + ä¸å£å¾ 3 表ï¼ç®åä»åºæ è¾åºæ ¼å¼æ¨¡æ¿ï¼`docs/è´§è¿` åªæè¾å
¥æ¨¡æ¿ä¸ 08-04 çæä»¶ï¼`模æ¿_交ä¼ç»H203-1表` æ¯ç©ºå£³ï¼ï¼éç¨æ·æä¾åºåæä»¶æè½åâä»
æ¿æ¢æ°æ®âæ¹é ï¼åå¸å®¢è¿ï¼8 模æ¿ï¼ãè½èï¼1 模æ¿ï¼ãæèµï¼7ææ ·ä¾ 4 .xlsï¼å·²ææ¨¡æ¿/æ ·ä¾å¯¹é½ã |
| | | - æèµ DB å½åä»
50 æ¡ 2026-07 æåº¦æ°æ®ï¼41 ä¸ªç»æµå¼ºå¿é¡¹ç®ä»
1 æ¡ææåº¦æ°æ®ï¼ï¼é鿰坼å
¥å®æ´æèµææ¥æè½çæå®æ´æ¥è¡¨ï¼æ¥è¡¨é»è¾æ£å¸¸ï¼æ¯æ°æ®éé®é¢ï¼ã |
| | | - æ
客ä¸å£å¾çåé
å乡å
¬äº¤/巡游åºç§/ç½çº¦è½¦è¡ä»å¾
åå¸å®¢è¿å£å¾æ¥å
¥ã |
| | | |
| | | ### 2026-08-25ï¼ç»5ï¼è°ç æµè¯ï¼ï¼ç¨æ·æ´æ°æ
客è¾å
¥/è¾åºæ ·ä¾ + æèµæ ¡éªéæ±ï¼å¾
确认å£å¾å宿½ï¼ |
| | | **ç¨æ·æ¹å¨ï¼æ¬æ¬¡ä¼è¯å¼å¤´åç°ï¼** |
| | | - å
¬è·¯æ
客è¾å
¥ `docs/å
¬è·¯æ
客+è½è/å
¬è·¯æ
客ç®åå·¥ä½.xlsx` æ´æ°ï¼11:21ï¼707KBï¼ï¼ç°å« 6 sheetï¼å
¬è·¯æ
客6ææ¥ï¼28åÃ770è¡ï¼ä»æ æ ¸å®æ§åå è§£éåï¼ãè¿æ¿è½¦è¾æ°ãå
¬è·¯æ
客åå¸å·æç»/ä¸å£å¾æç»/ä¸å£å¾æå/ä¸å£å¾åæï¼4 个å
嵿 ·ä¾ï¼ã |
| | | - æ°å¢ `docs/å
¬è·¯æ
客+è½è/è¾åº/` 4 å¼ **æ¨¡æ¿æ ¼å¼æ ·ä¾**ï¼ç¨æ·æå·¥åºåï¼éç¨åºçæï¼ï¼ |
| | | - `çæ_å
¬è·¯æ
客åå¸å·.xlsx`ï¼58è¡Ã284åã237 åå¹¶ï¼r4-7=å
¨ç(客è¿é/å¨è½¬é/个ä½å®¢è¿é/个ä½å¨è½¬é)ï¼r8 èµ· 17 å¸å· 2~4 è¡ä¸çï¼æ¦æ±/éå·/èå·/æ©æ½/仿¡/æ½æ±/天é¨/æåºæ 个ä½è¡ï¼ï¼C..O=2026å¹´1-6æ+累计ãR..AP=2025å¹´12æ+累计ãAS..=2024 å
¨å¹´ï¼åæ¯/累计=å
¬å¼ï¼åå²å¼æ ·ä¾èªå¸¦ã |
| | | - `çæ_ä¸å£å¾æç».xlsx`ï¼188è¡Ã43åã143 åå¹¶ï¼å®¢è¿éå r5-94 + å¨è½¬éå r99-188ï¼å=å
¨ç+17å¸å·Ã5ææ (æ»/ç线/å
¬äº¤/åºç§/ç½çº¦è½¦)ï¼æ°æ®åº **2378 个å¤é¨å¼ç¨å
¬å¼**ï¼`[1]ç线å
车!C8`ã`[1]å
¬äº¤!C9-[1]å
¬äº¤!C11`ã`[1]åºç§!`ã`[1]ç½çº¦è½¦!`ï¼æå `D:\çç´ç工使件\å®¢è´§è¿æåº¦æ°æ®æ±æ»\2026å¹´\6æ\2026å¹´6æå®¢è¿éãè´§è¿é(111)1.xlsx`ï¼å
é¨å
¬å¼ 4818 个ï¼åæ¯/累计ï¼ã |
| | | - `çæ_ä¸å£å¾æå.xlsx`ï¼20è¡Ã9å**åå累计**ï¼å¸å·/客è¿é/æå/忝/å¢éæå/å¨è½¬é/æå/忝/å¢éæåï¼RANK+SUM å
¬å¼ï¼æ é¢"2026å¹´1-6æå
¨çåå¸å·ç´¯è®¡å®æé路客è¿ç产æ
åµ"ï¼ââæ³¨æå·¥ä½ç°¿å
åµ sheet æ¯åå(彿+累计18å)ï¼ç¬ç«æ ·ä¾æ¹æååï¼éä¸ç¨æ·ç¡®è®¤æåªç§çæã |
| | | - `çæ_ä¸å£å¾åæ.xlsx`ï¼7è¡Ã8åã1 åå¹¶ï¼æ»/ç线/å
¬äº¤/åºç§/ç½çº¦è½¦ à 累计客è¿é/忝/å æ¯ + 累计å¨è½¬é/忝/å æ¯ï¼æ»=23496.1693/0.0777ãå¨è½¬ 917059.1993/-0.0295ï¼ã |
| | | - æèµæ°å¢ 3 个 0 åèéæ± txtï¼â æèµè¾å
¥è¦ä¸ã模æ¿_æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7)ãæ ¡éªãä¸ä¸è´å¼¹çªï¼â¡æ 6/7 ææä»¶å¤¹å¸å·æ¥è¡¨æ´æ°ã6/7æç©æµç«åºæèµæç»ã项ç®åæ°æ®ï¼â¢æ 6/7 ææä»¶å¤¹æ´æ°ãæ´æ°_6/7æå®¢è¿ç«æèµæç»è¡¨ãä¸¤å¼ å¤§è¡¨ï¼ç¨æ·åæçä¼¼ç¬è¯¯ï¼åºä¸º 6æ/7æä¸¤å¼ ï¼ã |
| | | **æµè¯ç»æï¼åå端åå¨è·ï¼8090/8080ï¼** |
| | | - æ
客导å
¥æ° xlsxï¼`POST /api/import/passengerMonthly` â 769 æ¡æå+767 è¿æ¿ï¼0 失败ï¼`/api/data/list?type=h2031&period=2026-07` total=769 âï¼cityBus=142ãcityTaxi=17 âã |
| | | - æ
客 4 表å½å导åºç»æå
¨é¨ä¸æ ·ä¾ä¸ç¬¦ï¼åå¸å· 401Ã96/0åå¹¶ vs 58Ã284/237ï¼ä¸å£å¾æç» 191Ã96/0 vs 188Ã43/143ï¼æå 18ååå vs 9åååï¼åæ 7Ã8/0åå¹¶ vs 7Ã8/1åå¹¶ â éå
¨é¨ææ ·ä¾æ¨¡æ¿åã |
| | | - æèµæ ¡éªï¼`POST /api/audit/execute?reportType=INVEST&period=2026-07` â 2 æ¡ PENDINGï¼RULE_I004 éä¸é«éå·´ä¸ç»¼åå®¢è¿æ¢çº½æèµç³»ç»ç¼ºå¤±ï¼RULE_I003 天é¨å¸ç©æµä¸å¿æ¬æå®æ 3058 vs æèµç³»ç» 600ï¼ç»å¯¹å·®2458/ç¸å¯¹å·®409.7%ï¼â æ ¡éªæ°æ®å·²å
·å¤ï¼investment_project/monthly/system å 148 æ¡ï¼ï¼**åç«¯å¼¹çªæªå®ç°**ã |
| | | - æèµ 4 è¡¨å¯¼åº investPlan/FiveYearLogistics/Billion/County å
¨é¨ OK âï¼08-24 å·²éªè¯ä¸ 7 ææ ·ä¾ä¸è´ï¼ã |
| | | **å¾
ç¨æ·ç¡®è®¤å£å¾å宿½** |
| | | 1. æ
客 4 表模æ¿åï¼ä»¿ `exportCityByTemplate`ï¼ï¼ä»¥ `docs/å
¬è·¯æ
客+è½è/è¾åº/` 4 å¼ ä¸ºåºç¨¿ï¼ä¿ç表头/åå¹¶/å宽/è¡é«/æ ·å¼/å
é¨å
¬å¼ï¼æ°æ®åºå¡«åºå
å¼ï¼**å¤é¨å¼ç¨å
¬å¼å¿
é¡»æ¿æ¢ä¸ºæ°å¼**ï¼æºæä»¶ `çç´ç工使件` ä¸å¨é¡¹ç®å
ï¼å¦åæå¼æé¾å¼¹æ´æ°ï¼ï¼çæå `FormulaEvaluator.evaluateAll()` éç®ç¼åå¼ã |
| | | 2. é确认ï¼åå¸å·"个ä½å®¢è¿é/å¨è½¬é"è¡æ°æ®æºï¼è¾å
¥æ 个ä½å段ï¼ï¼ä¸å£å¾ åºç§/ç½çº¦è½¦ åé
å乡å£å¾ï¼æ ·ä¾åºç§ 3131.95/ç½çº¦è½¦ 2063.45 ä¸ºç¨æ·åèå¼ï¼DB æ æ¤å段ï¼å
¬äº¤æ passengerChengxiang/turnoverChengxiang å¯å¡«ï¼ï¼æå表æååç´¯è®¡è¿æ¯å½æ+累计ååã |
| | | 3. æèµå¼¹çªï¼DataImport.vue 导å
¥æèµææ¥åè° audit/execute?reportType=INVESTï¼æ PENDING å¼¹çªå差弿ç»ã |
| | | 4. æèµè¾å
¥æ´æ° M2ï¼è§£æ 6/7 ææä»¶å¤¹å¸å· xls æ¥è¡¨âæé¡¹ç®åæ´æ°ãæ´æ°_6/7æå®¢è¿ç«/ç©æµç«åºæèµæç»ãåæ®µï¼èªå¼å§ç´¯è®¡/èªå¹´å累计/æ¬æå®æ/å»ºè®¾é¶æ®µ/形象è¿åº¦ï¼ï¼éå
å®å¹é
é®ä¸è¦ççç¥ï¼å»ºè®® 1 å¸å·ååã |
| | | **临æ¶äº§ç©**ï¼`_test_passenger/`ï¼4 å¼ æ
客ç°å¯¼åº + 4 å¼ æèµç°å¯¼åºï¼å¯¹æ¯ç¨ï¼ã |
| | | |
| | | ### 2026-08-25ï¼ç»6ï¼å®æ½å®æï¼ï¼æ
客 4 表模æ¿å + 个ä½å®¢è¿é导å
¥ + æèµæ ¡éªå¼¹çªï¼ç¨æ·å·²ææ¿ 3 å£å¾ï¼ |
| | | **å£å¾ç¡®è®¤**ï¼â ä¸å£å¾æç»è·¨ç°¿å
¬å¼âæ¿æ¢ä¸ºåºå
æ°å¼ï¼2025 åä¿ç模æ¿ç¼åå¼ï¼ï¼â¡åå¸å·"个ä½å®¢è¿é/å¨è½¬é"è¡=ç¨æ·å¯¼å
¥ä¸ªä½æ°æ®åæå¸å·èåå¡«å
ï¼â¢ä¸å£å¾æå=åå累计ï¼é 1 个æåºå½æã1-6 æåºç´¯è®¡ï¼ã |
| | | **å端ï¼å·²ç¼è¯éå¯éªè¯ï¼** |
| | | - `application.yml` æ°å¢ `passenger.template-dir: docs/å
¬è·¯æ
客+è½è/è¾åº`ï¼`ReportExportService` 4 个æ
å®¢å¯¼åºæ¹æ³å
¨é¨æ¹ä¸ºæ¨¡æ¿åºç¨¿çæï¼ä¿ç表头/åå¹¶/å宽/å
¬å¼/æ ·å¼ï¼ä»
æ¿æ¢æ°æ®ï¼ï¼ |
| | | - åå¸å·ï¼2026 å¹´ 1..6 æåå¡«åºå
ç线客è¿é/å¨è½¬éï¼metric 0/1ï¼ï¼ä¸ªä½è¡å¡«ä¸ªä½å¯¼å
¥æ°æ®ï¼2025/2024 åå²åä¿ç模æ¿å¼ï¼ç´¯è®¡/忝/å
¨çè¡å
¬å¼ä¿çéç®ã |
| | | - ä¸å£å¾æç»ï¼æ°æ®åº 2378 个跨簿å¼ç¨å
¨é¨æ¿æ¢ââ2026 æåº¦åå¡«åºå
å¼ï¼ç线=h2031ãå
¬äº¤=cityBus åé
å乡ãåºç§/ç½çº¦è½¦=æ¾å¼ 0ï¼ï¼2025 åæ¿æ¢ä¸ºæ¨¡æ¿ç¼åå¼ï¼å»å¹´æ°æ®ä¸ä¸¢ï¼ï¼ç´¯è®¡/忝/æ»è¡å
¬å¼ä¿çéç®ã |
| | | - ä¸å£å¾æåï¼ååç´¯è®¡ï¼æ é¢éæ¥è¡¨æï¼1 æ="2026å¹´1æâ¦"ï¼1-6 æ="2026å¹´1-6æâ¦ç´¯è®¡"ï¼å¹´æ¥="1-12æâ¦"ï¼ï¼B/D/F/H å¡«å¼ï¼RANK/SUM å
¬å¼ä¿çã |
| | | - ä¸å£å¾åæï¼B/C/F/G å¡«å¼ï¼æ»/ç线/å
¬äº¤/åºç§/ç½çº¦è½¦ç´¯è®¡+忝ï¼ï¼å æ¯å
¬å¼ä¿çã |
| | | - æ°å¢ä¸ªä½å®¢è¿é导å
¥ï¼è¡¨ `passenger_individual_monthly`ï¼period/region_code/enterprise_code/enterprise_name/passenger_count/turnoverï¼ï¼æ¥å£ `POST /api/import/passengerIndividual`ï¼POI è§£æååå«"个ä½å®¢è¿é/个ä½å¨è½¬é"ï¼å period å¹çï¼ï¼å®ä½/Mapper `PassengerIndividualMonthly`ã |
| | | - æä»¶åç»ä¸ä¸ºæ ·ä¾åï¼çæ_å
¬è·¯æ
客åå¸å·.xlsxãçæ_ä¸å£å¾æç».xlsxãçæ_ä¸å£å¾æå.xlsxãçæ_ä¸å£å¾åæ.xlsxï¼REPORT_FILE_NAMES + series zip + åç«¯åæ¥ï¼ã |
| | | **å端ï¼chunk 4/7 已嫿°ä»£ç ï¼** |
| | | - `DataImport.vue`ï¼å
¬è·¯æ
å®¢ç»æ°å¢ã个ä½å®¢è¿é/å¨è½¬éã导å
¥ï¼æèµææ¥ï¼å®¢è¿/ç©æµï¼å¯¼å
¥æååèªå¨è° `audit/execute?reportType=INVEST`ï¼æ PENDING å¼¹çªååºå·®å¼æç»ï¼è§åå/项ç®/å®é
/åºå/å·®å¼ï¼ï¼æ å·®å¼æç¤ºä¸è´ã |
| | | - `ReportExport.vue`ï¼æ
客 4 å¼ é项å䏿 ·ä¾ä¸è´ï¼æç¤ºè¯æ´æ°ã |
| | | **éªè¯ï¼å
¨é¨éè¿ï¼** |
| | | - 4 å¼ è¡¨ dims/åå¹¶åå
æ ¼ä¸æ ·ä¾å®å
¨ä¸è´ï¼åå¸å· 58Ã284/237ãä¸å£å¾æç» 188Ã43/143ãæå 20Ã9/1ãåæ 7Ã8/1ï¼ï¼å¤é¨å¼ç¨æ®ç 0ã |
| | | - æ°å¼æ½æ¥ï¼æ¦æ± 6 æç线客è¿é 341.1759/å¨è½¬é 19301.074ï¼ä¸æ ·ä¾ä¸è´ï¼ï¼é»ç³ä¸ªä½å®¢è¿é 3.0/å¨è½¬é 150.0ï¼å¯¼å
¥ 30000 人/150 ä¸äººå
¬éï¼ï¼2025 åä¿ç模æ¿å¼ä¸è½¬æ°å¼ï¼R11=255.17ï¼ï¼å
¨çè¡å
¬å¼éç®æ£ç¡®ï¼å¨è½¬é K å 112416.37ï¼ã |
| | | - 个ä½å¯¼å
¥ï¼4 æ¡æå 0 失败ï¼éå¤å¯¼å
¥å¹çï¼æµè¯æ°æ®å·²æ¸
çï¼DB 0 æ¡ï¼ã |
| | | - å¹´æ¥æ¨¡å¼ï¼æ é¢"2026å¹´1-12æâ¦"æ£ç¡®ã |
| | | - æèµæ ¡éªï¼2 æ¡ PENDINGï¼éä¸é«é巴䏿¢çº½ç¼ºå¤±ã天é¨ç©æµ 3058 vs 600ï¼â åç«¯å¼¹çªæ°æ®æºå°±ç»ªã |
| | | **踩åï¼éè¦ï¼** |
| | | - **POI 5.2.5 `Cell.setCellValue(double)` 对å
¬å¼åå
æ ¼åªæ´æ°ç¼åå¼ãä¸ç§»é¤å
¬å¼**ï¼åå
¥åå¿
é¡»å
`setBlank()` å `setCellValue`ï¼æ¾å¼ 0 éæä½åºå± XMLï¼`getCTCell().setT(N).setV("0")`ï¼ãä¸å£å¾æç»å¤é¨å¼ç¨æ¿æ¢å æ¤è¿å·¥ä¸æ¬¡ã |
| | | - åå¸å·æ¨¡æ¿å¨è½¬é/个ä½è¡ A å为空ï¼åå¹¶åå
æ ¼åªåé¦è¡ï¼â è¡è¯å«é¡»ç¨ B åææ + åä¸ä¸ªé空 A åï¼currentRegionï¼ï¼å¦åè¿äºè¡è¢«è·³è¿ä¿çæ¨¡æ¿æ ·ä¾å¼ã |
| | | - å `metricValue(agg, 1)` è¿å客è¿éï¼0/1 å£å¾ç¸åï¼â åå¸å·å¨è½¬éè¡ç´æ¥å `agg.turnoverTotal/10000`ã |
| | | - å
±äº«å
¬å¼å¤æï¼POI `getCellFormula()` å±å¼æ£å¸¸ï¼ä½ `keepCached`/`fillMidCell` å¿
é¡»å
读ç¼åå¼å setBlankï¼é¡ºåºä¸å¯åï¼ã |
| | | **å¾
å**ï¼â åå¸å·/ä¸å£å¾æç»æ¨¡æ¿ä»
1-6 æåï¼7-12 æéç¨æ·ä¸å年模æ¿ï¼â¡ä¸å£å¾åºç§/ç½çº¦è½¦åé
åä¹¡æ°æ®æºå¾
æ¥å
¥ï¼ç°æ¾å¼ 0ï¼ï¼â¢æèµ M2ï¼æ 6/7 ææä»¶å¤¹å¸å·æ¥è¡¨æ´æ°ãæ´æ°_6/7æå®¢è¿ç«/ç©æµç«åºæèµæç»ãï¼æªå¼å·¥ï¼éå®å¹é
è§åï¼â£DB æèµæåº¦ä»
50 æ¡ 2026-07ï¼investment_monthlyï¼ï¼å®æ´æ¥è¡¨é补导å
¥ã |
| | | |
| | | ### 2026-08-25ï¼ç»7ï¼ï¼æ¨¡æ¿ä¸è´æ§å
¨éæ ¸æ¥ + è´§è¿ 3 表模æ¿åæ¹é ï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·è¦æ±**ï¼æ ¸æ¥ åå¸å®¢è¿/è´§è¿/è½è/æ¿åï¼æ
客/æèµï¼å¯¼åºæ¯å¦æ»¡è¶³ãä¸å¯¼å
¥æ¨¡æ¿çå宽ã表头ãåå¹¶è§åå®å
¨ä¸è´ï¼ä»
æ¿æ¢æ°æ®ï¼å¯¼åº Excelãã |
| | | **æ ¸æ¥ç»è®ºï¼14 å¼ å
¨é¨ PASSï¼** |
| | | - åå¸å®¢è¿ 6 å¼ ï¼å
¬äº¤/巡游åºç§/轨é轮渡/åå¸å·ç´¯è®¡/åå¸å·æç»/æ±æ»ï¼ï¼ç»æå®å
¨ä¸è´ï¼å« 63/63/95/18/18/4 åå¹¶ãå宽ã表头ï¼ï¼æ°æ®å·®å¼å为å£å¾æè´ï¼é彿/累计ã忝æ¸
空ãç½çº¦è½¦æ¾å¼ 0ï¼ã |
| | | - å
¬è·¯æ
客 4 å¼ ï¼åå¸å·/ä¸å£å¾æç»/æå/åæï¼ï¼ç»æå®å
¨ä¸è´ï¼237/143/1/1 åå¹¶ï¼ã |
| | | - è½è 1 å¼ ï¼å
¨ç Sheet1 ä¸ `模æ¿_è½è¿æ±æ»è¡¨ .xlsx` å®å
¨ä¸è´ï¼åå¹¶ 2ãå宽 5ï¼ï¼å¦æéæ±çæ 17 å¸å· sheetï¼æ 颿¹å模æ¿çã第Xå£åº¦ãï¼å代ç åãXå£åº¦ãï¼ã |
| | | - è´§è¿ 3 å¼ ï¼**æ¬æ¬¡ä»é¶å»ºè¡¨æ¹é 为模æ¿åºç¨¿çæ**ï¼æ¨¡æ¿=`docs/è´§è¿/çæ_*.xlsx`ï¼ä¸ `è´§è¿ç®åå·¥ä½.xlsx` åç»æï¼ï¼ |
| | | - åå¸å·æç»ï¼ä»¥æ¨¡æ¿ä¸ºåºç¨¿ï¼å¡« C~M æåº¦å¼/忝 + O/P 累计/ç´¯è®¡åæ¯ï¼æåº¦åä»
1-6 æï¼æ¨¡æ¿åºå®ï¼ï¼ç´¯è®¡åææéæ 1..N æï¼N>6 æ¶æåº¦åä»åªæ¾ç¤º 1-6 æï¼ä¸æ
客å£å¾ä¸è´ï¼7-12 æå¾
ä¸å年模æ¿ï¼ã |
| | | - è´§è¿éæå/å¨è½¬éæåï¼ä»¥æ¨¡æ¿ä¸ºåºç¨¿ï¼å¡« B~Pï¼è§ä¸/è§ä¸/å计 + æå/å å
¨çæ¯é/忝/å¢éæåï¼ï¼è´§è¿é Q/R å æ¯å
¬å¼ä¿çä¸å¨ï¼å¨è½¬é Q/R ææ°æ°æ®éç®ã |
| | | - æå表补ä¸äºåå
缺失ç è§ä¸åæ¯/è§ä¸åæ¯ï¼å
¨ç+å¸å·ï¼è§ä¸åæ¯=模æ¿å»å¹´å计-H2032å»å¹´è§ä¸ï¼ã |
| | | - æ°å¢ `freight.template-dir: docs/è´§è¿` é
ç½®ï¼application.ymlï¼+ `resolveFreightTemplate`ã |
| | | - ä¿®å¤è¿ç¨ä¸åç°çåï¼POI åå· 0-based å·®ä¸åï¼æåº¦å¼åå° B/D/F/..ï¼å·²ä¿®æ£ä¸º C/E/G/..ï¼ï¼åå¼åé¡» setBlankï¼POI å
¬å¼åå
æ ¼åªæ´æ°ç¼åï¼ã |
| | | **éªè¯æ¹å¼**ï¼`_test_check2/v5_*.xlsx`ï¼14 å¼ ç°å¯¼åºï¼éä¸ä¸æ¨¡æ¿å¯¹æ¯ dims/åå¹¶éå/å宽éåï¼å
¨é¨ PASSï¼æ½æ¥æå表 2026-07 è§ä¸æ°æ®è½ä½æ£ç¡®ï¼å
¨ç 186.8988ãæ¦æ± 72.9224 æå 1ãå å
¨çæ¯é 0.3902ï¼ï¼è½èæ é¢=2026年第äºå£åº¦è½èæ±æ»æ
åµè¡¨ã |
| | | **注æ**ï¼è´§è¿ DB å½åä»
h2032 æ 2026-07 æ°æ®ï¼40 æ¡ï¼ï¼`freight_turnover_import`/`scale_split_transport` 为空 â æç»/æåç°å¯¼åºå¤§é¨å为空ï¼ä»
è§ä¸è¡æå¼ï¼ï¼å±æ°æ®æªå¯¼å
¥ï¼éæ ¼å¼é®é¢ï¼ç¨æ·éå
导å
¥ã模æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æå表ãã |
| | | **å¾
åä¸å**ï¼â 模æ¿ä»
1-6 æåï¼7-12 æéä¸å年模æ¿ï¼è´§è¿/æ
客åï¼ï¼â¡æèµ 4 表ä»ä¸ºä»£ç èªå»ºå¸å±ï¼æ ·ä¾ä¸º .xls æ åå¹¶/æ èªå®ä¹åå®½ï¼æä¸æ ·ä¾å¯¹é½ï¼ï¼æªçº³å
¥æ¨¡æ¿åºç¨¿ã |
| | | |
| | | ### 2026-08-25ï¼ç»8ï¼ï¼æèµ 4 表æ¹ä¸ºã模æ¿åºç¨¿å使¿æ¢ãï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·è¦æ±**ï¼å
¬è·¯æ
客ãåå¸å®¢è¿ãè½èãæèµå个模åå¿
é¡»æãæ ¼å¼æ¨¡æ¿ãçå宽ã表头ãåå¹¶è§åå®å
¨ä¸è´çæï¼ä»
æ¿æ¢æ°æ®ï¼å¯¼åº Excelã |
| | | **æ¬è½®æ¹é ï¼ReportExportService.javaï¼`traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java`ï¼** |
| | | - `investPlan`ï¼å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ»ï¼çº¦ 2380 è¡èµ·ï¼ï¼éè¡æ«æ R5~æ«å°¾ï¼æ å计è¡/åæ é¢è¡/å¸å·å°è/åç»æ é¢/项ç®è¡ åç±»ï¼æªå¹é
é¡¹ç®æ´è¡æ¸
空ï¼ä¿çåå¹¶æ ·å¼ï¼ï¼`ä¸ã客è¿ç«åºåºç¡è®¾æ½å»ºè®¾`â客è¿ç«åºåã`äºã交éç©æµåºç¡è®¾æ½å»ºè®¾`âç©æµååºåã`ä¸ãèæ§è¥è¿è´§è½¦æ¥åºæ´æ°`âæ´åæ¸
ç©ºï¼æ æ°æ®æºï¼ã |
| | | - `investFiveYearLogistics`ï¼åäºäºè§åç©æµé¡¹ç®ï¼çº¦ 2511 è¡èµ·ï¼ï¼sheet1 å
¨éå使¿æ¢ï¼85Ã248/99 åå¹¶ï¼ï¼sheet0ï¼æ2ï¼ä»
æ´æ°æ¥åæ é¢ï¼æ¨¡æ¿ sheet0 æ¬èº«æ æ ·ä¾æ°æ®ï¼ã |
| | | - `investBillion`ï¼äº¿å
æèµï¼ï¼ä¸è½®å·²ä¸ºæ¨¡æ¿åºç¨¿ï¼ä¿æï¼17Ã6/2 + 80Ã24/25ï¼ã |
| | | - `investCounty`ï¼ç»æµå¼ºå¿ï¼çº¦ 2659 è¡èµ·ï¼ï¼éå R4 èµ·æ B å项ç®åå¹é
ï¼æªå¹é
ä¿ç空ç½ã |
| | | - æ°å¢è¾
婿¹æ³ï¼çº¦ 2710 è¡èµ·ï¼ï¼`nvl`/`blankInvestRow`ï¼setBlank 0..lastCol ä¿çæ ·å¼/åå¹¶ï¼/`templateCityFull`ï¼æ¨¡æ¿çåâè§èå¸å·ï¼å« æåº/æ©æ½ å«åï¼èµ° RegionUtilï¼/`isCityLabel`/`matchInvest`ï¼å
æç±»å«/å¸å·è¿æ»¤åéï¼åè¯ååæä¼ï¼/`normInvestName`/`investNameScore`ï¼å½ä¸å精确>åç¼å
å«>ä¸åé¨å
å«>5å>ååºå>LCS å
åºï¼å¿åºåç¼ä¸ä¸è´æ£ 400ï¼å½ä¸ååå䏿¨¡æ¿å¸¦å¿åºä½åéä¸å«âæç»ï¼é¿å
å®é½âå½é³ 误é
ï¼/`isInvestSubsequence`/`investLcs`/`extractInvestCounty`ã |
| | | - `writeInvestAggHssf`ï¼ç§»é¤ col12ï¼èªç¹ï¼åå
¥ï¼æ æ°æ®æºï¼ï¼ä¿ç 7/8/9/22/23ã |
| | | **éªè¯ï¼å
¨é¨éè¿ï¼** |
| | | - ç»æå¯¹æ¯ `_shots/cmp_invest2.py`ï¼4 表 dims/åå¹¶/å宽䏿¨¡æ¿å®å
¨ä¸è´ï¼investPlan 272Ã38/111ï¼investFiveYearLogistics sheet0 32Ã24/33 + sheet1 85Ã248/99ï¼investBillion 17Ã6/2 + 80Ã24/25ï¼investCounty 65534Ã256/57ï¼PASSã |
| | | - æ°æ®è½ä½ `_shots/dump_data_check.py`ï¼å
¨çå计ãå¸å·å°èãé¡¹ç®æ°æ®ãèæ§è´§è½¦æ®µæ¸
空ãäºäºè¡¨åè®¡è¡æ¸
空ã强å¿è¡¨é¡¹ç®å夿¡å·åå
¥åæ£ç¡®ã |
| | | - åç§°å¹é
è´¨é `_shots/sim_match3.py`ï¼PLAN 130/183ãFIVEYEAR 40/63ãCOUNTY 41/41ï¼æªå¹é
åä¸ºâæ¨¡æ¿æ ·ä¾æä½æ±æ»å¤§è¡¨/DB æ âç项ç®ï¼èæ§è´§è½¦æ®µãåæç«äºãæ¦æ±å¤©æ²³ç»¼åå®¢è¿æ¢çº½ãèå·å¸éæ°´èè¿ç©æµä¸å¿çï¼ï¼å±æ°æ®ç¼ºå¤±é误é
ã |
| | | - 产ç©ï¼`_shots/invest_out2/`ï¼4 .xls + selected4.zipï¼ï¼æ¬è½® API 夿µï¼`GET /api/report/export/selected?period=2026-07&mode=month&types=investPlan,investFiveYearLogistics,investBillion,investCounty` æ£å¸¸è¿å zipï¼4 æä»¶é½å
¨ï¼å½åæ£ç¡®ï¼ã |
| | | **å端ç°ç¶ç¡®è®¤ï¼æ¬è½®æ ¸å¯¹ï¼æ éåæ¹ï¼** |
| | | - æ¥è¡¨çæé¡µï¼ç¬¬ä¸æ è´§è¿/å
¬è·¯æ
客/åå¸å®¢è¿ï¼ç¬¬äºæ è½è/æèµï¼çæå
容å¤é䏿+å
¨é/æ¸
空+åã导åºãæé®ï¼1 å¼ ç´æ¥ä¸è½½ãå¤å¼ æå
zipï¼ï¼ææ¥/å¹´æ¥ åéé½å
¨ãæªå¾ `_shots/report_page.png`ã |
| | | - æ°æ®å¯¼å
¥é¡µï¼æ¥è¡¨æå¡ç + è´§è¿/å
¬è·¯æ
客/åå¸å®¢è¿/è½è/æèµäºä¸ªä¸ææ¡ï¼æ¨¡åéæ©ï¼ï¼ä¸ä¼ /导å
¥/æè¿è®°å½é½å
¨ã |
| | | - ä¼ä¸è§£é页ï¼4 ç±»æ¥è¡¨åæ®µå®æ´ï¼ä¼ä¸åç§°/è½¦è¾æ°/客è¿é/å¨è½¬é/å¹³åè¿è·/ä¼ä¸è§£éçï¼ï¼æ âåªè½çå°ä¼ä¸åâé®é¢ã |
| | | - æ°æ®æ¥çï¼è½¨è¿¹éç¨/è´§è¿éå¨è½¬é/H203-1/åå¸å
¬äº¤ææ¥/巡游åºç§ææ¥ åæãå
¨å±æ¥çãã |
| | | - å¯ç å å¯ï¼sys_user.admin å·²æ¯ BCryptï¼60 ä½ï¼ï¼æ»¡è¶³âè´¦å·å å¯åå¨âéæ±ã |
| | | **éç**ï¼â èæ§è´§è½¦æ¥åºæ´æ°å/äºäºè¡¨åè®¡è¡æ æ°æ®æºï¼çäºææ°æ®ï¼ï¼â¡è´§è¿ DB ç¼ºãæ¨¡æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æå表ã导å
¥ï¼æç»/æå导åºå¤§é¨åä¸ºç©ºï¼æ ¼å¼æ£å¸¸ï¼æ°æ®æªå¯¼å
¥ï¼ï¼â¢æ¨¡æ¿ä»
1-6 æåï¼7-12 æéä¸å年模æ¿ï¼è´§è¿/æ
客åï¼ï¼â£æèµ M2ï¼å¸å·å表解æï¼æªå¼å·¥ã |
| | | |
| | | |
| | | ### 2026-08-25ï¼ç»9ï¼ï¼HANDOFF.md æ¢å¤ + ä¸çåæ¶å°¾ï¼å·²å®æï¼ |
| | | **äºä»¶**ï¼æ¬ä¼è¯ä¸ HANDOFF.md æ¾è¢«è¯¯è¦çï¼ä¸æ¡ PowerShell WriteAllLines å½ä»¤å AddRange(Object[]) ç±»åé误失败ï¼`$result` åªå©æ°å¤´è¡ â æä»¶è¢«è¦çæ 506 åèï¼ï¼éå䏿¬¡æ bug çéæ¾åæå®æ±¡ææ 28,084 åèï¼ãåèãéå¤ï¼ã |
| | | **æ¢å¤æ¹æ³ï¼å·²å®æå¹¶éªè¯ï¼**ï¼ä» 4 个ä¼è¯ JSONL æ¥å¿éæ¾ 31 æ¡åå²åå
¥å½ä»¤å°æ²ç `_shots/handoff_replay/`ï¼å¿
é¡»ç¨ pwsh 7.6.4 + è·¯å¾æ¿æ¢ä¿®å¤çèæ¬ï¼è·¯å¾å«åææ éç¨ chr(92) æ¼æ¥é¿å
被 Python è§£ææè½¬ä¹ï¼ï¼éåæ ¡éªï¼base ä¸ 08-22 åæä¸è´ãæä»¶å¤´å°¾ä¸ç ´åå读åå¿«ç
§ä¸è´ã忥è¾åºä¸åå§è¾åºå¯¹é½ãå·²æ¢å¤ä¸º 74,447 åèï¼å 74,547ï¼å·® 100 åè/52 å符为ä¸é´æ®µç©ºç½çº§å·®å¼ï¼æ¥å¿å·²æªææ æ³æ¢å¤ï¼ä¸å½±åå
容ï¼ã污æçå¤ä»½å¨ `_shots/HANDOFF.md.polluted_1633.bak`ï¼ä¿çæªå ï¼ã |
| | | **ä¸çåæ¶å°¾**ï¼å·²ç¨ `start-dev.ps1` 宿´éå¯åå端并éªè¯ï¼ |
| | | - å端 8090 å°±ç»ªï¼æ¥å¿ backend.logï¼`Started TrafficAuditApplication` + `Tomcat started on port(s): 8090`ï¼PID 30380ï¼HTTP 200ï¼ã |
| | | - å端 8080 å°±ç»ªï¼æ¥å¿ traffic-audit-web/frontend.logï¼`Compiled successfully` + `App running at:`ï¼PID 23624ï¼HTTP 200ï¼ã |
| | | - IDEA æ¬ä½å¨è¿è¡ï¼idea64.exeï¼11:00 å¯å¨ï¼ï¼å端å½åç±å½ä»¤è¡/start-dev 管çï¼è¥æ³æ¹å IDEA 管çï¼å
å¨ IDEA Run é
ç½®é `TrafficAuditApplication` ç¹ â¶ï¼ä¼å ç¨ 8090ï¼éå
åå½ä»¤è¡å®ä¾ï¼ï¼æç´æ¥ç¨èæ¬éå¯ã |
| | | **䏿¬¡å¼å·¥å»ºè®®**ï¼å
读 AGENTS.md + æ¬æä»¶ï¼ç¡®è®¤æèµ M2ï¼å¸å·å表解æï¼ä¼å
级ï¼éçäºé¡¹è§ç»8ï¼è´§è¿æç»/æåç¼ºæ°æ®æºã模æ¿ä»
1-6 æåãèæ§è´§è½¦æ¥åºæ´æ°åçäºæï¼ã |
| | | |
| | | |
| | | ### 2026-08-26 忍ä¼è¯ï¼æ°æ®åºæ¸
空 + ç¨æ· 4 项æè§å¤çï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·æä»¤**ï¼â ææè¾åºææ¡£å¿
é¡»ä¸ãæ ¼å¼æ¨¡æ¿ãå宽/表头/åå¹¶å®å
¨ä¸è´ãä»
æ¿æ¢æ°æ®ï¼â¡éæ¨¡åæ ¸å¯¹éæ±ï¼ç¹åï¼å·¡æ¸¸åºç§è§å 7 æ¡çé¢åªæ¾ç¤º 4 æ¡ãè½èè§å没çï¼ï¼â¢ä¼ä¸è§£éè¡é´è·å¤ªå¤§ï¼â£éå¤å¯¼å
¥æ¯å¦ä¼å两份ã |
| | | **0. æ°æ®åºæ¸
空ï¼ç¨æ·è¦æ±ï¼** |
| | | - 26 å¼ è¡¨ TRUNCATE å
¨æ¸
ï¼è¡¨ç»æä¿çãèªå¢éç½®ï¼ï¼å¤ä»½ `_shots/db_backup_20260826_004520.sql`ï¼28,115,426 åèï¼å«å
¨åºç»æä¸æ°æ®ï¼ã |
| | | - æ¸
空åå·²æ¢å¤ `audit_rule`ï¼ä»å¤ä»½æåå 33 æ¡ + æ°å¢ H204 6 æ¡ = 39 æ¡ã`sys_role`(4)/`sys_dept`/`sys_user` æªæ¢å¤ï¼å sys_user æ¬å°± 0 è¡ï¼éè¦æ¶ä»å¤ä»½æ init.sql éæ°åå§åï¼ã |
| | | **1. 模æ¿ä¸è´æ§ï¼æ ¸å¿ä¿®å¤ï¼è·¯å¾é
ç½®é误ï¼** |
| | | - åç°ï¼`application.yml` ç `passenger.template-dir: docs/å
¬è·¯æ
客+è½è/è¾åº` æå**ä¸åå¨**çç®å½ï¼å®é
ç®å½ `docs/å
¬è·¯æ
客/è¾åº`ï¼ï¼è½èæ `energy.template-dir` é
ç½®ï¼é»è®¤ `docs/å
¬è·¯æ
客+è½è` åæ ·ä¸åå¨ï¼ï¼ä»£ç 读è½èæ¨¡æ¿ `模æ¿_è½è¿æ±æ»è¡¨ .xlsx` èç¨æ·åæ¨æ´æ°çæ¯ `docs/è½è/çæ_è½è¿æ±æ»è¡¨.xlsx`ï¼00:24~00:43 ç¨æ·æ¹éæ´æ°äºè´§è¿ 3 å¼ + æ
客 4 å¼ + è½è 1 å¼ æ¨¡æ¿ï¼ã |
| | | - ä¿®å¤ï¼`application.yml` â passenger=docs/å
¬è·¯æ
客/è¾åºãæ°å¢ energy=docs/è½èï¼`ReportExportService` è½è模æ¿åæ¹ä¸º `çæ_è½è¿æ±æ»è¡¨.xlsx`ã |
| | | - éªè¯ï¼10 å¼ å¯¼åºï¼è´§è¿ 3 + æ
客 4 + è½è 1 + åå¸å
¬äº¤/巡游åºç§ï¼è°ç¨ `/api/report/export/*` å
¨é¨ 200ï¼ç¨ openpyxl å¯¹æ¯æ¨¡æ¿ä¸å¯¼åºï¼sheet 维度/åå¹¶åå
æ ¼æ°/å宽 å
¨é¨ PASSï¼æèµ 4 表 zipï¼selectedï¼æ£å¸¸è¿åï¼ç»ææ¤åå·²éªï¼ã |
| | | **2. å®¡æ ¸è§åæ ¸å¯¹** |
| | | - CITY_TAXIï¼`sql_city_taxi.sql` 䏿¸
空å `audit_rule` å 7 æ¡ï¼RULE_T001~T007ï¼ï¼`AuditEngineService` 弿 5 ç§ compareType å
¨é¨å®ç°ï¼å« T003 å¨è½¬éç¯æ¯æ¨å¯¼ãT006 å车次载客人æ°åæ¨ãT007 载客车次ä¸å®¢è¿éè¶å¿ï¼ãâçé¢åªæ 4 æ¡âåå ï¼å®¡æ ¸ç»æé¡µåªæ¾ç¤º**触åçé®é¢è®°å½**ï¼æªè§¦åçè§åçä¸å°ï¼ä¸åè§å管ç页æ¯ç©ºå£³ `<div>è§å管ç</div>`ãå端æ è§å APIãâ å·²å®ç°è§å管ç页ï¼è§ä¸ï¼ã |
| | | - H204 è½èï¼éæ± 7 æ¡ï¼ååªå®ç° 4 æ¡ï¼E001~E004ï¼E004 ä¸ºéæ±å¤è¡¥å
ï¼ã**æ°å¢ RULE_E005~E010**ï¼å¼æ `checkEnergyLoadedRatio/FuelRange/FuelMom/VsFreightReport/AvgDistVsFreight/LoadedTonnageRatio` + `prevQuarter`/`fuelPer100kmMedian` è¾
å©ï¼`executeEnergyAudit` å¢å ä¸ä¸å£åº¦ç¯æ¯åºåä¸ H2032 ä¼ä¸ææ¥åºåï¼`docs/init.sql` è¡¥ç§åï¼å·²å
¥åºï¼ã |
| | | - è§å管çé¡µï¼æ°å¢ `RuleManageController.java`ï¼GET /api/rules/listãPOST /api/rules/toggleãPOST /api/rules/updateï¼+ éå `RuleManage.vue`ï¼6 ç±»æ¥è¡¨ç±»å忢ãè§åå
¨å表ãéå¼å¯ç¼è¾ãç级å¯éãå¯ç¨å¼å
³ï¼ã |
| | | **3. ä¼ä¸è§£éè¡é«** |
| | | - `ExplainReview.vue`ï¼è¡¨æ ¼ `size="mini"` + è¡é« 32px + åå
æ ¼ padding 3pxï¼è§£éå `max-height:62px` å
鍿»å¨ï¼ä¸åæè¡æå¾è¿é«ã |
| | | **4. éå¤å¯¼å
¥å¹ç确认** |
| | | - å端ææå¯¼å
¥æ¹æ³ï¼H2032/H2031/个ä½/è½è/è¿æ¿/轨迹/è§ä¸è§ä¸/è´§è¿å¨è½¬é/æèµ/ç³»ç»å¯¼åº/å
¬äº¤/åºç§/åºç§è¿æ¿ï¼å为ãåæ¥è¡¨æå
å æ§æ°æ®+æ¸
ç对åºå®¡æ ¸ç»æåæå
¥ãï¼éå¤å¯¼å
¥**ä¸ä¼å两份**ï¼`import_batch` æ¯æ¬¡å¯¼å
¥è®°ä¸æ¡å±æä½ççï¼é¢æï¼ã |
| | | - å端 `DataImport.vue` æé® `:loading` é²è¿ç¹ï¼`importCityTaxiAuth` æ `synchronized`ï¼å
¶ä½æ¹æ³æ éï¼åç¨æ·å®é
æ å¹¶åé®é¢ï¼å¦é强ä¸è´å¯å ï¼ã |
| | | **å½åè¿è¡ç¶æ** |
| | | - å端 8090 å·²ç¨ start-dev.ps1 éå¯å è½½æ°ä»£ç ï¼backend.log `Started TrafficAuditApplication` + `Tomcat started on port(s): 8090`ï¼HTTP 200ï¼`/api/rules/list?reportType=H204` è¿å 10 æ¡ï¼ã |
| | | - å端 8080 å¨è·ï¼frontend.log `Compiled successfully` + `App running at:`ï¼çæ´æ°å·²å è½½ RuleManage/ExplainReview æ°é¡µé¢ï¼ã |
| | | - éªè¯äº§ç©ï¼`_shots/verify_export/`ï¼10 å¼ å¯¼åº + invest_selected.zipï¼ã`_shots/cmp_struct2.py`ã`_shots/cmp_width.py`ã`_shots/extract_rules.py`ã`_shots/old_audit_rule.sql`ã`_shots/patch_*.py` åä¿çã |
| | | **éç/å¾
å**ï¼â æèµ M2ï¼å¸å·å表容éè§£æï¼ä»æªå¼å·¥ï¼ç´ æå¨ `docs/æèµ/è¾å
¥/` é½å¤ï¼ï¼â¡7-12 ææ¨¡æ¿éç¨æ·æä¾ä¸å年模æ¿ï¼æç¡®è®¤èªè¡æ©å± 1-6 ææ¨¡æ¿ï¼ï¼â¢è´§è¿ã模æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æåè¡¨ãæªå¯¼å
¥ï¼æç»/æåå¯¼åºæ°æ®ä¸ºç©ºï¼ï¼â£èæ§è´§è½¦æ¥åºæ´æ°å/äºäºè¡¨åè®¡è¡æ æ°æ®æºï¼â¤æ°æ®åºå·²æ¸
空ï¼ä¸å¡æ°æ®ä¸ `sys_role`/`sys_user` ééæ°åå§åï¼â¥è½èæ° 6 æ¡è§åçéå¼ï¼ä¸ä½æ°Â±50%ãç¯æ¯Â±50%ãææ¥å¯¹æ¯ 50%/60% çï¼è¯·ç¨æ·ç¡®è®¤å£å¾ã |
| | | |
| | | |
| | | ### 2026-08-26 ä¸åä¼è¯ï¼å®¡æ ¸ç»æé¡µä½éªä¼åï¼keep-alive + ææ¡æç»å¡çè§å¾ï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ· 2 个é®é¢**ï¼â ç¹ãæ°æ®ã龿¥è·³è½¬ DataView è¿ååï¼å®¡æ ¸ç»æä¸¢å¤±ééæ°ç¹ãæ§è¡å®¡æ ¸ãï¼â¡ãææ¡æç»ãçé¢å¤ªä¸ãä¼ä¸åé®é¢é½çä¸å
¨ã |
| | | **æ¬è½®æ¹å¨ï¼çº¯åç«¯ï¼æ éå端ï¼** |
| | | - `traffic-audit-web/src/App.vue`ï¼`<router-view />` å¤å
`<keep-alive include="AuditResult">`ï¼è·³è½¬ãæ°æ®/è§£éãåè¿åæ¶ç»ä»¶å®ä¾è¢«ç¼åï¼æ¥è¡¨æ/è¿æ»¤æ¡ä»¶/å®¡æ ¸ç»æ/éä¸ç¶æåæ ·ä¿çï¼ä¸åéè¦éæ°æ§è¡å®¡æ ¸ã |
| | | - `traffic-audit-web/src/views/AuditResult.vue`ï¼ææ¡æç»ç± el-table æ¹ä¸ºã䏿¡é®é¢ä¸å¼ å¡çãï¼ |
| | | - å¡ç头é¨ï¼å¾éæ¡ + ä¼ä¸åï¼å®æ´å±ç¤ºï¼+ æ¥è¡¨æ + ç级 tag + ç¶æ tag + ãæ°æ®/è§£éã龿¥ + 确认/å¿½ç¥æé®ï¼ |
| | | - å¡ç主ä½ï¼è§åå + è§åç¼ç + å®é
/åºå/å·®å¼ ä¸è¡ï¼æµ
ç°åºåç»å±ç¤ºï¼é¿ææ¬èªå¨æ¢è¡ï¼ï¼ |
| | | - é¡¶é¨å·¥å
·æ å ãå
¨éå½å N æ¡ãï¼å«åéæ indeterminateï¼ï¼é
ååææ¹é确认/忽ç¥ï¼ |
| | | - 红è²é®é¢å¡ç左边æ¡çº¢è²ãé»è²å·¦è¾¹æ¡æ©è²ãå·²å¤çå¡çæ´ä½æ·¡åï¼å表åºç¬ç«æ»å¨ã |
| | | - æ°æ®å è½½æ¶ç»æ¯æ¡ç»æåå§å `_selected:false`ï¼ä¿è¯ el-checkbox ååºå¼ï¼ï¼æ¹ééæ©æ¹ä¸º `filteredResults.filter(r => r._selected)` 宿¶è®¡ç®ã |
| | | **éªè¯**ï¼frontend.log `Compiled successfully in 678ms`ï¼çæ´æ°çæï¼10:31:11ï¼ï¼é¡µé¢è·¯ç±è·³è½¬/è¿åç¶æä¿çé»è¾ç± keep-alive ä¿è¯ã |
| | | - 追å ï¼10:42ï¼ï¼ç¨æ·åé¦é®é¢åºåä»åå° â æ°å¢ãèç¦æ¥çãæ¨¡å¼ï¼`focusMode`ï¼ï¼ä¸é®éèç»è®¡å¡/çéæ /è§åç»è®¡/h3 æ é¢ï¼ä»
çæ¥è¡¨ç±»å+æ¥è¡¨æ+æé®ä¸è¡ + é®é¢åè¡¨å æ»¡ï¼`main-focus` æ¶ç´§å
è¾¹è·ï¼ï¼éåºèç¦è¿åãfrontend.log `Compiled successfully in 1103ms`ï¼10:42:30ï¼ãpatchï¼`_shots/patch_focus_mode.py`ã |
| | | **产ç©**ï¼`_shots/patch_detail_view.py`ï¼æ¬è½® patch èæ¬ï¼ä¿çï¼ã |
| | | **éç/å¾
åä¸å**ï¼æèµ M2 仿ªå¼å·¥ï¼ç´ æå¨ `docs/æèµ/è¾å
¥/` é½å¤ï¼ï¼7-12 ææ¨¡æ¿å¾
ç¨æ·æä¾æç¡®è®¤æ©å±ï¼è´§è¿ã模æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æåè¡¨ãæªå¯¼å
¥ï¼æç»/æåå¯¼åºæ°æ®ä¸ºç©ºï¼ï¼è½èæ° 6 æ¡è§åéå¼å£å¾å¾
ç¨æ·ç¡®è®¤ã |
| | | |
| | | |
| | | ### 2026-08-26 ä¸åä¼è¯ï¼ç»2ï¼ï¼æ°æ®å¯¼å
¥æ¨¡åè¡¥å
¨æ
客/è½èè¿æ¿ç¬ç«å¯¼å
¥ï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·åé¦**ï¼æ°æ®å¯¼å
¥é¡µãå
¬è·¯æ
客ãä¸æåªæææ¥+个ä½ä¸¤é¡¹ï¼è´§è¿å´æ 5 项ï¼ï¼ãè½èãåªæ 1 项ï¼å®é
`docs/å
¬è·¯æ
客/模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsx`ã`docs/è½è/模æ¿_è½è车è¾è¿æ¿ä¿¡æ¯.xlsx` 齿ç¬ç«æ¨¡æ¿ä½æ æ³åç¬å¯¼å
¥ï¼åªè½éææ¥/壿¥ç¬¬äºä¸ª sheet èªå¨è§£æï¼ã |
| | | **设计ç»è®º**ï¼æ¨¡å䏿 = è¯¥æ¨¡åæ¨¡æ¿æ¸
åï¼è¿æ¿ç±»æ¨¡æ¿æ¯æç¬ç«å¯¼å
¥ï¼åæ¶ä¿çãé主æä»¶ç¬¬äºä¸ª sheet èªå¨å¯¼å
¥ãçå
¼å®¹è¡ä¸ºï¼å导å
¥è¦çï¼å¹çï¼ã |
| | | **å端**ï¼`DataImportService.java` + `DataImportController.java`ï¼ |
| | | - æ°å¢ `POST /api/import/passengerAuth`ï¼è§£æ 4 åï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼â `passenger_auth_vehicle`ï¼åæ¥è¡¨æå
å æ§æ°æ® + æ¸
H2031 å®¡æ ¸ç»æï¼`deleteRuleResults` æ°ç§ææ¹æ³ï¼ã |
| | | - æ°å¢ `POST /api/import/energyAuth`ï¼è§£æ 5 åï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼â `h204_auth_vehicle`ï¼åæ¥è¡¨æå
å + æ¸
H204 å®¡æ ¸ç»æã |
| | | **å端**ï¼`DataImport.vue`ï¼ |
| | | - å
¬è·¯æ
客䏿å ãè¿æ¿è½¦è¾æ°ãï¼è½è䏿å ãè½è车è¾è¿æ¿ä¿¡æ¯ãï¼TYPE_LABELS è¡¥æ°ç±»å + 大åé®ï¼æè¿å¯¼å
¥è®°å½ H2031/H204/PASSENGER_AUTH çæ¾ç¤ºå好åï¼ï¼typeDesc/templateName/endpoint åæ¥è¡¥å
ã |
| | | **éªè¯**ï¼å端 `mvn compile` éè¿ï¼éå¯å curl 宿µï¼æ
å®¢è¿æ¿æ¨¡æ¿å¯¼å
¥ 767 æ¡ãè½èè¿æ¿æ¨¡æ¿å¯¼å
¥ 1114 æ¡ï¼åæåï¼æµè¯æ 2099-01 å·²æ¸
çï¼ï¼å端 frontend.log `Compiled successfully in 308ms`ï¼10:55:35ï¼ã |
| | | **说æ**ï¼åå¸å®¢è¿ç轨é/轮渡/ç½çº¦è½¦æ¨¡æ¿å±ææ¥åæ®µä¸æåé»è¾ï¼éæ±ææ¡£ç¡®è®¤ï¼ï¼éç¬ç«å¯¼å
¥ç±»åï¼ä¸æä¿æ 3 é¡¹ï¼æèµæ¨¡æ¿æå¸å·ä¸æ¥åå§æä»¶ + æ±æ»å¤§è¡¨å¤çï¼ä¸æä¿æ 3 项ã |
| | | **产ç©**ï¼`_shots/patch_auth_import.py`ï¼å端ï¼ã`_shots/patch_dataimport_options.py`ï¼å端ï¼ã`_shots/compile_auth_import.log`ã |
| | | |
| | | |
| | | ### 2026-08-26 ä¸åä¼è¯ï¼ç»3ï¼ï¼æ°æ®å¯¼å
¥é¡µæ¹çââå¾
导å
¥éå + æ¬æå¯¼å
¥è¿åº¦æ»è§ï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·åé¦**ï¼å¯¼å
¥é¡µäºä¸ªæ¨¡åä¸æäºæ¥ï¼éä¸ä¸ªæ¸
空å
¶ä»ï¼ï¼ä¸æ¬¡åªè½ä¼ ä¸ä¸ªæä»¶ï¼æ¯æé夿ä½å¤ï¼è¦ä¸ä»½"æ¬æåæ¨¡å导å
¥ç¶æ"çæ£æ¥æ¸
åã |
| | | **å端**ï¼`DataImport.vue` æ´ä½éåï¼`_shots/rewrite_dataimport.py`ï¼ |
| | | - äºä¸ªæ¨¡å䏿äºç¸ç¬ç«ï¼äºæ¥é»è¾å é¤ï¼ï¼éç±»ååè¡å
åºç°ãéæ©æä»¶ â å å
¥éåãï¼æ¨¡æ¿å/说æ/大æä»¶èæ¶æç¤ºè·éå½åéä¸ç±»åæ¾ç¤ºã |
| | | - æ°å¢ãå¾
导å
¥éåãå¡çï¼æ¨¡å/ç±»å/æä»¶/ç¶æï¼å¾
导å
¥/导å
¥ä¸/æå/失败ï¼/ç»æï¼å¤±è´¥å¸¦åå ï¼/æä½ï¼åæ¡å¯¼å
¥ãç§»é¤ï¼ï¼é¡¶é¨ãå
¨é¨å¯¼å
¥ï¼Nï¼ã串è¡é个æ§è¡ï¼æèµç±»å导å
¥åèªå¨è§¦åæèµç³»ç»æ ¡éªå¼¹çªã |
| | | - æ°å¢ãæ¬æå¯¼å
¥è¿åº¦ãå¡çï¼ææ¥è¡¨æ à 模åç±»åå±ç¤º 已导å
¥Nè¡ï¼ç»¿ï¼/é¨åå¤±è´¥ï¼æ©ï¼/æªå¯¼å
¥ï¼ç°ï¼ï¼éæ¥è¡¨æåæ¢èªå¨å·æ°ã |
| | | - ã导å
¥è®°å½ãæ¹ä¸ºæå½åæ¥è¡¨æè¿æ»¤æ¾ç¤ºï¼limit 50ï¼ï¼ææ¡ä¿®æ£ä¸º"éæ©ç±»åå¹¶æè½½æä»¶"ã |
| | | **å端**ï¼`DataImportController.java`ï¼ |
| | | - `/api/import/batches` å¢å å¯é `period` åæ°ï¼limit ä¸éæå° 200ï¼ï¼ä¾è¿åº¦æ»è§ææ¥è¡¨ææåã |
| | | **éªè¯**ï¼å端 `mvn compile` éè¿ + start-dev.ps1 éå¯å°±ç»ªï¼`GET /api/import/batches?period=2026-07` æ£å¸¸è¿å该æè®°å½ï¼è´§è¿éå¨è½¬é/è§ä¸è§ä¸/轨迹/è¿æ¿/巡游åºç§å已导å
¥ï¼ï¼å端 frontend.log `Compiled successfully in 522ms`ï¼11:01:41ï¼ã |
| | | **å¾
åï¼ç¨æ·å¯éçé¦ä¸æ·»è±ï¼**ï¼æ¯ä¸ªå¯¼å
¥ç±»åæå ãä¸è½½æ¨¡æ¿ã龿¥ï¼å端æ´é² docs 模æ¿éææä»¶ï¼ï¼ç¨æ·ä¸ç¨å»æä»¶å¤¹æ¾æ¨¡æ¿ã |
| | | |
| | | |
| | | ### 2026-08-26 ä¸åä¼è¯ï¼ç»4ï¼ï¼æ°æ®å¯¼å
¥é¡µãä¸è½½æ¨¡æ¿ãå
¥å£ï¼å·²å®æéªè¯ï¼ |
| | | **å®ç°** |
| | | - å端ï¼`application.yml` æ°å¢ `docs.template-root: docs`ï¼æ°å»º `TemplateDownloadController.java`ï¼`com.trafficaudit.config`ï¼ï¼`GET /api/templates/{*path}` ä¸è½½ docs 䏿¨¡æ¿ï¼Content-Disposition 带 UTF-8 æä»¶åï¼é²è·¯å¾ç©¿è¶ï¼æ¨¡æ¿å®ä½ä¸æ¥è¡¨æ¨¡åä¸è´ï¼é
ç½®ç®å½ â user.dir â user.dir/.. é级åéï¼å spring-boot:run ç user.dir æ¯ traffic-audit-serverï¼ãéæèµæºæ å°æ¹æ¡ï¼WebConfigï¼å ç¸å¯¹è·¯å¾è§£æå¤±è´¥å·²å¼ç¨å é¤ã |
| | | - å端 `DataImport.vue`ï¼æ¯ä¸ªå¯¼å
¥ç±»åæ°å¢ `templateUrl`ï¼docs ç¸å¯¹è·¯å¾ï¼ï¼æ¨¡æ¿è¯´æåºæ¾ç¤ºãä¸è½½æ¨¡æ¿ã龿¥ï¼æèµä¸¤æ¨¡æ¿æå `docs/æèµ/è¾å
¥/客è¿ï¼å®¢è¿ç«ï¼/` ä¸ `docs/æèµ/è¾å
¥/ç©æµï¼ç©æµååºï¼/` ä¸çæ´æ°æç»æä»¶ï¼`passengerIndividual` æ ç¬ç«æ¨¡æ¿æä»¶ï¼ä¸æ¾ç¤ºä¸è½½ã |
| | | **éªè¯**ï¼14 个模æ¿ä¸è½½å
¨é¨ 200ï¼å«ä¸æè·¯å¾ä¸å
¨è§æ¬å·/éå·ï¼ï¼å端 frontend.log `Compiled successfully in 505ms`ï¼11:10:10ï¼ã |
| | | **注æ**ï¼æèµæ¨¡æ¿æä»¶å带æä»½ï¼æ´æ°_7æâ¦ï¼ï¼æ¯æéåæ¥æ´æ° `DataImport.vue` ç templateUrl ææ¹ä¸ºå¨ææ å°ã |
| | | |
| | | |
| | | ### 2026-08-26 ä¸åä¼è¯ï¼æ°æ®å¯¼å
¥é¡µ tabs å + å
¨å±æ¨¡å顺åºç»ä¸ï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·åé¦**ï¼â æ°æ®å¯¼å
¥ç颿¨¡å太å¤ã飿 ¼ç¹çï¼â¡ææé¡µé¢æ¨¡å顺åºåºç»ä¸ä¸ºãè´§è¿ â å
¬è·¯æ
客 â åå¸å®¢è¿ â æèµ â è½èãï¼å½åé¨å页颿æèµ/è½èæå¨åé¢ï¼ã |
| | | **å端æ¹å¨ï¼4 个 Vue æä»¶ï¼** |
| | | - `DataImport.vue`ï¼äºä¸ªæ¨¡åä¸æå¹¶ææ¹ä¸º **el-tabs é项å¡**ï¼é¡ºåºï¼è´§è¿/å
¬è·¯æ
客/åå¸å®¢è¿/æèµ/è½èï¼ï¼éä¸ç±»ååæ¾ç¤ºãéæ©æä»¶ â ç«å³å¯¼å
¥ / å å
¥éåãï¼å¡ç头é¨å å¼å¯¼ææ¡ï¼`groups` éæä¸ºæ°é¡ºåºï¼`activeModule: 'freight'` å å
¥ dataãæä½é¾ä¸åï¼éç±»åâææä»¶âç«å³å¯¼å
¥/å å
¥éåâå
¨é¨å¯¼å
¥ã |
| | | - `AuditResult.vue`ï¼æ¥è¡¨ç±»å radio é¡ºåºæ¹ä¸º è´§è¿H2032âæ
客H2031âåå¸å
¬äº¤CITY_BUSâ巡游åºç§CITY_TAXIâæèµINVESTâè½èH204ã |
| | | - `RuleManage.vue`ï¼åä¸é¡ºåºã |
| | | - `ReportExport.vue`ï¼`modules` é¡ºåºæ¹ä¸º freightâpassengerâcityPassengerâinvestâenergyï¼å¡ç CSS `repeat(6,1fr)`ï¼ç¬¬1-3å¼ span2ã第4-5å¼ span3ï¼ç¬¬ä¸æ è´§è¿/æ
客/åå¸å®¢è¿ï¼ç¬¬äºæ æèµ/è½èï¼ã |
| | | **éªè¯ï¼æ 头æµè§å¨çå® DOM + æªå¾ï¼** |
| | | - å端 frontend.log ææ° `Compiled successfully`ï¼11:30:34ï¼ï¼æ ç¼è¯é误ã |
| | | - 导å
¥é¡µ tabs = `["è´§è¿","å
¬è·¯æ
客","åå¸å®¢è¿","æèµ","è½è"]`ï¼active=è´§è¿ï¼æé® = `["éæ©æä»¶","ç«å³å¯¼å
¥","å å
¥éå"]`ï¼overviewGroups å顺åºï¼0 个 JS éè¯¯ï¼æªå¾ `_shots/import_page_tabs2.png`ï¼äººå·¥ç¡®è®¤æ¸
ç½æ å¼å¸¸ï¼ã |
| | | - ä¾§è¾¹æ å页éªè¯ï¼å®¡æ ¸ç»æ radio=è´§è¿/æ
客/åå¸å
¬äº¤/巡游åºç§/æèµ/è½è + ææ¡æç»/æä¼ä¸åå¹¶ï¼è§å管ç radio å6项ååºï¼æ¥è¡¨çæå¡ç=è´§è¿(H203-2)/å
¬è·¯æ
客(H203-1)/åå¸å®¢è¿/æèµ/è½è(H204)ï¼æªå¾ `_shots/order_check3.png`ï¼å¡çå¸å±ç¾è§ï¼ã |
| | | - **è·¯ç±å页å**ï¼`location.hash='#/audit'` ç´æ¥èµå¼ä¸çæï¼è¢«éç½®å #/importï¼ï¼å¿
é¡»ç¹å»ä¾§è¾¹æ `.el-menu-item` å页ã |
| | | **产ç©**ï¼`_shots/patch_module_order.py`ã`_shots/patch_radio_order.py`ã`_shots/patch_tab_clean.py`ã`_shots/shot_import_tabs.js`ã`_shots/shot_import_tabs2.js`ã`_shots/order_check.js`ã`_shots/order_check2.js`ã`_shots/order_check3.js`ã |
| | | **å¾
åï¼å¯éé¦ä¸æ·»è±ï¼**ï¼è¥ç¨æ·ä»è§ç¹çï¼å¯æãæ¬æå¯¼å
¥è¿åº¦ãä¸ãéæ©ç±»åæè½½æä»¶ãä¸¤å¼ å¡åå¹¶æä¸å¼ ï¼å¯¼å
¥ç±»åæãä¸è½½æ¨¡æ¿ã龿¥å·²å¨ç»4è½å°ã |
| | | |
| | | |
| | | ### 2026-08-26 ä¸åä¼è¯ï¼æèµ M2 å¸å·å表æ¹é导å
¥ï¼å·²å®æéªè¯ï¼ |
| | | **éæ±**ï¼HANDOFF éçãæèµ M2ï¼å¸å·å表解æï¼æªå¼å·¥ãâ å®ç°æå¸å·å表ç®å½æ¹é导å
¥å®¢è¿ç«åº/ç©æµååºæåº¦æ°æ®ã |
| | | **å®ç°** |
| | | - å端 `DataImportService.importInvestCityDir(period, category)` + `POST /api/import/investCityBatch`ï¼period=yyyy-MMï¼category=客è¿ç«åº|ç©æµååºï¼ï¼æ«æ `docs/æèµ/è¾å
¥/{ç±»å«ç®å½}/{Næ}` ä¸å
¨é¨ xls/xlsx/etï¼è·³è¿ pdfï¼Næç± period æ¨å¯¼ï¼ï¼å®¹éè§£ææ¯ä¸ªæä»¶ï¼å®ä½è¡¨å¤´è¡ + ååå
³é®è¯æ å° + åå±è¡¨å¤´åå¹¶ + å¸å·å°èè¡è¯å«ï¼ï¼éæä»¶è¿åè¡æ°/失败è¡å·ï¼å¹çï¼åç±»å«åæ¥è¡¨ææåº¦+INVEST å®¡æ ¸å
å ï¼é¡¹ç®ä¸»æ¡£æã项ç®å+ç±»å«ãupsertï¼ï¼ä¸ã模æ¿_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xlsãè§èåå¹é
æ è®° is_five_yearã |
| | | - å端 `DataImport.vue` æèµ tab æ°å¢ãå¸å·å表æ¹é导å
¥ï¼å®¢è¿ç«åº/ç©æµååºï¼ãç±»å +ãä»ç®å½æ¹é导å
¥ãæé® + éæä»¶ç»æå¼¹çªã |
| | | - æä»¶è¯»åï¼åªè¯»æä»¶å¿
é¡» `new FileInputStream(f)` å
ä¸å±ï¼ç´æ¥ `WorkbookFactory.create(File)` æãæç»è®¿é®ãï¼ã |
| | | **DB ç°ç¶ï¼mysql 3308 / traffic_auditï¼** |
| | | - investment_projectï¼å®¢è¿ç«åº 46ãç©æµååº 132ï¼å·²æ¸
2 个å¤å¿æ®çï¼ï¼å
¨é¨ total_investment>0ã |
| | | - investment_monthlyï¼å®¢è¿ 6æ35/7æ46ãç©æµ 6æ132/7æ133ï¼å
± 346ï¼ï¼0 失败ï¼2026-07 åäºäºé¡¹ç® 35 个ã |
| | | - å¯¼åº `GET /api/report/export/investFiveYearLogistics?period=2026-07` åè®¡ï¼æ»æèµ 2038714.59 / èªå¼å§ç´¯è®¡ 691375.3 / èªå¹´å累计 246779.55ï¼ä¸ DB èåéåä¸è´ï¼probeï¼`_shots/verify_export_m2/report_v2.txt`ï¼ã |
| | | **æ¬è½®æ°è¸©åï¼å¡å¿
è®°ä½ï¼** |
| | | 1. ãæ»æèµãvsãè®¡åæ»æèµãï¼æ¦æ±ç表头æ¯ãæ»æèµãï¼å®æ/è¥é³çæ¯ãè®¡å æ»æèµï¼ä¸å
ï¼ãï¼ä¸é´æç©ºæ ¼ï¼â åæ å°å¿
é¡» `contains("è®¡åæ»æèµ")||contains("æ»æèµ")` 䏿é¤ã累计ãï¼å¦å total=0ã |
| | | 2. **å
¬å¼åå
æ ¼**ï¼.etï¼WPS 跨表å
¬å¼ `='2026å¹´6æ '!I12+L12`ï¼`DataFormatter.formatCellValue` æ evaluator æ¶åªè¿åå
¬å¼ææ¬âè§£æ 0ãä¿®å¤ï¼`getDecimal/getDecimalOrNull/getString` 对 FORMULA åå
æ ¼è¯» `getCachedFormulaResultType()`/`getNumericCellValue()`ã |
| | | 3. **å¤ sheet å岿件éæ**ï¼è¥é³ .et å« 1-7 æ 12 个 sheetï¼åé»è¾ãé countInvestRows æå¤ãä¼éé 2æ sheetï¼é¦ä¸ª 6 è¡ï¼ï¼7ææ°æ®éå 2æå¼ãä¿®å¤ï¼`investSheetMatchesPeriod` æ sheet åå«ãNæ/0Næ/periodãä¼å
ï¼æ°æ®è¡>0 æ¶ +1000000 åï¼ã |
| | | 4. 项ç®å嫿¢è¡ï¼`å¸å®é«æ°åºå»åºå·é¾é¡¹ç® |
| | | ï¼ä¸æï¼`ï¼ï¼`replaceAll("[\r\n]+","")`ï¼ä¿®å¤åæ®ççå¤å¿é¡¹ç®ï¼é»è±¹å½é
ç©æµå/å¸å®å»åºï¼å·²ä» investment_project å é¤ï¼æ æåº¦å¼ç¨ï¼ã |
| | | 5. æ seq åï¼éå· Sheet2ï¼ï¼æ seq æåºå·å¤å®ãæ seq ç¨ `investmentCity(nt)` è¯å«å¸å·è¡ã |
| | | 6. ãå¡«æ¥åä½ãå¸å·æåå¿
é¡» contains å¹é
ï¼"æ¦æ±å¸äº¤éè¿è¾å±å®¢è¿äºä¸åå±ä¸å¿"ï¼ã |
| | | 7. æ½æ±/æåºæä»¶è§£æ 1 æ¡ï¼æ½æ±ä¼ åå
¬è·¯æ¸¯ï¼æ¯æ£ç¡®çï¼å¤ sheet éæä¿®å¤ä¸å½±åå
¶ç»è®ºã |
| | | **åäºäº 28 个缺项ç®ï¼çå®ç¼ºå£ï¼** |
| | | - æ¨¡æ¿ 63 项ï¼è§èåå¹é
35 è¦ç / 缺 28ï¼æ°æ®æºä¸å¨å¸å·å表ï¼ï¼æ¸
åè§ `_shots/five_year_report.txt`ï¼éç¨æ·æä¾æ°æ®æç¡®è®¤å£å¾ã |
| | | **产ç©**ï¼`_shots/insert_m2.py`ï¼M2 ä»£ç æå
¥ï¼ã`_shots/fix_*.py`ï¼å bug ä¿®å¤ï¼ã`_shots/fix_formula_cached.py`ã`_shots/fix_sheet_period.py`ã`_shots/m2_compile*.log`ã`_shots/ProbeEt*.java`ï¼POI å
¬å¼/å¤ sheet æ¢æµï¼ã`_shots/verify_export_m2/`ï¼åäºäºå¯¼åºå¤éªï¼ã`_shots/five_year_report.txt`ã`_shots/verify_m2.js` + `_shots/m2_batch_dialog.png`ï¼å端弹çªéªè¯ï¼ã |
| | | **å¾
å**ï¼â 28 个åäºäºé¡¹ç®ç¼ºå£çç¨æ·æä¾æ°æ®æºï¼â¡è½èæ° 6 æ¡è§åéå¼å£å¾å¾
确认ï¼â¢è´§è¿ã模æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æåè¡¨ãæªå¯¼å
¥ï¼â£7-12 ææ¨¡æ¿å¾
ç¨æ·æä¾ï¼â¤å¯éãæ¬æå¯¼å
¥è¿åº¦ãä¸ãæè½½æä»¶ã两å¡åå¹¶ã |
| | | |
| | | |
| | | ### 2026-08-26 ååä¼è¯ï¼æ¥è¡¨çæ P0 ä¸é¡¹ + æ¨¡æ¿æ°æ®è§å梳çï¼å·²å®æéªè¯ï¼ |
| | | **èæ¯**ï¼ç¨æ·é®ãæ¥è¡¨çææ¨¡åæä¹çãâ ç»åºè¯ä¼°ï¼æ¨¡æ¿åºç¨¿çææ¯äº®ç¹ï¼æ°æ®å®å¤æ§ä¸å¯è§/é误信æ¯è¢«å/æä»¶åä¸ä¸è´/åæä»¶è¿å¤§/ææ¥è¯ä¹æè¯¯è¯»ï¼ï¼ç¨æ·åæå
å P0 ä¸é¡¹ã |
| | | **P0-1 æ°æ®å°±ç»ªæ ¡éª** |
| | | - å端 `ReportExportService.checkExportReady(period, mode, types)` + `GET /api/report/ready`ï¼ææ¥è¡¨ç±»åç»è®¡ä¾èµè¡¨è¡æ°ï¼month 精确å¹é
report_periodãyear ç¨ likeRight 年份åç¼ï¼ï¼ |
| | | - è´§è¿ä¸è¡¨ â æ¨¡æ¿_è´§è¿éå¨è½¬é(freight_turnover_import) + è§ä¸è§ä¸æå表(scale_split_transport) + H203-2 ææ¥(h2032_enterprise_monthly) |
| | | - æ
客å表 â H203-1 ææ¥(h2031_enterprise_monthly) + 个ä½(passenger_individual_monthly) |
| | | - cityBus/cityRailFerry â city_bus_monthlyï¼cityTaxi â city_taxi_monthlyï¼åå¸å®¢è¿æ±æ»ä¸è¡¨ â å
¬äº¤+åºç§ |
| | | - æèµå表 â investment_monthlyï¼è½è â h204_vehicle_quarterly |
| | | - å端 `exportChecked` å
è° readyï¼æç¼ºæ°æ®è¡¨ â `$confirm`ãæ°æ®æªå°±ç»ªãååºç¼ºåªäºï¼å¦ãåå¸å
¬äº¤å®¢è¿éåå¸å·æç»ï¼ç¼ºæ°æ®ï¼åå¸å
¬äº¤ææ¥ï¼ãï¼ï¼å¯ãä»è¦å¯¼åº/åæ¶ãï¼æ 缺 â ç´æ¥å¯¼åºã |
| | | **P0-2 é误éåº** |
| | | - æ°å¢ `GlobalExceptionHandler`ï¼com.trafficaudit.commonï¼ï¼Exception â HTTP 500 + `Result.error(500, message)`ï¼log.error å
¨éå æ ï¼ï¼å端 `failWith(err)` 读 `err.response.data.message` æ¾ç¤ºå
·ä½åå ã |
| | | - éªè¯ï¼`curl /api/report/export/energySummary?period=2026-99` â `{"code":500,"message":"32",...}` é¾è·¯éã |
| | | **P0-3 æä»¶åç»ä¸ + MIME** |
| | | - å端æ
客 4 个 fileName 对é½å端å
¨åï¼å
¬è·¯æ
客åå¸å·æç»/ä¸å£å¾æç»/ä¸å£å¾æå/ä¸å£å¾åæï¼ï¼`buildResponse` æ .xls ç¨ `application/vnd.ms-excel`ã.xlsx ç¨ ooxmlã |
| | | **顺æä¿®å¤**ï¼`ReportExport.vue` data 缺 `activeMenu: '/report'`ï¼æ¤åä¾§è¾¹æ å½å页ä¸é«äº® + Vue warnï¼ã |
| | | **éªè¯**ï¼ready å表æ°å¼ä¸ DB éè¡¨æ ¸å¯¹ä¸è´ï¼cityDetail=690ãpassenger=771+缺个ä½ãcityTaxi=17ãinvest=179ãenergy=126ãcityBus=0 åç¡®åæ å
¬äº¤ææ¥æªå¯¼å
¥ï¼ï¼æ 头æµè§å¨ï¼`_shots/report_ready_dialog.js`ï¼ï¼case1 ç¼ºæ°æ®å¼¹ãæ°æ®æªå°±ç»ªã确认æ¡å
容æ£ç¡®ãcase2 æ°æ®é½å
¨ç´æ¥ä¸è½½+ãæ¥è¡¨å¯¼åºæåãï¼JS 0 é误ï¼å端 12:55:10 Compiled successfullyã |
| | | **æ¨¡æ¿æ°æ®è§åï¼ç¨æ·æ°ç¡®è®¤çéæ±æ¹åï¼å¾
å£å¾ç¡®è®¤ï¼**ï¼æ¨¡æ¿å³æå¨ï¼ç³»ç»ä¸¥æ ¼ææ¨¡æ¿ä¸ç±»è§åå¡«æ°ââA å¡«æ¾è§åï¼æ°æ®åºè¡/å/å并对å·å
¥åº§ï¼ãB 计ç®è§åï¼æ¨¡æ¿å·²æå
¬å¼å¦è½èæç®ç³»æ° D3=C3/B3ãM3=K3/I3*100*0.86*1.4571ãå叿±æ»å计 P3=P4+P5+P8+P9ã忝 L14=H14/M14-1 ç â ä¿çå
¬å¼åªå¡«åå§éï¼æ¨¡æ¿æ å
¬å¼å¦è´§è¿_è´§è¿éå¨è½¬é â ç³»ç»æå£å¾ç®å¥½å¡«å
¥ï¼ãC æ ¼å¼è§åï¼å宽/表头/åå¹¶/åä½å®å
¨ä¸è´ï¼ã**å¾
ç¨æ·ç¡®è®¤**ï¼â å
¬å¼ç±»æ¨¡æ¿ä¿çå
¬å¼ vs ç³»ç»åæ»ç®å¥½çå¼ï¼â¡æ°æ®è¡æ°è¶
模æ¿é¢çè¡æ°æ¶æ¯å¦å¨ææè¡å¹¶åæ¥å
¬å¼ï¼â¢ãæ°æ®è§åãæ¯å¦è¿å
æ¬å使¢ç®/å®¡æ ¸åå¡«æ°/ææ¥ 1~N æå£å¾ã |
| | | |
| | | ### 2026-08-26 ä¸åç»ï¼å®¡æ ¸æ 记表ï¼audit_runï¼è½å°ï¼åå²å·²å®¡æ ¸æ°æ®ä¸å误æ¥ãæªå®¡æ ¸ãï¼å·²å®æéªè¯ï¼ |
| | | **ç¨æ·å£å¾ï¼å
³é®ï¼**ï¼ç°ææ°æ®ï¼7 æå以åï¼é½æ¯çº¿ä¸å®¡æ ¸éè¿çãå¯ç´æ¥å¯¼åºï¼ä¸åºæç¤ºæªå®¡æ ¸ï¼åªæ **8 ææ°æ®æªå®¡æ ¸**ãä¹åç¨ `audit_result` é®é¢æ¡æ°å¤æ audited æ¯éçââå®¡æ ¸å
¨éè¿æ¶ 0 æ¡ä¼è¯¯å¤ä¸ºæªå®¡æ ¸ã |
| | | **æ¹æ¡**ï¼æ°å¢ `audit_run` æ 记表ï¼report_type + report_period å¯ä¸ï¼ï¼ãå·²å®¡æ ¸ã以该表为åï¼å¯¼åºå ready æ ¡éªæ¥ audit_runï¼ä¸åä¾èµ audit_result è¡æ°ï¼auditRows ä¿ç为信æ¯å段ï¼ã |
| | | **å端ï¼å·²ç¼è¯éå¯éªè¯ï¼** |
| | | - 建表 `audit_run`ï¼id/report_type/report_period/created_atï¼uk_type_periodï¼utf8mb4ï¼ï¼MySQL 3308 traffic_auditã |
| | | - æ°å¢ `AuditRun` å®ä½ + `AuditRunMapper`ï¼BaseMapperï¼ã |
| | | - `AuditEngineService`ï¼4 åæ° `markAudited(reportType, period, rules, hasData)`ï¼å¹çï¼æ è§å/æ æ°æ®ä¸åï¼+ æ°å¢ 2 åæ°éè½½ `boolean markAudited(reportType, period)`ââå
é¨æç±»ååå¯ç¨è§åï¼rulesOfï¼+ æç±»åæ¥æ°æ®è¡¨ï¼H2032âh2032ãH2031âpassengerãH204âenergyãINVESTâinvestMonthlyãCITY_BUSâcityBusãCITY_TAXIâcityTaxiï¼ååæ è®°ï¼6 个 execute æ¹æ³æ«å°¾å·²è° 2 åæ°çï¼æ§è¡å®¡æ ¸åèªå¨åæ è®°ï¼ã |
| | | - `AuditController`ï¼`POST /api/audit/markAudited?period=&reportType=` è¿å `Result<Boolean>`ï¼æ¯å¦åå
¥æ è®°ï¼ã |
| | | - `ReportExportService.checkExportReady`ï¼`audited` æ¹ä¸ºé auditType æ¥ `audit_run`ï¼month=eq 精确 / year=likeRightï¼ï¼ä»»ä¸ç±»åææ è®°å³ audited=trueï¼æ³¨å
¥ `AuditRunMapper`ã |
| | | **å端ï¼çæ´æ°å·²çæï¼Compiled successfullyï¼** |
| | | - `AuditResult.vue`ï¼ãæ§è¡å®¡æ ¸ãææ°å¢ãæ è®°å·²å®¡æ ¸ãæé®ï¼è° markAuditedï¼æåæç¤º/æ è§åæ æ°æ®æç¤ºå¹¶å·æ°å表ï¼ã |
| | | - `ReportExport.vue` æ éæ¹ï¼`unaudited = ready && audited===false` é»è¾å·²ä¸ audit_run è¯ä¹å»åã |
| | | **å岿°æ®è¡¥æ ï¼audit_run å·²åå
¥ 6 æ¡ï¼**ï¼H2031 07ãH2032 07ãH204 07ãINVEST 06+07ãCITY_TAXI 07ï¼**H2032 08 æªæ **ï¼8 ææªå®¡æ ¸ï¼ï¼CITY_BUS æ æ°æ®ä¸æ ã |
| | | **éªè¯ï¼å
¨é¨éè¿ï¼** |
| | | - `GET /api/report/ready?period=2026-07&mode=month&types=cityDetail,passengerCityDetail,cityTaxiDetail,investPlan,energySummary` â å
¨é¨ audited=trueï¼passengerCityDetail auditRows=0 ä½ audited=trueï¼æ£æ¯ä¿®å¤ç¹ï¼ã |
| | | - `period=2026-08` â cityDetail rows=142ï¼H2032 8 æææ°æ®ï¼audited=falseï¼å
¶ä½æ æ°æ® ready=falseã |
| | | - `POST /api/audit/markAudited`ï¼H2032/2026-07 â trueï¼å¹çï¼ï¼CITY_BUS/2026-07 â falseï¼æ æ°æ®ä¸æ è®°ï¼ã |
| | | - å端 start-dev.ps1 éå¯ OKï¼Started + 8090ï¼ï¼å端 14:32:12 Compiledã |
| | | **产ç©**ï¼`_shots/compile_auditrun.log`ã`%TEMP%\patch_*.py`ï¼audit_2arg / audit_controller2 / report_audited / auditresult_vue2ï¼ã`%TEMP%\db_mark_history.py`ã |
| | | **éç/å¾
åä¸å**ï¼æèµ M2 æªå¼å·¥ï¼7-12 ææ¨¡æ¿å¾
æä¾ï¼è´§è¿ã模æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æåè¡¨ãæªå¯¼å
¥ï¼è½èæ°è§åéå¼å¾
确认ã |
| | | |
| | | ### 2026-08-26 ä¸åç»2ï¼ç»å½æ¥é + ç¨æ·ç®¡çï¼è´è´£æ¨¡åå¾éï¼+ æ¥è¡¨çæé¡µè½¯é离ï¼å·²å®æéªè¯ï¼ |
| | | **éæ±ï¼ç¨æ·ç¡®è®¤ï¼**ï¼æ¥è¡¨çæé¡µæãä¸äººåå·¥ãå软é离ï¼è´§è¿1人ãæ
客+æèµ+è½è1人ãåå¸å®¢è¿1人ï¼ââç»å½åé»è®¤åªçèªå·±è´è´£ç模åãå¯åæ¢ãæ¥çå
¨é¨æ¨¡åãï¼æ¨¡åå½å±å¨**ç¨æ·ä¸å¾é**ï¼æ´çµæ´»ï¼ã |
| | | **ç°ç¶åç°**ï¼ç»å½å为åç»å½ï¼Login.vue ç´æ¥è·³è½¬ä¸æ ¡éªï¼ãUserManage.vue 为å ä½é¡µãsys_user/sys_role 表为空 â æ¬æ¬¡ä¸å¹¶è¡¥é½é¾è·¯ã |
| | | **æ°æ®åº**ï¼`sys_user` å `modules` åæ®µï¼éå·åéï¼freight,passenger,invest,energy,cityPassengerï¼ï¼åå§å 4 个账å·ï¼å¯ç å 123456ï¼BCrypt ç± spring-security-crypto çæï¼ï¼ |
| | | - admin/管çåï¼5 模åå
¨éï¼ãzhangsan/å¼ ä¸ï¼freightï¼ãlisi/æåï¼passenger,invest,energyï¼ãwangwu/çäºï¼cityPassengerï¼ã |
| | | **å端ï¼å·²ç¼è¯éå¯ï¼** |
| | | - `User` å®ä½å modulesï¼æ°å¢ `UserController`ï¼/api/user/list å
³é®åæç´¢ã/save æ°å¢ç¼è¾=å¯ç çç©ºä¸æ¹+BCryptã/delete ç¦æ¢å adminï¼ï¼`LoginController` ç»å½è¿å token+username+realName+modulesï¼é误 401ï¼ã |
| | | **å端ï¼çæ´æ°å·²çæï¼Compiled successfully 15:20:43ï¼** |
| | | - `Login.vue` è° /api/auth/loginï¼å token/username/realName/modulesï¼`router/index.js` å ç»å½å®å«ï¼æ token 访é®ä¸å¡é¡µè·³ /loginï¼ï¼`main.js` å
¨å± mixin logoutï¼æ¸
localStorage åç»å½é¡µï¼ï¼8 个ä¸å¡é¡µä¾§è¾¹æ ç»ä¸å ãéåºç»å½ãèå项ã |
| | | - `UserManage.vue` éåï¼ç¨æ·å表ï¼ç¨æ·å/å§å/ææºå·/è´è´£æ¨¡å tags/ç¶æ/å建æ¶é´/ç¼è¾å é¤ï¼+ æ°å¢ç¼è¾å¼¹çªï¼å«ãè´è´£æ¨¡åãel-checkbox-group 5 项 + ç¶æï¼ã |
| | | - `ReportExport.vue` 软é离ï¼é¡¶é¨ focus-bar æ¾ç¤ºãå½åç¨æ·/è´è´£æ¨¡åã+ ãæ¥çå
¨é¨æ¨¡åãel-switchï¼`visibleModules` computed é»è®¤è¿æ»¤èªå·±ç模åï¼æªé
ç½®æè¿æ»¤ä¸ºç©ºåæ¾ç¤ºå
¨é¨ï¼ã |
| | | **éªè¯ï¼å
¨é¨éè¿ï¼** |
| | | - ç»å½ï¼admin è¿å 5 模åãzhangsan è¿å freightãé误å¯ç 401ï¼ç¨æ· CRUDï¼æ°å¢/éå 400/ç¼è¾æ¹æ¨¡å/å é¤ï¼æµè¯è´¦å·å·²æ¸
çï¼æç» 4 ç¨æ·ã |
| | | - æ 头æµè§å¨ e2eï¼`_shots/usermgr_e2e.js/png`ï¼ï¼æªç»å½è®¿é® #/report â è·³ #/loginï¼zhangsan ç»å½ â /report åªæ¾ç¤ºè´§è¿å¡ï¼focus-barï¼å½åç¨æ·ï¼å¼ ä¸ è´è´£æ¨¡åï¼è´§è¿æ¥è¡¨ï¼H203-2ï¼ï¼ï¼ç¹å¼å
³ â 5 å¼ å¡å
¨æ¾ç¤ºï¼admin ç»å½ â /users è¡¨æ ¼ 4 è¡ï¼JS 0 é误ã |
| | | **产ç©**ï¼`_shots/usermgr_e2e.js/png`ã`_shots/compile_usermgr.log`ã`%TEMP%\db_init_users.py`ï¼åå§åè´¦å· SQLï¼å¯å¤ç¨ï¼ã |
| | | **éç/å¾
åä¸å**ï¼æèµ M2 æªå¼å·¥ï¼7-12 ææ¨¡æ¿å¾
æä¾ï¼è´§è¿ã模æ¿_è´§è¿éå¨è½¬éããè§ä¸è§ä¸æåè¡¨ãæªå¯¼å
¥ï¼è½èæ°è§åéå¼å¾
ç¡®è®¤ãæ¥è¡¨é¡µãç¶æå¯è§åï¼æ°æ®å°±ç»ª/å·²å®¡æ ¸ç¶ææ¡ï¼ãä¸ãæè¿çæè®°å½ãä»å¨å¾
å讨论å表ã |
| | | |
| | | ### 2026-08-26 æé´ç»5ï¼æ¥è¡¨æ¨¡æ¿èªå¨å峿©åï¼1-6æ â 1-12æï¼+ æ¥è¡¨çæé¡µå模åå·¥ä½å°ï¼å·²å®æéªè¯ï¼ |
| | | **éæ±ï¼ç¨æ·ç¡®è®¤ï¼**ï¼â æå½åæ³æ³å
åºä¸ççææï¼å模åå·¥ä½å°çé¢ï¼ï¼â¡æåº¦æ¨¡æ¿**èªå¨å峿©å±å**ï¼å¤å¶å头/å
¬å¼/æ ·å¼å¹¶åæ¥å
¬å¼å¼ç¨ï¼æ©åæ ¼å¼ä¸è§åæ 1-6 ææ¢æè§å举ä¸åä¸ï¼æ ééæäººå·¥ç¡®è®¤ã |
| | | **å端æ©åï¼å·²ç¼è¯éå¯ + HTTP 导åºéªè¯éè¿ï¼** |
| | | - ä¸å¼ æ¥è¡¨å¯ç¨æ©åï¼`exportCityDetail`ï¼è´§è¿åå¸å·ï¼ã`exportPassengerCityDetail`ï¼å
¬è·¯æ
客åå¸å·ï¼ã`exportPassengerMidDetail`ï¼ä¸å£å¾æç»ï¼ã`fillMonths = Math.min(currentMonth,6)` æ¹ä¸º `currentMonth`ï¼`fillMonths > 6` æ¶è°ç¨ `extendMonthlyColumns(sheet, headerRow, year, dataStart, dataEnd)`ã |
| | | - æ©åé»è¾ï¼ç´¯è®¡åå¯¹ä» 14/15 åç§»å° 26/27 åï¼ABï¼ï¼7-12 æåå¯¹ç± 1 æå对å
éï¼è¡¨å¤´ææ¬/æ¥æä¿®æ£ãæ ·å¼å¤å¶ãé跨簿å
¬å¼å
éä¸åå¼ç¨åæ¥å¹³ç§»ï¼æ£å `shiftMonthFormula`ï¼ï¼ç´¯è®¡åå
¬å¼éå为 12 个æå¼ä¹åï¼`fixCumulativeFormulas`ï¼å
¬å¼ä¸å¸¦ `=` åç¼ï¼POI `setCellFormula` 䏿¥å `=` å¼å¤´ââ已踩åä¿®å¤ï¼ã |
| | | - 忝åï¼å #REF! å
¬å¼è¦å为 Java åºå
计ç®å¼ãè´§è¿è° `getCityMonthYoy`/`getProvinceMonthYoy`ï¼æ
客æ°å¢ `passengerCumYoy`/`passengerMonthVal` + ç´¯è®¡åæ¯è¦åï¼ä¸å£å¾æ°å¢ `fillMidYoyCell`/`fillMidCumYoyColumn`ã循ç¯èµ·ç¹ r=4 â r=3ï¼R4 æ¯å
¨çåºåæ è®°è¡ï¼A4:A7 åå¹¶ï¼ï¼æ
客 #REF! æ¸
é¶ã |
| | | - å·²ç¥å£å¾ï¼æ²¿ç¨ç°ç¶ï¼æªæ¹ï¼ï¼æ¨¡æ¿è¡¨å¤´ä¸ºã1æä¸å»å¹´åæ¯ãï¼7-12 æåæ¯åæ²¿ç¨ 1-6 æååæ³ï¼å³ãå»å¹´åæ vs ä»å¹´åæãç忝æ°å¼ï¼`yearYoy`ï¼ãè¥åç»è¦æ±æ¹ä¸ºãç¯æ¯ä¸æãé妿¹ã |
| | | - **æ°æ®æºç¼ºå£ï¼é代ç é®é¢ï¼éç¨æ·è¡¥æ°æ®ï¼**ï¼â `freight_turnover_import` 2026-07 ç彿忮µï¼freight_m07/turnover_m07ï¼ä¸º NULLï¼åªæå»å¹´åæ â 7 æååªæ H2032 è§ä¸è´§è¿é + æå表å¨è½¬éï¼å
¨çå计/è§ä¸è´§è¿é 7 æä¸ºç©ºï¼é¢è§å¼¹çªãç¼ºææ°æ®ï¼7æã䏿¤ä¸è´ï¼ï¼â¡`h2031_enterprise_monthly` åªæ 2026-07ï¼771 è¡ï¼ï¼1-6 æä¸ 2025 å¹´å为空 â æ
客表 1-6 æå空ãç´¯è®¡åæ¯ï¼AB åï¼ç©ºï¼â¢8-12 æåæ°æ®ä¾èµåç»æä»½æ°æ®æºã |
| | | - HTTP 导åºéªè¯ï¼2026-07 ææ¥ï¼ï¼cityDetail A1:AB112ãpassengerCityDetail A1:AB58ãpassengerMidDetail A1:AB94ï¼è¡¨å¤´ 1-12 æ + 累计 + 忝æ£ç¡®ï¼#REF! å
¨é¨æ¸
é¶ï¼ç´¯è®¡åå
¬å¼ =C+E+G+â¦+Yï¼12 个æå¼åä¹åï¼ï¼åå¹¶åå
æ ¼åæ¥ã |
| | | **å端ï¼ReportExport.vue éåä¸ºåæ¨¡åå·¥ä½å°ï¼çæ´æ°å·²çæï¼æ ç¼è¯é误ï¼** |
| | | - 左侧 240px 模å导èªï¼æ¨¡åå + å¼ æ° + å½åæ¥è¡¨æï¼ï¼å³ä¾§å模å颿¿ï¼é¡¶é¨æ¥è¡¨æ¹å¼/æ¥è¡¨æï¼ä¸é´æ¥è¡¨è¡¨æ ¼ï¼å¾é + æ¥è¡¨å + æ°æ®ç¶ææ ç¾ãæ°æ®å°±ç»ª/æªå®¡æ ¸/ç¼ºæ°æ®ã+ 夿³¨ + é¢è§/导åºï¼ï¼åºé¨å
¨é +ãé¢è§éä¸/导åºéä¸ï¼zipï¼ãã |
| | | - é»è®¤èç¦å½åç¨æ·è´è´£æ¨¡åï¼ç»å½æ¶æç¨æ· modules è¿æ»¤ï¼ï¼é¡¶é¨ãæ¥çå
¨é¨æ¨¡åãå¼å
³å¯åæ¢å
¨é¨ 5 模åï¼ä¿ç `/report/ready` ç¶æå·æ°ã`/report/preview` é¢è§å¼¹çªã`/report/export/{key}` åå¼ å¯¼åºã`/report/export/selected` zip å¤å¼ 导åºã |
| | | - æ 头æµè§å¨ e2eï¼`_shots/report_workbench_e2e2.js`ï¼ï¼å¼ ä¸ç»å½ â åªæ¾ç¤ºè´§è¿æ¨¡åï¼navCount=1ï¼ï¼å¼ãæ¥çå
¨é¨æ¨¡åãâ 5 模åå
¨æ¾ï¼è´§è¿ 2026-07 ä¸å¼ æ¥è¡¨ç¶æåãæ°æ®å°±ç»ªãï¼é¢è§å¼¹çªæ é¢ãé¢è§æ°æ® - è´§è¿æ¥è¡¨ï¼H203-2ï¼ï¼2026-07ï¼ã+ æ±æ»/è¦ç/缺ææç¤ºæ£ç¡®ï¼JS 0 éè¯¯ãæªå¾ `_shots/report_workbench*.png`ï¼HTTP å¯¼åºæ ·å¼ `_shots/dl/*.xlsx`ã |
| | | **éªè¯å½ä»¤/产ç©**ï¼`GET /api/report/export/{key}?period=2026-07&mode=month`ï¼ä¸ç§ key å 200ï¼ï¼`_verify_http_export*.py`ã`_verify_expand*.py`ã`_test_expand_*.xlsx`ã |
| | | **éç/å¾
åä¸å**ï¼æèµ M2 æªå¼å·¥ï¼è´§è¿ã模æ¿_è´§è¿éå¨è½¬éãæ±æ»è¡¨ 7 ææ°æ®æªå¯¼å
¥ï¼å½±åå计/è§ä¸ 7 æåï¼ï¼æ
客 2025 å¹´åææ°æ®ç¼ºå¤±ï¼ç´¯è®¡åæ¯ç©ºï¼ï¼8-12 æåæ°æ®ä¾èµæ°æ®æºï¼è½èæ°è§åéå¼å¾
确认ã |
| | | |
| | | ### 2026-08-26 æé´ç»6ï¼æ¥è¡¨çæé¡µãæ¥è¡¨æ¹å¼/æ¥è¡¨æãæ¹ä¸ºä¸ææ¡ï¼å·²å®æéªè¯ï¼ |
| | | **éæ±ï¼ç¨æ·ç¡®è®¤ï¼**ï¼çé¢ä¸å¯é项æ¹ä¸ºä¸ææ¡éæ©æ¹å¼ã |
| | | **æ¹å¨ï¼ReportExport.vueï¼** |
| | | - ãæ¥è¡¨æ¹å¼ãç± el-radio-group æ¹ä¸º el-selectï¼ææ¥/å¹´æ¥ï¼ï¼åæ¢æ¹å¼æ¶èªå¨æ¸
空æ¥è¡¨æï¼æ°å¢ `onModeChange`ï¼ã |
| | | - ãæ¥è¡¨æãç± el-date-picker æ¹ä¸º el-select åç»ä¸æï¼æ°å¢ `periodGroups`ï¼ï¼å¹´æ¥ååº 2025/2026/2027 å¹´ï¼ææ¥æå¹´åç»ååº 1-12 æï¼è½è模åï¼å£åº¦æ¥ï¼åªåå£åº¦æ«æ 3/6/9/12ï¼åç»æ 颿 注ãï¼å£åº¦æ«æï¼ãã |
| | | - æ 头æµè§å¨ e2eï¼`_shots/report_dropdowns_e2e.js`ï¼ï¼ææ¥âå¹´æ¥åæ¢ã年份/æä»½åç»ä¸æãé 2026-07 åç¶æå·æ°ãæ°æ®å°±ç»ªããJS 0 éè¯¯ãæªå¾ `_shots/report_dropdowns.png`ã |
| | | **éç/å¾
åä¸å**ï¼è§æé´ç»5ï¼æ°æ®æºç¼ºå£çï¼ã |
| | | |
| | | ### 2026-08-26 æé´ç»7ï¼æ¥è¡¨ç±»åæ¹ä¸ºä¸ææ¡ï¼å·²å®æéªè¯ï¼ |
| | | **éæ±ï¼ç¨æ·æ¾æ¸
ï¼**ï¼ä¸æ¬¡æ¹çæ¯ãæ¥è¡¨æ¹å¼/æ¥è¡¨æã䏿æ¡ï¼ç¨æ·å®é
æçæ¯ãéæ©æ¥è¡¨çç±»åãä¹è¦ç¨ä¸ææ¡ã |
| | | **æ¹å¨ï¼ReportExport.vueï¼** |
| | | - å é¤å·¦ä¾§ 240px 模åå¯¼èªæ ï¼module-navï¼ä¸ãæ¥çå
¨é¨æ¨¡åãå¼å
³ï¼showAllModules å visibleModules 计ç®å±æ§ä¸å¹¶ç§»é¤ï¼ã |
| | | - 颿¿å¤´é¨æ¹ä¸ºãæ¥è¡¨ç±»åãel-selectï¼ååºå
¨é¨ 5 个模åï¼è´§è¿/å
¬è·¯æ
客/åå¸å®¢è¿/æèµ/è½èï¼ï¼é»è®¤ä»èç¦å½åç¨æ·è´è´£æ¨¡åï¼å¼ ä¸âè´§è¿ï¼ï¼å¯å¨ä¸ææ¡èªç±åæ¢å
¶ä»æ¨¡åï¼è½¯é离 + çµæ´»å
¼é¡¾ï¼ã |
| | | - æ¥è¡¨æ¹å¼ãæ¥è¡¨æä¸ææ¡ä¿æä¸åï¼è½èæ¨¡åæ¥è¡¨æä»åªåå£åº¦æ«æ 3/6/9/12ã |
| | | - æ¸
çï¼selectModuleâonModuleChangeãå é¤ periodTextãå é¤ module-nav ç¸å
³ CSSã |
| | | **éªè¯ï¼æ 头æµè§å¨ `_shots/report_type_dropdown_e2e.js`ï¼** |
| | | - æ ç¼è¯é误ãJS 0 éè¯¯ï¼æ¥è¡¨ç±»å䏿 5 个é项é½å
¨ï¼é»è®¤å¼ãè´§è¿æ¥è¡¨ï¼H203-2ï¼ãï¼åæèµâ4 å¼ æ¥è¡¨ãåè½èâ1 å¼ ï¼è½è¿æ±æ»è¡¨ï¼ï¼è½èæ¥è¡¨æä»
å£åº¦æ«æï¼ååè´§è¿æ£å¸¸ãæªå¾ `_shots/report_type_dropdown.png`ã |
| | | **éç/å¾
åä¸å**ï¼è§æé´ç»5ï¼è´§è¿ 7 ææ±æ»è¡¨æªå¯¼å
¥ãæ
客 1-6 æ/2025 åæç¼ºå¤±ã8-12 ææ°æ®ä¾èµæ°æ®æºï¼ã |
| New file |
| | |
| | | # åç±»å¼æºé¡¹ç®è°ç ä¸å¯¹æ¯ï¼2026-08-23ï¼ |
| | | |
| | | > æ£ç´¢èå´ï¼GitHub Search APIï¼ä¸è±æå¤å
³é®è¯ï¼ãGitee APIï¼æªè®¤è¯ä¸å¯ç¨ï¼å·²è·³è¿ï¼ãç¥åé¡¹ç® README æ ¸å®ã |
| | | > æ£ç´¢æ¥æï¼2026-08-23 |
| | | |
| | | ## ä¸ãç»è®ºå
è¡ |
| | | |
| | | **æªåç°ä¸æ¬é¡¹ç®ç´æ¥åç±»ç弿ºé¡¹ç®**ï¼äº¤éç»è®¡æ¥è¡¨å®¡æ ¸ + ä¸åæé + Excel 导å
¥å®¡æ ¸ + æ¥è¡¨çæ + AI è§£éï¼è¿æ¡ç»åå¨ GitHub/Gitee åæ ç°æå®ç°ï¼ã |
| | | |
| | | åå ï¼ |
| | | 1. è¿ç±»ç³»ç»å¼ºç»å®åç/åè¡ä¸ç»è®¡è°æ¥å¶åº¦ï¼ä¸å¡å£å¾æ æ³éç¨åï¼ |
| | | 2. æ¿å¡é¡¹ç®éå¸¸ç§æå交ä»ï¼å¾å°å¼æºï¼ |
| | | 3. æ ¸å¿ä»·å¼å¨å®¡æ ¸è§åä¸ä¸å¡å£å¾ï¼ä¸å¨éç¨åè½ã |
| | | |
| | | å æ¤**èªç æ¹åæ£ç¡®ï¼ä¸éè¦çº ç»"éå/æç°æ"**ï¼ä½å¯ä»¥ä»åç±»"è½åç»ä»¶å"弿ºé¡¹ç®å¸æ¶è®¾è®¡æè·¯ã |
| | | |
| | | ## äºãåç
§é¡¹ç®å¯¹æ¯ |
| | | |
| | | ### A. æ¿åºç»è®¡æ¥è¡¨çæï¼ææ¥è¿çç´æ¥åç±»ï¼ |
| | | **NIC Chennai â Monthly Statistical Report Generation** |
| | | - å®ä½ï¼å°åº¦å½å®¶ä¿¡æ¯ä¸å¿ï¼NICï¼å®ä¹ ç项ç®ï¼èªå¨åçææ¿åºéè´æåº¦ç»è®¡æ¥è¡¨ï¼MSRï¼ |
| | | - ææ¯æ ï¼Spring Boot + Vue.js + PostgreSQL + Apache POI |
| | | - åè½ï¼å¤æ°æ®åºå¨æè¿æ¥ï¼ç®¡ç端é
ç½®ï¼ãæè´¢å¹´/æä»½çææ¥è¡¨ãExcel æ ·å¼åè¾åºï¼åå¹¶åå
æ ¼/åç»/å计ï¼ãWeb ä¸è½½ |
| | | - 䏿¬é¡¹ç®å¯¹æ¯ï¼ |
| | | - ç¸ä¼¼ï¼Spring Boot + VueãExcel æ¥è¡¨è¾åºãææçæç»è®¡æ¥è¡¨ |
| | | - å·®è·ï¼æä»¬æ´å¼ºï¼ï¼æ 导å
¥è§£æãæ å®¡æ ¸è§åå¼æãæ ä¸åæéãæ AIãåæ¥è¡¨åºæ¯ |
| | | - å¯åé´ï¼å¤æ°æ®æºè¿æ¥ç®¡ççé¢ï¼å¨æé
ç½®æ°æ®æºï¼ââæèµæ¨¡å对æ¥"æèµç³»ç»"æ¶å¯åè |
| | | |
| | | ### B. å¿«éå¼åå¹³å°ï¼å½å
æ¿å¡åºåº§ï¼ |
| | | **RuoYi-Vueï¼è¥ä¾ï¼** |
| | | - å®ä½ï¼å½å
æä¸»æµç Java åå端å离快éå¼åå¹³å°ï¼å¤§éæ¿å¡ç³»ç»åºäºå® |
| | | - ææ¯æ ï¼Spring Bootï¼2/3/4 å¤åæ¯ï¼+ Spring Security + JWT + Redis + Vue2/3 + Element UI/Plus |
| | | - åè½ï¼ä»£ç çæå¨ã卿æéèåãæé®çº§æéãæä½æ¥å¿ã宿¶ä»»å¡ãå¤ç§æ· |
| | | - 䏿¬é¡¹ç®å¯¹æ¯ï¼ |
| | | - ç¸ä¼¼ï¼æéä½ç³»è®¾è®¡ï¼ç¨æ·/è§è²/èå/æ¥å¿ï¼ä¸æ¬é¡¹ç®åæºï¼å±ä¸çæ ååæ³ |
| | | - å·®è·ï¼æä»¬ç¼ºï¼ï¼ä»£ç çæå¨ï¼å 表æ¶ç大ééå¤ä»£ç ï¼ãRedis ç¼åã宿¶ä»»å¡ãæä½æ¥å¿æ³¨è§£å |
| | | - å¯åé´ï¼**è¡¨ç»æåæ´åçä¸é®ä»£ç çæ**ï¼RuoYi-generatorï¼ââæ©å± H203-1/H204/æèµæ¨¡åæ¶æå®ç¨ï¼æ¬é¡¹ç®å½åæ©å±é½æ¯æå Controller/Service/Mapper |
| | | |
| | | ### C. æ¥è¡¨ / BI çæ |
| | | **JimuReportï¼ç§¯æ¨æ¥è¡¨ï¼â
8k+** |
| | | - å®ä½ï¼å¼æºæ¥è¡¨å·¥å
·ï¼ç±» Excel å¨çº¿æ¥è¡¨è®¾è®¡å¨ + Chat2BIï¼å¯¹è¯å¼ AI æ¥è¡¨ï¼ |
| | | - ææ¯æ ï¼Spring Boot ç³»ï¼å
¼å®¹å½äº§ä¿¡åæ°æ®æº |
| | | - 䏿¬é¡¹ç®å¯¹æ¯ï¼æ¬é¡¹ç®æ¥è¡¨ç¨ EasyExcel + åºå®æ¨¡æ¿ï¼å¤ç¨ä¸å¯æ§ï¼ï¼JimuReport ç"模æ¿å¨çº¿è®¾è®¡"ä¸"Chat2BI"æè·¯ä¸æä»¬ AI åææ¹åä¸è´ï¼ä½å¼å
¥ææ¬é« |
| | | - å¯åé´ï¼è¿æï¼ï¼è¥æ¥è¡¨æ°éå¢é¿å°å åå¼ ï¼å¯èèæ"æ¥è¡¨æ¨¡æ¿"é
ç½®å/å¨çº¿è®¾è®¡åï¼å½åé¶æ®µåºå®æ¨¡æ¿æ´ç¨³ |
| | | |
| | | **DataEase â
24k** |
| | | - å®ä½ï¼å¼æº BI å·¥å
·ï¼Tableau æ¿ä»£ï¼ï¼æ°æ®æºè¿æ¥ + å¯è§å + å¤§å± |
| | | - 䏿¬é¡¹ç®å¯¹æ¯ï¼æ¬é¡¹ç®ä¸éè¦ BI åæè½åï¼ä½æªæ¥"æ°æ®çæ¿"æ©å±ï¼architecture.md æå°çæ°æ®çæ¿ï¼å¯ç´æ¥ç¨ DataEase æåé´å
¶è®¾è®¡ |
| | | |
| | | ### D. æ°æ®è´¨é / å®¡æ ¸è§å弿ï¼ä¸"å®¡æ ¸å¼æ"çå¿µææ¥è¿ï¼ |
| | | **Apache Griffin â
1.1k** |
| | | - å®ä½ï¼Apache 顶级项ç®ï¼æ¨¡å驱å¨çæ°æ®è´¨éå¹³å° |
| | | - 模å¼ï¼å®ä¹ measureï¼è§å/ææ ï¼â 宿¶æ§è¡ â çæè´¨éæ¥åï¼è§åé
ç½®åãè·¨æ°æ®æº |
| | | - 䏿¬é¡¹ç®å¯¹æ¯ï¼**å®¡æ ¸å¼æè®¾è®¡ç念å®å
¨ä¸è´**ï¼audit_rule 表 = measureï¼æ§è¡åçæ audit_result = è´¨éæ¥åï¼ï¼æä»¬å·²æé形䏿´è´´åä¸å¡ï¼compare_type æ´ä¸å¡åï¼ |
| | | |
| | | **Great Expectations â
11k / Soda Core â
2.4k** |
| | | - å®ä½ï¼Python æ°æ®è´¨éæ¡æ¶ï¼å£°æå¼ expectation/checks + æ°æ®å¥çº¦ |
| | | - 䏿¬é¡¹ç®å¯¹æ¯ï¼å£°æå¼è§å + åè¦å级ï¼RED/YELLOWï¼æè·¯ä¸æä»¬ä¸è´ |
| | | - å¯åé´ï¼è§åçç»ç»æ¹å¼ï¼æè¡¨/æå段åç»ãè§åä¾èµå
³ç³»ï¼ãè§åæ§è¡ç»æçå¯è§åï¼Dashboardï¼ |
| | | |
| | | ### E. å端 Excel 导å
¥ç»ä»¶ |
| | | **react-spreadsheet-import â
553** |
| | | - å®ä½ï¼Excel/CSV 导å
¥æµç¨ç»ä»¶ï¼èªå¨åå¹é
+ æ ¡éª + é¢è§ |
| | | - å¯åé´ï¼ä½éªå±ï¼ï¼å¯¼å
¥æ¶èªå¨è¯å«ååæ å°ã导å
¥é¢è§ãé误è¡é«äº®ââæ¬é¡¹ç®å¯¼å
¥å·²åè¡å·æ¥éï¼å¯å "ååèªå¨å¹é
"æåä½éª |
| | | |
| | | ## ä¸ãææ¯æ æ¨ªåå¯¹æ¯ |
| | | |
| | | | 维度 | æ¬é¡¹ç® | 主æµåç
§ï¼RuoYi çï¼ | è¯ä»· | |
| | | |------|--------|---------------------|------| |
| | | | å端 | Spring Boot 2.x | Spring Boot 3/4 | 已忧ï¼ä½çæä¸å½±åå¼åï¼é¿æéè§åå级 | |
| | | | å端 | Vue 2 + Element UI | Vue 3 + Element Plus | Vue2 å·²åæ¢ç»´æ¤ï¼çæå¤ç¨ï¼é¿æéå级 | |
| | | | æ°æ®åº | MySQL 8 | MySQL/PG | æ£å¸¸ | |
| | | | 导å
¥å¯¼åº | EasyExcel | POI/EasyExcel | ä¸»æµ | |
| | | | æé | èªç JWT + ä¸å | Spring Security + JWT | ä¸ä¸»æµå¯¹é½ | |
| | | | AI | DeepSeek éæ | JimuReport Chat2BI | æ¬é¡¹ç®å¨æ¿å¡åºæ¯æ´å
å¶ï¼è±æ+è§£éï¼ | |
| | | |
| | | ## åã对æ¬é¡¹ç®çå¯ç¤ºï¼å¼å·¥åå
±è¯ï¼ |
| | | |
| | | 1. **ä¸å¼å
¥å¤§æ¡æ¶éå**ï¼æ ç´æ¥åç±»å¯æï¼èªç 路线æ£ç¡®ï¼RuoYi/DataEase å
¨éå¼å
¥ææ¬è¿å¤§äºæ¶çã |
| | | 2. **æå¼å¾åé´çæ¯ RuoYi ç代ç çæå¨**ï¼æ©å± H203-1/H204/æèµæ¶ï¼è¥å
æä¸ä¸ª"è¡¨ç»æ â çæ CRUD+导å
¥æ¨¡æ¿éª¨æ¶"çå°å·¥å
·ï¼ææå·¥æ¨¡æ¿ï¼ï¼è½æ¾èæéä¸ä¿æé£æ ¼ä¸è´ã |
| | | 3. **å®¡æ ¸å¼æç»§ç»èµ°"è§åé
ç½®å"**ï¼audit_rule 驱å¨ï¼ï¼ä¸ Griffin/GX ç念ä¸è´ï¼æ©å±æ¯è¾ç±»åï¼MOM_EQ_ZERO/EXISTENCE/CROSS_TABLE/TREND/RATIO_CHANGEï¼æ¶ä¿æå£°æå¼ï¼ä¸åæ»ã |
| | | 4. **æ¥è¡¨è¾åºç»´æåºå®æ¨¡æ¿ + EasyExcel**ï¼æä¸ä¸å¨çº¿è®¾è®¡å¨ï¼çæ¥è¡¨æ°éå¤äºåè¯ä¼° JimuReport å¼é
ç½®åã |
| | | 5. **æèµæ¨¡å对æ¥"æèµç³»ç»"æ¶**ï¼å¯åè NIC MSR çå¨ææ°æ®æºè¿æ¥ç®¡çã |
| | | 6. **ææ¯åºç»è®°**ï¼Vue2/SB2 å级ã代ç çæå¨ãæä½æ¥å¿æ³¨è§£åï¼åå
¥è¿ææ¸
åï¼ä¸é»å¡å½åæ©å±ã |
| | | |
| | | ## äºãæ£ç´¢è®°å½ï¼å¤æ¥ï¼ |
| | | |
| | | - GitHub 䏿å
³é®è¯ï¼ç»è®¡æ¥è¡¨ç³»ç»/æ¥è¡¨å®¡æ ¸/æ°æ®å®¡æ ¸å¹³å°/æ¥è¡¨ç®¡ç Spring Boot â æ ç´æ¥åç±» |
| | | - GitHub è±æå
³é®è¯ï¼data quality validation / report generation excel spring boot / data validation excel import / statistical reporting â æ ç´æ¥åç±» |
| | | - Gitee APIï¼æªè®¤è¯è¿å空ï¼å¾
è¡¥ï¼å¯æµè§å¨è®¿é® gitee.com æç´¢"ç»è®¡æ¥è¡¨/交éç»è®¡"äººå·¥æ ¸å¯¹ï¼ |
| New file |
| | |
| | | # æ©å±æ¨¡åè¯ä¼°ï¼å
¬è·¯æ
客 / è½è / åå¸å®¢è¿ / æèµï¼ |
| | | |
| | | > æ´æ°æ¥æï¼2026-08-22 |
| | | > 便®ï¼`docs/æ©å±è¡¨æ¨¡æ¿/` ä¸éæ±ææ¡£ä¸æ¨¡æ¿å®æï¼å
¬è·¯å®¢è¿ç®å.docxãåå¸å®¢è¿æ°æ®ä¼å.docxãå
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsxãé路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼.xlsxãåå¸å®¢è¿æ°æ®æ¨¡æ¿.xlsxãæèµ è¾å
¥/è¾åº æ ·ä¾ï¼ã |
| | | |
| | | ## ä¸ãæ»è§ï¼æ¨¡å Ã ç°ææ¡æ¶å¤ç¨åº¦ |
| | | |
| | | | 模å | 对åºå¶åº¦è¡¨ | 导å
¥ | å®¡æ ¸ | æ¥è¡¨ | æ¡æ¶å¤ç¨åº¦ | å»ºè®®ææ | |
| | | |------|-----------|------|------|------|-----------|---------| |
| | | | å
¬è·¯æ
客è¿è¾ | 交ä¼ç»H203-1 | é« | é« | é« | é«ï¼å ä¹å
¨å¤ç¨ï¼ | 1 | |
| | | | é路货è¿è½è | 交ä¼ç»H204ï¼å£æ¥ï¼ | ä¸ | ä¸ï¼é跨表å¼ç¨ï¼ | é« | ä¸é« | 2 | |
| | | | åå¸å®¢è¿ï¼å
¬äº¤/åºç§/ç½çº¦ï¼ | 交ä¼ç»U203ç | ä¸ï¼ä¾èµå¤é¨æ°æ®æºï¼ | ä½ï¼è§åç±»åæ°ï¼ | ä¸ | ä¸ | 3 | |
| | | | æèµï¼å®¢è¿ç«åº/ç©æµååºï¼ | é¨é¨ç»è®¡ï¼è¡¨1-1/1-2çï¼ | ä½ï¼æ ¼å¼ä¸ç»ä¸ï¼ | ä½ï¼è§åå°ï¼ | ä¸ | ä½ï¼ç¬ç«å»ºæ¨¡ï¼ | 4 | |
| | | |
| | | ## äºãå
¬è·¯æ
客è¿è¾ï¼H203-1ï¼ââ 建议æå
å |
| | | |
| | | ### æ°æ®ç»æï¼å®æ `å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsx`ï¼ |
| | | - `å
¬è·¯æ
客6ææ¥`ï¼ä¼ä¸ææ¥æç»ï¼28 åï¼çº¦ 768 å®¶ä¼ä¸ãåæ®µä¸è´§è¿ H203-2 é«åº¦åæï¼ |
| | | - ä¼ä¸ç»´åº¦ï¼æå±å°åºãä¼ä¸ä»£ç ãä¼ä¸åç§° |
| | | - 车è¾/载客ä½ï¼æ»è®¡ã客è¿ç车ï¼å«å®çº¿æ
游ï¼ã客è¿å
车ï¼å«éå®çº¿æ
æ¸¸ï¼ |
| | | - 客è¿é/æ
客å¨è½¬éï¼æ»è®¡ãä¸ç±»/äºç±»/ä¸ç±»/åç±»ç线ãå
车 |
| | | - å¹³åè¿è·ï¼æ»è®¡ãä¸~åç±»ãå
车 |
| | | - `è¿æ¿è½¦è¾æ°`ï¼4 åï¼ä¼ä¸ä»£ç ãä¼ä¸åç§°ãè½¦è¾æ°ãè½½å®¢ä½æ°ï¼ââè¿æ¿å¯¹æ¯æ°æ®æºï¼åæä»¶å
sheet ææ°å¯¼å
¥ç±»åï¼ |
| | | - `å
¬è·¯æ
客åå¸å·æç»`ï¼çæè¡¨ï¼284 åï¼12 个æÃå£å¾Ã忝+累计ï¼ï¼å£å¾å«æ»å®¢è¿éãå
¬è·¯ç线ãåé
å乡å
¬äº¤ã巡游åºç§ãç½çº¦è½¦ |
| | | - `ä¸å£å¾è®¡ç®è¿ç¨`ï¼æåº¦æ±æ»è®¡ç®è¡¨ï¼æ»å®¢è¿é=ç线+åé
å
¬äº¤+巡游åºç§+ç½çº¦è½¦ï¼ |
| | | |
| | | ### å®¡æ ¸è§åï¼éæ±åæï¼4 ç»ï¼ |
| | | 1. è½¦è¾æ°/è½½å®¢ä½æ°ä¸è¿æ¿å¯¹æ¯ï¼è½¦è¾å·®è· ±3 å°ã载客ä½å·®è· ±50 â æ ¸å® |
| | | 2. 客è¿é/æ
客å¨è½¬éï¼ä¸~åç±»ãå
车ï¼ç¯æ¯ä¸æ = 0 â æ ¸å® |
| | | 3. å¹³åè¿è·ï¼ä¸~åç±»ï¼ç¯æ¯ ±10%ãå
车平åè¿è· = 䏿 â æ ¸å® |
| | | 4. æ ççº¿å´æç线客è¿éãæ å
è½¦å´æå
车客è¿é â æ ¸å® |
| | | |
| | | ### å¤ç¨ç¹ |
| | | - 导å
¥ï¼æä¼ä¸éè¡è§£æï¼å®å
¨å¤ç¨ H2032 导å
¥æ¨¡å¼ï¼æ°å¢è§£æå¨+ç®æ 表å³å¯ï¼ |
| | | - å®¡æ ¸ï¼DIFF/RATIO/MOM å·²æï¼éæ°å¢ `ç¯æ¯=0 æ ¸å®`ï¼MOM_EQ_ZEROï¼ä¸ `æ 车æéçç¾`ï¼EXISTENCEï¼ä¸¤ç§æ¯è¾ç±»å |
| | | - æ¥è¡¨ï¼åå¸å·æç»+忝+累计ï¼å¤ç¨è´§è¿æ¥è¡¨æ¨¡å¼ |
| | | |
| | | ## ä¸ãé路货è¿è½èï¼H204 壿¥ï¼ââ éæ©å±å®¡æ ¸å¼æ |
| | | |
| | | ### æ°æ®ç»æï¼å®æ `é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼.xlsx`ï¼ |
| | | - 车è¾å£æ¥æç»ï¼**æ¯è¡ä¸è¾è½¦**ï¼ä¸æ¯æ¯è¡ä¸å®¶ä¼ä¸ï¼ï¼çº¦ 18 åï¼æå±å°åºãä¼ä¸ä»£ç ãä¼ä¸åç§°ã车çå·ã车è¾ç±»åãçæç±»åãæ è®°å¨ä½ãæ»è¡ç¨ã载货éç¨ã空驶éç¨ãè´§è¿éãè´§ç©å¨è½¬éãå¹³åè¿è·ãç¾å
¬éåèãå¨è½¬éåèç |
| | | - `è¿æ¿è½¦è¾ä¿¡æ¯`ï¼5 åï¼è½¦çå·ã车è¾ç±»åãçæç±»åãæ è®°å¨ä½ãåçµå¼è´¨éï¼ |
| | | - `è½è¿æ±æ»è¡¨`ï¼çæè¡¨ï¼13 åï¼æçæç±»åï¼æ´æ²¹/汽油/LNG/CNG/纯çµ/çæçµæ± ï¼æ±æ» |
| | | |
| | | ### å®¡æ ¸è§åï¼éæ±åæï¼7 ç»ï¼ |
| | | 1. 车è¾ç±»å/çæç±»å/æ è®°å¨ä½ä¸è¿æ¿å¯¹æ¯ï¼ä¸ä¸è´ â æ ¸å® |
| | | 2. 载货éç¨/æ»è¡ç¨ ä¸ å¹³åè¿è·/åè¶å¹³åéç¨ ç¸å·® ±10% â æ ¸å® |
| | | 3. åçæç±»åç¾å
¬éåèè¶
èå´ â æ ¸å® |
| | | 4. åä¸è¾è½¦ç¾å
¬éåèç¯æ¯ ±50% â æ ¸å® |
| | | 5. åä¸ä¼ä¸è½¦è¾è´§è¿é/å¨è½¬éä¹å > è´§è¿ææ¥ 50% â æ ¸å®ï¼**跨表å¼ç¨è´§è¿ H203-2**ï¼ |
| | | 6. 车è¾å¹³åè¿è· ä¸ è´§è¿ææ¥å¹³åè¿è· ç¸å·® ±60% â æ ¸å®ï¼**跨表å¼ç¨**ï¼ |
| | | 7. å¹³å载货å¨ä½/æ è®°å¨ä½ å°äº 0.5 æå¤§äº 1 â æ ¸å® |
| | | |
| | | ### å¤ç¨ç¹ |
| | | - æ°æ®æ¨¡åæ¯"车è¾"ç²åº¦ï¼éæ°å»º `h204_vehicle_quarterly` 表ï¼ä½å¯¼å
¥/å®¡æ ¸/æ¥è¡¨é¾è·¯åæ¡æ¶ |
| | | - å®¡æ ¸å¼æéæ°å¢**跨表å¼ç¨**æ¯è¾ï¼CROSS_TABLEï¼ï¼è§åè½å¼ç¨å¦ä¸å¼ æ¥è¡¨çæ°æ®åå¯¹æ¯ |
| | | |
| | | ## åãåå¸å®¢è¿ï¼å
¬äº¤ / 巡游åºç§ / ç½çº¦è½¦ï¼ââ è§åæå¤æ |
| | | |
| | | ### æ°æ®ç»æï¼å®æ `åå¸å®¢è¿æ°æ®æ¨¡æ¿.xlsx`ï¼ |
| | | - `å
¬è·¯æ»å®¢è¿`ã`åå¸å®¢è¿`ï¼å为å¸å·çº§çæè¡¨ï¼274 å / 69 åï¼åæå®¢è¿é/å¨è½¬é + 忝 + 累计ï¼ï¼æ¨¡æ¿å
å
¬å¼å¼ç¨å¤é¨æ°æ®ï¼å¤å¤ #REF!ï¼ééæ°æ¥æ°æ®æºï¼ |
| | | - ç½çº¦è½¦æåï¼å®¢è¿é/è®¢åæ»æ°ç±é¨çº§ä¸åï¼å·¡æ¸¸åºç§å¨ç³»ç»å
坿¥ï¼éæå
¬å¼æåå°åå¸å·ï¼Excel å
ææåå
¬å¼ï¼ééæ ¼æ ¸å¯¹ï¼ |
| | | |
| | | ### å®¡æ ¸è§åï¼éæ±åæï¼ |
| | | - å
¬äº¤ 4 ç»ï¼åé
å乡ä¸åå¸å
è¶å¿ä¸è´ï¼ä¸ä¸è´ä¸ç¯æ¯å¢é>2% æ ¸å®ï¼ï¼å®¢è¿éä¸å¨è½¬éè¶å¿ä¸è´ï¼åå¸å
å æ¯åå>1% æ ¸å®ï¼å¹³åè¿è·åå>3 å
¬éæ ¸å® |
| | | - 巡游åºç§ 7 ç»ï¼è¿è¥è½¦æ°ä¸è¿æ¿å¯¹æ¯ >3%ï¼è¶å¿ä¸è´ï¼å®¢è¿é/å¨è½¬éè¶å¿ä¸è´ï¼å æ¯åå>1%ï¼å¹³åè¿è·åå>3 å
¬éï¼å车次载客系æ°åå>0.2ï¼è½½å®¢è½¦æ¬¡ä¸å®¢è¿éè¶å¿ä¸è´ |
| | | - 计ç®å
¬å¼ï¼å¹³åè¿è·=æ
客å¨è½¬é/客è¿éï¼å车次载客人æ°=客è¿é/è½½å®¢è½¦æ¬¡æ»æ° |
| | | |
| | | ### å¤ç¨ç¹ |
| | | - å®¡æ ¸å¼æéæ°å¢ï¼è¶å¿ä¸è´æ§ï¼TRENDï¼ãå æ¯ååï¼RATIO_CHANGEï¼çæ¯è¾ç±»å |
| | | - æ°æ®æ¥æºä¸æ¢ Excelï¼é¨çº§ä¸åç½çº¦è½¦æ°ãè¿æ¿è½¦è¾ï¼ä»
巡游åºç§ï¼â éè¦å¤æ°æ®æºå¯¼å
¥è®¾è®¡ |
| | | - 建议æç¬¬ä¸ï¼è§å弿æ©å±ææ¬é«ï¼ä¸ä¾èµå¤é¨æ°æ®å£å¾ç¡®è®¤ï¼ |
| | | |
| | | ## äºãæèµæ¨¡åï¼å®¢è¿ç«åº / ç©æµååºï¼ââ ç¬ç«å»ºæ¨¡ |
| | | |
| | | ### æ°æ®ç»æï¼å®æ `æèµ/` ç®å½ï¼ |
| | | - è¾å
¥ï¼17 å¸å· à 2 ç±»ï¼å®¢è¿ç«åºãç©æµååºï¼Ã æ¯æä¸æ¥è¡¨ï¼è¡¨1-1 é¡¹ç®æç»ã表1-2 å»ºè®¾è§æ¨¡/ç产è½åï¼ï¼æ ·ä¾ä¸º 2026 å¹´ 6/7 æ |
| | | - **æ ¼å¼ä¸ç»ä¸**ï¼åæ° 20~26 ä¸çãæ é¢è¡ä½ç½®ä¸åãæä»¶ç±»åæ··åï¼.xls/.xlsx/.et/.pdfï¼ï¼å¸å·èªè¡å¡«æ¥ |
| | | - è¾åºï¼4 å¼ åºå®æ ¼å¼æ±æ»è¡¨ï¼ç»é¢å¯¼ï¼ï¼ |
| | | 1. 2026å¹´å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ»ï¼é¡¹ç®è¿åº¦ã1-N æç´¯è®¡ãå®é
è¿åº¦ãçè¡¥èµéï¼ |
| | | 2. "åäºäº"è§å纳å
¥çåæ¹å§éç¹é¡¹ç®æ¸
åç©æµé¡¹ç®è¿å±æ
åµï¼è¡¨1-2+åé¡¹ç®æèµå®ææ
åµï¼ |
| | | 3. æ¹åçï¼å®¢è´§ç«åºï¼ï¼äº¿å
æèµé¡¹ç®ï¼ï¼æèµæ»è¡¨ï¼å£åº¦/å¹´åº¦å®æï¼+ è§ä¸é¡¹ç®è¡¨ï¼24 åï¼ |
| | | 4. ï¼ç»æµå¼ºå¿é¡¹ç®ï¼æ¹åç交éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨ï¼å
¨çè´§è¿ï¼ç©æµï¼åºç¡è®¾æ½æèµææ¥ï¼17 åï¼ |
| | | - 妿 `æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7).xls`ï¼8 åï¼åä½ãæ¶æãè®¡åæ»æèµãç´¯è®¡å®æãæ¬å¹´è®¡åãèªå¹´å累计ã彿宿ï¼ââ**å·²ææèµç³»ç»æ°æ®æºå¯å¯¹æ¥** |
| | | |
| | | ### å¤ç¨ç¹ |
| | | - æ°æ®æ¨¡åæ¯"项ç®"维度ï¼é¡¹ç®åç§°ãæ»æèµãç´¯è®¡å®æãæ¬å¹´è®¡åãæ¬æå®æãè¿åº¦ã建çé¢ç§¯ï¼ï¼ä¸ä¼ä¸/ææ ç»´åº¦ä¸å â æ°å»º `investment` 模å |
| | | - 导å
¥ï¼åå¸å·æ ¼å¼å®¹éè§£æå·¥ä½éæå¤§ï¼å»ºè®®åæ¶æä¾åºå®æ¨¡æ¿+èªå¨è¯å«ä¸¤ç§è·¯å¾ï¼æç´æ¥å¯¹æ¥æèµç³»ç»ï¼ |
| | | - å®¡æ ¸ï¼è§åå°ï¼ç¼ºæ°ãè¿åº¦å¼å¸¸ãä¸ç³»ç»æ°æ¯å¯¹ä¸ºä¸»ï¼ |
| | | - æ¥è¡¨ï¼4 å¼ åºå®æ ¼å¼æ¨¡æ¿ï¼å¤ç¨æ¥è¡¨å¯¼åºæ¡æ¶ |
| | | - æé/æä½æ¥å¿/LLM/AI è§£éåºåº§å¯ç´æ¥å¤ç¨ |
| | | |
| | | ## å
ã宿½é¡ºåºä¸æ¶æå»ºè®® |
| | | |
| | | ### 顺åºï¼æ"å¿«éè§æ + éæ¥è¡¥è½å"ï¼ |
| | | 1. **å
¬è·¯æ
客 H203-1**ï¼éªè¯"ä¸å¥æ¡æ¶å¤å¼ 表"ï¼ææ¬æä½ |
| | | 2. **è½è H204**ï¼è¡¥é½å®¡æ ¸å¼æ"跨表å¼ç¨"è½å |
| | | 3. **åå¸å®¢è¿**ï¼è¡¥é½"è¶å¿/å æ¯"夿è§åï¼ç¡®è®¤ç½çº¦è½¦æ°æ®æ¥æº |
| | | 4. **æèµ**ï¼ç¬ç«æ¨¡åï¼å
å³ç"å¯¹æ¥æèµç³»ç» vs è§£æå¸å·æ¥è¡¨" |
| | | |
| | | ### æ¶æå»ºè®®ï¼é¿å
å 代ç ï¼ |
| | | 1. æ¥è¡¨ç±»å注å表åï¼`report_type`ï¼ç°æ `audit_rule.report_type` å·²é¢çï¼â æ¯ç§æ¥è¡¨ = 导å
¥è§£æå¨ + ç®æ 表 + è§åé + æ¥è¡¨æ¨¡æ¿ï¼éè¿æ³¨å表æè½½ |
| | | 2. å®¡æ ¸å¼ææ°å¢æ¯è¾ç±»åï¼å¯é
ç½®ãè§å表驱å¨ï¼ä¸åæ»ï¼ï¼ |
| | | - `MOM_EQ_ZERO`ï¼ç¯æ¯=0 æ ¸å®ï¼ã`EXISTENCE`ï¼æ 车æéçç¾ï¼ |
| | | - `CROSS_TABLE`ï¼è·¨è¡¨å¼ç¨å¯¹æ¯ï¼ã`TREND`ï¼è¶å¿ä¸è´æ§ï¼ã`RATIO_CHANGE`ï¼å æ¯ååï¼ |
| | | 3. 夿°æ®æºå¯¼å
¥ï¼Excel æä»¶ + å¤é¨ç³»ç»æ¥è¯¢å¯¼åºï¼å¦æèµç³»ç»ï¼+ é¨çº§ä¸åæ°æ® |
| | | 4. AI è§£é/è±æ/æéå
¨é¨æç°æ `llmintegration`ã`security` å¤ç¨ |
| | | |
| | | ### å·¥ä½éç²ä¼°ï¼åäººï¼ |
| | | | 模å | å端 | å端 | å计ï¼äººå¤©ï¼ | |
| | | |------|------|------|------------| |
| | | | å
¬è·¯æ
客 H203-1 | 3~5 | 1~2 | 4~7 | |
| | | | è½è H204 | 4~6 | 1~2 | 5~8 | |
| | | | åå¸å®¢è¿ | 6~10 | 2~3 | 8~13 | |
| | | | æèµ | 8~12 | 2~3 | 10~15 | |
| | | |
| | | > ä¸å«éæ±ç¡®è®¤ãæ°æ®å£å¾æ ¸å®ãèè°æµè¯æ¶é´ã |
| New file |
| | |
| | | # æèµæ¨¡å宿½æ¹æ¡ï¼å®¢è¿ç«åº / ç©æµååºï¼ |
| | | |
| | | > åºäº `docs/æ©å±è¡¨æ¨¡æ¿/æèµ/` å
¨é¨æ ·ä¾å®ææ´çï¼2026-08-23ï¼ |
| | | > ç»è®ºå
è¡ï¼**ä¸»æ°æ®æºèµ°"å
¨çæ±æ»å¤§è¡¨ + å¸å·ä¸æ¥è¡¨è§£æ"ï¼æèµç³»ç»å¯¼åºä»
ä½å®¡æ ¸æ¯å¯¹**ï¼å»ºè®®å两æè½å°ï¼ç¬¬ä¸æå
让 4 å¼ é¢å¯¼æ¥è¡¨è·éã |
| | | |
| | | ## ä¸ãæ°æ®æºå®æç»è®º |
| | | |
| | | ### 1. æèµç³»ç»æ¥è¯¢å¯¼åºï¼æå¹²åï¼ä½å段æå°ï¼ |
| | | `æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7).xls`ï¼149 è¡ Ã 8 åï¼ï¼ |
| | | |
| | | | å | åæ®µ | æ ·ä¾ | |
| | | |----|------|------| |
| | | | A | è¡å· | 2 | |
| | | | B | åä½ï¼å³é¡¹ç®åç§°ï¼ | æ±å£å®¢è¿ä¸å¿ | |
| | | | C | æ¶æ | 2026å¹´7æ | |
| | | | D | è®¡åæ»æèµ | 315700 | |
| | | | E | èªå¼å§å»ºè®¾è³æ¬æç´¯è®¡å®ææèµ | 177685.4 | |
| | | | F | æ¬å¹´è®¡åæèµ | 30000 | |
| | | | G | èªå¹´åç´¯è®¡å®ææèµ | 4000 | |
| | | | H | 彿宿æèµ | 500 | |
| | | |
| | | - ä¼ç¹ï¼ç»æåºå®ãæ é容éè§£æã |
| | | - å±éï¼**æ å¸å·ãæ è¿åº¦/形象è¿åº¦/æ¹å¤æå·**ï¼åªè½æ¯æ 4 å¼ è¾åºè¡¨éçæ°å¼åï¼ä¸è¶³ä»¥ç¬ç«çæé¢å¯¼æ¥è¡¨ã |
| | | |
| | | ### 2. å¸å·ä¸æ¥è¡¨ï¼æ ¼å¼ä¸ç»ä¸ï¼é容éè§£æï¼ |
| | | - 客è¿ç«åºæ ·ä¾ï¼å®æ 18 åãæ±æ»å¤§è¡¨ 20 åï¼ç©æµæ ·ä¾ï¼é»ç³ 23 åãæ±æ»è¡¨ 24 åã |
| | | - å
Œ
±å段ï¼åä½ç½®å 表èå¼ï¼é¡»æè¡¨å¤´å
³é®è¯å¹é
ï¼ï¼ |
| | | åºå· / 项ç®åç§°åç«çº§ / æèµå»ºè®¾åä½ / 建设æ§è´¨ï¼æ°å»º/ç»å»º/æ¹æ©å»ºï¼/ å¼å·¥æ¶é´ / 竣工æ¶é´ / è®¡åæ»æèµ / èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµ / æ¬å¹´è®¡åæèµ / èªå¹´åç´¯è®¡å®ææèµ / å
¶ä¸æ¬æå®ææèµ / å»ºè®¾é¶æ®µï¼å¨å»º/å®å·¥/åæï¼/ 形象è¿åº¦ / 工坿¹å¤ / å设æ¹å¤ / 建çé¢ç§¯ / ç¨å·¥äººæ°ã |
| | | - åç»è¡ï¼`ä¸ã使ç¨äº¤éé¨è½¦è´ç¨èµé建设项ç®`ã`äºã使ç¨ççæ²¹ç¨è¿è¿èµé建设项ç®`ã`ä¸ãåæç»¼åè¿è¾æå¡ç«/ä¸è¬é¡¹ç®`ã`åãå车äº/åæç»¼åæå¡ç«`ã`äºã计åå¤é¡¹ç®`ï¼å¸å·å°è®¡è¡å«"å°è®¡/å计"ã |
| | | - æä»¶æ··åï¼`.xls / .xlsx / .et / .pdf`ï¼æ é¢è¡ä½ç½®ä¸ä¸ï¼é¨åæ æ é¢ç´æ¥æ¯è¡¨å¤´ï¼ã |
| | | |
| | | ### 3. å
¨çæ±æ»ä¸é´è¡¨ï¼æ¨è䏿䏻å
¥å£ï¼ |
| | | - `6æ/7æå®¢è¿ç«æèµæç»è¡¨.xlsx`ï¼ä¸¤ sheetï¼æç» 121 è¡ Ã 20 å + æ±æ»ï¼ï¼å·²æ¯å
¨ç 17 å¸å·åç»åè®¡ç»æï¼æ ¼å¼ç»ä¸ã |
| | | - `6æç©æµç«åºæèµæç».xls`ï¼4 sheetï¼æ2 å»ºè®¾è§æ¨¡è¡¨ / åé¡¹ç®æèµå®ææ
åµ / åå¸å·è§åå
å¤é¡¹ç®å®ææ
åµ / å¸å·æ±æ»ï¼ï¼æ ¼å¼ç»ä¸ã |
| | | |
| | | ### 4. è¾åº 4 å¼ è¡¨ï¼ç»é¢å¯¼ï¼éæ±è§ `æ¯ä¸ªæååºè¿äºåºå®æ ¼å¼ç表ï¼ç»é¢å¯¼ç.txt`ï¼ |
| | | | # | 表å | ç»æ | å
³é®å£å¾ | |
| | | |---|------|------|---------| |
| | | | 1 | 2026å¹´å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ» | 28 åï¼åå±è¡¨å¤´ï¼å计/客è¿ç«åº/ç©æµ 大å + 17 å¸å·å°è®¡ + é¡¹ç®æç» | åºå·/å¸å·/å¿åº/项ç®/æ§è´¨/建设æ¶é´/æ»æèµ/ç´¯è®¡å®æ/2026年计å(å计·é¨Â·ç·èªç¹Â·è¿åº¦)/2026·2027建议计å/åæå·¥ä½/1-Næç´¯è®¡å®æ/å®é
è¿åº¦/çè¡¥èµé/夿³¨ | |
| | | | 2 | "åäºäº"è§åç©æµé¡¹ç®è¿å± | å¸å·å°è®¡ + é¡¹ç®æç» | åºå·/项ç®/ä¼ä¸/å¼å·¥/å®å·¥/æ»æèµ/ç´¯è®¡å®æ/æ¬æå®æ/è¿åº¦/è¿å±æè¿° | |
| | | | 3 | æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç® | 2 sheetï¼æèµæ»è¡¨(6åå£åº¦å£å¾) + è§ä¸é¡¹ç®è¡¨(24å) | è§ä¸é¡¹ç®ï¼ç份/å¸å·/项ç®/ç±»å«/建设å
容/ç¶æ/æ§è´¨/å¼å·¥/æ»æèµ/累计/åæè®¡åä¸å®æï¼**çéâ¥1亿å
项ç®** | |
| | | | 4 | ï¼ç»æµå¼ºå¿é¡¹ç®ï¼äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡ | 17 å | åºå·/项ç®/ä¼ä¸/æ»æèµ/ç´¯è®¡å®æ/æ¬å¹´è®¡å/èªå¹´å累计/æ¬æå®æ/è¿åº¦/è¿å±/å·¥å¯å设æ¹å¤ | |
| | | |
| | | **æ´å¯**ï¼4 å¼ è¾åºè¡¨æ¯å䏿¹é¡¹ç®æ°æ®ç 4 ç§è§è§ï¼å
¨ç计åå
¨æ¯ / åäºäºç©æµéç¹ / 亿å
è§æ¨¡ä»¥ä¸ / ç»æµå¼ºå¿ï¼ï¼æ½ä¸å¥é¡¹ç®ä¸»æ¡£å³å¯å¤ç¨ã |
| | | |
| | | ## äºãæ°æ®æ¨¡åï¼å»ºè®® 2 å¼ è¡¨ï¼ |
| | | |
| | | ### investment_project 项ç®ä¸»æ¡£ï¼æé¡¹ç®å+ç±»åå¯ä¸ï¼ |
| | | | åæ®µ | 说æ | |
| | | |------|------| |
| | | | id / project_name | ä¸»é® / 项ç®åç§°ï¼å¯ä¸é®ï¼project_name + categoryï¼ | |
| | | | category | 客è¿ç«åº / ç©æµååº | |
| | | | city / county | å¸å· / å¿ï¼å¸ãåºï¼ | |
| | | | construct_nature | æ°å»º/ç»å»º/æ¹æ©å»º | |
| | | | start_year / end_year | å¼å·¥ / 竣工æ¶é´ | |
| | | | total_investment | è®¡åæ»æèµï¼ä¸å
ï¼doubleï¼ | |
| | | | approval_gk / approval_cs | å·¥å¯ / å设æ¹å¤æå· | |
| | | | planned_level | æ¯å¦ 2026 é¢å®æè®¡åå
/ "åäºäº"éç¹ / 亿å
é¡¹ç®æ è®° | |
| | | | source | æ°æ®æ¥æºï¼å
¨çæ±æ»è¡¨/å¸å·æ¥è¡¨/ç³»ç»å¯¼åºï¼ | |
| | | |
| | | ### investment_monthly æåº¦å®æè¡¨ï¼report_period + project_idï¼ |
| | | | åæ®µ | 说æ | |
| | | |------|------| |
| | | | report_period | æ¥è¡¨æ 2026-07 | |
| | | | project_id | å
³è主档 | |
| | | | year_plan | æ¬å¹´è®¡åæèµ | |
| | | | year_cum | èªå¹´åç´¯è®¡å®ææèµ | |
| | | | month_done | 彿宿æèµ | |
| | | | start_cum | èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµ | |
| | | | progress_stage / progress_desc | å»ºè®¾é¶æ®µ / 形象è¿åº¦ | |
| | | | building_area | 建çé¢ç§¯ï¼å®¢è¿è¡¨ï¼ | |
| | | |
| | | > æ¥è¡¨æä»¥é¡µé¢éæ©ä¸ºåï¼éé¢/é¢ç§¯ç»ä¸ double åå¨ï¼ä¸å BigDecimalã |
| | | |
| | | ## ä¸ã导å
¥è®¾è®¡ï¼ä¸¤æï¼ |
| | | |
| | | ### 䏿ï¼å
¨çæ±æ»å¤§è¡¨ + ç³»ç»å¯¼åºï¼å¿«éè·é 4 å¼ è¡¨ï¼ |
| | | - æ°å¢å¯¼å
¥ç±»å"æèµææ¥"ï¼æ¯æ `6æ/7æå®¢è¿ç«æèµæç»è¡¨.xlsx`ï¼æç» sheetï¼ä¸ `6æç©æµç«åºæèµæç».xls`ï¼åé¡¹ç®æèµå®ææ
åµ sheetï¼ä¸¤ç±»ç»ä¸æ ¼å¼ã |
| | | - è§£æè¦ç¹ï¼è·³è¿åç»è¡/å°è®¡è¡ï¼å
³é®è¯ï¼å¸å·å+æ°ååã`å°è®¡/å计`ã`ä¸ãäºãä¸â¦`ï¼ï¼è¯å«é¡¹ç®æç»è¡ï¼è¡¨å¤´æå
³é®è¯æ å°åã |
| | | - å¹çï¼åæ¥è¡¨æå 餿§æ°æ®ï¼ä¸»æ¡£æå upsertï¼æåº¦è¡¨æ period å é¤é建ï¼ã |
| | | - 忶坼å
¥ `æ¥è¯¢ç»æ(æèµç³»ç»â¦).xls` ä½ä¸ºå®¡æ ¸æ¯å¯¹åºåï¼å `investment_system` 表æç´æ¥å®¡æ ¸æ¶è¯»æä»¶â建议å表ï¼ã |
| | | |
| | | ### äºæï¼17 å¸å·å表容éè§£æ |
| | | - è§£æå¨ä¸çº§å®¹éï¼â æå
³é®è¯å®ä½è¡¨å¤´è¡ï¼`项ç®åç§°`+`æ»æèµ`ï¼â â¡æè¡¨å¤´å
³é®è¯æ å°åï¼`计å.*æ»æèµ`âæ»æèµçï¼â â¢åç»/å°è®¡è¡è¿æ»¤ã |
| | | - æ ¼å¼æ³¨åè¡¨ï¼æ¯ç§å¸å·æ ¼å¼ = ä¸ä¸ªè§£æå¨é
ç½®ï¼åæ å° + æ é¢è¡åç§» + è·³è¿è¡æ¨¡å¼ï¼ï¼æ°å¢æ ¼å¼å é
ç½®ä¸æ¹ä»£ç ã |
| | | - æ¯æ `.xls/.xlsx`ï¼`.et`ï¼WPSï¼ä¸ `.pdf` å
æç¤º"请å¦å为 xls/xlsx"ã |
| | | |
| | | ## åãå®¡æ ¸è§åï¼è§å表驱å¨ï¼report_type=INVESTï¼ |
| | | | è§å | æ¯è¾ç±»å | 说æ | |
| | | |------|---------|------| |
| | | | 䏿èµç³»ç»æ°å¯¹æ¯ | CROSS_DIFF | è®¡åæ»æèµ/èªå¹´å累计/彿宿 ä¸ç³»ç»å¯¼åºå·®å¼è¶
éå¼ï¼å¦æ»æèµ>5% æç»å¯¹å·®>100ä¸ï¼ | |
| | | | 宿è¶
计å | RANGE | 彿宿 > æ¬å¹´è®¡åå©ä½ï¼ç´¯è®¡å®æ > è®¡åæ»æèµÃ1.05 | |
| | | | è¿åº¦çç¾ | LOGIC | é¶æ®µ=å®å·¥ä½å®æç<1ï¼é¶æ®µ=å¨å»ºä½ç´¯è®¡=0 | |
| | | | å¿
填缺失 | NULL_CHECK | 项ç®å/æ»æèµ/ç´¯è®¡å®æ 为空 | |
| | | |
| | | ## äºãæ¥è¡¨çæï¼å¤ç¨ç°æå¯¼åºæ¡æ¶ï¼ |
| | | - 4 个端ç¹ï¼`/api/report/export/investPlan`ï¼é¢å®æè®¡å表ï¼ã`investFiveYearLogistics`ï¼åäºäºç©æµï¼ã`investBillion`ï¼äº¿å
项ç®ï¼å«æèµæ»è¡¨+è§ä¸é¡¹ç®è¡¨ä¸¤ sheetï¼ã`investCounty`ï¼ç»æµå¼ºå¿è¡¨ï¼ã |
| | | - 卿æä»½ï¼`period=2026-07` â æ é¢/åå¤´ç¨ `1-7æç´¯è®¡`ï¼å¸å·é¡ºåºæ²¿ç¨ `RegionUtil.cityList()`ã |
| | | - 亿å
项ç®è¡¨ï¼æ `total_investment ⥠10000 ä¸å
` çéï¼æèµæ»è¡¨æå£åº¦å£å¾ä»æåº¦å®ææ¨å¯¼ï¼ä¸å£åº¦=1-3æãäºå£åº¦=4-6æâ¦ï¼ã |
| | | |
| | | ## å
ãå端 |
| | | - `DataImport.vue`ï¼æ°å¢"æèµææ¥"ï¼å
¨çæ±æ»å¤§è¡¨ï¼ä¸"æèµç³»ç»å¯¼åº"两ç§å¯¼å
¥ç±»åã |
| | | - `ReportExport.vue`ï¼æ°å¢"æèµæ¥è¡¨ï¼INVESTï¼"å¡çï¼4 ä¸ªçææé®ã |
| | | - `AuditResult.vue`ï¼æ¥è¡¨ç±»åå¢å "æèµææ¥"ï¼å¯éï¼äºæï¼ã |
| | | |
| | | ## ä¸ãéç¨ç¢ä¸å·¥ä½éï¼åäººï¼ |
| | | | éç¨ç¢ | å
容 | å·¥ä½é | |
| | | |--------|------|--------| |
| | | | M1 | å
¨çæ±æ»å¤§è¡¨å¯¼å
¥ + 主档/æåº¦è¡¨ + 4 å¼ æ¥è¡¨è¾åº | å端 4~5 人天 + å端 1~2 人天 | |
| | | | M2 | å¸å·å表容éè§£æ + ç³»ç»å¯¼åºæ¯å¯¹å®¡æ ¸ | å端 4~6 人天 | |
| | | | M3 | å端å
¥å£æ´å + èè°ï¼ä¸å¸å·æ ¸å¯¹ 6/7 ææ ·ä¾ï¼ | 2~3 人天 | |
| | | |
| | | > ä¸å«éæ±ç¡®è®¤ä¸æ°æ®å£å¾æ ¸å®ï¼å»ºè®® M1 宿å³è®©ç¨æ·ç¨ 6/7 ææ ·ä¾æ ¸å¯¹ 4 å¼ è¾åºè¡¨ä¸ç°æ Excel æ¯å¦ä¸è´ã |
| | | |
| | | ## å
«ãå¾
ç¨æ·ç¡®è®¤çç¹ |
| | | 1. ä¸»æ°æ®æºï¼å
以"å
¨çæ±æ»å¤§è¡¨"为åï¼M1ï¼ï¼è¿æ¯ç´æ¥ä¸"å¸å·å表解æ"ï¼ |
| | | 2. ç³»ç»å¯¼åºæ¯å¦æ¯æé½æï¼å¯ä½æ¯å¯¹åºåï¼ï¼æ¯å¦æä¾é¡¹ç®-å¸å·å¯¹ç
§ï¼ç³»ç»å¯¼åºæ å¸å·å段ï¼ï¼ |
| | | 3. 亿å
项ç®çéå£å¾ï¼â¥1 亿ï¼10000 ä¸ï¼æ¯å¦åç¡®ï¼ç»æµå¼ºå¿é¡¹ç®æ¸
忝妿åºå®ååï¼ |
| | |
| | | import com.trafficaudit.auditengine.entity.AuditResult; |
| | | import com.trafficaudit.auditengine.service.AuditEngineService; |
| | | import com.trafficaudit.common.Result; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | private AuditEngineService auditService; |
| | | |
| | | @PostMapping("/execute") |
| | | public Result<List<AuditResult>> executeAudit(@RequestParam("period") String period) { |
| | | List<AuditResult> results = auditService.executeAudit(period); |
| | | public Result<List<AuditResult>> executeAudit(@RequestParam("period") String period, |
| | | @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) { |
| | | List<AuditResult> results; |
| | | if ("H2031".equals(reportType)) { |
| | | results = auditService.executePassengerAudit(period); |
| | | } else if ("H204".equals(reportType)) { |
| | | results = auditService.executeEnergyAudit(period); |
| | | } else if ("INVEST".equals(reportType)) { |
| | | results = auditService.executeInvestmentAudit(period); |
| | | } else if ("CITY_BUS".equals(reportType)) { |
| | | results = auditService.executeCityBusAudit(period); |
| | | } else if ("CITY_TAXI".equals(reportType)) { |
| | | results = auditService.executeCityTaxiAudit(period); |
| | | } else { |
| | | results = auditService.executeAudit(period); |
| | | } |
| | | return Result.ok(results); |
| | | } |
| | | |
| | | /** æ 记该æ¥è¡¨æ+ç±»å为ãå·²å®¡æ ¸ãï¼çº¿ä¸å®¡æ ¸éè¿çæ°æ®ï¼å
å®¡æ ¸ååºè¡¨ç便®ï¼å¯¼åºåä¸åæç¤ºæªå®¡æ ¸ï¼ */ |
| | | @PostMapping("/markAudited") |
| | | public Result<Boolean> markAudited(@RequestParam("period") String period, |
| | | @RequestParam("reportType") String reportType) { |
| | | return Result.ok(auditService.markAudited(reportType, period)); |
| | | } |
| | | |
| | | @GetMapping("/ruleStats") |
| | | public Result<java.util.List<java.util.Map<String, Object>>> ruleStats(@RequestParam("period") String period, |
| | | @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) { |
| | | return Result.ok(auditService.ruleStats(period, reportType)); |
| | | } |
| | | |
| | | @GetMapping("/latestPeriod") |
| | | public Result<String> latestPeriod(@RequestParam(value = "reportType", defaultValue = "H2032") String reportType) { |
| | | return Result.ok(auditService.latestPeriod(reportType)); |
| | | } |
| | | |
| | | @GetMapping("/export") |
| | | public ResponseEntity<byte[]> exportResults(@RequestParam("period") String period, |
| | | @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) throws Exception { |
| | | byte[] bytes = auditService.exportResults(period, reportType); |
| | | String name = java.net.URLEncoder.encode("å®¡æ ¸ç»æ_" + period + ".xlsx", "UTF-8"); |
| | | return ResponseEntity.ok() |
| | | .header("Content-Disposition", "attachment; filename*=UTF-8''" + name) |
| | | .contentType(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM) |
| | | .body(bytes); |
| | | } |
| | | |
| | | @GetMapping("/results") |
| | | public Result<List<AuditResult>> getResults(@RequestParam("period") String period) { |
| | | List<AuditResult> results = auditService.queryResults(period); |
| | | public Result<List<AuditResult>> getResults(@RequestParam("period") String period, |
| | | @RequestParam(value = "reportType", defaultValue = "H2032") String reportType) { |
| | | List<AuditResult> results = auditService.queryResults(period, reportType); |
| | | return Result.ok(results); |
| | | } |
| | | |
| New file |
| | |
| | | package com.trafficaudit.auditengine.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import lombok.Data; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** æ¥è¡¨æå®¡æ ¸éè¿æ è®°ï¼ææ¥è¡¨ç±»åææ¥è¡¨æå·²å®æå®¡æ ¸ï¼çº¿ä¸å®¡æ ¸éè¿/ç³»ç»å®¡æ ¸æ§è¡ååå
¥ï¼ï¼å¯¼åºå ready æ ¡éªä¾æ® */ |
| | | @Data |
| | | @TableName("audit_run") |
| | | public class AuditRun { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private String reportType; |
| | | private String reportPeriod; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.auditengine.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.auditengine.entity.AuditRun; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface AuditRunMapper extends BaseMapper<AuditRun> {} |
| | |
| | | |
| | | 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.dataimport.entity.EnergyAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly; |
| | | import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.InvestmentMonthly; |
| | | import com.trafficaudit.dataimport.entity.InvestmentProject; |
| | | import com.trafficaudit.dataimport.entity.InvestmentSystem; |
| | | import com.trafficaudit.dataimport.entity.CityBusMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiAuth; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.TransportAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.VehicleTrackMileage; |
| | | import com.trafficaudit.dataimport.mapper.EnergyAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentSystemMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper; |
| | | import com.trafficaudit.rulemanage.entity.AuditRule; |
| | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | |
| | | FREIGHT_TYPE_NAMES.put("freightGrain", "ç²®é£"); |
| | | } |
| | | |
| | | /** H204 车è¾ç±»åç -> å
许çè¿æ¿è½¦è¾ç±»åï¼å·²å»æé/ä¸/è½»ååç¼ï¼ */ |
| | | private static final Map<String, java.util.Set<String>> ENERGY_VEHICLE_TYPE_ALLOWED = new HashMap<>(); |
| | | |
| | | /** H204 çæç±»åç -> å
许çè¿æ¿çæç±»å */ |
| | | private static final Map<String, java.util.Set<String>> ENERGY_FUEL_TYPE_ALLOWED = new HashMap<>(); |
| | | |
| | | static { |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("01", new java.util.HashSet<>(java.util.Arrays.asList("æ®é货车", "æ æ¿è´§è½¦", "èªå¸è´§è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("02", new java.util.HashSet<>(java.util.Arrays.asList("å¹³æ¿è´§è½¦", "æ æ¿è´§è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("03", new java.util.HashSet<>(java.util.Arrays.asList("仿
å¼è´§è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("04", new java.util.HashSet<>(java.util.Arrays.asList("å¢å¼è´§è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("06", new java.util.HashSet<>(java.util.Arrays.asList("ç½å¼è´§è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("07", new java.util.HashSet<>(java.util.Arrays.asList("ç¹æ®ç»æè´§è½¦", "车è¾è¿è¾è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("08", new java.util.HashSet<>(java.util.Arrays.asList("èªå¸è´§è½¦", "æ æ¿è´§è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("09", new java.util.HashSet<>(java.util.Arrays.asList("车è¾è¿è¾è½¦", "ä¸ç½®è½´è½¦è¾è¿è¾è½¦"))); |
| | | ENERGY_VEHICLE_TYPE_ALLOWED.put("11", new java.util.HashSet<>(java.util.Arrays.asList("åæçµå¼è½¦"))); |
| | | |
| | | ENERGY_FUEL_TYPE_ALLOWED.put("01", new java.util.HashSet<>(java.util.Arrays.asList("汽油"))); |
| | | ENERGY_FUEL_TYPE_ALLOWED.put("02", new java.util.HashSet<>(java.util.Arrays.asList("æ´æ²¹"))); |
| | | ENERGY_FUEL_TYPE_ALLOWED.put("03", new java.util.HashSet<>(java.util.Arrays.asList("åç¼©å¤©ç¶æ°"))); |
| | | ENERGY_FUEL_TYPE_ALLOWED.put("04", new java.util.HashSet<>(java.util.Arrays.asList("æ¶²æå¤©ç¶æ°", "æ¶²åå¤©ç¶æ°", "åçæ"))); |
| | | ENERGY_FUEL_TYPE_ALLOWED.put("07", new java.util.HashSet<>(java.util.Arrays.asList("çµå¨"))); |
| | | ENERGY_FUEL_TYPE_ALLOWED.put("08", new java.util.HashSet<>(java.util.Arrays.asList("æ°¢æ°"))); |
| | | } |
| | | |
| | | @Resource |
| | | private AuditRuleMapper ruleMapper; |
| | | @Resource |
| | | private AuditResultMapper resultMapper; |
| | | @Resource |
| | | private AuditRunMapper auditRunMapper; |
| | | @Resource |
| | | private H2032EnterpriseMonthlyMapper h2032Mapper; |
| | | @Resource |
| | | private TransportAuthVehicleMapper transportAuthMapper; |
| | | @Resource |
| | | private VehicleTrackMileageMapper trackMileageMapper; |
| | | @Resource |
| | | private PassengerEnterpriseMonthlyMapper passengerMapper; |
| | | @Resource |
| | | private PassengerAuthVehicleMapper passengerAuthMapper; |
| | | @Resource |
| | | private EnergyVehicleQuarterlyMapper energyMapper; |
| | | @Resource |
| | | private EnergyAuthVehicleMapper energyAuthMapper; |
| | | @Resource |
| | | private InvestmentProjectMapper investProjectMapper; |
| | | @Resource |
| | | private InvestmentMonthlyMapper investMonthlyMapper; |
| | | @Resource |
| | | private InvestmentSystemMapper investSystemMapper; |
| | | @Resource |
| | | private CityBusMonthlyMapper cityBusMapper; |
| | | @Resource |
| | | private CityTaxiMonthlyMapper cityTaxiMapper; |
| | | @Resource |
| | | private CityTaxiAuthMapper cityTaxiAuthMapper; |
| | | |
| | | /** è®°å½ã该æè¯¥ç±»åå·²å®æå®¡æ ¸ãæ è®°ï¼å
å®¡æ ¸ååºè¡¨ä¾æ®ï¼æ è§åææ æ°æ®ä¸æ è®°ï¼é夿§è¡å¹çï¼ */ |
| | | public void markAudited(String reportType, String reportPeriod, List<AuditRule> rules, boolean hasData) { |
| | | if (rules == null || rules.isEmpty() || !hasData) return; |
| | | Long cnt = auditRunMapper.selectCount(new LambdaQueryWrapper<AuditRun>() |
| | | .eq(AuditRun::getReportType, reportType) |
| | | .eq(AuditRun::getReportPeriod, reportPeriod)); |
| | | if (cnt != null && cnt > 0) return; |
| | | AuditRun run = new AuditRun(); |
| | | run.setReportType(reportType); |
| | | run.setReportPeriod(reportPeriod); |
| | | auditRunMapper.insert(run); |
| | | } |
| | | |
| | | /** 2 åæ°éè½½ï¼ææ¥è¡¨ç±»åèªå¨åå¯ç¨è§å + æ ¡éªè¯¥æææ°æ®åååæ è®°ï¼çº¿ä¸å®¡æ ¸éè¿æ°æ®æå¨æ è®° / Controller è°ç¨ï¼ */ |
| | | public boolean markAudited(String reportType, String reportPeriod) { |
| | | List<AuditRule> rules = rulesOf(reportType); |
| | | if (rules.isEmpty()) { |
| | | log.info("markAudited skip: no enabled rules for {}", reportType); |
| | | return false; |
| | | } |
| | | long dataCount; |
| | | if ("H2031".equals(reportType)) { |
| | | dataCount = passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod)); |
| | | } else if ("H204".equals(reportType)) { |
| | | dataCount = energyMapper.selectCount(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod)); |
| | | } else if ("INVEST".equals(reportType)) { |
| | | dataCount = investMonthlyMapper.selectCount(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, reportPeriod)); |
| | | } else if ("CITY_BUS".equals(reportType)) { |
| | | dataCount = cityBusMapper.selectCount(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, reportPeriod)); |
| | | } else if ("CITY_TAXI".equals(reportType)) { |
| | | dataCount = cityTaxiMapper.selectCount(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, reportPeriod)); |
| | | } else { |
| | | dataCount = h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod)); |
| | | } |
| | | if (dataCount <= 0) { |
| | | log.info("markAudited skip: no data for {} / {}", reportType, reportPeriod); |
| | | return false; |
| | | } |
| | | markAudited(reportType, reportPeriod, rules, true); |
| | | return true; |
| | | } |
| | | |
| | | public List<AuditResult> executeAudit(String reportPeriod) { |
| | | List<AuditRule> rules = ruleMapper.selectEnabledRules(); |
| | | List<AuditRule> rules = rulesOf("H2032"); |
| | | List<H2032EnterpriseMonthly> reports = h2032Mapper.selectList( |
| | | new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod)); |
| | |
| | | } |
| | | } |
| | | log.info("Audit complete: {} reports, {} issues for {}", reports.size(), results.size(), reportPeriod); |
| | | if (!rules.isEmpty() && !reports.isEmpty()) markAudited("H2032", reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | public List<AuditResult> queryResults(String reportPeriod) { |
| | | public List<AuditResult> executePassengerAudit(String reportPeriod) { |
| | | List<AuditRule> rules = rulesOf("H2031"); |
| | | List<PassengerEnterpriseMonthly> reports = passengerMapper.selectList( |
| | | new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod)); |
| | | |
| | | Map<String, PassengerAuthVehicle> authMap = loadPassengerAuthMap(reportPeriod); |
| | | Map<String, PassengerEnterpriseMonthly> lastMonthMap = loadPassengerLastMonthMap(reportPeriod); |
| | | |
| | | // å¹çï¼åªæ¸
é¤ H2031 è§åçæ§å®¡æ ¸ç»æ |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : rules) ruleIds.add(rule.getId()); |
| | | if (!ruleIds.isEmpty()) { |
| | | resultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | |
| | | Map<Long, AuditRule> ruleMap = new HashMap<>(); |
| | | for (AuditRule rule : rules) ruleMap.put(rule.getId(), rule); |
| | | |
| | | List<AuditResult> results = new ArrayList<>(); |
| | | for (PassengerEnterpriseMonthly report : reports) { |
| | | for (AuditRule rule : rules) { |
| | | AuditResult result = checkPassengerRule(rule, report, authMap, lastMonthMap); |
| | | if (result != null) { |
| | | result.setRuleName(rule.getRuleName()); |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | result.setEnterpriseName(report.getEnterpriseName()); |
| | | result.setVerifyExplanation(report.getVerifyExplanation()); |
| | | resultMapper.insert(result); |
| | | results.add(result); |
| | | } |
| | | } |
| | | } |
| | | log.info("Passenger audit complete: {} reports, {} issues for {}", reports.size(), results.size(), reportPeriod); |
| | | if (!rules.isEmpty() && !reports.isEmpty()) markAudited("H2031", reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | |
| | | // ========== H204 è½èå®¡æ ¸ï¼è½¦è¾ç²åº¦ï¼ä¸è¿æ¿è½¦è¾ä¿¡æ¯è·¨è¡¨å¯¹æ¯ï¼ ========== |
| | | |
| | | public List<AuditResult> executeEnergyAudit(String reportPeriod) { |
| | | List<AuditRule> rules = rulesOf("H204"); |
| | | List<EnergyVehicleQuarterly> vehicles = energyMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod)); |
| | | |
| | | Map<String, EnergyAuthVehicle> authMap = new HashMap<>(); |
| | | List<EnergyAuthVehicle> authList = energyAuthMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, reportPeriod)); |
| | | for (EnergyAuthVehicle v : authList) { |
| | | authMap.putIfAbsent(key(v.getPlateNo()), v); |
| | | } |
| | | |
| | | // E007 ç¯æ¯åºåï¼ä¸ä¸å£åº¦å车çç¾å
¬éåè |
| | | String lastPeriod = prevQuarter(reportPeriod); |
| | | Map<String, EnergyVehicleQuarterly> lastQuarterMap = new HashMap<>(); |
| | | if (lastPeriod != null) { |
| | | for (EnergyVehicleQuarterly v : energyMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, lastPeriod))) { |
| | | lastQuarterMap.putIfAbsent(key(v.getPlateNo()), v); |
| | | } |
| | | } |
| | | |
| | | // E006 è¶
èå´åºåï¼æ¬æåçæç±»åç¾å
¬éåèä¸ä½æ° |
| | | Map<String, Double> fuelMedianMap = fuelPer100kmMedian(vehicles); |
| | | |
| | | // E008/E009 对æ¯åºåï¼åæ H2032 ä¼ä¸ææ¥ï¼æä¼ä¸åç´¢å¼ï¼ |
| | | Map<String, H2032EnterpriseMonthly> h2032Map = new HashMap<>(); |
| | | for (H2032EnterpriseMonthly m : h2032Mapper.selectList( |
| | | new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod))) { |
| | | String ent = m.getEnterpriseName(); |
| | | if (ent != null && !ent.trim().isEmpty()) h2032Map.putIfAbsent(ent.trim(), m); |
| | | } |
| | | |
| | | // å¹çï¼åªæ¸
é¤ H204 è§åçæ§å®¡æ ¸ç»æ |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : rules) ruleIds.add(rule.getId()); |
| | | if (!ruleIds.isEmpty()) { |
| | | resultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | |
| | | List<AuditResult> results = new ArrayList<>(); |
| | | for (EnergyVehicleQuarterly vehicle : vehicles) { |
| | | for (AuditRule rule : rules) { |
| | | AuditResult result = checkEnergyRule(rule, vehicle, authMap, fuelMedianMap, lastQuarterMap, h2032Map); |
| | | if (result != null) { |
| | | result.setRuleName(rule.getRuleName()); |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | result.setEnterpriseName(vehicle.getEnterpriseName()); |
| | | result.setVerifyExplanation(null); |
| | | resultMapper.insert(result); |
| | | results.add(result); |
| | | } |
| | | } |
| | | } |
| | | log.info("Energy audit complete: {} vehicles, {} issues for {}", vehicles.size(), results.size(), reportPeriod); |
| | | if (!rules.isEmpty() && !vehicles.isEmpty()) markAudited("H204", reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | private AuditResult checkEnergyRule(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, EnergyAuthVehicle> authMap, |
| | | Map<String, Double> fuelMedianMap, |
| | | Map<String, EnergyVehicleQuarterly> lastQuarterMap, |
| | | Map<String, H2032EnterpriseMonthly> h2032Map) { |
| | | try { |
| | | switch (rule.getCompareType()) { |
| | | case "CROSS_DIFF": |
| | | return checkEnergyCrossDiff(rule, vehicle, authMap); |
| | | case "EXISTENCE_AUTH": |
| | | return checkEnergyExistence(rule, vehicle, authMap); |
| | | case "LOADED_RATIO_DIFF": |
| | | return checkEnergyLoadedRatio(rule, vehicle); |
| | | case "FUEL_RANGE": |
| | | return checkEnergyFuelRange(rule, vehicle, fuelMedianMap); |
| | | case "FUEL_MOM": |
| | | return checkEnergyFuelMom(rule, vehicle, lastQuarterMap); |
| | | case "FREIGHT_VS_H2032": |
| | | return checkEnergyVsFreightReport(rule, vehicle, h2032Map); |
| | | case "AVG_DIST_VS_H2032": |
| | | return checkEnergyAvgDistVsFreight(rule, vehicle, h2032Map); |
| | | case "LOADED_TON_RATIO": |
| | | return checkEnergyLoadedTonnageRatio(rule, vehicle); |
| | | default: |
| | | return null; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Energy rule check error: {} - {}", rule.getRuleCode(), e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** è·¨è¡¨åæ®µå¯¹æ¯ï¼è½¦è¾ç±»å/çæç±»åæç 表æ¯å¯¹ï¼æ è®°å¨ä½ï¼çµå¼è½¦ç¨åçµå¼è´¨éï¼æé弿¯å¯¹ */ |
| | | private AuditResult checkEnergyCrossDiff(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, EnergyAuthVehicle> authMap) { |
| | | EnergyAuthVehicle auth = authMap.get(key(vehicle.getPlateNo())); |
| | | if (auth == null) return null; // è¿æ¿ç¼ºå¤±ç± EXISTENCE_AUTH è§åæç¤º |
| | | |
| | | String field = rule.getCheckField(); |
| | | if ("vehicleType".equals(field)) { |
| | | java.util.Set<String> allowed = ENERGY_VEHICLE_TYPE_ALLOWED.get(vehicle.getVehicleTypeCode()); |
| | | if (allowed == null || !allowed.contains(normalizeVehicleType(auth.getVehicleType()))) { |
| | | return buildEnergyResult(rule, vehicle, |
| | | "è½è=" + valueOf(vehicle.getVehicleType()) + "(ç " + valueOf(vehicle.getVehicleTypeCode()) |
| | | + "), è¿æ¿=" + valueOf(auth.getVehicleType()), |
| | | "车è¾ç±»åä¸è´", "车è¾ç±»åä¸ä¸è´"); |
| | | } |
| | | return null; |
| | | } |
| | | if ("fuelType".equals(field)) { |
| | | java.util.Set<String> allowed = ENERGY_FUEL_TYPE_ALLOWED.get(vehicle.getFuelTypeCode()); |
| | | if (allowed == null || !allowed.contains(key(auth.getFuelType()))) { |
| | | return buildEnergyResult(rule, vehicle, |
| | | "è½è=" + valueOf(vehicle.getFuelType()) + "(ç " + valueOf(vehicle.getFuelTypeCode()) |
| | | + "), è¿æ¿=" + valueOf(auth.getFuelType()), |
| | | "çæç±»åä¸è´", "çæç±»åä¸ä¸è´"); |
| | | } |
| | | return null; |
| | | } |
| | | if ("markedTonnage".equals(field)) { |
| | | double actual = nvl(vehicle.getMarkedTonnage()); |
| | | double reference = "11".equals(vehicle.getVehicleTypeCode()) |
| | | ? nvl(auth.getTractionQuality()) |
| | | : nvl(auth.getMarkedTonnage()); |
| | | double threshold = Double.parseDouble(rule.getThreshold()); |
| | | double diff = Math.abs(actual - reference); |
| | | if (diff > threshold) { |
| | | String refName = "11".equals(vehicle.getVehicleTypeCode()) ? "åçµå¼è´¨é" : "æ è®°å¨ä½"; |
| | | return buildEnergyResult(rule, vehicle, |
| | | "è½èæ è®°å¨ä½=" + fmt(actual) + ", è¿æ¿" + refName + "=" + fmt(reference), |
| | | "å·®è·â¤" + fmt(threshold), "å·®è·=" + fmt(diff)); |
| | | } |
| | | return null; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** è½èæç»è½¦çå¨è¿æ¿è½¦è¾ä¿¡æ¯ä¸ä¸å卿¶æç¤º */ |
| | | private AuditResult checkEnergyExistence(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, EnergyAuthVehicle> authMap) { |
| | | EnergyAuthVehicle auth = authMap.get(key(vehicle.getPlateNo())); |
| | | if (auth == null) { |
| | | return buildEnergyResult(rule, vehicle, |
| | | "车çå·=" + vehicle.getPlateNo(), "è¿æ¿è½¦è¾ä¿¡æ¯åå¨", "è¿æ¿è½¦è¾ä¿¡æ¯ç¼ºå¤±"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** E005 载货éç¨å æ¯ vs å¹³åè¿è·/åè¶å¹³åéç¨ ç¸å·®Â±10% */ |
| | | private AuditResult checkEnergyLoadedRatio(AuditRule rule, EnergyVehicleQuarterly vehicle) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.1); |
| | | double total = nvl(vehicle.getTotalMileage()); |
| | | double loaded = nvl(vehicle.getLoadedMileage()); |
| | | double avgDist = nvl(vehicle.getAvgDistance()); |
| | | double avgTrip = nvl(vehicle.getAvgTripMileage()); |
| | | if (total <= 0 || avgTrip <= 0 || avgDist <= 0) return null; |
| | | double r1 = loaded / total; |
| | | double r2 = avgDist / avgTrip; |
| | | if (r2 <= 0) return null; |
| | | double diff = Math.abs(r1 - r2) / r2; |
| | | if (diff <= threshold) return null; |
| | | return buildEnergyResult(rule, vehicle, |
| | | "载货éç¨/æ»è¡ç¨=" + fmt4(r1) + "ï¼å¹³åè¿è·/åè¶å¹³åéç¨=" + fmt4(r2), |
| | | "两è
ç¸å·®â¤" + fmt(threshold * 100) + "%", |
| | | "ç¸å·®=" + fmt(diff * 100) + "%"); |
| | | } |
| | | |
| | | /** E006 åçæç±»åç¾å
¬éåèå离ä¸ä½æ°è¶
éå¼ï¼é»è®¤ 50%ï¼ */ |
| | | private AuditResult checkEnergyFuelRange(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, Double> fuelMedianMap) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.5); |
| | | Double v = vehicle.getFuelPer100km(); |
| | | if (v == null || v <= 0 || vehicle.getFuelTypeCode() == null) return null; |
| | | Double median = fuelMedianMap.get(vehicle.getFuelTypeCode().trim()); |
| | | if (median == null || median <= 0) return null; |
| | | double diff = Math.abs(v - median) / median; |
| | | if (diff <= threshold) return null; |
| | | return buildEnergyResult(rule, vehicle, |
| | | "ç¾å
¬éåè=" + fmt(v) + "ï¼" + valueOf(vehicle.getFuelType()) + "ï¼ï¼åçæä¸ä½æ°=" + fmt(median), |
| | | "å离ä¸ä½æ°â¤" + fmt(threshold * 100) + "%", |
| | | "å离=" + fmt(diff * 100) + "%"); |
| | | } |
| | | |
| | | /** E007 åä¸è¾è½¦ç¾å
¬éåèç¯æ¯ååè¶
éå¼ï¼é»è®¤ ±50%ï¼ */ |
| | | private AuditResult checkEnergyFuelMom(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, EnergyVehicleQuarterly> lastQuarterMap) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.5); |
| | | Double cur = vehicle.getFuelPer100km(); |
| | | if (cur == null || cur <= 0) return null; |
| | | EnergyVehicleQuarterly last = lastQuarterMap.get(key(vehicle.getPlateNo())); |
| | | if (last == null || last.getFuelPer100km() == null || last.getFuelPer100km() <= 0) return null; |
| | | double diff = Math.abs(cur - last.getFuelPer100km()) / last.getFuelPer100km(); |
| | | if (diff <= threshold) return null; |
| | | return buildEnergyResult(rule, vehicle, |
| | | "æ¬æç¾å
¬éåè=" + fmt(cur) + "ï¼ä¸å£åº¦=" + fmt(last.getFuelPer100km()), |
| | | "ç¯æ¯ååâ¤" + fmt(threshold * 100) + "%", |
| | | "ç¯æ¯åå=" + fmt(diff * 100) + "%"); |
| | | } |
| | | |
| | | /** E008 è½èè´§è¿é/å¨è½¬éä¸ H2032 ææ¥å¯¹æ¯è¶
éå¼ï¼é»è®¤ 50%ï¼ */ |
| | | private AuditResult checkEnergyVsFreightReport(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, H2032EnterpriseMonthly> h2032Map) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.5); |
| | | String ent = vehicle.getEnterpriseName(); |
| | | if (ent == null || ent.trim().isEmpty()) return null; |
| | | H2032EnterpriseMonthly m = h2032Map.get(ent.trim()); |
| | | if (m == null) return null; |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (m.getFreightTotal() != null && m.getFreightTotal() > 0 && vehicle.getFreight() != null) { |
| | | double r = vehicle.getFreight() / m.getFreightTotal(); |
| | | if (r > threshold) { |
| | | msgs.add("è½èè´§è¿é=" + fmt(vehicle.getFreight()) + "ï¼ææ¥=" + fmt(m.getFreightTotal()) |
| | | + "ï¼å æ¯=" + fmt4(r)); |
| | | } |
| | | } |
| | | if (m.getTurnoverTotal() != null && m.getTurnoverTotal() > 0 && vehicle.getTurnover() != null) { |
| | | double r = vehicle.getTurnover() / m.getTurnoverTotal(); |
| | | if (r > threshold) { |
| | | msgs.add("è½èå¨è½¬é=" + fmt(vehicle.getTurnover()) + "ï¼ææ¥=" + fmt(m.getTurnoverTotal()) |
| | | + "ï¼å æ¯=" + fmt4(r)); |
| | | } |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildEnergyResult(rule, vehicle, String.join("ï¼", msgs), |
| | | "è½èéâ¤ææ¥" + fmt(threshold * 100) + "%", "è¶
è¿ææ¥" + fmt(threshold * 100) + "%"); |
| | | } |
| | | |
| | | /** E009 车è¾å¹³åè¿è·ä¸ H2032 ææ¥å¹³åè¿è·ç¸å·®è¶
éå¼ï¼é»è®¤ ±60%ï¼ */ |
| | | private AuditResult checkEnergyAvgDistVsFreight(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | Map<String, H2032EnterpriseMonthly> h2032Map) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.6); |
| | | String ent = vehicle.getEnterpriseName(); |
| | | if (ent == null || ent.trim().isEmpty()) return null; |
| | | H2032EnterpriseMonthly m = h2032Map.get(ent.trim()); |
| | | if (m == null || m.getAvgDistance() == null || m.getAvgDistance() <= 0) return null; |
| | | double cur = nvl(vehicle.getAvgDistance()); |
| | | if (cur <= 0) return null; |
| | | double diff = Math.abs(cur - m.getAvgDistance()) / m.getAvgDistance(); |
| | | if (diff <= threshold) return null; |
| | | return buildEnergyResult(rule, vehicle, |
| | | "è½èå¹³åè¿è·=" + fmt(cur) + "ï¼ææ¥=" + fmt(m.getAvgDistance()), |
| | | "ç¸å·®â¤" + fmt(threshold * 100) + "%", |
| | | "ç¸å·®=" + fmt(diff * 100) + "%"); |
| | | } |
| | | |
| | | /** E010 å¹³å载货å¨ä½/æ è®°å¨ä½ æ¯å¼è¶
åº [0.5, 1] */ |
| | | private AuditResult checkEnergyLoadedTonnageRatio(AuditRule rule, EnergyVehicleQuarterly vehicle) { |
| | | double lo = 0.5, hi = 1.0; |
| | | String t = rule.getThreshold(); |
| | | if (t != null && t.contains(",")) { |
| | | String[] parts = t.split(","); |
| | | try { |
| | | lo = Double.parseDouble(parts[0].trim()); |
| | | hi = Double.parseDouble(parts[1].trim()); |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | double marked = nvl(vehicle.getMarkedTonnage()); |
| | | double loaded = nvl(vehicle.getAvgLoadedTonnage()); |
| | | if (marked <= 0 || loaded <= 0) return null; |
| | | double r = loaded / marked; |
| | | if (r >= lo && r <= hi) return null; |
| | | return buildEnergyResult(rule, vehicle, |
| | | "å¹³å载货å¨ä½=" + fmt(loaded) + "ï¼æ è®°å¨ä½=" + fmt(marked) + "ï¼æ¯å¼=" + fmt4(r), |
| | | "æ¯å¼å¨[" + fmt(lo) + "," + fmt(hi) + "]", |
| | | "æ¯å¼=" + fmt4(r) + " è¶
åºèå´"); |
| | | } |
| | | |
| | | /** ä¸ä¸å£åº¦æ¥è¡¨æï¼2026-06 â 2026-03 */ |
| | | private String prevQuarter(String period) { |
| | | if (period == null || period.length() < 7) return null; |
| | | try { |
| | | int year = Integer.parseInt(period.substring(0, 4)); |
| | | int month = Integer.parseInt(period.substring(5, 7)); |
| | | int pm = month - 3; |
| | | int py = year; |
| | | if (pm <= 0) { |
| | | pm += 12; |
| | | py -= 1; |
| | | } |
| | | return String.format("%04d-%02d", py, pm); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** æ¬æåçæç±»åç¾å
¬éåèä¸ä½æ° */ |
| | | private Map<String, Double> fuelPer100kmMedian(List<EnergyVehicleQuarterly> vehicles) { |
| | | Map<String, List<Double>> byFuel = new HashMap<>(); |
| | | for (EnergyVehicleQuarterly v : vehicles) { |
| | | if (v.getFuelTypeCode() == null || v.getFuelPer100km() == null || v.getFuelPer100km() <= 0) continue; |
| | | byFuel.computeIfAbsent(v.getFuelTypeCode().trim(), k -> new ArrayList<>()).add(v.getFuelPer100km()); |
| | | } |
| | | Map<String, Double> med = new HashMap<>(); |
| | | for (Map.Entry<String, List<Double>> e : byFuel.entrySet()) { |
| | | List<Double> list = e.getValue(); |
| | | list.sort(null); |
| | | double m = list.size() % 2 == 1 ? list.get(list.size() / 2) |
| | | : (list.get(list.size() / 2 - 1) + list.get(list.size() / 2)) / 2.0; |
| | | med.put(e.getKey(), m); |
| | | } |
| | | return med; |
| | | } |
| | | |
| | | private Map<String, EnergyAuthVehicle> loadEnergyAuthMap(String reportPeriod) { |
| | | Map<String, EnergyAuthVehicle> map = new HashMap<>(); |
| | | List<EnergyAuthVehicle> list = energyAuthMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, reportPeriod)); |
| | | for (EnergyAuthVehicle v : list) { |
| | | map.putIfAbsent(key(v.getPlateNo()), v); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | /** è¿æ¿è½¦è¾ç±»å廿 éå/ä¸å/è½»å åç¼åæ¯å¯¹ */ |
| | | private String normalizeVehicleType(String type) { |
| | | if (type == null) return ""; |
| | | String t = type.trim(); |
| | | for (String prefix : new String[]{"éå", "ä¸å", "è½»å"}) { |
| | | if (t.startsWith(prefix)) { |
| | | t = t.substring(prefix.length()); |
| | | break; |
| | | } |
| | | } |
| | | return t; |
| | | } |
| | | |
| | | private String valueOf(String v) { |
| | | return v == null ? "空" : v.trim(); |
| | | } |
| | | |
| | | private AuditResult buildEnergyResult(AuditRule rule, EnergyVehicleQuarterly vehicle, |
| | | String actual, String threshold, String deviation) { |
| | | AuditResult result = new AuditResult(); |
| | | result.setRuleId(rule.getId()); |
| | | result.setReportId(vehicle.getId()); |
| | | result.setEnterpriseCode(vehicle.getEnterpriseCode()); |
| | | result.setReportPeriod(vehicle.getReportPeriod()); |
| | | result.setActualValue(actual); |
| | | result.setThresholdValue(threshold); |
| | | result.setDeviation(deviation); |
| | | result.setStatus("PENDING"); |
| | | return result; |
| | | } |
| | | |
| | | |
| | | // ========== INVEST æèµå®¡æ ¸ï¼æ±æ»å¤§è¡¨ ä¸ æèµç³»ç»å¯¼åº 跨表对æ¯ï¼ ========== |
| | | |
| | | public List<AuditResult> executeInvestmentAudit(String reportPeriod) { |
| | | List<AuditRule> rules = rulesOf("INVEST"); |
| | | List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, reportPeriod)); |
| | | Map<Long, InvestmentProject> projectMap = new HashMap<>(); |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) { |
| | | projectMap.put(p.getId(), p); |
| | | } |
| | | Map<String, List<InvestmentSystem>> sysMap = new HashMap<>(); |
| | | for (InvestmentSystem s : investSystemMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentSystem>() |
| | | .eq(InvestmentSystem::getReportPeriod, reportPeriod))) { |
| | | sysMap.computeIfAbsent(normInvestName(s.getProjectName()), k -> new ArrayList<>()).add(s); |
| | | } |
| | | |
| | | // å¹çï¼åªæ¸
é¤ INVEST è§åçæ§å®¡æ ¸ç»æ |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : rules) ruleIds.add(rule.getId()); |
| | | if (!ruleIds.isEmpty()) { |
| | | resultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | |
| | | List<AuditResult> results = new ArrayList<>(); |
| | | for (InvestmentMonthly monthly : monthlies) { |
| | | InvestmentProject project = projectMap.get(monthly.getProjectId()); |
| | | String projectName = project == null ? "æªç¥é¡¹ç®" : project.getProjectName(); |
| | | InvestmentSystem sys = findInvestSystem(sysMap, projectName); |
| | | for (AuditRule rule : rules) { |
| | | AuditResult result = checkInvestRule(rule, monthly, projectName, sys); |
| | | if (result != null) { |
| | | result.setRuleName(rule.getRuleName()); |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | result.setEnterpriseName(projectName); |
| | | result.setVerifyExplanation(null); |
| | | resultMapper.insert(result); |
| | | results.add(result); |
| | | } |
| | | } |
| | | } |
| | | log.info("Investment audit complete: {} projects, {} issues for {}", monthlies.size(), results.size(), reportPeriod); |
| | | if (!rules.isEmpty() && !monthlies.isEmpty()) markAudited("INVEST", reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | private AuditResult checkInvestRule(AuditRule rule, InvestmentMonthly monthly, |
| | | String projectName, InvestmentSystem sys) { |
| | | try { |
| | | switch (rule.getCompareType()) { |
| | | case "CROSS_DIFF": |
| | | return checkInvestCrossDiff(rule, monthly, projectName, sys); |
| | | case "EXISTENCE_SYS": |
| | | return checkInvestExistence(rule, monthly, projectName, sys); |
| | | default: |
| | | return null; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Investment rule check error: {} - {}", rule.getRuleCode(), e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** 䏿èµç³»ç»å¯¼åºæ°å¼å¯¹æ¯ï¼é弿 ¼å¼ "ç»å¯¹å·®|ç¸å¯¹å·®%" */ |
| | | private AuditResult checkInvestCrossDiff(AuditRule rule, InvestmentMonthly monthly, |
| | | String projectName, InvestmentSystem sys) { |
| | | if (sys == null) return null; // ç¼ºå¤±ç± EXISTENCE_SYS è§åæç¤º |
| | | String field = rule.getCheckField(); |
| | | double actual; |
| | | double reference; |
| | | String fieldName; |
| | | if ("startCum".equals(field)) { |
| | | actual = nvl(monthly.getStartCum()); |
| | | reference = nvl(sys.getStartCum()); |
| | | fieldName = "èªå¼å§å»ºè®¾ç´¯è®¡"; |
| | | } else if ("yearCum".equals(field)) { |
| | | actual = nvl(monthly.getYearCum()); |
| | | reference = nvl(sys.getYearCum()); |
| | | fieldName = "èªå¹´å累计"; |
| | | } else if ("monthDone".equals(field)) { |
| | | actual = nvl(monthly.getMonthDone()); |
| | | reference = nvl(sys.getMonthDone()); |
| | | fieldName = "æ¬æå®æ"; |
| | | } else { |
| | | return null; |
| | | } |
| | | String[] parts = rule.getThreshold().split("\\|"); |
| | | double absThreshold = parts.length > 0 ? parseDoubleSafe(parts[0], 50) : 50; |
| | | double pctThreshold = parts.length > 1 ? parseDoubleSafe(parts[1], 5) : 5; |
| | | double diff = Math.abs(actual - reference); |
| | | double base = Math.max(Math.abs(reference), 1.0); |
| | | double pct = diff / base * 100; |
| | | boolean hit = diff > absThreshold && pct > pctThreshold; |
| | | if (!hit && reference == 0 && actual > absThreshold) { |
| | | hit = true; |
| | | } |
| | | if (hit) { |
| | | return buildInvestResult(rule, monthly, |
| | | "æ±æ»å¤§è¡¨" + fieldName + "=" + fmt(actual) + ", æèµç³»ç»=" + fmt(reference), |
| | | "ç»å¯¹å·®â¤" + fmt(absThreshold) + "ä¸ ä¸ ç¸å¯¹å·®â¤" + fmt(pctThreshold) + "%", |
| | | "ç»å¯¹å·®=" + fmt(diff) + "ä¸, ç¸å¯¹å·®=" + fmt(Math.round(pct * 10) / 10.0) + "%"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** æ±æ»å¤§è¡¨é¡¹ç®å¨æèµç³»ç»å¯¼åºä¸ä¸åå¨ */ |
| | | private AuditResult checkInvestExistence(AuditRule rule, InvestmentMonthly monthly, |
| | | String projectName, InvestmentSystem sys) { |
| | | if (sys == null) { |
| | | return buildInvestResult(rule, monthly, |
| | | "项ç®åç§°=" + projectName, "æèµç³»ç»å¯¼åºä¸åå¨", "æèµç³»ç»å¯¼åºä¸ä¸åå¨"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private AuditResult buildInvestResult(AuditRule rule, InvestmentMonthly monthly, |
| | | String actual, String threshold, String deviation) { |
| | | AuditResult result = new AuditResult(); |
| | | result.setRuleId(rule.getId()); |
| | | result.setReportId(monthly.getId()); |
| | | result.setReportPeriod(monthly.getReportPeriod()); |
| | | result.setActualValue(actual); |
| | | result.setThresholdValue(threshold); |
| | | result.setDeviation(deviation); |
| | | result.setStatus("PENDING"); |
| | | return result; |
| | | } |
| | | |
| | | // ========== CITY_BUS åå¸å
¬äº¤å®¡æ ¸ï¼4 ç»è§åï¼ä¼ä¸çº§ï¼ ========== |
| | | |
| | | public List<AuditResult> executeCityBusAudit(String reportPeriod) { |
| | | List<AuditRule> rules = rulesOf("CITY_BUS"); |
| | | List<CityBusMonthly> rows = cityBusMapper.selectList( |
| | | new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, reportPeriod)); |
| | | |
| | | // å¹çï¼åªæ¸
é¤ CITY_BUS è§åçæ§å®¡æ ¸ç»æ |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : rules) ruleIds.add(rule.getId()); |
| | | if (!ruleIds.isEmpty()) { |
| | | resultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | |
| | | List<AuditResult> results = new ArrayList<>(); |
| | | for (CityBusMonthly row : rows) { |
| | | // æ å
¬äº¤ç»è¥æ´»å¨ä¼ä¸ï¼è¿è¥è½¦æ°/客è¿é/å¨è½¬éå 0ï¼è·³è¿ |
| | | if (nvl(row.getOpVehicles()) <= 0 && nvl(row.getPassengerVolume()) <= 0 |
| | | && nvl(row.getTurnover()) <= 0) { |
| | | continue; |
| | | } |
| | | for (AuditRule rule : rules) { |
| | | AuditResult result = checkCityBusRule(rule, row); |
| | | if (result != null) { |
| | | result.setRuleName(rule.getRuleName()); |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | result.setEnterpriseName(row.getEnterpriseName()); |
| | | result.setVerifyExplanation(row.getVerifyExplanation()); |
| | | resultMapper.insert(result); |
| | | results.add(result); |
| | | } |
| | | } |
| | | } |
| | | log.info("CityBus audit complete: {} enterprises, {} issues for {}", rows.size(), results.size(), reportPeriod); |
| | | if (!rules.isEmpty() && !rows.isEmpty()) markAudited("CITY_BUS", reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | private AuditResult checkCityBusRule(AuditRule rule, CityBusMonthly row) { |
| | | try { |
| | | switch (rule.getCompareType()) { |
| | | case "TREND_CONSISTENCY": |
| | | return checkCityBusTrend(rule, row); |
| | | case "RATIO_CHANGE": |
| | | return checkCityBusRatioChange(rule, row); |
| | | case "AVG_DIST_CHANGE": |
| | | return checkCityBusAvgDistChange(rule, row); |
| | | default: |
| | | return null; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("CityBus rule check error: {} - {}", rule.getRuleCode(), e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** è¶å¿ä¸è´ï¼check_field 䏿坹忮µï¼ç¯æ¯/忝ï¼è¶å¿ä¸ä¸è´ä¸å¢éè¶
éå¼éæ ¸å® */ |
| | | private AuditResult checkCityBusTrend(AuditRule rule, CityBusMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.02); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("passengerCityMom")) { |
| | | checkTrendPair(msgs, "åå¸å
客è¿éç¯æ¯", row.getRatePassengerCityMom(), |
| | | "åé
å乡客è¿éç¯æ¯", row.getRateChengxiangMom(), threshold); |
| | | } |
| | | if (field.contains("passengerCityYoy")) { |
| | | checkTrendPair(msgs, "åå¸å
客è¿é忝", row.getRatePassengerCityYoy(), |
| | | "åé
å乡客è¿é忝", row.getRateChengxiangYoy(), threshold); |
| | | } |
| | | if (field.contains("passengerMom")) { |
| | | checkTrendPair(msgs, "客è¿éç¯æ¯", row.getRatePassengerMom(), |
| | | "æ
客å¨è½¬éç¯æ¯", row.getRateTurnoverMom(), threshold); |
| | | } |
| | | if (field.contains("passengerYoy")) { |
| | | checkTrendPair(msgs, "客è¿é忝", row.getRatePassengerYoy(), |
| | | "æ
客å¨è½¬é忝", row.getRateTurnoverYoy(), threshold); |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildCityBusResult(rule, row, String.join("ï¼", msgs), |
| | | "ä¸¤ææ ç¯æ¯/忝è¶å¿ä¸è´", "è¶å¿ä¸ä¸è´ä¸å¢éè¶
éå¼"); |
| | | } |
| | | |
| | | private void checkTrendPair(List<String> msgs, String aName, Double aVal, |
| | | String bName, Double bVal, double threshold) { |
| | | double a = nvl(aVal); |
| | | double b = nvl(bVal); |
| | | boolean aUp = a > 0, bUp = b > 0; |
| | | boolean aDown = a < 0, bDown = b < 0; |
| | | if ((aUp && bDown) || (aDown && bUp)) { |
| | | double maxAbs = Math.max(Math.abs(a), Math.abs(b)); |
| | | if (maxAbs > threshold) { |
| | | msgs.add(aName + "=" + fmt(a) + ", " + bName + "=" + fmt(b) + "ï¼è¶å¿ä¸ä¸è´ï¼"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** å æ¯ååï¼æ¬æå æ¯ vs ç±ç¯æ¯å¢é忍çä¸æå æ¯ï¼åå>1% éæ ¸å® */ |
| | | private AuditResult checkCityBusRatioChange(AuditRule rule, CityBusMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.01); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("passengerCityRatio")) { |
| | | Double change = cityBusRatioChange(row.getPassengerCityRatio(), |
| | | row.getPassengerCity(), row.getRatePassengerCityMom(), |
| | | row.getPassengerVolume(), row.getRatePassengerMom()); |
| | | if (change != null && change > threshold) { |
| | | msgs.add("åå¸å
客è¿éå æ¯åå=" + fmt4(change) + "ï¼>1%ï¼"); |
| | | } |
| | | } |
| | | if (field.contains("chengxiangRatio")) { |
| | | Double change = cityBusRatioChange(row.getChengxiangRatio(), |
| | | row.getPassengerChengxiang(), row.getRateChengxiangMom(), |
| | | row.getPassengerVolume(), row.getRatePassengerMom()); |
| | | if (change != null && change > threshold) { |
| | | msgs.add("åé
å乡客è¿éå æ¯åå=" + fmt4(change) + "ï¼>1%ï¼"); |
| | | } |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildCityBusResult(rule, row, String.join("ï¼", msgs), |
| | | "å æ¯ååâ¤1%", "å æ¯åå>1%"); |
| | | } |
| | | |
| | | /** å æ¯ç¯æ¯ååï¼æ¬æå æ¯ vs ä¸æå æ¯ï¼ç±ç¯æ¯å¢é忍ï¼ï¼æ æ³æ¨ç®è¿å null */ |
| | | private Double cityBusRatioChange(Double curRatio, Double curValue, Double curRate, |
| | | Double curTotal, Double totalRate) { |
| | | if (curRatio == null) return null; |
| | | double r = nvl(curRate); |
| | | double tr = nvl(totalRate); |
| | | if (r <= -1.0 || tr <= -1.0) return null; |
| | | double lastRatio = (nvl(curValue) / (1 + r)) / (nvl(curTotal) / (1 + tr)); |
| | | if (Double.isNaN(lastRatio) || Double.isInfinite(lastRatio)) return null; |
| | | return Math.abs(curRatio - lastRatio); |
| | | } |
| | | |
| | | /** å¹³åè¿è·ååï¼ç¯æ¯å¢åï¼å
¬éï¼æåé
åä¹¡ç± å¨è½¬é/客è¿é ç¯æ¯åæ¨ï¼è¶
è¿ 3 å
¬ééæ ¸å® */ |
| | | private AuditResult checkCityBusAvgDistChange(AuditRule rule, CityBusMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 3.0); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("avgDistanceMomChange")) { |
| | | double chg = Math.abs(nvl(row.getAvgDistanceMomChange())); |
| | | if (chg > threshold) { |
| | | msgs.add("å¹³åè¿è·ç¯æ¯å¢å=" + fmt(nvl(row.getAvgDistanceMomChange())) + "å
Ž"); |
| | | } |
| | | } |
| | | if (field.contains("avgDistanceCityMomChange")) { |
| | | double chg = Math.abs(nvl(row.getAvgDistanceCityMomChange())); |
| | | if (chg > threshold) { |
| | | msgs.add("åå¸å
å¹³åè¿è·ç¯æ¯å¢å=" + fmt(nvl(row.getAvgDistanceCityMomChange())) + "å
Ž"); |
| | | } |
| | | } |
| | | if (field.contains("avgDistanceChengxiang")) { |
| | | double cur = nvl(row.getAvgDistanceChengxiang()); |
| | | double km = nvl(row.getRateChengxiangMom()); |
| | | double kt = nvl(row.getRateChengxiangTurnoverMom()); |
| | | if (cur > 0 && km > -1.0 && kt > -1.0) { |
| | | double last = cur * (1 + km) / (1 + kt); |
| | | double chg = Math.abs(cur - last); |
| | | if (chg > threshold) { |
| | | msgs.add("åé
å乡平åè¿è·åå=" + fmt(chg) + "å
¬éï¼æ¬æ" + fmt(cur) |
| | | + " vs 䏿" + fmt(last) + "ï¼"); |
| | | } |
| | | } |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildCityBusResult(rule, row, String.join("ï¼", msgs), |
| | | "å¹³åè¿è·ååâ¤" + fmt(threshold) + "å
¬é", "å¹³åè¿è·åå>" + fmt(threshold) + "å
Ž"); |
| | | } |
| | | |
| | | private AuditResult buildCityBusResult(AuditRule rule, CityBusMonthly row, |
| | | String actual, String threshold, String deviation) { |
| | | AuditResult result = new AuditResult(); |
| | | result.setRuleId(rule.getId()); |
| | | result.setReportId(row.getId()); |
| | | result.setReportPeriod(row.getReportPeriod()); |
| | | result.setActualValue(actual); |
| | | result.setThresholdValue(threshold); |
| | | result.setDeviation(deviation); |
| | | result.setStatus("PENDING"); |
| | | return result; |
| | | } |
| | | |
| | | // ========== CITY_TAXI 巡游åºç§å®¡æ ¸ï¼7 ç»è§åï¼å¸å·çº§ï¼ ========== |
| | | |
| | | public List<AuditResult> executeCityTaxiAudit(String reportPeriod) { |
| | | List<AuditRule> rules = rulesOf("CITY_TAXI"); |
| | | List<CityTaxiMonthly> rows = cityTaxiMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, reportPeriod)); |
| | | |
| | | // å¹çï¼åªæ¸
é¤ CITY_TAXI è§åçæ§å®¡æ ¸ç»æ |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : rules) ruleIds.add(rule.getId()); |
| | | if (!ruleIds.isEmpty()) { |
| | | resultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | |
| | | // è¿æ¿è½¦è¾æå¸å·è®¡æ°ï¼T001 对æ¯åºåï¼ |
| | | Map<String, Long> authCount = loadTaxiAuthCount(reportPeriod); |
| | | |
| | | List<AuditResult> results = new ArrayList<>(); |
| | | for (CityTaxiMonthly row : rows) { |
| | | if (row.getCity() == null || row.getCity().trim().isEmpty()) continue; |
| | | // æ ç»è¥æ´»å¨å¸å·ï¼è¿è¥è½¦æ°/客è¿é/å¨è½¬éå 0ï¼è·³è¿ |
| | | if (nvl(row.getOpVehicles()) <= 0 && nvl(row.getPassengerVolume()) <= 0 |
| | | && nvl(row.getTurnover()) <= 0) { |
| | | continue; |
| | | } |
| | | for (AuditRule rule : rules) { |
| | | AuditResult result = checkCityTaxiRule(rule, row, authCount); |
| | | if (result != null) { |
| | | result.setRuleName(rule.getRuleName()); |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | result.setEnterpriseName(row.getCity()); |
| | | result.setVerifyExplanation(row.getVerifyExplanation()); |
| | | resultMapper.insert(result); |
| | | results.add(result); |
| | | } |
| | | } |
| | | } |
| | | log.info("CityTaxi audit complete: {} cities, {} issues for {}", rows.size(), results.size(), reportPeriod); |
| | | if (!rules.isEmpty() && !rows.isEmpty()) markAudited("CITY_TAXI", reportPeriod); |
| | | return results; |
| | | } |
| | | |
| | | private Map<String, Long> loadTaxiAuthCount(String reportPeriod) { |
| | | Map<String, Long> map = new HashMap<>(); |
| | | List<CityTaxiAuth> auths = cityTaxiAuthMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiAuth>() |
| | | .eq(CityTaxiAuth::getReportPeriod, reportPeriod)); |
| | | for (CityTaxiAuth a : auths) { |
| | | if (a.getCity() == null || a.getCity().trim().isEmpty()) continue; |
| | | map.merge(a.getCity(), 1L, Long::sum); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | private AuditResult checkCityTaxiRule(AuditRule rule, CityTaxiMonthly row, Map<String, Long> authCount) { |
| | | try { |
| | | switch (rule.getCompareType()) { |
| | | case "CROSS_DIFF_PCT": |
| | | return checkTaxiAuthDiff(rule, row, authCount); |
| | | case "TREND_CONSISTENCY": |
| | | return checkCityTaxiTrend(rule, row); |
| | | case "RATIO_CHANGE": |
| | | return checkCityTaxiRatioChange(rule, row); |
| | | case "AVG_DIST_CHANGE": |
| | | return checkCityTaxiAvgDistChange(rule, row); |
| | | case "PAX_PER_TRIP_CHANGE": |
| | | return checkTaxiPaxPerTripChange(rule, row); |
| | | default: |
| | | return null; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("CityTaxi rule check error: {} - {}", rule.getRuleCode(), e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** T001 è¿è¥è½¦æ° vs è¿æ¿å¨è¥è½¦è¾æ°ï¼å·®å¼ç>3% éæ ¸å®ï¼REDï¼ */ |
| | | private AuditResult checkTaxiAuthDiff(AuditRule rule, CityTaxiMonthly row, Map<String, Long> authCount) { |
| | | // æ´ææªå¯¼å
¥è¿æ¿æ°æ®æ¶è·³è¿å¯¹æ¯ï¼é¿å
æâè¿æ¿=0â误å½åºåæ¥å·®å¼ï¼ |
| | | if (authCount == null || authCount.isEmpty()) return null; |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 3.0); |
| | | double monthly = nvl(row.getOpVehicles()); |
| | | Long auth = authCount.get(row.getCity()); |
| | | long authV = auth == null ? 0 : auth; |
| | | if (monthly <= 0 && authV <= 0) return null; |
| | | double diffPct = authV <= 0 ? 100.0 : Math.abs(monthly - authV) / authV * 100.0; |
| | | if (diffPct <= threshold) return null; |
| | | return buildTaxiResult(rule, row, |
| | | "ææ¥è¿è¥è½¦è¾æ°=" + fmt(monthly) + "ï¼è¿æ¿å¨è¥è½¦è¾æ°=" + authV + "ï¼å·®å¼ç=" + fmt(diffPct) + "%", |
| | | "å·®å¼çâ¤" + fmt(threshold) + "%", |
| | | "å·®å¼ç=" + fmt(diffPct) + "%>" + fmt(threshold) + "%"); |
| | | } |
| | | |
| | | /** T002/T003/T007 è¶å¿ä¸è´ï¼check_field 䏿坹忮µï¼ç¯æ¯/忝ï¼è¶å¿ä¸ä¸è´ä¸å¢éè¶
éå¼éæ ¸å® */ |
| | | private AuditResult checkCityTaxiTrend(AuditRule rule, CityTaxiMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.02); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("passengerCityMom")) { |
| | | checkTrendPair(msgs, "åå¸å
客è¿éç¯æ¯", row.getRatePassengerCityMom(), |
| | | "åé
å乡客è¿éç¯æ¯", row.getRatePassengerChengxiangMom(), threshold); |
| | | } |
| | | if (field.contains("turnoverMomDerived")) { |
| | | // ææ¥æ ç´æ¥å¨è½¬éç¯æ¯åï¼å¨è½¬é=客è¿éÃå¹³åè¿è·ï¼ç±ä¸¤è
ç¯æ¯æ¨å¯¼ |
| | | Double rateTurnover = taxiTurnoverMomDerived(row); |
| | | if (rateTurnover != null) { |
| | | checkTrendPair(msgs, "客è¿éç¯æ¯", row.getRatePassengerMom(), |
| | | "æ
客å¨è½¬éç¯æ¯(æ¨å¯¼)", rateTurnover, threshold); |
| | | } |
| | | } |
| | | if (field.contains("tripMom")) { |
| | | checkTrendPair(msgs, "è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯", row.getRateTripMom(), |
| | | "客è¿éç¯æ¯", row.getRatePassengerMom(), threshold); |
| | | } |
| | | if (field.contains("tripYoy")) { |
| | | checkTrendPair(msgs, "è½½å®¢è½¦æ¬¡åæ¯", row.getRateTripYoy(), |
| | | "客è¿é忝", row.getRatePassengerYoy(), threshold); |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildTaxiResult(rule, row, String.join("ï¼", msgs), |
| | | "ä¸¤ææ ç¯æ¯/忝è¶å¿ä¸è´", "è¶å¿ä¸ä¸è´ä¸å¢éè¶
éå¼"); |
| | | } |
| | | |
| | | /** æ¨å¯¼å¨è½¬éç¯æ¯ï¼rateâ(1+客è¿éç¯æ¯)Ã(1+å¹³åè¿è·ç¯æ¯)-1ï¼å¹³åè¿è·ç¯æ¯ç±ä¸æè¿è·=æ¬æâå¢åæ¨ç® */ |
| | | private Double taxiTurnoverMomDerived(CityTaxiMonthly row) { |
| | | double dist = nvl(row.getAvgDistance()); |
| | | double chg = nvl(row.getAvgDistanceMomChange()); |
| | | double rp = nvl(row.getRatePassengerMom()); |
| | | if (dist <= 0 || dist - chg <= 0 || rp <= -1.0) return null; |
| | | double rd = chg / (dist - chg); |
| | | return (1 + rp) * (1 + rd) - 1; |
| | | } |
| | | |
| | | /** T004 å æ¯ååï¼åå¸å
客è¿éå æ¯åå>1% éæ ¸å® */ |
| | | private AuditResult checkCityTaxiRatioChange(AuditRule rule, CityTaxiMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.01); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("passengerCityRatio")) { |
| | | Double change = cityBusRatioChange(row.getPassengerCityRatio(), |
| | | row.getPassengerCity(), row.getRatePassengerCityMom(), |
| | | row.getPassengerVolume(), row.getRatePassengerMom()); |
| | | if (change != null && change > threshold) { |
| | | msgs.add("åå¸å
客è¿éå æ¯åå=" + fmt4(change) + "ï¼>1%ï¼"); |
| | | } |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildTaxiResult(rule, row, String.join("ï¼", msgs), |
| | | "å æ¯ååâ¤1%", "å æ¯åå>1%"); |
| | | } |
| | | |
| | | /** T005 å¹³åè¿è·ååï¼ç¯æ¯å¢åï¼å
¬éï¼æåé
å乡ç±å¹³åè¿è·å¢é忍ï¼è¶
è¿ 3 å
¬ééæ ¸å® */ |
| | | private AuditResult checkCityTaxiAvgDistChange(AuditRule rule, CityTaxiMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 3.0); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("avgDistanceMomChange")) { |
| | | double chg = Math.abs(nvl(row.getAvgDistanceMomChange())); |
| | | if (chg > threshold) { |
| | | msgs.add("å¹³åè¿è·ç¯æ¯å¢å=" + fmt(nvl(row.getAvgDistanceMomChange())) + "å
Ž"); |
| | | } |
| | | } |
| | | if (field.contains("avgDistanceCityMomChange")) { |
| | | double chg = Math.abs(nvl(row.getAvgDistanceCityMomChange())); |
| | | if (chg > threshold) { |
| | | msgs.add("åå¸å
å¹³åè¿è·ç¯æ¯å¢å=" + fmt(nvl(row.getAvgDistanceCityMomChange())) + "å
Ž"); |
| | | } |
| | | } |
| | | if (field.contains("avgDistanceChengxiang")) { |
| | | double cur = nvl(row.getAvgDistanceChengxiang()); |
| | | double dist = nvl(row.getAvgDistance()); |
| | | double chg = nvl(row.getAvgDistanceMomChange()); |
| | | if (cur > 0 && dist > 0 && dist > chg) { |
| | | double rd = chg / (dist - chg); |
| | | double last = cur / (1 + rd); |
| | | double diff = Math.abs(cur - last); |
| | | if (diff > threshold) { |
| | | msgs.add("åé
å乡平åè¿è·åå=" + fmt(diff) + "å
¬éï¼æ¬æ" + fmt(cur) |
| | | + " vs 䏿" + fmt(last) + "ï¼"); |
| | | } |
| | | } |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildTaxiResult(rule, row, String.join("ï¼", msgs), |
| | | "å¹³åè¿è·ååâ¤" + fmt(threshold) + "å
¬é", "å¹³åè¿è·åå>" + fmt(threshold) + "å
Ž"); |
| | | } |
| | | |
| | | /** T006 å车次载客人æ°ååï¼ç±ä¸æ(载客车次/客è¿éç¯æ¯)忍ï¼åå>0.2人 éæ ¸å® */ |
| | | private AuditResult checkTaxiPaxPerTripChange(AuditRule rule, CityTaxiMonthly row) { |
| | | double threshold = parseDoubleSafe(rule.getThreshold(), 0.2); |
| | | String field = rule.getCheckField() == null ? "" : rule.getCheckField(); |
| | | List<String> msgs = new ArrayList<>(); |
| | | if (field.contains("passengersPerTrip")) { |
| | | double cur = nvl(row.getPassengersPerTrip()); |
| | | double rp = nvl(row.getRatePassengerMom()); |
| | | double rt = nvl(row.getRateTripMom()); |
| | | if (cur > 0 && rp > -1.0 && rt > -1.0) { |
| | | double last = cur * (1 + rt) / (1 + rp); |
| | | double diff = Math.abs(cur - last); |
| | | if (diff > threshold) { |
| | | msgs.add("å车次载客人æ°åå=" + fmt(diff) + "äººï¼æ¬æ" + fmt(cur) |
| | | + " vs 䏿" + fmt(last) + "ï¼"); |
| | | } |
| | | } |
| | | } |
| | | if (msgs.isEmpty()) return null; |
| | | return buildTaxiResult(rule, row, String.join("ï¼", msgs), |
| | | "å车次载客人æ°ååâ¤" + fmt(threshold) + "人", "å车次载客人æ°åå>" + fmt(threshold) + "人"); |
| | | } |
| | | |
| | | private AuditResult buildTaxiResult(AuditRule rule, CityTaxiMonthly row, |
| | | String actual, String threshold, String deviation) { |
| | | AuditResult result = new AuditResult(); |
| | | result.setRuleId(rule.getId()); |
| | | result.setReportId(row.getId()); |
| | | result.setReportPeriod(row.getReportPeriod()); |
| | | result.setActualValue(actual); |
| | | result.setThresholdValue(threshold); |
| | | result.setDeviation(deviation); |
| | | result.setStatus("PENDING"); |
| | | return result; |
| | | } |
| | | |
| | | /** 项ç®åå½ä¸åï¼å»æ¬å·å符ãè¿æ¥ç¬¦ï¼ç½é©¬æ°åç»ä¸ï¼å»ç/å¸/å¿/åºåç¼ä¸ç©ºç½ */ |
| | | private String normInvestName(String name) { |
| | | if (name == null) return ""; |
| | | String s = name.trim(); |
| | | s = s.replaceAll("[ï¼(]", "").replaceAll("[ï¼)]", ""); |
| | | s = s.replaceAll("[·â¢âï¼_\\-]", ""); |
| | | s = s.replaceAll("[â
â
¡â
¢â
£â
¤â
¥â
¦â
§â
¨â
©â
°â
±â
²â
³â
´â
µâ
¶â
·â
¸â
¹]", "I"); |
| | | s = s.replaceAll("^(æ¹åç|æ¦æ±å¸|é»ç³å¸|åå °å¸|宿å¸|è¥é³å¸|éå·å¸|èé¨å¸|åæå¸|èå·å¸|é»åå¸|å¸å®å¸|éå·å¸|æ©æ½å·|仿¡å¸|æ½æ±å¸|天é¨å¸|ç¥åæ¶æåº)", ""); |
| | | s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:å¿|å¸|åº))", ""); |
| | | s = s.replaceAll("\\s+", ""); |
| | | return s; |
| | | } |
| | | |
| | | /** åç§°å½ä¸åå精确å¹é
ï¼ååå¤åéæå¿åºæ¶æ§ï¼ï¼å¤±è´¥åæè¯åååç¼/å
å«/ååºå模ç³å¹é
*/ |
| | | private InvestmentSystem findInvestSystem(Map<String, List<InvestmentSystem>> sysMap, String projectName) { |
| | | String norm = normInvestName(projectName); |
| | | if (norm.isEmpty()) return null; |
| | | List<InvestmentSystem> exact = sysMap.get(norm); |
| | | if (exact != null && !exact.isEmpty()) { |
| | | return disambiguateInvest(exact, projectName); |
| | | } |
| | | InvestmentSystem best = null; |
| | | int bestScore = Integer.MIN_VALUE; |
| | | for (Map.Entry<String, List<InvestmentSystem>> e : sysMap.entrySet()) { |
| | | String key = e.getKey(); |
| | | if (key.length() < 5 || norm.length() < 5) continue; |
| | | String shortSide = key.length() <= norm.length() ? key : norm; |
| | | String longSide = key.length() > norm.length() ? key : norm; |
| | | int idx = longSide.indexOf(shortSide); |
| | | int lenDiff = Math.abs(key.length() - norm.length()); |
| | | int score; |
| | | if (idx == 0) { |
| | | // åç¼å¼å
å«ï¼çå=é¿åå¼å¤´ï¼æå¯ä¿¡ |
| | | score = 1000 - lenDiff * 2; |
| | | } else if (idx > 0 && shortSide.length() >= 6) { |
| | | // ä¸åé¨å
å«ï¼çåè¶³å¤é¿æå¯ä¿¡ |
| | | score = 500 - idx * 5 - lenDiff * 2 + shortSide.length(); |
| | | } else if (idx > 0 && shortSide.length() == 5 && lenDiff <= 5) { |
| | | // 5åçåä¸åé¨ï¼å·®å¼é¨åä¸å« å¿/å¸/å· æå¹é
ï¼æé¤ ç½ç°å¿ç»¼åç©æµå è¿ç±»ï¼ |
| | | String diff = longSide.substring(0, idx) + longSide.substring(idx + shortSide.length()); |
| | | if (diff.matches(".*(å¿|å¸|å·).*")) continue; |
| | | score = 400 - idx * 5 - lenDiff; |
| | | } else if (isInvestSubsequence(shortSide, longSide) && lenDiff <= 8) { |
| | | // ååºåå
åºï¼çå为é¿å廿æå
¥è¯åçååºåï¼å¦ ç»¼åæ¢çº½ vs 综åå®¢è¿æ¢çº½ãç©æµä¸å¿ vs ç©æµåä¸å¿ï¼ |
| | | score = 300 - lenDiff * 3; |
| | | } else { |
| | | continue; |
| | | } |
| | | if (score > bestScore) { |
| | | bestScore = score; |
| | | best = disambiguateInvest(e.getValue(), projectName); |
| | | } |
| | | } |
| | | return best; |
| | | } |
| | | |
| | | /** å¤ä¸ªç³»ç»é¡¹ç®å½ä¸ååååï¼å¦ å¸ä¸°å¿ç»¼åç©æµå/ç½ç°å¿ç»¼åç©æµå â 综åç©æµåï¼ï¼æå¿åºåç¼åºå */ |
| | | private InvestmentSystem disambiguateInvest(List<InvestmentSystem> candidates, String projectName) { |
| | | if (candidates == null || candidates.isEmpty()) return null; |
| | | if (candidates.size() == 1) return candidates.get(0); |
| | | String county = extractCounty(projectName); |
| | | if (!county.isEmpty()) { |
| | | for (InvestmentSystem c : candidates) { |
| | | String raw = c.getProjectName() == null ? "" : c.getProjectName(); |
| | | if (raw.contains(county)) return c; |
| | | } |
| | | } |
| | | return candidates.get(0); |
| | | } |
| | | |
| | | /** æååç§°ä¸ç å¿/å¸/åº åç¼ï¼å¦ å¸ä¸°å¿ï¼ */ |
| | | private String extractCounty(String name) { |
| | | if (name == null) return ""; |
| | | java.util.regex.Matcher m = java.util.regex.Pattern.compile("([\\u4e00-\\u9fa5]{2,4}(?:å¿|å¸|åº))").matcher(name); |
| | | if (m.find()) return m.group(1); |
| | | return ""; |
| | | } |
| | | |
| | | /** 夿ç忝å¦ä¸ºé¿åçå符ååºåï¼æé¡ºåºåºç°å³å¯ï¼å
许ä¸é´æå
¥è¯ï¼ */ |
| | | private boolean isInvestSubsequence(String shortSide, String longSide) { |
| | | int i = 0; |
| | | for (int j = 0; i < shortSide.length() && j < longSide.length(); j++) { |
| | | if (shortSide.charAt(i) == longSide.charAt(j)) i++; |
| | | } |
| | | return i == shortSide.length(); |
| | | } |
| | | |
| | | private double parseDoubleSafe(String v, double def) { |
| | | try { |
| | | return Double.parseDouble(v.trim()); |
| | | } catch (Exception e) { |
| | | return def; |
| | | } |
| | | } |
| | | |
| | | public List<AuditResult> queryResults(String reportPeriod, String reportType) { |
| | | String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim(); |
| | | List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod) |
| | | .orderByAsc(AuditResult::getId)); |
| | | attachInfo(results); |
| | | return results; |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule rule : ruleMapper.selectList(null)) { |
| | | if (type.equals(rule.getReportType())) ruleIds.add(rule.getId()); |
| | | } |
| | | List<AuditResult> filtered = new ArrayList<>(); |
| | | for (AuditResult result : results) { |
| | | if (ruleIds.contains(result.getRuleId())) filtered.add(result); |
| | | } |
| | | attachInfo(filtered); |
| | | return filtered; |
| | | } |
| | | |
| | | public void reviewResult(Long id, String status, String comment) { |
| | |
| | | if (results.isEmpty()) return; |
| | | Map<Long, AuditRule> ruleMap = new HashMap<>(); |
| | | for (AuditRule rule : ruleMapper.selectList(null)) ruleMap.put(rule.getId(), rule); |
| | | Map<Long, String> nameMap = new HashMap<>(); |
| | | Map<Long, String> explainMap = new HashMap<>(); |
| | | Map<Long, String> h2032Name = new HashMap<>(); |
| | | Map<Long, String> h2032Explain = new HashMap<>(); |
| | | Map<Long, String> passengerName = new HashMap<>(); |
| | | Map<Long, String> passengerExplain = new HashMap<>(); |
| | | Map<Long, String> energyName = new HashMap<>(); |
| | | Map<Long, String> investName = new HashMap<>(); |
| | | Map<Long, String> busName = new HashMap<>(); |
| | | Map<Long, String> busExplain = new HashMap<>(); |
| | | Map<Long, String> taxiName = new HashMap<>(); |
| | | Map<Long, String> taxiExplain = new HashMap<>(); |
| | | for (H2032EnterpriseMonthly r : h2032Mapper.selectList(null)) { |
| | | nameMap.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | explainMap.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | h2032Name.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | h2032Explain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | for (PassengerEnterpriseMonthly r : passengerMapper.selectList(null)) { |
| | | passengerName.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | passengerExplain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | for (EnergyVehicleQuarterly r : energyMapper.selectList(null)) { |
| | | energyName.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | } |
| | | for (CityBusMonthly r : cityBusMapper.selectList(null)) { |
| | | busName.putIfAbsent(r.getId(), r.getEnterpriseName()); |
| | | busExplain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | for (CityTaxiMonthly r : cityTaxiMapper.selectList(null)) { |
| | | taxiName.putIfAbsent(r.getId(), r.getCity()); |
| | | taxiExplain.putIfAbsent(r.getId(), r.getVerifyExplanation()); |
| | | } |
| | | Map<Long, Long> investProjectId = new HashMap<>(); |
| | | for (InvestmentMonthly r : investMonthlyMapper.selectList(null)) { |
| | | investProjectId.putIfAbsent(r.getId(), r.getProjectId()); |
| | | } |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) { |
| | | for (Map.Entry<Long, Long> e : investProjectId.entrySet()) { |
| | | if (e.getValue().equals(p.getId())) investName.putIfAbsent(e.getKey(), p.getProjectName()); |
| | | } |
| | | } |
| | | for (AuditResult result : results) { |
| | | AuditRule rule = ruleMap.get(result.getRuleId()); |
| | |
| | | result.setRuleCode(rule.getRuleCode()); |
| | | result.setAlertLevel(rule.getAlertLevel()); |
| | | } |
| | | // åæ¥è¡¨è¡¨ id åèªç¬ç«èªå¢ï¼éæè§åæå±æ¥è¡¨ç±»åå表åå¡«ä¼ä¸åç§° |
| | | String type = rule == null ? null : rule.getReportType(); |
| | | Map<Long, String> nameMap = "INVEST".equals(type) ? investName |
| | | : "H204".equals(type) ? energyName |
| | | : "H2031".equals(type) ? passengerName |
| | | : "CITY_BUS".equals(type) ? busName |
| | | : "CITY_TAXI".equals(type) ? taxiName : h2032Name; |
| | | Map<Long, String> explainMap = "H2031".equals(type) ? passengerExplain |
| | | : "CITY_BUS".equals(type) ? busExplain |
| | | : "CITY_TAXI".equals(type) ? taxiExplain : h2032Explain; |
| | | String enterpriseName = nameMap.get(result.getReportId()); |
| | | if (enterpriseName == null) { |
| | | // 䏿¥æ°æ®è¢«éæ°å¯¼å
¥åæ§å®¡æ ¸ç»æå·²å¤±æï¼ç»åºæç¤ºèéç©ºç½ |
| | |
| | | result.setEnterpriseName(enterpriseName); |
| | | result.setVerifyExplanation(explainMap.get(result.getReportId())); |
| | | } |
| | | } |
| | | |
| | | /** æè¿ææ°æ®çæ¥è¡¨æï¼ç¨äºå端é»è®¤éä¸ï¼ */ |
| | | public String latestPeriod(String reportType) { |
| | | String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim(); |
| | | String max = null; |
| | | switch (type) { |
| | | case "H2031": |
| | | for (PassengerEnterpriseMonthly m : passengerMapper.selectList( |
| | | new LambdaQueryWrapper<PassengerEnterpriseMonthly>().select(PassengerEnterpriseMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | case "H204": |
| | | for (EnergyVehicleQuarterly m : energyMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyVehicleQuarterly>().select(EnergyVehicleQuarterly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | case "INVEST": |
| | | for (InvestmentMonthly m : investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>().select(InvestmentMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | case "CITY_BUS": |
| | | for (CityBusMonthly m : cityBusMapper.selectList( |
| | | new LambdaQueryWrapper<CityBusMonthly>().select(CityBusMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | case "CITY_TAXI": |
| | | for (CityTaxiMonthly m : cityTaxiMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiMonthly>().select(CityTaxiMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | default: |
| | | for (H2032EnterpriseMonthly m : h2032Mapper.selectList( |
| | | new LambdaQueryWrapper<H2032EnterpriseMonthly>().select(H2032EnterpriseMonthly::getReportPeriod))) { |
| | | max = maxPeriod(max, m.getReportPeriod()); |
| | | } |
| | | break; |
| | | } |
| | | return max; |
| | | } |
| | | |
| | | private String maxPeriod(String a, String b) { |
| | | if (b == null || b.trim().isEmpty()) return a; |
| | | if (a == null || a.compareTo(b) < 0) return b; |
| | | return a; |
| | | } |
| | | |
| | | /** å®¡æ ¸ç»æå¯¼åº Excelï¼å«ä¼ä¸å/è§åå/ç¶æ/å®¡æ ¸æè§ï¼ */ |
| | | public byte[] exportResults(String reportPeriod, String reportType) throws Exception { |
| | | List<AuditResult> results = queryResults(reportPeriod, reportType); |
| | | try (XSSFWorkbook wb = new XSSFWorkbook()) { |
| | | Sheet sheet = wb.createSheet("å®¡æ ¸ç»æ"); |
| | | String[] headers = {"æ¥è¡¨æ", "ä¼ä¸/å¸å·", "è§åç¼ç ", "å®¡æ ¸è§å", "ç级", "å®é
å¼", "éå¼/åºå", "å差说æ", "ç¶æ", "å®¡æ ¸æè§"}; |
| | | Row head = sheet.createRow(0); |
| | | for (int i = 0; i < headers.length; i++) { |
| | | Cell cell = head.createCell(i); |
| | | cell.setCellValue(headers[i]); |
| | | org.apache.poi.ss.usermodel.CellStyle style = wb.createCellStyle(); |
| | | org.apache.poi.ss.usermodel.Font font = wb.createFont(); |
| | | font.setBold(true); |
| | | style.setFont(font); |
| | | cell.setCellStyle(style); |
| | | } |
| | | int r = 1; |
| | | for (AuditResult res : results) { |
| | | Row row = sheet.createRow(r++); |
| | | row.createCell(0).setCellValue(valueOf(res.getReportPeriod())); |
| | | row.createCell(1).setCellValue(valueOf(res.getEnterpriseName())); |
| | | row.createCell(2).setCellValue(valueOf(res.getRuleCode())); |
| | | row.createCell(3).setCellValue(valueOf(res.getRuleName())); |
| | | row.createCell(4).setCellValue(valueOf(res.getAlertLevel())); |
| | | row.createCell(5).setCellValue(valueOf(res.getActualValue())); |
| | | row.createCell(6).setCellValue(valueOf(res.getThresholdValue())); |
| | | row.createCell(7).setCellValue(valueOf(res.getDeviation())); |
| | | row.createCell(8).setCellValue(auditStatusCn(res.getStatus())); |
| | | row.createCell(9).setCellValue(valueOf(res.getReviewComment())); |
| | | } |
| | | int[] widths = {12, 28, 12, 28, 8, 36, 22, 36, 10, 28}; |
| | | for (int i = 0; i < widths.length; i++) { |
| | | sheet.setColumnWidth(i, widths[i] * 256); |
| | | } |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | wb.write(out); |
| | | return out.toByteArray(); |
| | | } |
| | | } |
| | | |
| | | private String auditStatusCn(String status) { |
| | | if ("CONFIRMED".equals(status)) return "已确认"; |
| | | if ("IGNORED".equals(status)) return "已忽ç¥"; |
| | | return "å¾
å¤ç"; |
| | | } |
| | | |
| | | /** è§åæ§è¡ç»è®¡ï¼æ¯æ¡å¯ç¨è§åçæ£æ¥å¯¹è±¡æ°ä¸å½ä¸æ° */ |
| | | public List<Map<String, Object>> ruleStats(String reportPeriod, String reportType) { |
| | | String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim(); |
| | | List<AuditRule> rules = rulesOf(type); |
| | | long checked; |
| | | switch (type) { |
| | | case "H2031": |
| | | checked = passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod)); |
| | | break; |
| | | case "H204": |
| | | checked = energyMapper.selectCount(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod)); |
| | | break; |
| | | case "INVEST": |
| | | checked = investMonthlyMapper.selectCount(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, reportPeriod)); |
| | | break; |
| | | case "CITY_BUS": |
| | | checked = cityBusMapper.selectCount(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, reportPeriod)); |
| | | break; |
| | | case "CITY_TAXI": |
| | | checked = cityTaxiMapper.selectCount(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, reportPeriod)); |
| | | break; |
| | | default: |
| | | checked = h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod)); |
| | | break; |
| | | } |
| | | List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, reportPeriod)); |
| | | Map<Long, Long> hitMap = new HashMap<>(); |
| | | for (AuditResult r : results) { |
| | | hitMap.merge(r.getRuleId(), 1L, Long::sum); |
| | | } |
| | | List<Map<String, Object>> out = new ArrayList<>(); |
| | | for (AuditRule rule : rules) { |
| | | Map<String, Object> m = new HashMap<>(); |
| | | m.put("ruleId", rule.getId()); |
| | | m.put("ruleCode", rule.getRuleCode()); |
| | | m.put("ruleName", rule.getRuleName()); |
| | | m.put("alertLevel", rule.getAlertLevel()); |
| | | m.put("checked", checked); |
| | | m.put("hit", hitMap.getOrDefault(rule.getId(), 0L)); |
| | | m.put("skipped", ruleSkipped(rule, reportPeriod)); |
| | | out.add(m); |
| | | } |
| | | return out; |
| | | } |
| | | |
| | | /** 跨表/ç¯æ¯åºåæ°æ®æ´æç¼ºå¤±æ¶ï¼è§åæ 记为跳è¿ï¼é¿å
误æ¥ï¼ */ |
| | | private boolean ruleSkipped(AuditRule rule, String period) { |
| | | String code = rule.getRuleCode(); |
| | | if (code == null) return false; |
| | | switch (code) { |
| | | case "RULE_001": |
| | | case "RULE_002": |
| | | return transportAuthMapper.selectCount(new LambdaQueryWrapper<TransportAuthVehicle>() |
| | | .eq(TransportAuthVehicle::getReportPeriod, period)) == 0; |
| | | case "RULE_003": |
| | | return trackMileageMapper.selectCount(new LambdaQueryWrapper<VehicleTrackMileage>() |
| | | .eq(VehicleTrackMileage::getReportPeriod, period)) == 0; |
| | | case "RULE_004": |
| | | return h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, getLastPeriod(period))) == 0; |
| | | case "RULE_P001": |
| | | case "RULE_P002": |
| | | return passengerAuthMapper.selectCount(new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(PassengerAuthVehicle::getReportPeriod, period)) == 0; |
| | | case "RULE_P003": |
| | | case "RULE_P004": |
| | | case "RULE_P005": |
| | | case "RULE_P006": |
| | | return passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, getLastPeriod(period))) == 0; |
| | | case "RULE_E001": |
| | | case "RULE_E002": |
| | | case "RULE_E003": |
| | | return energyAuthMapper.selectCount(new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, period)) == 0; |
| | | case "RULE_E008": |
| | | case "RULE_E009": |
| | | return h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, period)) == 0; |
| | | case "RULE_T001": |
| | | return cityTaxiAuthMapper.selectCount(new LambdaQueryWrapper<CityTaxiAuth>() |
| | | .eq(CityTaxiAuth::getReportPeriod, period)) == 0; |
| | | case "RULE_I001": |
| | | case "RULE_I002": |
| | | case "RULE_I003": |
| | | return investSystemMapper.selectCount(new LambdaQueryWrapper<InvestmentSystem>() |
| | | .eq(InvestmentSystem::getReportPeriod, period)) == 0; |
| | | default: |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private List<AuditRule> rulesOf(String reportType) { |
| | | List<AuditRule> all = ruleMapper.selectEnabledRules(); |
| | | List<AuditRule> matched = new ArrayList<>(); |
| | | for (AuditRule rule : all) { |
| | | String type = rule.getReportType(); |
| | | if (reportType.equals(type) || (type == null && "H2032".equals(reportType))) { |
| | | matched.add(rule); |
| | | } |
| | | } |
| | | return matched; |
| | | } |
| | | |
| | | private AuditResult checkRule(AuditRule rule, H2032EnterpriseMonthly report, |
| | |
| | | case "NULL_CHECK": |
| | | return checkNull(rule, report); |
| | | case "OUT_RANGE": |
| | | return checkOutRange(rule, report, authMap); |
| | | return checkOutRange(rule, report); |
| | | default: |
| | | return null; |
| | | } |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** 䏿¥å¨è½¬éä¸è½¨è¿¹æµç®å¨è½¬é对æ¯ï¼é«åº30%æ ¸å® */ |
| | | /** 䏿¥å¨è½¬éä¸è½¨è¿¹æµç®å¨è½¬é对æ¯ï¼é«åºé弿¯ä¾æ ¸å® */ |
| | | private AuditResult checkRatio(AuditRule rule, H2032EnterpriseMonthly report, |
| | | Map<String, TransportAuthVehicle> authMap, |
| | | Map<String, VehicleTrackMileage> trackMap) { |
| | |
| | | |
| | | if (ratio > threshold) { |
| | | return buildResult(rule, report, |
| | | "䏿¥å¨è½¬é=" + fmt(actual) + ", 轨迹æµç®=" + Math.round(estimatedTurnover), |
| | | "æ¯å¼â¤" + fmt(threshold), "æ¯å¼=" + fmt(ratio)); |
| | | "䏿¥å¨è½¬é=" + fmt(actual) + ", 轨迹æµç®=" + fmt(estimatedTurnover), |
| | | "â¤è½¨è¿¹æµç®Ã" + fmt(threshold), "=轨迹æµç®Ã" + fmt(ratio)); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** è´§è¿ç±»åç¯æ¯ï¼æ¬ææ°å¢è´§è¿ç±»åæ ¸å®ï¼å¤çæ ¸å®ï¼å°çä¸ç®¡ï¼ */ |
| | | /** è´§è¿ç±»åç¯æ¯ï¼æ¬ææ°å¢ï¼ä¸æä¸º0ãæ¬æ>0ï¼éæ ¸å®ï¼å°çä¸ç®¡ */ |
| | | private AuditResult checkMom(AuditRule rule, H2032EnterpriseMonthly report, |
| | | Map<String, H2032EnterpriseMonthly> lastMonthMap) { |
| | | H2032EnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode())); |
| | | if (lastMonth == null) return null; |
| | | |
| | | String[] fields = {"freightContainer", "freightCoal", "freightOilGas", "freightCrudeOil", |
| | | "freightMetalOre", "freightIronOre", "freightBuilding", "freightGrain"}; |
| | | List<String> newTypes = new ArrayList<>(); |
| | | for (String field : fields) { |
| | | double current = getFieldValue(report, field); |
| | | double last = getFieldValue(lastMonth, field); |
| | | List<String> hits = new ArrayList<>(); |
| | | for (String field : rule.getCheckField().split(",")) { |
| | | double current = getH2032FieldValue(report, field); |
| | | double last = getH2032FieldValue(lastMonth, field); |
| | | if (current > 0 && last == 0) { |
| | | newTypes.add(FREIGHT_TYPE_NAMES.get(field) + "=" + fmt(current) + "å¨"); |
| | | hits.add(h2032FieldLabel(field) + "=" + fmt(current)); |
| | | } |
| | | } |
| | | if (!newTypes.isEmpty()) { |
| | | return buildResult(rule, report, "æ¬ææ°å¢è´§ç±»: " + String.join("ã", newTypes), |
| | | "ä¸æä¸º0", "ç¯æ¯æ°å¢"); |
| | | if (!hits.isEmpty()) { |
| | | return buildResult(rule, report, "æ¬ææ°å¢: " + String.join("ã", hits), |
| | | "ä¸æè¯¥ç±»å为0", "æ¬ææ°å¢è´§è¿ç±»åï¼ä¸ææ ï¼"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** æ éè£
ç®±è½¦ä½æéè£
箱货è¿é */ |
| | | /** æè´§è¿éä½å¯¹åºè½¦è¾ä¸º0 éæ ¸å®ï¼å¦ æéè£
箱货è¿éæ éè£
箱车è¾ï¼ */ |
| | | private AuditResult checkNull(AuditRule rule, H2032EnterpriseMonthly report) { |
| | | double containerFreight = nvl(report.getFreightContainer()); |
| | | double containerVehicle = nvl(report.getVehicleContainer()); |
| | | if (containerFreight > 0 && containerVehicle == 0) { |
| | | return buildResult(rule, report, "éè£
箱货è¿é=" + fmt(containerFreight), |
| | | "æéè£
箱车", "æ éè£
ç®±è½¦ä½æéè£
箱货è¿é"); |
| | | String[] fields = rule.getCheckField().split(","); |
| | | if (fields.length < 2) return null; |
| | | double volume = getH2032FieldValue(report, fields[0]); |
| | | double vehicle = getH2032FieldValue(report, fields[1]); |
| | | if (volume > 0 && vehicle == 0) { |
| | | return buildResult(rule, report, |
| | | h2032FieldLabel(fields[0]) + "=" + fmt(volume) + ", " + h2032FieldLabel(fields[1]) + "=0", |
| | | "æ" + h2032FieldLabel(fields[0]) + "åºæå¯¹åºè½¦è¾", "æ" + h2032FieldLabel(fields[0]) + "使 " + h2032FieldLabel(fields[1])); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** æ´è½¦å¨ä½ä¸å¨(4,40]èå´å
æ ¸å® */ |
| | | private AuditResult checkOutRange(AuditRule rule, H2032EnterpriseMonthly report, |
| | | Map<String, TransportAuthVehicle> authMap) { |
| | | TransportAuthVehicle auth = findAuth(authMap, report.getEnterpriseName()); |
| | | if (auth == null) return null; |
| | | if (num(auth.getOtherCount()) == 0) return null; |
| | | |
| | | double wholeTonnage = nvl(auth.getOtherTons()) / num(auth.getOtherCount()); |
| | | |
| | | String[] range = rule.getThreshold().split(","); |
| | | double min = Double.parseDouble(range[0]); |
| | | double max = Double.parseDouble(range[1]); |
| | | |
| | | if (wholeTonnage < min || wholeTonnage > max) { |
| | | return buildResult(rule, report, "æ´è½¦å¨ä½=" + fmt(wholeTonnage), |
| | | fmt(min) + " ⤠æ´è½¦å¨ä½ ⤠" + fmt(max), "è¶
åºèå´"); |
| | | /** æ°å¼èå´æ£æ¥ï¼å¦ æ´è½¦å¨ä½ 4â¤ä¸â¤40ï¼ */ |
| | | private AuditResult checkOutRange(AuditRule rule, H2032EnterpriseMonthly report) { |
| | | String[] bounds = rule.getThreshold().split(","); |
| | | if (bounds.length < 2) return null; |
| | | double value = getH2032FieldValue(report, rule.getCheckField()); |
| | | double low = Double.parseDouble(bounds[0].trim()); |
| | | double high = Double.parseDouble(bounds[1].trim()); |
| | | if (value > 0 && (value < low || value > high)) { |
| | | return buildResult(rule, report, h2032FieldLabel(rule.getCheckField()) + "=" + fmt(value), |
| | | "èå´ " + fmt(low) + "~" + fmt(high), "è¶
åºèå´"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // ========== è¾
婿¹æ³ ========== |
| | | |
| | | private AuditResult buildResult(AuditRule rule, H2032EnterpriseMonthly report, |
| | | String actual, String threshold, String deviation) { |
| | |
| | | return result; |
| | | } |
| | | |
| | | private TransportAuthVehicle findAuth(Map<String, TransportAuthVehicle> authMap, String name) { |
| | | return authMap.get(key(name)); |
| | | } |
| | | |
| | | private Map<String, TransportAuthVehicle> loadAuthMap(String reportPeriod) { |
| | | Map<String, TransportAuthVehicle> map = new HashMap<>(); |
| | | List<TransportAuthVehicle> list = transportAuthMapper.selectList( |
| | |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, VehicleTrackMileage> loadTrackMap(String period) { |
| | | private Map<String, VehicleTrackMileage> loadTrackMap(String reportPeriod) { |
| | | Map<String, VehicleTrackMileage> map = new HashMap<>(); |
| | | List<VehicleTrackMileage> list = trackMileageMapper.selectList( |
| | | new LambdaQueryWrapper<VehicleTrackMileage>() |
| | | .eq(VehicleTrackMileage::getReportPeriod, period)); |
| | | .eq(VehicleTrackMileage::getReportPeriod, reportPeriod)); |
| | | for (VehicleTrackMileage v : list) { |
| | | map.putIfAbsent(key(v.getEnterpriseName()), v); |
| | | } |
| | |
| | | return map; |
| | | } |
| | | |
| | | private TransportAuthVehicle findAuth(Map<String, TransportAuthVehicle> authMap, String name) { |
| | | return authMap.get(key(name)); |
| | | private String getLastPeriod(String period) { |
| | | String[] parts = period.split("-"); |
| | | int year = Integer.parseInt(parts[0]); |
| | | int month = Integer.parseInt(parts[1]); |
| | | if (month == 1) { |
| | | year--; |
| | | month = 12; |
| | | } else { |
| | | month--; |
| | | } |
| | | return year + "-" + (month < 10 ? "0" + month : String.valueOf(month)); |
| | | } |
| | | |
| | | private String key(String s) { |
| | | return s == null ? "" : s.trim(); |
| | | } |
| | | |
| | | /** double æ¾ç¤ºæ ¼å¼åï¼é¿å
ç§å¦è®¡æ°æ³ï¼æ´æ°å¼ä¸å¸¦å°æ° */ |
| | | private String fmt(double v) { |
| | | if (Double.isNaN(v) || Double.isInfinite(v)) return String.valueOf(v); |
| | | if (v == Math.rint(v) && Math.abs(v) < 1e15) { |
| | | return String.valueOf((long) v); |
| | | } |
| | | String s = String.format("%.6f", v); |
| | | if (s.indexOf('.') >= 0) { |
| | | s = s.replaceAll("0+$", "").replaceAll("\\.$", ""); |
| | | } |
| | | return s; |
| | | } |
| | | |
| | | private double nvl(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | |
| | | private double nvl(Integer v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | |
| | | private int num(Integer v) { |
| | | return v == null ? 0 : v; |
| | | } |
| | | |
| | | private double getFieldValue(H2032EnterpriseMonthly report, String fieldName) { |
| | | private double getH2032FieldValue(H2032EnterpriseMonthly report, String fieldName) { |
| | | try { |
| | | java.lang.reflect.Field field = H2032EnterpriseMonthly.class.getDeclaredField(fieldName); |
| | | field.setAccessible(true); |
| | |
| | | } |
| | | } |
| | | |
| | | private String getLastPeriod(String period) { |
| | | String[] parts = period.split("-"); |
| | | int year = Integer.parseInt(parts[0]); |
| | | int month = Integer.parseInt(parts[1]); |
| | | if (month == 1) { |
| | | year--; |
| | | month = 12; |
| | | } else { |
| | | month--; |
| | | private String h2032FieldLabel(String field) { |
| | | String label = FREIGHT_TYPE_NAMES.get(field); |
| | | if (label != null) return label; |
| | | java.util.Map<String, String> labels = new HashMap<>(); |
| | | labels.put("vehicleTotal", "è½¦è¾æ°"); |
| | | labels.put("tonsTotal", "æ»å¨ä½"); |
| | | labels.put("vehicleContainer", "éè£
ç®±è½¦è¾æ°"); |
| | | labels.put("tonsWhole", "æ´è½¦å¨ä½"); |
| | | labels.put("turnoverTotal", "å¨è½¬é"); |
| | | labels.put("vehicleWhole", "æ´è½¦è½¦è¾æ°"); |
| | | String v = labels.get(field); |
| | | return v == null ? field : v; |
| | | } |
| | | return String.format("%d-%02d", year, month); |
| | | |
| | | private boolean nearlyEqual(double a, double b) { |
| | | if (a == b) return true; |
| | | return Math.abs(a - b) <= 1e-6 * Math.max(1.0, Math.max(Math.abs(a), Math.abs(b))); |
| | | } |
| | | |
| | | private String key(String s) { |
| | | if (s == null) return ""; |
| | | return s.trim().replaceAll("\\s+", ""); |
| | | } |
| | | |
| | | private double nvl(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | |
| | | private double num(Integer v) { |
| | | return v == null ? 0.0 : v.doubleValue(); |
| | | } |
| | | |
| | | private String fmt(double v) { |
| | | if (v == Math.floor(v) && !Double.isInfinite(v)) { |
| | | return String.valueOf((long) v); |
| | | } |
| | | return String.valueOf(Math.round(v * 100.0) / 100.0); |
| | | } |
| | | |
| | | /** å æ¯/å°æ°å¼ï¼ä¿ç 4 ä½å°æ° */ |
| | | private String fmt4(double v) { |
| | | if (v == Math.floor(v) && !Double.isInfinite(v)) { |
| | | return String.valueOf((long) v); |
| | | } |
| | | return String.valueOf(Math.round(v * 10000.0) / 10000.0); |
| | | } |
| | | |
| | | // ========== H2031 æ
å®¢å®¡æ ¸ ========== |
| | | |
| | | private AuditResult checkPassengerRule(AuditRule rule, PassengerEnterpriseMonthly report, |
| | | Map<String, PassengerAuthVehicle> authMap, |
| | | Map<String, PassengerEnterpriseMonthly> lastMonthMap) { |
| | | try { |
| | | switch (rule.getCompareType()) { |
| | | case "DIFF": |
| | | return checkPassengerDiff(rule, report, authMap); |
| | | case "MOM_EQ_ZERO": |
| | | return checkPassengerMomEqZero(rule, report, lastMonthMap); |
| | | case "MOM_PCT": |
| | | return checkPassengerMomPct(rule, report, lastMonthMap); |
| | | case "MOM_EQ": |
| | | return checkPassengerMomEq(rule, report, lastMonthMap); |
| | | case "EXISTENCE": |
| | | return checkPassengerExistence(rule, report); |
| | | default: |
| | | return null; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Passenger rule check error: {} - {}", rule.getRuleCode(), e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** è½¦è¾æ°/è½½å®¢ä½æ° ä¸è¿æ¿å¯¹æ¯ï¼å·®è·è¶
é弿 ¸å® */ |
| | | private AuditResult checkPassengerDiff(AuditRule rule, PassengerEnterpriseMonthly report, |
| | | Map<String, PassengerAuthVehicle> authMap) { |
| | | PassengerAuthVehicle auth = authMap.get(key(report.getEnterpriseName())); |
| | | if (auth == null) return null; |
| | | double actual; |
| | | double reference; |
| | | if (rule.getCheckField().contains("seat")) { |
| | | actual = num(report.getSeatTotal()); |
| | | reference = num(auth.getSeatCount()); |
| | | } else { |
| | | actual = num(report.getVehicleTotal()); |
| | | reference = num(auth.getVehicleCount()); |
| | | } |
| | | double threshold = Double.parseDouble(rule.getThreshold()); |
| | | double diff = Math.abs(actual - reference); |
| | | if (diff > threshold) { |
| | | return buildPassengerResult(rule, report, "䏿¥=" + fmt(actual) + ", è¿æ¿=" + fmt(reference), |
| | | "å·®è·â¤" + fmt(threshold), "å·®è·=" + fmt(diff)); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** ç¯æ¯ä¸ä¸ªæçäº0ï¼å¼ä¸ä¸æç¸åéæ ¸å®ï¼ä¸æä¸º0䏿 ¸å®ï¼ */ |
| | | private AuditResult checkPassengerMomEqZero(AuditRule rule, PassengerEnterpriseMonthly report, |
| | | Map<String, PassengerEnterpriseMonthly> lastMonthMap) { |
| | | PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode())); |
| | | if (lastMonth == null) return null; |
| | | List<String> hits = new ArrayList<>(); |
| | | for (String field : rule.getCheckField().split(",")) { |
| | | double current = getPassengerFieldValue(report, field); |
| | | double last = getPassengerFieldValue(lastMonth, field); |
| | | if (last > 0 && nearlyEqual(current, last)) { |
| | | hits.add(fieldLabel(field) + "=" + fmt(current)); |
| | | } |
| | | } |
| | | if (!hits.isEmpty()) { |
| | | return buildPassengerResult(rule, report, "æ¬æä¸ä¸æç¸å: " + String.join("ã", hits), |
| | | "ç¯æ¯â 0", "ç¯æ¯=0"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** å¹³åè¿è·ç¯æ¯è¶
±threshold% æ ¸å® */ |
| | | private AuditResult checkPassengerMomPct(AuditRule rule, PassengerEnterpriseMonthly report, |
| | | Map<String, PassengerEnterpriseMonthly> lastMonthMap) { |
| | | PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode())); |
| | | if (lastMonth == null) return null; |
| | | double threshold = Double.parseDouble(rule.getThreshold()); |
| | | List<String> hits = new ArrayList<>(); |
| | | for (String field : rule.getCheckField().split(",")) { |
| | | double current = getPassengerFieldValue(report, field); |
| | | double last = getPassengerFieldValue(lastMonth, field); |
| | | if (last > 0) { |
| | | double pct = Math.abs((current - last) / last) * 100.0; |
| | | if (pct > threshold) { |
| | | hits.add(fieldLabel(field) + "=" + fmt(current) + "ï¼ä¸æ=" + fmt(last) + "ï¼ç¯æ¯" + fmt(pct) + "%"); |
| | | } |
| | | } |
| | | } |
| | | if (!hits.isEmpty()) { |
| | | return buildPassengerResult(rule, report, String.join("ï¼", hits), |
| | | "ç¯æ¯â¤Â±" + fmt(threshold) + "%", "ç¯æ¯è¶
é"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** å
车平åè¿è·çäºä¸ææ ¸å® */ |
| | | private AuditResult checkPassengerMomEq(AuditRule rule, PassengerEnterpriseMonthly report, |
| | | Map<String, PassengerEnterpriseMonthly> lastMonthMap) { |
| | | PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode())); |
| | | if (lastMonth == null) return null; |
| | | List<String> hits = new ArrayList<>(); |
| | | for (String field : rule.getCheckField().split(",")) { |
| | | double current = getPassengerFieldValue(report, field); |
| | | double last = getPassengerFieldValue(lastMonth, field); |
| | | if (last > 0 && nearlyEqual(current, last)) { |
| | | hits.add(fieldLabel(field) + "=" + fmt(current)); |
| | | } |
| | | } |
| | | if (!hits.isEmpty()) { |
| | | return buildPassengerResult(rule, report, "çäºä¸æ: " + String.join("ã", hits), |
| | | "ä¸ä¸æä¸å", "ä¸ä¸æç¸å"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** æ ç线/æ å
è½¦å´æå¯¹åºå®¢è¿éï¼check_field é¦ä¸ªä¸ºè½¦è¾å段ï¼å
¶ä½ä¸ºå®¢è¿éåæ®µ */ |
| | | private AuditResult checkPassengerExistence(AuditRule rule, PassengerEnterpriseMonthly report) { |
| | | String[] fields = rule.getCheckField().split(","); |
| | | if (fields.length < 2) return null; |
| | | String vehicleField = fields[0]; |
| | | double vehicleCount = getPassengerFieldValue(report, vehicleField); |
| | | if (vehicleCount > 0) return null; |
| | | List<String> hits = new ArrayList<>(); |
| | | for (int i = 1; i < fields.length; i++) { |
| | | double value = getPassengerFieldValue(report, fields[i]); |
| | | if (value > 0) { |
| | | hits.add(fieldLabel(fields[i]) + "=" + fmt(value)); |
| | | } |
| | | } |
| | | if (!hits.isEmpty()) { |
| | | String label = "schedule".equals(vehicleField) || "vehicleSchedule".equals(vehicleField) ? "ç线" : "å
车"; |
| | | return buildPassengerResult(rule, report, String.join("ã", hits), |
| | | "æ" + label + "车è¾", "æ " + label + "车è¾ä½æå®¢è¿é"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private Map<String, PassengerAuthVehicle> loadPassengerAuthMap(String reportPeriod) { |
| | | Map<String, PassengerAuthVehicle> map = new HashMap<>(); |
| | | List<PassengerAuthVehicle> list = passengerAuthMapper.selectList( |
| | | new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(PassengerAuthVehicle::getReportPeriod, reportPeriod)); |
| | | for (PassengerAuthVehicle v : list) { |
| | | map.putIfAbsent(key(v.getEnterpriseName()), v); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, PassengerEnterpriseMonthly> loadPassengerLastMonthMap(String period) { |
| | | Map<String, PassengerEnterpriseMonthly> map = new HashMap<>(); |
| | | String lastPeriod = getLastPeriod(period); |
| | | List<PassengerEnterpriseMonthly> list = passengerMapper.selectList( |
| | | new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, lastPeriod)); |
| | | for (PassengerEnterpriseMonthly v : list) { |
| | | map.putIfAbsent(key(v.getEnterpriseCode()), v); |
| | | } |
| | | return map; |
| | | } |
| | | |
| | | private double getPassengerFieldValue(PassengerEnterpriseMonthly report, String fieldName) { |
| | | try { |
| | | java.lang.reflect.Field field = PassengerEnterpriseMonthly.class.getDeclaredField(fieldName); |
| | | field.setAccessible(true); |
| | | Object value = field.get(report); |
| | | if (value == null) return 0.0; |
| | | if (value instanceof Double) return (Double) value; |
| | | if (value instanceof Integer) return ((Integer) value).doubleValue(); |
| | | return Double.parseDouble(value.toString()); |
| | | } catch (Exception e) { |
| | | return 0.0; |
| | | } |
| | | } |
| | | |
| | | private String fieldLabel(String field) { |
| | | java.util.Map<String, String> labels = new HashMap<>(); |
| | | labels.put("passengerClass1", "ä¸ç±»ç线客è¿é"); |
| | | labels.put("passengerClass2", "äºç±»ç线客è¿é"); |
| | | labels.put("passengerClass3", "ä¸ç±»ç线客è¿é"); |
| | | labels.put("passengerClass4", "åç±»ç线客è¿é"); |
| | | labels.put("passengerCharter", "å
车客è¿é"); |
| | | labels.put("turnoverClass1", "ä¸ç±»ç线å¨è½¬é"); |
| | | labels.put("turnoverClass2", "äºç±»ç线å¨è½¬é"); |
| | | labels.put("turnoverClass3", "ä¸ç±»ç线å¨è½¬é"); |
| | | labels.put("turnoverClass4", "åç±»ç线å¨è½¬é"); |
| | | labels.put("turnoverCharter", "å
车å¨è½¬é"); |
| | | labels.put("avgDistanceClass1", "ä¸ç±»ç线平åè¿è·"); |
| | | labels.put("avgDistanceClass2", "äºç±»ç线平åè¿è·"); |
| | | labels.put("avgDistanceClass3", "ä¸ç±»ç线平åè¿è·"); |
| | | labels.put("avgDistanceClass4", "åç±»ç线平åè¿è·"); |
| | | labels.put("avgDistanceCharter", "å
车平åè¿è·"); |
| | | String label = labels.get(field); |
| | | return label == null ? field : label; |
| | | } |
| | | |
| | | private AuditResult buildPassengerResult(AuditRule rule, PassengerEnterpriseMonthly report, |
| | | String actual, String threshold, String deviation) { |
| | | AuditResult result = new AuditResult(); |
| | | result.setRuleId(rule.getId()); |
| | | result.setReportId(report.getId()); |
| | | result.setEnterpriseCode(report.getEnterpriseCode()); |
| | | result.setReportPeriod(report.getReportPeriod()); |
| | | result.setActualValue(actual); |
| | | result.setThresholdValue(threshold); |
| | | result.setDeviation(deviation); |
| | | result.setStatus("PENDING"); |
| | | return result; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.common; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.http.HttpStatus; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.ExceptionHandler; |
| | | import org.springframework.web.bind.annotation.RestControllerAdvice; |
| | | |
| | | /** å
¨å±å¼å¸¸å¤çï¼æ 500 é误åå éåºå°å端æç¤ºï¼P0ï¼å¯¼åºå¤±è´¥å¯è§å
·ä½åå ï¼ */ |
| | | @Slf4j |
| | | @RestControllerAdvice |
| | | public class GlobalExceptionHandler { |
| | | |
| | | @ExceptionHandler(Exception.class) |
| | | public ResponseEntity<Result<String>> handle(Exception e) { |
| | | log.error("Unhandled exception", e); |
| | | String msg = e.getMessage(); |
| | | if (msg == null || msg.trim().isEmpty()) msg = e.toString(); |
| | | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) |
| | | .body(Result.error(500, msg)); |
| | | } |
| | | } |
| | |
| | | |
| | | ALIAS_MAP.put("æ©æ½", "æ©æ½å·"); |
| | | ALIAS_MAP.put("æ©æ½å¸", "æ©æ½å·"); |
| | | ALIAS_MAP.put("æ©æ½åå®¶æèæèªæ²»å·", "æ©æ½å·"); |
| | | ALIAS_MAP.put("天é¨", "天é¨å¸"); |
| | | ALIAS_MAP.put("æåº", "ç¥åæ¶æåº"); |
| | | ALIAS_MAP.put("ç¥åæ¶", "ç¥åæ¶æåº"); |
| New file |
| | |
| | | package com.trafficaudit.config; |
| | | |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.core.io.FileSystemResource; |
| | | import org.springframework.core.io.Resource; |
| | | import org.springframework.http.HttpHeaders; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.net.URLEncoder; |
| | | |
| | | /** |
| | | * æ°æ®å¯¼å
¥é¡µãä¸è½½æ¨¡æ¿ãï¼/api/templates/** -> docs ç®å½ä¸çæ¨¡æ¿æä»¶ã |
| | | * ç¨ Controller èééæèµæºæ å°ï¼ä¾¿äºè®¾ç½®ä¸ææä»¶åçä¸è½½å¤´å¹¶é²è·¯å¾ç©¿è¶ã |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/templates") |
| | | public class TemplateDownloadController { |
| | | |
| | | @Value("${docs.template-root:docs}") |
| | | private String templateRoot; |
| | | |
| | | @GetMapping("/{*path}") |
| | | public ResponseEntity<Resource> download(@PathVariable String path) throws IOException { |
| | | File file = resolveTemplateFile(path); |
| | | if (file == null) { |
| | | return ResponseEntity.notFound().build(); |
| | | } |
| | | String encoded = URLEncoder.encode(file.getName(), "UTF-8").replace("+", "%20"); |
| | | return ResponseEntity.ok() |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) |
| | | .contentType(MediaType.APPLICATION_OCTET_STREAM) |
| | | .contentLength(file.length()) |
| | | .body(new FileSystemResource(file)); |
| | | } |
| | | |
| | | /** 模æ¿å®ä½ï¼é
ç½®ç®å½ â user.dir â user.dir/.. é级åéï¼ä¸æ¥è¡¨æ¨¡åä¸è´ï¼ï¼å¹¶é²è·¯å¾ç©¿è¶ */ |
| | | private File resolveTemplateFile(String rel) throws IOException { |
| | | String r = rel; |
| | | while (r.startsWith("./")) r = r.substring(2); |
| | | if (r.contains("..")) { |
| | | return null; |
| | | } |
| | | String[] roots = { |
| | | templateRoot, |
| | | System.getProperty("user.dir") + "/" + templateRoot, |
| | | System.getProperty("user.dir") + "/../" + templateRoot |
| | | }; |
| | | for (String root : roots) { |
| | | if (root == null || root.trim().isEmpty()) continue; |
| | | File f = new File(root, r); |
| | | if (f.exists() && f.isFile()) { |
| | | return f.getCanonicalFile(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | |
| | | |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.dataimport.dto.ImportResult; |
| | | import com.trafficaudit.dataimport.entity.ImportBatch; |
| | | import com.trafficaudit.dataimport.mapper.ImportBatchMapper; |
| | | import com.trafficaudit.dataimport.service.DataImportService; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Slf4j |
| | | @RestController |
| | |
| | | |
| | | @Resource |
| | | private DataImportService importService; |
| | | @Resource |
| | | private ImportBatchMapper importBatchMapper; |
| | | |
| | | /** H203-2表 é路货ç©è¿è¾æåº¦ç产æ
åµ */ |
| | | @PostMapping("/h2032") |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** H204 é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼å£åº¦è½èæç» + è¿æ¿è½¦è¾ä¿¡æ¯ï¼ */ |
| | | @PostMapping("/energyMonthly") |
| | | public Result<String> importEnergyMonthly(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importEnergyMonthly(file, period); |
| | | String msg = "H204è½è壿¥å¯¼å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡ï¼å«åæ¥è¡¨æè¿æ¿è½¦è¾ä¿¡æ¯ï¼"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("energyMonthly import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** è½è车è¾è¿æ¿ä¿¡æ¯ï¼ç¬ç«æ¨¡æ¿ï¼ä¹å¯é H204 壿¥ç¬¬äºä¸ª sheet ä¸å¹¶å¯¼å
¥ï¼ */ |
| | | @PostMapping("/energyAuth") |
| | | public Result<String> importEnergyAuth(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importEnergyAuth(file, period); |
| | | String msg = "è½è车è¾è¿æ¿ä¿¡æ¯å¯¼å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("energyAuth import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** H203-1表 å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµï¼å«è¿æ¿è½¦è¾æ°sheetï¼ */ |
| | | @PostMapping("/passengerMonthly") |
| | | public Result<String> importPassengerMonthly(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importPassengerMonthly(file, period); |
| | | String msg = "H2031æ
å®¢ææ¥å¯¼å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡ï¼å«åæ¥è¡¨æè¿æ¿è½¦è¾æ°æ®ï¼"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("passengerMonthly import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** å
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬éï¼ä¼ä¸çº¿ä¸æ±æ¥ï¼å¯¼å
¥åä¾åå¸å·è¡¨ä¸ªä½è¡å¡«å
ï¼ */ |
| | | @PostMapping("/passengerIndividual") |
| | | public Result<String> importPassengerIndividual(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importPassengerIndividual(file, period); |
| | | String msg = "个ä½å®¢è¿é导å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("passengerIndividual import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°ï¼ç¬ç«æ¨¡æ¿ï¼ä¹å¯é H203-1 ææ¥ç¬¬äºä¸ª sheet ä¸å¹¶å¯¼å
¥ï¼ */ |
| | | @PostMapping("/passengerAuth") |
| | | public Result<String> importPassengerAuth(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importPassengerAuth(file, period); |
| | | String msg = "æ
å®¢è¿æ¿è½¦è¾æ°å¯¼å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("passengerAuth import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** åå¸å
¬äº¤ææ¥ï¼åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼ä¼ä¸çº§æç»ï¼ */ |
| | | @PostMapping("/cityBus") |
| | | public Result<String> importCityBus(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importCityBus(file, period); |
| | | String msg = "åå¸å
¬äº¤ææ¥å¯¼å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("cityBus import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** 巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼å¸å·çº§ï¼ */ |
| | | @PostMapping("/cityTaxi") |
| | | public Result<String> importCityTaxi(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | ImportResult result = importService.importCityTaxi(file, period); |
| | | String msg = "巡游åºç§ææ¥å¯¼å
¥å®æï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("cityTaxi import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** åºç§è½¦è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦è¾çº§æç»ï¼ç¨äºè¿è¥è½¦æ°å¯¹æ¯ï¼ */ |
| | | @PostMapping("/cityTaxiAuth") |
| | | public Result<String> importCityTaxiAuth(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | int count = importService.importCityTaxiAuth(file, period); |
| | | return Result.ok("åºç§è½¦è¿æ¿è½¦è¾å¯¼å
¥æå, å
± " + count + " æ¡è®°å½"); |
| | | } catch (Exception e) { |
| | | log.error("cityTaxiAuth import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** æèµææ¥ï¼å
¨çæ±æ»å¤§è¡¨ï¼ï¼category=客è¿ç«åº/ç©æµååº */ |
| | | @PostMapping("/investment") |
| | | public Result<String> importInvestment(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period, |
| | | @RequestParam(value = "category", defaultValue = "客è¿ç«åº") String category) { |
| | | try { |
| | | ImportResult result = importService.importInvestment(file, period, category); |
| | | String msg = "æèµææ¥å¯¼å
¥å®æï¼" + category + "ï¼ï¼æå " + result.getSuccess() + " æ¡ï¼å¤±è´¥ " + result.getFail() + " æ¡"; |
| | | if (result.getFail() > 0) { |
| | | java.util.List<String> details = result.getFailDetails(); |
| | | int shown = Math.min(details.size(), 100); |
| | | for (int i = 0; i < shown; i++) { |
| | | msg += "\n" + details.get(i); |
| | | } |
| | | if (details.size() > shown) { |
| | | msg += "\nâ¦â¦å
± " + details.size() + " è¡å¤±è´¥"; |
| | | } |
| | | } |
| | | return Result.ok(msg); |
| | | } catch (Exception e) { |
| | | log.error("investment import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** æèµå¸å·å表æ¹é导å
¥ï¼M2ï¼ï¼æ«æ docs/æèµ/è¾å
¥/{ç±»å«}/{Xæ} ç®å½å
¨é¨å¸å·æ¥è¡¨ï¼category=客è¿ç«åº/ç©æµååº */ |
| | | @PostMapping("/investCityBatch") |
| | | public Result<Object> importInvestCityBatch(@RequestParam("period") String period, |
| | | @RequestParam(value = "category", defaultValue = "客è¿ç«åº") String category) { |
| | | try { |
| | | Map<String, Object> result = importService.importInvestCityDir(period, category); |
| | | return Result.ok(result); |
| | | } catch (Exception e) { |
| | | log.error("investCityBatch import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** æèµç³»ç»å¯¼åºï¼å®¡æ ¸æ¯å¯¹åºåï¼ */ |
| | | @PostMapping("/investmentSystem") |
| | | public Result<String> importInvestmentSystem(@RequestParam("file") MultipartFile file, |
| | | @RequestParam("period") String period) { |
| | | try { |
| | | int count = importService.importInvestmentSystem(file, period); |
| | | return Result.ok("æèµç³»ç»å¯¼åºå¯¼å
¥æå, å
± " + count + " æ¡è®°å½"); |
| | | } catch (Exception e) { |
| | | log.error("investmentSystem import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | /** è¿æ¿è½¦è¾ */ |
| | | @PostMapping("/transportAuth") |
| | | public Result<String> importTransportAuth(@RequestParam("file") MultipartFile file, |
| | |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | /** ç®å½æµè§å¨ï¼path 为空è¿åé¡¹ç® docs æ°æ®ç®å½ï¼è¿ååç®å½ä¸å½åç®å½ Excel æä»¶ */ |
| | | @GetMapping("/dirs") |
| | | public Result<Map<String, Object>> dirs(@RequestParam(value = "path", required = false) String path) { |
| | | try { |
| | | Map<String, Object> result = importService.listDirs(path); |
| | | return Result.ok(result); |
| | | } catch (Exception e) { |
| | | log.error("list dirs error", e); |
| | | return Result.error("ç®å½æµè§å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** æè·¯å¾å¯¼å
¥å个 Excel æä»¶ï¼å端éæä»¶å¯¼å
¥æ¾ç¤ºè¿åº¦ï¼ï¼æ¥è¡¨æä¼å
仿件åè¯å« */ |
| | | @PostMapping("/fromDirFile") |
| | | public Result<Map<String, Object>> importFromDirFile(@RequestParam("type") String type, |
| | | @RequestParam(value = "period", required = false) String period, |
| | | @RequestParam(value = "skipImported", defaultValue = "true") boolean skipImported, |
| | | @RequestParam("path") String path) { |
| | | try { |
| | | Map<String, Object> result = importService.importDirFileByPath(type, period, skipImported, path); |
| | | return Result.ok(result); |
| | | } catch (Exception e) { |
| | | log.error("fromDirFile import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** 仿å¡ç«¯æä»¶å¤¹æ¹é导å
¥ï¼æ«æ dir ä¸å
¨é¨ .xlsx/.xlsï¼æ type é个导å
¥ï¼æ¥è¡¨æä¼å
仿件åè¯å« */ |
| | | @PostMapping("/fromDir") |
| | | public Result<Object> importFromDir(@RequestParam("type") String type, |
| | | @RequestParam(value = "period", required = false) String period, |
| | | @RequestParam(value = "skipImported", defaultValue = "true") boolean skipImported, |
| | | @RequestParam("dir") String dir) { |
| | | try { |
| | | Map<String, Object> result = importService.importFromDir(type, period, skipImported, dir); |
| | | return Result.ok(result); |
| | | } catch (Exception e) { |
| | | log.error("fromDir import error", e); |
| | | return Result.error("导å
¥å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** 导å
¥æ¹æ¬¡åå²ï¼æ°æ®å¯¼å
¥é¡µå±ç¤ºæè¿å¯¼å
¥ç»æï¼ */ |
| | | @GetMapping("/batches") |
| | | public Result<Map<String, Object>> batches(@RequestParam(value = "type", required = false) String type, |
| | | @RequestParam(value = "period", required = false) String period, |
| | | @RequestParam(value = "limit", defaultValue = "20") int limit) { |
| | | int safeLimit = Math.min(Math.max(limit, 1), 200); |
| | | LambdaQueryWrapper<ImportBatch> w = new LambdaQueryWrapper<ImportBatch>() |
| | | .eq(type != null && !type.trim().isEmpty(), ImportBatch::getImportType, type) |
| | | .eq(period != null && !period.trim().isEmpty(), ImportBatch::getReportPeriod, period) |
| | | .orderByDesc(ImportBatch::getId) |
| | | .last("limit " + safeLimit); |
| | | List<ImportBatch> list = importBatchMapper.selectList(w); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("records", list); |
| | | return Result.ok(result); |
| | | } |
| | | |
| | | } |
| | |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.dataimport.entity.FreightTurnoverImport; |
| | | import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.CityBusMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiAuth; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.ScaleSplitTransport; |
| | | import com.trafficaudit.dataimport.entity.TransportAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.VehicleTrackMileage; |
| | | import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper; |
| | | import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper; |
| | | import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper; |
| | |
| | | private ScaleSplitTransportMapper scaleSplitMapper; |
| | | @Resource |
| | | private FreightTurnoverImportMapper freightTurnoverMapper; |
| | | @Resource |
| | | private PassengerEnterpriseMonthlyMapper passengerMapper; |
| | | @Resource |
| | | private PassengerAuthVehicleMapper passengerAuthMapper; |
| | | @Resource |
| | | private CityBusMonthlyMapper cityBusMapper; |
| | | @Resource |
| | | private CityTaxiMonthlyMapper cityTaxiMapper; |
| | | @Resource |
| | | private CityTaxiAuthMapper cityTaxiAuthMapper; |
| | | |
| | | @GetMapping("/list") |
| | | public Result<Map<String, Object>> list(@RequestParam("type") String type, |
| | |
| | | fillPage(result, r); |
| | | break; |
| | | } |
| | | case "h2031": { |
| | | LambdaQueryWrapper<PassengerEnterpriseMonthly> w = new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(notEmpty(period), PassengerEnterpriseMonthly::getReportPeriod, period) |
| | | .like(!kw.isEmpty(), PassengerEnterpriseMonthly::getEnterpriseName, kw) |
| | | .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))); |
| | | fillPage(result, r); |
| | | break; |
| | | } |
| | | case "cityBus": { |
| | | LambdaQueryWrapper<CityBusMonthly> w = new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(notEmpty(period), CityBusMonthly::getReportPeriod, period) |
| | | .like(!kw.isEmpty(), CityBusMonthly::getEnterpriseName, kw) |
| | | .orderByDesc(CityBusMonthly::getId); |
| | | Page<CityBusMonthly> p = new Page<>(safePage, safeSize); |
| | | Page<CityBusMonthly> r = cityBusMapper.selectPage(p, w); |
| | | result.put("records", toMaps(r.getRecords(), propsOf(BUS_COLUMNS))); |
| | | fillPage(result, r); |
| | | break; |
| | | } |
| | | case "cityTaxi": { |
| | | LambdaQueryWrapper<CityTaxiMonthly> w = new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(notEmpty(period), CityTaxiMonthly::getReportPeriod, period) |
| | | .like(!kw.isEmpty(), CityTaxiMonthly::getCity, kw) |
| | | .orderByAsc(CityTaxiMonthly::getId); |
| | | Page<CityTaxiMonthly> p = new Page<>(safePage, safeSize); |
| | | Page<CityTaxiMonthly> r = cityTaxiMapper.selectPage(p, w); |
| | | result.put("records", toMaps(r.getRecords(), propsOf(TAXI_COLUMNS))); |
| | | fillPage(result, r); |
| | | break; |
| | | } |
| | | case "cityTaxiAuth": { |
| | | LambdaQueryWrapper<CityTaxiAuth> w = new LambdaQueryWrapper<CityTaxiAuth>() |
| | | .eq(notEmpty(period), CityTaxiAuth::getReportPeriod, period) |
| | | .like(!kw.isEmpty(), CityTaxiAuth::getCity, kw) |
| | | .orderByDesc(CityTaxiAuth::getId); |
| | | Page<CityTaxiAuth> p = new Page<>(safePage, safeSize); |
| | | Page<CityTaxiAuth> r = cityTaxiAuthMapper.selectPage(p, w); |
| | | result.put("records", toMaps(r.getRecords(), propsOf(TAXI_AUTH_COLUMNS))); |
| | | fillPage(result, r); |
| | | break; |
| | | } |
| | | case "passengerAuth": { |
| | | LambdaQueryWrapper<PassengerAuthVehicle> w = new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(notEmpty(period), PassengerAuthVehicle::getReportPeriod, period) |
| | | .like(!kw.isEmpty(), PassengerAuthVehicle::getEnterpriseName, kw) |
| | | .orderByDesc(PassengerAuthVehicle::getId); |
| | | Page<PassengerAuthVehicle> p = new Page<>(safePage, safeSize); |
| | | Page<PassengerAuthVehicle> r = passengerAuthMapper.selectPage(p, w); |
| | | result.put("records", toMaps(r.getRecords(), propsOf(PAUTH_COLUMNS))); |
| | | fillPage(result, r); |
| | | break; |
| | | } |
| | | case "freightTurnover": { |
| | | LambdaQueryWrapper<FreightTurnoverImport> w = new LambdaQueryWrapper<FreightTurnoverImport>() |
| | | .eq(notEmpty(period), FreightTurnoverImport::getReportPeriod, period) |
| | |
| | | case "trackMileage": return TRACK_COLUMNS; |
| | | case "scaleSplit": return SPLIT_COLUMNS; |
| | | case "freightTurnover": return FREIGHT_COLUMNS; |
| | | case "h2031": return H2031_COLUMNS; |
| | | case "cityBus": return BUS_COLUMNS; |
| | | case "cityTaxi": return TAXI_COLUMNS; |
| | | case "cityTaxiAuth": return TAXI_AUTH_COLUMNS; |
| | | case "passengerAuth": return PAUTH_COLUMNS; |
| | | default: return new ArrayList<>(); |
| | | } |
| | | } |
| | |
| | | m.put("label", label); |
| | | m.put("width", width); |
| | | return m; |
| | | |
| | | } |
| | | |
| | | private static final List<Map<String, Object>> H2032_COLUMNS = new ArrayList<>(); |
| | |
| | | private static final List<Map<String, Object>> TRACK_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> SPLIT_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> FREIGHT_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> H2031_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> PAUTH_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> BUS_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> TAXI_COLUMNS = new ArrayList<>(); |
| | | private static final List<Map<String, Object>> TAXI_AUTH_COLUMNS = new ArrayList<>(); |
| | | |
| | | static { |
| | | H2032_COLUMNS.add(col("reportPeriod", "æ¥è¡¨æ", 100)); |
| | |
| | | for (int m = 1; m <= 12; m++) { |
| | | FREIGHT_COLUMNS.add(col(String.format("lastTurnoverM%02d", m), "å»å¹´å¨è½¬é" + m + "æ", 140)); |
| | | } |
| | | |
| | | H2031_COLUMNS.add(col("reportPeriod", "æ¥è¡¨æ", 100)); |
| | | H2031_COLUMNS.add(col("regionCode", "æå±å°åº", 100)); |
| | | H2031_COLUMNS.add(col("enterpriseCode", "ä¼ä¸ä»£ç ", 130)); |
| | | H2031_COLUMNS.add(col("enterpriseName", "ä¼ä¸åç§°", 220)); |
| | | H2031_COLUMNS.add(col("vehicleTotal", "客è¿è½¦è¾å计", 110)); |
| | | H2031_COLUMNS.add(col("seatTotal", "æ ¸å®è½½å®¢ä½å计", 130)); |
| | | H2031_COLUMNS.add(col("vehicleSchedule", "ççº¿è½¦è¾æ°", 110)); |
| | | 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("turnoverTotal", "å¨è½¬éå计", 110)); |
| | | H2031_COLUMNS.add(col("passengerClass1", "客è¿éä¸ç±»", 110)); |
| | | H2031_COLUMNS.add(col("passengerClass2", "客è¿éäºç±»", 110)); |
| | | H2031_COLUMNS.add(col("passengerClass3", "客è¿éä¸ç±»", 110)); |
| | | H2031_COLUMNS.add(col("passengerClass4", "客è¿éåç±»", 110)); |
| | | H2031_COLUMNS.add(col("passengerCharter", "客è¿éå
车", 110)); |
| | | H2031_COLUMNS.add(col("turnoverClass1", "å¨è½¬éä¸ç±»", 110)); |
| | | H2031_COLUMNS.add(col("turnoverClass2", "å¨è½¬éäºç±»", 110)); |
| | | H2031_COLUMNS.add(col("turnoverClass3", "å¨è½¬éä¸ç±»", 110)); |
| | | H2031_COLUMNS.add(col("turnoverClass4", "å¨è½¬éåç±»", 110)); |
| | | H2031_COLUMNS.add(col("turnoverCharter", "å¨è½¬éå
车", 110)); |
| | | H2031_COLUMNS.add(col("avgDistanceTotal", "å¹³åè¿è·å计", 120)); |
| | | H2031_COLUMNS.add(col("avgDistanceClass1", "ä¸ç±»å¹³åè¿è·", 120)); |
| | | H2031_COLUMNS.add(col("avgDistanceClass2", "äºç±»å¹³åè¿è·", 120)); |
| | | H2031_COLUMNS.add(col("avgDistanceClass3", "ä¸ç±»å¹³åè¿è·", 120)); |
| | | H2031_COLUMNS.add(col("avgDistanceClass4", "å类平åè¿è·", 120)); |
| | | H2031_COLUMNS.add(col("avgDistanceCharter", "å
车平åè¿è·", 120)); |
| | | BUS_COLUMNS.add(col("reportPeriod", "æ¥è¡¨æ", 100)); |
| | | BUS_COLUMNS.add(col("regionCode", "æå±å°åº", 100)); |
| | | BUS_COLUMNS.add(col("city", "å¸å·", 110)); |
| | | BUS_COLUMNS.add(col("enterpriseName", "ä¼ä¸åç§°", 220)); |
| | | 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("ratePassengerMom", "客è¿éç¯æ¯", 100)); |
| | | BUS_COLUMNS.add(col("ratePassengerCityMom", "åå¸å
ç¯æ¯", 100)); |
| | | BUS_COLUMNS.add(col("rateChengxiangMom", "åé
åä¹¡ç¯æ¯", 110)); |
| | | BUS_COLUMNS.add(col("ratePassengerYoy", "客è¿é忝", 100)); |
| | | BUS_COLUMNS.add(col("passengerCityRatio", "åå¸å
å æ¯", 100)); |
| | | BUS_COLUMNS.add(col("chengxiangRatio", "åé
åä¹¡å æ¯", 110)); |
| | | BUS_COLUMNS.add(col("contactPerson", "填表人", 100)); |
| | | BUS_COLUMNS.add(col("reportDate", "æ¥åºæ¥æ", 110)); |
| | | BUS_COLUMNS.add(col("verifyExplanation", "æ ¸å®è§£é", 220)); |
| | | TAXI_COLUMNS.add(col("reportPeriod", "æ¥è¡¨æ", 100)); |
| | | TAXI_COLUMNS.add(col("regionCode", "æå±å°åº", 100)); |
| | | 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("opVehicles", "è¿è¥è½¦è¾æ°", 110)); |
| | | TAXI_COLUMNS.add(col("avgDistance", "å¹³åè¿è·", 100)); |
| | | TAXI_COLUMNS.add(col("avgDistanceCity", "åå¸å
å¹³åè¿è·", 130)); |
| | | 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("verifyExplanation", "æ ¸å®è§£é", 220)); |
| | | TAXI_AUTH_COLUMNS.add(col("reportPeriod", "æ¥è¡¨æ", 100)); |
| | | TAXI_AUTH_COLUMNS.add(col("city", "å¸å·", 110)); |
| | | TAXI_AUTH_COLUMNS.add(col("plateNo", "车çå·ç ", 120)); |
| | | TAXI_AUTH_COLUMNS.add(col("vehicleType", "车è¾ç±»å", 110)); |
| | | TAXI_AUTH_COLUMNS.add(col("fuelType", "çæç±»å", 100)); |
| | | TAXI_AUTH_COLUMNS.add(col("opStatus", "è¥è¿ç¶æ", 100)); |
| | | TAXI_AUTH_COLUMNS.add(col("certStatus", "è¯ä»¶ç¶æ", 100)); |
| | | TAXI_AUTH_COLUMNS.add(col("licenseNo", "ç»è¥æå·", 130)); |
| | | TAXI_AUTH_COLUMNS.add(col("archiveNo", "æ¡£æ¡å·", 130)); |
| | | TAXI_AUTH_COLUMNS.add(col("roadCertNo", "éè·¯è¿è¾è¯åå·", 150)); |
| | | TAXI_AUTH_COLUMNS.add(col("operatorName", "䏿·åç§°", 240)); |
| | | TAXI_AUTH_COLUMNS.add(col("operatorAddr", "䏿·å°å", 280)); |
| | | PAUTH_COLUMNS.add(col("reportPeriod", "æ¥è¡¨æ", 100)); |
| | | PAUTH_COLUMNS.add(col("enterpriseCode", "ä¼ä¸ä»£ç ", 130)); |
| | | PAUTH_COLUMNS.add(col("enterpriseName", "ä¼ä¸åç§°", 240)); |
| | | PAUTH_COLUMNS.add(col("vehicleCount", "è½¦è¾æ°", 100)); |
| | | PAUTH_COLUMNS.add(col("seatCount", "è½½å®¢ä½æ°", 100)); |
| | | } |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.dataimport.entity.CityBusMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * ä¼ä¸è§£éï¼ç¬ç«äºå®¡æ ¸ç»æçè§£éåè¡¨ä¸ AI åæå
¥å£ |
| | | * ä¼ä¸è§£éï¼ç¬ç«äºå®¡æ ¸ç»æçè§£éåè¡¨ä¸ AI åæå
¥å£ã |
| | | * æ¯æå¤æ¥è¡¨ç±»åï¼H2032 è´§è¿ãH2031 å
¬è·¯æ
客ãCITY_BUS åå¸å
¬äº¤ãCITY_TAXI 巡游åºç§ã |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | |
| | | |
| | | @Resource |
| | | private H2032EnterpriseMonthlyMapper h2032Mapper; |
| | | @Resource |
| | | private PassengerEnterpriseMonthlyMapper passengerMapper; |
| | | @Resource |
| | | private CityBusMonthlyMapper cityBusMapper; |
| | | @Resource |
| | | private CityTaxiMonthlyMapper cityTaxiMapper; |
| | | |
| | | @GetMapping("/list") |
| | | public Result<Map<String, Object>> list(@RequestParam(value = "period", required = false) String period, |
| | | public Result<Map<String, Object>> list(@RequestParam(value = "reportType", defaultValue = "H2032") String reportType, |
| | | @RequestParam(value = "period", required = false) String period, |
| | | @RequestParam(value = "keyword", required = false) String keyword, |
| | | @RequestParam(value = "hasExplanation", defaultValue = "false") boolean hasExplanation, |
| | | @RequestParam(value = "page", defaultValue = "1") long page, |
| | | @RequestParam(value = "size", defaultValue = "20") long size) { |
| | | String kw = keyword == null ? "" : keyword.trim(); |
| | | boolean hasPeriod = period != null && !period.trim().isEmpty(); |
| | | |
| | | LambdaQueryWrapper<H2032EnterpriseMonthly> w = new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(hasPeriod, H2032EnterpriseMonthly::getReportPeriod, period) |
| | | .isNotNull(H2032EnterpriseMonthly::getVerifyExplanation) |
| | | .ne(H2032EnterpriseMonthly::getVerifyExplanation, "") |
| | | .like(!kw.isEmpty(), H2032EnterpriseMonthly::getEnterpriseName, kw) |
| | | .orderByDesc(H2032EnterpriseMonthly::getId); |
| | | |
| | | Page<H2032EnterpriseMonthly> p = new Page<>(Math.max(page, 1), Math.min(Math.max(size, 1), 200)); |
| | | Page<H2032EnterpriseMonthly> r = h2032Mapper.selectPage(p, w); |
| | | long p = Math.max(page, 1); |
| | | long s = Math.min(Math.max(size, 1), 200); |
| | | |
| | | List<Map<String, Object>> records = new ArrayList<>(); |
| | | long total = 0; |
| | | |
| | | if ("H2031".equals(reportType)) { |
| | | LambdaQueryWrapper<PassengerEnterpriseMonthly> w = new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(hasPeriod, PassengerEnterpriseMonthly::getReportPeriod, period) |
| | | .isNotNull(hasExplanation, PassengerEnterpriseMonthly::getVerifyExplanation) |
| | | .ne(hasExplanation, PassengerEnterpriseMonthly::getVerifyExplanation, "") |
| | | .like(!kw.isEmpty(), PassengerEnterpriseMonthly::getEnterpriseName, kw) |
| | | .orderByDesc(PassengerEnterpriseMonthly::getId); |
| | | Page<PassengerEnterpriseMonthly> r = passengerMapper.selectPage(new Page<>(p, s), w); |
| | | for (PassengerEnterpriseMonthly e : r.getRecords()) { |
| | | Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(), |
| | | e.getEnterpriseCode(), e.getEnterpriseName(), e.getVerifyExplanation()); |
| | | row.put("vehicleTotal", e.getVehicleTotal()); |
| | | row.put("seatTotal", e.getSeatTotal()); |
| | | row.put("passengerTotal", e.getPassengerTotal()); |
| | | row.put("turnoverTotal", e.getTurnoverTotal()); |
| | | row.put("avgDistance", e.getAvgDistanceTotal()); |
| | | records.add(row); |
| | | } |
| | | total = r.getTotal(); |
| | | } else if ("CITY_BUS".equals(reportType)) { |
| | | LambdaQueryWrapper<CityBusMonthly> w = new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(hasPeriod, CityBusMonthly::getReportPeriod, period) |
| | | .isNotNull(hasExplanation, CityBusMonthly::getVerifyExplanation) |
| | | .ne(hasExplanation, CityBusMonthly::getVerifyExplanation, "") |
| | | .like(!kw.isEmpty(), CityBusMonthly::getEnterpriseName, kw) |
| | | .orderByDesc(CityBusMonthly::getId); |
| | | Page<CityBusMonthly> r = cityBusMapper.selectPage(new Page<>(p, s), w); |
| | | for (CityBusMonthly e : r.getRecords()) { |
| | | Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(), |
| | | e.getEnterpriseCode(), e.getEnterpriseName(), e.getVerifyExplanation()); |
| | | row.put("city", e.getCity()); |
| | | row.put("opVehicles", e.getOpVehicles()); |
| | | row.put("passengerVolume", e.getPassengerVolume()); |
| | | row.put("turnover", e.getTurnover()); |
| | | row.put("avgDistance", e.getAvgDistance()); |
| | | records.add(row); |
| | | } |
| | | total = r.getTotal(); |
| | | } else if ("CITY_TAXI".equals(reportType)) { |
| | | LambdaQueryWrapper<CityTaxiMonthly> w = new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(hasPeriod, CityTaxiMonthly::getReportPeriod, period) |
| | | .isNotNull(hasExplanation, CityTaxiMonthly::getVerifyExplanation) |
| | | .ne(hasExplanation, CityTaxiMonthly::getVerifyExplanation, "") |
| | | .like(!kw.isEmpty(), CityTaxiMonthly::getCity, kw) |
| | | .orderByDesc(CityTaxiMonthly::getId); |
| | | Page<CityTaxiMonthly> r = cityTaxiMapper.selectPage(new Page<>(p, s), w); |
| | | for (CityTaxiMonthly e : r.getRecords()) { |
| | | Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(), |
| | | null, e.getCity(), e.getVerifyExplanation()); |
| | | row.put("city", e.getCity()); |
| | | row.put("opVehicles", e.getOpVehicles()); |
| | | row.put("tripTotal", e.getTripTotal()); |
| | | row.put("passengerVolume", e.getPassengerVolume()); |
| | | row.put("turnover", e.getTurnover()); |
| | | row.put("avgDistance", e.getAvgDistance()); |
| | | records.add(row); |
| | | } |
| | | total = r.getTotal(); |
| | | } else { |
| | | LambdaQueryWrapper<H2032EnterpriseMonthly> w = new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(hasPeriod, H2032EnterpriseMonthly::getReportPeriod, period) |
| | | .isNotNull(hasExplanation, H2032EnterpriseMonthly::getVerifyExplanation) |
| | | .ne(hasExplanation, H2032EnterpriseMonthly::getVerifyExplanation, "") |
| | | .like(!kw.isEmpty(), H2032EnterpriseMonthly::getEnterpriseName, kw) |
| | | .orderByDesc(H2032EnterpriseMonthly::getId); |
| | | Page<H2032EnterpriseMonthly> r = h2032Mapper.selectPage(new Page<>(p, s), w); |
| | | for (H2032EnterpriseMonthly e : r.getRecords()) { |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("id", e.getId()); |
| | | row.put("reportPeriod", e.getReportPeriod()); |
| | | row.put("regionCode", e.getRegionCode()); |
| | | row.put("enterpriseCode", e.getEnterpriseCode()); |
| | | row.put("enterpriseName", e.getEnterpriseName()); |
| | | Map<String, Object> row = baseRow(e.getId(), e.getReportPeriod(), e.getRegionCode(), |
| | | e.getEnterpriseCode(), e.getEnterpriseName(), e.getVerifyExplanation()); |
| | | row.put("vehicleTotal", e.getVehicleTotal()); |
| | | row.put("tonsTotal", e.getTonsTotal()); |
| | | row.put("freightTotal", e.getFreightTotal()); |
| | | row.put("turnoverTotal", e.getTurnoverTotal()); |
| | | row.put("avgDistance", e.getAvgDistance()); |
| | | row.put("verifyExplanation", e.getVerifyExplanation()); |
| | | records.add(row); |
| | | } |
| | | total = r.getTotal(); |
| | | } |
| | | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("reportType", reportType); |
| | | result.put("records", records); |
| | | result.put("total", r.getTotal()); |
| | | result.put("current", r.getCurrent()); |
| | | result.put("size", r.getSize()); |
| | | result.put("total", total); |
| | | result.put("current", p); |
| | | result.put("size", s); |
| | | return Result.ok(result); |
| | | } |
| | | |
| | | /** |
| | | * ä¿åä¼ä¸è§£éï¼æ´æ°å¯¹åºæ¥æºè¡¨ï¼H2032/H2031/CITY_BUS/CITY_TAXIï¼è¡ç verifyExplanationã |
| | | * å®¡æ ¸ç»æé¡µå
åµè§£é模åä¸ãä¼ä¸è§£éã页å
±ç¨æ¤æ¥å£ã |
| | | */ |
| | | @PostMapping("/save") |
| | | public Result<String> save(@RequestParam("reportType") String reportType, |
| | | @RequestParam("reportId") Long reportId, |
| | | @RequestParam(value = "explanation", required = false) String explanation) { |
| | | String exp = explanation == null ? "" : explanation.trim(); |
| | | boolean ok = false; |
| | | String t = reportType == null ? "" : reportType; |
| | | try { |
| | | switch (t) { |
| | | case "H2032": { |
| | | H2032EnterpriseMonthly e = h2032Mapper.selectById(reportId); |
| | | if (e != null) { e.setVerifyExplanation(exp); h2032Mapper.updateById(e); ok = true; } |
| | | break; |
| | | } |
| | | case "H2031": { |
| | | PassengerEnterpriseMonthly e = passengerMapper.selectById(reportId); |
| | | if (e != null) { e.setVerifyExplanation(exp); passengerMapper.updateById(e); ok = true; } |
| | | break; |
| | | } |
| | | case "CITY_BUS": { |
| | | CityBusMonthly e = cityBusMapper.selectById(reportId); |
| | | if (e != null) { e.setVerifyExplanation(exp); cityBusMapper.updateById(e); ok = true; } |
| | | break; |
| | | } |
| | | case "CITY_TAXI": { |
| | | CityTaxiMonthly e = cityTaxiMapper.selectById(reportId); |
| | | if (e != null) { e.setVerifyExplanation(exp); cityTaxiMapper.updateById(e); ok = true; } |
| | | break; |
| | | } |
| | | default: |
| | | throw new RuntimeException("䏿¯æçæ¥è¡¨ç±»åï¼" + reportType); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("explain save error", e); |
| | | return Result.error("ä¿å失败: " + e.getMessage()); |
| | | } |
| | | if (!ok) { |
| | | return Result.error("æªæ¾å°å¯¹åºçæ°æ®è¡ï¼reportId=" + reportId + "ï¼"); |
| | | } |
| | | return Result.ok(exp); |
| | | } |
| | | |
| | | private Map<String, Object> baseRow(Long id, String reportPeriod, String regionCode, |
| | | String enterpriseCode, String enterpriseName, String verifyExplanation) { |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("id", id); |
| | | row.put("reportPeriod", reportPeriod); |
| | | row.put("regionCode", regionCode); |
| | | row.put("enterpriseCode", enterpriseCode); |
| | | row.put("enterpriseName", enterpriseName); |
| | | row.put("verifyExplanation", verifyExplanation); |
| | | return row; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * åå¸å®¢è¿ç¬¬1æ¥ï¼åå¸å
¬äº¤ä¼ä¸æåº¦æ°æ® |
| | | * æ°æ®æºï¼å¯¼å
¥_åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼YYYYå¹´Mæï¼.xlsï¼ä¼ä¸çº§æç»ï¼ä»
åå
Œ
±æ±½çµè½¦åæ®µï¼ |
| | | */ |
| | | @Data |
| | | @TableName("city_bus_monthly") |
| | | public class CityBusMonthly { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ 2026-07 */ |
| | | private String reportPeriod; |
| | | /** æå±å°åºï¼è¡æ¿åºå代ç ï¼ */ |
| | | private String regionCode; |
| | | /** å¸å·ï¼ç±å°åºä»£ç æ å°ï¼ */ |
| | | private String city; |
| | | /** ä¼ä¸ä»£ç */ |
| | | private String enterpriseCode; |
| | | /** ä¼ä¸åç§° */ |
| | | private String enterpriseName; |
| | | |
| | | /** å
Œ
±æ±½çµè½¦è¿è¥è½¦æ° */ |
| | | private Double opVehicles; |
| | | /** è¿è¥ç«ç¹æ»ä¸ªæ° */ |
| | | private Double stopTotal; |
| | | /** å
¶ä¸ï¼åå¸å
è¿è¥ç«ç¹ */ |
| | | private Double stopCity; |
| | | /** å
Œ
±æ±½çµè½¦å®¢è¿éï¼ä¸äººæ¬¡ï¼ */ |
| | | private Double passengerVolume; |
| | | /** å
¶ä¸ï¼åå¸å
客è¿éï¼ä¸äººæ¬¡ï¼ */ |
| | | private Double passengerCity; |
| | | /** å
Œ
±æ±½çµè½¦æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ */ |
| | | private Double turnover; |
| | | /** å
¶ä¸ï¼åå¸å
æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ */ |
| | | private Double turnoverCity; |
| | | /** å
Œ
±æ±½çµè½¦å¹³åè¿è·ï¼å
¬éï¼ */ |
| | | private Double avgDistance; |
| | | /** åå¸å
å¹³åè¿è·ï¼å
¬éï¼ */ |
| | | private Double avgDistanceCity; |
| | | /** åé
å乡客è¿éï¼ä¸äººæ¬¡ï¼ */ |
| | | private Double passengerChengxiang; |
| | | /** åé
å乡æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ */ |
| | | private Double turnoverChengxiang; |
| | | /** åé
å乡平åè¿è·ï¼å
¬éï¼ */ |
| | | private Double avgDistanceChengxiang; |
| | | |
| | | /** è¿è¥è½¦æ°ç¯æ¯å¢é */ |
| | | private Double rateOpVehiclesMom; |
| | | /** 客è¿éç¯æ¯å¢é */ |
| | | private Double ratePassengerMom; |
| | | /** åå¸å
客è¿éç¯æ¯ */ |
| | | private Double ratePassengerCityMom; |
| | | /** åé
å乡客è¿éç¯æ¯å¢é */ |
| | | private Double rateChengxiangMom; |
| | | /** åé
å乡æ
客å¨è½¬éç¯æ¯å¢é */ |
| | | private Double rateChengxiangTurnoverMom; |
| | | /** æ
客å¨è½¬éç¯æ¯å¢é */ |
| | | private Double rateTurnoverMom; |
| | | /** å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼ */ |
| | | private Double avgDistanceMomChange; |
| | | /** åå¸å
å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼ */ |
| | | private Double avgDistanceCityMomChange; |
| | | /** å车æ¥å客è¿é */ |
| | | private Double dailyPassengerPerVehicle; |
| | | |
| | | /** è¿è¥è½¦æ°åæ¯å¢é */ |
| | | private Double rateOpVehiclesYoy; |
| | | /** 客è¿é忝å¢é */ |
| | | private Double ratePassengerYoy; |
| | | /** åå¸å
客è¿é忝å¢é */ |
| | | private Double ratePassengerCityYoy; |
| | | /** åé
å乡客è¿é忝å¢é */ |
| | | private Double rateChengxiangYoy; |
| | | /** åé
å乡æ
客å¨è½¬é忝å¢é */ |
| | | private Double rateChengxiangTurnoverYoy; |
| | | /** æ
客å¨è½¬é忝å¢é */ |
| | | private Double rateTurnoverYoy; |
| | | |
| | | /** åå¸å
è¿è¥ç«ç¹å æ¯ */ |
| | | private Double stopCityRatio; |
| | | /** åå¸å
客è¿éå æ¯ */ |
| | | private Double passengerCityRatio; |
| | | /** åé
å乡客è¿éå æ¯ */ |
| | | private Double chengxiangRatio; |
| | | |
| | | /** 填表人 */ |
| | | private String contactPerson; |
| | | /** èç³»çµè¯ */ |
| | | private String contactPhone; |
| | | /** æ¥åºæ¥æ */ |
| | | private String reportDate; |
| | | /** æ ¸å®æ§åå è§£é */ |
| | | private String verifyExplanation; |
| | | /** 䏿¥è¯´æ */ |
| | | private String reportNote; |
| | | /** ä¿®æ¹è®°å½ */ |
| | | private String modifyRecord; |
| | | |
| | | /** 轨é交é_客è¿éï¼ä¸äººæ¬¡ï¼ */ |
| | | private Double railPassengerVolume; |
| | | /** 轨é交é_æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ */ |
| | | private Double railTurnover; |
| | | /** åå¸å®¢è¿è½®æ¸¡_客è¿éï¼ä¸äººæ¬¡ï¼ */ |
| | | private Double ferryPassengerVolume; |
| | | /** åå¸å®¢è¿è½®æ¸¡_æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ */ |
| | | private Double ferryTurnover; |
| | | |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * åå¸å®¢è¿ç¬¬2æ¥ï¼åºç§è½¦è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦è¾çº§æç»ï¼ |
| | | * æ°æ®æºï¼è¾å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xls |
| | | */ |
| | | @Data |
| | | @TableName("city_taxi_auth") |
| | | public class CityTaxiAuth { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ */ |
| | | private String reportPeriod; |
| | | /** å¸å·ï¼ç±è¯å·/æ¡£æ¡å·/å°åæåï¼ */ |
| | | private String city; |
| | | /** 车çå·ç */ |
| | | private String plateNo; |
| | | /** 车è¾ç±»å */ |
| | | private String vehicleType; |
| | | /** ç»è¥ç±»å */ |
| | | private String bizType; |
| | | /** 车主åç§° */ |
| | | private String ownerName; |
| | | /** ç»è¥æå· */ |
| | | private String licenseNo; |
| | | /** æ¡£æ¡å· */ |
| | | private String archiveNo; |
| | | /** éè·¯è¿è¾è¯åå· */ |
| | | private String roadCertNo; |
| | | /** çæç±»å */ |
| | | private String fuelType; |
| | | /** 䏿·åç§° */ |
| | | private String operatorName; |
| | | /** 䏿·å°å */ |
| | | private String operatorAddr; |
| | | /** è¥è¿ç¶æ */ |
| | | private String opStatus; |
| | | /** è¯ä»¶ç¶æ */ |
| | | private String certStatus; |
| | | |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * åå¸å®¢è¿ç¬¬2æ¥ï¼å·¡æ¸¸åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼å¸å·çº§ï¼ |
| | | * æ°æ®æºï¼å¯¼å
¥_巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼YYYYå¹´Mæï¼.xls |
| | | */ |
| | | @Data |
| | | @TableName("city_taxi_monthly") |
| | | public class CityTaxiMonthly { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ 2026-07 */ |
| | | private String reportPeriod; |
| | | /** æå±å°åºä»£ç */ |
| | | private String regionCode; |
| | | /** å¸å· */ |
| | | private String city; |
| | | |
| | | /** è½½å®¢è½¦æ¬¡æ»æ° */ |
| | | private Double tripTotal; |
| | | /** è½½å®¢è½¦æ¬¡æ»æ°_åå¸å
*/ |
| | | private Double tripCity; |
| | | /** 客è¿éï¼ä¸äººæ¬¡ï¼ */ |
| | | private Double passengerVolume; |
| | | /** 客è¿é_å
¶ä¸åå¸å
*/ |
| | | private Double passengerCity; |
| | | /** æ
客å¨è½¬éï¼ä¸äººå
¬éï¼ */ |
| | | private Double turnover; |
| | | /** æ
客å¨è½¬é_åå¸å
*/ |
| | | private Double turnoverCity; |
| | | /** è¿è¥è½¦è¾æ° */ |
| | | private Double opVehicles; |
| | | /** 巡游åºç§æ±½è½¦å¹³åè¿è·ï¼å
¬éï¼ */ |
| | | private Double avgDistance; |
| | | /** åå¸å
å¹³åè¿è·ï¼å
¬éï¼ */ |
| | | private Double avgDistanceCity; |
| | | /** å车æ¥å载客车次 */ |
| | | private Double dailyTripsPerVehicle; |
| | | /** å车æ¥å客è¿é */ |
| | | private Double dailyPassengerPerVehicle; |
| | | /** åè½¦æ¬¡è½½å®¢äººæ° */ |
| | | private Double passengersPerTrip; |
| | | /** å车次载客人æ°_åå¸å
*/ |
| | | private Double passengersPerTripCity; |
| | | /** å车次载客人æ°_åé
å乡 */ |
| | | private Double passengersPerTripChengxiang; |
| | | |
| | | /** è¿è¥è½¦æ°ç¯æ¯å¢é */ |
| | | private Double rateOpVehiclesMom; |
| | | /** è¿è¥è½¦æ°åæ¯å¢é */ |
| | | private Double rateOpVehiclesYoy; |
| | | /** è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯å¢é */ |
| | | private Double rateTripMom; |
| | | /** è½½å®¢è½¦æ¬¡åæ¯å¢é */ |
| | | private Double rateTripYoy; |
| | | /** 客è¿éç¯æ¯å¢é */ |
| | | private Double ratePassengerMom; |
| | | /** 客è¿é忝å¢é */ |
| | | private Double ratePassengerYoy; |
| | | /** å¹³åè¿è·ç¯æ¯å¢åï¼å
¬éï¼ */ |
| | | private Double avgDistanceMomChange; |
| | | /** å¹³åè¿è·_åå¸å
ç¯æ¯å¢åï¼å
¬éï¼ */ |
| | | private Double avgDistanceCityMomChange; |
| | | /** è½½å®¢è½¦æ¬¡æ»æ°ç¯æ¯å¢é_åå¸å
*/ |
| | | private Double rateTripCityMom; |
| | | /** 客è¿éç¯æ¯å¢é_åå¸å
*/ |
| | | private Double ratePassengerCityMom; |
| | | /** è½½å®¢è½¦æ¬¡æ»æ°_åé
åä¹¡ç¯æ¯å¢é */ |
| | | private Double rateTripChengxiangMom; |
| | | /** 客è¿é_åé
åä¹¡ç¯æ¯å¢é */ |
| | | private Double ratePassengerChengxiangMom; |
| | | /** å¹³åè¿è·_åé
å乡ï¼å
¬éï¼ */ |
| | | private Double avgDistanceChengxiang; |
| | | |
| | | /** è½½å®¢è½¦æ¬¡æ»æ°_åå¸å
å æ¯ */ |
| | | private Double tripCityRatio; |
| | | /** 客è¿é_åå¸å
å æ¯ */ |
| | | private Double passengerCityRatio; |
| | | |
| | | private String contactPerson; |
| | | private String contactPhone; |
| | | private String reportDate; |
| | | private String verifyExplanation; |
| | | private String reportNote; |
| | | private String modifyRecord; |
| | | |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * é路货è¿è½è_è¿æ¿è½¦è¾ä¿¡æ¯ï¼è½¦çç²åº¦ï¼ |
| | | */ |
| | | @Data |
| | | @TableName("h204_auth_vehicle") |
| | | public class EnergyAuthVehicle { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ */ |
| | | private String reportPeriod; |
| | | /** 车çå· */ |
| | | private String plateNo; |
| | | /** 车è¾ç±»å */ |
| | | private String vehicleType; |
| | | /** çæç±»å */ |
| | | private String fuelType; |
| | | /** æ è®°å¨ä½ï¼å¨ï¼ */ |
| | | private Double markedTonnage; |
| | | /** åçµå¼è´¨éï¼çµå¼è½¦æ è®°å¨ä½ï¼ */ |
| | | private Double tractionQuality; |
| | | private Long importBatchId; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * 交ä¼ç»H204表ï¼é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼è½¦è¾ç²åº¦å£æ¥ï¼ |
| | | */ |
| | | @Data |
| | | @TableName("h204_vehicle_quarterly") |
| | | public class EnergyVehicleQuarterly { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ 2026-06ï¼å£åº¦æ«æï¼ */ |
| | | private String reportPeriod; |
| | | /** æå±å°åºï¼è¡æ¿åºå代ç ï¼ */ |
| | | private String regionCode; |
| | | /** ä¼ä¸ä»£ç */ |
| | | private String enterpriseCode; |
| | | /** ä¼ä¸åç§° */ |
| | | private String enterpriseName; |
| | | /** 车çå· */ |
| | | private String plateNo; |
| | | /** 车è¾ç±»å代ç */ |
| | | private String vehicleTypeCode; |
| | | /** 车è¾ç±»åï¼ä¸æï¼ */ |
| | | private String vehicleType; |
| | | /** çæç±»å代ç */ |
| | | private String fuelTypeCode; |
| | | /** çæç±»åï¼ä¸æï¼ */ |
| | | private String fuelType; |
| | | /** åºå年份 */ |
| | | private Integer manufactureYear; |
| | | /** æ è®°å¨ä½ï¼å¨ï¼ */ |
| | | private Double markedTonnage; |
| | | /** æ»è¡ç¨ï¼å
¬éï¼ */ |
| | | private Double totalMileage; |
| | | /** 载货éç¨ï¼å
¬éï¼ */ |
| | | private Double loadedMileage; |
| | | /** 空驶éç¨ï¼å
¬éï¼ */ |
| | | private Double emptyMileage; |
| | | /** è´§è¿éï¼å¨ï¼ */ |
| | | private Double freight; |
| | | /** è´§ç©å¨è½¬éï¼å¨å
¬éï¼ */ |
| | | private Double turnover; |
| | | /** è¶æ¬¡æ°ï¼ä¸ªï¼ */ |
| | | private Integer tripCount; |
| | | /** çæ1计éåä½ */ |
| | | private String fuelUnit; |
| | | /** çæ1æ¶èé */ |
| | | private Double fuelConsumption; |
| | | /** å¹³åè¿è·ï¼å
¬éï¼ */ |
| | | private Double avgDistance; |
| | | /** å¹³å载货å¨ä½ï¼å¨ï¼ */ |
| | | private Double avgLoadedTonnage; |
| | | /** åè¶å¹³åéç¨ï¼å
¬éï¼ */ |
| | | private Double avgTripMileage; |
| | | /** ç¾å
¬éåè */ |
| | | @TableField("fuel_per_100km") |
| | | private Double fuelPer100km; |
| | | /** å¨è½¬éåè */ |
| | | private Double turnoverFuel; |
| | | private Long importBatchId; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æèµé¡¹ç®æåº¦å®ææ
åµ |
| | | */ |
| | | @Data |
| | | @TableName("investment_monthly") |
| | | public class InvestmentMonthly { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ 2026-07 */ |
| | | private String reportPeriod; |
| | | /** 项ç®ä¸»æ¡£ID */ |
| | | private Long projectId; |
| | | /** æ¬å¹´è®¡åæèµï¼ä¸å
ï¼ */ |
| | | private Double yearPlan; |
| | | /** èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼ */ |
| | | private Double yearCum; |
| | | /** å
¶ä¸ï¼æ¬æå®ææèµï¼ä¸å
ï¼ */ |
| | | private Double monthDone; |
| | | /** èªå¼å§å»ºè®¾ç´¯è®¡å®ææèµï¼ä¸å
ï¼ */ |
| | | private Double startCum; |
| | | /** å»ºè®¾é¶æ®µ */ |
| | | private String progressStage; |
| | | /** 形象è¿åº¦ */ |
| | | private String progressDesc; |
| | | /** 建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼ */ |
| | | private Double buildingArea; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æèµé¡¹ç®ä¸»æ¡£ï¼å®¢è¿ç«åº / ç©æµååºï¼ |
| | | */ |
| | | @Data |
| | | @TableName("investment_project") |
| | | public class InvestmentProject { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** 项ç®åç§° */ |
| | | private String projectName; |
| | | /** ç±»å«ï¼å®¢è¿ç«åº / ç©æµååº */ |
| | | private String category; |
| | | /** å¸å· */ |
| | | private String city; |
| | | /** å¿ï¼å¸ãåºï¼ */ |
| | | private String county; |
| | | /** æèµå»ºè®¾åä½ */ |
| | | private String builderName; |
| | | /** 建设æ§è´¨ */ |
| | | private String constructNature; |
| | | /** å¼å·¥æ¶é´ */ |
| | | private String startTime; |
| | | /** 建æ/竣工æ¶é´ */ |
| | | private String endTime; |
| | | /** è®¡åæ»æèµï¼ä¸å
ï¼ */ |
| | | private Double totalInvestment; |
| | | /** 工坿¹å¤ */ |
| | | private String approvalGk; |
| | | /** å设æ¹å¤ */ |
| | | private String approvalCs; |
| | | /** 建çé¢ç§¯ï¼å¹³æ¹ç±³ï¼ */ |
| | | private Double buildingArea; |
| | | /** 亿å
æèµé¡¹ç®æ è®° */ |
| | | private Integer isBillion; |
| | | /** ç»æµå¼ºå¿é¡¹ç®æ è®° */ |
| | | private Integer isCounty; |
| | | /** åäºäºéç¹ç©æµé¡¹ç®æ è®° */ |
| | | private Integer isFiveYear; |
| | | /** æ°æ®æ¥æº */ |
| | | private String source; |
| | | private Long importBatchId; |
| | | private LocalDateTime createdAt; |
| | | private LocalDateTime updatedAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * æèµç³»ç»æ¥è¯¢å¯¼åºï¼å®¡æ ¸æ¯å¯¹åºåï¼ |
| | | */ |
| | | @Data |
| | | @TableName("investment_system") |
| | | public class InvestmentSystem { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ 2026-07 */ |
| | | private String reportPeriod; |
| | | /** 项ç®åç§°ï¼åä½ï¼ */ |
| | | private String projectName; |
| | | /** è®¡åæ»æèµï¼ä¸å
ï¼ */ |
| | | private Double totalInvestment; |
| | | /** èªå¼å§å»ºè®¾è³æ¬æç´¯è®¡å®ææèµï¼ä¸å
ï¼ */ |
| | | private Double startCum; |
| | | /** æ¬å¹´è®¡åæèµï¼ä¸å
ï¼ */ |
| | | private Double yearPlan; |
| | | /** èªå¹´åç´¯è®¡å®ææèµï¼ä¸å
ï¼ */ |
| | | private Double yearCum; |
| | | /** 彿宿æèµï¼ä¸å
ï¼ */ |
| | | private Double monthDone; |
| | | private Long importBatchId; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°æ®ï¼è½¦è¾æ°/è½½å®¢ä½æ°ï¼ |
| | | */ |
| | | @Data |
| | | @TableName("passenger_auth_vehicle") |
| | | public class PassengerAuthVehicle { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private String reportPeriod; |
| | | /** ä¼ä¸ä»£ç */ |
| | | private String enterpriseCode; |
| | | /** ä¼ä¸åç§° */ |
| | | private String enterpriseName; |
| | | /** è½¦è¾æ° */ |
| | | private Integer vehicleCount; |
| | | /** è½½å®¢ä½æ° */ |
| | | private Integer seatCount; |
| | | private Long importBatchId; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * 交ä¼ç»H203-1表ï¼å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ |
| | | */ |
| | | @Data |
| | | @TableName("h2031_enterprise_monthly") |
| | | public class PassengerEnterpriseMonthly { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private String reportPeriod; |
| | | /** æå±å°åºï¼è¡æ¿åºå代ç ï¼ */ |
| | | private String regionCode; |
| | | /** ä¼ä¸ä»£ç */ |
| | | private String enterpriseCode; |
| | | /** ä¼ä¸åç§° */ |
| | | private String enterpriseName; |
| | | |
| | | /** 客è¿è½¦è¾_æ»è®¡ */ |
| | | private Integer vehicleTotal; |
| | | /** æ ¸å®è½½å®¢ä½_æ»è®¡ */ |
| | | private Integer seatTotal; |
| | | /** è½¦è¾æ°_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼ */ |
| | | private Integer vehicleSchedule; |
| | | /** æ ¸å®è½½å®¢ä½_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼ */ |
| | | private Integer seatSchedule; |
| | | /** è½¦è¾æ°_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼ */ |
| | | private Integer vehicleCharter; |
| | | /** æ ¸å®è½½å®¢ä½_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼ */ |
| | | private Integer seatCharter; |
| | | |
| | | /** 客è¿é_æ»è®¡ */ |
| | | private Double passengerTotal; |
| | | /** æ
客å¨è½¬é_æ»è®¡ */ |
| | | private Double turnoverTotal; |
| | | |
| | | /** 客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
æ¸¸ï¼ */ |
| | | private Double passengerClass1; |
| | | private Double turnoverClass1; |
| | | /** 客è¿é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
æ¸¸ï¼ */ |
| | | private Double passengerClass2; |
| | | private Double turnoverClass2; |
| | | /** 客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
æ¸¸ï¼ */ |
| | | private Double passengerClass3; |
| | | private Double turnoverClass3; |
| | | /** 客è¿é_å类客è¿ç线ï¼å«å®çº¿æ
æ¸¸ï¼ */ |
| | | private Double passengerClass4; |
| | | private Double turnoverClass4; |
| | | /** 客è¿é_客è¿å
车ï¼å«éå®çº¿æ
æ¸¸ï¼ */ |
| | | private Double passengerCharter; |
| | | private Double turnoverCharter; |
| | | |
| | | /** 客è¿å¹³åè¿è· */ |
| | | private Double avgDistanceTotal; |
| | | /** ä¸ç±»å®¢è¿ç线平åè¿è· */ |
| | | private Double avgDistanceClass1; |
| | | private Double avgDistanceClass2; |
| | | private Double avgDistanceClass3; |
| | | private Double avgDistanceClass4; |
| | | /** æ
游ãå
车平åè¿è· */ |
| | | private Double avgDistanceCharter; |
| | | |
| | | private String verifyExplanation; |
| | | private Long importBatchId; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * å
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬éï¼ä¼ä¸çº¿ä¸æ±æ¥ï¼ç¨æ·å¯¼å
¥åæå¸å·æ±æ»å¡«å
åå¸å·è¡¨ä¸ªä½è¡ï¼ |
| | | */ |
| | | @Data |
| | | @TableName("passenger_individual_monthly") |
| | | public class PassengerIndividualMonthly { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | /** æ¥è¡¨æ 2026-07 */ |
| | | private String reportPeriod; |
| | | /** æå±å°åºï¼è¡æ¿åºå代ç ï¼ */ |
| | | private String regionCode; |
| | | /** ä¼ä¸ä»£ç */ |
| | | private String enterpriseCode; |
| | | /** ä¼ä¸åç§° */ |
| | | private String enterpriseName; |
| | | /** å
¶ä¸ä¸ªä½å®¢è¿éï¼äººï¼ */ |
| | | private Double passengerCount; |
| | | /** å
¶ä¸ä¸ªä½æ
客å¨è½¬éï¼äººå
¬éï¼ */ |
| | | private Double turnover; |
| | | private LocalDateTime createdAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.CityBusMonthly; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface CityBusMonthlyMapper extends BaseMapper<CityBusMonthly> {} |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiAuth; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface CityTaxiAuthMapper extends BaseMapper<CityTaxiAuth> {} |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface CityTaxiMonthlyMapper extends BaseMapper<CityTaxiMonthly> {} |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.EnergyAuthVehicle; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface EnergyAuthVehicleMapper extends BaseMapper<EnergyAuthVehicle> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface EnergyVehicleQuarterlyMapper extends BaseMapper<EnergyVehicleQuarterly> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.InvestmentMonthly; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface InvestmentMonthlyMapper extends BaseMapper<InvestmentMonthly> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.InvestmentProject; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface InvestmentProjectMapper extends BaseMapper<InvestmentProject> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.InvestmentSystem; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface InvestmentSystemMapper extends BaseMapper<InvestmentSystem> { |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.PassengerAuthVehicle; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface PassengerAuthVehicleMapper extends BaseMapper<PassengerAuthVehicle> {} |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface PassengerEnterpriseMonthlyMapper extends BaseMapper<PassengerEnterpriseMonthly> {} |
| New file |
| | |
| | | package com.trafficaudit.dataimport.mapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | @Mapper |
| | | public interface PassengerIndividualMonthlyMapper extends BaseMapper<PassengerIndividualMonthly> {} |
| | |
| | | import com.trafficaudit.auditengine.entity.AuditResult; |
| | | import com.trafficaudit.auditengine.mapper.AuditResultMapper; |
| | | import com.trafficaudit.common.util.RegionUtil; |
| | | import com.trafficaudit.dataimport.entity.EnergyAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly; |
| | | import com.trafficaudit.dataimport.entity.FreightTurnoverImport; |
| | | import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly; |
| | | import com.trafficaudit.dataimport.entity.ImportBatch; |
| | | import com.trafficaudit.dataimport.entity.InvestmentMonthly; |
| | | import com.trafficaudit.dataimport.entity.InvestmentProject; |
| | | import com.trafficaudit.dataimport.entity.InvestmentSystem; |
| | | import com.trafficaudit.dataimport.entity.CityBusMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiAuth; |
| | | import com.trafficaudit.dataimport.entity.ScaleSplitTransport; |
| | | import com.trafficaudit.dataimport.entity.TransportAuthVehicle; |
| | | import com.trafficaudit.dataimport.entity.VehicleTrackMileage; |
| | | import com.trafficaudit.dataimport.mapper.EnergyAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper; |
| | | import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper; |
| | | import com.trafficaudit.rulemanage.mapper.AuditRuleMapper; |
| | | import com.trafficaudit.dataimport.mapper.ImportBatchMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentSystemMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper; |
| | | import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper; |
| | | import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper; |
| | | import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.ibatis.session.ExecutorType; |
| | | import org.apache.ibatis.session.SqlSession; |
| | | import org.apache.ibatis.session.SqlSessionFactory; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellType; |
| | | import org.apache.poi.ss.usermodel.DataFormatter; |
| | |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.ss.usermodel.WorkbookFactory; |
| | | import cn.hutool.core.io.IoUtil; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | |
| | | import java.util.Set; |
| | | import com.trafficaudit.dataimport.dto.ImportResult; |
| | | import org.apache.poi.ss.usermodel.DateUtil; |
| | | import java.util.Comparator; |
| | | import java.util.Arrays; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | |
| | |
| | | public class DataImportService { |
| | | |
| | | private static final DataFormatter FORMATTER = new DataFormatter(); |
| | | |
| | | /** H204 è½èæç»è½¦è¾ç±»åç -> 䏿 */ |
| | | private static final Map<String, String> ENERGY_VEHICLE_TYPE_NAMES = new HashMap<>(); |
| | | |
| | | /** H204 è½èæç»çæç±»åç -> 䏿 */ |
| | | private static final Map<String, String> ENERGY_FUEL_TYPE_NAMES = new HashMap<>(); |
| | | |
| | | /** H204 çæ1计éåä½ç -> 䏿åä½ */ |
| | | private static final Map<String, String> ENERGY_FUEL_UNIT_NAMES = new HashMap<>(); |
| | | |
| | | static { |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("01", "æ®é货车"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("02", "å¹³æ¿è´§è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("03", "仿
å¼è´§è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("04", "å¢å¼è´§è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("06", "ç½å¼è´§è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("07", "ç¹æ®ç»æè´§è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("08", "èªå¸è´§è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("09", "车è¾è¿è¾è½¦"); |
| | | ENERGY_VEHICLE_TYPE_NAMES.put("11", "éååæçµå¼è½¦"); |
| | | |
| | | ENERGY_FUEL_TYPE_NAMES.put("01", "汽油"); |
| | | ENERGY_FUEL_TYPE_NAMES.put("02", "æ´æ²¹"); |
| | | ENERGY_FUEL_TYPE_NAMES.put("03", "åç¼©å¤©ç¶æ°"); |
| | | ENERGY_FUEL_TYPE_NAMES.put("04", "æ¶²åå¤©ç¶æ°"); |
| | | ENERGY_FUEL_TYPE_NAMES.put("07", "çµå¨"); |
| | | ENERGY_FUEL_TYPE_NAMES.put("08", "æ°¢æ°"); |
| | | |
| | | ENERGY_FUEL_UNIT_NAMES.put("1", "å"); |
| | | ENERGY_FUEL_UNIT_NAMES.put("2", "å"); |
| | | ENERGY_FUEL_UNIT_NAMES.put("4", "åå
"); |
| | | ENERGY_FUEL_UNIT_NAMES.put("5", "åå
"); |
| | | ENERGY_FUEL_UNIT_NAMES.put("7", "åç¦æ¶"); |
| | | } |
| | | |
| | | @Resource |
| | | private H2032EnterpriseMonthlyMapper h2032Mapper; |
| | |
| | | private FreightTurnoverImportMapper freightTurnoverMapper; |
| | | @Resource |
| | | private AuditResultMapper auditResultMapper; |
| | | @Resource |
| | | private PassengerEnterpriseMonthlyMapper passengerMapper; |
| | | @Resource |
| | | private PassengerIndividualMonthlyMapper passengerIndividualMapper; |
| | | @Resource |
| | | private PassengerAuthVehicleMapper passengerAuthMapper; |
| | | @Resource |
| | | private AuditRuleMapper ruleMapper; |
| | | @Resource |
| | | private EnergyVehicleQuarterlyMapper energyMapper; |
| | | @Resource |
| | | private EnergyAuthVehicleMapper energyAuthMapper; |
| | | @Resource |
| | | private InvestmentProjectMapper investProjectMapper; |
| | | @Resource |
| | | private InvestmentMonthlyMapper investMonthlyMapper; |
| | | @Resource |
| | | private InvestmentSystemMapper investSystemMapper; |
| | | @Resource |
| | | private CityBusMonthlyMapper cityBusMapper; |
| | | @Resource |
| | | private CityTaxiMonthlyMapper cityTaxiMapper; |
| | | @Resource |
| | | private CityTaxiAuthMapper cityTaxiAuthMapper; |
| | | @Resource |
| | | private SqlSessionFactory sqlSessionFactory; |
| | | |
| | | // ========== H2032 ææ¥ ========== |
| | | |
| | |
| | | throw e; |
| | | } |
| | | |
| | | // å¹çï¼åæ¥è¡¨æéæ°å¯¼å
¥ï¼å
å 餿§æ°æ® |
| | | // å¹çï¼åæ¥è¡¨æéæ°å¯¼å
¥ï¼å
å 餿§æ°æ®ï¼åªæ¸
H2032 è§åçå®¡æ ¸ç»æï¼ |
| | | h2032Mapper.delete(new LambdaQueryWrapper<H2032EnterpriseMonthly>() |
| | | .eq(H2032EnterpriseMonthly::getReportPeriod, period)); |
| | | List<Long> h2032RuleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if ("H2032".equals(rule.getReportType())) h2032RuleIds.add(rule.getId()); |
| | | } |
| | | if (!h2032RuleIds.isEmpty()) { |
| | | auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period)); |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, h2032RuleIds)); |
| | | } |
| | | |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | |
| | | // ========== H203-1 å
¬è·¯æ
å®¢ææ¥ï¼å«è¿æ¿è½¦è¾æ°sheetï¼ ========== |
| | | |
| | | public ImportResult importPassengerMonthly(MultipartFile file, String period) throws Exception { |
| | | List<PassengerRow> rows = new ArrayList<>(); |
| | | List<PassengerAuthVehicle> authList = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | // sheet0: å
¬è·¯æ
客6ææ¥ |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = gs(row, colMap, "ä¼ä¸åç§°"); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | PassengerEnterpriseMonthly entity = new PassengerEnterpriseMonthly(); |
| | | entity.setReportPeriod(period); |
| | | entity.setRegionCode(gs(row, colMap, "æå±å°åº")); |
| | | entity.setEnterpriseCode(gs(row, colMap, "ä¼ä¸ä»£ç ")); |
| | | entity.setEnterpriseName(name.trim()); |
| | | entity.setVehicleTotal(gi(row, colMap, "客è¿è½¦è¾_æ»è®¡")); |
| | | entity.setSeatTotal(gi(row, colMap, "æ ¸å®è½½å®¢ä½_æ»è®¡")); |
| | | entity.setVehicleSchedule(gi(row, colMap, "è½¦è¾æ°_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼")); |
| | | entity.setSeatSchedule(gi(row, colMap, "æ ¸å®è½½å®¢ä½_客è¿ç车ï¼å«å®çº¿æ
游客è¿å®¢è½¦ï¼")); |
| | | entity.setVehicleCharter(gi(row, colMap, "è½¦è¾æ°_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼")); |
| | | entity.setSeatCharter(gi(row, colMap, "æ ¸å®è½½å®¢ä½_客è¿å
车ï¼å«éå®çº¿æ
游客è¿å®¢è½¦ï¼")); |
| | | entity.setPassengerTotal(gd(row, colMap, "客è¿é_æ»è®¡")); |
| | | entity.setTurnoverTotal(gd(row, colMap, "æ
客å¨è½¬é_æ»è®¡")); |
| | | entity.setPassengerClass1(gd(row, colMap, "客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setTurnoverClass1(gd(row, colMap, "æ
客å¨è½¬é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setPassengerClass2(gd(row, colMap, "客è¿é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setTurnoverClass2(gd(row, colMap, "æ
客å¨è½¬é_äºç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setPassengerClass3(gd(row, colMap, "客è¿é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setTurnoverClass3(gd(row, colMap, "æ
客å¨è½¬é_ä¸ç±»å®¢è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setPassengerClass4(gd(row, colMap, "客è¿é_å类客è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setTurnoverClass4(gd(row, colMap, "æ
客å¨è½¬é_å类客è¿ç线ï¼å«å®çº¿æ
游ï¼")); |
| | | entity.setPassengerCharter(gd(row, colMap, "客è¿é_客è¿å
车ï¼å«éå®çº¿æ
游ï¼")); |
| | | entity.setTurnoverCharter(gd(row, colMap, "æ
客å¨è½¬é_客è¿å
车ï¼å«éå®çº¿æ
游ï¼")); |
| | | entity.setAvgDistanceTotal(gd(row, colMap, "客è¿å¹³åè¿è·")); |
| | | entity.setAvgDistanceClass1(gd(row, colMap, "ä¸ç±»å®¢è¿ç线平åè¿è·")); |
| | | entity.setAvgDistanceClass2(gd(row, colMap, "äºç±»å®¢è¿ç线平åè¿è·")); |
| | | entity.setAvgDistanceClass3(gd(row, colMap, "ä¸ç±»å®¢è¿ç线平åè¿è·")); |
| | | entity.setAvgDistanceClass4(gd(row, colMap, "å类客è¿ç线平åè¿è·")); |
| | | entity.setAvgDistanceCharter(gd(row, colMap, "æ
游ãå
车平åè¿è·")); |
| | | entity.setVerifyExplanation(gs(row, colMap, "æ ¸å®æ§åå è§£é")); |
| | | rows.add(new PassengerRow(r + 1, entity)); |
| | | } |
| | | // sheet1: è¿æ¿è½¦è¾æ°ï¼åºå®4åï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼ |
| | | if (wb.getNumberOfSheets() > 1) { |
| | | Sheet authSheet = wb.getSheetAt(1); |
| | | for (int r = 1; r <= authSheet.getLastRowNum(); r++) { |
| | | Row row = authSheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = getString(row, 1); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | PassengerAuthVehicle auth = new PassengerAuthVehicle(); |
| | | auth.setReportPeriod(period); |
| | | auth.setEnterpriseCode(getString(row, 0)); |
| | | auth.setEnterpriseName(name.trim()); |
| | | auth.setVehicleCount(getInt(row, 2)); |
| | | auth.setSeatCount(getInt(row, 3)); |
| | | authList.add(auth); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Passenger monthly parse error", e); |
| | | throw e; |
| | | } |
| | | |
| | | // å¹çï¼åæ¥è¡¨æéæ°å¯¼å
¥ï¼å
å 餿§æ°æ® |
| | | passengerMapper.delete(new LambdaQueryWrapper<PassengerEnterpriseMonthly>() |
| | | .eq(PassengerEnterpriseMonthly::getReportPeriod, period)); |
| | | passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(PassengerAuthVehicle::getReportPeriod, period)); |
| | | // åªæ¸
H2031 è§å产ççå®¡æ ¸ç»æï¼é¿å
误å åæçè´§è¿å®¡æ ¸ç»æ |
| | | List<Long> h2031RuleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if ("H2031".equals(rule.getReportType())) h2031RuleIds.add(rule.getId()); |
| | | } |
| | | if (!h2031RuleIds.isEmpty()) { |
| | | auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, h2031RuleIds)); |
| | | } |
| | | |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (PassengerRow item : rows) { |
| | | try { |
| | | passengerMapper.insert(item.entity); |
| | | 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.excelRow + " è¡ï¼" + item.entity.getEnterpriseName() + "ï¼ï¼" + reason); |
| | | log.error("Passenger insert error: row {}, {}", item.excelRow, item.entity.getEnterpriseName(), e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) { |
| | | errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "H2031", period, rows.size(), success, failDetails.size(), errorDetail); |
| | | |
| | | int authSuccess = 0; |
| | | for (PassengerAuthVehicle auth : authList) { |
| | | try { |
| | | passengerAuthMapper.insert(auth); |
| | | authSuccess++; |
| | | } catch (Exception e) { |
| | | log.error("passenger auth insert error: {}", auth.getEnterpriseName(), e); |
| | | } |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "PASSENGER_AUTH", period, authList.size(), authSuccess, 0, null); |
| | | log.info("Passenger monthly imported: {} rows, auth {} rows for {}", success, authSuccess, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | |
| | | |
| | | /** å
¬è·¯æ
客个ä½å®¢è¿é/å¨è½¬éï¼ä¼ä¸çº¿ä¸æ±æ¥æ°æ®ï¼ææå±å°åºå¯¼å
¥ï¼ä¾åå¸å·è¡¨ä¸ªä½è¡å¡«å
ï¼ */ |
| | | public ImportResult importPassengerIndividual(MultipartFile file, String period) throws Exception { |
| | | List<PassengerIndividualMonthly> rows = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<String, Integer> colMap = buildHeaderMap(sheet.getRow(0)); |
| | | Integer codeCol = colMap.get("æå±å°åº"); |
| | | Integer entCol = colMap.get("ä¼ä¸ä»£ç "); |
| | | Integer nameCol = colMap.get("ä¼ä¸åç§°"); |
| | | Integer passCol = null; |
| | | Integer turnCol = null; |
| | | for (Map.Entry<String, Integer> e : colMap.entrySet()) { |
| | | String k = e.getKey(); |
| | | if (k != null && k.contains("个ä½") && k.contains("客è¿é")) passCol = e.getValue(); |
| | | if (k != null && k.contains("个ä½") && (k.contains("å¨è½¬é") || k.contains("å¨è½¬"))) turnCol = e.getValue(); |
| | | } |
| | | if (passCol == null && turnCol == null) { |
| | | throw new RuntimeException("æªæ¾å°ã个ä½å®¢è¿é/ä¸ªä½æ
客å¨è½¬éãåï¼è¯·æ£æ¥æ¨¡æ¿ï¼ååéå
å«â个ä½å®¢è¿éââ个ä½å¨è½¬éâï¼"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = nameCol != null ? getString(row, nameCol) : null; |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | Double pass = passCol != null ? getDecimalOrNull(row, passCol) : null; |
| | | Double turn = turnCol != null ? getDecimalOrNull(row, turnCol) : null; |
| | | if ((pass == null || pass == 0.0) && (turn == null || turn == 0.0)) continue; |
| | | PassengerIndividualMonthly entity = new PassengerIndividualMonthly(); |
| | | entity.setReportPeriod(period); |
| | | entity.setRegionCode(codeCol != null ? getString(row, codeCol) : null); |
| | | entity.setEnterpriseCode(entCol != null ? getString(row, entCol) : null); |
| | | entity.setEnterpriseName(name.trim()); |
| | | entity.setPassengerCount(pass); |
| | | entity.setTurnover(turn); |
| | | rows.add(entity); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Passenger individual parse error", e); |
| | | throw e; |
| | | } |
| | | passengerIndividualMapper.delete(new LambdaQueryWrapper<PassengerIndividualMonthly>() |
| | | .eq(PassengerIndividualMonthly::getReportPeriod, period)); |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (PassengerIndividualMonthly item : rows) { |
| | | try { |
| | | passengerIndividualMapper.insert(item); |
| | | 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("第 " + (rows.indexOf(item) + 2) + " è¡ï¼" + item.getEnterpriseName() + "ï¼ï¼" + reason); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) { |
| | | errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "PASSENGER_INDIVIDUAL", period, rows.size(), success, failDetails.size(), errorDetail); |
| | | log.info("Passenger individual imported: {} rows for {}", success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | /** å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°ï¼ç¬ç«æ¨¡æ¿ï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼ */ |
| | | 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)); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = gs(row, colMap, "ä¼ä¸åç§°"); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | PassengerAuthVehicle auth = new PassengerAuthVehicle(); |
| | | auth.setReportPeriod(period); |
| | | auth.setEnterpriseCode(gs(row, colMap, "ä¼ä¸ä»£ç ")); |
| | | auth.setEnterpriseName(name.trim()); |
| | | auth.setVehicleCount(gi(row, colMap, "è½¦è¾æ°")); |
| | | auth.setSeatCount(gi(row, colMap, "è½½å®¢ä½æ°")); |
| | | authList.add(auth); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Passenger auth parse error", e); |
| | | throw e; |
| | | } |
| | | // å¹çï¼åæ¥è¡¨æéæ°å¯¼å
¥ï¼å
å 餿§æ°æ® |
| | | passengerAuthMapper.delete(new LambdaQueryWrapper<PassengerAuthVehicle>() |
| | | .eq(PassengerAuthVehicle::getReportPeriod, period)); |
| | | deleteRuleResults("H2031", period); |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (PassengerAuthVehicle auth : authList) { |
| | | try { |
| | | passengerAuthMapper.insert(auth); |
| | | 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("第 " + (authList.indexOf(auth) + 2) + " è¡ï¼" + auth.getEnterpriseName() + "ï¼ï¼" + reason); |
| | | log.error("Passenger auth insert error: {}", auth.getEnterpriseName(), e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | recordBatch(file.getOriginalFilename(), "PASSENGER_AUTH", period, authList.size(), success, failDetails.size(), errorDetail); |
| | | log.info("Passenger auth imported: {} rows for {}", success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | // ========== H204 é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼å£æ¥ï¼å«è¿æ¿è½¦è¾ä¿¡æ¯sheetï¼ ========== |
| | | |
| | | /** è½èæç»æ¯ç»åºå®18åï¼è½¦çå·èµ·å§åï¼0-basedï¼ï¼3,21,39,57,75 */ |
| | | private static final int[] ENERGY_GROUP_COLS = {3, 21, 39, 57, 75}; |
| | | |
| | | public ImportResult importEnergyMonthly(MultipartFile file, String period) throws Exception { |
| | | List<EnergyRow> rows = new ArrayList<>(); |
| | | List<EnergyAuthVehicle> authList = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | // sheet0: 第äºå£åº¦è´§è½¦è½èæç»ï¼æ¯è¡å·¦å³å5ç»è½¦è¾ï¼æ¯ç»åºå®18åï¼ |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String enterpriseName = getString(row, 2); |
| | | if (enterpriseName == null || enterpriseName.trim().isEmpty()) continue; |
| | | for (int group : ENERGY_GROUP_COLS) { |
| | | String plate = getString(row, group); |
| | | if (plate == null || plate.trim().isEmpty()) continue; |
| | | EnergyVehicleQuarterly e = new EnergyVehicleQuarterly(); |
| | | e.setReportPeriod(period); |
| | | e.setRegionCode(getString(row, 0)); |
| | | e.setEnterpriseCode(getString(row, 1)); |
| | | e.setEnterpriseName(enterpriseName.trim()); |
| | | e.setPlateNo(plate.trim()); |
| | | String vtc = getString(row, group + 1); |
| | | e.setVehicleTypeCode(vtc); |
| | | e.setVehicleType(vtc == null ? null : ENERGY_VEHICLE_TYPE_NAMES.getOrDefault(vtc, vtc)); |
| | | String ftc = getString(row, group + 2); |
| | | e.setFuelTypeCode(ftc); |
| | | e.setFuelType(ftc == null ? null : ENERGY_FUEL_TYPE_NAMES.getOrDefault(ftc, ftc)); |
| | | e.setManufactureYear(getIntOrNull(row, group + 3)); |
| | | e.setMarkedTonnage(getDecimalOrNull(row, group + 4)); |
| | | e.setTotalMileage(getDecimalOrNull(row, group + 5)); |
| | | e.setLoadedMileage(getDecimalOrNull(row, group + 6)); |
| | | e.setEmptyMileage(getDecimalOrNull(row, group + 7)); |
| | | e.setFreight(getDecimalOrNull(row, group + 8)); |
| | | e.setTurnover(getDecimalOrNull(row, group + 9)); |
| | | e.setTripCount(getIntOrNull(row, group + 10)); |
| | | String unit = getString(row, group + 11); |
| | | e.setFuelUnit(unit == null ? null : ENERGY_FUEL_UNIT_NAMES.getOrDefault(unit, unit)); |
| | | e.setFuelConsumption(getDecimalOrNull(row, group + 12)); |
| | | e.setAvgDistance(getDecimalOrNull(row, group + 13)); |
| | | e.setAvgLoadedTonnage(getDecimalOrNull(row, group + 14)); |
| | | e.setAvgTripMileage(getDecimalOrNull(row, group + 15)); |
| | | e.setFuelPer100km(getDecimalOrNull(row, group + 16)); |
| | | e.setTurnoverFuel(getDecimalOrNull(row, group + 17)); |
| | | rows.add(new EnergyRow(r + 1, e)); |
| | | } |
| | | } |
| | | // sheet1: è¿æ¿è½¦è¾ä¿¡æ¯ï¼åºå®5åï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼ |
| | | if (wb.getNumberOfSheets() > 1) { |
| | | Sheet authSheet = wb.getSheetAt(1); |
| | | for (int r = 1; r <= authSheet.getLastRowNum(); r++) { |
| | | Row row = authSheet.getRow(r); |
| | | if (row == null) continue; |
| | | String plate = getString(row, 0); |
| | | if (plate == null || plate.trim().isEmpty()) continue; |
| | | EnergyAuthVehicle auth = new EnergyAuthVehicle(); |
| | | auth.setReportPeriod(period); |
| | | auth.setPlateNo(plate.trim()); |
| | | auth.setVehicleType(getString(row, 1)); |
| | | auth.setFuelType(getString(row, 2)); |
| | | auth.setMarkedTonnage(getDecimal(row, 3)); |
| | | auth.setTractionQuality(getDecimal(row, 4)); |
| | | authList.add(auth); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Energy monthly parse error", e); |
| | | throw e; |
| | | } |
| | | |
| | | // å¹çï¼åæ¥è¡¨æéæ°å¯¼å
¥ï¼å
å 餿§æ°æ®ï¼ä¸¤è¡¨ + H204 è§åå®¡æ ¸ç»æï¼ |
| | | energyMapper.delete(new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, period)); |
| | | energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, period)); |
| | | List<Long> h204RuleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if ("H204".equals(rule.getReportType())) h204RuleIds.add(rule.getId()); |
| | | } |
| | | if (!h204RuleIds.isEmpty()) { |
| | | auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, h204RuleIds)); |
| | | } |
| | | |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (EnergyRow item : rows) { |
| | | try { |
| | | energyMapper.insert(item.entity); |
| | | 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.excelRow + " è¡ï¼" + item.entity.getEnterpriseName() |
| | | + " " + item.entity.getPlateNo() + "ï¼ï¼" + reason); |
| | | log.error("Energy insert error: row {}, {}", item.excelRow, item.entity.getPlateNo(), e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) { |
| | | errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "H204", period, rows.size(), success, failDetails.size(), errorDetail); |
| | | |
| | | int authSuccess = 0; |
| | | for (EnergyAuthVehicle auth : authList) { |
| | | try { |
| | | energyAuthMapper.insert(auth); |
| | | authSuccess++; |
| | | } catch (Exception e) { |
| | | log.error("energy auth insert error: {}", auth.getPlateNo(), e); |
| | | } |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), authSuccess, 0, null); |
| | | log.info("Energy monthly imported: {} vehicles, auth {} rows for {}", success, authSuccess, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | /** è½è车è¾è¿æ¿ä¿¡æ¯ï¼ç¬ç«æ¨¡æ¿ï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼ */ |
| | | public ImportResult importEnergyAuth(MultipartFile file, String period) throws Exception { |
| | | List<EnergyAuthVehicle> authList = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String plate = getString(row, 0); |
| | | if (plate == null || plate.trim().isEmpty()) continue; |
| | | EnergyAuthVehicle auth = new EnergyAuthVehicle(); |
| | | auth.setReportPeriod(period); |
| | | auth.setPlateNo(plate.trim()); |
| | | auth.setVehicleType(getString(row, 1)); |
| | | auth.setFuelType(getString(row, 2)); |
| | | auth.setMarkedTonnage(getDecimal(row, 3)); |
| | | auth.setTractionQuality(getDecimal(row, 4)); |
| | | authList.add(auth); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Energy auth parse error", e); |
| | | throw e; |
| | | } |
| | | // å¹çï¼åæ¥è¡¨æéæ°å¯¼å
¥ï¼å
å 餿§æ°æ® |
| | | energyAuthMapper.delete(new LambdaQueryWrapper<EnergyAuthVehicle>() |
| | | .eq(EnergyAuthVehicle::getReportPeriod, period)); |
| | | deleteRuleResults("H204", period); |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (EnergyAuthVehicle auth : authList) { |
| | | try { |
| | | energyAuthMapper.insert(auth); |
| | | 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("第 " + (authList.indexOf(auth) + 2) + " è¡ï¼" + auth.getPlateNo() + "ï¼ï¼" + reason); |
| | | log.error("Energy auth insert error: {}", auth.getPlateNo(), e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | recordBatch(file.getOriginalFilename(), "ENERGY_AUTH", period, authList.size(), success, failDetails.size(), errorDetail); |
| | | log.info("Energy auth imported: {} rows for {}", success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | /** H204 è¡è®°å½ï¼ä¿ç Excel è¡å·ç¨äºå¤±è´¥å®ä½ */ |
| | | private static class EnergyRow { |
| | | final int excelRow; |
| | | final EnergyVehicleQuarterly entity; |
| | | |
| | | EnergyRow(int excelRow, EnergyVehicleQuarterly entity) { |
| | | this.excelRow = excelRow; |
| | | this.entity = entity; |
| | | } |
| | | } |
| | | |
| | | /** 空åå
æ ¼è¿å nullï¼ç¨äºè½èæç»æªå¡«åé¡¹ä¸ 0 åºåï¼ */ |
| | | private Integer getIntOrNull(Row row, int idx) { |
| | | Double val = getDecimalOrNull(row, idx); |
| | | return val == null ? null : val.intValue(); |
| | | } |
| | | |
| | | // ========== è¿æ¿è½¦è¾ ========== |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** æ°æ®åååå 餿宿¥è¡¨ç±»åæå®æ¥è¡¨æçå®¡æ ¸ç»æï¼ééæ°æ§è¡å®¡æ ¸ï¼ */ |
| | | private void deleteRuleResults(String reportType, String period) { |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if (reportType.equals(rule.getReportType())) ruleIds.add(rule.getId()); |
| | | } |
| | | if (!ruleIds.isEmpty()) { |
| | | auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | } |
| | | |
| | | /** æè¡¨å¤´å称建ç«åå·æ å°ï¼å
¼å®¹æ°æ§æ¨¡æ¿åä½å·®å¼ */ |
| | | private Map<String, Integer> buildHeaderMap(Row header) { |
| | | Map<String, Integer> colMap = new HashMap<>(); |
| | |
| | | } |
| | | } |
| | | |
| | | private static class PassengerRow { |
| | | int excelRow; |
| | | PassengerEnterpriseMonthly entity; |
| | | |
| | | PassengerRow(int excelRow, PassengerEnterpriseMonthly entity) { |
| | | this.excelRow = excelRow; |
| | | this.entity = entity; |
| | | } |
| | | } |
| | | |
| | | |
| | | private String getString(Row row, int idx) { |
| | | Cell cell = row.getCell(idx); |
| | | if (cell == null) return null; |
| | | // å
¬å¼åå
æ ¼ï¼è¯»ç¼åå符串ï¼.et/WPS æä»¶å¸¸è§ï¼DataFormatter æ evaluator æ¶åªè¿åå
¬å¼ææ¬ï¼ |
| | | if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.STRING) { |
| | | String cached = cell.getStringCellValue(); |
| | | cached = cached == null ? null : cached.trim(); |
| | | return cached == null || cached.isEmpty() ? null : cached; |
| | | } |
| | | String val = FORMATTER.formatCellValue(cell).trim(); |
| | | return val.isEmpty() ? null : val; |
| | | } |
| | |
| | | if (cell == null) return 0.0; |
| | | // æ°å¼åå
æ ¼ç´æ¥ååå§å¼ï¼é¿å
被 Excel æ¾ç¤ºæ ¼å¼ï¼ç¾åæ¯/0_ çï¼å½±åè§£æ |
| | | if (cell.getCellType() == CellType.NUMERIC) { |
| | | return cell.getNumericCellValue(); |
| | | } |
| | | // å
¬å¼åå
æ ¼ï¼è¯»ç¼åæ°å¼ï¼.et/WPS 跨表å
¬å¼å¸¸è§ï¼DataFormatter æ evaluator æ¶åªè¿åå
¬å¼ææ¬ï¼ |
| | | if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.NUMERIC) { |
| | | return cell.getNumericCellValue(); |
| | | } |
| | | String val = FORMATTER.formatCellValue(cell).trim(); |
| | |
| | | if (cell.getCellType() == CellType.NUMERIC) { |
| | | return cell.getNumericCellValue(); |
| | | } |
| | | if (cell.getCellType() == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.NUMERIC) { |
| | | return cell.getNumericCellValue(); |
| | | } |
| | | String val = FORMATTER.formatCellValue(cell).trim(); |
| | | if (val.isEmpty()) return null; |
| | | try { |
| | |
| | | return null; |
| | | } |
| | | } |
| | | // ========== æèµæ¨¡åï¼å®¢è¿ç«åº / ç©æµååºï¼å
¨çæ±æ»å¤§è¡¨ï¼ ========== |
| | | |
| | | /** 亿å
æèµé¡¹ç®åºå®ååï¼2026å¹´è§ä¸é¡¹ç®è¡¨ï¼ */ |
| | | private static final java.util.Set<String> INVEST_BILLION_NAMES = new LinkedHashSet<>(Arrays.asList( |
| | | "æ±å£å®¢è¿ä¸å¿", "é祥å¸ç»¼å交éå®¢è¿æ¢çº½ä¸æé¡¹ç®", "èªåæºæ
§ç©æµå", "å½èå
¬è·¯æ¸¯", |
| | | "åå °ç产æå¡åå½å®¶ç©æµæ¢çº½å·¥ç¨é¡¹ç®", "ç§å½å¿èæ©äº§ä¸ç»¼åç©æµä¸å¿", "å®é½ä¾éåè´¸ç©æµå", |
| | | "é¿é³å¿å乡å·é¾ç©æµå", "å®ææ¸¯å
´å±±æ¸¯åºç®¡å
¬æ°´å¤å¼èè¿å»ºè®¾é¡¹ç®", "çæ¹å·é¾ï¼è¥é³ï¼äº¤æä¸å¿é¡¹ç®", |
| | | "æ¦æ±ä¸å¨åä¸å·é¾æ¸¯äºæé¡¹ç®", "æ¹åé¿æ±ç°ä»£ç©æµäº§ä¸éè示èåºÂ·ç°ä»£ç©æµå", |
| | | "éå·è±æ¹å½é
æºåºç©ºæ¸¯åå½å®¶ç©æµæ¢çº½æºæ
§å
Œ
񆫂
è´§ç«é¡¹ç®", "è鍿ºæ
§å·é¾ç©æµå", |
| | | "èé¨åç«é¡ºæ´ç©æµåºå°ï¼èé¨ååéµéè·¯ç©æµåï¼", "æ²æ´å¤å¼èè¿ç°ä»£ç©æµåä¸æï¼æ²æ´ç
¤çå¨å¤â¢éè£
ç®±æ£æ¹éåºå°ï¼", |
| | | "é¦è¡¡åæºæ
§å·é¾ç©æµäºæé¡¹ç®Gåº", "åæå¹¿æµæºæ
§ç©æµçµå产ä¸å", "ç³éï¼åæï¼æºæ
§ç©æµçµå产ä¸åï¼ä¸æï¼", |
| | | "å
¬å®å¿å¤å¼èè¿ç»¼åç©æµå", "èå·å¸éæ°´èè¿ç©æµä¸å¿", "èå·å¸çæµ·å½é
ç©æµå䏿", "èå·ç»å¼åºæºè½ä»å¨ç©æµå项ç®", |
| | | "éä¸ï¼é»åï¼æ°æºç©æµç»¼åæå¡ä¸å¿", |
| | | "éæ¹èµ£åè´¸ç©æµä¸å¿ä¸æï¼éåå¿ç©æµé
éä¸å¿ãéåæºæ
§ç©æµäº§ä¸å建设ãéåæºæ
§ç©æµäº§ä¸å建设项ç®ï¼äºæï¼ãå·é¾é
éä¸å¿ï¼é¡¹ç®", |
| | | "å¸å®å½é
鿏¯ç©æµå-å¸å®å¸å½é
鿏¯ç»¼åç©æµåï¼ä¸æï¼", "å¸å®å½é
鿏¯ç©æµå-æ°å»ºå¸å®éè·¯ç©æµåºå°é¡¹ç®", |
| | | "å´é³å¿ç»¼åç©æµååé
å¥è®¾æ½å»ºè®¾é¡¹ç®", "æ©æ½ä¸éåªç©æµå", "èä¸å·¥è£
产ä¸éèåºä»å¨ç©æµé¡¹ç®", |
| | | "æå
ééåä¸ç©æµæ¢çº½åºå°é¡¹ç®", "æ¹åè¿ç¿è¾¾ä¾åºé¾æ»é¨ç ååç产åºå°é¡¹ç®", "䏿¥èªç©ºå·é¾ç©æµä¸å¿", |
| | | "ä¸å³¡æºæ
§èªç©ºç©æµäº§ä¸å", "æ±æ±è¥é³ï¼å°æ²³ï¼æ¸¯å¤å¼èè¿ç©æµå", "å®èå
¨çéè¿é¡¹ç®", "æ¦æ±å¤©æ²³ç»¼åå®¢è¿æ¢çº½", |
| | | "å¹¿æ°´å¸æ¨å®¶å¯¨å
¬éç©æµå项ç®---å¹¿æ°´å¸æ¨å¯¨é路货åºï¼å¹¿æ°´å¸åé«å¶éç©æµè´§åºï¼", "鳿°ç»¼åå®¢è¿æ¢çº½ç«", |
| | | "å½é³å¸é«é综å客è¿ç«", "宿å客è¿ç«", "åæ¼³å¿å
¬éæ¢ä¹ä¸å¿(éä¸é«éåæ¼³ç»¼åå®¢è¿æ¢çº½)", "京山åå
¬é综åå®¢è¿æ¢çº½", |
| | | "沿æ±é«éæ±å·åç«ç«å广åº(综åå®¢è¿æ¢çº½)", "æ¦æé«éé»å西综åå®¢è¿æ¢çº½", "é»é»é«éæµ æ°´ç»¼åå®¢è¿æ¢çº½", |
| | | "éä¸é«éå·´ä¸ç»¼åå®¢è¿æ¢çº½", "ä¸éä¾åºé¾æ¹å管çä¸å¿é¡¹ç®", "é³é»æ¸¯éæ°´èè¿äºæä¸æ¬§çåéç»ä¸å¿", |
| | | "西马å½é
ï¼é»ç³ï¼æºæ
§ç©æµå", "åå °å¸ååä»å¨ç©æµä¸å¿", "ææ±å¸é¡¾å®¶åºä»å¨ç©æµä¸å¿ï¼ä¸æï¼", "ä¸å³¡å»ºæç©æµå", |
| | | "å®é½å¸éå综åç©æµè¿è¾å»é", "å®æä¼ åå
¬è·¯æ¸¯äºæ", "æ¦æ±æ¸¯èªè¥é³æºæ
§ç©æµå", "è°·åå¿åä¹¡æºæ
§ä»å¨ç©æµäº§ä¸å", |
| | | "ä¹å·éæ²ç°å½é
ä¾åºé¾ä¸å¿é¡¹ç®", "æ¹åæå³°å½é
ç©æµå", "æ¹åä¸éä¾åºé¾æéå
¬å¸å
è£
é
å¥ãä»å¨è¿è¾é¡¹ç®", |
| | | "大æå¿çµåç©æµäº§ä¸å", "å®éå¸å乡å·é¾ç©æµé
éä¸å¿", "大å«å±±æºæ
§ç©æµäº§ä¸å", "æ¦ç©´å¸ä¾é社éééé
综åæå¡ä¸å¿å·¥ç¨", |
| | | "ç½ç°å¿ç»¼åç©æµå", "é»è±¹å½é
ç©æµåï¼é»å综åååºï¼", "麻åå¸å产å综åç©æµå", "å·´ä¸ç»æµå¼ååºé龿¡¥æºæ
§ç©æµäº§ä¸å", |
| | | "鹤峰æ¦éµå±±ä»å¨ç©æµå", "å¸ä¸°å¿ç»¼åç©æµå", "宣æ©å¿å¯éç©æµå", "æ¥å¤å¿é¾å¤ç©æµå交éç©æµä¸å¿", |
| | | "仿¡å½å®¶é«æ°åºç©æµæ¢çº½ä¸å¿å»ºè®¾é¡¹ç®", "天é¨å¸ç©æµä¸å¿å»ºè®¾é¡¹ç®")); |
| | | |
| | | /** ç»æµå¼ºå¿é¡¹ç®åºå®åå */ |
| | | private static final java.util.Set<String> INVEST_COUNTY_NAMES = new LinkedHashSet<>(Arrays.asList( |
| | | "西马å½é
ï¼é»ç³ï¼æºæ
§ç©æµå", "é»ç³å¤§å¶æ¹é«æ°åºæºæ
§å·é¾ç©æµå", "大嶿¹é«æ°æºæ
§ç©æµæ¸¯é¡¹ç®ï¼å西åä¾åºé¾æå¡ä¸å¿ï¼", |
| | | "ç§å½å¿èæ©äº§ä¸ç»¼åç©æµä¸å¿", "å®é½ä¾éåè´¸ç©æµå", "é¿é³å¿å乡å·é¾ç©æµååå
¶é
å¥è®¾æ½å»ºè®¾é¡¹ç®", |
| | | "å®ææ¸¯å
´å±±æ¸¯åºç®¡å
¬æ°´å¤å¼èè¿å»ºè®¾é¡¹ç®", "ææ±å¸é¡¾å®¶åºä»å¨ç©æµä¸å¿ï¼ä¸æï¼", "å®é½å¸éå综åç©æµè¿è¾å»é", |
| | | "èæ²³å£å¸å产åä»å¨ç©æµäº¤æä¸å¿å»ºè®¾é¡¹ç®", "è°·åå¿åä¹¡æºæ
§ä»å¨ç©æµäº§ä¸å", "æ²æ´å¤å¼èè¿ç°ä»£ç©æµå", |
| | | "大æå¿çµåç©æµäº§ä¸å", "åºå叿ºæ
§å·é¾ä»å¨ç©æµå", "åæå¹¿æµæºæ
§ç©æµçµå产ä¸å建设项ç®", "å®éå¸å乡å·é¾ç©æµé
éä¸å¿", |
| | | "æ¾æ»å¸æç»ç©æµå", "å
¬å®å¿å¤å¼èè¿ç»¼åç©æµå", "ä¸å½ä¾éå
¬å®åè´¸ç©æµåï¼å
¬å®å¿åéééé
ä¸å¿ï¼", "åæ´²æºæ
§ç©æµå", |
| | | "大å«å±±æºæ
§ç©æµäº§ä¸å", "æ¦ç©´å¸ä¾é社éééé
综åæå¡ä¸å¿å·¥ç¨", "ç½ç°å¿ç»¼åç©æµå", "éä¸ï¼é»åï¼æ°æºç©æµç»¼åæå¡ä¸å¿", |
| | | "麻åå¸å产å综åç©æµå", "éæ¹èµ£åè´¸ç©æµä¸å¿ï¼ä¸æï¼", "åé±¼å¿æºæ
§äº¤éç©æµæ¢çº½ä¸å¿", |
| | | "å´é³å¿å½å®¶çº§ç»¼åç©æµæ¢çº½åé
å¥è®¾æ½å»ºè®¾é¡¹ç®", "åº·åæºæ
§ç©æµå", "综åç©æµåï¼æ¹å交æéå·æºæ
§ä¾åºé¾äº§ä¸åï¼", |
| | | "å·´ä¸ç»æµå¼ååºé龿¡¥æºæ
§ç©æµäº§ä¸å", "æ©æ½å¸ä¸éåªç©æµå", "鹤峰æ¦éµå±±ä»å¨ç©æµå", "å¸ä¸°å¿ç»¼åç©æµå", |
| | | "æ¥å¤å¿é¾å¤ç©æµå交éç©æµä¸å¿", "宣æ©å¿å¯éç©æµå", "仿¡å½å®¶é«æ°åºç©æµæ¢çº½ä¸å¿å»ºè®¾é¡¹ç®", |
| | | "èä¸å·¥è£
产ä¸éèåºä»å¨ç©æµé¡¹ç®", "ä¸å½è¾è°·å·é¾ä»å¨ç©æµå", "天é¨å¸ç©æµä¸å¿å»ºè®¾é¡¹ç®", "ç¥åæ¶ç©æµå")); |
| | | |
| | | /** "åäºäº"éç¹ç©æµé¡¹ç®åºå®åå */ |
| | | private static final java.util.Set<String> INVEST_FIVE_YEAR_NAMES = new LinkedHashSet<>(Arrays.asList( |
| | | "èªåæºæ
§ç©æµå", "ä¸éä¾åºé¾æ¹å管çä¸å¿é¡¹ç®", "é³é»æ¸¯éæ°´èè¿äºæä¸æ¬§çåéç»ä¸å¿", "æå
ééåä¸ç©æµæ¢çº½åºå°é¡¹ç®", |
| | | "䏿¬§çåé
å¥å¤å¼èè¿ä¸è½¬ä¸å¿é¡¹ç®", "æ°å¼åé¶è¡è´·æ¬¾é¿æ±ä¸æ¸¸ï¼æ¦æ±ï¼æºæ
§ç©æµæ¢çº½é¡¹ç®é路工ä¸ç«å项", |
| | | "é»ç³æ°æ¸¯å½èæºæ
§å
¬è·¯æ¸¯é¡¹ç®", "åå °ç产æå¡åå½å®¶ç©æµæ¢çº½å·¥ç¨é¡¹ç®", "åå °å¸ååä»å¨ç©æµä¸å¿", |
| | | "å¯¿åº·æ°¸ä¹æºè½åç©æµè®¾å¤å级æ¹é ", "åå °æ±½é
ï¼å¹¿åºï¼ç©æµä¸å¿æ©è½å级项ç®", "åå °æ¦å½å±±æºåºèªç©ºç©æµå项ç®", |
| | | "丹æ±å£å¸éå®¶æ¸¯éæ°´å
¬ç©ºèè¿åºå°", "é¿é³å¿å乡å·é¾ç©æµååå
¶é
å¥è®¾æ½å»ºè®¾é¡¹ç®", "ä¸å³¡å»ºæç©æµå", |
| | | "å®é½å¸éå综åç©æµè¿è¾å»é", "ä¸å³¡æºæ
§èªç©ºç©æµäº§ä¸å", "å½é³å¸åéµåå·¥ååºç»¼åç©æµå", "è¿å®å¿ç©æµä¸å¿æè½å级项ç®", |
| | | "宿叿ºæ
§ç©æµéæ£ä¸å¿", "å®é½å¸æåå¤å¼èè¿ä¸´æ¸¯ç©æµå", "çæ¹å·é¾ï¼è¥é³ï¼äº¤æä¸å¿é¡¹ç®", "ä¼ åéè¥¿åæºæ
§ç©æµä¸å¿é¡¹ç®", |
| | | "æ¦æ±æ¸¯èªè¥é³æºæ
§ç©æµå", "åç½æ²³æ¸¯æ£è´§è½¬è¿ä¸å¿é¡¹ç®", "æ±æ±è¥é³ï¼å°æ²³ï¼æ¸¯å¤å¼èè¿ç©æµå", "è¥å·åºå¤§å®ååå¤å¼èè¿äº¤æä¸å¿", |
| | | "ç»¼åæ§ä¾åºé¾äº§ä¸ååºï¼ä¸å½ææºè°·ä¾åºé¾æºæ
§äº§ä¸åãè°·åæºæ
§å·é¾ç©æµä¾åºé¾äº§ä¸åï¼", "åæ¼³å¿ç°ä»£æºè½ç©æµååº", |
| | | "æ¹åé¿æ±ç°ä»£ç©æµäº§ä¸éè示èåºÂ·ç°ä»£ç©æµå", "å¼ä¸ç°ä»£ç©æµäº§ä¸å", "å®èå
¨çéè¿é¡¹ç®", "æ²æ´å¤å¼èè¿ç°ä»£ç©æµå", |
| | | "京山å¸å
¬éç©æµä¸å¿", "æ°ææå·¥ä¸åæºæ
§ç©æµå", "é¿èéè·¯ï¼é祥ï¼å
¬éèè¿éè£
ç®±ç©æµå", "å±å®¶å²æºæ
§ç©æµå", |
| | | "䏿¹å·é¾æºæ
§ç©æµé
éä¸å¿", "åºå叿ºæ
§å·é¾ä»å¨ç©æµå", "åæå¹¿æµæºæ
§ç©æµçµå产ä¸å建设项ç®", |
| | | "京æ±ç²¤å¤§ééåæå½å®¶ç»¼åè´§è¿æ¢çº½", "å
¬å®å¿å¤å¼èè¿ç»¼åç©æµå", "èå·ç»å¼åºæºè½ä»å¨ç©æµå项ç®", |
| | | "ä¸å½ä¾éå
¬å®åè´¸ç©æµåï¼å
¬å®å¿åéééé
ä¸å¿ï¼", "çå©å¸æ¸¯å£ç©æµåï¼ååºåï¼", "éä¸ï¼é»åï¼æ°æºç©æµç»¼åæå¡ä¸å¿", |
| | | "å´é³å¿å½å®¶çº§ç»¼åç©æµæ¢çº½åé
å¥è®¾æ½å»ºè®¾é¡¹ç®", "å¸å®é«æ°åºå»åºå·é¾é¡¹ç®ï¼ä¸æï¼", "å¸å®åºæ°åç©æµäº§ä¸åååºç¡è®¾æ½å»ºè®¾é¡¹ç®", |
| | | "éå±±å¿ç¹è²å产åç°ä»£ç©æµåºå°å»ºè®¾é¡¹ç®", "èµ¤å£æ²¿æ±å
¬éèè¿ç»¿è²ç©æµä¸å¿é¡¹ç®", "å¸å®åä¹¡æºæ
§ç©æµå", |
| | | "综åç©æµåï¼æ¹å交æéå·æºæ
§ä¾åºé¾äº§ä¸åï¼", "éå·ç»¼åä»å¨é
éä¸å¿", |
| | | "å¹¿æ°´å¸æ¨å®¶å¯¨å
¬éç©æµå项ç®---ç¬¬ä¸æå¹¿æ°´å¸æ¨å¯¨é路货åºï¼å¹¿æ°´å¸åé«å¶éç©æµè´§åºï¼", |
| | | "å·´ä¸ç»æµå¼ååºé龿¡¥æºæ
§ç©æµäº§ä¸å", "鹤峰æ¦éµå±±ä»å¨ç©æµå", "å¸ä¸°å¿ç»¼åç©æµå", |
| | | "仿¡å½å®¶é«æ°åºç©æµæ¢çº½ä¸å¿å»ºè®¾é¡¹ç®", "天é¨å¸åå
Œ
±ç»¼åç©æµå", "æ½æ±å¸å产åç°ä»£ç©æµåºç¡è®¾æ½å»ºè®¾é¡¹ç®", |
| | | "æ½æ±ç°ä»£åä¸ç§æç¤ºèåå·é¾ç©æµä¸å¿é¡¹ç®", "æ¹åæ½çµç©æµæéå
¬å¸ç°ä»£ç©æµä¸å¿ï¼ä¸ãäºã䏿ï¼")); |
| | | |
| | | /** æ±æ»å¤§è¡¨å¸å·çå -> è§èå */ |
| | | private static final Map<String, String> INVEST_CITY_SHORT_TO_FULL = new LinkedHashMap<>(); |
| | | |
| | | static { |
| | | INVEST_CITY_SHORT_TO_FULL.put("æ¦æ±", "æ¦æ±å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("é»ç³", "é»ç³å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("åå °", "åå °å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("宿", "宿å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("è¥é³", "è¥é³å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("éå·", "éå·å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("èé¨", "èé¨å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("åæ", "åæå¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("èå·", "èå·å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("é»å", "é»åå¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("å¸å®", "å¸å®å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("éå·", "éå·å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("æ©æ½", "æ©æ½å·"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("仿¡", "仿¡å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("æ½æ±", "æ½æ±å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("天é¨", "天é¨å¸"); |
| | | INVEST_CITY_SHORT_TO_FULL.put("æåº", "ç¥åæ¶æåº"); |
| | | } |
| | | |
| | | /** |
| | | * æèµææ¥å¯¼å
¥ï¼å
¨çæ±æ»å¤§è¡¨ï¼ã |
| | | * category: 客è¿ç«åºï¼xlsx æç» sheetï¼/ ç©æµååºï¼xls åé¡¹ç®æèµå®ææ
åµ sheetï¼ |
| | | * å¹çï¼åæ¥è¡¨ææ§æåº¦æ°æ®ä¸ INVEST å®¡æ ¸ç»æå
å ï¼é¡¹ç®ä¸»æ¡£æ 项ç®å+ç±»å« upsertã |
| | | */ |
| | | public ImportResult importInvestment(MultipartFile file, String period, String category) throws Exception { |
| | | boolean passenger = "客è¿ç«åº".equals(category); |
| | | List<InvestRow> rows = new ArrayList<>(); |
| | | List<String> cityOrder = new ArrayList<>(); |
| | | String currentCity = null; |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = pickInvestmentSheet(wb, passenger); |
| | | // å®ä½è¡¨å¤´è¡ï¼å
å«"项ç®åç§°"ä¸å
¶åææ°æ® |
| | | int headerRow = -1; |
| | | for (int r = 0; r <= Math.min(sheet.getLastRowNum(), 8); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = getString(row, 0); |
| | | String b = getString(row, 1); |
| | | if (b != null && b.contains("项ç®åç§°") && (a == null || a.contains("åºå·") || a.contains("åº"))) { |
| | | headerRow = r; |
| | | break; |
| | | } |
| | | } |
| | | if (headerRow < 0) { |
| | | throw new RuntimeException("æªæ¾å°è¡¨å¤´è¡ï¼éå
å« åºå·/项ç®åç§° åï¼"); |
| | | } |
| | | for (int r = headerRow + 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = getString(row, 0); |
| | | String b = getString(row, 1); |
| | | if (a == null || a.trim().isEmpty()) { |
| | | if (b == null || b.trim().isEmpty()) continue; |
| | | } |
| | | String aTrim = a == null ? "" : a.trim(); |
| | | String bTrim = b == null ? "" : b.trim(); |
| | | // å计/æ é¢è¡ |
| | | if (aTrim.contains("å计") || aTrim.contains("æ»è®¡")) continue; |
| | | // èµéç±»å«/åç»æ é¢è¡ï¼ä¸ãäºãä¸ãåãâ¦ï¼ |
| | | if (aTrim.matches("^[ä¸äºä¸åäºå
ä¸å
«ä¹å]+ã.*")) continue; |
| | | // å¸å·å°èè¡ |
| | | String city = investmentCity(aTrim); |
| | | if (city != null) { |
| | | currentCity = city; |
| | | if (!cityOrder.contains(city)) cityOrder.add(city); |
| | | continue; |
| | | } |
| | | // 项ç®è¡ï¼åºå·ä¸ºæ°åä¸é¡¹ç®åé空 |
| | | boolean seqNum = aTrim.matches("\\d+(\\.\\d+)?"); |
| | | if (seqNum && !bTrim.isEmpty()) { |
| | | InvestmentProject project = new InvestmentProject(); |
| | | project.setProjectName(bTrim); |
| | | project.setCategory(category); |
| | | project.setCity(currentCity); |
| | | project.setBuilderName(getString(row, 2)); |
| | | project.setConstructNature(getString(row, 3)); |
| | | project.setStartTime(investTime(getString(row, 4))); |
| | | project.setEndTime(investTime(getString(row, 5))); |
| | | project.setTotalInvestment(getDecimal(row, 6)); |
| | | project.setApprovalGk(getString(row, 18)); |
| | | project.setApprovalCs(getString(row, 19)); |
| | | project.setSource("å
¨çæ±æ»å¤§è¡¨"); |
| | | project.setIsBillion(INVEST_BILLION_NAMES.contains(bTrim) ? 1 : 0); |
| | | project.setIsCounty(INVEST_COUNTY_NAMES.contains(bTrim) ? 1 : 0); |
| | | project.setIsFiveYear(INVEST_FIVE_YEAR_NAMES.contains(bTrim) ? 1 : 0); |
| | | |
| | | InvestmentMonthly monthly = new InvestmentMonthly(); |
| | | monthly.setReportPeriod(period); |
| | | monthly.setYearPlan(getDecimal(row, 8)); |
| | | monthly.setYearCum(getDecimal(row, 9)); |
| | | monthly.setMonthDone(getDecimal(row, 10)); |
| | | monthly.setStartCum(getDecimal(row, 7)); |
| | | monthly.setProgressStage(getString(row, 11)); |
| | | monthly.setProgressDesc(getString(row, 12)); |
| | | monthly.setBuildingArea(getDecimalOrNull(row, 15)); |
| | | rows.add(new InvestRow(r + 1, project, monthly)); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Investment import parse error: {}", category, e); |
| | | throw e; |
| | | } |
| | | |
| | | // å¹çæ¸
çï¼åæ¥è¡¨æãåç±»å«æåº¦æ°æ® + INVEST å®¡æ ¸ç»æï¼é¡¹ç®ä¸»æ¡£è·¨æä¿çï¼æå upsertï¼ |
| | | List<InvestmentProject> sameCat = investProjectMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category)); |
| | | java.util.Set<Long> catProjectIds = new HashSet<>(); |
| | | for (InvestmentProject pc : sameCat) catProjectIds.add(pc.getId()); |
| | | if (!catProjectIds.isEmpty()) { |
| | | investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period) |
| | | .in(InvestmentMonthly::getProjectId, catProjectIds)); |
| | | } |
| | | 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)); |
| | | } |
| | | |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (InvestRow item : rows) { |
| | | try { |
| | | // 主档 upsertï¼æ 项ç®å+ç±»å«ï¼ |
| | | InvestmentProject exist = investProjectMapper.selectOne(new LambdaQueryWrapper<InvestmentProject>() |
| | | .eq(InvestmentProject::getProjectName, item.project.getProjectName()) |
| | | .eq(InvestmentProject::getCategory, category) |
| | | .last("LIMIT 1")); |
| | | if (exist == null) { |
| | | investProjectMapper.insert(item.project); |
| | | item.monthly.setProjectId(item.project.getId()); |
| | | } else { |
| | | item.project.setId(exist.getId()); |
| | | item.project.setCreatedAt(exist.getCreatedAt()); |
| | | investProjectMapper.updateById(item.project); |
| | | item.monthly.setProjectId(exist.getId()); |
| | | } |
| | | investMonthlyMapper.insert(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.excelRow + " è¡ï¼" + item.project.getProjectName() + "ï¼ï¼" + reason); |
| | | log.error("Investment insert error: row {}", item.excelRow, e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | recordBatch(file.getOriginalFilename(), "INVEST_" + category, period, rows.size(), success, failDetails.size(), errorDetail); |
| | | log.info("Investment {} imported: {} rows for {}", category, success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | // ========== æèµæ¨¡å M2ï¼å¸å·å表æ¹é导å
¥ï¼å®¹éè§£æï¼ ========== |
| | | |
| | | /** |
| | | * å¸å·å表æ¹é导å
¥ï¼æ«æ docs/æèµ/è¾å
¥/{ç±»å«ç®å½}/{Xæ} ä¸å
¨é¨ xls/xlsx/etï¼è·³è¿ pdfï¼ï¼ |
| | | * 容éè§£ææ¯ä¸ªæä»¶ï¼å®ä½è¡¨å¤´è¡ + ååæ å°ï¼ï¼upsert 项ç®ä¸»æ¡£ + åå
¥æåº¦ï¼ |
| | | * å¹¶ä¸ãåäºäºãæ¨¡æ¿æ¸
åè§èåå¹é
åæ è®° is_five_yearã |
| | | */ |
| | | public Map<String, Object> importInvestCityDir(String period, String category) throws Exception { |
| | | boolean passenger = "客è¿ç«åº".equals(category); |
| | | String catDir = passenger ? "客è¿ï¼å®¢è¿ç«ï¼" : "ç©æµï¼ç©æµååºï¼"; |
| | | String monthDir = investMonthDir(period); |
| | | if (monthDir.isEmpty()) throw new RuntimeException("æ¥è¡¨ææ ¼å¼åºä¸º yyyy-MMï¼ä¾å¦ 2026-07"); |
| | | File dir = resolveInvestInputDir(catDir, monthDir); |
| | | if (dir == null || !dir.isDirectory()) { |
| | | throw new RuntimeException("æªæ¾å°å¸å·å表ç®å½ï¼docs/æèµ/è¾å
¥/" + catDir + "/" + monthDir); |
| | | } |
| | | Set<String> fiveYearNames = loadFiveYearProjectNames(); |
| | | List<InvestCityRow> rows = new ArrayList<>(); |
| | | List<Map<String, Object>> fileResults = new ArrayList<>(); |
| | | File[] files = dir.listFiles((d, n) -> { |
| | | String lower = n.toLowerCase(); |
| | | return lower.endsWith(".xls") || lower.endsWith(".xlsx") || lower.endsWith(".et"); |
| | | }); |
| | | if (files == null || files.length == 0) { |
| | | throw new RuntimeException("ç®å½ä¸æ²¡æå¯è§£æçæ¥è¡¨æä»¶ï¼xls/xlsx/etï¼ï¼" + dir.getAbsolutePath()); |
| | | } |
| | | Arrays.sort(files, Comparator.comparing(File::getName)); |
| | | for (File f : files) { |
| | | Map<String, Object> fr = new LinkedHashMap<>(); |
| | | fr.put("fileName", f.getName()); |
| | | try (java.io.InputStream is = new java.io.FileInputStream(f); |
| | | Workbook wb = WorkbookFactory.create(is)) { |
| | | parseInvestCityWorkbook(wb, f.getName(), 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.getName(), e); |
| | | } |
| | | fileResults.add(fr); |
| | | } |
| | | // å¹çæ¸
çï¼åç±»å«åæ¥è¡¨ææåº¦æ°æ® + INVEST å®¡æ ¸ç»æï¼é¡¹ç®ä¸»æ¡£è·¨æä¿çï¼æå upsertï¼ |
| | | cleanInvestMonthly(category, period); |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (InvestCityRow item : rows) { |
| | | 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); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | recordBatch("å¸å·å表æ¹é导å
¥ï¼" + catDir + "/" + monthDir + "ï¼", "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 {} {} imported: {} rows (fiveYear matched {}/{})", |
| | | category, period, success, fiveYearMatched, fiveYearNames.size()); |
| | | return result; |
| | | } |
| | | |
| | | /** å¤ sheet å岿件ï¼è¥é³ .et çï¼ï¼sheet å嫿¥è¡¨æä¼å
ï¼å
¶æ¬¡éæ°æ®è¡æå¤ */ |
| | | private boolean investSheetMatchesPeriod(Sheet sh, String period) { |
| | | if (period == null || !period.matches("\\d{4}-\\d{2}")) return false; |
| | | String name = sh.getSheetName(); |
| | | if (name == null || name.isEmpty()) return false; |
| | | String norm = name.replace(" ", ""); |
| | | int month = Integer.parseInt(period.substring(5, 7)); |
| | | return norm.contains(month + "æ") || norm.contains(String.format("%02d", month) + "æ") |
| | | || name.contains(period) || name.contains(period.replace("-0", "-")); |
| | | } |
| | | |
| | | /** è§£æå个å¸å·æ¥è¡¨ï¼é sheetï¼æ¥è¡¨æåä¼å
+ æ°æ®è¡æ°ï¼ï¼æååæ å°æå项ç®è¡ */ |
| | | private void parseInvestCityWorkbook(Workbook wb, String fileName, String category, String period, |
| | | Set<String> fiveYearNames, List<InvestCityRow> rows, Map<String, Object> fr) { |
| | | Sheet best = null; |
| | | Map<String, Integer> bestCol = null; |
| | | int bestScore = -1; |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | Sheet sh = wb.getSheetAt(i); |
| | | Map<String, Integer> col = findInvestHeader(sh); |
| | | if (col == null) continue; |
| | | int cnt = countInvestRows(sh, col); |
| | | int score = cnt + (cnt > 0 && investSheetMatchesPeriod(sh, period) ? 1000000 : 0); |
| | | if (score > bestScore) { |
| | | best = sh; |
| | | bestCol = col; |
| | | bestScore = score; |
| | | } |
| | | } |
| | | if (best == null) { |
| | | fr.put("success", 0); |
| | | fr.put("error", "æªæ¾å°å«ã项ç®åç§°ãç表头"); |
| | | return; |
| | | } |
| | | String city = cityFromFileName(fileName); |
| | | if (city == null) city = cityFromSheetHeader(best); |
| | | String currentCity = city; |
| | | Map<String, Integer> col = bestCol; |
| | | int nameCol = col.get("name"); |
| | | boolean hasSeq = col.containsKey("seq"); |
| | | int seqCol = hasSeq ? col.get("seq") : 0; |
| | | int parsed = 0; |
| | | for (int r = col.get("headerRow") + 1; r <= best.getLastRowNum(); r++) { |
| | | Row row = best.getRow(r); |
| | | if (row == null) continue; |
| | | String name = cellStr(row, nameCol); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | String nt = name.trim().replaceAll("[\r\n]+", ""); |
| | | if (nt.contains("å计") || nt.contains("æ»è®¡")) continue; |
| | | if (nt.matches("^[ä¸äºä¸åäºå
ä¸å
«ä¹å]+ã.*")) continue; |
| | | if (nt.matches("^ï¼[ä¸äºä¸åäºå
ä¸å
«ä¹å]+ï¼.*")) continue; |
| | | 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); |
| | | if (c != null) currentCity = c; |
| | | continue; |
| | | } |
| | | if (!seq.trim().matches("\\d+(\\.\\d+)?")) continue; |
| | | } else { |
| | | // æ åºå·åï¼éå·çï¼ï¼é¡¹ç®åé空å³é¡¹ç®è¡ï¼å¸å·å°èè¡ç¨ investmentCity è¯å« |
| | | String c = investmentCity(nt); |
| | | if (c != null) { currentCity = c; continue; } |
| | | } |
| | | InvestmentProject project = new InvestmentProject(); |
| | | project.setProjectName(nt); |
| | | project.setCategory(category); |
| | | project.setCity(currentCity); |
| | | project.setBuilderName(cellStr(row, col.get("builder"))); |
| | | project.setConstructNature(cellStr(row, col.get("nature"))); |
| | | project.setStartTime(investTime(cellStr(row, col.get("start")))); |
| | | project.setEndTime(investTime(cellStr(row, col.get("end")))); |
| | | project.setTotalInvestment(cellNum(row, col.get("total"))); |
| | | project.setApprovalGk(cellStr(row, col.get("gk"))); |
| | | project.setApprovalCs(cellStr(row, col.get("cs"))); |
| | | project.setBuildingArea(cellNumOrNull(row, col.get("area"))); |
| | | project.setSource("å¸å·å表"); |
| | | project.setIsBillion(INVEST_BILLION_NAMES.contains(nt) ? 1 : 0); |
| | | project.setIsCounty(INVEST_COUNTY_NAMES.contains(nt) ? 1 : 0); |
| | | project.setIsFiveYear(fiveYearNames.contains(normalizeProjectName(nt)) ? 1 : 0); |
| | | InvestmentMonthly monthly = new InvestmentMonthly(); |
| | | monthly.setReportPeriod(period); |
| | | monthly.setStartCum(cellNum(row, col.get("startcum"))); |
| | | monthly.setYearPlan(cellNum(row, col.get("yearplan"))); |
| | | monthly.setYearCum(cellNum(row, col.get("yearcum"))); |
| | | monthly.setMonthDone(cellNum(row, col.get("monthdone"))); |
| | | monthly.setProgressStage(cellStr(row, col.get("stage"))); |
| | | monthly.setProgressDesc(cellStr(row, col.get("desc"))); |
| | | monthly.setBuildingArea(cellNumOrNull(row, col.get("area"))); |
| | | rows.add(new InvestCityRow(r + 1, fileName, project, monthly)); |
| | | parsed++; |
| | | } |
| | | fr.put("city", currentCity); |
| | | fr.put("success", parsed); |
| | | } |
| | | |
| | | /** å®ä½å«ã项ç®åç§°ãç表头è¡å¹¶æååå
³é®è¯å»ºç«åæ å°ï¼æ¯æåå±è¡¨å¤´åå¹¶ï¼ */ |
| | | private Map<String, Integer> findInvestHeader(Sheet sh) { |
| | | int maxHeaderRow = Math.min(22, sh.getLastRowNum()); |
| | | for (int r = 0; r <= maxHeaderRow; r++) { |
| | | Row row = sh.getRow(r); |
| | | if (row == null) continue; |
| | | boolean hasName = false; |
| | | for (int c = 0; c <= row.getLastCellNum(); c++) { |
| | | String v = cellStr(row, c); |
| | | if (v != null && v.contains("项ç®åç§°")) { hasName = true; break; } |
| | | } |
| | | if (!hasName) continue; |
| | | Map<Integer, String> colTexts = new HashMap<>(); |
| | | for (int rr = r; rr <= Math.min(r + 3, sh.getLastRowNum()); rr++) { |
| | | Row rr2 = sh.getRow(rr); |
| | | if (rr2 == null) continue; |
| | | for (int c = 0; c <= rr2.getLastCellNum(); c++) { |
| | | String v = cellStr(rr2, c); |
| | | if (v == null) continue; |
| | | colTexts.merge(c, v, (a, b) -> a + "|" + b); |
| | | } |
| | | } |
| | | Map<String, Integer> col = new HashMap<>(); |
| | | for (Map.Entry<Integer, String> e : colTexts.entrySet()) { |
| | | int c = e.getKey(); |
| | | String v = e.getValue(); |
| | | if (v.contains("项ç®åç§°")) col.putIfAbsent("name", c); |
| | | else if (v.contains("æèµå»ºè®¾") || v.contains("建设åä½") || v.contains("项ç®ä¸ä¸»")) col.putIfAbsent("builder", c); |
| | | else if (v.contains("建设æ§è´¨")) col.putIfAbsent("nature", c); |
| | | else if (v.contains("å¼å·¥")) col.putIfAbsent("start", c); |
| | | else if (v.contains("竣工") || v.contains("建æ")) col.putIfAbsent("end", c); |
| | | else if ((v.contains("è®¡åæ»æèµ") || v.contains("æ»æèµ")) && !v.contains("累计")) col.putIfAbsent("total", c); |
| | | else if (v.contains("èªå¼å§å»ºè®¾")) col.putIfAbsent("startcum", c); |
| | | else if (v.contains("æ¬å¹´è®¡å")) col.putIfAbsent("yearplan", c); |
| | | else if (v.contains("èªå¹´å累计")) col.putIfAbsent("yearcum", c); |
| | | else if (v.contains("æ¬æå®æ")) col.putIfAbsent("monthdone", c); |
| | | else if (v.contains("å»ºè®¾é¶æ®µ")) col.putIfAbsent("stage", c); |
| | | else if (v.contains("形象è¿åº¦")) col.putIfAbsent("desc", c); |
| | | else if (v.contains("å·¥å¯") && !v.contains("项ç®åç§°")) col.putIfAbsent("gk", c); |
| | | else if (v.contains("å设") && !v.contains("项ç®åç§°")) col.putIfAbsent("cs", c); |
| | | else if (v.contains("建çé¢ç§¯") && !v.contains("æ°å¢")) col.putIfAbsent("area", c); |
| | | } |
| | | if (!col.containsKey("name")) continue; |
| | | for (int c = 0; c <= row.getLastCellNum(); c++) { |
| | | String v = cellStr(row, c); |
| | | if (v != null && v.trim().matches("åº\\s*å·")) { col.put("seq", c); break; } |
| | | } |
| | | // fallbackï¼yearcum ç¼ºå¤±æ¶æ startcum+2ï¼æ»æèµ/èªå¼å§ç´¯è®¡/æ¬å¹´è®¡å/èªå¹´å累计 çæ åååºï¼ |
| | | if (!col.containsKey("yearcum") && col.containsKey("startcum")) { |
| | | int yc = col.get("startcum") + 2; |
| | | if (yc <= row.getLastCellNum()) col.put("yearcum", yc); |
| | | } |
| | | // monthdone ç¼ºå¤±æ¶æ yearcum+1 |
| | | if (!col.containsKey("monthdone") && col.containsKey("yearcum")) { |
| | | int md = col.get("yearcum") + 1; |
| | | if (md <= row.getLastCellNum()) col.put("monthdone", md); |
| | | } |
| | | for (String k : new String[]{"builder", "nature", "start", "end", "total", "startcum", |
| | | "yearplan", "yearcum", "monthdone", "stage", "desc", "gk", "cs", "area"}) { |
| | | col.putIfAbsent(k, -1); |
| | | } |
| | | col.put("headerRow", r); |
| | | return col; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** ç»è®¡ sheet å
é¡¹ç®æ°æ®è¡æ°ï¼ç¨äºå¤ sheet å岿件é彿 sheetï¼ */ |
| | | private int countInvestRows(Sheet sh, Map<String, Integer> col) { |
| | | int nameCol = col.get("name"); |
| | | boolean hasSeq = col.containsKey("seq"); |
| | | int seqCol = hasSeq ? col.get("seq") : 0; |
| | | int count = 0; |
| | | for (int r = col.get("headerRow") + 1; r <= sh.getLastRowNum(); r++) { |
| | | Row row = sh.getRow(r); |
| | | if (row == null) continue; |
| | | String name = cellStr(row, nameCol); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | String nt = name.trim().replaceAll("[\r\n]+", ""); |
| | | if (nt.contains("å计") || nt.contains("æ»è®¡")) continue; |
| | | if (nt.matches("^[ä¸äºä¸åäºå
ä¸å
«ä¹å]+ã.*")) continue; |
| | | if (nt.replace(" ", "").matches(".*å°\\s*计.*")) continue; |
| | | if (nt.startsWith("å¡«æ¥") || nt.startsWith("åä½è´è´£äºº") || nt.matches("^\\d+ã.*")) continue; |
| | | if (!hasSeq) { |
| | | if (nt.length() > 4) count++; |
| | | } else { |
| | | String seq = cellStr(row, seqCol); |
| | | if (seq != null && seq.trim().matches("\\d+(\\.\\d+)?")) count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | /** 仿件åæåå¸å·ï¼å¦ "ï¼åæå¸éè¿ä¸å¿ï¼â¦" -> åæå¸ï¼ */ |
| | | private String cityFromFileName(String fileName) { |
| | | for (Map.Entry<String, String> e : INVEST_CITY_SHORT_TO_FULL.entrySet()) { |
| | | if (fileName.contains(e.getKey())) return e.getValue(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** ä» sheet 头é¨ãå¡«æ¥åä½ï¼XXãè¡æåå¸å· */ |
| | | private String cityFromSheetHeader(Sheet sh) { |
| | | for (int r = 0; r < Math.min(6, sh.getLastRowNum() + 1); r++) { |
| | | Row row = sh.getRow(r); |
| | | if (row == null) continue; |
| | | for (int c = 0; c <= row.getLastCellNum(); c++) { |
| | | String v = cellStr(row, c); |
| | | if (v == null || !v.contains("å¡«æ¥åä½")) continue; |
| | | java.util.regex.Matcher m = java.util.regex.Pattern |
| | | .compile("å¡«æ¥åä½[ï¼:ï¼(]?(çç« |ç¾ç« )?[ï¼)]?[ï¼:]*([^\\sï¼,ï¼;]{2,14})") |
| | | .matcher(v); |
| | | if (m.find()) { |
| | | String raw = m.group(2); |
| | | for (Map.Entry<String, String> e : INVEST_CITY_SHORT_TO_FULL.entrySet()) { |
| | | if (raw.contains(e.getKey())) return e.getValue(); |
| | | } |
| | | String city = investmentCity(raw); |
| | | if (city != null) return city; |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** è§£æãåäºäºãè§åç©æµé¡¹ç®æ¨¡æ¿æ¸
åï¼è¿åè§èå项ç®åéå */ |
| | | private Set<String> loadFiveYearProjectNames() { |
| | | Set<String> names = new LinkedHashSet<>(); |
| | | File template = resolveInvestTemplate("模æ¿_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls"); |
| | | if (template == null || !template.exists()) { |
| | | log.warn("åäºäºæ¨¡æ¿ä¸åå¨ï¼is_five_year æ è®°è·³è¿: {}", template); |
| | | return names; |
| | | } |
| | | try (java.io.InputStream is = new java.io.FileInputStream(template); |
| | | Workbook wb = WorkbookFactory.create(is)) { |
| | | Sheet sh = null; |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | if (wb.getSheetName(i).contains("åé¡¹ç®æèµ")) { sh = wb.getSheetAt(i); break; } |
| | | } |
| | | if (sh == null) sh = wb.getSheetAt(0); |
| | | Map<String, Integer> col = findInvestHeader(sh); |
| | | if (col == null) return names; |
| | | int nameCol = col.get("name"); |
| | | int seqCol = col.containsKey("seq") ? col.get("seq") : 0; |
| | | for (int r = col.get("headerRow") + 1; r <= sh.getLastRowNum(); r++) { |
| | | Row row = sh.getRow(r); |
| | | if (row == null) continue; |
| | | String seq = cellStr(row, seqCol); |
| | | String name = cellStr(row, nameCol); |
| | | if (seq == null || name == null) continue; |
| | | if (!seq.trim().matches("\\d+")) continue; |
| | | names.add(normalizeProjectName(name.trim())); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("load five year project names error", e); |
| | | } |
| | | return names; |
| | | } |
| | | |
| | | /** 项ç®åè§èåï¼å»ç©ºç½/æ¢è¡/æ¬å·å
容/æ ç¹/ã项ç®ãåç¼ï¼ç¨äºåäºäºæ¸
åå¹é
*/ |
| | | private String normalizeProjectName(String name) { |
| | | String s = name.replaceAll("\\s+", "").replace("\n", ""); |
| | | s = s.replaceAll("[ï¼(ã\\[][^ï¼)ã\\]]*[ï¼)ã\\]]", ""); |
| | | s = s.replaceAll("[ï¼,ã.ãï¼;ï¼:â\\-ï¼_/\\\\|]", ""); |
| | | s = s.replace("ç©æµé¡¹ç®", "ç©æµ").replace("项ç®", ""); |
| | | return s; |
| | | } |
| | | |
| | | /** æ¥è¡¨æ "2026-07" -> "7æ" */ |
| | | private String investMonthDir(String period) { |
| | | if (period == null || !period.matches("\\d{4}-\\d{2}")) return ""; |
| | | int month = Integer.parseInt(period.substring(5, 7)); |
| | | return month + "æ"; |
| | | } |
| | | |
| | | /** æèµè¾å
¥ç®å½å®ä½ï¼user.dir -> user.dir/.. é级åéï¼spring-boot:run ç user.dir æ¯ traffic-audit-serverï¼ */ |
| | | private File resolveInvestInputDir(String catDir, String monthDir) { |
| | | String rel = "docs/æèµ/è¾å
¥/" + catDir + "/" + monthDir; |
| | | for (String baseDir : new String[]{System.getProperty("user.dir"), System.getProperty("user.dir") + "/.."}) { |
| | | File f = new File(baseDir, rel); |
| | | if (f.isDirectory()) return f; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private File resolveInvestTemplate(String fileName) { |
| | | String rel = "docs/æèµ/模æ¿/" + fileName; |
| | | for (String baseDir : new String[]{System.getProperty("user.dir"), System.getProperty("user.dir") + "/.."}) { |
| | | File f = new File(baseDir, rel); |
| | | if (f.isFile()) return f; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** åç±»å«åæ¥è¡¨ææåº¦æ°æ® + INVEST å®¡æ ¸ç»ææ¸
çï¼å¹çï¼ */ |
| | | private void cleanInvestMonthly(String category, String period) { |
| | | List<InvestmentProject> sameCat = investProjectMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentProject>().eq(InvestmentProject::getCategory, category)); |
| | | java.util.Set<Long> catProjectIds = new HashSet<>(); |
| | | for (InvestmentProject pc : sameCat) catProjectIds.add(pc.getId()); |
| | | if (!catProjectIds.isEmpty()) { |
| | | investMonthlyMapper.delete(new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period) |
| | | .in(InvestmentMonthly::getProjectId, catProjectIds)); |
| | | } |
| | | 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)); |
| | | } |
| | | } |
| | | |
| | | /** 项ç®ä¸»æ¡£ upsertï¼æ 项ç®å+ç±»å«ï¼+ æåº¦åå
¥ */ |
| | | private void saveInvestProjectRow(InvestmentProject project, InvestmentMonthly monthly) { |
| | | InvestmentProject exist = investProjectMapper.selectOne(new LambdaQueryWrapper<InvestmentProject>() |
| | | .eq(InvestmentProject::getProjectName, project.getProjectName()) |
| | | .eq(InvestmentProject::getCategory, project.getCategory()) |
| | | .last("LIMIT 1")); |
| | | if (exist == null) { |
| | | investProjectMapper.insert(project); |
| | | monthly.setProjectId(project.getId()); |
| | | } else { |
| | | project.setId(exist.getId()); |
| | | project.setCreatedAt(exist.getCreatedAt()); |
| | | investProjectMapper.updateById(project); |
| | | monthly.setProjectId(exist.getId()); |
| | | } |
| | | investMonthlyMapper.insert(monthly); |
| | | } |
| | | |
| | | private String cellStr(Row row, int idx) { |
| | | if (idx < 0) return null; |
| | | return getString(row, idx); |
| | | } |
| | | |
| | | private Double cellNum(Row row, int idx) { |
| | | if (idx < 0) return 0.0; |
| | | return getDecimal(row, idx); |
| | | } |
| | | |
| | | private Double cellNumOrNull(Row row, int idx) { |
| | | if (idx < 0) return null; |
| | | return getDecimalOrNull(row, idx); |
| | | } |
| | | |
| | | /** å¸å·å表è¡è®°å½ï¼ä¿çæä»¶ + Excel è¡å·ç¨äºå¤±è´¥å®ä½ */ |
| | | private static class InvestCityRow { |
| | | final int excelRow; |
| | | final String fileName; |
| | | final InvestmentProject project; |
| | | final InvestmentMonthly monthly; |
| | | |
| | | InvestCityRow(int excelRow, String fileName, InvestmentProject project, InvestmentMonthly monthly) { |
| | | this.excelRow = excelRow; |
| | | this.fileName = fileName; |
| | | this.project = project; |
| | | this.monthly = monthly; |
| | | } |
| | | } |
| | | /** æèµç³»ç»å¯¼åºå¯¼å
¥ï¼å®¡æ ¸æ¯å¯¹åºåï¼ï¼åºå®8åï¼è¡å·/åä½/æ¶æ/è®¡åæ»æèµ/èªå¼å§ç´¯è®¡/æ¬å¹´è®¡å/èªå¹´å累计/彿宿 */ |
| | | public int importInvestmentSystem(MultipartFile file, String period) throws Exception { |
| | | List<InvestmentSystem> list = new ArrayList<>(); |
| | | int failRows = 0; |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = getString(row, 1); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | InvestmentSystem sys = new InvestmentSystem(); |
| | | sys.setReportPeriod(period); |
| | | sys.setProjectName(name.trim()); |
| | | sys.setTotalInvestment(getDecimal(row, 3)); |
| | | sys.setStartCum(getDecimal(row, 4)); |
| | | sys.setYearPlan(getDecimal(row, 5)); |
| | | sys.setYearCum(getDecimal(row, 6)); |
| | | sys.setMonthDone(getDecimal(row, 7)); |
| | | list.add(sys); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("Investment system parse error", e); |
| | | throw e; |
| | | } |
| | | investSystemMapper.delete(new LambdaQueryWrapper<InvestmentSystem>() |
| | | .eq(InvestmentSystem::getReportPeriod, period)); |
| | | int success = 0; |
| | | for (InvestmentSystem sys : list) { |
| | | try { |
| | | investSystemMapper.insert(sys); |
| | | success++; |
| | | } catch (Exception e) { |
| | | failRows++; |
| | | log.error("Investment system insert error: {}", sys.getProjectName(), e); |
| | | } |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "INVEST_SYSTEM", period, list.size(), success, failRows, null); |
| | | log.info("Investment system imported: {} rows for {}", success, period); |
| | | return success; |
| | | } |
| | | |
| | | /** åå¸å
¬äº¤ææ¥å¯¼å
¥ï¼åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼ä¼ä¸çº§æç»ï¼åå
Œ
±æ±½çµè½¦åæ®µï¼ */ |
| | | public ImportResult importCityBus(MultipartFile file, String period) throws Exception { |
| | | List<CityBusRow> rows = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Row header = sheet.getRow(0); |
| | | if (header == null || getString(header, 2) == null |
| | | || !getString(header, 2).contains("ä¼ä¸åç§°")) { |
| | | throw new RuntimeException("æªæ¾å°è¡¨å¤´è¡ï¼éå
å« ä¼ä¸åç§° åï¼"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = getString(row, 2); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | CityBusMonthly item = new CityBusMonthly(); |
| | | item.setReportPeriod(period); |
| | | item.setRegionCode(getString(row, 0)); |
| | | item.setCity(RegionUtil.cityByCode(getString(row, 0))); |
| | | item.setEnterpriseCode(getString(row, 1)); |
| | | item.setEnterpriseName(name.trim()); |
| | | item.setOpVehicles(getDecimal(row, 5)); |
| | | item.setStopTotal(getDecimal(row, 6)); |
| | | item.setStopCity(getDecimal(row, 7)); |
| | | item.setPassengerVolume(getDecimal(row, 8)); |
| | | item.setPassengerCity(getDecimal(row, 9)); |
| | | item.setTurnover(getDecimal(row, 10)); |
| | | item.setTurnoverCity(getDecimal(row, 11)); |
| | | item.setRailPassengerVolume(getDecimal(row, 13)); |
| | | item.setRailTurnover(getDecimal(row, 14)); |
| | | item.setFerryPassengerVolume(getDecimal(row, 17)); |
| | | item.setFerryTurnover(getDecimal(row, 18)); |
| | | item.setAvgDistance(getDecimal(row, 19)); |
| | | item.setAvgDistanceCity(getDecimal(row, 20)); |
| | | item.setContactPerson(getString(row, 24)); |
| | | item.setContactPhone(getString(row, 25)); |
| | | item.setReportDate(getDateString(row, 26)); |
| | | item.setRateOpVehiclesMom(getDecimal(row, 29)); |
| | | item.setRatePassengerMom(getDecimal(row, 30)); |
| | | item.setRatePassengerCityMom(getDecimal(row, 31)); |
| | | item.setRateChengxiangMom(getDecimal(row, 32)); |
| | | item.setRateChengxiangTurnoverMom(getDecimal(row, 33)); |
| | | item.setAvgDistanceMomChange(getDecimal(row, 34)); |
| | | item.setAvgDistanceCityMomChange(getDecimal(row, 35)); |
| | | item.setDailyPassengerPerVehicle(getDecimal(row, 36)); |
| | | item.setRateOpVehiclesYoy(getDecimal(row, 39)); |
| | | item.setRatePassengerYoy(getDecimal(row, 40)); |
| | | item.setRatePassengerCityYoy(getDecimal(row, 41)); |
| | | item.setRateChengxiangYoy(getDecimal(row, 42)); |
| | | item.setRateChengxiangTurnoverYoy(getDecimal(row, 43)); |
| | | item.setStopCityRatio(getDecimal(row, 44)); |
| | | item.setPassengerCityRatio(getDecimal(row, 45)); |
| | | item.setChengxiangRatio(getDecimal(row, 46)); |
| | | item.setRateTurnoverYoy(getDecimal(row, 47)); |
| | | item.setRateTurnoverMom(getDecimal(row, 48)); |
| | | item.setPassengerChengxiang(getDecimal(row, 49)); |
| | | item.setTurnoverChengxiang(getDecimal(row, 50)); |
| | | item.setAvgDistanceChengxiang(getDecimal(row, 51)); |
| | | item.setVerifyExplanation(getString(row, 65)); |
| | | item.setReportNote(getString(row, 66)); |
| | | item.setModifyRecord(getString(row, 67)); |
| | | rows.add(new CityBusRow(r + 1, item)); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("CityBus import parse error: {}", e.getMessage()); |
| | | throw e; |
| | | } |
| | | // å¹çï¼åæ¥è¡¨æå
æ¸
æ§æ°æ®ä¸ CITY_BUS å®¡æ ¸ç»æ |
| | | cityBusMapper.delete(new LambdaQueryWrapper<CityBusMonthly>() |
| | | .eq(CityBusMonthly::getReportPeriod, period)); |
| | | List<Long> busRuleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if ("CITY_BUS".equals(rule.getReportType())) busRuleIds.add(rule.getId()); |
| | | } |
| | | if (!busRuleIds.isEmpty()) { |
| | | auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, busRuleIds)); |
| | | } |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (CityBusRow item : rows) { |
| | | try { |
| | | cityBusMapper.insert(item.entity); |
| | | 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.excelRow + " è¡ï¼" + item.entity.getEnterpriseName() + "ï¼ï¼" + reason); |
| | | log.error("CityBus insert error: row {}", item.excelRow, e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | recordBatch(file.getOriginalFilename(), "CITY_BUS", period, rows.size(), success, failDetails.size(), errorDetail); |
| | | log.info("CityBus imported: {} rows for {}", success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | /** åå¸å
¬äº¤è¡è®°å½ï¼ä¿ç Excel è¡å·ç¨äºå¤±è´¥å®ä½ */ |
| | | private static class CityBusRow { |
| | | final int excelRow; |
| | | final CityBusMonthly entity; |
| | | |
| | | CityBusRow(int excelRow, CityBusMonthly entity) { |
| | | this.excelRow = excelRow; |
| | | this.entity = entity; |
| | | } |
| | | } |
| | | |
| | | /** 巡游åºç§ææ¥å¯¼å
¥ï¼å¸å·çº§ï¼17 è¡ï¼å«æ ¸å®è¯´æ/䏿¥è¯´æ/ä¿®æ¹è®°å½ï¼ */ |
| | | public ImportResult importCityTaxi(MultipartFile file, String period) throws Exception { |
| | | List<TaxiRow> rows = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Row header = sheet.getRow(0); |
| | | if (header == null || getString(header, 2) == null |
| | | || !getString(header, 2).contains("ä¼ä¸åç§°")) { |
| | | throw new RuntimeException("æªæ¾å°è¡¨å¤´è¡ï¼éå
å« ä¼ä¸åç§° åï¼"); |
| | | } |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = getString(row, 2); |
| | | if (name == null || name.trim().isEmpty()) continue; |
| | | CityTaxiMonthly item = new CityTaxiMonthly(); |
| | | item.setReportPeriod(period); |
| | | item.setRegionCode(getString(row, 0)); |
| | | item.setCity(cityOf(getString(row, 0), name)); |
| | | item.setTripTotal(getDecimal(row, 4)); |
| | | item.setTripCity(getDecimal(row, 5)); |
| | | item.setPassengerVolume(getDecimal(row, 6)); |
| | | item.setPassengerCity(getDecimal(row, 7)); |
| | | item.setTurnover(getDecimal(row, 8)); |
| | | item.setTurnoverCity(getDecimal(row, 9)); |
| | | item.setOpVehicles(getDecimal(row, 10)); |
| | | item.setAvgDistance(getDecimal(row, 11)); |
| | | item.setAvgDistanceCity(getDecimal(row, 12)); |
| | | item.setContactPerson(getString(row, 15)); |
| | | item.setContactPhone(getString(row, 16)); |
| | | item.setReportDate(getDateString(row, 17)); |
| | | item.setDailyTripsPerVehicle(getDecimal(row, 18)); |
| | | item.setDailyPassengerPerVehicle(getDecimal(row, 19)); |
| | | item.setPassengersPerTrip(getDecimal(row, 20)); |
| | | item.setRateOpVehiclesMom(getDecimal(row, 21)); |
| | | item.setRateOpVehiclesYoy(getDecimal(row, 22)); |
| | | item.setRateTripMom(getDecimal(row, 23)); |
| | | item.setRateTripYoy(getDecimal(row, 24)); |
| | | item.setRatePassengerMom(getDecimal(row, 25)); |
| | | item.setRatePassengerYoy(getDecimal(row, 26)); |
| | | item.setAvgDistanceMomChange(getDecimal(row, 27)); |
| | | item.setPassengersPerTripCity(getDecimal(row, 28)); |
| | | item.setRateTripCityMom(getDecimal(row, 29)); |
| | | item.setRatePassengerCityMom(getDecimal(row, 30)); |
| | | item.setAvgDistanceCityMomChange(getDecimal(row, 31)); |
| | | item.setTripCityRatio(getDecimal(row, 32)); |
| | | item.setPassengerCityRatio(getDecimal(row, 33)); |
| | | item.setPassengersPerTripChengxiang(getDecimal(row, 34)); |
| | | item.setRateTripChengxiangMom(getDecimal(row, 35)); |
| | | item.setRatePassengerChengxiangMom(getDecimal(row, 36)); |
| | | item.setAvgDistanceChengxiang(getDecimal(row, 37)); |
| | | item.setVerifyExplanation(getString(row, 38)); |
| | | item.setReportNote(getString(row, 39)); |
| | | item.setModifyRecord(getString(row, 40)); |
| | | rows.add(new TaxiRow(r + 1, item)); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("CityTaxi import parse error: {}", e.getMessage()); |
| | | throw e; |
| | | } |
| | | // å¹çï¼åæ¥è¡¨æå
æ¸
æ§æ°æ®ä¸ CITY_TAXI å®¡æ ¸ç»æ |
| | | cityTaxiMapper.delete(new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .eq(CityTaxiMonthly::getReportPeriod, period)); |
| | | deleteAuditResults(period, "CITY_TAXI"); |
| | | int success = 0; |
| | | List<String> failDetails = new ArrayList<>(); |
| | | for (TaxiRow item : rows) { |
| | | try { |
| | | cityTaxiMapper.insert(item.entity); |
| | | 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.excelRow + " è¡ï¼" + item.entity.getCity() + "ï¼ï¼" + reason); |
| | | log.error("CityTaxi insert error: row {}", item.excelRow, e); |
| | | } |
| | | } |
| | | String errorDetail = String.join("\n", failDetails); |
| | | if (errorDetail.length() > 4000) errorDetail = errorDetail.substring(0, 4000) + "\nâ¦â¦"; |
| | | recordBatch(file.getOriginalFilename(), "CITY_TAXI", period, rows.size(), success, failDetails.size(), errorDetail); |
| | | log.info("CityTaxi imported: {} rows for {}", success, period); |
| | | return new ImportResult(success, failDetails.size(), failDetails); |
| | | } |
| | | |
| | | /** åºç§è½¦è¿æ¿è½¦è¾ä¿¡æ¯å¯¼å
¥ï¼è½¦è¾çº§æç»ï¼æå¸å·å½å±ç»è®¡ç¨ï¼ */ |
| | | public synchronized int importCityTaxiAuth(MultipartFile file, String period) throws Exception { |
| | | List<CityTaxiAuth> list = new ArrayList<>(); |
| | | try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | for (int r = 1; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String plate = getString(row, 0); |
| | | if (plate == null || plate.trim().isEmpty()) continue; |
| | | CityTaxiAuth item = new CityTaxiAuth(); |
| | | item.setReportPeriod(period); |
| | | item.setCity(taxiCity(getString(row, 9), getString(row, 8), getString(row, 4), getString(row, 18))); |
| | | item.setPlateNo(plate.trim()); |
| | | item.setVehicleType(getString(row, 1)); |
| | | item.setBizType(getString(row, 2)); |
| | | item.setOwnerName(getString(row, 3)); |
| | | item.setLicenseNo(getString(row, 4)); |
| | | item.setFuelType(getString(row, 7)); |
| | | item.setArchiveNo(getString(row, 8)); |
| | | item.setRoadCertNo(getString(row, 9)); |
| | | item.setOperatorName(getString(row, 17)); |
| | | item.setOperatorAddr(getString(row, 18)); |
| | | item.setOpStatus(getString(row, 19)); |
| | | item.setCertStatus(getString(row, 20)); |
| | | list.add(item); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("CityTaxiAuth import parse error", e); |
| | | throw e; |
| | | } |
| | | // å¹çï¼åæ¥è¡¨æå
æ¸
æ§æ°æ®ï¼è¿æ¿è½¦è¾éå¤§ï¼æ¹éæ¸
çï¼ |
| | | cityTaxiAuthMapper.delete(new LambdaQueryWrapper<CityTaxiAuth>() |
| | | .eq(CityTaxiAuth::getReportPeriod, period)); |
| | | deleteAuditResults(period, "CITY_TAXI"); |
| | | // æ¹éæå
¥ï¼BATCH 模å¼ï¼ï¼42636 è¡çº¦ 20~40 ç§ï¼éæ¡æå
¥é约 6 åéä¼è§¦åå端è¶
æ¶ |
| | | int success = 0; |
| | | int fail = 0; |
| | | try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) { |
| | | CityTaxiAuthMapper batchMapper = session.getMapper(CityTaxiAuthMapper.class); |
| | | for (CityTaxiAuth item : list) { |
| | | batchMapper.insert(item); |
| | | } |
| | | session.flushStatements(); |
| | | session.commit(); |
| | | success = list.size(); |
| | | } catch (Exception e) { |
| | | fail = list.size(); |
| | | log.error("CityTaxiAuth batch insert error", e); |
| | | } |
| | | recordBatch(file.getOriginalFilename(), "CITY_TAXI_AUTH", period, list.size(), success, fail, null); |
| | | log.info("CityTaxiAuth imported: {} rows for {}", success, period); |
| | | return success; |
| | | } |
| | | |
| | | /** è¿æ¿åºç§è½¦å¸å·æåï¼éè·¯è¿è¾è¯åå·âæ¡£æ¡å·âç»è¥æå·â䏿·å°åï¼å 6 ä½åºå代ç */ |
| | | private static final java.util.regex.Pattern TAXI_REGION_PAT = |
| | | java.util.regex.Pattern.compile("(4290\\d{2}|42(?:0[1-9]|1[0-3]|28)\\d{2})"); |
| | | |
| | | private String taxiCity(String... fields) { |
| | | for (String f : fields) { |
| | | if (f == null) continue; |
| | | java.util.regex.Matcher m = TAXI_REGION_PAT.matcher(f); |
| | | if (m.find()) { |
| | | String city = RegionUtil.cityByCode(m.group(1)); |
| | | if (city != null) return city; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** å¸å·å½å±ï¼ä¼å
æåºå代ç è¯å«ï¼å
¶æ¬¡æåç§°è§èå */ |
| | | private String cityOf(String regionCode, String name) { |
| | | String city = RegionUtil.cityByCode(regionCode); |
| | | if (city != null) return city; |
| | | return RegionUtil.normalizeCityName(name); |
| | | } |
| | | |
| | | /** æ¸
餿æ¥è¡¨ç±»å卿宿¥è¡¨æçå®¡æ ¸ç»æï¼å¯¼å
¥å¹çï¼ */ |
| | | private void deleteAuditResults(String period, String reportType) { |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (com.trafficaudit.rulemanage.entity.AuditRule rule : ruleMapper.selectList(null)) { |
| | | if (reportType.equals(rule.getReportType())) ruleIds.add(rule.getId()); |
| | | } |
| | | if (!ruleIds.isEmpty()) { |
| | | auditResultMapper.delete(new LambdaQueryWrapper<AuditResult>() |
| | | .eq(AuditResult::getReportPeriod, period) |
| | | .in(AuditResult::getRuleId, ruleIds)); |
| | | } |
| | | } |
| | | |
| | | /** 巡游åºç§è¡è®°å½ï¼ä¿ç Excel è¡å·ç¨äºå¤±è´¥å®ä½ */ |
| | | private static class TaxiRow { |
| | | final int excelRow; |
| | | final CityTaxiMonthly entity; |
| | | |
| | | TaxiRow(int excelRow, CityTaxiMonthly entity) { |
| | | this.excelRow = excelRow; |
| | | this.entity = entity; |
| | | } |
| | | } |
| | | |
| | | private Sheet pickInvestmentSheet(Workbook wb, boolean passenger) { |
| | | if (passenger) { |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | String name = wb.getSheetName(i); |
| | | if (name != null && name.contains("æç»")) return wb.getSheetAt(i); |
| | | } |
| | | return wb.getSheetAt(0); |
| | | } |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | String name = wb.getSheetName(i); |
| | | if (name != null && name.contains("åé¡¹ç®æèµ")) return wb.getSheetAt(i); |
| | | } |
| | | return wb.getSheetAt(0); |
| | | } |
| | | |
| | | /** å¸å·å°èè¡è¯å«ï¼çå/è§èå -> è§èåï¼éå¸å·è¡è¿å null */ |
| | | private String investmentCity(String a) { |
| | | if (a == null) return null; |
| | | String full = INVEST_CITY_SHORT_TO_FULL.get(a); |
| | | if (full != null) return full; |
| | | String norm = RegionUtil.normalizeCityName(a); |
| | | if (RegionUtil.CITY_LIST.contains(norm)) return norm; |
| | | return null; |
| | | } |
| | | |
| | | /** å¼å·¥/å»ºææ¶é´ï¼æ°åï¼2025 æ 202501ï¼æææ¬ç»ä¸ä¸º yyyy æ yyyyMM */ |
| | | private String investTime(String v) { |
| | | if (v == null || v.trim().isEmpty()) return null; |
| | | String t = v.trim(); |
| | | if (t.matches("\\d+\\.0")) t = t.substring(0, t.length() - 2); |
| | | if (t.matches("\\d{4}")) return t; |
| | | if (t.matches("\\d{6}")) return t; |
| | | if (t.matches("\\d{4}-\\d{2}-\\d{2}")) return t; |
| | | return t; |
| | | } |
| | | |
| | | /** æèµè¡è®°å½ï¼ä¿ç Excel è¡å·ç¨äºå¤±è´¥å®ä½ */ |
| | | private static class InvestRow { |
| | | final int excelRow; |
| | | final InvestmentProject project; |
| | | final InvestmentMonthly monthly; |
| | | |
| | | InvestRow(int excelRow, InvestmentProject project, InvestmentMonthly monthly) { |
| | | this.excelRow = excelRow; |
| | | this.project = project; |
| | | this.monthly = monthly; |
| | | } |
| | | } |
| | | |
| | | // ========== éç¨æä»¶å¤¹æ¹é导å
¥ï¼æå¡ç«¯ç®å½æµè§å¨ï¼ ========== |
| | | |
| | | /** |
| | | * ç®å½æµè§å¨ï¼path 为空æ¶è¿åé¡¹ç® docs æ°æ®ç®å½ä¸çåç®å½ï¼ |
| | | * è¿åå½åç®å½ç»å¯¹è·¯å¾ãä¸çº§ç®å½ãåç®å½å表åå
¶ä¸ç´æ¥å
å«ç Excel æä»¶å/æ°éã |
| | | */ |
| | | public Map<String, Object> listDirs(String path) throws Exception { |
| | | File cur; |
| | | if (path == null || path.trim().isEmpty()) { |
| | | File docs = resolveDocsDir(); |
| | | if (!docs.isDirectory()) { |
| | | throw new RuntimeException("æªæ¾å°æ°æ®ç®å½ docsï¼å½åè¿è¡ç®å½ï¼" + System.getProperty("user.dir") + "ï¼"); |
| | | } |
| | | cur = docs; |
| | | } else { |
| | | cur = new File(path); |
| | | if (!cur.isDirectory()) { |
| | | throw new RuntimeException("ç®å½ä¸å卿䏿¯æä»¶å¤¹ï¼" + path); |
| | | } |
| | | } |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("current", cur.getAbsolutePath()); |
| | | result.put("parent", cur.getParent() == null ? "" : cur.getParent()); |
| | | List<Map<String, Object>> dirs = new ArrayList<>(); |
| | | File[] subs = cur.listFiles(File::isDirectory); |
| | | if (subs != null) { |
| | | Arrays.sort(subs, Comparator.comparing(File::getName)); |
| | | for (File d : subs) { |
| | | if (d.isHidden()) continue; |
| | | Map<String, Object> m = new LinkedHashMap<>(); |
| | | m.put("name", d.getName()); |
| | | m.put("path", d.getAbsolutePath()); |
| | | dirs.add(m); |
| | | } |
| | | } |
| | | result.put("dirs", dirs); |
| | | List<String> excelFiles = new ArrayList<>(); |
| | | File[] files = cur.listFiles((d, n) -> { |
| | | String lower = n.toLowerCase(); |
| | | return lower.endsWith(".xlsx") || lower.endsWith(".xls"); |
| | | }); |
| | | if (files != null) { |
| | | Arrays.sort(files, Comparator.comparing(File::getName)); |
| | | for (File f : files) excelFiles.add(f.getName()); |
| | | } |
| | | result.put("excelCount", excelFiles.size()); |
| | | result.put("files", excelFiles); |
| | | return result; |
| | | } |
| | | |
| | | /** å䏿¥æ¾é¡¹ç® docs æ°æ®ç®å½ï¼å
¼å®¹ä» IDEA 以 server 模åç®å½å¯å¨çæ
åµï¼ */ |
| | | private File resolveDocsDir() { |
| | | File base = new File(System.getProperty("user.dir")); |
| | | File cur = base; |
| | | for (int i = 0; i < 4 && cur != null; i++) { |
| | | File candidate = new File(cur, "docs"); |
| | | if (candidate.isDirectory()) return candidate; |
| | | cur = cur.getParentFile(); |
| | | } |
| | | return new File(base, "docs"); |
| | | } |
| | | |
| | | /** |
| | | * 仿å¡ç«¯æä»¶å¤¹æ¹é导å
¥ï¼åªæ«æ dir ç´æ¥å
å«ç .xlsx/.xlsï¼ |
| | | * æ type é个è°ç¨å¯¹åºå¯¼å
¥æ¹æ³ï¼ä¸ä¸ä¼ 导å
¥åä¸å¥è§£æé»è¾ï¼ï¼è¿åéæä»¶ç»æã |
| | | */ |
| | | public Map<String, Object> importFromDir(String type, String period, boolean skipImported, String dir) throws Exception { |
| | | File folder = new File(dir); |
| | | if (!folder.isDirectory()) { |
| | | throw new RuntimeException("ç®å½ä¸å卿䏿¯æä»¶å¤¹ï¼" + dir); |
| | | } |
| | | File[] files = folder.listFiles((d, n) -> { |
| | | String lower = n.toLowerCase(); |
| | | return lower.endsWith(".xlsx") || lower.endsWith(".xls"); |
| | | }); |
| | | if (files == null || files.length == 0) { |
| | | throw new RuntimeException("ç®å½ä¸æ²¡æ Excel æä»¶ï¼.xlsx/.xlsï¼ï¼" + folder.getAbsolutePath()); |
| | | } |
| | | Arrays.sort(files, Comparator.comparing(File::getName)); |
| | | List<Map<String, Object>> fileResults = new ArrayList<>(); |
| | | int totalSuccess = 0, totalFail = 0; |
| | | for (File f : files) { |
| | | Map<String, Object> fr = new LinkedHashMap<>(); |
| | | fr.put("fileName", f.getName()); |
| | | try { |
| | | String filePeriod = detectPeriod(f.getName(), period); |
| | | if (filePeriod == null || filePeriod.trim().isEmpty()) { |
| | | throw new RuntimeException("æ æ³ä»æä»¶åè¯å«æ¥è¡¨æï¼æ¯æ 2026å¹´7æ / 2026.7 / 2026-07 / 7æï¼ï¼ä¹æªéæ©æ¥è¡¨æï¼" + f.getName()); |
| | | } |
| | | fr.put("period", filePeriod); |
| | | if (skipImported && alreadyImported(filePeriod, f.getName())) { |
| | | fr.put("skipped", true); |
| | | fr.put("success", 0); |
| | | fr.put("fail", 0); |
| | | totalFail++; |
| | | fileResults.add(fr); |
| | | continue; |
| | | } |
| | | byte[] bytes; |
| | | try (InputStream is = new FileInputStream(f)) { |
| | | bytes = IoUtil.readBytes(is); |
| | | } |
| | | MultipartFile mf = new ByteArrayMultipartFile(f.getName(), bytes); |
| | | ImportResult r = importDirFile(type, mf, filePeriod); |
| | | fr.put("success", r.getSuccess()); |
| | | fr.put("fail", r.getFail()); |
| | | if (r.getFail() > 0 && r.getFailDetails() != null && !r.getFailDetails().isEmpty()) { |
| | | String d = r.getFailDetails().get(0); |
| | | if (d.length() > 300) d = d.substring(0, 300); |
| | | fr.put("error", d); |
| | | } |
| | | totalSuccess += r.getSuccess(); |
| | | totalFail += r.getFail(); |
| | | } catch (Exception e) { |
| | | String msg = e.getMessage() == null ? e.toString() : e.getMessage(); |
| | | if (msg.length() > 300) msg = msg.substring(0, 300); |
| | | fr.put("success", 0); |
| | | fr.put("fail", 0); |
| | | fr.put("error", msg); |
| | | totalFail++; |
| | | } |
| | | fileResults.add(fr); |
| | | } |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("type", type); |
| | | result.put("total", fileResults.size()); |
| | | result.put("success", totalSuccess); |
| | | result.put("fail", totalFail); |
| | | result.put("files", fileResults); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * ææä»¶è·¯å¾å¯¼å
¥å个 Excelï¼ä¾å端éæä»¶å¯¼å
¥æ¾ç¤ºè¿åº¦ï¼ã |
| | | * æ¥è¡¨æä¼å
仿件åè¯å«ï¼å¦ 2026å¹´7æ / 2026.7 / 2026-07 / 7æï¼ï¼è¯å«ä¸å°æç¨ periodã |
| | | */ |
| | | public Map<String, Object> importDirFileByPath(String type, String period, boolean skipImported, String filePath) throws Exception { |
| | | File f = new File(filePath); |
| | | if (!f.isFile()) { |
| | | throw new RuntimeException("æä»¶ä¸åå¨ï¼" + filePath); |
| | | } |
| | | String filePeriod = detectPeriod(f.getName(), period); |
| | | if (filePeriod == null || filePeriod.trim().isEmpty()) { |
| | | throw new RuntimeException("æ æ³ä»æä»¶åè¯å«æ¥è¡¨æï¼æ¯æ 2026å¹´7æ / 2026.7 / 2026-07 / 7æï¼ï¼ä¹æªéæ©æ¥è¡¨æï¼" + f.getName()); |
| | | } |
| | | Map<String, Object> fr = new LinkedHashMap<>(); |
| | | fr.put("fileName", f.getName()); |
| | | fr.put("period", filePeriod); |
| | | if (skipImported && alreadyImported(filePeriod, f.getName())) { |
| | | fr.put("skipped", true); |
| | | fr.put("success", 0); |
| | | fr.put("fail", 0); |
| | | fr.put("error", "该æ¥è¡¨æå·²å¯¼å
¥è¿æ¤æä»¶ï¼å·²è·³è¿ï¼å¦éè¦çè¯·åæ¶å¾éâè·³è¿å·²å¯¼å
¥æä»¶âï¼"); |
| | | return fr; |
| | | } |
| | | byte[] bytes; |
| | | try (InputStream is = new FileInputStream(f)) { |
| | | bytes = IoUtil.readBytes(is); |
| | | } |
| | | ImportResult r = importDirFile(type, new ByteArrayMultipartFile(f.getName(), bytes), filePeriod); |
| | | fr.put("success", r.getSuccess()); |
| | | fr.put("fail", r.getFail()); |
| | | if (r.getFail() > 0 && r.getFailDetails() != null && !r.getFailDetails().isEmpty()) { |
| | | String d = r.getFailDetails().get(0); |
| | | if (d.length() > 300) d = d.substring(0, 300); |
| | | fr.put("error", d); |
| | | } |
| | | return fr; |
| | | } |
| | | |
| | | /** 该æ¥è¡¨ææ¯å¦å·²æå导å
¥è¿ååæä»¶ï¼ç¨äºæä»¶å¤¹éå¤å¯¼å
¥æ¶è·³è¿ï¼ */ |
| | | private boolean alreadyImported(String period, String fileName) { |
| | | if (period == null || fileName == null) return false; |
| | | Long cnt = importBatchMapper.selectCount(new LambdaQueryWrapper<ImportBatch>() |
| | | .eq(ImportBatch::getReportPeriod, period) |
| | | .eq(ImportBatch::getFileName, fileName) |
| | | .eq(ImportBatch::getFailRows, 0)); |
| | | return cnt != null && cnt > 0; |
| | | } |
| | | |
| | | /** 仿件åè¯å«æ¥è¡¨æï¼2026å¹´7æ / 2026å¹´07æ / 2026.7 / 2026-07 / 202607 / 7æï¼æ å¹´ä»½ç¨ period 年份æå½åå¹´ä»½ï¼ */ |
| | | private String detectPeriod(String fileName, String defaultPeriod) { |
| | | if (fileName == null) return defaultPeriod; |
| | | String name = fileName.replace("ï¼", "(").replace("ï¼", ")").replace(" ", ""); |
| | | java.util.regex.Matcher m; |
| | | m = java.util.regex.Pattern.compile("(\\d{4})\\s*å¹´\\s*(\\d{1,2})\\s*æ").matcher(name); |
| | | if (m.find()) return String.format("%04d-%02d", Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2))); |
| | | m = java.util.regex.Pattern.compile("(\\d{4})[.\\-/\\s](\\d{1,2})").matcher(name); |
| | | if (m.find()) return String.format("%04d-%02d", Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2))); |
| | | m = java.util.regex.Pattern.compile("(?:^|[^\\d])(\\d{4})(\\d{2})(?=[^\\d]|$)").matcher(name); |
| | | if (m.find()) { |
| | | int y = Integer.parseInt(m.group(1)); |
| | | int mo = Integer.parseInt(m.group(2)); |
| | | if (y >= 2000 && y <= 2100 && mo >= 1 && mo <= 12) { |
| | | return String.format("%04d-%02d", y, mo); |
| | | } |
| | | } |
| | | m = java.util.regex.Pattern.compile("(\\d{1,2})\\s*æ").matcher(name); |
| | | if (m.find()) { |
| | | int mo = Integer.parseInt(m.group(1)); |
| | | if (mo >= 1 && mo <= 12) { |
| | | return String.format("%04d-%02d", defaultYear(defaultPeriod), mo); |
| | | } |
| | | } |
| | | return defaultPeriod; |
| | | } |
| | | |
| | | private int defaultYear(String defaultPeriod) { |
| | | if (defaultPeriod != null && defaultPeriod.matches("\\d{4}-\\d{2}")) { |
| | | return Integer.parseInt(defaultPeriod.substring(0, 4)); |
| | | } |
| | | return java.time.Year.now().getValue(); |
| | | } |
| | | |
| | | /** ç±»åååï¼ä¸ä¸ä¼ 导å
¥åä¸å¥è§£ææ¹æ³ */ |
| | | private ImportResult importDirFile(String type, MultipartFile file, String period) throws Exception { |
| | | switch (type) { |
| | | case "h2032": return importH2032(file, period); |
| | | case "passengerMonthly": return importPassengerMonthly(file, period); |
| | | case "passengerIndividual": return importPassengerIndividual(file, period); |
| | | case "passengerAuth": return importPassengerAuth(file, period); |
| | | case "energyMonthly": return importEnergyMonthly(file, period); |
| | | case "energyAuth": return importEnergyAuth(file, period); |
| | | case "cityBus": return importCityBus(file, period); |
| | | case "cityTaxi": return importCityTaxi(file, period); |
| | | case "cityTaxiAuth": return new ImportResult(importCityTaxiAuth(file, period), 0, new ArrayList<>()); |
| | | case "investment": return importInvestment(file, period, "客è¿ç«åº"); |
| | | case "investmentLogistics": return importInvestment(file, period, "ç©æµååº"); |
| | | case "investmentSystem": return new ImportResult(importInvestmentSystem(file, period), 0, new ArrayList<>()); |
| | | case "transportAuth": return new ImportResult(importTransportAuth(file, period), 0, new ArrayList<>()); |
| | | case "trackMileage": return new ImportResult(importTrackMileage(file, period), 0, new ArrayList<>()); |
| | | case "freightTurnover": return new ImportResult(importFreightTurnover(file, period), 0, new ArrayList<>()); |
| | | case "scaleSplit": return new ImportResult(importScaleSplit(file, period), 0, new ArrayList<>()); |
| | | default: throw new RuntimeException("䏿¯æç导å
¥ç±»åï¼" + type); |
| | | } |
| | | } |
| | | |
| | | /** ææå¡ç«¯æä»¶å
è£
为 MultipartFileï¼å¤ç¨å导å
¥æ¹æ³ */ |
| | | public static class ByteArrayMultipartFile implements MultipartFile { |
| | | private final String name; |
| | | private final byte[] content; |
| | | |
| | | public ByteArrayMultipartFile(String name, byte[] content) { |
| | | this.name = name; |
| | | this.content = content; |
| | | } |
| | | |
| | | @Override |
| | | public String getName() { return name; } |
| | | |
| | | @Override |
| | | public String getOriginalFilename() { return name; } |
| | | |
| | | @Override |
| | | public String getContentType() { |
| | | String lower = name.toLowerCase(); |
| | | return lower.endsWith(".xls") |
| | | ? "application/vnd.ms-excel" |
| | | : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isEmpty() { return content == null || content.length == 0; } |
| | | |
| | | @Override |
| | | public long getSize() { return content == null ? 0 : content.length; } |
| | | |
| | | @Override |
| | | public byte[] getBytes() { return content; } |
| | | |
| | | @Override |
| | | public InputStream getInputStream() { return new ByteArrayInputStream(content); } |
| | | |
| | | @Override |
| | | public void transferTo(File dest) throws IOException { |
| | | try (FileOutputStream out = new FileOutputStream(dest)) { |
| | | out.write(content); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | new String[]{"report_period", "region_name", "freight_m01", "freight_m02", "freight_m03", "freight_m04", |
| | | "freight_m05", "freight_m06", "freight_m07", "freight_m08", "freight_m09", "freight_m10", "freight_m11", |
| | | "freight_m12", "turnover_m01", "turnover_m12"}); |
| | | register("h2031_enterprise_monthly", "å
¬è·¯æ
å®¢ææ¥H203-1表(éè·¯æ
客è¿è¾æåº¦ç产æ
åµ)", |
| | | new String[]{"id", "report_period", "region_code", "enterprise_code", "enterprise_name", "vehicle_total", |
| | | "seat_total", "passenger_total", "turnover_total", "avg_distance_total", "verify_explanation"}, |
| | | new String[]{"主é®", "æ¥è¡¨æ", "æå±å°åºä»£ç ", "ä¼ä¸ä»£ç ", "ä¼ä¸åç§°", "è½¦è¾æ°", "è½½å®¢ä½æ°", |
| | | "客è¿é(ä¸äºº)", "æ
客å¨è½¬é(ä¸äººå
¬é)", "å¹³åè¿è·(å
¬é)", "ä¼ä¸æ ¸å®è§£é"}, |
| | | new String[]{"id", "report_period", "enterprise_name", "vehicle_total", "seat_total", |
| | | "passenger_total", "turnover_total", "avg_distance_total", "verify_explanation"}); |
| | | register("city_bus_monthly", "åå¸å
¬äº¤æåº¦è¿è¥æ
åµ(ä¼ä¸çº§ï¼å«è½¨é/è½®æ¸¡åæ®µ)", |
| | | new String[]{"id", "report_period", "region_code", "city", "enterprise_name", "op_vehicles", |
| | | "passenger_volume", "turnover", "avg_distance", "passenger_chengxiang", "turnover_chengxiang", |
| | | "verify_explanation"}, |
| | | new String[]{"主é®", "æ¥è¡¨æ", "æå±å°åºä»£ç ", "å¸å·", "ä¼ä¸åç§°", "è¿è¥è½¦æ°", "客è¿é(ä¸äººæ¬¡)", |
| | | "æ
客å¨è½¬é(ä¸äººå
¬é)", "å¹³åè¿è·(å
¬é)", "åé
å乡客è¿é", "åé
å乡å¨è½¬é", "ä¼ä¸æ ¸å®è§£é"}, |
| | | new String[]{"id", "report_period", "enterprise_name", "op_vehicles", "passenger_volume", |
| | | "turnover", "avg_distance", "verify_explanation"}); |
| | | register("city_taxi_monthly", "巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥(å¸å·çº§)", |
| | | new String[]{"id", "report_period", "region_code", "city", "trip_total", "passenger_volume", |
| | | "turnover", "op_vehicles", "avg_distance", "verify_explanation"}, |
| | | new String[]{"主é®", "æ¥è¡¨æ", "æå±å°åºä»£ç ", "å¸å·", "è½½å®¢è½¦æ¬¡æ»æ°", "客è¿é(ä¸äººæ¬¡)", |
| | | "æ
客å¨è½¬é(ä¸äººå
¬é)", "è¿è¥è½¦è¾æ°", "å¹³åè¿è·(å
¬é)", "ä¼ä¸æ ¸å®è§£é"}, |
| | | new String[]{"id", "report_period", "city", "trip_total", "passenger_volume", |
| | | "turnover", "op_vehicles", "avg_distance", "verify_explanation"}); |
| | | register("audit_result", "å®¡æ ¸ç»æ", |
| | | new String[]{"id", "rule_id", "report_id", "enterprise_code", "report_period", "actual_value", |
| | | "threshold_value", "deviation", "status", "review_comment", "created_at"}, |
| | |
| | | if (ctx.get("reportId") != null) { |
| | | sb.append("å
³è䏿¥è®°å½ID: ").append(ctx.get("reportId")).append("\n"); |
| | | } |
| | | if (ctx.get("reportType") != null) { |
| | | sb.append("å
³èä¸å¡ç±»å: ").append(ctx.get("reportType")).append("\n"); |
| | | } |
| | | if (ctx.get("tableName") != null) { |
| | | sb.append("å
³èæ°æ®è¡¨: ").append(ctx.get("tableName")).append("ï¼æ¥è¯¢è¯¥ä¼ä¸è§£éå¯¹åºæ°æ®æ¶ä¼å
æ¥æ¤è¡¨ï¼æ id=").append(ctx.get("reportId")).append(" è¿æ»¤ï¼\n"); |
| | | } |
| | | } |
| | | sb.append("\nä½ å¯ä»¥è°ç¨ query_database å·¥å
·æ¥è¯¢æ°æ®åºè·åç宿°æ®ï¼å¦ä¼ä¸è½¦è¾æ°ãå¨ä½ãè´§è¿éãå¨è½¬éã轨迹éç¨ãè§ä¸è§ä¸æåãå»å¹´åæçã\n\n"); |
| | | sb.append("æ°æ®åºè¡¨ç»æå¦ä¸ï¼ååä¸å¾ä½¿ç¨ camelCaseï¼ä¾å¦ enterpriseNameãvehicleTotalãfreightTotalãturnoverTotalï¼filters çé®åæ ·ä½¿ç¨ camelCaseï¼ï¼\n"); |
| | |
| | | |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.reportexport.service.ReportExportService; |
| | | import com.trafficaudit.system.service.OperationLogService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.http.HttpHeaders; |
| | | import org.springframework.http.MediaType; |
| | |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.net.URLEncoder; |
| | | import java.util.List; |
| | | import java.nio.charset.StandardCharsets; |
| | | |
| | | @Slf4j |
| | |
| | | |
| | | @Resource |
| | | private ReportExportService reportService; |
| | | @Resource |
| | | private OperationLogService operationLogService; |
| | | |
| | | /** çæ_è´§è¿éåå¸å·æç».xlsx */ |
| | | @GetMapping("/export/cityDetail") |
| | | public ResponseEntity<byte[]> exportCityDetail(@RequestParam("period") String period) throws Exception { |
| | | return buildResponse(reportService.exportCityDetail(period), "çæ_è´§è¿éåå¸å·æç».xlsx"); |
| | | public ResponseEntity<byte[]> exportCityDetail(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_è´§è¿éåå¸å·æç».xlsx", period); |
| | | return buildResponse(reportService.exportCityDetail(period, mode), "çæ_è´§è¿éåå¸å·æç».xlsx"); |
| | | } |
| | | |
| | | /** çæ_è´§è¿éæå.xlsx */ |
| | | @GetMapping("/export/freightRank") |
| | | public ResponseEntity<byte[]> exportFreightRank(@RequestParam("period") String period) throws Exception { |
| | | return buildResponse(reportService.exportFreightRank(period), "çæ_è´§è¿éæå.xlsx"); |
| | | public ResponseEntity<byte[]> exportFreightRank(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_è´§è¿éæå.xlsx", period); |
| | | return buildResponse(reportService.exportFreightRank(period, mode), "çæ_è´§è¿éæå.xlsx"); |
| | | } |
| | | |
| | | /** çæ_å¨è½¬éæå.xlsx */ |
| | | @GetMapping("/export/turnoverRank") |
| | | public ResponseEntity<byte[]> exportTurnoverRank(@RequestParam("period") String period) throws Exception { |
| | | return buildResponse(reportService.exportTurnoverRank(period), "çæ_å¨è½¬éæå.xlsx"); |
| | | public ResponseEntity<byte[]> exportTurnoverRank(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_å¨è½¬éæå.xlsx", period); |
| | | return buildResponse(reportService.exportTurnoverRank(period, mode), "çæ_å¨è½¬éæå.xlsx"); |
| | | } |
| | | |
| | | /** çæ_å
¬è·¯æ
客åå¸å·æç».xlsxï¼month: æ¥è¡¨æ yyyy-MMï¼year: 年份 yyyyï¼ */ |
| | | @GetMapping("/export/passengerCityDetail") |
| | | public ResponseEntity<byte[]> exportPassengerCityDetail(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_å
¬è·¯æ
客åå¸å·æç».xlsx", period); |
| | | return buildResponse(reportService.exportPassengerCityDetail(period, mode), "çæ_å
¬è·¯æ
客åå¸å·æç».xlsx"); |
| | | } |
| | | |
| | | |
| | | /** çæ_å
¬è·¯æ
客ä¸å£å¾æç».xlsx */ |
| | | @GetMapping("/export/passengerMidDetail") |
| | | public ResponseEntity<byte[]> exportPassengerMidDetail(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_å
¬è·¯æ
客ä¸å£å¾æç».xlsx", period); |
| | | return buildResponse(reportService.exportPassengerMidDetail(period, mode), "çæ_å
¬è·¯æ
客ä¸å£å¾æç».xlsx"); |
| | | } |
| | | |
| | | /** çæ_å
¬è·¯æ
客ä¸å£å¾æå.xlsx */ |
| | | @GetMapping("/export/passengerMidRank") |
| | | public ResponseEntity<byte[]> exportPassengerMidRank(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_å
¬è·¯æ
客ä¸å£å¾æå.xlsx", period); |
| | | return buildResponse(reportService.exportPassengerMidRank(period, mode), "çæ_å
¬è·¯æ
客ä¸å£å¾æå.xlsx"); |
| | | } |
| | | |
| | | /** çæ_å
¬è·¯æ
客ä¸å£å¾åæ.xlsx */ |
| | | @GetMapping("/export/passengerMidAnalysis") |
| | | public ResponseEntity<byte[]> exportPassengerMidAnalysis(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_å
¬è·¯æ
客ä¸å£å¾åæ.xlsx", period); |
| | | return buildResponse(reportService.exportPassengerMidAnalysis(period, mode), "çæ_å
¬è·¯æ
客ä¸å£å¾åæ.xlsx"); |
| | | } |
| | | |
| | | /** çæ_è½è¿æ±æ»è¡¨.xlsxï¼H204ï¼å
¨ç + åå¸å· sheetï¼ */ |
| | | @GetMapping("/export/energySummary") |
| | | public ResponseEntity<byte[]> exportEnergySummary(@RequestParam("period") String period) throws Exception { |
| | | logExport("çæ_è½è¿æ±æ»è¡¨.xlsx", period); |
| | | return buildResponse(reportService.exportEnergySummary(period), "çæ_è½è¿æ±æ»è¡¨.xlsx"); |
| | | } |
| | | /** çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsxï¼åå¸å®¢è¿ç¬¬1æ¥ï¼å
¬äº¤ï¼ */ |
| | | @GetMapping("/export/cityBusDetail") |
| | | public ResponseEntity<byte[]> exportCityBusDetail(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsx", period); |
| | | return buildResponse(reportService.exportCityBusDetail(period, mode), "çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsx"); |
| | | } |
| | | |
| | | /** çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsxï¼åå¸å®¢è¿ç¬¬2æ¥ï¼å·¡æ¸¸åºç§ï¼ */ |
| | | @GetMapping("/export/cityTaxiDetail") |
| | | public ResponseEntity<byte[]> exportCityTaxiDetail(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsx", period); |
| | | return buildResponse(reportService.exportCityTaxiDetail(period, mode), "çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsx"); |
| | | } |
| | | |
| | | /** çæ_2026å¹´å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xlsxï¼æèµæ¨¡å表1ï¼ */ |
| | | @GetMapping("/export/investPlan") |
| | | public ResponseEntity<byte[]> exportInvestPlan(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xls", period); |
| | | return buildResponse(reportService.investPlan(period, mode), "çæ_å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xls"); |
| | | } |
| | | |
| | | /** çæ_"åäºäº"è§åç©æµé¡¹ç®è¿å±æ
åµ.xlsxï¼æèµæ¨¡å表2ï¼ */ |
| | | @GetMapping("/export/investFiveYearLogistics") |
| | | public ResponseEntity<byte[]> exportInvestFiveYearLogistics(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls", period); |
| | | return buildResponse(reportService.investFiveYearLogistics(period, mode), "çæ_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls"); |
| | | } |
| | | |
| | | /** çæ_æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®.xlsï¼æèµæ¨¡å表3ï¼æèµæ»è¡¨+è§ä¸é¡¹ç®è¡¨ä¸¤sheetï¼ */ |
| | | @GetMapping("/export/investBillion") |
| | | public ResponseEntity<byte[]> exportInvestBillion(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®.xls", period); |
| | | return buildResponse(reportService.investBillion(period, mode), "çæ_æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®.xls"); |
| | | } |
| | | |
| | | /** çæ_ï¼ç»æµå¼ºå¿é¡¹ç®ï¼äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨.xlsxï¼æèµæ¨¡å表4ï¼ */ |
| | | @GetMapping("/export/investCounty") |
| | | public ResponseEntity<byte[]> exportInvestCounty(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_ç»æµå¼ºå¿äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨.xls", period); |
| | | return buildResponse(reportService.investCounty(period, mode), "çæ_ç»æµå¼ºå¿äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨.xls"); |
| | | } |
| | | |
| | | /** çæ_轨é轮渡客è¿éåå¸å·æç».xlsxï¼åå¸å®¢è¿ï¼è½¨é/轮渡åèªå
¬äº¤æºæä»¶ï¼ */ |
| | | @GetMapping("/export/cityRailFerryDetail") |
| | | public ResponseEntity<byte[]> exportCityRailFerryDetail(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_轨é轮渡客è¿éåå¸å·æç».xlsx", period); |
| | | return buildResponse(reportService.exportCityRailFerryDetail(period, mode), "çæ_轨é轮渡客è¿éåå¸å·æç».xlsx"); |
| | | } |
| | | |
| | | /** çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsxï¼åå¸åå¸å·_模æ¿åºç¨¿ï¼ */ |
| | | @GetMapping("/export/cityPassengerCitySum") |
| | | public ResponseEntity<byte[]> exportCityPassengerCitySum(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsx", period); |
| | | return buildResponse(reportService.exportCityPassengerCitySum("导å
¥æ¨¡æ¿_åå¸åå¸å·.xlsx", period, mode), "çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsx"); |
| | | } |
| | | |
| | | /** çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsxï¼åå¸å®¢è¿åå¸å·æç»è¡¨_模æ¿åºç¨¿ï¼ */ |
| | | @GetMapping("/export/cityPassengerDetailSum") |
| | | public ResponseEntity<byte[]> exportCityPassengerDetailSum(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx", period); |
| | | return buildResponse(reportService.exportCityPassengerCitySum("导å
¥æ¨¡æ¿_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx", period, mode), "çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx"); |
| | | } |
| | | |
| | | /** çæ_åå¸å®¢è¿æ±æ».xlsxï¼å叿±æ»_模æ¿åºç¨¿ï¼ */ |
| | | @GetMapping("/export/cityPassengerSummary") |
| | | public ResponseEntity<byte[]> exportCityPassengerSummary(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExport("çæ_åå¸å®¢è¿æ±æ».xlsx", period); |
| | | return buildResponse(reportService.exportCityPassengerSummary(period, mode), "çæ_åå¸å®¢è¿æ±æ».xlsx"); |
| | | } |
| | | |
| | | /** çæ_åå¸å®¢è¿æ¥è¡¨ç³»å.zipï¼ä¸é®æå
å
¨é¨ï¼å
¬äº¤/åºç§/轨é轮渡æç» + åå¸å·ç´¯è®¡ + åå¸å·æç»è¡¨ + æ±æ»ï¼ */ |
| | | @GetMapping("/export/cityPassengerSeries") |
| | | public ResponseEntity<byte[]> exportCityPassengerSeries(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExportZip("çæ_åå¸å®¢è¿æ¥è¡¨ç³»å.zip", period); |
| | | String fileName = String.format("çæ_åå¸å®¢è¿æ¥è¡¨ç³»å_%s.zip", period); |
| | | String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); |
| | | return ResponseEntity.ok() |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) |
| | | .contentType(MediaType.parseMediaType("application/zip")) |
| | | .body(reportService.exportCityPassengerSeries(period, mode)); |
| | | } |
| | | |
| | | /** çæ_è´§è¿æ¥è¡¨ç³»å.zipï¼ä¸é®æå
ï¼æç»+æåï¼ */ |
| | | @GetMapping("/export/freightSeries") |
| | | public ResponseEntity<byte[]> exportFreightSeries(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExportZip("çæ_è´§è¿æ¥è¡¨ç³»å.zip", period); |
| | | return zipResponse(reportService.exportFreightSeries(period, mode), String.format("çæ_è´§è¿æ¥è¡¨ç³»å_%s.zip", period)); |
| | | } |
| | | |
| | | /** çæ_å
¬è·¯æ
客æ¥è¡¨ç³»å.zipï¼ä¸é®æå
ï¼åå¸å·æç»+ä¸å£å¾æç»/æå/åæï¼ */ |
| | | @GetMapping("/export/passengerSeries") |
| | | public ResponseEntity<byte[]> exportPassengerSeries(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExportZip("çæ_å
¬è·¯æ
客æ¥è¡¨ç³»å.zip", period); |
| | | return zipResponse(reportService.exportPassengerSeries(period, mode), String.format("çæ_å
¬è·¯æ
客æ¥è¡¨ç³»å_%s.zip", period)); |
| | | } |
| | | |
| | | /** çæ_æèµæ¥è¡¨ç³»å.zipï¼ä¸é®æå
ï¼é¢å®æ/åäºäº/亿å
/ç»æµå¼ºå¿ï¼ */ |
| | | @GetMapping("/export/investSeries") |
| | | public ResponseEntity<byte[]> exportInvestSeries(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception { |
| | | logExportZip("çæ_æèµæ¥è¡¨ç³»å.zip", period); |
| | | return zipResponse(reportService.exportInvestSeries(period, mode), String.format("çæ_æèµæ¥è¡¨ç³»å_%s.zip", period)); |
| | | } |
| | | |
| | | /** çæ_æéæ¥è¡¨.zipï¼types éå·åéï¼å端"çæéä¸"å¤éï¼ */ |
| | | @GetMapping("/export/selected") |
| | | public ResponseEntity<byte[]> exportSelected(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode, |
| | | @RequestParam("types") String types) throws Exception { |
| | | List<String> typeList = new java.util.ArrayList<>(); |
| | | for (String t : types.split(",")) { |
| | | String s = t.trim(); |
| | | if (!s.isEmpty()) typeList.add(s); |
| | | } |
| | | logExportZip("çæ_æéæ¥è¡¨.zip", period); |
| | | return zipResponse(reportService.exportSelectedSeries(period, mode, typeList), |
| | | String.format("çæ_æéæ¥è¡¨_%s.zip", period)); |
| | | } |
| | | |
| | | /** 导åºåæ°æ®å°±ç»ªæ ¡éªï¼è¿åæ¯å¼ æ¥è¡¨ä¾èµçæ°æ®è¡æ°/缺失æç¤ºï¼types éå·åéï¼ */ |
| | | @GetMapping("/ready") |
| | | public Result<java.util.Map<String, Object>> exportReady(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode, |
| | | @RequestParam("types") String types) { |
| | | List<String> typeList = new java.util.ArrayList<>(); |
| | | for (String t : types.split(",")) { |
| | | String s = t.trim(); |
| | | if (!s.isEmpty()) typeList.add(s); |
| | | } |
| | | return Result.ok(reportService.checkExportReady(period, mode, typeList)); |
| | | } |
| | | |
| | | /** æ¥è¡¨æ°æ®é¢è§ï¼è½»éçï¼ï¼å°±ç»ª + æåº¦è¦ç + æ ¸å¿ææ æè¦ + é®é¢æ¸
åï¼types éå·åéï¼ */ |
| | | @GetMapping("/preview") |
| | | public Result<java.util.Map<String, Object>> reportPreview(@RequestParam("period") String period, |
| | | @RequestParam(value = "mode", defaultValue = "month") String mode, |
| | | @RequestParam("types") String types) { |
| | | List<String> typeList = new java.util.ArrayList<>(); |
| | | for (String t : types.split(",")) { |
| | | String s = t.trim(); |
| | | if (!s.isEmpty()) typeList.add(s); |
| | | } |
| | | return Result.ok(reportService.reportPreview(period, mode, typeList)); |
| | | } |
| | | |
| | | private void logExport(String reportName, String period) { |
| | | operationLogService.record("æ¥è¡¨çæ", "å¯¼åºæ¥è¡¨", reportName + "ï¼æ¥è¡¨æï¼" + period + "ï¼"); |
| | | } |
| | | |
| | | private void logExportZip(String zipName, String period) { |
| | | operationLogService.record("æ¥è¡¨çæ", "å¯¼åºæ¥è¡¨ç³»å", zipName + "ï¼æ¥è¡¨æï¼" + period + "ï¼"); |
| | | } |
| | | |
| | | private ResponseEntity<byte[]> zipResponse(byte[] data, String fileName) throws Exception { |
| | | String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); |
| | | return ResponseEntity.ok() |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) |
| | | .contentType(MediaType.parseMediaType("application/zip")) |
| | | .body(data); |
| | | } |
| | | |
| | | private ResponseEntity<byte[]> buildResponse(byte[] data, String fileName) throws Exception { |
| | | String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); |
| | | String type = fileName.toLowerCase().endsWith(".xls") |
| | | ? "application/vnd.ms-excel" |
| | | : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; |
| | | return ResponseEntity.ok() |
| | | .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded) |
| | | .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) |
| | | .contentType(MediaType.parseMediaType(type)) |
| | | .body(data); |
| | | } |
| | | } |
| | |
| | | package com.trafficaudit.reportexport.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; |
| | | import com.trafficaudit.common.util.RegionUtil; |
| | | import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly; |
| | | import com.trafficaudit.dataimport.entity.FreightTurnoverImport; |
| | | import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.InvestmentMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityBusMonthly; |
| | | import com.trafficaudit.dataimport.entity.CityTaxiMonthly; |
| | | import com.trafficaudit.dataimport.entity.InvestmentProject; |
| | | import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly; |
| | | import com.trafficaudit.dataimport.entity.PassengerIndividualMonthly; |
| | | import com.trafficaudit.dataimport.entity.ScaleSplitTransport; |
| | | 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.rulemanage.entity.AuditRule; |
| | | import com.trafficaudit.rulemanage.mapper.AuditRuleMapper; |
| | | import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.FreightTurnoverImportMapper; |
| | | import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.PassengerIndividualMonthlyMapper; |
| | | import com.trafficaudit.dataimport.mapper.ScaleSplitTransportMapper; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellStyle; |
| | | import org.apache.poi.ss.usermodel.CellType; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.FormulaEvaluator; |
| | | import org.apache.poi.xssf.usermodel.XSSFSheet; |
| | | import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| | | import org.apache.poi.xssf.usermodel.XSSFCell; |
| | | import org.apache.poi.hssf.usermodel.HSSFCell; |
| | | import org.apache.poi.hssf.usermodel.HSSFRow; |
| | | import org.apache.poi.hssf.usermodel.HSSFSheet; |
| | | import org.apache.poi.hssf.usermodel.HSSFWorkbook; |
| | | import org.apache.poi.ss.util.CellRangeAddress; |
| | | import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.TreeSet; |
| | | |
| | | /** |
| | | * æ¥è¡¨çææå¡ï¼3 ç±»è¾åºæ¥è¡¨ |
| | |
| | | private ScaleSplitTransportMapper scaleSplitMapper; |
| | | @Resource |
| | | private FreightTurnoverImportMapper freightTurnoverMapper; |
| | | @Resource |
| | | private PassengerEnterpriseMonthlyMapper passengerMapper; |
| | | @Resource |
| | | private PassengerIndividualMonthlyMapper passengerIndividualMapper; |
| | | @Resource |
| | | private EnergyVehicleQuarterlyMapper energyMapper; |
| | | @Resource |
| | | private InvestmentProjectMapper investProjectMapper; |
| | | @Resource |
| | | private InvestmentMonthlyMapper investMonthlyMapper; |
| | | @Resource |
| | | private CityBusMonthlyMapper cityBusMapper; |
| | | @Resource |
| | | private CityTaxiMonthlyMapper cityTaxiMapper; |
| | | @Resource |
| | | private AuditResultMapper auditResultMapper; |
| | | @Resource |
| | | private AuditRunMapper auditRunMapper; |
| | | @Resource |
| | | private AuditRuleMapper auditRuleMapper; |
| | | /** åå¸å®¢è¿æ¨¡æ¿ç®å½ï¼application.yml city-passenger.template-dirï¼ */ |
| | | @Value("${city-passenger.template-dir:docs/åå¸å®¢è¿}") |
| | | private String templateDir; |
| | | /** è½èæ±æ»æ¨¡æ¿ç®å½ï¼application.yml energy.template-dirï¼ */ |
| | | @Value("${energy.template-dir:docs/å
¬è·¯æ
客+è½è}") |
| | | private String energyTemplateDir; |
| | | /** å
¬è·¯æ
客è¾åºæ¨¡æ¿ç®å½ï¼application.yml passenger.template-dirï¼ */ |
| | | @Value("${passenger.template-dir:docs/å
¬è·¯æ
客+è½è/è¾åº}") |
| | | private String passengerTemplateDir; |
| | | |
| | | /** è´§è¿æ¥è¡¨æ¨¡æ¿ç®å½ï¼application.yml freight.template-dirï¼ */ |
| | | @Value("${freight.template-dir:docs/è´§è¿}") |
| | | private String freightTemplateDir; |
| | | |
| | | /** æèµæ¥è¡¨æ¨¡æ¿ç®å½ï¼application.yml investment.template-dirï¼ */ |
| | | @Value("${investment.template-dir:docs/æèµ/模æ¿}") |
| | | private String investTemplateDir; |
| | | |
| | | // ==================== 导åºåæ°æ®å°±ç»ªæ ¡éªï¼P0ï¼é²æ¢å¯¼åºç©ºè¡¨ï¼ ==================== |
| | | |
| | | /** è¿åæ¯å¼ æ¥è¡¨ä¾èµçæ°æ®è¡æ°ä¸ç¼ºå¤±æç¤º */ |
| | | public Map<String, Object> checkExportReady(String period, String mode, List<String> types) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("period", period); |
| | | result.put("mode", mode); |
| | | List<Map<String, Object>> items = new ArrayList<>(); |
| | | for (String t : types) { |
| | | Map<String, Object> it = new LinkedHashMap<>(); |
| | | it.put("key", t); |
| | | List<String> notes = new ArrayList<>(); |
| | | int rows; |
| | | List<String> auditTypes = new ArrayList<>(); |
| | | switch (t) { |
| | | case "cityDetail": |
| | | case "freightRank": |
| | | case "turnoverRank": |
| | | rows = freightReadyRows(period, mode, notes); |
| | | auditTypes.add("H2032"); |
| | | break; |
| | | case "passengerCityDetail": |
| | | case "passengerMidDetail": |
| | | case "passengerMidRank": |
| | | case "passengerMidAnalysis": |
| | | rows = passengerReadyRows(period, mode, notes); |
| | | auditTypes.add("H2031"); |
| | | break; |
| | | case "cityBusDetail": |
| | | case "cityRailFerryDetail": |
| | | rows = addReadyCount(() -> cityBusMapper.selectCount(periodQw(period, mode, CityBusMonthly::getReportPeriod)), "åå¸å
¬äº¤ææ¥", notes); |
| | | auditTypes.add("CITY_BUS"); |
| | | break; |
| | | case "cityTaxiDetail": |
| | | rows = addReadyCount(() -> cityTaxiMapper.selectCount(periodQw(period, mode, CityTaxiMonthly::getReportPeriod)), "巡游åºç§ææ¥", notes); |
| | | auditTypes.add("CITY_TAXI"); |
| | | break; |
| | | case "cityPassengerCitySum": |
| | | case "cityPassengerDetailSum": |
| | | case "cityPassengerSummary": |
| | | rows = addReadyCount(() -> cityBusMapper.selectCount(periodQw(period, mode, CityBusMonthly::getReportPeriod)), "åå¸å
¬äº¤ææ¥", notes) |
| | | + addReadyCount(() -> cityTaxiMapper.selectCount(periodQw(period, mode, CityTaxiMonthly::getReportPeriod)), "巡游åºç§ææ¥", notes); |
| | | auditTypes.add("CITY_BUS"); |
| | | auditTypes.add("CITY_TAXI"); |
| | | break; |
| | | case "investPlan": |
| | | case "investFiveYearLogistics": |
| | | case "investBillion": |
| | | case "investCounty": |
| | | rows = addReadyCount(() -> investMonthlyMapper.selectCount(periodQw(period, mode, InvestmentMonthly::getReportPeriod)), "æèµå¸å·å表/æ±æ»æ°æ®", notes); |
| | | auditTypes.add("INVEST"); |
| | | break; |
| | | case "energySummary": |
| | | rows = addReadyCount(() -> energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod)), "è½è车è¾å£åº¦æ°æ®", notes); |
| | | auditTypes.add("H204"); |
| | | break; |
| | | default: |
| | | rows = 0; |
| | | notes.add("æªç¥æ¥è¡¨ç±»åï¼" + t); |
| | | } |
| | | int auditRows = 0; |
| | | boolean audited = false; |
| | | for (String at : auditTypes) { |
| | | auditRows += auditRowsOf(period, mode, at); |
| | | audited = audited || auditedMarked(period, mode, at); |
| | | } |
| | | it.put("rows", rows); |
| | | it.put("ready", rows > 0); |
| | | it.put("auditRows", auditRows); |
| | | it.put("audited", audited); |
| | | it.put("note", String.join("ï¼", notes)); |
| | | items.add(it); |
| | | } |
| | | result.put("items", items); |
| | | return result; |
| | | } |
| | | |
| | | // ==================== æ¥è¡¨æ°æ®é¢è§ï¼è½»éçï¼ï¼å°±ç»ª + æåº¦è¦ç + æ ¸å¿æè¦ + é®é¢æ¸
å ==================== |
| | | |
| | | /** æ¥è¡¨æ°æ®é¢è§ï¼å¨å¯¼åºåå±ç¤ºæ¯å¼ 表çå°±ç»ªç¶æãæåº¦è¦çãæ ¸å¿ææ æè¦ä¸é®é¢æ¸
å */ |
| | | public Map<String, Object> reportPreview(String period, String mode, List<String> types) { |
| | | Map<String, Object> ready = checkExportReady(period, mode, types); |
| | | @SuppressWarnings("unchecked") |
| | | List<Map<String, Object>> items = (List<Map<String, Object>>) ready.get("items"); |
| | | Map<String, Map<String, Object>> byKey = new LinkedHashMap<>(); |
| | | for (Map<String, Object> it : items) byKey.put((String) it.get("key"), it); |
| | | |
| | | String year = period != null && period.length() >= 4 ? period.substring(0, 4) : ""; |
| | | String yearPrefix = year + "-"; |
| | | int month = monthOf(period, mode); |
| | | int yearNum = 0; |
| | | try { yearNum = Integer.parseInt(year); } catch (Exception ignore) {} |
| | | |
| | | boolean freight = false, passenger = false, cityBus = false, cityTaxi = false, invest = false, energy = false; |
| | | for (String t : types) { |
| | | if (isFreightType(t)) freight = true; |
| | | else if (isPassengerType(t)) passenger = true; |
| | | else if ("cityTaxiDetail".equals(t) || isCityPassengerType(t)) { cityTaxi = true; cityBus = true; } |
| | | else if (isInvestType(t)) invest = true; |
| | | else if ("energySummary".equals(t)) energy = true; |
| | | } |
| | | |
| | | // æåº¦è¦çï¼è¯¥å¹´ 1..month ä¸åªäºæææºæ°æ® |
| | | Set<Integer> freightCover = new TreeSet<>(); |
| | | Set<Integer> passengerCover = new TreeSet<>(); |
| | | Set<Integer> busCover = new TreeSet<>(); |
| | | Set<Integer> taxiCover = new TreeSet<>(); |
| | | Set<Integer> investCover = new TreeSet<>(); |
| | | if (freight) { |
| | | FreightTurnoverImport ftProv = loadFreightTurnover(period).get("æ¹åç"); |
| | | for (int m = 1; m <= month; m++) { |
| | | if (ftProv != null && freightMonth(ftProv, m) != null) freightCover.add(m); |
| | | } |
| | | } |
| | | if (passenger) { |
| | | passengerCover.addAll(monthsWithData(() -> passengerMapper.selectList(new LambdaQueryWrapper<PassengerEnterpriseMonthly>().likeRight(PassengerEnterpriseMonthly::getReportPeriod, yearPrefix)), |
| | | o -> ((PassengerEnterpriseMonthly) o).getReportPeriod(), month)); |
| | | passengerCover.addAll(monthsWithData(() -> passengerIndividualMapper.selectList(new LambdaQueryWrapper<PassengerIndividualMonthly>().likeRight(PassengerIndividualMonthly::getReportPeriod, yearPrefix)), |
| | | o -> ((PassengerIndividualMonthly) o).getReportPeriod(), month)); |
| | | } |
| | | if (cityBus) { |
| | | busCover.addAll(monthsWithData(() -> cityBusMapper.selectList(new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix)), |
| | | o -> ((CityBusMonthly) o).getReportPeriod(), month)); |
| | | } |
| | | if (cityTaxi) { |
| | | taxiCover.addAll(monthsWithData(() -> cityTaxiMapper.selectList(new LambdaQueryWrapper<CityTaxiMonthly>().likeRight(CityTaxiMonthly::getReportPeriod, yearPrefix)), |
| | | o -> ((CityTaxiMonthly) o).getReportPeriod(), month)); |
| | | } |
| | | if (invest) { |
| | | investCover.addAll(monthsWithData(() -> investMonthlyMapper.selectList(new LambdaQueryWrapper<InvestmentMonthly>().likeRight(InvestmentMonthly::getReportPeriod, yearPrefix)), |
| | | o -> ((InvestmentMonthly) o).getReportPeriod(), month)); |
| | | } |
| | | |
| | | for (Map.Entry<String, Map<String, Object>> e : byKey.entrySet()) { |
| | | String t = e.getKey(); |
| | | Map<String, Object> it = e.getValue(); |
| | | List<Map<String, Object>> summary = new ArrayList<>(); |
| | | List<String> problems = new ArrayList<>(); |
| | | List<Integer> missing = new ArrayList<>(); |
| | | List<Integer> coverage = new ArrayList<>(); |
| | | if (Boolean.FALSE.equals(it.get("audited"))) problems.add("该æ¥è¡¨æå°æªå®¡æ ¸éè¿"); |
| | | if (isFreightType(t)) { |
| | | coverage.addAll(freightCover); |
| | | missing = missingMonths(freightCover, month); |
| | | freightPreview(period, month, summary, problems); |
| | | } else if (isPassengerType(t)) { |
| | | coverage.addAll(passengerCover); |
| | | missing = missingMonths(passengerCover, month); |
| | | passengerPreview(yearNum, month, t, summary, problems); |
| | | } else if ("cityTaxiDetail".equals(t)) { |
| | | coverage.addAll(taxiCover); |
| | | missing = missingMonths(taxiCover, month); |
| | | cityPassengerPreview(yearPrefix, month, t, summary, problems); |
| | | } else if (isCityPassengerType(t)) { |
| | | if ("cityBusDetail".equals(t) || "cityRailFerryDetail".equals(t)) { |
| | | // å
¬äº¤/轨é轮渡æç»åªä¾èµå
¬äº¤ææ¥è¡¨ï¼è½¨é/è½®æ¸¡æ°æ®å¨å
¬äº¤è¡¨å
ï¼ |
| | | coverage.addAll(busCover); |
| | | missing = missingMonths(busCover, month); |
| | | } else { |
| | | Set<Integer> cov = new TreeSet<>(); |
| | | cov.addAll(busCover); |
| | | cov.addAll(taxiCover); |
| | | coverage.addAll(cov); |
| | | missing = missingMonths(cov, month); |
| | | } |
| | | cityPassengerPreview(yearPrefix, month, t, summary, problems); |
| | | } else if (isInvestType(t)) { |
| | | coverage.addAll(investCover); |
| | | // æèµä¸ºå½ææ¥è¡¨ï¼æ 1..N 累计å£å¾ï¼ï¼åªæç¤ºå½æç¼ºå¤± |
| | | if (!investCover.contains(month)) missing.add(month); |
| | | investPreview(period, mode, summary, problems); |
| | | } else if ("energySummary".equals(t)) { |
| | | Long ec = energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod)); |
| | | if (ec != null && ec > 0) coverage.add(month); |
| | | energyPreview(period, mode, summary, problems); |
| | | } |
| | | if (!missing.isEmpty()) { |
| | | problems.add("ç¼ºææ°æ®ï¼" + joinMonths(missing) + "ï¼1~" + month + "æç´¯è®¡ééæé½å
¨ï¼"); |
| | | } |
| | | it.put("monthCoverage", coverage); |
| | | it.put("missingMonths", missing); |
| | | it.put("summary", summary); |
| | | it.put("problems", problems); |
| | | } |
| | | return ready; |
| | | } |
| | | |
| | | private boolean isFreightType(String t) { |
| | | return "cityDetail".equals(t) || "freightRank".equals(t) || "turnoverRank".equals(t); |
| | | } |
| | | |
| | | private boolean isPassengerType(String t) { |
| | | return "passengerCityDetail".equals(t) || "passengerMidDetail".equals(t) || "passengerMidRank".equals(t) || "passengerMidAnalysis".equals(t); |
| | | } |
| | | |
| | | private boolean isCityPassengerType(String t) { |
| | | return "cityBusDetail".equals(t) || "cityRailFerryDetail".equals(t) || "cityPassengerCitySum".equals(t) |
| | | || "cityPassengerDetailSum".equals(t) || "cityPassengerSummary".equals(t); |
| | | } |
| | | |
| | | private boolean isInvestType(String t) { |
| | | return "investPlan".equals(t) || "investFiveYearLogistics".equals(t) || "investBillion".equals(t) || "investCounty".equals(t); |
| | | } |
| | | |
| | | private boolean isEnergyType(String t) { |
| | | return "energySummary".equals(t); |
| | | } |
| | | |
| | | /** æ report_period like 'yyyy-' ç»è®¡ 1..limit æä¸ææ°æ®çæä»½ï¼ååºï¼ */ |
| | | private Set<Integer> monthsWithData(java.util.function.Supplier<java.util.List<?>> rows, java.util.function.Function<Object, String> periodOf, int limit) { |
| | | Set<Integer> set = new TreeSet<>(); |
| | | for (Object o : rows.get()) { |
| | | String rp = periodOf.apply(o); |
| | | if (rp == null) continue; |
| | | String[] parts = rp.split("-"); |
| | | if (parts.length < 2) continue; |
| | | try { |
| | | int m = Integer.parseInt(parts[1]); |
| | | if (m >= 1 && m <= limit) set.add(m); |
| | | } catch (Exception ignore) {} |
| | | } |
| | | return set; |
| | | } |
| | | |
| | | /** 1..limit ä¸ç¼ºå¤±çæä»½ */ |
| | | private List<Integer> missingMonths(Set<Integer> coverage, int limit) { |
| | | List<Integer> missing = new ArrayList<>(); |
| | | for (int m = 1; m <= limit; m++) if (!coverage.contains(m)) missing.add(m); |
| | | return missing; |
| | | } |
| | | |
| | | private String joinMonths(List<Integer> months) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int m : months) { |
| | | if (sb.length() > 0) sb.append("ã"); |
| | | sb.append(m).append("æ"); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | private void addSum(List<Map<String, Object>> summary, String label, Double value, String unit) { |
| | | Map<String, Object> m = new LinkedHashMap<>(); |
| | | m.put("label", label); |
| | | m.put("value", value); |
| | | m.put("unit", unit); |
| | | summary.add(m); |
| | | } |
| | | |
| | | /** è´§è¿æè¦ï¼å
¨ç累计货è¿éï¼å计/è§ä¸/è§ä¸ï¼+ å¨è½¬éï¼æå表å
¨ç累计è¡ï¼ */ |
| | | private void freightPreview(String period, int month, List<Map<String, Object>> summary, List<String> problems) { |
| | | Map<String, FreightTurnoverImport> ft = loadFreightTurnover(period); |
| | | Map<String, Double> h2032Cum = loadH2032FreightCumulative(period); |
| | | ScaleSplitTransport provCum = getProvinceCumulative(period); |
| | | Double total = freightCum(ft.get("æ¹åç"), month); |
| | | Double totalWan = total == null ? null : round(total / 10000.0, 4); |
| | | double above = 0.0; |
| | | for (Double v : h2032Cum.values()) if (v != null) above += v; |
| | | double aboveWan = round(above / 10000.0, 4); |
| | | Double belowWan = totalWan == null ? null : round(totalWan - aboveWan, 4); |
| | | addSum(summary, "å
¨ç累计货è¿éï¼å计ï¼", totalWan, "ä¸å¨"); |
| | | addSum(summary, "å
¶ä¸ï¼è§ä¸", aboveWan, "ä¸å¨"); |
| | | addSum(summary, "å
¶ä¸ï¼è§ä¸", belowWan, "ä¸å¨"); |
| | | if (provCum != null) { |
| | | addSum(summary, "å
¨ç累计å¨è½¬éï¼å计ï¼", provCum.getTotalTurnover(), "ä¸å¨å
Ž"); |
| | | addSum(summary, "å
¶ä¸ï¼è§ä¸", provCum.getAboveScaleTurnover(), "ä¸å¨å
Ž"); |
| | | addSum(summary, "å
¶ä¸ï¼è§ä¸", provCum.getBelowScaleTurnover(), "ä¸å¨å
Ž"); |
| | | } else { |
| | | problems.add("æå表æ å
¨ç累计è¡ï¼å¨è½¬é/è§ä¸è§ä¸ç¼ºï¼"); |
| | | } |
| | | if (totalWan == null) problems.add("模æ¿_è´§è¿éå¨è½¬éæ å
¨ç累计ï¼ç´¯è®¡è´§è¿é为空"); |
| | | } |
| | | |
| | | /** æ
客æè¦ï¼ä¼ä¸+个ä½ï¼åå¸å·ï¼æä¸å£å¾ä¼ä¸ï¼ä¸å£å¾ 3 è¡¨ï¼ */ |
| | | private void passengerPreview(int yearNum, int month, String type, List<Map<String, Object>> summary, List<String> problems) { |
| | | Map<Integer, Map<String, PassengerAgg>> data = loadPassengerAggMap(); |
| | | Map<Integer, Map<String, double[]>> indi = loadPassengerIndividualMap(); |
| | | double entPax = 0.0, entTurn = 0.0, indiPax = 0.0, indiTurn = 0.0; |
| | | boolean anyEnt = false, anyIndi = false; |
| | | for (int m = 1; m <= month; m++) { |
| | | PassengerAgg agg = aggOf(data, yearNum, m, "æ¹åç"); |
| | | if (agg != null) { |
| | | entPax += agg.passengerTotal / 10000.0; |
| | | entTurn += agg.turnoverTotal / 10000.0; |
| | | anyEnt = true; |
| | | } |
| | | Map<String, double[]> mm = indi.get(yearNum * 100 + m); |
| | | if (mm != null) { |
| | | double[] pv = mm.get("æ¹åç"); |
| | | if (pv != null) { |
| | | indiPax += pv[0]; |
| | | indiTurn += pv[1]; |
| | | anyIndi = true; |
| | | } |
| | | } |
| | | } |
| | | if ("passengerCityDetail".equals(type)) { |
| | | addSum(summary, "ä¼ä¸å®¢è¿é累计", round(entPax, 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "个ä½å®¢è¿é累计", round(indiPax, 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "å计客è¿é累计", round(entPax + indiPax, 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "å计å¨è½¬é累计", round(entTurn + indiTurn, 4), "ä¸äººå
Ž"); |
| | | if (!anyEnt && !anyIndi) problems.add("H203-1 æ
å®¢ææ¥ä¸ä¸ªä½æ°æ®åæ "); |
| | | } else { |
| | | addSum(summary, "ä¸å£å¾å®¢è¿é累计", round(entPax, 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "ä¸å£å¾å¨è½¬é累计", round(entTurn, 4), "ä¸äººå
Ž"); |
| | | if (!anyEnt) problems.add("H203-1 æ
å®¢ææ¥æ ä¼ä¸æ°æ®"); |
| | | } |
| | | } |
| | | |
| | | /** åå¸å®¢è¿æè¦ï¼å
¬äº¤/åºç§/轨é/轮渡å
¨çç´¯è®¡ï¼ææ 0..7ï¼ */ |
| | | private void cityPassengerPreview(String yearPrefix, int month, String type, List<Map<String, Object>> summary, List<String> problems) { |
| | | double[] prov = loadCityPassengerCumulative(yearPrefix, month).getOrDefault("å
¨ç", new double[8]); |
| | | boolean allZero = true; |
| | | for (double v : prov) if (v != 0.0) allZero = false; |
| | | switch (type) { |
| | | case "cityTaxiDetail": |
| | | addSum(summary, "åºç§å®¢è¿é累计", round(prov[2], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "åºç§å¨è½¬é累计", round(prov[3], 4), "ä¸äººå
Ž"); |
| | | break; |
| | | case "cityRailFerryDetail": |
| | | addSum(summary, "轨é客è¿é累计", round(prov[4], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "轨éå¨è½¬é累计", round(prov[5], 4), "ä¸äººå
Ž"); |
| | | addSum(summary, "轮渡客è¿é累计", round(prov[6], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "轮渡å¨è½¬é累计", round(prov[7], 4), "ä¸äººå
Ž"); |
| | | break; |
| | | case "cityBusDetail": |
| | | addSum(summary, "å
¬äº¤å®¢è¿é累计", round(prov[0], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "å
¬äº¤å¨è½¬é累计", round(prov[1], 4), "ä¸äººå
Ž"); |
| | | addSum(summary, "轨é客è¿é累计", round(prov[4], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "轨éå¨è½¬é累计", round(prov[5], 4), "ä¸äººå
Ž"); |
| | | addSum(summary, "轮渡客è¿é累计", round(prov[6], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "轮渡å¨è½¬é累计", round(prov[7], 4), "ä¸äººå
Ž"); |
| | | break; |
| | | default: |
| | | addSum(summary, "å
¬äº¤å®¢è¿é累计", round(prov[0], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "å
¬äº¤å¨è½¬é累计", round(prov[1], 4), "ä¸äººå
Ž"); |
| | | addSum(summary, "åºç§å®¢è¿é累计", round(prov[2], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "åºç§å¨è½¬é累计", round(prov[3], 4), "ä¸äººå
Ž"); |
| | | addSum(summary, "轨é客è¿é累计", round(prov[4], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "轨éå¨è½¬é累计", round(prov[5], 4), "ä¸äººå
Ž"); |
| | | addSum(summary, "轮渡客è¿é累计", round(prov[6], 4), "ä¸äººæ¬¡"); |
| | | addSum(summary, "轮渡å¨è½¬é累计", round(prov[7], 4), "ä¸äººå
Ž"); |
| | | } |
| | | if (allZero) problems.add("å
¨ç累计å¼å
¨ä¸º 0ï¼å
¬äº¤/åºç§ææ¥å¯è½æªå¯¼å
¥ï¼"); |
| | | } |
| | | |
| | | /** æèµæè¦ï¼é¡¹ç®æ° + æ¬æå®æ/èªå¹´å/èªå¼å§ç´¯è®¡ */ |
| | | private void investPreview(String period, String mode, List<Map<String, Object>> summary, List<String> problems) { |
| | | List<InvestmentMonthly> list = investMonthlyMapper.selectList(periodQw(period, mode, InvestmentMonthly::getReportPeriod)); |
| | | Set<Long> proj = new HashSet<>(); |
| | | double monthDone = 0.0, yearCum = 0.0, startCum = 0.0; |
| | | for (InvestmentMonthly m : list) { |
| | | if (m.getProjectId() != null) proj.add(m.getProjectId()); |
| | | monthDone += nz(m.getMonthDone()); |
| | | yearCum += nz(m.getYearCum()); |
| | | startCum += nz(m.getStartCum()); |
| | | } |
| | | addSum(summary, "ææåº¦è®°å½é¡¹ç®æ°", (double) proj.size(), "个"); |
| | | addSum(summary, "æ¬æå®ææèµå计", round(monthDone, 2), "ä¸å
"); |
| | | addSum(summary, "èªå¹´åç´¯è®¡å®æ", round(yearCum, 2), "ä¸å
"); |
| | | addSum(summary, "èªå¼å§ç´¯è®¡å®æ", round(startCum, 2), "ä¸å
"); |
| | | if (list.isEmpty()) problems.add("æ æèµæåº¦æ°æ®"); |
| | | } |
| | | |
| | | /** è½èæè¦ï¼è½¦è¾è®°å½æ° + çæ²¹æ¶èå计ï¼å£åº¦è¡¨ï¼ææéå£åº¦æ«æï¼ */ |
| | | private void energyPreview(String period, String mode, List<Map<String, Object>> summary, List<String> problems) { |
| | | Long cnt = energyMapper.selectCount(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod)); |
| | | int rows = cnt == null ? 0 : cnt.intValue(); |
| | | double fuel = 0.0; |
| | | if (rows > 0) { |
| | | for (EnergyVehicleQuarterly e : energyMapper.selectList(periodQw(period, mode, EnergyVehicleQuarterly::getReportPeriod))) { |
| | | fuel += nz(e.getFuelConsumption()); |
| | | } |
| | | } |
| | | addSum(summary, "车è¾è®°å½æ°", (double) rows, "æ¡"); |
| | | addSum(summary, "çæ²¹æ¶èå计", round(fuel, 2), "â"); |
| | | if (rows == 0) problems.add("该å£åº¦æ è½èè½¦è¾æ°æ®"); |
| | | } |
| | | |
| | | private int freightReadyRows(String period, String mode, List<String> notes) { |
| | | return addReadyCount(() -> freightTurnoverMapper.selectCount(periodQw(period, mode, FreightTurnoverImport::getReportPeriod)), "模æ¿_è´§è¿éå¨è½¬é", notes) |
| | | + addReadyCount(() -> scaleSplitMapper.selectCount(periodQw(period, mode, ScaleSplitTransport::getReportPeriod)), "è§ä¸è§ä¸æå表", notes) |
| | | + addReadyCount(() -> h2032Mapper.selectCount(periodQw(period, mode, H2032EnterpriseMonthly::getReportPeriod)), "H203-2 è´§è¿ææ¥", notes); |
| | | } |
| | | |
| | | private int passengerReadyRows(String period, String mode, List<String> notes) { |
| | | return addReadyCount(() -> passengerMapper.selectCount(periodQw(period, mode, PassengerEnterpriseMonthly::getReportPeriod)), "H203-1 æ
å®¢ææ¥", notes) |
| | | + addReadyCount(() -> passengerIndividualMapper.selectCount(periodQw(period, mode, PassengerIndividualMonthly::getReportPeriod)), "个ä½å®¢è¿é/å¨è½¬é", notes); |
| | | } |
| | | |
| | | private <T> LambdaQueryWrapper<T> periodQw(String period, String mode, SFunction<T, ?> periodGetter) { |
| | | LambdaQueryWrapper<T> qw = new LambdaQueryWrapper<>(); |
| | | if ("year".equals(mode)) { |
| | | qw.likeRight(periodGetter, period); |
| | | } else { |
| | | qw.eq(periodGetter, period); |
| | | } |
| | | return qw; |
| | | } |
| | | |
| | | private int addReadyCount(java.util.function.Supplier<Long> counter, String label, List<String> notes) { |
| | | Long cnt = counter.get(); |
| | | if (cnt == null || cnt <= 0) notes.add("ç¼ºæ°æ®ï¼" + label); |
| | | return cnt == null ? 0 : cnt.intValue(); |
| | | } |
| | | |
| | | /** 该æ¥è¡¨æè¯¥å®¡æ ¸ç±»å已产ççå®¡æ ¸è®°å½æ°ï¼å
å®¡æ ¸ååºè¡¨çåç½®æ£æ¥ï¼ */ |
| | | private int auditRowsOf(String period, String mode, String ruleType) { |
| | | List<AuditRule> rules = auditRuleMapper.selectList(new LambdaQueryWrapper<AuditRule>() |
| | | .eq(AuditRule::getReportType, ruleType) |
| | | .eq(AuditRule::getIsEnabled, 1)); |
| | | if (rules.isEmpty()) return 0; |
| | | List<Long> ruleIds = new ArrayList<>(); |
| | | for (AuditRule r : rules) ruleIds.add(r.getId()); |
| | | LambdaQueryWrapper<AuditResult> qw = new LambdaQueryWrapper<>(); |
| | | if ("year".equals(mode)) qw.likeRight(AuditResult::getReportPeriod, period); |
| | | else qw.eq(AuditResult::getReportPeriod, period); |
| | | Long cnt = auditResultMapper.selectCount(qw.in(AuditResult::getRuleId, ruleIds)); |
| | | return cnt == null ? 0 : cnt.intValue(); |
| | | } |
| | | |
| | | /** 该æ¥è¡¨æè¯¥å®¡æ ¸ç±»åæ¯å¦å·²æãå®¡æ ¸éè¿æ è®°ãï¼audit_run 表ï¼çº¿ä¸å®¡æ ¸éè¿/ç³»ç»å®¡æ ¸æ§è¡ååå
¥ï¼å¯¼åºåä¸åæç¤ºæªå®¡æ ¸ï¼ */ |
| | | private boolean auditedMarked(String period, String mode, String ruleType) { |
| | | LambdaQueryWrapper<AuditRun> qw = new LambdaQueryWrapper<AuditRun>() |
| | | .eq(AuditRun::getReportType, ruleType); |
| | | if ("year".equals(mode)) qw.likeRight(AuditRun::getReportPeriod, period); |
| | | else qw.eq(AuditRun::getReportPeriod, period); |
| | | Long cnt = auditRunMapper.selectCount(qw); |
| | | return cnt != null && cnt > 0; |
| | | } |
| | | |
| | | // ==================== 1. çæ_è´§è¿éåå¸å·æç».xlsx ==================== |
| | | |
| | | public byte[] exportCityDetail(String period) throws Exception { |
| | | int currentMonth = parseMonth(period); |
| | | String year = period.substring(0, 4); |
| | | public byte[] exportCityDetail(String period, String mode) throws Exception { |
| | | int currentMonth = monthOf(period, mode); |
| | | int fillMonths = currentMonth; // 1..N æï¼N>6 æ¶æ¨¡æ¿èªå¨å峿©åå° 12 æï¼ |
| | | boolean extended = fillMonths > 6; |
| | | Map<String, Map<Integer, ScaleSplitTransport>> monthData = loadMonthData(period); |
| | | Map<Integer, ScaleSplitTransport> provinceMonthMap = loadProvinceMonthMap(period); |
| | | ScaleSplitTransport provinceCum = getProvinceCumulative(period); |
| | |
| | | Map<String, Double> h2032FreightCum = loadH2032FreightCumulative(period); |
| | | Map<String, FreightTurnoverImport> ftMap = loadFreightTurnover(period); |
| | | |
| | | XSSFWorkbook wb = new XSSFWorkbook(); |
| | | Sheet sheet = wb.createSheet("è´§è¿éåå¸å·æç»"); |
| | | |
| | | Row r1 = sheet.createRow(0); |
| | | r1.createCell(0).setCellValue("æ¥è¡¨æï¼" + year + "å¹´" + currentMonth + "æ"); |
| | | sheet.createRow(1); |
| | | Row header = sheet.createRow(2); |
| | | header.createCell(0).setCellValue("å°åº åç§°"); |
| | | header.createCell(1).setCellValue("ææ "); |
| | | CellStyle dateStyle = wb.createCellStyle(); |
| | | dateStyle.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat("yyyy-mm-dd")); |
| | | for (int m = 1; m <= currentMonth; m++) { |
| | | Cell c = header.createCell(2 + (m - 1) * 2); |
| | | c.setCellValue(java.sql.Date.valueOf(year + "-" + pad(m) + "-01")); |
| | | c.setCellStyle(dateStyle); |
| | | header.createCell(3 + (m - 1) * 2).setCellValue(m + "æä¸å»å¹´åæ¯"); |
| | | } |
| | | int cumCol = 2 + currentMonth * 2; |
| | | header.createCell(cumCol).setCellValue("累计å¨è½¬é"); |
| | | header.createCell(cumCol + 1).setCellValue("忝"); |
| | | sheet.createRow(3); |
| | | |
| | | int rowIdx = 4; |
| | | // 以 docs/è´§è¿/çæ_è´§è¿éåå¸å·æç».xlsx 为åºç¨¿ï¼ä¿çæ é¢/表头/åå¹¶/å宽/æ ·å¼ï¼ä»
æ¿æ¢æ°æ®åº |
| | | File template = resolveFreightTemplate("çæ_è´§è¿éåå¸å·æç».xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | if (extended) extendMonthlyColumns(sheet, 2, Integer.parseInt(period.substring(0, 4)), 4, sheet.getLastRowNum()); |
| | | String[][] provinceMetrics = { |
| | | {"è´§è¿é ï¼ä¸å¨ï¼", "freight", "total"}, |
| | | {"è´§ç©å¨è½¬é ï¼ä¸å¨å
¬éï¼", "turnover", "total"}, |
| | |
| | | {"å
¶ä¸è§ä¸è´§ç©å¨è½¬é (ä¸å¨å
Ž)", "turnover", "below"} |
| | | }; |
| | | FreightTurnoverImport ftProvince = ftMap.get("æ¹åç"); |
| | | int rowIdx = 4; // 0-basedï¼ç¬¬5è¡èµ·ä¸º å
¨ç 6 ææ |
| | | for (String[] metric : provinceMetrics) { |
| | | Row row = sheet.createRow(rowIdx++); |
| | | row.createCell(0).setCellValue("å
¨ç"); |
| | | row.createCell(1).setCellValue(metric[0]); |
| | | for (int m = 1; m <= currentMonth; m++) { |
| | | setNumeric(row, 2 + (m - 1) * 2, getProvinceMonthValue(provinceMonthMap, ftProvince, m, metric[1], metric[2], h2032Freight)); |
| | | setNumeric(row, 3 + (m - 1) * 2, getProvinceMonthYoy(provinceMonthMap, ftProvince, m, metric[1], metric[2])); |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | fillFreightDetailRow(row, fillMonths, extended ? 26 : 14, |
| | | m -> getProvinceMonthValue(provinceMonthMap, ftProvince, m, metric[1], metric[2], h2032Freight), |
| | | m -> getProvinceMonthYoy(provinceMonthMap, ftProvince, m, metric[1], metric[2]), |
| | | getProvinceCumValue(provinceCum, ftProvince, metric[1], metric[2], h2032FreightCum, currentMonth), |
| | | getProvinceCumYoy(provinceCum, ftProvince, metric[1], metric[2], currentMonth)); |
| | | rowIdx++; |
| | | } |
| | | setNumeric(row, cumCol, getProvinceCumValue(provinceCum, ftProvince, metric[1], metric[2], h2032FreightCum, currentMonth)); |
| | | setNumeric(row, cumCol + 1, getProvinceCumYoy(provinceCum, ftProvince, metric[1], metric[2], currentMonth)); |
| | | } |
| | | |
| | | for (String city : RegionUtil.cityList()) { |
| | | String[][] metrics = { |
| | | {"è§ä¸+è§ä¸è´§è¿é(ä¸å¨)", "freight", "total"}, |
| | |
| | | FreightTurnoverImport ftCity = ftMap.get(city); |
| | | ScaleSplitTransport cum = cumMap.get(city); |
| | | for (String[] metric : metrics) { |
| | | Row row = sheet.createRow(rowIdx++); |
| | | row.createCell(0).setCellValue(RegionUtil.shortName(city)); |
| | | row.createCell(1).setCellValue(metric[0]); |
| | | for (int m = 1; m <= currentMonth; m++) { |
| | | setNumeric(row, 2 + (m - 1) * 2, getCityMonthValue(monthData, ftCity, city, m, metric[1], metric[2], h2032Freight)); |
| | | setNumeric(row, 3 + (m - 1) * 2, getCityMonthYoy(monthData, ftCity, city, m, metric[1], metric[2])); |
| | | } |
| | | setNumeric(row, cumCol, getCityCumValue(cum, ftCity, metric[1], metric[2], h2032FreightCum, city, currentMonth)); |
| | | setNumeric(row, cumCol + 1, getCityCumYoy(cum, ftCity, metric[1], metric[2], currentMonth)); |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | fillFreightDetailRow(row, fillMonths, extended ? 26 : 14, |
| | | m -> getCityMonthValue(monthData, ftCity, city, m, metric[1], metric[2], h2032Freight), |
| | | m -> getCityMonthYoy(monthData, ftCity, city, m, metric[1], metric[2]), |
| | | getCityCumValue(cum, ftCity, metric[1], metric[2], h2032FreightCum, city, currentMonth), |
| | | getCityCumYoy(cum, ftCity, metric[1], metric[2], currentMonth)); |
| | | rowIdx++; |
| | | } |
| | | } |
| | | |
| | | autoWidth(sheet, cumCol + 2); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | // ==================== 2. çæ_è´§è¿éæå.xlsx ==================== |
| | | |
| | | public byte[] exportFreightRank(String period) throws Exception { |
| | | public byte[] exportFreightRank(String period, String mode) throws Exception { |
| | | String year = period.substring(0, 4); |
| | | int month = parseMonth(period); |
| | | int month = monthOf(period, mode); |
| | | Map<String, FreightTurnoverImport> ftMap = loadFreightTurnover(period); |
| | | Map<String, Double> h2032FreightCum = loadH2032FreightCumulative(period); |
| | | Map<String, Double> h2032FreightYoy = computeH2032Yoy(period); |
| | | Map<String, Double> h2032FreightCumLast = loadH2032FreightCumulative( |
| | | (Integer.parseInt(period.substring(0, 4)) - 1) + period.substring(4)); |
| | | |
| | | Map<String, Double> above = new HashMap<>(); |
| | | Map<String, Double> below = new HashMap<>(); |
| | |
| | | Double provTotal = freightCum(ftMap.get("æ¹åç"), month); |
| | | Double provBelow = provTotal == null ? null : round(provTotal - provAboveWan, 4); |
| | | Double provTotalYoy = freightCumYoy(ftMap.get("æ¹åç"), month); |
| | | double provAboveLast = 0.0; |
| | | for (Double v : h2032FreightCumLast.values()) { |
| | | if (v != null) provAboveLast += v; |
| | | } |
| | | Double provAboveYoy = provAboveLast == 0.0 ? null : round((provAbove - provAboveLast) / provAboveLast, 4); |
| | | Double provTotalLast = lastFreightCum(ftMap.get("æ¹åç"), month); |
| | | Double provBelowLast = provTotalLast == null ? null : round(provTotalLast - provAboveLast / 10000.0, 4); |
| | | Double provBelowYoy = provBelowLast == null || provBelowLast == 0.0 ? null |
| | | : round((provBelow - provBelowLast) / provBelowLast, 4); |
| | | |
| | | for (String city : RegionUtil.cityList()) { |
| | | Double a = h2032FreightCum.get(city); |
| | |
| | | total.put(city, t); |
| | | below.put(city, t == null ? null : round(t - aWan, 4)); |
| | | aboveYoy.put(city, h2032FreightYoy.get(city)); |
| | | belowYoy.put(city, null); |
| | | // è§ä¸åæ¯ = (è§ä¸ä»å¹´ - è§ä¸å»å¹´) / è§ä¸å»å¹´ï¼è§ä¸å»å¹´ = 模æ¿å»å¹´å计 - H2032å»å¹´è§ä¸ |
| | | Double tLast = lastFreightCum(ftMap.get(city), month); |
| | | Double aLast = h2032FreightCumLast.get(city); |
| | | double aLastWan = aLast == null ? 0.0 : round(aLast / 10000.0, 4); |
| | | Double belowLast = tLast == null ? null : round(tLast - aLastWan, 4); |
| | | Double b = below.get(city); |
| | | belowYoy.put(city, belowLast == null || belowLast == 0.0 || b == null |
| | | ? null : round((b - belowLast) / belowLast, 4)); |
| | | totalYoy.put(city, freightCumYoy(ftMap.get(city), month)); |
| | | } |
| | | |
| | | XSSFWorkbook wb = new XSSFWorkbook(); |
| | | Sheet sheet = wb.createSheet("è´§è¿éæå"); |
| | | |
| | | Row title = sheet.createRow(0); |
| | | title.createCell(0).setCellValue(year + "å¹´1-" + month + "æå
¨çåå¸å·ç´¯è®¡å®æå
¬è·¯è´§è¿éæ
åµ"); |
| | | |
| | | int rowIdx = 1; |
| | | rowIdx = writeRankHead(sheet, rowIdx, "è´§è¿é", "å¨"); |
| | | rowIdx = writeFreightRankRows(sheet, rowIdx, null, above, below, total, aboveYoy, belowYoy, totalYoy, |
| | | provAboveWan, provBelow, provTotal, null, null, provTotalYoy, true); |
| | | for (String city : RegionUtil.cityList()) { |
| | | rowIdx = writeFreightRankRows(sheet, rowIdx, city, above, below, total, aboveYoy, belowYoy, totalYoy, |
| | | provAboveWan, provBelow, provTotal, null, null, provTotalYoy, false); |
| | | // 以 docs/è´§è¿/çæ_è´§è¿éæå.xlsx 为åºç¨¿ï¼ä¿ç表头/åå¹¶/å宽/æ ·å¼ä¸ Q/R å æ¯å
¬å¼ï¼ä»
æ¿æ¢æ°æ® |
| | | File template = resolveFreightTemplate("çæ_è´§è¿éæå.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | sheet.getRow(0).getCell(0).setCellValue(year + "å¹´" + cumRange(month) + "å
¨çåå¸å·ç´¯è®¡å®æå
¬è·¯è´§è¿éæ
åµ"); |
| | | sheet.getRow(22).getCell(0).setCellValue(year + "å¹´" + month + "æå
¨çåå¸å·ç´¯è®¡å®æå
¬è·¯è´§è¿éæ
åµ"); |
| | | fillFreightRankBlock(sheet, 3, above, below, total, aboveYoy, belowYoy, totalYoy, |
| | | provAboveWan, provBelow, provTotal, provAboveYoy, provBelowYoy, provTotalYoy); |
| | | fillFreightRankBlock(sheet, 25, above, below, total, aboveYoy, belowYoy, totalYoy, |
| | | provAboveWan, provBelow, provTotal, provAboveYoy, provBelowYoy, provTotalYoy); |
| | | try { |
| | | wb.getCreationHelper().createFormulaEvaluator().evaluateAll(); |
| | | } catch (Exception e) { |
| | | log.warn("è´§è¿éæå模æ¿å
¬å¼æ±å¼å¤±è´¥: {}", e.getMessage()); |
| | | } |
| | | |
| | | // æ¬æåºåï¼å½ææ é¢ + ç´¯è®¡æ°æ®ï¼ä¸æ ·ä¾ç»æä¸è´ï¼ |
| | | sheet.createRow(rowIdx++); |
| | | Row title2 = sheet.createRow(rowIdx++); |
| | | title2.createCell(0).setCellValue(year + "å¹´" + month + "æå
¨çåå¸å·ç´¯è®¡å®æ" + "å
¬è·¯è´§è¿éæ
åµ"); |
| | | rowIdx = writeRankHead(sheet, rowIdx, "è´§è¿é", "å¨"); |
| | | rowIdx = writeFreightRankRows(sheet, rowIdx, null, above, below, total, aboveYoy, belowYoy, totalYoy, |
| | | provAboveWan, provBelow, provTotal, null, null, provTotalYoy, true); |
| | | for (String city : RegionUtil.cityList()) { |
| | | rowIdx = writeFreightRankRows(sheet, rowIdx, city, above, below, total, aboveYoy, belowYoy, totalYoy, |
| | | provAboveWan, provBelow, provTotal, null, null, provTotalYoy, false); |
| | | } |
| | | |
| | | // æ«å°¾ç©ºè¡ï¼ä¸æ ·ä¾ç»æå¯¹é½ï¼ |
| | | for (int i = 0; i < 4; i++) sheet.createRow(rowIdx++); |
| | | |
| | | autoWidth(sheet, 18); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | // ==================== 3. çæ_å¨è½¬éæå.xlsx ==================== |
| | | |
| | | public byte[] exportTurnoverRank(String period) throws Exception { |
| | | public byte[] exportTurnoverRank(String period, String mode) throws Exception { |
| | | String year = period.substring(0, 4); |
| | | int month = parseMonth(period); |
| | | int month = monthOf(period, mode); |
| | | Map<String, ScaleSplitTransport> cumMap = loadCumulativeMap(period); |
| | | ScaleSplitTransport province = getProvinceCumulative(period); |
| | | |
| | | XSSFWorkbook wb = new XSSFWorkbook(); |
| | | Sheet sheet = wb.createSheet("å¨è½¬éæå"); |
| | | |
| | | Row title = sheet.createRow(0); |
| | | title.createCell(0).setCellValue(year + "å¹´1-" + month + "æå
¨çåå¸å·ç´¯è®¡å®æå
¬è·¯è´§ç©è¿è¾å¨è½¬éæ
åµ"); |
| | | |
| | | int rowIdx = 1; |
| | | rowIdx = writeRankHead(sheet, rowIdx, "å¨è½¬é", "å¨å
Ž"); |
| | | rowIdx = writeTurnoverRankRows(sheet, rowIdx, null, cumMap, province, true); |
| | | for (String city : RegionUtil.cityList()) { |
| | | rowIdx = writeTurnoverRankRows(sheet, rowIdx, city, cumMap, province, false); |
| | | } |
| | | |
| | | // æ¬æåºåï¼å½ææ é¢ + ç´¯è®¡æ°æ®ï¼ä¸æ ·ä¾ç»æä¸è´ï¼ |
| | | sheet.createRow(rowIdx++); |
| | | Row title2 = sheet.createRow(rowIdx++); |
| | | title2.createCell(0).setCellValue(year + "å¹´" + month + "æå
¨çåå¸å·ç´¯è®¡å®æ" + "å
¬è·¯è´§ç©è¿è¾å¨è½¬éæ
åµ"); |
| | | rowIdx = writeRankHead(sheet, rowIdx, "å¨è½¬é", "å¨å
Ž"); |
| | | rowIdx = writeTurnoverRankRows(sheet, rowIdx, null, cumMap, province, true); |
| | | for (String city : RegionUtil.cityList()) { |
| | | rowIdx = writeTurnoverRankRows(sheet, rowIdx, city, cumMap, province, false); |
| | | } |
| | | |
| | | // æ«å°¾ç©ºè¡ï¼ä¸æ ·ä¾ç»æå¯¹é½ï¼ |
| | | for (int i = 0; i < 4; i++) sheet.createRow(rowIdx++); |
| | | |
| | | autoWidth(sheet, 18); |
| | | // 以 docs/è´§è¿/çæ_å¨è½¬éæå.xlsx 为åºç¨¿ï¼ä¿ç表头/åå¹¶/å宽/æ ·å¼ï¼ä»
æ¿æ¢æ°æ®ï¼Q/R å æ¯ä¸ºæ¨¡æ¿ç¼åå¼ï¼ææ°æ°æ®éç® |
| | | File template = resolveFreightTemplate("çæ_å¨è½¬éæå.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | sheet.getRow(0).getCell(0).setCellValue(year + "å¹´" + cumRange(month) + "å
¨çåå¸å·ç´¯è®¡å®æå
¬è·¯è´§ç©è¿è¾å¨è½¬éæ
åµ"); |
| | | sheet.getRow(22).getCell(0).setCellValue(year + "å¹´" + month + "æå
¨çåå¸å·ç´¯è®¡å®æå
¬è·¯è´§ç©è¿è¾å¨è½¬éæ
åµ"); |
| | | fillTurnoverRankBlock(sheet, 3, cumMap, province); |
| | | fillTurnoverRankBlock(sheet, 25, cumMap, province); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | /** è´§è¿æ¥è¡¨æ¨¡æ¿å®ä½ï¼docs/è´§è¿ï¼ */ |
| | | private File resolveFreightTemplate(String fileName) throws Exception { |
| | | return resolveAnyTemplate(freightTemplateDir, fileName); |
| | | } |
| | | |
| | | /** åè´§è¿æ¨¡æ¿æ°æ®åå
æ ¼ï¼å
¬å¼åä¸å¨ï¼æå¼å
æ¸
空ååï¼POI 对å
¬å¼åå
æ ¼åªæ´æ°ç¼åï¼ï¼æ 弿¸
ç©ºæ¨¡æ¿æ ·ä¾ */ |
| | | private void setFreightCell(Row row, int idx, Double v) { |
| | | Cell c = row.getCell(idx); |
| | | if (c != null && c.getCellType() == CellType.FORMULA) return; |
| | | if (c == null) c = row.createCell(idx); |
| | | c.setBlank(); |
| | | if (v != null) c.setCellValue(v); |
| | | } |
| | | |
| | | private void setFreightCell(Row row, int idx, Integer v) { |
| | | if (v == null) { |
| | | setFreightCell(row, idx, (Double) null); |
| | | } else { |
| | | setFreightCell(row, idx, v.doubleValue()); |
| | | } |
| | | } |
| | | |
| | | // ==================== æåº¦æ¨¡æ¿èªå¨æ©åï¼1-6æ â 1-12æï¼ ==================== |
| | | |
| | | /** æåº¦æ¨¡æ¿å峿©åï¼ç´¯è®¡åï¼14/15ï¼ç§»å°æ«å°¾ï¼26/27ï¼ï¼7..12 æåç± 1 æåå
éï¼è¡¨å¤´/æ ·å¼/å
¬å¼å¼ç¨åæ¥ï¼ */ |
| | | private void extendMonthlyColumns(Sheet sheet, int headerRow, int year, int dataStartRow, int dataEndRow) { |
| | | moveColumnPair(sheet, headerRow, 14, 26, dataEndRow); // 累计/ç´¯è®¡åæ¯ â AA/AB |
| | | for (int m = 7; m <= 12; m++) { |
| | | int dst = 2 + (m - 1) * 2; |
| | | cloneColumnPair(sheet, headerRow, 2, 3, dst, dst + 1, year, m, dataStartRow, dataEndRow); |
| | | } |
| | | fixCumulativeFormulas(sheet, headerRow, dataEndRow); // AA 累计å
¬å¼ = 12 个æå¼ä¹å |
| | | } |
| | | |
| | | /** æä¸å对ï¼å¼å+忝åï¼ä» src ç§»å° dstï¼å«è¡¨å¤´/æ°æ®/æ ·å¼/åå¹¶/å宽ï¼ï¼src å
容æ¸
空 */ |
| | | private void moveColumnPair(Sheet sheet, int headerRow, int src, int dst, int lastRow) { |
| | | for (int r = headerRow; r <= lastRow; r++) { |
| | | copyCellForColumn(sheet, r, src, r, dst, false); |
| | | copyCellForColumn(sheet, r, src + 1, r, dst + 1, false); |
| | | clearCellContent(sheet, r, src); |
| | | clearCellContent(sheet, r, src + 1); |
| | | } |
| | | moveMergedRegion(sheet, src, dst); |
| | | moveMergedRegion(sheet, src + 1, dst + 1); |
| | | copyColumnWidth(sheet, src, dst); |
| | | copyColumnWidth(sheet, src + 1, dst + 1); |
| | | } |
| | | |
| | | /** æ 1 æå对å
é为 m æå对ï¼è¡¨å¤´ææ¬/æ¥æä¿®æ£ãæ ·å¼å¤å¶ãé跨簿å
¬å¼å
é并忥åå¼ç¨ */ |
| | | private void cloneColumnPair(Sheet sheet, int headerRow, int srcVal, int srcYoy, int dstVal, int dstYoy, |
| | | int year, int month, int dataStartRow, int dataEndRow) { |
| | | for (int r = headerRow; r <= dataEndRow; r++) { |
| | | boolean dataZone = r >= dataStartRow; |
| | | cloneCellForMonth(sheet, r, srcVal, dstVal, year, month, dataZone); |
| | | cloneCellForMonth(sheet, r, srcYoy, dstYoy, year, month, dataZone); |
| | | } |
| | | cloneMergedRegion(sheet, srcVal, dstVal); |
| | | cloneMergedRegion(sheet, srcYoy, dstYoy); |
| | | copyColumnWidth(sheet, srcVal, dstVal); |
| | | copyColumnWidth(sheet, srcYoy, dstYoy); |
| | | } |
| | | |
| | | /** å¤å¶åæ ¼ï¼move=false åæ ·å¤å¶ï¼move=true è¡¨å¤´ææ¬/æ¥æä¿®æ£ãæ°æ®åºä»
æ ·å¼+é跨簿å
¬å¼ï¼åå¼ç¨åæ¥ï¼ */ |
| | | private void copyCellForColumn(Sheet sheet, int r, int srcC, int dstR, int dstC, boolean monthClone) { |
| | | Row srcRow = sheet.getRow(r); |
| | | if (srcRow == null) return; |
| | | Cell src = srcRow.getCell(srcC); |
| | | if (src == null) return; |
| | | Cell dst = getOrCreateCell(sheet, dstR, dstC); |
| | | dst.setBlank(); |
| | | dst.setCellStyle(src.getCellStyle()); |
| | | if (!monthClone) { |
| | | if (src.getCellType() == CellType.FORMULA) { |
| | | String f = src.getCellFormula(); |
| | | if (f != null && !f.contains("#REF!")) dst.setCellFormula(f); |
| | | } else if (src.getCellType() == CellType.STRING) { |
| | | dst.setCellValue(src.getStringCellValue()); |
| | | } else if (src.getCellType() == CellType.NUMERIC) { |
| | | dst.setCellValue(src.getNumericCellValue()); |
| | | } else if (src.getCellType() == CellType.BOOLEAN) { |
| | | dst.setCellValue(src.getBooleanCellValue()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** å
éåæ ¼å°ç®æ æåï¼è¡¨å¤´è¡ï¼dataZone=falseï¼ä¿®æ£æä»½/å¹´ä»½ææ¬ææ¥æï¼æ°æ®åºä»
æ ·å¼+é跨簿å
¬å¼å
é */ |
| | | private void cloneCellForMonth(Sheet sheet, int r, int srcC, int dstC, int year, int month, boolean dataZone) { |
| | | Row srcRow = sheet.getRow(r); |
| | | if (srcRow == null) return; |
| | | Cell src = srcRow.getCell(srcC); |
| | | if (src == null) return; |
| | | Cell dst = getOrCreateCell(sheet, r, dstC); |
| | | dst.setBlank(); |
| | | dst.setCellStyle(src.getCellStyle()); |
| | | if (!dataZone) { |
| | | if (src.getCellType() == CellType.NUMERIC && org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(src)) { |
| | | java.util.Calendar cal = java.util.Calendar.getInstance(); |
| | | cal.set(year, month - 1, 1, 0, 0, 0); |
| | | cal.clear(java.util.Calendar.MILLISECOND); |
| | | dst.setCellValue(cal.getTime()); |
| | | } else if (src.getCellType() == CellType.STRING) { |
| | | String t = src.getStringCellValue(); |
| | | if (t != null && !t.isEmpty()) { |
| | | dst.setCellValue(t.replaceFirst("^\\d{4}å¹´", year + "å¹´").replaceFirst("(\\d+)æ", month + "æ")); |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | // æ°æ®åºï¼ä»
å¤å¶æ ·å¼ï¼é跨簿ãé #REF! çå
¬å¼å
é并忥æå¼åå¼ç¨ |
| | | if (src.getCellType() == CellType.FORMULA) { |
| | | String f = src.getCellFormula(); |
| | | if (f != null && !f.contains("[") && !f.contains("#REF!")) { |
| | | dst.setCellFormula(shiftMonthFormula(f, colLetter(dstC))); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** å
¬å¼åå¼ç¨å¹³ç§»ï¼æ¬è¡¨æå¼åï¼C/E/G/I/K/M/O/Q/S/U/W/Yï¼â ç®æ ååæ¯ï¼æé¤è·¨ç°¿ [..]! å¼ç¨ */ |
| | | private String shiftMonthFormula(String formula, String dstColLetter) { |
| | | return formula.replaceAll("(?<![A-Za-z0-9\\[\\]!])([CEGIKMOQSUWY])(\\d+)", dstColLetter + "$2"); |
| | | } |
| | | |
| | | /** AA 累计åå
¬å¼éå为 12 个æå¼ä¹åï¼æ¨¡æ¿å为 1-6 æä¹åï¼ */ |
| | | private void fixCumulativeFormulas(Sheet sheet, int headerRow, int dataEndRow) { |
| | | for (int r = headerRow; r <= dataEndRow; r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | Cell c = row.getCell(26); |
| | | if (c == null || c.getCellType() != CellType.FORMULA) continue; |
| | | String f = c.getCellFormula(); |
| | | if (f == null) continue; |
| | | java.util.regex.Matcher m = java.util.regex.Pattern |
| | | .compile("(?<![A-Za-z0-9\\[\\]!])([CEGIKMOQSUWY])(\\d+)").matcher(f); |
| | | if (!m.find()) continue; |
| | | int rowNum = Integer.parseInt(m.group(2)); |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (char col : new char[]{'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y'}) { |
| | | if (sb.length() > 0) sb.append("+"); |
| | | sb.append(col).append(rowNum); |
| | | } |
| | | c.setCellFormula(sb.toString()); |
| | | } |
| | | } |
| | | |
| | | /** åå¹¶åå
æ ¼ï¼src åååå¹¶ â dst åï¼åå½¢ç¶ï¼ï¼å¹¶ç§»é¤ååå¹¶ */ |
| | | private void moveMergedRegion(Sheet sheet, int srcCol, int dstCol) { |
| | | java.util.List<CellRangeAddress> moves = new java.util.ArrayList<>(); |
| | | for (CellRangeAddress m : sheet.getMergedRegions()) { |
| | | if (m.getFirstColumn() == srcCol && m.getLastColumn() == srcCol) moves.add(m); |
| | | } |
| | | for (CellRangeAddress m : moves) { |
| | | sheet.addMergedRegion(new CellRangeAddress(m.getFirstRow(), m.getLastRow(), dstCol, dstCol)); |
| | | for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) { |
| | | if (sheet.getMergedRegion(i).equals(m)) { |
| | | sheet.removeMergedRegion(i); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** åå¹¶åå
æ ¼ï¼src åååå¹¶ â dst åï¼åå½¢ç¶ï¼ä¿çååå¹¶ï¼ */ |
| | | private void cloneMergedRegion(Sheet sheet, int srcCol, int dstCol) { |
| | | for (CellRangeAddress m : sheet.getMergedRegions()) { |
| | | if (m.getFirstColumn() == srcCol && m.getLastColumn() == srcCol) { |
| | | sheet.addMergedRegion(new CellRangeAddress(m.getFirstRow(), m.getLastRow(), dstCol, dstCol)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void copyColumnWidth(Sheet sheet, int src, int dst) { |
| | | try { |
| | | int w = sheet.getColumnWidth(src); |
| | | if (w > 0) sheet.setColumnWidth(dst, w); |
| | | } catch (Exception ignore) { } |
| | | } |
| | | |
| | | private Cell getOrCreateCell(Sheet sheet, int r, int c) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) row = sheet.createRow(r); |
| | | Cell cell = row.getCell(c); |
| | | if (cell == null) cell = row.createCell(c); |
| | | return cell; |
| | | } |
| | | |
| | | private void clearCellContent(Sheet sheet, int r, int c) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) return; |
| | | Cell cell = row.getCell(c); |
| | | if (cell != null) cell.setBlank(); |
| | | } |
| | | |
| | | private String colLetter(int colIdx) { |
| | | return org.apache.poi.ss.util.CellReference.convertNumToColString(colIdx); |
| | | } |
| | | |
| | | /** æ
客åå¸å·æå¼ï¼0=客è¿é/1=å¨è½¬é/2=个ä½å®¢è¿é/3=个ä½å¨è½¬éï¼ */ |
| | | private double passengerMonthVal(Map<Integer, Map<String, PassengerAgg>> data, Map<Integer, Map<String, double[]>> indi, |
| | | int year, int month, String region, int metricIdx) { |
| | | if (metricIdx <= 1) { |
| | | PassengerAgg agg = aggOf(data, year, month, region); |
| | | Double val = agg == null ? null |
| | | : (metricIdx == 0 ? agg.passengerTotal / 10000.0 : agg.turnoverTotal / 10000.0); |
| | | return val == null ? 0.0 : val; |
| | | } |
| | | return individualVal(indi, year, month, region, metricIdx - 2); |
| | | } |
| | | |
| | | /** æ
å®¢ç´¯è®¡åæ¯ï¼å»å¹´ 1..months æç´¯è®¡ï¼ï¼æ å»å¹´æ°æ®è¿å null */ |
| | | private Double passengerCumYoy(Map<Integer, Map<String, PassengerAgg>> data, Map<Integer, Map<String, double[]>> indi, |
| | | int year, int months, String region, int metricIdx) { |
| | | double v = 0.0, lv = 0.0; |
| | | for (int m = 1; m <= months; m++) { |
| | | v += passengerMonthVal(data, indi, year, m, region, metricIdx); |
| | | lv += passengerMonthVal(data, indi, year - 1, m, region, metricIdx); |
| | | } |
| | | return lv == 0.0 ? null : (v - lv) / lv; |
| | | } |
| | | |
| | | /** ä¸å£å¾æåæ¯ï¼k=-1 表示æ»è¡ï¼ç线+å
¬äº¤ä¹åï¼åºç§/ç½çº¦è½¦æ æºæ°æ®æ 0ï¼ï¼æ å»å¹´æ°æ®ç空 */ |
| | | private void fillMidYoyCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month, |
| | | String area, int k, boolean volume) { |
| | | double v = 0.0, lv = 0.0; |
| | | if (k == -1) { |
| | | for (int kk = 1; kk <= 4; kk++) { |
| | | if (kk >= 3) continue; |
| | | v += midClassVal(mid, year, month, area, kk, volume); |
| | | lv += midClassVal(mid, year - 1, month, area, kk, volume); |
| | | } |
| | | } else if (k <= 2) { |
| | | v = midClassVal(mid, year, month, area, k, volume); |
| | | lv = midClassVal(mid, year - 1, month, area, k, volume); |
| | | } |
| | | c.setBlank(); |
| | | if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4)); |
| | | } |
| | | |
| | | /** ä¸å£å¾ç´¯è®¡åæ¯åï¼1..months æç´¯è®¡ï¼k=-1 æ»è¡ = ç线+å
¬äº¤ï¼ */ |
| | | private void fillMidCumYoyColumn(Sheet sheet, Map<Integer, Map<String, double[][]>> mid, int year, |
| | | int months, int colIdx) { |
| | | List<String> areas = midTemplateAreas(); |
| | | for (int r0 = 4; r0 <= sheet.getLastRowNum(); r0++) { |
| | | int rr = r0 + 1; |
| | | int blockOffset = -1; |
| | | boolean volume = false; |
| | | if (rr >= 5 && rr <= 94) { |
| | | blockOffset = rr - 5; |
| | | volume = true; |
| | | } else if (rr >= 99 && rr <= 188) { |
| | | blockOffset = rr - 99; |
| | | volume = false; |
| | | } |
| | | if (blockOffset < 0 || blockOffset % 5 == 0) continue; |
| | | int k = blockOffset % 5; |
| | | String area = areas.get(blockOffset / 5); |
| | | double v = 0.0, lv = 0.0; |
| | | for (int m = 1; m <= months; m++) { |
| | | if (k <= 2) { |
| | | v += midClassVal(mid, year, m, area, k, volume); |
| | | lv += midClassVal(mid, year - 1, m, area, k, volume); |
| | | } |
| | | } |
| | | Row row = sheet.getRow(r0); |
| | | if (row == null) continue; |
| | | Cell c = row.getCell(colIdx); |
| | | if (c == null) c = row.createCell(colIdx); |
| | | c.setBlank(); |
| | | if (v != 0.0 && lv != 0.0) c.setCellValue(round((v - lv) / lv, 4)); |
| | | } |
| | | } |
| | | |
| | | /** è´§è¿åå¸å·æç»è¡å¡«å
ï¼1..fillMonths æå¼/åæ¯ï¼æå¤ 12 æï¼å
¶ä½æ¸
空ï¼+ 累计/ç´¯è®¡åæ¯ï¼cumCol 起两åï¼ */ |
| | | private void fillFreightDetailRow(Row row, int fillMonths, int cumCol, |
| | | java.util.function.IntFunction<Double> monthValue, |
| | | java.util.function.IntFunction<Double> monthYoy, |
| | | Double cumValue, Double cumYoy) { |
| | | for (int m = 1; m <= (fillMonths > 6 ? 12 : 6); m++) { |
| | | if (m <= fillMonths) { |
| | | setFreightCell(row, 2 + (m - 1) * 2, monthValue.apply(m)); |
| | | setFreightCell(row, 3 + (m - 1) * 2, monthYoy.apply(m)); |
| | | } else { |
| | | setFreightCell(row, 2 + (m - 1) * 2, (Double) null); |
| | | setFreightCell(row, 3 + (m - 1) * 2, (Double) null); |
| | | } |
| | | } |
| | | setFreightCell(row, cumCol, cumValue); |
| | | setFreightCell(row, cumCol + 1, cumYoy); |
| | | } |
| | | |
| | | /** è´§è¿éæååå¡«å
ï¼æ¨¡æ¿ 1-based r4 èµ·ï¼å
¨ç+17å¸å·ï¼ï¼Q/R å æ¯å
¬å¼ä¿çä¸å¨ */ |
| | | private void fillFreightRankBlock(Sheet sheet, int start, |
| | | Map<String, Double> above, Map<String, Double> below, Map<String, Double> total, |
| | | Map<String, Double> aboveYoy, Map<String, Double> belowYoy, Map<String, Double> totalYoy, |
| | | Double provAbove, Double provBelow, Double provTotal, |
| | | Double provAboveYoy, Double provBelowYoy, Double provTotalYoy) { |
| | | Row provRow = sheet.getRow(start); |
| | | if (provRow == null) provRow = sheet.createRow(start); |
| | | setFreightCell(provRow, 1, provAbove); |
| | | setFreightCell(provRow, 4, provAboveYoy); |
| | | setFreightCell(provRow, 6, provBelow); |
| | | setFreightCell(provRow, 9, provBelowYoy); |
| | | setFreightCell(provRow, 11, provTotal); |
| | | setFreightCell(provRow, 14, provTotalYoy); |
| | | int rowIdx = start + 1; |
| | | for (String city : RegionUtil.cityList()) { |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | setFreightCell(row, 1, above.get(city)); |
| | | setFreightCell(row, 2, rankOfMap(above, city)); |
| | | setFreightCell(row, 3, ratioOf(above.get(city), provAbove)); |
| | | setFreightCell(row, 4, aboveYoy.get(city)); |
| | | setFreightCell(row, 5, yoyRankOfMap(aboveYoy, city)); |
| | | setFreightCell(row, 6, below.get(city)); |
| | | setFreightCell(row, 7, rankOfMap(below, city)); |
| | | setFreightCell(row, 8, ratioOf(below.get(city), provBelow)); |
| | | setFreightCell(row, 9, belowYoy.get(city)); |
| | | setFreightCell(row, 10, yoyRankOfMap(belowYoy, city)); |
| | | setFreightCell(row, 11, total.get(city)); |
| | | setFreightCell(row, 12, rankOfMap(total, city)); |
| | | setFreightCell(row, 13, ratioOf(total.get(city), provTotal)); |
| | | setFreightCell(row, 14, totalYoy.get(city)); |
| | | setFreightCell(row, 15, yoyRankOfMap(totalYoy, city)); |
| | | rowIdx++; |
| | | } |
| | | } |
| | | |
| | | /** å¨è½¬éæååå¡«å
ï¼æ¨¡æ¿ 1-based r4 èµ·ï¼å
¨ç+17å¸å·ï¼ */ |
| | | private void fillTurnoverRankBlock(Sheet sheet, int start, |
| | | Map<String, ScaleSplitTransport> cumMap, ScaleSplitTransport province) { |
| | | if (province == null) province = new ScaleSplitTransport(); |
| | | Double provAbove = province.getAboveScaleTurnover(); |
| | | Double provBelow = province.getBelowScaleTurnover(); |
| | | Double provTotal = province.getTotalTurnover(); |
| | | Row provRow = sheet.getRow(start); |
| | | if (provRow == null) provRow = sheet.createRow(start); |
| | | setFreightCell(provRow, 1, provAbove); |
| | | setFreightCell(provRow, 4, province.getAboveScaleYoy()); |
| | | setFreightCell(provRow, 6, provBelow); |
| | | setFreightCell(provRow, 9, province.getBelowScaleYoy()); |
| | | setFreightCell(provRow, 11, provTotal); |
| | | setFreightCell(provRow, 14, province.getTotalYoy()); |
| | | setFreightShare(provRow, provAbove, provTotal); |
| | | int rowIdx = start + 1; |
| | | for (String city : RegionUtil.cityList()) { |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | ScaleSplitTransport cum = cumMap.get(city); |
| | | Double above = cum == null ? null : cum.getAboveScaleTurnover(); |
| | | Double below = cum == null ? null : cum.getBelowScaleTurnover(); |
| | | Double total = cum == null ? null : cum.getTotalTurnover(); |
| | | setFreightCell(row, 1, above); |
| | | setFreightCell(row, 2, cum == null ? null : rankOfTurnover(cumMap, city, 0)); |
| | | setFreightCell(row, 3, ratioOf(above, provAbove)); |
| | | setFreightCell(row, 4, cum == null ? null : cum.getAboveScaleYoy()); |
| | | setFreightCell(row, 5, cum == null ? null : yoyRankOfTurnover(cumMap, city, 0)); |
| | | setFreightCell(row, 6, below); |
| | | setFreightCell(row, 7, cum == null ? null : rankOfTurnover(cumMap, city, 1)); |
| | | setFreightCell(row, 8, ratioOf(below, provBelow)); |
| | | setFreightCell(row, 9, cum == null ? null : cum.getBelowScaleYoy()); |
| | | setFreightCell(row, 10, cum == null ? null : yoyRankOfTurnover(cumMap, city, 1)); |
| | | setFreightCell(row, 11, total); |
| | | setFreightCell(row, 12, cum == null ? null : rankOfTurnover(cumMap, city, 2)); |
| | | setFreightCell(row, 13, ratioOf(total, provTotal)); |
| | | setFreightCell(row, 14, cum == null ? null : cum.getTotalYoy()); |
| | | setFreightCell(row, 15, cum == null ? null : yoyRankOfTurnover(cumMap, city, 2)); |
| | | setFreightShare(row, above, total); |
| | | rowIdx++; |
| | | } |
| | | } |
| | | |
| | | /** å¨è½¬éæå Q/R å æ¯ï¼æ¨¡æ¿ä¸ºç¼åå¼ï¼ææ°æ°æ®éç®ï¼è§ä¸å æ¯ 1 ä½å°æ°ï¼ */ |
| | | private void setFreightShare(Row row, Double above, Double total) { |
| | | if (total != null && total > 0 && above != null) { |
| | | double share = round(above * 10.0 / total, 1); |
| | | setFreightCell(row, 16, share); |
| | | setFreightCell(row, 17, round(10.0 - share, 1)); |
| | | } else { |
| | | setFreightCell(row, 16, (Double) null); |
| | | setFreightCell(row, 17, (Double) null); |
| | | } |
| | | } |
| | | |
| | | // ==================== æ°æ®å è½½ ==================== |
| | | |
| | | /** å½ææ°æ®: city -> month -> recordï¼ä¸å«å
¨çï¼ */ |
| | |
| | | return rank; |
| | | } |
| | | |
| | | // ==================== 4. çæ_å
¬è·¯æ
客åå¸å·æç».xlsxï¼ææ¥/å¹´æ¥ï¼ ==================== |
| | | |
| | | /** |
| | | * å
¬è·¯æ
客åå¸å·æç» |
| | | * @param period ææ¥: 2026-06ï¼ä»å¹´1-6æï¼ï¼å¹´æ¥: 2026ï¼å
¨å¹´12个æï¼ |
| | | * @param mode month/year |
| | | */ |
| | | /** |
| | | * çæ_å
¬è·¯æ
客åå¸å·.xlsxï¼ä»¥ docs/å
¬è·¯æ
客+è½è/è¾åº 模æ¿ä¸ºåºç¨¿ï¼ |
| | | * ä¿ç表头/åå¹¶/å宽/å
¬å¼/æ ·å¼ï¼ä»
æ¿æ¢ 2026 å¹´ 1..6 ææåº¦åæ°æ®ï¼å®¢è¿é/å¨è½¬é/个ä½è¡ï¼ã |
| | | * 2025/2024 åå²åä¿ç模æ¿å¼ï¼ç´¯è®¡/忝/å
¨çè¡å
¬å¼ä¿çå¹¶éç®ã |
| | | */ |
| | | public byte[] exportPassengerCityDetail(String period, String mode) throws Exception { |
| | | int maxMonth = monthOf(period, mode); |
| | | int currentYear = Integer.parseInt(period.substring(0, 4)); |
| | | File template = resolvePassengerTemplate("çæ_å
¬è·¯æ
客åå¸å·.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | fixPassengerHeaderYear(sheet, currentYear); // è¡¨å¤´å¹´ä»½å¨æåï¼ä¸åæ» 2026ï¼ |
| | | Map<Integer, Map<String, PassengerAgg>> data = loadPassengerAggMap(); |
| | | Map<Integer, Map<String, double[]>> indi = loadPassengerIndividualMap(); |
| | | int fillMonths = maxMonth; // 1..N æï¼N>6 æ¶æ¨¡æ¿èªå¨å峿©åå° 12 æï¼ |
| | | boolean extended = fillMonths > 6; |
| | | if (extended) extendMonthlyColumns(sheet, 1, currentYear, 3, sheet.getLastRowNum()); |
| | | String currentRegion = null; |
| | | for (int r = 3; r <= sheet.getLastRowNum(); r++) { // ä» R4 èµ·ï¼A ååå¹¶è¡æ¯åºåæ è®°ï¼R4 å
¨çãR8 æ¦æ±å¸â¦ï¼ |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | Cell nameCell = row.getCell(0); |
| | | Cell metricCell = row.getCell(1); |
| | | if (metricCell == null) continue; |
| | | String areaName = cellText(nameCell); |
| | | if (areaName != null && !areaName.trim().isEmpty()) { |
| | | currentRegion = "å
¨ç".equals(areaName.trim()) ? "æ¹åç" : RegionUtil.normalizeCityName(areaName.trim()); |
| | | } |
| | | if (currentRegion == null) continue; |
| | | String metric = cellText(metricCell); |
| | | if (metric == null) continue; |
| | | int metricIdx = cityMetricIndexOf(metric); |
| | | if (metricIdx < 0) continue; |
| | | String region = currentRegion; |
| | | for (int m = 1; m <= fillMonths; m++) { |
| | | int col = 2 + (m - 1) * 2; // C,E,G,I,K,M,O,Q,S,U,W,Yï¼7-12 æä¸ºæ©ååï¼ |
| | | Cell c = row.getCell(col); |
| | | boolean keepFormula = c != null && c.getCellType() == CellType.FORMULA; // å
¨ç/累计çå
¬å¼ä¿ç |
| | | double v = passengerMonthVal(data, indi, currentYear, m, region, metricIdx); |
| | | if (!keepFormula) { |
| | | if (c == null) c = row.createCell(col); |
| | | if (v == 0.0) c.setBlank(); else c.setCellValue(round(v, 4)); |
| | | } |
| | | // 忝åï¼åºå
å»å¹´åæåæ¯ï¼æ¨¡æ¿ #REF! å
¬å¼æ¿æ¢ä¸ºæ°å¼ï¼å
¬å¼è¡ä¹è¦åï¼ |
| | | double lv = passengerMonthVal(data, indi, currentYear - 1, m, region, metricIdx); |
| | | Cell yc = row.getCell(col + 1); |
| | | if (yc == null) yc = row.createCell(col + 1); |
| | | if (v == 0.0 || lv == 0.0) yc.setBlank(); else yc.setCellValue(round((v - lv) / lv, 4)); |
| | | } |
| | | // ç´¯è®¡åæ¯åï¼æ¨¡æ¿ #REF! â åºå
å»å¹´ 1..N æç´¯è®¡åæ¯æ°å¼ï¼ |
| | | int cumCol = extended ? 26 : 14; |
| | | Double cumYoy = passengerCumYoy(data, indi, currentYear, fillMonths, region, metricIdx); |
| | | Cell yc2 = row.getCell(cumCol + 1); |
| | | if (yc2 == null) yc2 = row.createCell(cumCol + 1); |
| | | if (cumYoy == null) yc2.setBlank(); else yc2.setCellValue(round(cumYoy, 4)); |
| | | } |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** åå¸å·è¡¨ææ è¡ï¼0=客è¿é 1=å¨è½¬é 2=个ä½å®¢è¿é 3=个ä½å¨è½¬éï¼æ æ³è¯å«è¿å -1 */ |
| | | private int cityMetricIndexOf(String metric) { |
| | | if (metric == null) return -1; |
| | | boolean indi = metric.contains("个ä½"); |
| | | if (metric.contains("客è¿é")) return indi ? 2 : 0; |
| | | if (metric.contains("å¨è½¬é")) return indi ? 3 : 1; |
| | | return -1; |
| | | } |
| | | |
| | | private String cellText(Cell c) { |
| | | if (c == null) return null; |
| | | if (c.getCellType() == CellType.STRING) return c.getStringCellValue(); |
| | | if (c.getCellType() == CellType.NUMERIC) return String.valueOf(c.getNumericCellValue()); |
| | | return null; |
| | | } |
| | | |
| | | /** 个ä½å®¢è¿é/å¨è½¬éï¼ä¸äºº/ä¸äººå
¬éï¼ */ |
| | | private double individualVal(Map<Integer, Map<String, double[]>> indi, int year, int month, String region, int idx) { |
| | | Map<String, double[]> mm = indi.get(year * 100 + month); |
| | | if (mm == null) return 0; |
| | | double[] arr = mm.get(region); |
| | | if (arr == null) return 0; |
| | | return idx < arr.length ? arr[idx] : 0; |
| | | } |
| | | |
| | | /** 个ä½å®¢è¿é/å¨è½¬éæ°æ®ï¼å¹´*100+æ -> (å¸å·/å
¨ç -> [个ä½å®¢è¿é(ä¸äºº), 个ä½å¨è½¬é(ä¸äººå
Ž)]) */ |
| | | private Map<Integer, Map<String, double[]>> loadPassengerIndividualMap() { |
| | | Map<Integer, Map<String, double[]>> data = new HashMap<>(); |
| | | for (PassengerIndividualMonthly e : passengerIndividualMapper.selectList(null)) { |
| | | if (e.getReportPeriod() == null) continue; |
| | | String[] parts = e.getReportPeriod().split("-"); |
| | | if (parts.length != 2) continue; |
| | | int y, m; |
| | | try { |
| | | y = Integer.parseInt(parts[0]); |
| | | m = Integer.parseInt(parts[1]); |
| | | } catch (NumberFormatException ex) { |
| | | continue; |
| | | } |
| | | String city = RegionUtil.cityByCode(e.getRegionCode()); |
| | | if (city == null) continue; |
| | | int key = y * 100 + m; |
| | | double pass = nz(e.getPassengerCount()) / 10000.0; |
| | | double turn = nz(e.getTurnover()) / 10000.0; |
| | | double[] arr = data.computeIfAbsent(key, k -> new HashMap<>()).computeIfAbsent(city, k -> new double[2]); |
| | | arr[0] += pass; |
| | | arr[1] += turn; |
| | | double[] prov = data.get(key).computeIfAbsent("æ¹åç", k -> new double[2]); |
| | | prov[0] += pass; |
| | | prov[1] += turn; |
| | | } |
| | | return data; |
| | | } |
| | | |
| | | private PassengerAgg aggOf(Map<Integer, Map<String, PassengerAgg>> data, int year, int month, String region) { |
| | | Map<String, PassengerAgg> monthMap = data.get(year * 100 + month); |
| | | return monthMap == null ? null : monthMap.get(region); |
| | | } |
| | | |
| | | private Double cumOf(Map<Integer, Map<String, PassengerAgg>> data, int year, String region, int monthCount, int metricIdx) { |
| | | double sum = 0.0; |
| | | boolean has = false; |
| | | for (int m = 1; m <= monthCount; m++) { |
| | | Double v = metricValue(aggOf(data, year, m, region), metricIdx); |
| | | if (v != null) { |
| | | sum += v; |
| | | has = true; |
| | | } |
| | | } |
| | | return has ? sum : null; |
| | | } |
| | | |
| | | private Double yoy(Double cur, Double base) { |
| | | if (cur == null || base == null || base == 0.0) return null; |
| | | return round((cur - base) / base, 4); |
| | | } |
| | | |
| | | /** ææ åå¼ï¼å·²æ¢ç®ä¸ºä¸äºº/ä¸äººå
¬é/å
¬éï¼æ æ°æ®è¿å nullï¼ */ |
| | | private Double metricValue(PassengerAgg agg, int metric) { |
| | | if (agg == null) return null; |
| | | switch (metric) { |
| | | case 0: return agg.passengerTotal / 10000.0; |
| | | case 1: return agg.passengerTotal / 10000.0; |
| | | case 2: return agg.passengerClass1 / 10000.0; |
| | | case 3: return agg.passengerClass2 / 10000.0; |
| | | case 4: return agg.passengerClass3 / 10000.0; |
| | | case 5: return agg.passengerClass4 / 10000.0; |
| | | case 6: return agg.passengerCharter / 10000.0; |
| | | case 7: |
| | | case 8: |
| | | case 9: return null; // åå¸å®¢è¿æ¨¡åæ°æ®ï¼æç空 |
| | | case 10: return agg.turnoverTotal / 10000.0; |
| | | case 11: return agg.turnoverClass1 / 10000.0; |
| | | case 12: return agg.turnoverClass2 / 10000.0; |
| | | case 13: return agg.turnoverClass3 / 10000.0; |
| | | case 14: return agg.turnoverClass4 / 10000.0; |
| | | case 15: return agg.turnoverCharter / 10000.0; |
| | | case 16: return ratioOrNull(agg.turnoverTotal, agg.passengerTotal); |
| | | case 17: return ratioOrNull(agg.turnoverClass1, agg.passengerClass1); |
| | | case 18: return ratioOrNull(agg.turnoverClass2, agg.passengerClass2); |
| | | case 19: return ratioOrNull(agg.turnoverClass3, agg.passengerClass3); |
| | | case 20: return ratioOrNull(agg.turnoverClass4, agg.passengerClass4); |
| | | case 21: return ratioOrNull(agg.turnoverCharter, agg.passengerCharter); |
| | | default: return null; |
| | | } |
| | | } |
| | | |
| | | private Double ratioOrNull(double numerator, double denominator) { |
| | | if (denominator == 0.0) return null; |
| | | return numerator / denominator; |
| | | } |
| | | |
| | | /** åæå
¨å¸å·ä¼ä¸æ±æ» */ |
| | | private static class PassengerAgg { |
| | | double passengerTotal; |
| | | double passengerClass1; |
| | | double passengerClass2; |
| | | double passengerClass3; |
| | | double passengerClass4; |
| | | double passengerCharter; |
| | | double turnoverTotal; |
| | | double turnoverClass1; |
| | | double turnoverClass2; |
| | | double turnoverClass3; |
| | | double turnoverClass4; |
| | | double turnoverCharter; |
| | | |
| | | void add(PassengerEnterpriseMonthly e) { |
| | | passengerTotal += nz(e.getPassengerClass1()) + nz(e.getPassengerClass2()) + nz(e.getPassengerClass3()) |
| | | + nz(e.getPassengerClass4()) + nz(e.getPassengerCharter()); |
| | | passengerClass1 += nz(e.getPassengerClass1()); |
| | | passengerClass2 += nz(e.getPassengerClass2()); |
| | | passengerClass3 += nz(e.getPassengerClass3()); |
| | | passengerClass4 += nz(e.getPassengerClass4()); |
| | | passengerCharter += nz(e.getPassengerCharter()); |
| | | turnoverTotal += nz(e.getTurnoverClass1()) + nz(e.getTurnoverClass2()) + nz(e.getTurnoverClass3()) |
| | | + nz(e.getTurnoverClass4()) + nz(e.getTurnoverCharter()); |
| | | turnoverClass1 += nz(e.getTurnoverClass1()); |
| | | turnoverClass2 += nz(e.getTurnoverClass2()); |
| | | turnoverClass3 += nz(e.getTurnoverClass3()); |
| | | turnoverClass4 += nz(e.getTurnoverClass4()); |
| | | turnoverCharter += nz(e.getTurnoverCharter()); |
| | | } |
| | | |
| | | private double nz(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | // ==================== ä¸å£å¾æç»/æå/åæï¼H203-1 å
¬è·¯æ
å®¢ï¼ ==================== |
| | | |
| | | /** å è½½ å¹´*100+æ -> (å¸å·/å
¨ç -> ä¼ä¸æ±æ») æ°æ® */ |
| | | private Map<Integer, Map<String, PassengerAgg>> loadPassengerAggMap() { |
| | | Map<Integer, Map<String, PassengerAgg>> data = new HashMap<>(); |
| | | List<PassengerEnterpriseMonthly> list = passengerMapper.selectList(null); |
| | | for (PassengerEnterpriseMonthly e : list) { |
| | | if (e.getReportPeriod() == null) continue; |
| | | String[] parts = e.getReportPeriod().split("-"); |
| | | if (parts.length != 2) continue; |
| | | int y; |
| | | int m; |
| | | try { |
| | | y = Integer.parseInt(parts[0]); |
| | | m = Integer.parseInt(parts[1]); |
| | | } catch (NumberFormatException ex) { |
| | | continue; |
| | | } |
| | | String city = RegionUtil.cityByCode(e.getRegionCode()); |
| | | if (city == null) continue; |
| | | int key = y * 100 + m; |
| | | Map<String, PassengerAgg> monthMap = data.computeIfAbsent(key, k -> new HashMap<>()); |
| | | monthMap.computeIfAbsent(city, k -> new PassengerAgg()).add(e); |
| | | monthMap.computeIfAbsent("æ¹åç", k -> new PassengerAgg()).add(e); |
| | | } |
| | | return data; |
| | | } |
| | | |
| | | /** |
| | | * ä¸å£å¾æç»ï¼å®¢è¿éå + æ
客å¨è½¬éåï¼æ»/å
¬è·¯ç线/åé
å乡å
¬äº¤/巡游åºç§/ç½çº¦è½¦ï¼ |
| | | */ |
| | | /** |
| | | * çæ_ä¸å£å¾æç».xlsxï¼ä»¥æ ·ä¾ä¸ºåºç¨¿ãæ°æ®åºå¤é¨å¼ç¨å
¬å¼ï¼ç线/å
¬äº¤/åºç§/ç½çº¦è½¦ï¼æ¿æ¢ä¸ºåºå
æ°å¼ï¼ |
| | | * 2025 å¹´åä¿ç模æ¿ç¼åå¼ï¼å»å¹´æ°æ®ï¼ï¼å
é¨å
¬å¼ï¼ç´¯è®¡/忝/æ±åï¼ä¿çå¹¶éç®ã |
| | | */ |
| | | public byte[] exportPassengerMidDetail(String period, String mode) throws Exception { |
| | | int maxMonth = monthOf(period, mode); |
| | | int currentYear = Integer.parseInt(period.substring(0, 4)); |
| | | File template = resolvePassengerTemplate("çæ_ä¸å£å¾æç».xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | fixPassengerHeaderYear(sheet, currentYear); // è¡¨å¤´å¹´ä»½å¨æåï¼ä¸åæ» 2026ï¼ |
| | | Map<Integer, Map<String, double[][]>> mid = loadMidClassMap(); |
| | | int fillMonths = maxMonth; // 1..N æï¼N>6 æ¶æ¨¡æ¿èªå¨å峿©åå° 12 æï¼ |
| | | boolean extended = fillMonths > 6; |
| | | if (extended) extendMonthlyColumns(sheet, 2, currentYear, 4, sheet.getLastRowNum()); |
| | | List<String> areas = midTemplateAreas(); |
| | | for (Row row : sheet) { |
| | | if (row == null) continue; |
| | | for (Cell c : row) { |
| | | if (c.getCellType() != CellType.FORMULA) continue; |
| | | String f = c.getCellFormula(); |
| | | if (f == null) continue; |
| | | boolean crossBook = f.contains("["); |
| | | if (!crossBook && !isYoyMonthCol(c.getColumnIndex() + 1)) continue; // å
é¨å
¬å¼ï¼å计/累计ï¼ä¿çéç®ï¼åæ¯å #REF! è¦å为æ°å¼ |
| | | if (crossBook && !is2026MonthCol(c.getColumnIndex() + 1)) { |
| | | keepCached(c); // 2025 å¹´åä¿ç模æ¿ç¼åå¼ |
| | | continue; |
| | | } |
| | | int r = c.getRowIndex() + 1; |
| | | int col = c.getColumnIndex() + 1; |
| | | int blockOffset = -1; |
| | | boolean volume = false; |
| | | if (r >= 5 && r <= 94) { |
| | | blockOffset = r - 5; |
| | | volume = true; |
| | | } else if (r >= 99 && r <= 188) { |
| | | blockOffset = r - 99; |
| | | volume = false; |
| | | } |
| | | if (blockOffset < 0) { |
| | | keepCached(c); // r1/r2 夿³¨ç |
| | | continue; |
| | | } |
| | | if (blockOffset % 5 == 0) { |
| | | if (isYoyMonthCol(col)) { |
| | | fillMidYoyCell(c, mid, currentYear, monthOfCol(col), areas.get(blockOffset / 5), -1, volume); |
| | | } else { |
| | | keepCached(c); // æ»è¡å
é¨å
¬å¼ï¼é²å¾¡ï¼ |
| | | } |
| | | continue; |
| | | } |
| | | int k = blockOffset % 5; // 1=ç线 2=å
¬äº¤ 3=åºç§ 4=ç½çº¦è½¦ |
| | | String area = areas.get(blockOffset / 5); |
| | | if (is2026MonthCol(col)) { |
| | | fillMidCell(c, mid, currentYear, monthOfCol(col), area, k, volume); |
| | | } else if (isYoyMonthCol(col)) { |
| | | fillMidYoyCell(c, mid, currentYear, monthOfCol(col), area, k, volume); |
| | | } else { |
| | | keepCached(c); // 2025 å¹´åä¿ç模æ¿ç¼åå¼ |
| | | } |
| | | } |
| | | } |
| | | // ç´¯è®¡åæ¯åï¼æ¨¡æ¿ #REF! â åºå
å»å¹´ 1..N æç´¯è®¡åæ¯æ°å¼ï¼ |
| | | fillMidCumYoyColumn(sheet, mid, currentYear, fillMonths, extended ? 27 : 15); |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** ä¸å£å¾æç»æ¨¡æ¿å°åºé¡ºåºï¼ä¸ RegionUtil.CITY_LIST ä¸è´ï¼æ¦æ±å¸â¦ç¥åæ¶æåºï¼ */ |
| | | private List<String> midTemplateAreas() { |
| | | List<String> areas = new java.util.ArrayList<>(); |
| | | areas.add("æ¹åç"); |
| | | areas.addAll(RegionUtil.CITY_LIST); |
| | | return areas; |
| | | } |
| | | |
| | | private boolean is2026MonthCol(int col) { |
| | | return col >= 3 && col <= 25 && col % 2 == 1; |
| | | } |
| | | |
| | | /** æåæ¯åï¼1-based å¶æ°å 4..26ï¼ */ |
| | | private boolean isYoyMonthCol(int col) { |
| | | return col >= 4 && col <= 26 && col % 2 == 0; |
| | | } |
| | | |
| | | private int monthOfCol(int col) { |
| | | return (col - 1) / 2; |
| | | } |
| | | |
| | | /** å¡«ä¸å£å¾æ°æ®åå
æ ¼ï¼åºç§/ç½çº¦è½¦(3/4)æ¾å¼ 0ï¼ç线/å
¬äº¤å¡«åºå
å¼ï¼æ 弿¸
ç©ºï¼ */ |
| | | private void fillMidCell(Cell c, Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) { |
| | | if (k == 3 || k == 4) { |
| | | writeExplicitZero(c); // åºç§/ç½çº¦è½¦ææ æ°æ®æºï¼æ¾å¼ 0 |
| | | return; |
| | | } |
| | | double v = midClassVal(mid, year, month, area, k, volume); |
| | | // POI setCellValue(double) 对å
¬å¼åå
æ ¼åªæ´æ°ç¼åä¸ç§»é¤å
¬å¼ï¼å¿
é¡»å
setBlank ååå¼ |
| | | c.setBlank(); |
| | | if (v != 0.0) c.setCellValue(round(v, 4)); |
| | | } |
| | | |
| | | /** æ¾å¼åå
¥ 0ï¼POI setCellValue(0.0) ä¼è½¬ blankï¼éæä½åºå± XMLï¼ */ |
| | | private void writeExplicitZero(Cell c) { |
| | | c.setBlank(); |
| | | if (c instanceof XSSFCell) { |
| | | try { |
| | | ((XSSFCell) c).getCTCell().setT(STCellType.N); |
| | | ((XSSFCell) c).getCTCell().setV("0"); |
| | | } catch (Exception e) { |
| | | log.warn("writeExplicitZero failed: {}", e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private double midClassVal(Map<Integer, Map<String, double[][]>> mid, int year, int month, String area, int k, boolean volume) { |
| | | Map<String, double[][]> mm = mid.get(year * 100 + month); |
| | | if (mm == null) return 0; |
| | | double[][] arr = mm.get(area); |
| | | if (arr == null) return 0; |
| | | if (k < 0 || k >= arr.length) return 0; |
| | | return volume ? arr[k][0] : arr[k][1]; |
| | | } |
| | | |
| | | /** å
¬å¼åå
æ ¼æ¿æ¢ä¸ºç¼åæ°å¼ï¼é¿å
跨簿å¼ç¨æé¾ï¼éæ°å¼ç¼åç½®ç©ºï¼ */ |
| | | private void keepCached(Cell c) { |
| | | try { |
| | | double v = 0.0; |
| | | boolean has = false; |
| | | if (c.getCachedFormulaResultType() == CellType.NUMERIC) { |
| | | v = c.getNumericCellValue(); |
| | | has = true; |
| | | } |
| | | c.setBlank(); |
| | | if (has && v != 0.0) c.setCellValue(v); |
| | | } catch (Exception e) { |
| | | try { c.setBlank(); } catch (Exception ignore) { } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä¸å£å¾åç±»æåº¦æ°æ®ï¼å¹´*100+æ -> (å¸å·/å
¨ç -> double[5][2]) |
| | | * 维度 0=æ»é 1=å
¬è·¯ç线(h2031) 2=åé
å乡å
¬äº¤(cityBus) 3=巡游åºç§ 4=ç½çº¦è½¦ï¼[客è¿é(ä¸äºº), å¨è½¬é(ä¸äººå
Ž)] |
| | | */ |
| | | private Map<Integer, Map<String, double[][]>> loadMidClassMap() { |
| | | Map<Integer, Map<String, double[][]>> mid = new HashMap<>(); |
| | | for (PassengerEnterpriseMonthly e : passengerMapper.selectList(null)) { |
| | | if (e.getReportPeriod() == null) continue; |
| | | String[] parts = e.getReportPeriod().split("-"); |
| | | if (parts.length != 2) continue; |
| | | int y, m; |
| | | try { |
| | | y = Integer.parseInt(parts[0]); |
| | | m = Integer.parseInt(parts[1]); |
| | | } catch (NumberFormatException ex) { |
| | | continue; |
| | | } |
| | | String city = RegionUtil.cityByCode(e.getRegionCode()); |
| | | if (city == null) continue; |
| | | int key = y * 100 + m; |
| | | double pass = (nz(e.getPassengerClass1()) + nz(e.getPassengerClass2()) + nz(e.getPassengerClass3()) |
| | | + nz(e.getPassengerClass4()) + nz(e.getPassengerCharter())) / 10000.0; |
| | | double turn = (nz(e.getTurnoverClass1()) + nz(e.getTurnoverClass2()) + nz(e.getTurnoverClass3()) |
| | | + nz(e.getTurnoverClass4()) + nz(e.getTurnoverCharter())) / 10000.0; |
| | | addMidClass(mid, key, city, 1, pass, turn); |
| | | addMidClass(mid, key, "æ¹åç", 1, pass, turn); |
| | | } |
| | | for (CityBusMonthly b : cityBusMapper.selectList(null)) { |
| | | if (b.getReportPeriod() == null || b.getCity() == null) continue; |
| | | String[] parts = b.getReportPeriod().split("-"); |
| | | if (parts.length != 2) continue; |
| | | int y, m; |
| | | try { |
| | | y = Integer.parseInt(parts[0]); |
| | | m = Integer.parseInt(parts[1]); |
| | | } catch (NumberFormatException ex) { |
| | | continue; |
| | | } |
| | | String city = RegionUtil.normalizeCityName(b.getCity()); |
| | | if (city == null) continue; |
| | | int key = y * 100 + m; |
| | | addMidClass(mid, key, city, 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang())); |
| | | addMidClass(mid, key, "æ¹åç", 2, nz(b.getPassengerChengxiang()), nz(b.getTurnoverChengxiang())); |
| | | } |
| | | return mid; |
| | | } |
| | | |
| | | /** åå
¥åç±»å¼å¹¶ç´¯å æ»éï¼ç»´åº¦0ï¼ */ |
| | | private void addMidClass(Map<Integer, Map<String, double[][]>> mid, int key, String city, int k, double pass, double turn) { |
| | | double[][] arr = mid.computeIfAbsent(key, kk -> new HashMap<>()).computeIfAbsent(city, kk -> new double[5][2]); |
| | | arr[k][0] += pass; |
| | | arr[k][1] += turn; |
| | | arr[0][0] += pass; |
| | | arr[0][1] += turn; |
| | | } |
| | | |
| | | private int writeMidBlock(Sheet sheet, Map<Integer, Map<String, PassengerAgg>> data, int[] years, |
| | | int currentYear, int maxMonth, int startRow, String title, boolean volume) { |
| | | Row titleRow = sheet.createRow(startRow); |
| | | titleRow.createCell(0).setCellValue(title); |
| | | Row noteRow = sheet.createRow(startRow + 1); |
| | | noteRow.createCell(0).setCellValue("注ï¼ä¸å£å¾ç±å
¬è·¯ç线ãåé
å乡å
¬äº¤ãåé
å乡åºç§ãåé
å乡ç½çº¦è½¦åé¨åææ"); |
| | | Row header = sheet.createRow(startRow + 2); |
| | | header.createCell(0).setCellValue("å°åº åç§°"); |
| | | header.createCell(1).setCellValue("ææ "); |
| | | int col = 2; |
| | | for (int y : years) { |
| | | int monthCount = (y == currentYear) ? maxMonth : 12; |
| | | for (int m = 1; m <= monthCount; m++) { |
| | | header.createCell(col++).setCellValue(y + "å¹´" + m + "æ"); |
| | | header.createCell(col++).setCellValue(m + "æä¸å»å¹´åæ¯"); |
| | | } |
| | | header.createCell(col++).setCellValue(y + "年累计"); |
| | | header.createCell(col++).setCellValue("累计ä¸å»å¹´åæ¯"); |
| | | } |
| | | sheet.createRow(startRow + 3); |
| | | |
| | | String[] metrics = volume ? new String[]{ |
| | | "æ»å®¢è¿éï¼ä¸äººæ¬¡ï¼", |
| | | "å
¶ä¸ï¼å
¬è·¯ç线客è¿éï¼ä¸äººæ¬¡ï¼", |
| | | "åé
å乡å
¬äº¤å®¢è¿éï¼ä¸äººæ¬¡ï¼", |
| | | "åé
å乡巡游åºç§å®¢è¿éï¼ä¸äººæ¬¡ï¼", |
| | | "åé
å乡ç½çº¦è½¦å®¢è¿éï¼ä¸äººæ¬¡ï¼" |
| | | } : new String[]{ |
| | | "æ»æ
客å¨è½¬éï¼ä¸äººå
¬éï¼", |
| | | "å
¶ä¸ï¼å
¬è·¯ç线æ
客å¨è½¬éï¼ä¸äººå
¬éï¼", |
| | | "åé
å乡å
¬äº¤æ
客å¨è½¬éï¼ä¸äººå
¬éï¼", |
| | | "åé
å乡巡游åºç§æ
客å¨è½¬éï¼ä¸äººå
¬éï¼", |
| | | "åé
å乡ç½çº¦è½¦æ
客å¨è½¬éï¼ä¸äººå
¬éï¼" |
| | | }; |
| | | |
| | | int rowIdx = startRow + 4; |
| | | List<String> regions = new java.util.ArrayList<>(); |
| | | regions.add("æ¹åç"); |
| | | regions.addAll(RegionUtil.cityList()); |
| | | for (String region : regions) { |
| | | String label = "æ¹åç".equals(region) ? "å
¨ç" : RegionUtil.shortName(region); |
| | | for (int i = 0; i < metrics.length; i++) { |
| | | Row row = sheet.createRow(rowIdx++); |
| | | row.createCell(0).setCellValue(label); |
| | | row.createCell(1).setCellValue(metrics[i]); |
| | | col = 2; |
| | | for (int y : years) { |
| | | int monthCount = (y == currentYear) ? maxMonth : 12; |
| | | for (int m = 1; m <= monthCount; m++) { |
| | | Double cur = midMetricValue(aggOf(data, y, m, region), i, volume); |
| | | setNumeric(row, col++, cur == null ? null : round(cur, 4)); |
| | | Double base = midMetricValue(aggOf(data, y - 1, m, region), i, volume); |
| | | setNumeric(row, col++, yoy(cur, base)); |
| | | } |
| | | Double cum = midCumOf(data, y, region, monthCount, i, volume); |
| | | setNumeric(row, col++, cum == null ? null : round(cum, 4)); |
| | | Double cumBase = midCumOf(data, y - 1, region, monthCount, i, volume); |
| | | setNumeric(row, col++, yoy(cum, cumBase)); |
| | | } |
| | | } |
| | | } |
| | | return rowIdx; |
| | | } |
| | | |
| | | /** ä¸å£å¾ææ åå¼ï¼0=æ»é 1=å
¬è·¯ç线 2-4=å
¬äº¤/åºç§/ç½çº¦è½¦ï¼åå¸å®¢è¿æ¨¡åæªæ¥å
¥ï¼æç©ºï¼ */ |
| | | private Double midMetricValue(PassengerAgg agg, int metric, boolean volume) { |
| | | if (agg == null) return null; |
| | | if (volume) { |
| | | switch (metric) { |
| | | case 0: return agg.passengerTotal / 10000.0; |
| | | case 1: return agg.passengerTotal / 10000.0; |
| | | default: return null; |
| | | } |
| | | } |
| | | switch (metric) { |
| | | case 0: return agg.turnoverTotal / 10000.0; |
| | | case 1: return agg.turnoverTotal / 10000.0; |
| | | default: return null; |
| | | } |
| | | } |
| | | |
| | | private Double midCumOf(Map<Integer, Map<String, PassengerAgg>> data, int year, String region, |
| | | int monthCount, int metricIdx, boolean volume) { |
| | | double sum = 0.0; |
| | | boolean has = false; |
| | | for (int m = 1; m <= monthCount; m++) { |
| | | Double v = midMetricValue(aggOf(data, year, m, region), metricIdx, volume); |
| | | if (v != null) { |
| | | sum += v; |
| | | has = true; |
| | | } |
| | | } |
| | | return has ? sum : null; |
| | | } |
| | | |
| | | /** |
| | | * ä¸å£å¾æåï¼å·¦å=彿ï¼å®¢è¿é/å¨è½¬éåæå/忝/å¢éæåï¼ï¼å³å=1-Næç´¯è®¡ï¼åç»æï¼ |
| | | */ |
| | | /** |
| | | * çæ_ä¸å£å¾æå.xlsxï¼åå累计ï¼é 1 个æ=彿ï¼é 1-6 æ=累计ï¼ï¼RANK/SUM å
¬å¼ä¿çã |
| | | */ |
| | | public byte[] exportPassengerMidRank(String period, String mode) throws Exception { |
| | | int maxMonth = monthOf(period, mode); |
| | | int currentYear = Integer.parseInt(period.substring(0, 4)); |
| | | File template = resolvePassengerTemplate("çæ_ä¸å£å¾æå.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<Integer, Map<String, double[][]>> mid = loadMidClassMap(); |
| | | Row title = sheet.getRow(0); |
| | | if (title != null && title.getCell(0) != null) { |
| | | if (maxMonth == 12) { |
| | | title.getCell(0).setCellValue(currentYear + "å¹´1-12æå
¨çåå¸å·ç´¯è®¡å®æé路客è¿ç产æ
åµ"); |
| | | } else if (maxMonth == 1) { |
| | | title.getCell(0).setCellValue(currentYear + "å¹´1æå
¨çåå¸å·å®æé路客è¿ç产æ
åµ"); |
| | | } else { |
| | | title.getCell(0).setCellValue(currentYear + "å¹´1-" + maxMonth + "æå
¨çåå¸å·ç´¯è®¡å®æé路客è¿ç产æ
åµ"); |
| | | } |
| | | } |
| | | // å
¨çè¡ r3ï¼B3/F3 为 SUM å
¬å¼ä¿çï¼D3/H3 忝填å¼ï¼0-based å 3/7ï¼ |
| | | setValOrBlank(sheet, 2, 3, yoyOf(midCumClass(mid, currentYear, "æ¹åç", maxMonth, 0, true), |
| | | midCumClass(mid, currentYear - 1, "æ¹åç", maxMonth, 0, true))); |
| | | setValOrBlank(sheet, 2, 7, yoyOf(midCumClass(mid, currentYear, "æ¹åç", maxMonth, 0, false), |
| | | midCumClass(mid, currentYear - 1, "æ¹åç", maxMonth, 0, false))); |
| | | // å¸å·è¡ r4-20ï¼B/F 累计å¼ãD/H 忝ï¼C/E/G/I æåå
¬å¼ä¿ç |
| | | List<String> cities = RegionUtil.CITY_LIST; |
| | | for (int i = 0; i < cities.size(); i++) { |
| | | String city = cities.get(i); |
| | | int r0 = 3 + i; |
| | | double pass = midCumClass(mid, currentYear, city, maxMonth, 0, true); |
| | | double turn = midCumClass(mid, currentYear, city, maxMonth, 0, false); |
| | | double lastPass = midCumClass(mid, currentYear - 1, city, maxMonth, 0, true); |
| | | double lastTurn = midCumClass(mid, currentYear - 1, city, maxMonth, 0, false); |
| | | setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4)); |
| | | setValOrBlank(sheet, r0, 3, yoyOf(pass, lastPass)); |
| | | setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4)); |
| | | setValOrBlank(sheet, r0, 7, yoyOf(turn, lastTurn)); |
| | | } |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** 忰弿æ¸
空ï¼å
¬å¼åå
æ ¼ä¿çä¸å¨ï¼ */ |
| | | private void setValOrBlank(Sheet sheet, int r0, int c0, Double v) { |
| | | Row row = sheet.getRow(r0); |
| | | if (row == null) return; |
| | | Cell c = row.getCell(c0); |
| | | if (c == null) return; |
| | | if (c.getCellType() == CellType.FORMULA) return; |
| | | if (v == null || v == 0.0) c.setBlank(); else c.setCellValue(v); |
| | | } |
| | | |
| | | private Double yoyOf(double cur, double base) { |
| | | if (base == 0.0) return null; |
| | | return round((cur - base) / base, 4); |
| | | } |
| | | |
| | | /** ä¸å£å¾å类累计ï¼1..monthCount ææ±åï¼ */ |
| | | private double midCumClass(Map<Integer, Map<String, double[][]>> mid, int year, String area, int monthCount, int k, boolean volume) { |
| | | double sum = 0.0; |
| | | boolean has = false; |
| | | for (int m = 1; m <= monthCount; m++) { |
| | | Map<String, double[][]> mm = mid.get(year * 100 + m); |
| | | if (mm == null) continue; |
| | | double[][] arr = mm.get(area); |
| | | if (arr == null) continue; |
| | | sum += volume ? arr[k][0] : arr[k][1]; |
| | | has = true; |
| | | } |
| | | return has ? sum : 0.0; |
| | | } |
| | | |
| | | /** |
| | | * çæ_ä¸å£å¾åæ.xlsxï¼ä»¥æ ·ä¾ä¸ºåºç¨¿ï¼å¡«ç´¯è®¡å®¢è¿é/å¨è½¬é/忝ï¼å æ¯å
¬å¼ä¿çã |
| | | */ |
| | | public byte[] exportPassengerMidAnalysis(String period, String mode) throws Exception { |
| | | int maxMonth = monthOf(period, mode); |
| | | int currentYear = Integer.parseInt(period.substring(0, 4)); |
| | | File template = resolvePassengerTemplate("çæ_ä¸å£å¾åæ.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | Map<Integer, Map<String, double[][]>> mid = loadMidClassMap(); |
| | | Row title = sheet.getRow(0); |
| | | if (title != null && title.getCell(0) != null) { |
| | | title.getCell(0).setCellValue(currentYear + "å¹´" + cumRange(maxMonth) + "ä¸å£å¾å®¢è¿éåå¨è½¬é"); |
| | | } |
| | | // r3=æ» r4=ç线 r5=å
¬äº¤ r6=åºç§ r7=ç½çº¦è½¦ï¼1-basedï¼ï¼å B/C/F/G å¡«å¼ï¼D/H å æ¯å
¬å¼ä¿ç |
| | | String[] names = {"æ»å®¢è¿é", "å
¬è·¯ç线", "åé
å乡å
¬äº¤", "åé
å乡巡游åºç§", "åé
å乡ç½çº¦è½¦"}; |
| | | int[] dims = {0, 1, 2, 3, 4}; |
| | | for (int i = 0; i < names.length; i++) { |
| | | int r0 = 2 + i; |
| | | double pass = midCumClass(mid, currentYear, "æ¹åç", maxMonth, dims[i], true); |
| | | double turn = midCumClass(mid, currentYear, "æ¹åç", maxMonth, dims[i], false); |
| | | double lastPass = midCumClass(mid, currentYear - 1, "æ¹åç", maxMonth, dims[i], true); |
| | | double lastTurn = midCumClass(mid, currentYear - 1, "æ¹åç", maxMonth, dims[i], false); |
| | | setValOrBlank(sheet, r0, 1, pass == 0 ? null : round(pass, 4)); |
| | | setValOrBlank(sheet, r0, 2, yoyOf(pass, lastPass)); |
| | | setValOrBlank(sheet, r0, 5, turn == 0 ? null : round(turn, 4)); |
| | | setValOrBlank(sheet, r0, 6, yoyOf(turn, lastTurn)); |
| | | } |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | private double nvlOf(Double v) { |
| | | return v == null ? Double.NaN : v; |
| | | } |
| | | |
| | | private Double numOrNull(double v) { |
| | | return Double.isNaN(v) ? null : v; |
| | | } |
| | | |
| | | /** éåºæåï¼æ æ°æ® NaN ææåï¼ï¼è¿åå䏿 对åºå次 */ |
| | | private int[] rankValues(double[] values) { |
| | | Integer[] order = new Integer[values.length]; |
| | | for (int i = 0; i < values.length; i++) order[i] = i; |
| | | java.util.Arrays.sort(order, (a, b) -> { |
| | | boolean na = Double.isNaN(values[a]); |
| | | boolean nb = Double.isNaN(values[b]); |
| | | if (na && nb) return 0; |
| | | if (na) return 1; |
| | | if (nb) return -1; |
| | | return Double.compare(values[b], values[a]); |
| | | }); |
| | | int[] ranks = new int[values.length]; |
| | | for (int i = 0; i < order.length; i++) ranks[order[i]] = i + 1; |
| | | return ranks; |
| | | } |
| | | |
| | | // ==================== 5. çæ_è½è¿æ±æ»è¡¨ï¼H204 é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼ ==================== |
| | | |
| | | /** çæç±»åç -> å¨è½¬éåèæç®ç³»æ°ï¼åå
æ åç
¤æç®ï¼ */ |
| | | private static final Map<String, Double> ENERGY_FUEL_COEFF = new HashMap<>(); |
| | | |
| | | static { |
| | | ENERGY_FUEL_COEFF.put("01", 0.73 * 1.4714); // 汽油 |
| | | ENERGY_FUEL_COEFF.put("02", 0.86 * 1.4571); // æ´æ²¹ |
| | | ENERGY_FUEL_COEFF.put("03", 1.7572); // åç¼©å¤©ç¶æ° |
| | | ENERGY_FUEL_COEFF.put("04", 1.7572); // æ¶²åå¤©ç¶æ° |
| | | ENERGY_FUEL_COEFF.put("07", 0.1229); // çµå¨ |
| | | ENERGY_FUEL_COEFF.put("08", 0.3329 * 12.1951); // çæçµæ± ï¼æ°¢æ°ï¼ |
| | | } |
| | | |
| | | /** çæç -> æ±æ»è¡¨è¡åï¼ä¸æ¨¡æ¿è¡é¡ºåºä¸è´ï¼ */ |
| | | private static final String[][] ENERGY_FUEL_ROWS = { |
| | | {"æ´æ²¹è½¦", "02"}, |
| | | {"汽油车", "01"}, |
| | | {"æ¶²åå¤©ç¶æ°è½¦", "04"}, |
| | | {"åç¼©å¤©ç¶æ°è½¦", "03"}, |
| | | {"纯çµå¨è½¦", "07"}, |
| | | {"çæçµæ± 车", "08"} |
| | | }; |
| | | |
| | | /** |
| | | * è½è¿æ±æ»è¡¨ï¼æçæç±»ååç»æ±æ»ï¼å
¨ç + åå¸å·åä¸å¼ sheet |
| | | */ |
| | | public byte[] exportEnergySummary(String period) throws Exception { |
| | | List<EnergyVehicleQuarterly> list = energyMapper.selectList( |
| | | new LambdaQueryWrapper<EnergyVehicleQuarterly>() |
| | | .eq(EnergyVehicleQuarterly::getReportPeriod, period)); |
| | | int quarter = (parseMonth(period) + 2) / 3; |
| | | String[] QUARTER_CN = {"ä¸", "äº", "ä¸", "å"}; |
| | | String title = period.substring(0, 4) + "年第" + QUARTER_CN[quarter - 1] + "å£åº¦è½èæ±æ»æ
åµè¡¨"; |
| | | |
| | | Map<String, FuelAgg> province = new HashMap<>(); |
| | | Map<String, Map<String, FuelAgg>> cityMap = new HashMap<>(); |
| | | for (EnergyVehicleQuarterly e : list) { |
| | | String code = e.getFuelTypeCode(); |
| | | if (code == null || code.trim().isEmpty()) continue; |
| | | province.computeIfAbsent(code.trim(), k -> new FuelAgg()).add(e); |
| | | String city = RegionUtil.cityByCode(e.getRegionCode()); |
| | | if (city != null) { |
| | | cityMap.computeIfAbsent(city, k -> new HashMap<>()) |
| | | .computeIfAbsent(code.trim(), k2 -> new FuelAgg()).add(e); |
| | | } |
| | | } |
| | | |
| | | // 以 模æ¿_è½è¿æ±æ»è¡¨ .xlsx 为åºç¨¿ï¼ä¿çæ é¢/表头/åå¹¶/å
¬å¼/å宽/æ ·å¼ï¼ä»
æ¿æ¢æ°æ®åºæ°å¼ |
| | | File template = resolveAnyTemplate(energyTemplateDir, "çæ_è½è¿æ±æ»è¡¨.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet base = wb.getSheetAt(0); |
| | | base.getRow(0).getCell(0).setCellValue(title); |
| | | fillEnergyTemplateSheet(base, province); |
| | | for (String city : RegionUtil.cityList()) { |
| | | Map<String, FuelAgg> agg = cityMap.get(city); |
| | | if (agg == null || agg.isEmpty()) continue; |
| | | Sheet s = wb.cloneSheet(0); |
| | | wb.setSheetName(wb.getSheetIndex(s), RegionUtil.shortName(city) + "è½èæ±æ»"); |
| | | s.getRow(0).getCell(0).setCellValue(title); |
| | | fillEnergyTemplateSheet(s, agg); |
| | | } |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** è½èæ±æ»æ¨¡æ¿å¡«å
ï¼1-based 第3~8è¡ä¸º æ´æ²¹/汽油/æ¶²å天ç¶/å缩天ç¶/纯çµ/çæçµæ± ï¼D/J/L/M 为å
¬å¼åï¼ä»
å¡«æ°å¼å */ |
| | | private void fillEnergyTemplateSheet(Sheet sheet, Map<String, FuelAgg> data) { |
| | | for (int i = 0; i < ENERGY_FUEL_ROWS.length; i++) { |
| | | Row row = sheet.getRow(2 + i); |
| | | if (row == null) row = sheet.createRow(2 + i); |
| | | FuelAgg agg = data.get(ENERGY_FUEL_ROWS[i][1]); |
| | | if (agg == null || agg.count == 0) { |
| | | setEnergyValue(row, 1, null); |
| | | setEnergyValue(row, 2, null); |
| | | setEnergyValue(row, 4, null); |
| | | setEnergyValue(row, 5, null); |
| | | setEnergyValue(row, 6, null); |
| | | setEnergyValue(row, 7, null); |
| | | setEnergyValue(row, 8, null); |
| | | setEnergyValue(row, 10, null); |
| | | continue; |
| | | } |
| | | setEnergyValue(row, 1, (double) agg.count); |
| | | setEnergyValue(row, 2, agg.totalTonnage); |
| | | setEnergyValue(row, 4, agg.totalMileage); |
| | | setEnergyValue(row, 5, agg.loadedMileage); |
| | | setEnergyValue(row, 6, agg.emptyMileage); |
| | | setEnergyValue(row, 7, agg.freight); |
| | | setEnergyValue(row, 8, agg.turnover); |
| | | setEnergyValue(row, 10, agg.fuelConsumption); |
| | | } |
| | | } |
| | | |
| | | /** åè½èæ°å¼åï¼æå¼åå
¥ï¼æ 弿¸
ç©ºæ¨¡æ¿æ ·ä¾ï¼å
¬å¼åï¼D/J/L/Mï¼ä¸å¨ */ |
| | | private void setEnergyValue(Row row, int colIdx, Double v) { |
| | | Cell c = row.getCell(colIdx); |
| | | if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return; |
| | | if (c == null) c = row.createCell(colIdx); |
| | | if (v == null) c.setBlank(); else c.setCellValue(v); |
| | | } |
| | | |
| | | /** è½èæ±æ»ï¼æçæç±»åèå */ |
| | | private static class FuelAgg { |
| | | int count; |
| | | double totalTonnage; |
| | | double totalMileage; |
| | | double loadedMileage; |
| | | double emptyMileage; |
| | | double freight; |
| | | double turnover; |
| | | double fuelConsumption; |
| | | |
| | | void add(EnergyVehicleQuarterly e) { |
| | | count++; |
| | | totalTonnage += nz(e.getMarkedTonnage()); |
| | | totalMileage += nz(e.getTotalMileage()); |
| | | loadedMileage += nz(e.getLoadedMileage()); |
| | | emptyMileage += nz(e.getEmptyMileage()); |
| | | freight += nz(e.getFreight()); |
| | | turnover += nz(e.getTurnover()); |
| | | fuelConsumption += nz(e.getFuelConsumption()); |
| | | } |
| | | |
| | | private double nz(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | } |
| | | |
| | | // ==================== éç¨è¾
å© ==================== |
| | | |
| | | private int parseMonth(String period) { |
| | | return Integer.parseInt(period.split("-")[1]); |
| | | } |
| | | |
| | | private int monthOf(String period, String mode) { |
| | | return "year".equalsIgnoreCase(mode) ? 12 : parseMonth(period); |
| | | } |
| | | |
| | | /** 累计åºé´ææ¡ï¼1 ææ¾ç¤º "1æ"ï¼å
¶ä½æ¾ç¤º "1-Næ"ï¼é¿å
åºç° "1-1æ"ï¼ */ |
| | | private String cumRange(int month) { |
| | | return month <= 1 ? "1æ" : "1-" + month + "æ"; |
| | | } |
| | | |
| | | /** æ
客åå¸å·/ä¸å£å¾æç»æ¨¡æ¿è¡¨å¤´å¹´ä»½å¨æåï¼æ "2026å¹´1æ"â¦"2026å¹´6æ"ã"2026年累计" çå¹´ä»½æ¢ææéå¹´ */ |
| | | private void fixPassengerHeaderYear(Sheet sheet, int year) { |
| | | java.util.regex.Pattern p = java.util.regex.Pattern.compile("^\\d{4}å¹´"); |
| | | for (int r = 1; r <= 3; r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | for (Cell cell : row) { |
| | | if (cell.getCellType() != CellType.STRING) continue; |
| | | String v = cell.getStringCellValue(); |
| | | if (v == null || v.isEmpty()) continue; |
| | | String nv = p.matcher(v).replaceFirst(year + "å¹´"); |
| | | if (!nv.equals(v)) cell.setCellValue(nv); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private String pad(int m) { |
| | |
| | | sheet.setColumnWidth(1, 22 * 256); |
| | | } |
| | | |
| | | |
| | | // ==================== åå¸å®¢è¿ï¼å
¬äº¤ï¼åå¸å·æç» ==================== |
| | | |
| | | /** çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsxï¼ä»¥ åå¸å
¬äº¤_模æ¿.xlsx 为åºç¨¿ï¼ä¿çæ¨¡æ¿æ ¼å¼ä¸å
¬å¼ï¼ */ |
| | | public byte[] exportCityBusDetail(String period, String mode) throws Exception { |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | return exportCityByTemplate("导å
¥æ¨¡æ¿_åå¸å
¬äº¤.xlsx", period, mode, |
| | | loadCityBusByMonth(year + "-", month), |
| | | loadCityBusByMonth((year - 1) + "-", month)); |
| | | } |
| | | /** å è½½æå¹´ 1..limit æåå¸å
¬äº¤ä¼ä¸æ°æ®ï¼æ æ â å¸å· â [客è¿é,å¨è½¬é,åå¸å
客è¿é,åå¸å
å¨è½¬é] æ±æ»ï¼"å
¨ç" 为å
¨é¨å¸å·å计 */ |
| | | private Map<Integer, Map<String, double[]>> loadCityBusByMonth(String yearPrefix, int limit) { |
| | | Map<Integer, Map<String, double[]>> monthCity = new java.util.LinkedHashMap<>(); |
| | | List<CityBusMonthly> rows = cityBusMapper.selectList( |
| | | new LambdaQueryWrapper<CityBusMonthly>() |
| | | .likeRight(CityBusMonthly::getReportPeriod, yearPrefix)); |
| | | for (CityBusMonthly r : rows) { |
| | | int m = monthOf(r.getReportPeriod()); |
| | | if (m < 1 || m > limit) continue; |
| | | String city = r.getCity() == null ? "æªç¥" : r.getCity(); |
| | | Map<String, double[]> cityMap = monthCity.computeIfAbsent(m, k -> new HashMap<>()); |
| | | double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]); |
| | | arr[0] += nz(r.getPassengerVolume()); |
| | | arr[1] += nz(r.getTurnover()); |
| | | arr[2] += nz(r.getPassengerCity()); |
| | | arr[3] += nz(r.getTurnoverCity()); |
| | | double[] prov = cityMap.computeIfAbsent("å
¨ç", k -> new double[4]); |
| | | prov[0] += nz(r.getPassengerVolume()); |
| | | prov[1] += nz(r.getTurnover()); |
| | | prov[2] += nz(r.getPassengerCity()); |
| | | prov[3] += nz(r.getTurnoverCity()); |
| | | } |
| | | return monthCity; |
| | | } |
| | | |
| | | private int monthOf(String reportPeriod) { |
| | | if (reportPeriod == null) return 0; |
| | | String[] parts = reportPeriod.split("-"); |
| | | if (parts.length < 2) return 0; |
| | | try { |
| | | return Integer.parseInt(parts[1]); |
| | | } catch (Exception e) { |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | private double cityBusVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) { |
| | | Map<String, double[]> cityMap = monthCity.get(m); |
| | | if (cityMap == null) return 0; |
| | | double[] arr = cityMap.get(area); |
| | | if (arr == null) return 0; |
| | | return idx < arr.length ? arr[idx] : 0; |
| | | } |
| | | |
| | | // ==================== åå¸å®¢è¿ï¼å·¡æ¸¸åºç§ï¼åå¸å·æç» ==================== |
| | | |
| | | /** çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsxï¼ä»¥ 巡游åºç§_模æ¿.xlsx 为åºç¨¿ï¼ä¿çæ¨¡æ¿æ ¼å¼ä¸å
¬å¼ï¼ */ |
| | | public byte[] exportCityTaxiDetail(String period, String mode) throws Exception { |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | return exportCityByTemplate("导å
¥æ¨¡æ¿_巡游åºç§.xlsx", period, mode, |
| | | loadCityTaxiByMonth(year + "-", month), |
| | | loadCityTaxiByMonth((year - 1) + "-", month)); |
| | | } |
| | | /** å è½½æå¹´ 1..limit æå·¡æ¸¸åºç§æ°æ®ï¼æ æ â å¸å· â [客è¿é,å¨è½¬é,åå¸å
客è¿é,åå¸å
å¨è½¬é] æ±æ»ï¼"å
¨ç" 为å
¨é¨å¸å·å计 */ |
| | | private Map<Integer, Map<String, double[]>> loadCityTaxiByMonth(String yearPrefix, int limit) { |
| | | Map<Integer, Map<String, double[]>> monthCity = new java.util.LinkedHashMap<>(); |
| | | List<CityTaxiMonthly> rows = cityTaxiMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiMonthly>() |
| | | .likeRight(CityTaxiMonthly::getReportPeriod, yearPrefix)); |
| | | for (CityTaxiMonthly r : rows) { |
| | | int m = monthOf(r.getReportPeriod()); |
| | | if (m < 1 || m > limit) continue; |
| | | String city = r.getCity() == null ? "æªç¥" : r.getCity(); |
| | | Map<String, double[]> cityMap = monthCity.computeIfAbsent(m, k -> new HashMap<>()); |
| | | double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]); |
| | | arr[0] += nz(r.getPassengerVolume()); |
| | | arr[1] += nz(r.getTurnover()); |
| | | arr[2] += nz(r.getPassengerCity()); |
| | | arr[3] += nz(r.getTurnoverCity()); |
| | | double[] prov = cityMap.computeIfAbsent("å
¨ç", k -> new double[4]); |
| | | prov[0] += nz(r.getPassengerVolume()); |
| | | prov[1] += nz(r.getTurnover()); |
| | | prov[2] += nz(r.getPassengerCity()); |
| | | prov[3] += nz(r.getTurnoverCity()); |
| | | } |
| | | return monthCity; |
| | | } |
| | | |
| | | private double cityTaxiVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) { |
| | | Map<String, double[]> cityMap = monthCity.get(m); |
| | | if (cityMap == null) return 0; |
| | | double[] arr = cityMap.get(area); |
| | | if (arr == null) return 0; |
| | | return idx < arr.length ? arr[idx] : 0; |
| | | } |
| | | |
| | | // ==================== åå¸å®¢è¿ï¼è½¨é/轮渡ï¼åå¸å·æç» ==================== |
| | | |
| | | /** çæ_轨é轮渡客è¿éåå¸å·æç».xlsxï¼ä»¥ 轨éã轮渡_模æ¿.xlsx 为åºç¨¿ï¼è½¨é=å
¨ç/æ¦æ±/é»ç³ï¼è½®æ¸¡=å
¨ç/æ¦æ±ï¼ */ |
| | | public byte[] exportCityRailFerryDetail(String period, String mode) throws Exception { |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | Map<Integer, Map<String, double[]>> monthCity = loadCityRailFerryByMonth(year + "-", month); |
| | | File template = resolveTemplate("导å
¥æ¨¡æ¿_轨éã轮渡.xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | dynamicTitle(sheet, year, month); // æ 颿æ¥è¡¨æå¨æåï¼ä¸åæ»å¹´ä»½/æä»½ï¼ |
| | | // 轨éï¼å®¢è¿éè¡ 4(å
¨ç)/6(æ¦æ±)/8(é»ç³)ï¼å¨è½¬éè¡ä¸º +1ï¼ææ 0=轨é客è¿é 1=轨éå¨è½¬é |
| | | fillRailFerryBlock(sheet, monthCity, month, new String[][]{{"å
¨ç", "3"}, {"æ¦æ±å¸", "5"}, {"é»ç³å¸", "7"}}, 0); |
| | | // 轮渡ï¼å®¢è¿éè¡ 13(å
¨ç)/15(æ¦æ±)ï¼ææ 2=轮渡客è¿é 3=轮渡å¨è½¬é |
| | | fillRailFerryBlock(sheet, monthCity, month, new String[][]{{"å
¨ç", "12"}, {"æ¦æ±å¸", "14"}}, 2); |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** å è½½æå¹´ 1..limit æè½¨é/è½®æ¸¡æ°æ®ï¼æ â å¸å· â [轨é客è¿é,轨éå¨è½¬é,轮渡客è¿é,轮渡å¨è½¬é]ï¼å«"å
¨ç" */ |
| | | private Map<Integer, Map<String, double[]>> loadCityRailFerryByMonth(String yearPrefix, int limit) { |
| | | Map<Integer, Map<String, double[]>> monthCity = new java.util.LinkedHashMap<>(); |
| | | List<CityBusMonthly> rows = cityBusMapper.selectList( |
| | | new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix)); |
| | | for (CityBusMonthly r : rows) { |
| | | int m = monthOf(r.getReportPeriod()); |
| | | if (m < 1 || m > limit) continue; |
| | | String city = r.getCity() == null ? "æªç¥" : r.getCity(); |
| | | Map<String, double[]> cityMap = monthCity.computeIfAbsent(m, k -> new HashMap<>()); |
| | | double[] arr = cityMap.computeIfAbsent(city, k -> new double[4]); |
| | | arr[0] += nz(r.getRailPassengerVolume()); |
| | | arr[1] += nz(r.getRailTurnover()); |
| | | arr[2] += nz(r.getFerryPassengerVolume()); |
| | | arr[3] += nz(r.getFerryTurnover()); |
| | | double[] prov = cityMap.computeIfAbsent("å
¨ç", k -> new double[4]); |
| | | prov[0] += nz(r.getRailPassengerVolume()); |
| | | prov[1] += nz(r.getRailTurnover()); |
| | | prov[2] += nz(r.getFerryPassengerVolume()); |
| | | prov[3] += nz(r.getFerryTurnover()); |
| | | } |
| | | return monthCity; |
| | | } |
| | | |
| | | /** å¡«å
轨é/轮渡åï¼baseIdx 为客è¿éææ ï¼å¨è½¬éææ = baseIdx+1ï¼2026 æåº¦å C..Oï¼2025 å T.. æ æ°æ®åæ¸
空 */ |
| | | private void fillRailFerryBlock(Sheet sheet, Map<Integer, Map<String, double[]>> monthCity, int month, |
| | | String[][] areaRows, int baseIdx) { |
| | | for (String[] ar : areaRows) { |
| | | String area = ar[0]; |
| | | int paxRow = Integer.parseInt(ar[1]); |
| | | for (int m = 1; m <= month; m++) { |
| | | setDataCell(sheet, paxRow, 2 + (m - 1) * 2, railFerryVal(monthCity, area, m, baseIdx)); |
| | | setDataCell(sheet, paxRow + 1, 2 + (m - 1) * 2, railFerryVal(monthCity, area, m, baseIdx + 1)); |
| | | } |
| | | for (int m = 1; m <= 12; m++) { |
| | | setDataCell(sheet, paxRow, 19 + (m - 1) * 2, 0); |
| | | setDataCell(sheet, paxRow + 1, 19 + (m - 1) * 2, 0); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private double railFerryVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) { |
| | | Map<String, double[]> cityMap = monthCity.get(m); |
| | | if (cityMap == null) return 0; |
| | | double[] arr = cityMap.get(area); |
| | | if (arr == null) return 0; |
| | | return idx < arr.length ? arr[idx] : 0; |
| | | } |
| | | |
| | | /** åæ°æ®åå
æ ¼ï¼å为å
¬å¼åä¿çï¼å
¨çæ±å/æåå
¬å¼ï¼ï¼å¦åæå¼åå
¥ãæ 弿¸
ç©ºæ¨¡æ¿æ ·ä¾ */ |
| | | private void setDataCell(Sheet sheet, int rowIdx, int colIdx, double v) { |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | Cell c = row.getCell(colIdx); |
| | | if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return; |
| | | if (c == null) c = row.createCell(colIdx); |
| | | if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2)); |
| | | } |
| | | |
| | | /** æ¾å¼åå
¥ 0ï¼setDataCell 对 0 ç½®ç©ºï¼æ±æ»è¡¨ç½çº¦è½¦è¡éè¦æ¾ç¤º 0ï¼ */ |
| | | private void setZeroCell(Sheet sheet, int rowIdx, int colIdx) { |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | Cell c = row.getCell(colIdx); |
| | | if (c != null && c.getCellType() == org.apache.poi.ss.usermodel.CellType.FORMULA) return; |
| | | if (c == null) c = row.createCell(colIdx); |
| | | c.setCellValue(0.0); |
| | | } |
| | | /** éç®å
¬å¼ç¼åå¼ï¼ä¿çå
¬å¼ï¼æ±å¼å¼å¸¸å¿½ç¥ï¼ */ |
| | | private void recalc(XSSFWorkbook wb) { |
| | | try { |
| | | wb.getCreationHelper().createFormulaEvaluator().evaluateAll(); |
| | | } catch (Exception e) { |
| | | log.warn("åå¸å®¢è¿æ¨¡æ¿å
¬å¼æ±å¼å¤±è´¥: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | // ==================== åå¸å®¢è¿ åå¸å·ç´¯è®¡ï¼åå¸åå¸å·_æ¨¡æ¿ / åå¸å®¢è¿åå¸å·æç»è¡¨_模æ¿ï¼ ==================== |
| | | |
| | | /** çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsxï¼ä»¥ åå¸åå¸å·_模æ¿.xlsx / åå¸å®¢è¿åå¸å·æç»è¡¨_模æ¿.xlsx 为åºç¨¿ï¼ç½çº¦è½¦åæ æºæ°æ®çç©ºï¼ */ |
| | | public byte[] exportCityPassengerCitySum(String templateFileName, String period, String mode) throws Exception { |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | Map<String, double[]> cum = loadCityPassengerCumulative(year + "-", month); |
| | | File template = resolveTemplate(templateFileName); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | dynamicTitle(sheet, year, month); // æ 颿æ¥è¡¨æå¨æå |
| | | List<String> areas = new java.util.ArrayList<>(); |
| | | areas.add("å
¨ç"); |
| | | areas.addAll(RegionUtil.cityList()); |
| | | fillCitySumBlock(sheet, cum, areas, 4, 0); // 客è¿éåºï¼åä½ï¼ä¸äººæ¬¡ï¼POI 0 åºï¼ |
| | | fillCitySumBlock(sheet, cum, areas, 26, 1); // å¨è½¬éåºï¼åä½ï¼ä¸äººæ¬¡å
¬éï¼POI 0 åºï¼ |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** å¡«å
ä¸ä¸ªç´¯è®¡åºï¼startRow èµ·æ¯å°åº 1 è¡ï¼paxTurnIdx=0 ç¨å®¢è¿éã1 ç¨å¨è½¬é */ |
| | | private void fillCitySumBlock(Sheet sheet, Map<String, double[]> cum, List<String> areas, int startRow, int paxTurnIdx) { |
| | | int rowIdx = startRow; |
| | | for (String area : areas) { |
| | | double[] v = cum.get(area); |
| | | double bus = v == null ? 0 : v[paxTurnIdx == 0 ? 0 : 1]; |
| | | double taxi = v == null ? 0 : v[paxTurnIdx == 0 ? 2 : 3]; |
| | | double rail = v == null ? 0 : v[paxTurnIdx == 0 ? 4 : 5]; |
| | | double ferry = v == null ? 0 : v[paxTurnIdx == 0 ? 6 : 7]; |
| | | double total = bus + taxi + rail + ferry; // ç½çº¦è½¦æ æºæ°æ®ï¼ä¸è®¡ |
| | | setDataCell(sheet, rowIdx, 1, total); // B æ»ç´¯è®¡ |
| | | setDataCell(sheet, rowIdx, 6, bus); // G å
¬äº¤ |
| | | setDataCell(sheet, rowIdx, 11, taxi); // L åºç§ |
| | | setDataCell(sheet, rowIdx, 16, 0); // Q ç½çº¦è½¦ï¼æ æºæ°æ®âæ¸
ç©ºæ ·ä¾ï¼ |
| | | setDataCell(sheet, rowIdx, 21, rail); // V 轨é |
| | | setDataCell(sheet, rowIdx, 23, ferry); // X 轮渡 |
| | | // å¢éå E/J/O/T/W/Yï¼æ å»å¹´åæç´¯è®¡ â æ¸
ç©ºæ¨¡æ¿æ ·ä¾ï¼æå/å æ¯å
¬å¼ä¿çï¼ |
| | | setDataCell(sheet, rowIdx, 4, 0); |
| | | setDataCell(sheet, rowIdx, 9, 0); |
| | | setDataCell(sheet, rowIdx, 14, 0); |
| | | setDataCell(sheet, rowIdx, 19, 0); |
| | | setDataCell(sheet, rowIdx, 22, 0); |
| | | setDataCell(sheet, rowIdx, 24, 0); |
| | | rowIdx++; |
| | | } |
| | | } |
| | | |
| | | /** æå¹´ 1..limit æåå¸å®¢è¿ç´¯è®¡ï¼city â [å
¬äº¤å®¢è¿é,å
¬äº¤å¨è½¬é,åºç§å®¢è¿é,åºç§å¨è½¬é,轨é客è¿é,轨éå¨è½¬é,轮渡客è¿é,轮渡å¨è½¬é]ï¼å«"å
¨ç" */ |
| | | private Map<String, double[]> loadCityPassengerCumulative(String yearPrefix, int limit) { |
| | | Map<String, double[]> cum = new HashMap<>(); |
| | | for (CityBusMonthly r : cityBusMapper.selectList( |
| | | new LambdaQueryWrapper<CityBusMonthly>().likeRight(CityBusMonthly::getReportPeriod, yearPrefix))) { |
| | | int m = monthOf(r.getReportPeriod()); |
| | | if (m < 1 || m > limit) continue; |
| | | String city = r.getCity() == null ? "æªç¥" : r.getCity(); |
| | | double[] a = cum.computeIfAbsent(city, k -> new double[8]); |
| | | a[0] += nz(r.getPassengerVolume()); |
| | | a[1] += nz(r.getTurnover()); |
| | | a[4] += nz(r.getRailPassengerVolume()); |
| | | a[5] += nz(r.getRailTurnover()); |
| | | a[6] += nz(r.getFerryPassengerVolume()); |
| | | a[7] += nz(r.getFerryTurnover()); |
| | | double[] p = cum.computeIfAbsent("å
¨ç", k -> new double[8]); |
| | | p[0] += nz(r.getPassengerVolume()); |
| | | p[1] += nz(r.getTurnover()); |
| | | p[4] += nz(r.getRailPassengerVolume()); |
| | | p[5] += nz(r.getRailTurnover()); |
| | | p[6] += nz(r.getFerryPassengerVolume()); |
| | | p[7] += nz(r.getFerryTurnover()); |
| | | } |
| | | for (CityTaxiMonthly r : cityTaxiMapper.selectList( |
| | | new LambdaQueryWrapper<CityTaxiMonthly>().likeRight(CityTaxiMonthly::getReportPeriod, yearPrefix))) { |
| | | int m = monthOf(r.getReportPeriod()); |
| | | if (m < 1 || m > limit) continue; |
| | | String city = r.getCity() == null ? "æªç¥" : r.getCity(); |
| | | double[] a = cum.computeIfAbsent(city, k -> new double[8]); |
| | | a[2] += nz(r.getPassengerVolume()); |
| | | a[3] += nz(r.getTurnover()); |
| | | double[] p = cum.computeIfAbsent("å
¨ç", k -> new double[8]); |
| | | p[2] += nz(r.getPassengerVolume()); |
| | | p[3] += nz(r.getTurnover()); |
| | | } |
| | | return cum; |
| | | } |
| | | |
| | | // ==================== åå¸å®¢è¿ å
¨çæ±æ»ï¼å叿±æ»_模æ¿ï¼ ==================== |
| | | |
| | | /** çæ_åå¸å®¢è¿æ±æ».xlsxï¼ä»¥ å叿±æ»_模æ¿.xlsx 为åºç¨¿ï¼2026 段填åºå
1..N æç´¯è®¡ï¼åæ¯æ¸
空ï¼2025/2024 段ä¿ç模æ¿åå²åèå¼ï¼ */ |
| | | public byte[] exportCityPassengerSummary(String period, String mode) throws Exception { |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | double[] prov = loadCityPassengerCumulative(year + "-", month).getOrDefault("å
¨ç", new double[8]); |
| | | double busPax = prov[0], busTurn = prov[1]; |
| | | double taxiPax = prov[2], taxiTurn = prov[3]; |
| | | double railPax = prov[4], railTurn = prov[5]; |
| | | double ferryPax = prov[6], ferryTurn = prov[7]; |
| | | File template = resolveTemplate("导å
¥æ¨¡æ¿_å叿±æ».xlsx"); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | dynamicTitle(sheet, year, month); // æ 颿æ¥è¡¨æå¨æå |
| | | // è¡3 æ»å®¢è¿é=å
¬äº¤+åºç§è½¦(巡游+ç½çº¦è½¦)+轨é+轮渡ï¼è¡4 å
¬äº¤ï¼è¡5 åºç§è½¦=巡游+ç½çº¦è½¦ï¼è¡6 巡游ï¼è¡7 ç½çº¦è½¦ï¼è¡8 轨éï¼è¡9 轮渡 |
| | | setDataCell(sheet, 2, 1, busPax + taxiPax + railPax + ferryPax); // B3ï¼POI 0 åºè¡2ï¼ |
| | | setDataCell(sheet, 2, 3, busTurn + taxiTurn + railTurn + ferryTurn); // D3 |
| | | setDataCell(sheet, 3, 1, busPax); // B4 åå¸å
¬äº¤ |
| | | setDataCell(sheet, 3, 3, busTurn); // D4 |
| | | setDataCell(sheet, 4, 1, taxiPax); // B5 åå¸åºç§è½¦ï¼ç½çº¦è½¦æ æºæ°æ®ï¼ |
| | | setDataCell(sheet, 4, 3, taxiTurn); // D5 |
| | | setDataCell(sheet, 5, 1, taxiPax); // B6 å
¶ä¸å·¡æ¸¸åºç§ |
| | | setDataCell(sheet, 5, 3, taxiTurn); // D6 |
| | | setZeroCell(sheet, 6, 1); // B7 åå¸ç½çº¦è½¦ï¼æ¾å¼ 0ï¼setDataCell 对 0 ä¼ç½®ç©ºï¼ |
| | | setZeroCell(sheet, 6, 3); // D7 |
| | | setDataCell(sheet, 7, 1, railPax); // B8 轨é |
| | | setDataCell(sheet, 7, 3, railTurn); // D8 |
| | | setDataCell(sheet, 8, 1, ferryPax); // B9 轮渡 |
| | | setDataCell(sheet, 8, 3, ferryTurn);// D9 |
| | | for (int r = 2; r <= 8; r++) { // 忝 C/Eï¼æ å»å¹´åæ â æ¸
ç©ºæ ·ä¾ |
| | | setDataCell(sheet, r, 2, 0); |
| | | setDataCell(sheet, r, 4, 0); |
| | | } |
| | | recalc(wb); |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | // ==================== åå¸å®¢è¿ æ¥è¡¨ç³»åï¼ä¸é®æå
ï¼ ==================== |
| | | |
| | | /** çæ_åå¸å®¢è¿æ¥è¡¨ç³»å.zipï¼å
¬äº¤/åºç§/轨é轮渡æç» + åå¸å·ç´¯è®¡ + åå¸å·æç»è¡¨ + æ±æ» */ |
| | | public byte[] exportCityPassengerSeries(String period, String mode) throws Exception { |
| | | java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); |
| | | try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) { |
| | | addZipEntry(zos, "çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsx", exportCityBusDetail(period, mode)); |
| | | addZipEntry(zos, "çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsx", exportCityTaxiDetail(period, mode)); |
| | | addZipEntry(zos, "çæ_轨é轮渡客è¿éåå¸å·æç».xlsx", exportCityRailFerryDetail(period, mode)); |
| | | addZipEntry(zos, "çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsx", exportCityPassengerCitySum("导å
¥æ¨¡æ¿_åå¸åå¸å·.xlsx", period, mode)); |
| | | addZipEntry(zos, "çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx", exportCityPassengerCitySum("导å
¥æ¨¡æ¿_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx", period, mode)); |
| | | addZipEntry(zos, "çæ_åå¸å®¢è¿æ±æ».xlsx", exportCityPassengerSummary(period, mode)); |
| | | } |
| | | return bos.toByteArray(); |
| | | } |
| | | |
| | | /** çæ_è´§è¿æ¥è¡¨ç³»å.zipï¼è´§è¿éåå¸å·æç» + è´§è¿éæå + å¨è½¬éæå */ |
| | | public byte[] exportFreightSeries(String period, String mode) throws Exception { |
| | | java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); |
| | | try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) { |
| | | addZipEntry(zos, "çæ_è´§è¿éåå¸å·æç».xlsx", exportCityDetail(period, mode)); |
| | | addZipEntry(zos, "çæ_è´§è¿éæå.xlsx", exportFreightRank(period, mode)); |
| | | addZipEntry(zos, "çæ_å¨è½¬éæå.xlsx", exportTurnoverRank(period, mode)); |
| | | } |
| | | return bos.toByteArray(); |
| | | } |
| | | |
| | | /** çæ_å
¬è·¯æ
客æ¥è¡¨ç³»å.zipï¼åå¸å·æç» + ä¸å£å¾æç»/æå/åæ */ |
| | | public byte[] exportPassengerSeries(String period, String mode) throws Exception { |
| | | java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); |
| | | try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) { |
| | | addZipEntry(zos, "çæ_å
¬è·¯æ
客åå¸å·.xlsx", exportPassengerCityDetail(period, mode)); |
| | | addZipEntry(zos, "çæ_ä¸å£å¾æç».xlsx", exportPassengerMidDetail(period, mode)); |
| | | addZipEntry(zos, "çæ_ä¸å£å¾æå.xlsx", exportPassengerMidRank(period, mode)); |
| | | addZipEntry(zos, "çæ_ä¸å£å¾åæ.xlsx", exportPassengerMidAnalysis(period, mode)); |
| | | } |
| | | return bos.toByteArray(); |
| | | } |
| | | |
| | | /** çæ_æèµæ¥è¡¨ç³»å.zipï¼é¢å®æ + åäºäº + 亿å
+ ç»æµå¼ºå¿ */ |
| | | public byte[] exportInvestSeries(String period, String mode) throws Exception { |
| | | java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); |
| | | try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) { |
| | | addZipEntry(zos, "çæ_å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xls", investPlan(period, mode)); |
| | | addZipEntry(zos, "çæ_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls", investFiveYearLogistics(period, mode)); |
| | | addZipEntry(zos, "çæ_æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®.xls", investBillion(period, mode)); |
| | | addZipEntry(zos, "çæ_ç»æµå¼ºå¿äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨.xls", investCounty(period, mode)); |
| | | } |
| | | return bos.toByteArray(); |
| | | } |
| | | |
| | | /** çæ_æéæ¥è¡¨.zipï¼æ type å表æå
ï¼å端"çæéä¸"å¤éæ¶ç¨ï¼ */ |
| | | public byte[] exportSelectedSeries(String period, String mode, List<String> types) throws Exception { |
| | | java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); |
| | | try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) { |
| | | if (types != null) { |
| | | for (String t : types) { |
| | | if (t == null || t.trim().isEmpty()) continue; |
| | | byte[] data = selectedReportData(period, mode, t); |
| | | if (data != null) { |
| | | String name = REPORT_FILE_NAMES.get(t); |
| | | if (name == null) name = "çæ_" + t + ".xlsx"; |
| | | addZipEntry(zos, name, data); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return bos.toByteArray(); |
| | | } |
| | | |
| | | private byte[] selectedReportData(String period, String mode, String type) throws Exception { |
| | | switch (type) { |
| | | case "cityDetail": return exportCityDetail(period, mode); |
| | | case "freightRank": return exportFreightRank(period, mode); |
| | | case "turnoverRank": return exportTurnoverRank(period, mode); |
| | | case "passengerCityDetail": return exportPassengerCityDetail(period, mode); |
| | | case "passengerMidDetail": return exportPassengerMidDetail(period, mode); |
| | | case "passengerMidRank": return exportPassengerMidRank(period, mode); |
| | | case "passengerMidAnalysis": return exportPassengerMidAnalysis(period, mode); |
| | | case "energySummary": return exportEnergySummary(period); |
| | | case "investPlan": return investPlan(period, mode); |
| | | case "investFiveYearLogistics": return investFiveYearLogistics(period, mode); |
| | | case "investBillion": return investBillion(period, mode); |
| | | case "investCounty": return investCounty(period, mode); |
| | | case "cityBusDetail": return exportCityBusDetail(period, mode); |
| | | case "cityTaxiDetail": return exportCityTaxiDetail(period, mode); |
| | | case "cityRailFerryDetail": return exportCityRailFerryDetail(period, mode); |
| | | case "cityPassengerCitySum": return exportCityPassengerCitySum("导å
¥æ¨¡æ¿_åå¸åå¸å·.xlsx", period, mode); |
| | | case "cityPassengerDetailSum": return exportCityPassengerCitySum("导å
¥æ¨¡æ¿_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx", period, mode); |
| | | case "cityPassengerSummary": return exportCityPassengerSummary(period, mode); |
| | | default: return null; |
| | | } |
| | | } |
| | | |
| | | private static final Map<String, String> REPORT_FILE_NAMES = new HashMap<>(); |
| | | static { |
| | | REPORT_FILE_NAMES.put("cityDetail", "çæ_è´§è¿éåå¸å·æç».xlsx"); |
| | | REPORT_FILE_NAMES.put("freightRank", "çæ_è´§è¿éæå.xlsx"); |
| | | REPORT_FILE_NAMES.put("turnoverRank", "çæ_å¨è½¬éæå.xlsx"); |
| | | REPORT_FILE_NAMES.put("passengerCityDetail", "çæ_å
¬è·¯æ
客åå¸å·æç».xlsx"); |
| | | REPORT_FILE_NAMES.put("passengerMidDetail", "çæ_å
¬è·¯æ
客ä¸å£å¾æç».xlsx"); |
| | | REPORT_FILE_NAMES.put("passengerMidRank", "çæ_å
¬è·¯æ
客ä¸å£å¾æå.xlsx"); |
| | | REPORT_FILE_NAMES.put("passengerMidAnalysis", "çæ_å
¬è·¯æ
客ä¸å£å¾åæ.xlsx"); |
| | | REPORT_FILE_NAMES.put("energySummary", "çæ_è½è¿æ±æ»è¡¨.xlsx"); |
| | | REPORT_FILE_NAMES.put("investPlan", "çæ_å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xls"); |
| | | REPORT_FILE_NAMES.put("investFiveYearLogistics", "çæ_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls"); |
| | | REPORT_FILE_NAMES.put("investBillion", "çæ_æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®.xls"); |
| | | REPORT_FILE_NAMES.put("investCounty", "çæ_ç»æµå¼ºå¿äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨.xls"); |
| | | REPORT_FILE_NAMES.put("cityBusDetail", "çæ_åå¸å
¬äº¤å®¢è¿éåå¸å·æç».xlsx"); |
| | | REPORT_FILE_NAMES.put("cityTaxiDetail", "çæ_巡游åºç§å®¢è¿éåå¸å·æç».xlsx"); |
| | | REPORT_FILE_NAMES.put("cityRailFerryDetail", "çæ_轨é轮渡客è¿éåå¸å·æç».xlsx"); |
| | | REPORT_FILE_NAMES.put("cityPassengerCitySum", "çæ_åå¸å®¢è¿å®¢è¿éåå¸å·ç´¯è®¡.xlsx"); |
| | | REPORT_FILE_NAMES.put("cityPassengerDetailSum", "çæ_åå¸å®¢è¿åå¸å·æç»è¡¨.xlsx"); |
| | | REPORT_FILE_NAMES.put("cityPassengerSummary", "çæ_åå¸å®¢è¿æ±æ».xlsx"); |
| | | } |
| | | |
| | | private void addZipEntry(java.util.zip.ZipOutputStream zos, String name, byte[] data) throws Exception { |
| | | zos.putNextEntry(new java.util.zip.ZipEntry(name)); |
| | | zos.write(data); |
| | | zos.closeEntry(); |
| | | } |
| | | |
| | | /** åå¸å®¢è¿æ¨¡æ¿å¡«å
ï¼ä»¥ç®å½ä¸æ¨¡æ¿ä¸ºåºç¨¿ï¼ä¿çæ é¢/表头/åå¹¶/å
¬å¼/æ ·å¼ï¼ä»
æ¿æ¢æ°æ®åºå¸å·æåº¦å¼ */ |
| | | public byte[] exportCityByTemplate(String templateFileName, String period, String mode, |
| | | Map<Integer, Map<String, double[]>> monthCity, |
| | | Map<Integer, Map<String, double[]>> lastYearMonthCity) throws Exception { |
| | | int month = monthOf(period, mode); |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | File template = resolveTemplate(templateFileName); |
| | | try (InputStream in = new FileInputStream(template); |
| | | XSSFWorkbook wb = new XSSFWorkbook(in)) { |
| | | Sheet sheet = wb.getSheetAt(0); |
| | | dynamicTitle(sheet, year, month); // æ 颿æ¥è¡¨æå¨æåï¼ä¸åæ»å¹´ä»½/æä»½ï¼ |
| | | // ç»æå®å«ï¼æ°æ®åºåºä¸º å
¨ç+17å¸å· à 4 ææ = 72 è¡ï¼1-based 第5è¡èµ·ï¼ |
| | | if (sheet.getLastRowNum() < 75) { |
| | | throw new RuntimeException("åå¸å®¢è¿æ¨¡æ¿æ°æ®åºè¡æ°ä¸è¶³ï¼è¯·ç¡®è®¤æ¨¡æ¿æªæ¹ç: " + templateFileName); |
| | | } |
| | | List<String> areas = new java.util.ArrayList<>(); |
| | | areas.add("å
¨ç"); |
| | | areas.addAll(RegionUtil.cityList()); |
| | | // æ¨¡æ¿æ°æ®åºï¼1-based 第5è¡èµ·ï¼æ¯å°åº4è¡ï¼å®¢è¿é/å¨è½¬é/åå¸å
客è¿é/åå¸å
å¨è½¬éï¼ |
| | | int rowIdx = 4; |
| | | for (int a = 0; a < areas.size(); a++) { |
| | | String area = areas.get(a); |
| | | for (int k = 0; k < 4; k++) { |
| | | Row row = sheet.getRow(rowIdx); |
| | | if (row == null) row = sheet.createRow(rowIdx); |
| | | if (a > 0) { |
| | | // 2026å¹´ 1..N æï¼åºå
æå¼åå¡«ï¼2ä½å°æ°ï¼ï¼æ 弿¸
ç©ºæ¨¡æ¿æ ·ä¾ |
| | | for (int m = 1; m <= month; m++) { |
| | | double v = cityVal(monthCity, area, m, k); |
| | | Cell c = row.getCell(2 + (m - 1) * 2); |
| | | if (c == null) c = row.createCell(2 + (m - 1) * 2); |
| | | if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2)); |
| | | } |
| | | // 2025å¹´ 1..12 æï¼åºå
æå»å¹´æ°æ®åå¡«ï¼å¦åæ¸
ç©ºæ¨¡æ¿æ ·ä¾ |
| | | for (int m = 1; m <= 12; m++) { |
| | | double v = cityVal(lastYearMonthCity, area, m, k); |
| | | Cell c = row.getCell(19 + (m - 1) * 2); |
| | | if (c == null) c = row.createCell(19 + (m - 1) * 2); |
| | | if (v == 0) c.setBlank(); else c.setCellValue(round(v, 2)); |
| | | } |
| | | } |
| | | rowIdx++; |
| | | } |
| | | } |
| | | // éç®å
¬å¼ç¼åå¼ï¼å
¨çæ±å/忝/累计ï¼ä¿çå
¬å¼ï¼æ±å¼å¼å¸¸å¿½ç¥ï¼ |
| | | try { |
| | | wb.getCreationHelper().createFormulaEvaluator().evaluateAll(); |
| | | } catch (Exception e) { |
| | | log.warn("åå¸å®¢è¿æ¨¡æ¿å
¬å¼æ±å¼å¤±è´¥: {}", e.getMessage()); |
| | | } |
| | | return toBytes(wb); |
| | | } |
| | | } |
| | | |
| | | /** æ¨¡æ¿æä»¶å®ä½ï¼é
ç½®ç®å½ â user.dir â user.dir/.. é级åé */ |
| | | private File resolveTemplate(String fileName) throws Exception { |
| | | return resolveAnyTemplate(templateDir, fileName); |
| | | } |
| | | |
| | | /** å
¬è·¯æ
客è¾åºæ¨¡æ¿å®ä½ï¼docs/å
¬è·¯æ
客+è½è/è¾åºï¼ */ |
| | | private File resolvePassengerTemplate(String fileName) throws Exception { |
| | | return resolveAnyTemplate(passengerTemplateDir, fileName); |
| | | } |
| | | |
| | | /** éç¨æ¨¡æ¿æä»¶å®ä½ï¼ç¨äºåå¸å®¢è¿/è½èçä¸å模æ¿ç®å½ï¼ */ |
| | | private File resolveAnyTemplate(String dir, String fileName) throws Exception { |
| | | String rel = dir; |
| | | while (rel.startsWith("./")) rel = rel.substring(2); |
| | | String[] roots = { |
| | | dir, |
| | | System.getProperty("user.dir") + "/" + rel, |
| | | System.getProperty("user.dir") + "/../" + rel |
| | | }; |
| | | for (String root : roots) { |
| | | if (root == null || root.trim().isEmpty()) continue; |
| | | File f = new File(root, fileName); |
| | | if (f.exists() && f.isFile()) return f; |
| | | } |
| | | throw new RuntimeException("æªæ¾å°æ¨¡æ¿æä»¶: " + fileName); |
| | | } |
| | | |
| | | /** åå¸å®¢è¿æ¨¡æ¿æ é¢å¨æåï¼æ«æå
¨é¨ææ¬åå
æ ¼ï¼æ¿æ¢åæ»ç年份ä¸ç´¯è®¡åºé´ï¼å¦ "2026å¹´1-6æ" â "2027å¹´1-8æ"ï¼ï¼åæ é¢ "2026å¹´1æ" åªæ¢å¹´ä»½ï¼"1-12æ" æ´å¹´å¯¹æ¯åï¼2024/2025ï¼æ é¢ä¿æåæ · */ |
| | | private void dynamicTitle(Sheet sheet, int year, int month) { |
| | | java.util.regex.Pattern yearP = java.util.regex.Pattern.compile("\\d{4}å¹´"); |
| | | java.util.regex.Pattern rangeP = java.util.regex.Pattern.compile("(\\d+)-(\\d+)æ"); |
| | | String cum = cumRange(month); |
| | | for (int r = 0; r <= sheet.getLastRowNum(); r++) { |
| | | Row row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | for (Cell cell : row) { |
| | | if (cell.getCellType() != CellType.STRING) continue; |
| | | String v = cell.getStringCellValue(); |
| | | if (v == null || v.isEmpty() || v.contains("1-12æ")) continue; // æ´å¹´å¯¹æ¯åæ é¢ä¿æåæ · |
| | | String nv = yearP.matcher(v).replaceAll(year + "å¹´"); |
| | | nv = rangeP.matcher(nv).replaceAll(java.util.regex.Matcher.quoteReplacement(cum)); |
| | | if (!nv.equals(v)) cell.setCellValue(nv); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** åå¸å®¢è¿åå¸å·åå¼ï¼æ â å¸å· â ææ æ°ç»ï¼ */ |
| | | private double cityVal(Map<Integer, Map<String, double[]>> monthCity, String area, int m, int idx) { |
| | | Map<String, double[]> cityMap = monthCity.get(m); |
| | | if (cityMap == null) return 0; |
| | | double[] arr = cityMap.get(area); |
| | | if (arr == null) return 0; |
| | | return idx < arr.length ? arr[idx] : 0; |
| | | } |
| | | |
| | | // ==================== æèµæ¨¡å 4 å¼ é¢å¯¼æ¥è¡¨ï¼æ¨¡æ¿åºç¨¿çæï¼è¾åº .xlsï¼ ==================== |
| | | |
| | | /** å è½½æèµ .xls 模æ¿ï¼HSSFWorkbookï¼ä¿çå宽/表头/æ ·å¼/åå¹¶ï¼ */ |
| | | private HSSFWorkbook loadInvestTemplate(String fileName) throws Exception { |
| | | File f = resolveAnyTemplate(investTemplateDir, fileName); |
| | | try (InputStream in = new FileInputStream(f)) { |
| | | return new HSSFWorkbook(in); |
| | | } |
| | | } |
| | | |
| | | /** 模æ¿åå
æ ¼åææ¬ï¼å
æ¸
空ï¼ä¿çæ ·å¼/åå¹¶ï¼ååï¼null/空ç½ç空 */ |
| | | private void hssfText(Row row, int idx, String v) { |
| | | Cell c = row.getCell(idx); |
| | | if (c == null) c = row.createCell(idx); |
| | | c.setBlank(); |
| | | if (v != null && !v.trim().isEmpty()) c.setCellValue(v.trim()); |
| | | } |
| | | |
| | | /** 模æ¿åå
æ ¼åæ°å¼ï¼å
æ¸
空ï¼ä¿çæ ·å¼/åå¹¶ï¼åå */ |
| | | private void hssfNum(Row row, int idx, Double v) { |
| | | Cell c = row.getCell(idx); |
| | | if (c == null) c = row.createCell(idx); |
| | | c.setBlank(); |
| | | if (v != null) c.setCellValue(v); |
| | | } |
| | | |
| | | /** å 餿°æ®åºå¤ä½è¡ï¼fromRow..lastKeepRow-1ï¼ï¼å
ç§»é¤åå¹¶åå
æ ¼åå è¡è®°å½ï¼è¡¨å¤´åå¹¶ä¸åå½±å */ |
| | | private void trimHssfTail(HSSFSheet sheet, int fromRow, int lastKeepRow) { |
| | | for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) { |
| | | CellRangeAddress m = sheet.getMergedRegion(i); |
| | | if (m.getFirstRow() >= fromRow && m.getFirstRow() < lastKeepRow) { |
| | | sheet.removeMergedRegion(i); |
| | | } |
| | | } |
| | | for (int r = lastKeepRow - 1; r >= fromRow; r--) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row != null) sheet.removeRow(row); |
| | | } |
| | | } |
| | | |
| | | private void trimHssfTail(HSSFSheet sheet, int fromRow) { |
| | | trimHssfTail(sheet, fromRow, sheet.getLastRowNum() + 1); |
| | | } |
| | | |
| | | /** æ°å»ºè¡å¹¶å¤å¶æ¨¡æ¿è¡æ ·å¼ï¼å«è¡é«ï¼ï¼ä¿è¯ä¸æ¨¡æ¿æ°æ®åºå¤è§ä¸è´ */ |
| | | private HSSFRow createRowLike(HSSFSheet sheet, int rowIdx, HSSFRow styleRow) { |
| | | HSSFRow row = sheet.createRow(rowIdx); |
| | | if (styleRow != null) { |
| | | row.setHeight(styleRow.getHeight()); |
| | | for (int c = 0; c < styleRow.getLastCellNum(); c++) { |
| | | HSSFCell sc = styleRow.getCell(c); |
| | | if (sc == null) continue; |
| | | row.createCell(c).setCellStyle(sc.getCellStyle()); |
| | | } |
| | | } |
| | | return row; |
| | | } |
| | | |
| | | /** æ·»å åè¡åå¹¶åå
æ ¼ï¼ä¸æ¨¡æ¿æ°æ®åºåå¹¶è§åä¸è´ï¼ */ |
| | | private void addMerge(HSSFSheet sheet, int r, int firstCol, int lastCol) { |
| | | sheet.addMergedRegion(new CellRangeAddress(r, r, firstCol, lastCol)); |
| | | } |
| | | |
| | | /** è§£æ 4 ä½å¹´ä»½ä¸ºæ°å¼ï¼å模æ¿å¹´ä»½åï¼ï¼æ æ³è§£æè¿å null */ |
| | | private Double parseYearNum(String t) { |
| | | if (t == null || t.trim().isEmpty()) return null; |
| | | String s = t.trim().substring(0, Math.min(4, t.trim().length())); |
| | | try { |
| | | return Double.parseDouble(s); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** è§£æ 6 ä½ YYYYMM 为æ°å¼ï¼å模æ¿å¼å·¥æ¶é´åï¼ */ |
| | | private Double parseTimeYMNum(String t) { |
| | | if (t == null || t.trim().isEmpty()) return null; |
| | | String x = padTimeYM(t.trim()).replaceAll("[^0-9]", ""); |
| | | if (x.length() < 6) return null; |
| | | try { |
| | | return Double.parseDouble(x.substring(0, 6)); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private byte[] toBytesHssf(HSSFWorkbook wb) throws Exception { |
| | | try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { |
| | | wb.write(out); |
| | | wb.close(); |
| | | return out.toByteArray(); |
| | | } |
| | | } |
| | | |
| | | /** æèµæ¨¡æ¿ å¸å·å°èç»æï¼è¡¨1/表2 卿æè¡ç¨ï¼ */ |
| | | private static class InvestSection { |
| | | String blockCategory; |
| | | String city; |
| | | int startRow; |
| | | int styleRowIdx = -1; // 该å°èé¦ä¸ªé¡¹ç®è¡ï¼å¤å¶æ ·å¼ç¨ï¼ |
| | | List<Integer> projectRows = new ArrayList<>(); |
| | | Set<Long> matched = new HashSet<>(); |
| | | } |
| | | |
| | | /** å¨ pos è¡ä¹åæå
¥ n è¡ï¼ä¸æ¹è¡ä¸ç§»ï¼åå¹¶åºå
æé¤åææ°è¡å·æ¢å¤ï¼ï¼æ°è¡å¤å¶ styleRow æ ·å¼/è¡é«/åè¡åå¹¶ï¼è¿åé¦ä¸ªæ°è¡ç´¢å¼ */ |
| | | private int insertRowsDown(HSSFSheet sheet, int pos, int n, HSSFRow styleRow) { |
| | | if (n <= 0) return -1; |
| | | List<CellRangeAddress> below = new ArrayList<>(); |
| | | for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) { |
| | | CellRangeAddress m = sheet.getMergedRegion(i); |
| | | if (m.getFirstRow() > pos) { |
| | | below.add(m); |
| | | sheet.removeMergedRegion(i); |
| | | } |
| | | } |
| | | int last = sheet.getLastRowNum(); |
| | | if (pos < last) { |
| | | sheet.shiftRows(pos + 1, last, n, true, false); |
| | | } |
| | | for (CellRangeAddress m : below) { |
| | | sheet.addMergedRegion(new CellRangeAddress(m.getFirstRow() + n, m.getLastRow() + n, m.getFirstColumn(), m.getLastColumn())); |
| | | } |
| | | int first = pos + 1; |
| | | for (int i = 0; i < n; i++) { |
| | | HSSFRow row = createRowLike(sheet, first + i, styleRow); |
| | | if (styleRow != null) copyRowMerges(sheet, styleRow.getRowNum(), first + i); |
| | | } |
| | | return first; |
| | | } |
| | | |
| | | /** å¤å¶ styleRow æå¨è¡çåè¡åå¹¶åºå° toRowï¼æå
¥è¡å¤è§ä¸æ¨¡æ¿è¡ä¸è´ï¼ */ |
| | | private void copyRowMerges(HSSFSheet sheet, int fromRow, int toRow) { |
| | | for (int i = 0; i < sheet.getNumMergedRegions(); i++) { |
| | | CellRangeAddress m = sheet.getMergedRegion(i); |
| | | if (m.getFirstRow() == fromRow && m.getLastRow() == fromRow) { |
| | | sheet.addMergedRegion(new CellRangeAddress(toRow, toRow, m.getFirstColumn(), m.getLastColumn())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** æèµè¡¨ åºå·éæï¼è¡¨1 æåéç½®ã表2 æå¸å·å°èéç½®ã表4 å
¨è¡¨è¿ç»ï¼æ¨¡æ¿/æå
¥è¡ç»ä¸æåºï¼ */ |
| | | private void renumberInvestSeq(HSSFSheet sheet, String kind) { |
| | | int seq = 0; |
| | | int lastRow = sheet.getLastRowNum(); |
| | | for (int r = 4; r <= lastRow; r++) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = nvl(cellText(row.getCell(0))); |
| | | if ("investPlan".equals(kind)) { |
| | | if ("ä¸ã客è¿ç«åºåºç¡è®¾æ½å»ºè®¾".equals(a) || "äºã交éç©æµåºç¡è®¾æ½å»ºè®¾".equals(a) || "ä¸ãèæ§è¥è¿è´§è½¦æ¥åºæ´æ°".equals(a)) { |
| | | seq = 0; |
| | | continue; |
| | | } |
| | | } else if ("investFiveYear".equals(kind)) { |
| | | if (isCityLabel(a)) { |
| | | seq = 0; |
| | | continue; |
| | | } |
| | | } |
| | | String b = nvl(cellText(row.getCell(1))); |
| | | if (b.isEmpty()) continue; |
| | | hssfNum(row, 0, (double) (++seq)); |
| | | } |
| | | } |
| | | |
| | | /** 表1 项ç®è¡å¡«å
ï¼æ¨¡æ¿è¡/æå
¥è¡å
±ç¨ï¼cityText 䏿¨¡æ¿ B åæ ¼å¼ä¸è´ï¼ */ |
| | | private void fillInvestPlanProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq, String cityText) { |
| | | hssfNum(row, 0, (double) seq); |
| | | hssfText(row, 1, cityText); |
| | | hssfText(row, 2, valueOf(p.getCounty())); |
| | | hssfText(row, 3, p.getProjectName()); |
| | | hssfText(row, 4, valueOf(p.getConstructNature())); |
| | | hssfNum(row, 5, parseYearNum(p.getStartTime())); |
| | | hssfNum(row, 6, parseYearNum(p.getEndTime())); |
| | | hssfNum(row, 7, p.getTotalInvestment()); |
| | | hssfText(row, 20, p.getApprovalGk()); |
| | | hssfText(row, 21, p.getApprovalCs()); |
| | | if (m != null) { |
| | | hssfNum(row, 8, m.getStartCum()); |
| | | hssfNum(row, 9, m.getYearPlan()); |
| | | hssfText(row, 13, m.getProgressDesc()); |
| | | hssfNum(row, 22, m.getYearCum()); |
| | | hssfText(row, 23, valueOf(m.getProgressStage())); |
| | | } |
| | | } |
| | | |
| | | /** 表2 项ç®è¡å¡«å
ï¼æ¨¡æ¿è¡/æå
¥è¡å
±ç¨ï¼ */ |
| | | private void fillFiveYearProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq) { |
| | | hssfNum(row, 0, (double) seq); |
| | | hssfText(row, 1, p.getProjectName()); |
| | | hssfText(row, 2, valueOf(p.getBuilderName())); |
| | | hssfNum(row, 3, parseYearNum(p.getStartTime())); |
| | | hssfNum(row, 4, parseYearNum(p.getEndTime())); |
| | | hssfNum(row, 5, p.getTotalInvestment()); |
| | | if (m != null) { |
| | | hssfNum(row, 6, m.getStartCum()); |
| | | hssfNum(row, 7, m.getYearCum()); |
| | | hssfText(row, 8, valueOf(m.getProgressStage())); |
| | | hssfText(row, 9, m.getProgressDesc()); |
| | | } |
| | | } |
| | | |
| | | /** 表4 项ç®è¡å¡«å
ï¼æ¨¡æ¿è¡/æå
¥è¡å
±ç¨ï¼ */ |
| | | private void fillCountyProjectRow(HSSFRow row, InvestmentProject p, InvestmentMonthly m, int seq) { |
| | | hssfNum(row, 0, (double) seq); |
| | | hssfText(row, 1, p.getProjectName()); |
| | | hssfText(row, 2, valueOf(p.getBuilderName())); |
| | | hssfNum(row, 3, p.getTotalInvestment()); |
| | | if (m != null) { |
| | | hssfNum(row, 4, m.getStartCum()); |
| | | hssfNum(row, 5, m.getYearPlan()); |
| | | hssfNum(row, 6, m.getYearCum()); |
| | | hssfNum(row, 7, m.getMonthDone()); |
| | | hssfText(row, 8, valueOf(m.getProgressStage())); |
| | | hssfText(row, 9, m.getProgressDesc()); |
| | | if (m.getBuildingArea() != null && m.getBuildingArea() > 0) hssfNum(row, 12, m.getBuildingArea()); |
| | | } |
| | | hssfText(row, 15, p.getApprovalGk()); |
| | | hssfText(row, 16, p.getApprovalCs()); |
| | | } |
| | | |
| | | /** 表1ï¼2026å¹´å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ»ï¼æ¨¡æ¿åºç¨¿å使¿æ¢ï¼ä¿çå
¨é¨è¡/åå¹¶/å宽ï¼ä»
æ¿æ¢æ°æ®ï¼DB 项ç®å¤äºæ¨¡æ¿é¢çè¡æ¶å¨ææè¡ï¼ */ |
| | | public byte[] investPlan(String period, String mode) throws Exception { |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period)); |
| | | Map<Long, InvestmentProject> pmap = new HashMap<>(); |
| | | List<InvestmentProject> projects = investProjectMapper.selectList(null); |
| | | for (InvestmentProject p : projects) pmap.put(p.getId(), p); |
| | | Map<Long, InvestmentMonthly> mByProject = new HashMap<>(); |
| | | for (InvestmentMonthly m : monthlies) mByProject.put(m.getProjectId(), m); |
| | | |
| | | HSSFWorkbook wb = loadInvestTemplate("模æ¿_å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xls"); |
| | | HSSFSheet sheet = wb.getSheetAt(0); |
| | | // æ é¢/表头年份ä¸ç´¯è®¡æä»½ |
| | | hssfText(sheet.getRow(1), 0, year + "å¹´å
¨ç客货è¿ç«åºå»ºè®¾é¢å®æè®¡å表"); |
| | | hssfText(sheet.getRow(2), 0, "èè²é¡¹ç®ä¸º" + year + "å¹´å¸å·æç³æ¥èµéé¡¹ç® åä½ï¼ä¸å
"); |
| | | hssfText(sheet.getRow(3), 22, cumRange(month) + "ç´¯è®¡å®ææèµ"); |
| | | // ç»ææ«æï¼å(客è¿/ç©æµ/èæ§) -> å¸å·å°è -> 项ç®è¡ |
| | | List<InvestSection> sections = new ArrayList<>(); |
| | | String blockCategory = null; |
| | | InvestSection cur = null; |
| | | int firstProjectRow = -1; |
| | | int lastRow = sheet.getLastRowNum(); |
| | | for (int r = 5; r <= lastRow; r++) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = nvl(cellText(row.getCell(0))); |
| | | String b = nvl(cellText(row.getCell(1))); |
| | | String d = nvl(cellText(row.getCell(3))); |
| | | if (r == 5) continue; // å
¨çå计è¡åç¬å¤ç |
| | | if ("ä¸ã客è¿ç«åºåºç¡è®¾æ½å»ºè®¾".equals(a) || "äºã交éç©æµåºç¡è®¾æ½å»ºè®¾".equals(a) || "ä¸ãèæ§è¥è¿è´§è½¦æ¥åºæ´æ°".equals(a)) { |
| | | blockCategory = a.startsWith("ä¸ã") ? "客è¿ç«åº" : (a.startsWith("äºã") ? "ç©æµååº" : null); |
| | | cur = null; |
| | | continue; |
| | | } |
| | | if (a.startsWith("ä¸ã") || a.startsWith("äºã")) continue; // é¢å®æè®¡åå
/å¤ åç»æ é¢ï¼å½å½åå°è |
| | | if (b.isEmpty() && d.isEmpty()) { |
| | | cur = new InvestSection(); |
| | | cur.blockCategory = blockCategory; |
| | | cur.city = templateCityFull(a); |
| | | cur.startRow = r; |
| | | sections.add(cur); |
| | | continue; |
| | | } |
| | | if (cur != null) { |
| | | cur.projectRows.add(r); |
| | | if (cur.styleRowIdx < 0) cur.styleRowIdx = r; |
| | | if (firstProjectRow < 0) firstProjectRow = r; |
| | | } |
| | | } |
| | | HSSFRow globalStyle = firstProjectRow >= 0 ? sheet.getRow(firstProjectRow) : null; |
| | | // åä½éè¡æ¿æ¢ï¼å计/åæ é¢/å¸å·å°è/åç»/项ç®è¡ï¼æªå¹é
çæ¨¡æ¿è¡æ¸
ç©ºæ ·ä¾æ°æ®ï¼ç»æ/åå¹¶/å宽ä¸åï¼ |
| | | int seq = 0; |
| | | int si = -1; |
| | | for (int r = 5; r <= lastRow; r++) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = nvl(cellText(row.getCell(0))); |
| | | String b = nvl(cellText(row.getCell(1))); |
| | | String d = nvl(cellText(row.getCell(3))); |
| | | if (si + 1 < sections.size() && r >= sections.get(si + 1).startRow) si++; |
| | | InvestSection sec = si >= 0 && si < sections.size() ? sections.get(si) : null; |
| | | if (r == 5) { // å
¨çåè®¡è¡ |
| | | blankInvestRow(row, 25); |
| | | hssfText(row, 0, "å计"); |
| | | InvestAgg prov = new InvestAgg(); |
| | | for (InvestmentMonthly m : monthlies) prov.add(m, pmap.get(m.getProjectId())); |
| | | writeInvestAggHssf(row, prov); |
| | | continue; |
| | | } |
| | | if ("ä¸ã客è¿ç«åºåºç¡è®¾æ½å»ºè®¾".equals(a) || "äºã交éç©æµåºç¡è®¾æ½å»ºè®¾".equals(a)) { |
| | | blockCategory = a.startsWith("ä¸ã") ? "客è¿ç«åº" : "ç©æµååº"; |
| | | blankInvestRow(row, 25); |
| | | hssfText(row, 0, a); |
| | | InvestAgg agg = new InvestAgg(); |
| | | for (InvestmentMonthly m : monthlies) { |
| | | InvestmentProject p = pmap.get(m.getProjectId()); |
| | | if (p != null && blockCategory.equals(p.getCategory())) agg.add(m, p); |
| | | } |
| | | writeInvestAggHssf(row, agg); |
| | | seq = 0; |
| | | continue; |
| | | } |
| | | if ("ä¸ãèæ§è¥è¿è´§è½¦æ¥åºæ´æ°".equals(a)) { |
| | | blockCategory = null; // ææ æ°æ®æºï¼æ´åæ¸
空 |
| | | blankInvestRow(row, 25); |
| | | hssfText(row, 0, a); |
| | | seq = 0; |
| | | continue; |
| | | } |
| | | if (a.startsWith("ä¸ã") || a.startsWith("äºã")) { |
| | | // é¢å®æè®¡åå
/å¤ åç»æ é¢è¡ï¼æ æ°æ®ï¼ä»
ä¿çæ ç¾ |
| | | blankInvestRow(row, 25); |
| | | hssfText(row, 0, a); |
| | | continue; |
| | | } |
| | | if (b.isEmpty() && d.isEmpty()) { |
| | | // å¸å·å°èè¡ï¼éç®è¯¥å¸å·ï¼å½ååç±»å«ï¼å计 |
| | | String city = templateCityFull(a); |
| | | blankInvestRow(row, 25); |
| | | hssfText(row, 0, a); |
| | | InvestAgg agg = new InvestAgg(); |
| | | for (InvestmentMonthly m : monthlies) { |
| | | InvestmentProject p = pmap.get(m.getProjectId()); |
| | | if (p != null && city.equals(p.getCity()) |
| | | && (blockCategory == null || blockCategory.equals(p.getCategory()))) agg.add(m, p); |
| | | } |
| | | writeInvestAggHssf(row, agg); |
| | | continue; |
| | | } |
| | | // 项ç®è¡ï¼æ 模æ¿åç§°+å¸å·+åç±»å« å¹é
DB 项ç®ï¼æªå¹é
æ´è¡æ¸
空 |
| | | blankInvestRow(row, 25); |
| | | if (blockCategory == null) continue; // èæ§è´§è½¦æ®µæ æ°æ®æº |
| | | InvestmentProject p = matchInvest(projects, d, b, blockCategory); |
| | | if (p == null) continue; |
| | | if (sec != null) sec.matched.add(p.getId()); |
| | | InvestmentMonthly m = mByProject.get(p.getId()); |
| | | fillInvestPlanProjectRow(row, p, m, ++seq, b); |
| | | } |
| | | // 卿æè¡ï¼DB é¡¹ç®æ° > 模æ¿é¢çè¡æ°æ¶ï¼å¨å¸å·å°èæ«å°¾è¡¥é½ï¼èªåºå䏿è¡ï¼é¿å
è¡å·éä½ï¼ |
| | | for (int i = sections.size() - 1; i >= 0; i--) { |
| | | InvestSection s = sections.get(i); |
| | | if (s.blockCategory == null || s.city == null) continue; // èæ§è´§è½¦æ®µæ æ°æ®æº |
| | | List<InvestmentProject> extra = new ArrayList<>(); |
| | | for (InvestmentProject p : projects) { |
| | | if (s.blockCategory.equals(p.getCategory()) |
| | | && s.city.equals(RegionUtil.normalizeCityName(p.getCity())) |
| | | && !s.matched.contains(p.getId())) extra.add(p); |
| | | } |
| | | if (extra.isEmpty()) continue; |
| | | HSSFRow styleRow = s.styleRowIdx >= 0 ? sheet.getRow(s.styleRowIdx) : globalStyle; |
| | | int pos = s.projectRows.isEmpty() ? s.startRow : s.projectRows.get(s.projectRows.size() - 1); |
| | | int first = insertRowsDown(sheet, pos, extra.size(), styleRow); |
| | | for (int j = 0; j < extra.size(); j++) { |
| | | 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()); |
| | | } |
| | | } |
| | | renumberInvestSeq(sheet, "investPlan"); |
| | | return toBytesHssf(wb); |
| | | } |
| | | |
| | | /** å¸å·å°è/åè®¡è¡æ°å¼ï¼7æ»æèµ 8èªå¼å§ç´¯è®¡ 9æ¬å¹´è®¡å 22èªå¹´å累计 23å®é
è¿åº¦ï¼é¨/çèµéä¸èªç¹æ æ°æ®æºï¼çç©ºï¼ */ |
| | | private void writeInvestAggHssf(HSSFRow row, InvestAgg agg) { |
| | | hssfNum(row, 7, agg.total == 0 ? null : agg.total); |
| | | hssfNum(row, 8, agg.startCum == 0 ? null : agg.startCum); |
| | | hssfNum(row, 9, agg.yearPlan == 0 ? null : agg.yearPlan); |
| | | hssfNum(row, 22, agg.yearCum == 0 ? null : agg.yearCum); |
| | | if (agg.yearPlan > 0) hssfNum(row, 23, round(agg.yearCum / agg.yearPlan, 4)); |
| | | } |
| | | |
| | | |
| | | private static class InvestAgg { |
| | | double total, startCum, yearPlan, yearCum; |
| | | |
| | | void add(InvestmentMonthly m, InvestmentProject p) { |
| | | if (p != null) total += nz(p.getTotalInvestment()); |
| | | startCum += nz(m.getStartCum()); |
| | | yearPlan += nz(m.getYearPlan()); |
| | | yearCum += nz(m.getYearCum()); |
| | | } |
| | | |
| | | private double nz(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | } |
| | | |
| | | /** 表2ï¼"åäºäº"è§åç©æµé¡¹ç®è¿å±æ
åµï¼æ¨¡æ¿åºç¨¿å使¿æ¢ï¼ä¿çå
¨é¨è¡/åå¹¶/å宽ï¼ä»
æ¿æ¢æ°æ®ï¼DB 项ç®å¤äºæ¨¡æ¿é¢çè¡æ¶å¨ææè¡ï¼ */ |
| | | public byte[] investFiveYearLogistics(String period, String mode) throws Exception { |
| | | List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period)); |
| | | Map<Long, InvestmentProject> pmap = new HashMap<>(); |
| | | List<InvestmentProject> fiveProjects = new java.util.ArrayList<>(); |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) { |
| | | pmap.put(p.getId(), p); |
| | | if (Integer.valueOf(1).equals(p.getIsFiveYear())) fiveProjects.add(p); |
| | | } |
| | | Map<Long, InvestmentMonthly> mByProject = new HashMap<>(); |
| | | for (InvestmentMonthly m : monthlies) mByProject.put(m.getProjectId(), m); |
| | | List<InvestmentMonthly> mine = new java.util.ArrayList<>(); |
| | | for (InvestmentMonthly m : monthlies) { |
| | | InvestmentProject p = pmap.get(m.getProjectId()); |
| | | if (p != null && Integer.valueOf(1).equals(p.getIsFiveYear())) mine.add(m); |
| | | } |
| | | |
| | | HSSFWorkbook wb = loadInvestTemplate("模æ¿_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls"); |
| | | HSSFSheet sheet = wb.getSheetAt(1); // ã åé¡¹ç®æèµå®ææ
åµã |
| | | int lastRow = sheet.getLastRowNum(); |
| | | // ç»ææ«æï¼å¸å·å°è -> 项ç®è¡ |
| | | List<InvestSection> sections = new ArrayList<>(); |
| | | InvestSection cur = null; |
| | | int firstProjectRow = -1; |
| | | for (int r = 4; r <= lastRow; r++) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = nvl(cellText(row.getCell(0))); |
| | | String b = nvl(cellText(row.getCell(1))); |
| | | if (r == 4) continue; // å
¨çå计è¡åç¬å¤ç |
| | | if (b.isEmpty() && isCityLabel(a)) { |
| | | cur = new InvestSection(); |
| | | cur.city = templateCityFull(a); |
| | | cur.startRow = r; |
| | | sections.add(cur); |
| | | continue; |
| | | } |
| | | if (cur != null) { |
| | | cur.projectRows.add(r); |
| | | if (cur.styleRowIdx < 0) cur.styleRowIdx = r; |
| | | if (firstProjectRow < 0) firstProjectRow = r; |
| | | } |
| | | } |
| | | HSSFRow globalStyle = firstProjectRow >= 0 ? sheet.getRow(firstProjectRow) : null; |
| | | String currentCity = null; |
| | | int seq = 0; |
| | | int si = -1; |
| | | for (int r = 4; r <= lastRow; r++) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String a = nvl(cellText(row.getCell(0))); |
| | | String b = nvl(cellText(row.getCell(1))); |
| | | if (si + 1 < sections.size() && r >= sections.get(si + 1).startRow) si++; |
| | | InvestSection sec = si >= 0 && si < sections.size() ? sections.get(si) : null; |
| | | if (r == 4) { // å
¨çåè®¡è¡ |
| | | blankInvestRow(row, 12); |
| | | hssfText(row, 0, "å计"); |
| | | InvestAgg total = new InvestAgg(); |
| | | for (InvestmentMonthly m : mine) total.add(m, pmap.get(m.getProjectId())); |
| | | hssfNum(row, 5, total.total == 0 ? null : total.total); |
| | | hssfNum(row, 6, total.startCum == 0 ? null : total.startCum); |
| | | hssfNum(row, 7, total.yearCum == 0 ? null : total.yearCum); |
| | | continue; |
| | | } |
| | | if (b.isEmpty() && isCityLabel(a)) { |
| | | currentCity = templateCityFull(a); |
| | | seq = 0; |
| | | blankInvestRow(row, 12); |
| | | hssfText(row, 0, a); |
| | | InvestAgg agg = new InvestAgg(); |
| | | for (InvestmentMonthly m : mine) { |
| | | InvestmentProject p = pmap.get(m.getProjectId()); |
| | | if (p != null && currentCity.equals(p.getCity())) agg.add(m, p); |
| | | } |
| | | hssfNum(row, 5, agg.total == 0 ? null : agg.total); |
| | | hssfNum(row, 6, agg.startCum == 0 ? null : agg.startCum); |
| | | hssfNum(row, 7, agg.yearCum == 0 ? null : agg.yearCum); |
| | | continue; |
| | | } |
| | | // 项ç®è¡ï¼æ 模æ¿åç§°+å¸å· å¹é
DB åäºäºé¡¹ç®ï¼æªå¹é
æ´è¡æ¸
空 |
| | | blankInvestRow(row, 12); |
| | | InvestmentProject p = matchInvest(fiveProjects, b, currentCity, null); |
| | | if (p == null) continue; |
| | | if (sec != null) sec.matched.add(p.getId()); |
| | | InvestmentMonthly m = mByProject.get(p.getId()); |
| | | fillFiveYearProjectRow(row, p, m, ++seq); |
| | | } |
| | | // 卿æè¡ï¼DB åäºäºé¡¹ç®æ° > 模æ¿é¢çè¡æ°æ¶ï¼å¨å¸å·å°èæ«å°¾è¡¥é½ |
| | | for (int i = sections.size() - 1; i >= 0; i--) { |
| | | InvestSection s = sections.get(i); |
| | | if (s.city == null) continue; |
| | | List<InvestmentProject> extra = new ArrayList<>(); |
| | | for (InvestmentProject p : fiveProjects) { |
| | | if (s.city.equals(RegionUtil.normalizeCityName(p.getCity())) |
| | | && !s.matched.contains(p.getId())) extra.add(p); |
| | | } |
| | | if (extra.isEmpty()) continue; |
| | | HSSFRow styleRow = s.styleRowIdx >= 0 ? sheet.getRow(s.styleRowIdx) : globalStyle; |
| | | int pos = s.projectRows.isEmpty() ? s.startRow : s.projectRows.get(s.projectRows.size() - 1); |
| | | int first = insertRowsDown(sheet, pos, extra.size(), styleRow); |
| | | for (int j = 0; j < extra.size(); j++) { |
| | | HSSFRow row = sheet.getRow(first + j); |
| | | if (row == null) continue; |
| | | InvestmentProject p = extra.get(j); |
| | | fillFiveYearProjectRow(row, p, mByProject.get(p.getId()), 0); |
| | | } |
| | | } |
| | | renumberInvestSeq(sheet, "investFiveYear"); |
| | | return toBytesHssf(wb); |
| | | } |
| | | |
| | | /** 表4ï¼ï¼ç»æµå¼ºå¿é¡¹ç®ï¼æ¹åç交éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨ï¼æ¨¡æ¿åºç¨¿å使¿æ¢ï¼ä¿çå
¨é¨è¡/åå¹¶/å宽ï¼ä»
æ¿æ¢æ°æ®ï¼DB 项ç®å¤äºæ¨¡æ¿é¢çè¡æ¶å¨ææè¡ï¼ */ |
| | | public byte[] investCounty(String period, String mode) throws Exception { |
| | | List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period)); |
| | | Map<Long, InvestmentProject> pmap = new HashMap<>(); |
| | | List<InvestmentProject> countyProjects = new java.util.ArrayList<>(); |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) { |
| | | pmap.put(p.getId(), p); |
| | | if (Integer.valueOf(1).equals(p.getIsCounty())) countyProjects.add(p); |
| | | } |
| | | Map<Long, InvestmentMonthly> mByProject = new HashMap<>(); |
| | | for (InvestmentMonthly m : monthlies) mByProject.put(m.getProjectId(), m); |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | |
| | | HSSFWorkbook wb = loadInvestTemplate("模æ¿_ç»æµå¼ºå¿æèµç»è®¡æ¥è¡¨.xls"); |
| | | HSSFSheet sheet = wb.getSheetAt(0); |
| | | hssfText(sheet.getRow(0), 0, "ï¼ç»æµå¼ºå¿é¡¹ç®ï¼æ¹åç交éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨ï¼" + month + "æææ¥ï¼"); |
| | | hssfText(sheet.getRow(1), 0, "å
¨çè´§è¿ï¼ç©æµï¼åºç¡è®¾æ½å»ºè®¾æèµææ¥ï¼" + year + "å¹´" + month + "æï¼"); |
| | | int lastRow = Math.min(sheet.getLastRowNum(), 65533); |
| | | int seq = 0; |
| | | int lastProjectRow = -1; |
| | | Set<Long> matched = new HashSet<>(); |
| | | for (int r = 4; r <= lastRow; r++) { |
| | | HSSFRow row = sheet.getRow(r); |
| | | if (row == null) continue; |
| | | String name = nvl(cellText(row.getCell(1))); |
| | | if (name.isEmpty()) continue; // 模æ¿å°¾é¨ç©ºè¡ä¸å¤ç |
| | | lastProjectRow = r; |
| | | blankInvestRow(row, 20); |
| | | InvestmentProject p = matchInvest(countyProjects, name, null, null); |
| | | if (p == null) continue; |
| | | matched.add(p.getId()); |
| | | InvestmentMonthly m = mByProject.get(p.getId()); |
| | | fillCountyProjectRow(row, p, m, ++seq); |
| | | } |
| | | // 卿æè¡ï¼DB ç»æµå¼ºå¿é¡¹ç®æ° > 模æ¿é¢çè¡æ°æ¶ï¼å¨é¡¹ç®åºæ«å°¾è¡¥é½ |
| | | List<InvestmentProject> extra = new ArrayList<>(); |
| | | for (InvestmentProject p : countyProjects) { |
| | | if (!matched.contains(p.getId())) extra.add(p); |
| | | } |
| | | if (!extra.isEmpty()) { |
| | | HSSFRow styleRow = sheet.getRow(4); |
| | | int pos = lastProjectRow >= 4 ? lastProjectRow : 3; |
| | | int first = insertRowsDown(sheet, pos, extra.size(), styleRow); |
| | | for (int j = 0; j < extra.size(); j++) { |
| | | HSSFRow row = sheet.getRow(first + j); |
| | | if (row == null) continue; |
| | | InvestmentProject p = extra.get(j); |
| | | fillCountyProjectRow(row, p, mByProject.get(p.getId()), 0); |
| | | } |
| | | } |
| | | renumberInvestSeq(sheet, "investCounty"); |
| | | return toBytesHssf(wb); |
| | | } |
| | | |
| | | |
| | | /** 表3ï¼æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®ï¼æ¨¡æ¿åºç¨¿ .xlsï¼æèµæ»è¡¨ + è§ä¸é¡¹ç®è¡¨ 两 sheetï¼ */ |
| | | public byte[] investBillion(String period, String mode) throws Exception { |
| | | List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList( |
| | | new LambdaQueryWrapper<InvestmentMonthly>() |
| | | .eq(InvestmentMonthly::getReportPeriod, period)); |
| | | Map<Long, InvestmentProject> pmap = new HashMap<>(); |
| | | for (InvestmentProject p : investProjectMapper.selectList(null)) pmap.put(p.getId(), p); |
| | | List<InvestmentMonthly> mine = new java.util.ArrayList<>(); |
| | | for (InvestmentMonthly m : monthlies) { |
| | | InvestmentProject p = pmap.get(m.getProjectId()); |
| | | if (p != null && Integer.valueOf(1).equals(p.getIsBillion())) mine.add(m); |
| | | } |
| | | int year = Integer.parseInt(period.split("-")[0]); |
| | | int month = monthOf(period, mode); |
| | | |
| | | HSSFWorkbook wb = loadInvestTemplate("模æ¿_亿å
æèµé¡¹ç®.xls"); |
| | | // ---- sheet1 æèµæ»è¡¨ï¼åºå®éª¨æ¶ï¼ä»
å¡«ï¼äºï¼å
¶ä» å
¨å¹´é¢è®¡ï¼å
¨çæ¬å¹´è®¡åæäº¿ï¼ ---- |
| | | HSSFSheet s1 = wb.getSheetAt(0); |
| | | hssfText(s1.getRow(0), 0, year + "å¹´åºå®èµäº§æèµé¢è®¡å®ææ
åµ"); |
| | | double provinceYearPlan = 0; |
| | | for (InvestmentMonthly m : monthlies) provinceYearPlan += nz(m.getYearPlan()); |
| | | HSSFRow rowOther = s1.getRow(9); |
| | | for (int c = 1; c <= 5; c++) hssfNum(rowOther, c, null); |
| | | hssfNum(rowOther, 5, provinceYearPlan == 0 ? null : round(provinceYearPlan / 10000, 2)); |
| | | // ---- sheet2 è§ä¸é¡¹ç®è¡¨ï¼è¡¨å¤´å¹´ä»½ + å计 + 项ç®è¡ ---- |
| | | HSSFSheet s2 = wb.getSheetAt(1); |
| | | hssfText(s2.getRow(0), 0, year + "年交é建设项ç®å¨å¤æ
åµï¼è§æ¨¡ä»¥ä¸é¡¹ç®ï¼"); |
| | | hssfText(s2.getRow(2), 5, "æªæ¢å°" + year + "å¹´" + month + "æåºå»ºè®¾ç¶æ"); |
| | | hssfText(s2.getRow(2), 9, "æªè³" + (year - 1) + "å¹´å¹´åºå®é
宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 10, year + "å¹´1-3æå®é
宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 11, year + "å¹´4æè®¡å宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 12, year + "年第äºå£åº¦è®¡å宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 13, year + "å¹´å
¨å¹´è®¡å宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 16, year + "å¹´1-4æè®¡å宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 17, year + "å¹´4æå®é
宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 18, year + "å¹´1-4æç´¯è®¡å®ææèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 19, year + "å¹´5æå®é
宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 21, year + "å¹´6æå®é
宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 22, year + "年第äºå£åº¦å®é
宿æèµï¼ä¸å
ï¼"); |
| | | hssfText(s2.getRow(2), 23, year + "å¹´" + month + "æå®é
宿æèµ"); |
| | | // åè®¡è¡ R4ï¼col0-1 å±äºè¡¨å¤´åå¹¶åºï¼ä¿çï¼æ¸
空并éåæ°æ®åï¼ |
| | | HSSFRow totalRow = s2.getRow(4); |
| | | for (int c = 2; c <= 23; c++) hssfNum(totalRow, c, null); |
| | | InvestAgg total = new InvestAgg(); |
| | | for (InvestmentMonthly m : mine) total.add(m, pmap.get(m.getProjectId())); |
| | | hssfNum(totalRow, 8, total.total == 0 ? null : total.total); |
| | | hssfNum(totalRow, 13, total.yearPlan == 0 ? null : total.yearPlan); |
| | | hssfNum(totalRow, 23, sumMonth(mine) == 0 ? null : sumMonth(mine)); |
| | | // 项ç®è¡ï¼ä¿çæ¨¡æ¿ R79 注éè¡ |
| | | HSSFRow styleProject = s2.getRow(5); |
| | | trimHssfTail(s2, 5, 79); |
| | | int rowIdx = 5; |
| | | for (InvestmentMonthly m : mine) { |
| | | InvestmentProject p = pmap.get(m.getProjectId()); |
| | | HSSFRow row = createRowLike(s2, rowIdx++, styleProject); |
| | | hssfText(row, 0, "æ¹åç"); |
| | | hssfText(row, 1, RegionUtil.shortName(p.getCity())); |
| | | hssfText(row, 2, p.getProjectName()); |
| | | hssfText(row, 3, "客è¿ç«åº".equals(p.getCategory()) ? "综åå®¢è¿æ¢çº½" : "综åè´§è¿æ¢çº½"); |
| | | hssfText(row, 5, valueOf(m.getProgressStage())); |
| | | hssfText(row, 6, valueOf(p.getConstructNature())); |
| | | hssfNum(row, 7, parseTimeYMNum(p.getStartTime())); |
| | | hssfNum(row, 8, p.getTotalInvestment()); |
| | | hssfNum(row, 13, m.getYearPlan()); |
| | | hssfNum(row, 23, m.getMonthDone()); |
| | | } |
| | | trimHssfTail(s2, rowIdx, 79); |
| | | return toBytesHssf(wb); |
| | | } |
| | | |
| | | /** 空串å½ä¸ï¼cellText å¯è½è¿å nullï¼ */ |
| | | private String nvl(String v) { |
| | | return v == null ? "" : v.trim(); |
| | | } |
| | | |
| | | /** æ¸
ç©ºæ¨¡æ¿æ°æ®è¡æå®èå´åå
æ ¼ï¼ä¿çæ ·å¼ä¸åå¹¶åå
æ ¼ï¼ */ |
| | | private void blankInvestRow(HSSFRow row, int lastCol) { |
| | | if (row == null) return; |
| | | for (int c = 0; c <= lastCol; c++) { |
| | | Cell cell = row.getCell(c); |
| | | if (cell != null) cell.setBlank(); |
| | | } |
| | | } |
| | | |
| | | /** 模æ¿å¸å·çå -> è§èå¸å·åï¼æ¦æ±->æ¦æ±å¸ãæåº->ç¥åæ¶æåºãæ©æ½å·->æ©æ½å·ï¼ */ |
| | | private String templateCityFull(String label) { |
| | | if (label == null) return null; |
| | | String t = label.trim(); |
| | | String canon = RegionUtil.normalizeCityName(t); |
| | | for (String city : RegionUtil.cityList()) { |
| | | if (city.equals(canon)) return city; |
| | | } |
| | | for (String city : RegionUtil.cityList()) { |
| | | if (RegionUtil.shortName(city).equals(canon) || RegionUtil.shortName(city).equals(t)) return city; |
| | | } |
| | | return canon; |
| | | } |
| | | |
| | | /** å¤ææ¨¡æ¿è¡ A 忝å¦ä¸ºå¸å·å°èæ ç¾ */ |
| | | private boolean isCityLabel(String label) { |
| | | if (label == null || label.isEmpty()) return false; |
| | | String full = templateCityFull(label); |
| | | return full != null && RegionUtil.cityList().contains(full); |
| | | } |
| | | |
| | | /** å¨åéé¡¹ç®æ± 䏿åç§°å¹é
DB 项ç®ï¼å¯é å¸å·/ç±»å« è¿æ»¤ + å¿åºåç¼æ¶æ§ï¼ï¼æªå½ä¸è¿å null */ |
| | | private InvestmentProject matchInvest(List<InvestmentProject> pool, String name, String cityFull, String category) { |
| | | if (name == null || name.trim().isEmpty()) return null; |
| | | String norm = normInvestName(name); |
| | | if (norm.isEmpty()) return null; |
| | | String cityCanon = cityFull == null || cityFull.trim().isEmpty() ? null : RegionUtil.normalizeCityName(cityFull); |
| | | String county = extractInvestCounty(name); |
| | | List<InvestmentProject> cands = new java.util.ArrayList<>(); |
| | | for (InvestmentProject p : pool) { |
| | | if (category != null && !category.equals(p.getCategory())) continue; |
| | | if (cityCanon != null && !cityCanon.equals(RegionUtil.normalizeCityName(p.getCity()))) continue; |
| | | cands.add(p); |
| | | } |
| | | if (cands.isEmpty()) return null; |
| | | // å½ä¸åååå¤åéï¼å¿åºåç¼æ¶æ§ï¼å¦ ç½ç°å¿ç»¼åç©æµå vs å¸ä¸°å¿ç»¼åç©æµåï¼ |
| | | if (!county.isEmpty()) { |
| | | for (InvestmentProject p : cands) { |
| | | if (normInvestName(p.getProjectName()).equals(norm) |
| | | && p.getProjectName() != null && p.getProjectName().contains(county)) { |
| | | return p; |
| | | } |
| | | } |
| | | } |
| | | InvestmentProject best = null; |
| | | int bestScore = Integer.MIN_VALUE; |
| | | for (InvestmentProject p : cands) { |
| | | String pn = normInvestName(p.getProjectName()); |
| | | if (pn.isEmpty()) continue; |
| | | int s = investNameScore(norm, pn, county, p.getProjectName()); |
| | | if (s > bestScore) { |
| | | bestScore = s; |
| | | best = p; |
| | | } |
| | | } |
| | | return bestScore > 0 ? best : null; |
| | | } |
| | | |
| | | /** 项ç®åå½ä¸åï¼å»æ¬å·å符ãè¿æ¥ç¬¦ï¼ç½é©¬æ°åç»ä¸ï¼å»ç/å¸/å¿/åºåç¼ä¸ç©ºç½ï¼ä¸å®¡æ ¸å¼æä¸è´ï¼ */ |
| | | private String normInvestName(String name) { |
| | | if (name == null) return ""; |
| | | String s = name.trim(); |
| | | s = s.replaceAll("[ï¼(]", "").replaceAll("[ï¼)]", ""); |
| | | s = s.replaceAll("[·â¢âï¼_\\-]", ""); |
| | | s = s.replaceAll("[â
â
¡â
¢â
£â
¤â
¥â
¦â
§â
¨â
©â
°â
±â
²â
³â
´â
µâ
¶â
·â
¸â
¹]", "I"); |
| | | s = s.replaceAll("^(æ¹åç|æ¦æ±å¸|é»ç³å¸|åå °å¸|宿å¸|è¥é³å¸|éå·å¸|èé¨å¸|åæå¸|èå·å¸|é»åå¸|å¸å®å¸|éå·å¸|æ©æ½å·|仿¡å¸|æ½æ±å¸|天é¨å¸|ç¥åæ¶æåº)", ""); |
| | | s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:å¿|å¸|åº))", ""); |
| | | s = s.replaceAll("\\s+", ""); |
| | | return s; |
| | | } |
| | | |
| | | /** åç§°ç¸ä¼¼åº¦è¯åï¼ç²¾ç¡®>åç¼å
å«>ä¸åé¨å
å«>5å>ååºå>æé¿å
Œ
±å串ï¼å¿åºåç¼ä¸ä¸è´æç»/大æ£å */ |
| | | private int investNameScore(String norm, String key, String county, String rawB) { |
| | | if (norm.equals(key)) { |
| | | // å½ä¸ååååï¼è¥æ¨¡æ¿å¸¦å¿åºåç¼èåéåå§åä¸å«è¯¥å¿åº â æç»ï¼é² å®é½å¸ vs å½é³å¸ 误é
ï¼ |
| | | if (county != null && !county.isEmpty() && (rawB == null || !rawB.contains(county))) return -1; |
| | | return 2000; |
| | | } |
| | | if (key.length() < 5 || norm.length() < 5) return -1; |
| | | String shortSide = key.length() <= norm.length() ? key : norm; |
| | | String longSide = key.length() > norm.length() ? key : norm; |
| | | int idx = longSide.indexOf(shortSide); |
| | | int lenDiff = Math.abs(key.length() - norm.length()); |
| | | int base; |
| | | if (idx == 0) { |
| | | base = 1000 - lenDiff * 2; |
| | | } else if (idx > 0 && shortSide.length() >= 6) { |
| | | base = 500 - idx * 5 - lenDiff * 2 + shortSide.length(); |
| | | } else if (idx > 0 && shortSide.length() == 5 && lenDiff <= 5) { |
| | | String diff = longSide.substring(0, idx) + longSide.substring(idx + shortSide.length()); |
| | | if (diff.matches(".*(å¿|å¸|å·).*")) return -1; |
| | | base = 400 - idx * 5 - lenDiff; |
| | | } else if (isInvestSubsequence(shortSide, longSide) && lenDiff <= 8) { |
| | | base = 300 - lenDiff * 3; |
| | | } else { |
| | | int l = investLcs(norm, key); |
| | | if (l >= 6) { |
| | | base = 200 + l * 5 - lenDiff; |
| | | } else if (l == 5 && lenDiff <= 4 && county != null && !county.isEmpty() |
| | | && rawB != null && rawB.contains(county)) { |
| | | base = 150 + l * 5 - lenDiff; |
| | | } else { |
| | | return -1; |
| | | } |
| | | } |
| | | if (county != null && !county.isEmpty() && (rawB == null || !rawB.contains(county))) { |
| | | base -= 400; |
| | | } |
| | | return base; |
| | | } |
| | | |
| | | /** 夿ç忝å¦ä¸ºé¿åçå符ååºå */ |
| | | private boolean isInvestSubsequence(String shortSide, String longSide) { |
| | | int i = 0; |
| | | for (int j = 0; i < shortSide.length() && j < longSide.length(); j++) { |
| | | if (shortSide.charAt(i) == longSide.charAt(j)) i++; |
| | | } |
| | | return i == shortSide.length(); |
| | | } |
| | | |
| | | /** æé¿å
Œ
±å串é¿åº¦ */ |
| | | private int investLcs(String a, String b) { |
| | | int n = a.length(), m = b.length(); |
| | | if (n == 0 || m == 0) return 0; |
| | | int[][] dp = new int[n + 1][m + 1]; |
| | | int max = 0; |
| | | for (int i = 1; i <= n; i++) { |
| | | for (int j = 1; j <= m; j++) { |
| | | if (a.charAt(i - 1) == b.charAt(j - 1)) { |
| | | dp[i][j] = dp[i - 1][j - 1] + 1; |
| | | if (dp[i][j] > max) max = dp[i][j]; |
| | | } |
| | | } |
| | | } |
| | | return max; |
| | | } |
| | | |
| | | /** æååç§°ä¸ç å¿/å¸/åº åç¼ï¼å¦ å¸ä¸°å¿ãå®é½å¸ï¼ */ |
| | | private String extractInvestCounty(String name) { |
| | | if (name == null) return ""; |
| | | java.util.regex.Matcher mm = java.util.regex.Pattern.compile("([\\u4e00-\\u9fa5]{2,4}(?:å¿|å¸|åº))").matcher(name); |
| | | if (mm.find()) return mm.group(1); |
| | | return ""; |
| | | } |
| | | |
| | | |
| | | private double sumMonth(List<InvestmentMonthly> list) { |
| | | double s = 0; |
| | | for (InvestmentMonthly m : list) s += nz(m.getMonthDone()); |
| | | return s; |
| | | } |
| | | |
| | | private String padTimeYM(String t) { |
| | | if (t == null) return null; |
| | | String x = t.trim(); |
| | | if (x.matches("\\d{4}")) return x + "01"; |
| | | return x; |
| | | } |
| | | |
| | | private String valueOf(String v) { |
| | | return v == null || v.trim().isEmpty() ? "" : v.trim(); |
| | | } |
| | | private double nz(Double v) { |
| | | return v == null ? 0.0 : v; |
| | | } |
| | | private byte[] toBytes(XSSFWorkbook wb) throws Exception { |
| | | try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { |
| | | wb.write(out); |
| New file |
| | |
| | | package com.trafficaudit.rulemanage.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.rulemanage.entity.AuditRule; |
| | | import com.trafficaudit.rulemanage.mapper.AuditRuleMapper; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | /** è§å管çï¼ææ¥è¡¨ç±»åæ¥ç/å¯å/è°æ´éå¼ */ |
| | | @RestController |
| | | @RequestMapping("/api/rules") |
| | | public class RuleManageController { |
| | | |
| | | @Resource |
| | | private AuditRuleMapper ruleMapper; |
| | | |
| | | /** è§åå表ï¼å¯ææ¥è¡¨ç±»åè¿æ»¤ */ |
| | | @GetMapping("/list") |
| | | public Result<List<AuditRule>> list(@RequestParam(value = "reportType", required = false) String reportType) { |
| | | LambdaQueryWrapper<AuditRule> qw = new LambdaQueryWrapper<>(); |
| | | if (reportType != null && !reportType.trim().isEmpty()) { |
| | | qw.eq(AuditRule::getReportType, reportType.trim()); |
| | | } |
| | | qw.orderByAsc(AuditRule::getSortOrder); |
| | | return Result.ok(ruleMapper.selectList(qw)); |
| | | } |
| | | |
| | | /** å¯åè§å */ |
| | | @PostMapping("/toggle") |
| | | public Result<String> toggle(@RequestParam("id") Long id, |
| | | @RequestParam("enabled") Integer enabled) { |
| | | AuditRule rule = ruleMapper.selectById(id); |
| | | if (rule == null) return Result.error("è§åä¸åå¨"); |
| | | rule.setIsEnabled(enabled == null || enabled == 0 ? 0 : 1); |
| | | rule.setUpdatedAt(LocalDateTime.now()); |
| | | ruleMapper.updateById(rule); |
| | | return Result.ok("å·²æ´æ°"); |
| | | } |
| | | |
| | | /** æ´æ°éå¼/ç级/æè¿° */ |
| | | @PostMapping("/update") |
| | | public Result<String> update(@RequestBody AuditRule body) { |
| | | if (body.getId() == null) return Result.error("缺å°è§åID"); |
| | | AuditRule rule = ruleMapper.selectById(body.getId()); |
| | | if (rule == null) return Result.error("è§åä¸åå¨"); |
| | | if (body.getThreshold() != null) rule.setThreshold(body.getThreshold()); |
| | | if (body.getAlertLevel() != null) rule.setAlertLevel(body.getAlertLevel()); |
| | | if (body.getDescription() != null) rule.setDescription(body.getDescription()); |
| | | rule.setUpdatedAt(LocalDateTime.now()); |
| | | ruleMapper.updateById(rule); |
| | | return Result.ok("å·²ä¿å"); |
| | | } |
| | | } |
| | |
| | | package com.trafficaudit.security.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.security.captcha.CaptchaService; |
| | | import com.trafficaudit.security.utils.JwtUtils; |
| | | import com.trafficaudit.system.entity.User; |
| | | import com.trafficaudit.system.mapper.UserMapper; |
| | | import org.springframework.security.crypto.password.PasswordEncoder; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | |
| | | |
| | | @Resource |
| | | private CaptchaService captchaService; |
| | | |
| | | @Resource |
| | | private UserMapper userMapper; |
| | | |
| | | @Resource |
| | | private PasswordEncoder passwordEncoder; |
| | | |
| | | @PostMapping("/login") |
| | | public Result<Map<String, Object>> login(@RequestBody Map<String, Object> params) { |
| | |
| | | return Result.error(4001, "è¡ä¸ºéªè¯æªéè¿ï¼è¯·éæ°éªè¯"); |
| | | } |
| | | |
| | | if ("admin".equals(username) && "admin123".equals(password)) { |
| | | String token = jwtUtils.generateToken(username); |
| | | if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) { |
| | | return Result.error(401, "ç¨æ·åæå¯ç é误"); |
| | | } |
| | | User user = userMapper.selectOne(new LambdaQueryWrapper<User>() |
| | | .eq(User::getUsername, username.trim())); |
| | | if (user == null || !Integer.valueOf(1).equals(user.getStatus())) { |
| | | return Result.error(401, "ç¨æ·åæå¯ç é误"); |
| | | } |
| | | String encoded = user.getPassword(); |
| | | boolean ok; |
| | | try { |
| | | ok = encoded != null && passwordEncoder.matches(password, encoded); |
| | | } catch (IllegalArgumentException e) { |
| | | // åºä¸å¯ç 䏿¯åæ³ BCrypt æ¶åé为ææå¯¹æ¯ï¼é¿å
æ§æ°æ®æ æ³ç»å½ï¼ |
| | | ok = encoded != null && encoded.equals(password); |
| | | } |
| | | if (!ok) { |
| | | return Result.error(401, "ç¨æ·åæå¯ç é误"); |
| | | } |
| | | String token = jwtUtils.generateToken(username.trim()); |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("token", token); |
| | | data.put("username", username); |
| | | data.put("username", username.trim()); |
| | | data.put("realName", user.getRealName()); |
| | | data.put("modules", user.getModules() == null ? "" : user.getModules()); |
| | | return Result.ok(data); |
| | | } |
| | | return Result.error(401, "ç¨æ·åæå¯ç é误"); |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| New file |
| | |
| | | package com.trafficaudit.system.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.system.entity.User; |
| | | import com.trafficaudit.system.mapper.UserMapper; |
| | | import org.springframework.security.crypto.password.PasswordEncoder; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** ç¨æ·ç®¡çï¼å«è´è´£æ¥è¡¨æ¨¡åé
ç½®ï¼freight/passenger/invest/energy/cityPassengerï¼ */ |
| | | @RestController |
| | | @RequestMapping("/api/user") |
| | | public class UserController { |
| | | |
| | | @Resource |
| | | private UserMapper userMapper; |
| | | @Resource |
| | | private PasswordEncoder passwordEncoder; |
| | | |
| | | @GetMapping("/list") |
| | | public Result<List<Map<String, Object>>> list(@RequestParam(value = "keyword", required = false) String keyword) { |
| | | LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>(); |
| | | if (keyword != null && !keyword.trim().isEmpty()) { |
| | | String k = keyword.trim(); |
| | | qw.and(w -> w.like(User::getUsername, k).or().like(User::getRealName, k)); |
| | | } |
| | | qw.orderByAsc(User::getId); |
| | | List<Map<String, Object>> data = new ArrayList<>(); |
| | | for (User u : userMapper.selectList(qw)) { |
| | | Map<String, Object> m = new HashMap<>(); |
| | | m.put("id", u.getId()); |
| | | m.put("username", u.getUsername()); |
| | | m.put("realName", u.getRealName()); |
| | | m.put("phone", u.getPhone()); |
| | | m.put("status", u.getStatus()); |
| | | m.put("modules", u.getModules()); |
| | | m.put("createdAt", u.getCreatedAt() == null ? null : u.getCreatedAt().toString()); |
| | | data.add(m); |
| | | } |
| | | return Result.ok(data); |
| | | } |
| | | |
| | | /** æ°å¢/ç¼è¾ç¨æ·ï¼ç¼è¾æ¶ password ç空åä¸ä¿®æ¹å¯ç ï¼modules 为模å key æ°ç» */ |
| | | @PostMapping("/save") |
| | | public Result<Void> save(@RequestBody Map<String, Object> params) { |
| | | Object idObj = params.get("id"); |
| | | Long id = idObj == null ? null : Long.valueOf(idObj.toString()); |
| | | String username = str(params.get("username")); |
| | | if (username == null || username.trim().isEmpty()) return Result.error(400, "ç¨æ·åä¸è½ä¸ºç©º"); |
| | | username = username.trim(); |
| | | String password = str(params.get("password")); |
| | | String realName = str(params.get("realName")); |
| | | String phone = str(params.get("phone")); |
| | | Integer status = params.get("status") == null ? 1 : Integer.valueOf(params.get("status").toString()); |
| | | List<String> moduleKeys = new ArrayList<>(); |
| | | Object modulesObj = params.get("modules"); |
| | | if (modulesObj instanceof List) { |
| | | for (Object o : (List<?>) modulesObj) if (o != null) moduleKeys.add(o.toString()); |
| | | } |
| | | String modules = String.join(",", moduleKeys); |
| | | |
| | | LambdaQueryWrapper<User> dup = new LambdaQueryWrapper<User>().eq(User::getUsername, username); |
| | | if (id != null) dup.ne(User::getId, id); |
| | | if (userMapper.selectCount(dup) != null && userMapper.selectCount(dup) > 0) return Result.error(400, "ç¨æ·åå·²åå¨"); |
| | | |
| | | if (id == null) { |
| | | if (password == null || password.isEmpty()) return Result.error(400, "æ°å¢ç¨æ·å¿
须设置å¯ç "); |
| | | User u = new User(); |
| | | u.setUsername(username); |
| | | u.setRealName(realName); |
| | | u.setPhone(phone); |
| | | u.setStatus(status); |
| | | u.setModules(modules); |
| | | u.setPassword(passwordEncoder.encode(password)); |
| | | userMapper.insert(u); |
| | | } else { |
| | | User u = userMapper.selectById(id); |
| | | if (u == null) return Result.error(404, "ç¨æ·ä¸åå¨"); |
| | | u.setRealName(realName); |
| | | u.setPhone(phone); |
| | | u.setStatus(status); |
| | | u.setModules(modules); |
| | | if (password != null && !password.isEmpty()) u.setPassword(passwordEncoder.encode(password)); |
| | | userMapper.updateById(u); |
| | | } |
| | | return Result.ok(null); |
| | | } |
| | | |
| | | @PostMapping("/delete") |
| | | public Result<Void> delete(@RequestParam("id") Long id) { |
| | | User u = userMapper.selectById(id); |
| | | if (u == null) return Result.error(404, "ç¨æ·ä¸åå¨"); |
| | | if ("admin".equals(u.getUsername())) return Result.error(400, "admin ä¸å
许å é¤"); |
| | | userMapper.deleteById(id); |
| | | return Result.ok(null); |
| | | } |
| | | |
| | | private String str(Object o) { |
| | | return o == null ? null : o.toString(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.system.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.util.Date; |
| | | |
| | | /** æä½æ¥å¿ï¼sys_operation_logï¼ï¼æ¥è¡¨çæ/导å
¥çå
³é®æä½çç */ |
| | | @Data |
| | | @TableName("sys_operation_log") |
| | | public class OperationLog { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Long userId; |
| | | private String module; |
| | | private String action; |
| | | private String detail; |
| | | private String ipAddress; |
| | | private Date createdAt; |
| | | } |
| | |
| | | private String phone; |
| | | private Long deptId; |
| | | private Integer status; |
| | | /** è´è´£æ¥è¡¨æ¨¡åï¼éå·åéï¼freight,passenger,invest,energy,cityPassenger */ |
| | | private String modules; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.system.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.system.entity.OperationLog; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface OperationLogMapper extends BaseMapper<OperationLog> {} |
| New file |
| | |
| | | package com.trafficaudit.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.security.utils.JwtUtils; |
| | | import com.trafficaudit.system.entity.OperationLog; |
| | | import com.trafficaudit.system.entity.User; |
| | | import com.trafficaudit.system.mapper.OperationLogMapper; |
| | | import com.trafficaudit.system.mapper.UserMapper; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | /** |
| | | * æä½æ¥å¿æå¡ï¼è®°å½å
³é®æä½ï¼æ¥è¡¨çæ/ç³»åæå
çï¼ï¼èªå¨ä» Authorization 头解æå½åç¨æ·ã |
| | | * è§£æå¤±è´¥ææªç»å½æ¶ä¸æå¼å¸¸ï¼ä»
è®°å½æä½æ¬èº«ã |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class OperationLogService { |
| | | |
| | | @Resource |
| | | private OperationLogMapper operationLogMapper; |
| | | @Resource |
| | | private UserMapper userMapper; |
| | | @Resource |
| | | private JwtUtils jwtUtils; |
| | | @Resource |
| | | private HttpServletRequest request; |
| | | |
| | | public void record(String module, String action, String detail) { |
| | | try { |
| | | OperationLog entry = new OperationLog(); |
| | | entry.setModule(module); |
| | | entry.setAction(action); |
| | | entry.setDetail(detail); |
| | | entry.setUserId(resolveUserId()); |
| | | entry.setIpAddress(clientIp()); |
| | | operationLogMapper.insert(entry); |
| | | } catch (Exception e) { |
| | | log.warn("æä½æ¥å¿è®°å½å¤±è´¥: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private Long resolveUserId() { |
| | | try { |
| | | String auth = request.getHeader("Authorization"); |
| | | if (auth == null || !auth.startsWith("Bearer ")) { |
| | | return null; |
| | | } |
| | | String username = jwtUtils.getUsernameFromToken(auth.substring(7)); |
| | | if (username == null || username.isEmpty()) { |
| | | return null; |
| | | } |
| | | User user = userMapper.selectOne(new LambdaQueryWrapper<User>() |
| | | .eq(User::getUsername, username).last("limit 1")); |
| | | return user == null ? null : user.getId(); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String clientIp() { |
| | | try { |
| | | String ip = request.getHeader("X-Forwarded-For"); |
| | | if (ip != null && !ip.isEmpty()) { |
| | | return ip.split(",")[0].trim(); |
| | | } |
| | | return request.getRemoteAddr(); |
| | | } catch (Exception e) { |
| | | return ""; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | spring: |
| | | datasource: |
| | | url: jdbc:mysql://localhost:3305/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false |
| | | url: jdbc:mysql://localhost:3305/traffic_audit?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true |
| | | username: root |
| | | password: ${MYSQL_PASSWORD:root} |
| | | driver-class-name: com.mysql.cj.jdbc.Driver |
| | | servlet: |
| | | multipart: |
| | | max-file-size: 100MB |
| | | max-request-size: 100MB |
| | | jackson: |
| | | date-format: yyyy-MM-dd HH:mm:ss |
| | | time-zone: Asia/Shanghai |
| | |
| | | secret: ${JWT_SECRET:change-me-jwt-secret} |
| | | expiration: 86400000 |
| | | |
| | | docs: |
| | | template-root: docs |
| | | |
| | | city-passenger: |
| | | template-dir: docs/åå¸å®¢è¿ |
| | | |
| | | passenger: |
| | | template-dir: docs/å
¬è·¯æ
客 |
| | | |
| | | freight: |
| | | template-dir: docs/è´§è¿ |
| | | |
| | | energy: |
| | | template-dir: docs/è½è |
| | | |
| | | investment: |
| | | template-dir: docs/æèµ/æ¨¡æ¿ |
| | | |
| | | deepseek: |
| | | api-key: ${DEEPSEEK_API_KEY:your-deepseek-api-key} |
| | | api-url: https://api.deepseek.com/v1/chat/completions |
| | |
| | | <template> |
| | | <div id="app"> |
| | | <keep-alive include="AuditResult"> |
| | | <router-view /> |
| | | </keep-alive> |
| | | </div> |
| | | </template> |
| | | <script> |
| | |
| | | Vue.use(ElementUI) |
| | | Vue.config.productionTip = false |
| | | |
| | | // å
¨å±ç»åºï¼æ¸
é¤ç»å½æå¹¶åç»å½é¡µï¼å页é¢ä¾§è¾¹æ ãéåºç»å½ãèåå
±ç¨ï¼ |
| | | Vue.mixin({ |
| | | methods: { |
| | | logout() { |
| | | localStorage.removeItem('token') |
| | | localStorage.removeItem('username') |
| | | localStorage.removeItem('realName') |
| | | localStorage.removeItem('modules') |
| | | this.$router.push('/login') |
| | | } |
| | | } |
| | | }) |
| | | |
| | | new Vue({ |
| | | router, |
| | | render: h => h(App) |
| | |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <h3>å®¡æ ¸ç»æ</h3> |
| | | <el-form :inline="true"> |
| | | <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-radio-group v-model="reportType" @change="onTypeChange"> |
| | | <el-radio label="H2032">è´§è¿ææ¥</el-radio> |
| | | <el-radio label="H2031">æ
å®¢ææ¥</el-radio> |
| | | <el-radio label="CITY_BUS">åå¸å
¬äº¤ææ¥</el-radio> |
| | | <el-radio label="CITY_TAXI">巡游åºç§ææ¥</el-radio> |
| | | <el-radio label="INVEST">æèµææ¥</el-radio> |
| | | <el-radio label="H204">è½è壿¥</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"></el-date-picker> |
| | | <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> |
| | | <el-button @click="queryResults">æ¥è¯¢ç»æ</el-button> |
| | | <el-button type="success" icon="el-icon-download" @click="exportExcel">å¯¼åº Excel</el-button> |
| | | <el-button v-if="!focusMode" size="small" icon="el-icon-full-screen" @click="focusMode = true">èç¦æ¥ç</el-button> |
| | | <el-button v-else type="primary" size="small" icon="el-icon-crop" @click="focusMode = false">éåºèç¦</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-form :inline="true" style="margin-top:12px"> |
| | | <!-- é®é¢æ¦è§ç»è®¡å¡ --> |
| | | <div v-if="results.length && !focusMode" class="sev-cards" style="flex:none"> |
| | | <div class="sev-card sev-red"><b>{{ severityStats.red }}</b> 红è²é®é¢</div> |
| | | <div class="sev-card sev-yellow"><b>{{ severityStats.yellow }}</b> é»è²é®é¢</div> |
| | | <div class="sev-card sev-pending"><b>{{ severityStats.pending }}</b> å¾
å¤ç</div> |
| | | <div class="sev-card sev-done"><b>{{ severityStats.done }}</b> å·²å¤ç</div> |
| | | </div> |
| | | <el-form v-if="!focusMode" :inline="true" style="margin-top:8px;flex:none"> |
| | | <el-form-item label="ä¼ä¸åç§°"> |
| | | <el-input v-model="enterpriseFilter" placeholder="è¾å
¥ä¼ä¸åç§°å
³é®å" clearable style="width:220px"></el-input> |
| | | <el-input v-model="enterpriseFilter" placeholder="è¾å
¥ä¼ä¸åç§°å
³é®å" clearable style="width:200px" @keyup.enter.native="queryResults"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å®¡æ ¸è§å"> |
| | | <el-select v-model="ruleFilter" placeholder="å
¨é¨è§å" clearable style="width:200px"> |
| | | <el-select v-model="ruleFilter" placeholder="å
¨é¨è§å" clearable style="width:180px"> |
| | | <el-option v-for="rn in ruleOptions" :key="rn" :label="rn" :value="rn"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="è§å¾"> |
| | | <el-radio-group v-model="viewMode"> |
| | | <el-radio label="detail">ææ¡æç»</el-radio> |
| | | <el-radio label="merged">æä¼ä¸åå¹¶</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item v-if="viewMode === 'detail'"> |
| | | <el-button size="small" type="success" :disabled="!batchPending.length" @click="reviewBatch('CONFIRMED')">æ¹é确认({{ batchPending.length }})</el-button> |
| | | <el-button size="small" type="info" :disabled="!batchPending.length" @click="reviewBatch('IGNORED')">æ¹é忽ç¥({{ batchPending.length }})</el-button> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button size="small" @click="clearFilters">æ¸
ç©ºè¿æ»¤</el-button> |
| | | <el-button size="small" plain @click="statsCollapsed = !statsCollapsed">{{ statsCollapsed ? 'å±å¼' : 'æ¶èµ·' }}è§åç»è®¡</el-button> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <span style="color:#909399;font-size:12px">å
± {{ results.length }} æ¡ï¼å½åæ¾ç¤º {{ filteredResults.length }} æ¡</span> |
| | | <span style="color:#909399;font-size:12px">å
± {{ results.length }} æ¡é®é¢ï¼{{ mergedResults.length }} å®¶ä¼ä¸</span> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table-area"> |
| | | <el-table :data="filteredResults" border v-loading="loading" height="100%"> |
| | | <el-table-column prop="enterpriseName" label="ä¼ä¸åç§°" min-width="200" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="ruleName" label="å®¡æ ¸è§å" min-width="140"></el-table-column> |
| | | <el-table-column prop="actualValue" label="å®é
å¼" min-width="180" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="thresholdValue" label="éå¼/对æ¯" min-width="120"></el-table-column> |
| | | <el-table-column prop="deviation" label="å差说æ" min-width="140" show-overflow-tooltip></el-table-column> |
| | | <el-table-column label="ç级" width="80"> |
| | | <div v-if="ruleStats.length && !statsCollapsed && !focusMode" class="rule-stats" style="flex:none"> |
| | | <div class="stats-cards"> |
| | | <div v-for="rs in sortedRuleStats" :key="rs.ruleId" class="stat-card" |
| | | :class="{ 'has-hit': rs.hit > 0, 'is-skipped': rs.skipped, active: ruleFilter === rs.ruleName }" |
| | | :title="rs.ruleName + 'ï¼' + rs.ruleCode + 'ï¼æ£æ¥' + rs.checked + ' å½ä¸' + rs.hit + 'ï¼ç¹å»è¿æ»¤ï¼'" |
| | | @click="toggleRuleFilter(rs)"> |
| | | <template v-if="rs.skipped"> |
| | | <span class="stat-name">{{ rs.ruleName }}</span> |
| | | <span class="skip-label">åºå缺失ï¼å·²è·³è¿</span> |
| | | </template> |
| | | <template v-else> |
| | | <span class="stat-name">{{ rs.ruleName }}</span> |
| | | <span class="stat-num"><b :class="{ 'hit-num': rs.hit > 0 }">{{ rs.hit }}</b> / {{ rs.checked }}</span> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-if="focusMode" style="flex:none;padding:0 2px 6px;color:#909399;font-size:12px"> |
| | | å
± {{ results.length }} æ¡é®é¢ï¼{{ mergedResults.length }} å®¶ä¼ä¸ |
| | | </div> |
| | | <div class="table-area" style="flex:1;min-height:0"> |
| | | <!-- æç»è§å¾ï¼ä¸æ¡é®é¢ä¸å¼ å¡ç --> |
| | | <div v-if="viewMode === 'detail'" class="detail-cards-wrap" v-loading="loading"> |
| | | <div class="detail-toolbar"> |
| | | <el-checkbox :value="allSelected" :indeterminate="someSelected" @change="toggleSelectAll">å
¨éå½å {{ filteredResults.length }} æ¡</el-checkbox> |
| | | <span class="toolbar-tip">å¾éå坿¹é确认 / 忽ç¥</span> |
| | | </div> |
| | | <div v-for="row in filteredResults" :key="row.id" class="detail-card" |
| | | :class="{ 'card-red': row.alertLevel === 'RED', 'card-yellow': row.alertLevel === 'YELLOW', 'card-done': row.status !== 'PENDING' }"> |
| | | <div class="card-head"> |
| | | <el-checkbox v-model="row._selected" @change="updateBatchSelection"></el-checkbox> |
| | | <div class="card-ent"> |
| | | <div class="ent-name">{{ row.enterpriseName || row.city || 'æªç¥ä¼ä¸' }}</div> |
| | | <div class="ent-sub">{{ row.reportPeriod }}</div> |
| | | </div> |
| | | <el-tag :type="row.alertLevel === 'RED' ? 'danger' : 'warning'" size="small" effect="dark">{{ row.alertLevel }}</el-tag> |
| | | <el-tag :type="statusType(row.status)" size="small" effect="plain" class="status-tag"> |
| | | <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-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> |
| | | <div class="card-body"> |
| | | <div class="rule-title"> |
| | | <b>{{ row.ruleName }}</b> |
| | | <span class="rule-code">{{ row.ruleCode }}</span> |
| | | </div> |
| | | <div class="card-details"> |
| | | <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> |
| | | <div v-if="!filteredResults.length" class="empty-tip"> |
| | | <el-empty description="å½åç鿡件䏿²¡æé®é¢æ°æ®"></el-empty> |
| | | </div> |
| | | </div> |
| | | <!-- åå¹¶è§å¾ --> |
| | | <el-table v-else :data="mergedResults" border stripe v-loading="loading" height="100%" |
| | | :header-cell-style="{ background: '#F5F7FA', color: '#303133', fontWeight: 600 }"> |
| | | <el-table-column label="ä¼ä¸" min-width="190"> |
| | | <template slot-scope="scope"> |
| | | <el-tag :type="scope.row.alertLevel === 'RED' ? 'danger' : 'warning'">{{ scope.row.alertLevel }}</el-tag> |
| | | <div style="font-weight:600">{{ scope.row.enterpriseName }}</div> |
| | | <div style="color:#909399;font-size:12px;margin-top:2px">å
± {{ scope.row.items.length }} æ¡é®é¢</div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="100"> |
| | | <el-table-column label="å®¡æ ¸é®é¢æç»" min-width="520"> |
| | | <template slot-scope="scope"> |
| | | <el-tag :type="statusType(scope.row.status)">{{ scope.row.status }}</el-tag> |
| | | <div v-for="(item, i) in scope.row.items" :key="item.id" |
| | | style="padding:6px 0;border-bottom:1px dashed #EBEEF5" |
| | | :class="{ 'no-border': i === scope.row.items.length - 1 }"> |
| | | <div style="display:flex;align-items:center;flex-wrap:wrap;gap:6px"> |
| | | <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> |
| | | <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> |
| | | <div style="color:#606266;font-size:12px;line-height:1.6;margin-top:2px"> |
| | | <span>å®é
ï¼{{ item.actualValue }}</span> |
| | | <span style="margin:0 8px">åºåï¼{{ item.thresholdValue }}</span> |
| | | <span>å·®å¼ï¼{{ item.deviation }}</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="160"> |
| | | <el-table-column label="ä¼ä¸è§£é" width="340"> |
| | | <template slot-scope="scope"> |
| | | <el-button v-if="scope.row.status === 'PENDING'" size="mini" type="success" @click="review(scope.row, 'CONFIRMED')">确认</el-button> |
| | | <el-button v-if="scope.row.status === 'PENDING'" size="mini" type="info" @click="review(scope.row, 'IGNORED')">忽ç¥</el-button> |
| | | <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> |
| | | <el-button v-if="hasPending(scope.row)" size="mini" type="info" plain @click="reviewAll(scope.row, 'IGNORED')">å
¨é¨å¿½ç¥</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <script> |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | 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'] |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'AuditResult', |
| | | data() { |
| | | return { |
| | | period: '', loading: false, results: [], enterpriseFilter: '', ruleFilter: '' |
| | | period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false, |
| | | enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', batchSelection: [], focusMode: false, |
| | | explainDrafts: {}, savingExplainId: null, expandedExplain: null |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | const names = [] |
| | | this.results.forEach(r => { if (r.ruleName && names.indexOf(r.ruleName) < 0) names.push(r.ruleName) }) |
| | | return names |
| | | }, |
| | | severityStats() { |
| | | const s = { red: 0, yellow: 0, pending: 0, done: 0 } |
| | | this.results.forEach(r => { |
| | | if (r.alertLevel === 'RED') s.red++ |
| | | else if (r.alertLevel === 'YELLOW') s.yellow++ |
| | | if (r.status === 'PENDING') s.pending++ |
| | | else s.done++ |
| | | }) |
| | | return s |
| | | }, |
| | | batchPending() { return this.batchSelection.filter(x => x.status === 'PENDING') }, |
| | | allSelected() { |
| | | const list = this.filteredResults |
| | | return list.length > 0 && list.every(r => r._selected) |
| | | }, |
| | | someSelected() { |
| | | const list = this.filteredResults |
| | | return list.some(r => r._selected) && !this.allSelected |
| | | }, |
| | | filteredResults() { |
| | | const kw = (this.enterpriseFilter || '').trim() |
| | |
| | | if (rule && r.ruleName !== rule) return false |
| | | return true |
| | | }) |
| | | }, |
| | | sortedRuleStats() { |
| | | const arr = (this.ruleStats || []).slice() |
| | | arr.sort((a, b) => { |
| | | const ah = a.hit > 0 ? 1 : 0 |
| | | const bh = b.hit > 0 ? 1 : 0 |
| | | if (ah !== bh) return bh - ah |
| | | return (a.ruleId || 0) - (b.ruleId || 0) |
| | | }) |
| | | return arr |
| | | }, |
| | | mergedResults() { |
| | | const map = new Map() |
| | | this.filteredResults.forEach(r => { |
| | | const key = r.enterpriseName || r.actualValue || 'æªç¥ä¼ä¸' |
| | | if (!map.has(key)) map.set(key, []) |
| | | map.get(key).push(r) |
| | | }) |
| | | return Array.from(map.entries()) |
| | | .map(([name, items]) => ({ enterpriseName: name, items })) |
| | | .sort((a, b) => b.items.length - a.items.length) |
| | | } |
| | | }, |
| | | methods: { |
| | | dataType(t) { return DATA_TYPE_MAP[t] || '' }, |
| | | explainType(t) { return EXPLAIN_TYPES.indexOf(t) >= 0 }, |
| | | goData(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 || '' } }) |
| | | }, |
| | | goExplain(row) { |
| | | this.$router.push({ path: '/explain', query: { reportType: this.reportType, keyword: row.enterpriseName || row.city || '', period: row.reportPeriod } }) |
| | | }, |
| | | onTypeChange() { |
| | | this.ruleFilter = ''; this.batchSelection = []; this.results = []; this.ruleStats = [] |
| | | this.period = '' |
| | | this.loadLatestPeriod(true) |
| | | }, |
| | | onPeriodChange() { |
| | | if (this.period) this.queryResults(); else { this.results = []; this.ruleStats = [] } |
| | | }, |
| | | loadLatestPeriod(autoQuery) { |
| | | api.get('/audit/latestPeriod', { params: { reportType: this.reportType } }) |
| | | .then(res => { |
| | | const p = res.data |
| | | if (p && !this.period) { |
| | | this.period = p |
| | | if (autoQuery) this.queryResults() |
| | | } else if (autoQuery && !p) { |
| | | this.results = []; this.ruleStats = [] |
| | | } |
| | | }) |
| | | .catch(() => {}) |
| | | }, |
| | | executeAudit() { |
| | | if (!this.period) { this.$message.warning('è¯·éæ©æ¥è¡¨æ'); return } |
| | | this.loading = true |
| | | const fd = new FormData() |
| | | fd.append('period', this.period) |
| | | fd.append('reportType', this.reportType) |
| | | api.post('/audit/execute', fd) |
| | | .then(res => { |
| | | const count = (res.data || []).length |
| | | return api.get('/audit/ruleStats', { params: { period: this.period, reportType: this.reportType } }) |
| | | .then(stats => { |
| | | const list = stats.data || [] |
| | | const checked = list.reduce((s, r) => Math.max(s, Number(r.checked) || 0), 0) |
| | | if (list.length === 0) { |
| | | this.$message.warning('该æ¥è¡¨ç±»å没æå¯ç¨å®¡æ ¸è§åï¼æªæ§è¡å®¡æ ¸') |
| | | } else if (checked === 0) { |
| | | this.$message.warning('该æ¥è¡¨ææ²¡ææ°æ®ï¼æªæ§è¡å®¡æ ¸ï¼è¯·å
导å
¥æ°æ®ï¼') |
| | | } else { |
| | | const skipped = list.filter(x => x.skipped) |
| | | if (skipped.length > 0) { |
| | | const names = skipped.map(x => x.ruleCode).join('ã') |
| | | this.$message.warning('å®¡æ ¸å®æï¼å
± ' + count + ' æ¡é®é¢ï¼' + names + ' å åºåæ°æ®ï¼è¿æ¿/轨迹/䏿çï¼ç¼ºå¤±å·²è·³è¿') |
| | | } else { |
| | | this.$message.success('å®¡æ ¸å®æï¼å
± ' + count + ' æ¡é®é¢') |
| | | return api.get('/audit/results', { params: { period: this.period } }) |
| | | } |
| | | } |
| | | }) |
| | | .then(res => { this.results = res.data || [] }) |
| | | }) |
| | | .then(() => api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } })) |
| | | .then(res => { this.applyResults(res.data) }) |
| | | .then(() => this.loadRuleStats()) |
| | | .catch(() => { this.$message.error('å®¡æ ¸æ§è¡å¤±è´¥') }) |
| | | .finally(() => { this.loading = false }) |
| | | }, |
| | | markAudited() { |
| | | if (!this.period) { this.$message.warning('è¯·éæ©æ¥è¡¨æ'); return } |
| | | this.marking = true |
| | | const fd = new FormData() |
| | | fd.append('period', this.period) |
| | | fd.append('reportType', this.reportType) |
| | | api.post('/audit/markAudited', fd) |
| | | .then(res => { |
| | | if (res.data) { |
| | | this.$message.success('å·²æ 记该æã' + this.reportType + 'ãä¸ºå·²å®¡æ ¸ï¼æ¥è¡¨å¯¼åºä¸åæç¤ºæªå®¡æ ¸') |
| | | } else { |
| | | this.$message.warning('è¯¥ç±»åæ²¡æå¯ç¨è§åæè¯¥ææ²¡ææ°æ®ï¼æªåå
¥æ è®°') |
| | | } |
| | | return api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } }) |
| | | }) |
| | | .then(res => { this.applyResults(res.data) }) |
| | | .catch(() => { this.$message.error('æ 记失败') }) |
| | | .finally(() => { this.marking = false }) |
| | | }, |
| | | queryResults() { |
| | | if (!this.period) { this.$message.warning('è¯·éæ©æ¥è¡¨æ'); return } |
| | | this.loading = true |
| | | api.get('/audit/results', { params: { period: this.period } }) |
| | | .then(res => { this.results = res.data || [] }) |
| | | api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } }) |
| | | .then(res => { this.applyResults(res.data) }) |
| | | .then(() => this.loadRuleStats()) |
| | | .catch(() => { this.$message.error('æ¥è¯¢å¤±è´¥') }) |
| | | .finally(() => { this.loading = false }) |
| | | }, |
| | | toggleExplain(reportId) { |
| | | this.expandedExplain = this.expandedExplain === reportId ? null : reportId |
| | | }, |
| | | applyResults(list) { |
| | | this.results = (list || []).map(r => Object.assign({}, r, { _selected: false })) |
| | | this.results.forEach(r => { |
| | | if (r.reportId && this.explainDrafts[r.reportId] === undefined) { |
| | | this.$set(this.explainDrafts, r.reportId, r.verifyExplanation || '') |
| | | } |
| | | }) |
| | | }, |
| | | saveExplain(row) { |
| | | const reportId = row.reportId |
| | | if (!reportId) { this.$message.warning('æ æ³å®ä½è¯¥ä¼ä¸çæ°æ®è¡'); return } |
| | | const explanation = (this.explainDrafts[reportId] || '').trim() |
| | | this.savingExplainId = reportId |
| | | const fd = new FormData() |
| | | fd.append('reportType', this.reportType) |
| | | fd.append('reportId', reportId) |
| | | fd.append('explanation', explanation) |
| | | api.post('/explain/save', fd) |
| | | .then(() => { |
| | | this.$message.success('ä¼ä¸è§£éå·²ä¿å') |
| | | this.$set(this.explainDrafts, reportId, explanation) |
| | | this.results.forEach(r => { if (r.reportId === reportId) r.verifyExplanation = explanation }) |
| | | }) |
| | | .catch(e => { this.$message.error((e.response && e.response.data && e.response.data.message) || 'ä¿å失败') }) |
| | | .finally(() => { this.savingExplainId = null }) |
| | | }, |
| | | resetDraft(reportId) { |
| | | const row = this.results.find(r => r.reportId === reportId) |
| | | if (row) this.$set(this.explainDrafts, reportId, row.verifyExplanation || '') |
| | | }, |
| | | loadRuleStats() { |
| | | if (!this.period) return |
| | | api.get('/audit/ruleStats', { params: { period: this.period, reportType: this.reportType } }) |
| | | .then(res => { this.ruleStats = res.data || [] }) |
| | | .catch(() => { this.ruleStats = [] }) |
| | | }, |
| | | exportExcel() { |
| | | if (!this.period) { this.$message.warning('è¯·éæ©æ¥è¡¨æ'); return } |
| | | window.open('/api/audit/export?period=' + this.period + '&reportType=' + this.reportType) |
| | | }, |
| | | review(row, status) { |
| | | if (status === 'IGNORED') { |
| | | this.$prompt('请è¾å
¥å¿½ç¥åå ï¼æ¹ä¾¿äºå追溯ï¼', '忽ç¥é®é¢', { |
| | | inputPlaceholder: 'åå ï¼å¯éï¼', inputType: 'textarea', confirmButtonText: 'ç¡®å®å¿½ç¥', cancelButtonText: 'åæ¶' |
| | | }).then(({ value }) => this.doReview(row, status, (value || '').trim())) |
| | | .catch(() => {}) |
| | | } else { |
| | | this.doReview(row, status) |
| | | } |
| | | }, |
| | | doReview(row, status, comment) { |
| | | const fd = new FormData() |
| | | fd.append('id', row.id) |
| | | fd.append('status', status) |
| | | if (comment) fd.append('comment', comment) |
| | | api.post('/audit/review', fd) |
| | | .then(() => { row.status = status; this.$message.success('å·²å¤ç') }) |
| | | .then(() => { row.status = status; this.$message.success(status === 'CONFIRMED' ? '已确认' : '已忽ç¥') }) |
| | | .catch(() => { this.$message.error('æä½å¤±è´¥') }) |
| | | }, |
| | | updateBatchSelection() { this.batchSelection = this.filteredResults.filter(r => r._selected) }, |
| | | toggleSelectAll(value) { |
| | | this.filteredResults.forEach(r => { r._selected = value }) |
| | | this.updateBatchSelection() |
| | | }, |
| | | reviewBatch(status) { |
| | | const list = this.batchPending.slice() |
| | | if (list.length === 0) return |
| | | if (status === 'IGNORED') { |
| | | this.$prompt('请è¾å
¥å¿½ç¥åå ï¼åºç¨äºéä¸ç ' + list.length + ' æ¡ï¼', 'æ¹é忽ç¥', { |
| | | inputPlaceholder: 'åå ï¼å¯éï¼', inputType: 'textarea', confirmButtonText: 'ç¡®å®å¿½ç¥', cancelButtonText: 'åæ¶' |
| | | }).then(({ value }) => this.doReviewBatch(list, status, (value || '').trim())) |
| | | .catch(() => {}) |
| | | } else { |
| | | this.doReviewBatch(list, status) |
| | | } |
| | | }, |
| | | doReviewBatch(list, status, comment) { |
| | | let done = 0 |
| | | list.forEach(item => { |
| | | const fd = new FormData() |
| | | fd.append('id', item.id) |
| | | fd.append('status', status) |
| | | if (comment) fd.append('comment', comment) |
| | | api.post('/audit/review', fd) |
| | | .then(() => { item.status = status; done++; if (done === list.length) this.$message.success('å·²' + (status === 'CONFIRMED' ? '确认' : '忽ç¥') + ' ' + done + ' æ¡') }) |
| | | .catch(() => { this.$message.error('é¨åæä½å¤±è´¥') }) |
| | | }) |
| | | }, |
| | | clearFilters() { this.enterpriseFilter = ''; this.ruleFilter = '' }, |
| | | toggleRuleFilter(rs) { |
| | | this.ruleFilter = this.ruleFilter === rs.ruleName ? '' : rs.ruleName |
| | | }, |
| | | hasPending(group) { return group.items.some(x => x.status === 'PENDING') }, |
| | | reviewAll(group, status) { |
| | | const pending = group.items.filter(x => x.status === 'PENDING') |
| | | if (pending.length === 0) return |
| | | if (status === 'IGNORED') { |
| | | this.$prompt('请è¾å
¥å¿½ç¥åå ï¼åºç¨äºè¯¥ä¼ä¸ ' + pending.length + ' æ¡é®é¢ï¼', '忽ç¥', { |
| | | inputPlaceholder: 'åå ï¼å¯éï¼', inputType: 'textarea', confirmButtonText: 'ç¡®å®å¿½ç¥', cancelButtonText: 'åæ¶' |
| | | }).then(({ value }) => this.doReviewBatch(pending, status, (value || '').trim())) |
| | | .catch(() => {}) |
| | | } else { |
| | | this.doReviewBatch(pending, status) |
| | | } |
| | | }, |
| | | statusType(status) { |
| | | if (status === 'CONFIRMED') return 'success' |
| | | if (status === 'IGNORED') return 'info' |
| | | return 'warning' |
| | | }, |
| | | statusText(status) { |
| | | if (status === 'CONFIRMED') return '已确认' |
| | | if (status === 'IGNORED') return '已忽ç¥' |
| | | return 'å¾
å¤ç' |
| | | }, |
| | | statusIcon(status) { |
| | | if (status === 'CONFIRMED') return 'el-icon-circle-check' |
| | | if (status === 'IGNORED') return 'el-icon-circle-close' |
| | | return 'el-icon-time' |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadLatestPeriod(true) |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | .no-border { border-bottom: none !important; } |
| | | .sev-cards { display: flex; gap: 10px; } |
| | | .sev-card { flex: 1; max-width: 180px; border-radius: 6px; padding: 6px 12px; font-size: 13px; color: #606266; } |
| | | .sev-card b { font-size: 20px; margin-right: 4px; } |
| | | .sev-red { background: #FEF0F0; border: 1px solid #FDE2E2; } |
| | | .sev-red b { color: #F56C6C; } |
| | | .sev-yellow { background: #FDF6EC; border: 1px solid #FAECD8; } |
| | | .sev-yellow b { color: #E6A23C; } |
| | | .sev-pending { background: #F4F4F5; border: 1px solid #E9E9EB; } |
| | | .sev-pending b { color: #606266; } |
| | | .sev-done { background: #F0F9EB; border: 1px solid #E1F3D8; } |
| | | .sev-done b { color: #67C23A; } |
| | | .ent-name { font-weight: 600; color: #303133; } |
| | | .ent-sub { color: #909399; font-size: 12px; margin-top: 2px; } |
| | | .rule-name { font-weight: 500; color: #303133; } |
| | | .rule-code { color: #909399; font-size: 12px; margin-top: 2px; } |
| | | .detail-line { display: flex; align-items: baseline; line-height: 1.7; } |
| | | .dt-label { flex: none; width: 34px; color: #909399; font-size: 12px; } |
| | | .dt-val { flex: 1; color: #606266; word-break: break-all; } |
| | | .dt-warn { color: #E6A23C; } |
| | | .status-tag i { margin-right: 3px; } |
| | | .done-text { color: #C0C4CC; font-size: 12px; } |
| | | .explain-side { background: #FAFAFA; border: 1px solid #EBEEF5; border-radius: 6px; padding: 8px 10px; } |
| | | .explain-cell { min-height: 40px; } |
| | | .explain-compact .explain-preview { color: #909399; font-size: 12px; line-height: 1.5; margin: 4px 0; max-height: 40px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-all; } |
| | | .explain-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; } |
| | | .explain-title { font-weight: 600; color: #303133; font-size: 13px; } |
| | | .explain-cur { display: block; color: #909399; font-size: 12px; white-space: pre-wrap; word-break: break-all; line-height: 1.5; margin-bottom: 6px; } |
| | | .explain-actions { margin-top: 6px; } |
| | | .main-focus { padding: 8px 12px !important; } |
| | | .detail-cards-wrap { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 4px 16px; } |
| | | .detail-toolbar { display: flex; align-items: center; gap: 10px; padding: 0 2px 8px; } |
| | | .toolbar-tip { color: #909399; font-size: 12px; } |
| | | .detail-card { background: #FFF; border: 1px solid #EBEEF5; border-left: 3px solid #E6A23C; border-radius: 6px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(0,0,0,.04); } |
| | | .detail-card.card-red { border-left-color: #F56C6C; } |
| | | .detail-card.card-yellow { border-left-color: #E6A23C; } |
| | | .detail-card.card-done { opacity: .72; background: #FAFAFA; } |
| | | .card-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px 4px; } |
| | | .card-ent { flex: 1; min-width: 0; } |
| | | .spacer { flex: 1; } |
| | | .card-body { padding: 2px 12px 10px; } |
| | | .rule-title { display: flex; align-items: baseline; gap: 8px; margin: 2px 0 4px; } |
| | | .rule-title b { font-size: 14px; color: #303133; } |
| | | .card-details { display: flex; flex-wrap: wrap; gap: 4px 24px; background: #F8F9FB; border: 1px solid #F0F2F5; border-radius: 4px; padding: 6px 10px; } |
| | | .card-details .detail-line { display: flex; align-items: baseline; line-height: 1.8; min-width: 200px; } |
| | | .empty-tip { padding: 30px 0; } |
| | | .rule-stats { margin: 0 0 8px; } |
| | | .stats-cards { display: flex; overflow-x: auto; gap: 6px; padding: 4px 0; } |
| | | .stat-card { flex: none; display: inline-flex; align-items: center; gap: 6px; border: 1px solid #EBEEF5; border-radius: 14px; padding: 3px 10px; background: #FAFAFA; font-size: 12px; white-space: nowrap; cursor: pointer; } |
| | | .stat-card:hover { border-color: #C0C4CC; } |
| | | .stat-card.has-hit { background: #FDF6EC; border-color: #E6A23C; } |
| | | .stat-card.is-skipped { background: #F2F6FC; border-color: #DCDFE6; opacity: 0.85; } |
| | | .stat-card.active { border-color: #409EFF; box-shadow: 0 0 0 1px #409EFF; } |
| | | .skip-label { color: #909399; font-size: 12px; } |
| | | .stat-name { color: #606266; max-width: 150px; overflow: hidden; text-overflow: ellipsis; } |
| | | .stat-num { color: #909399; } |
| | | .stat-num b { font-weight: 700; } |
| | | .hit-num { color: #E6A23C; } |
| | | </style> |
| | |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | </el-menu> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item></el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | |
| | | <el-card style="margin-top:20px"> |
| | | <div slot="header">Demo æ°æ®æµ</div> |
| | | <ol> |
| | | <li>æ°æ®å¯¼å
¥ï¼H203-2ææ¥ / è¿æ¿è½¦è¾ / 轨迹éç¨ / è§ä¸è§ä¸æåï¼æ¯æéå¤å¯¼å
¥è¦çï¼</li> |
| | | <li>å®¡æ ¸å¼æï¼6æ¡é¢ç½®è§åï¼è½¦è¾/å¨ä½è¿æ¿å¯¹æ¯ãå¨è½¬é轨迹对æ¯ãè´§è¿ç±»åç¯æ¯ãéè£
ç®±å¼å¸¸ãæ´è½¦å¨ä½èå´ï¼</li> |
| | | <li>æ¥è¡¨çæï¼è´§è¿éå¨è½¬éãåå¸å·æç»ãè´§è¿éæåãå¨è½¬éæåï¼ç¹å»çæå¹¶ä¸è½½ï¼</li> |
| | | <li>æ°æ®å¯¼å
¥ï¼H203-2è´§è¿ææ¥ / H203-1æ
å®¢ææ¥ï¼å«è¿æ¿ï¼/ è¿æ¿è½¦è¾ / 轨迹éç¨ / è§ä¸è§ä¸æåï¼æ¯æéå¤å¯¼å
¥è¦çï¼</li> |
| | | <li>å®¡æ ¸å¼æï¼è´§è¿6æ¡ + æ
客8æ¡é¢ç½®è§åï¼è¿æ¿å¯¹æ¯ãç¯æ¯=0ãå¹³åè¿è·ç¯æ¯ãæ 车æéçç¾çï¼</li> |
| | | <li>æ¥è¡¨çæï¼è´§è¿ï¼åå¸å·æç»/æåï¼+ æ
客åå¸å·æç»ï¼ææ¥/å¹´æ¥ä¸¤ç§æ¨¡å¼ï¼</li> |
| | | </ol> |
| | | </el-card> |
| | | </div> |
| | |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | | <h3>导å
¥ä¸å¡æ°æ®</h3> |
| | | <el-card style="max-width:640px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="导å
¥ç±»å"> |
| | | <el-radio-group v-model="importType"> |
| | | <el-radio label="h2032">H203-2ææ¥</el-radio> |
| | | <el-radio label="transportAuth">è¿æ¿è½¦è¾</el-radio> |
| | | <el-radio label="trackMileage">轨迹éç¨</el-radio> |
| | | <el-radio label="scaleSplit">è§ä¸è§ä¸æå</el-radio> |
| | | <el-radio label="freightTurnover">è´§è¿éå¨è½¬é</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" style="width:100%"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="éæ©æä»¶"> |
| | | <el-upload ref="upload" :auto-upload="false" :limit="1" accept=".xlsx,.xls" |
| | | :on-change="handleFileChange" :file-list="fileList"> |
| | | <el-button slot="trigger" type="primary">éæ© Excel æä»¶</el-button> |
| | | </el-upload> |
| | | <div class="tip">请使ç¨å¯¹åºæ¨¡æ¿ï¼{{ templateName }}</div> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="success" @click="importData" :loading="loading">å¼å§å¯¼å
¥</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-card shadow="hover" class="import-card" style="margin-bottom:15px"> |
| | | <div class="card-head">æ¥è¡¨æ</div> |
| | | <el-date-picker v-model="period" type="month" placeholder="éæ©æä»½" value-format="yyyy-MM" |
| | | style="width:240px" @change="onPeriodChange"></el-date-picker> |
| | | <div class="tip">éæ©æ¥è¡¨æåï¼ä¸æ¹æ¾ç¤ºè¯¥æå模åç导å
¥è¿åº¦ï¼å模å䏿äºä¸å½±åï¼å¯å
é好类åãæè½½æä»¶ï¼åå å
¥å¾
导å
¥éåç»ä¸æ§è¡ã</div> |
| | | </el-card> |
| | | <el-alert v-if="result" type="success" style="margin-top:15px" closable @close="result=''"> |
| | | <div style="white-space: pre-line">{{ result }}</div> |
| | | </el-alert> |
| | | <el-alert v-if="error" :title="error" type="error" style="margin-top:15px" closable @close="error=''"></el-alert> |
| | | |
| | | <el-card shadow="hover" class="import-card" style="margin-bottom:15px"> |
| | | <div class="card-head">æ¬æå¯¼å
¥è¿åº¦ï¼{{ period || 'æªéæ©æ¥è¡¨æ' }}ï¼ |
| | | <el-button type="text" icon="el-icon-refresh" style="float:right" @click="loadOverview">å·æ°</el-button> |
| | | </div> |
| | | <div v-if="!period" class="tip">请å
å¨ä¸æ¹éæ©æ¥è¡¨æ</div> |
| | | <div v-else class="overview-grid"> |
| | | <div class="ov-row" v-for="g in groups" :key="g.key"> |
| | | <div class="ov-group">{{ g.name }}</div> |
| | | <div class="ov-item" v-for="t in g.types" :key="t.value" :class="overviewState(t.value).cls"> |
| | | <div class="ov-name">{{ t.label }}</div> |
| | | <div class="ov-status">{{ overviewState(t.value).text }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <el-card shadow="hover" class="import-card" style="margin-bottom:15px"> |
| | | <div class="card-head">éæ©ç±»åå¹¶æè½½æä»¶ |
| | | <span class="tip" style="float:right;line-height:24px">忍¡åäºä¸å½±åï¼å
éç±»åãæè½½æä»¶ï¼å¯ãç«å³å¯¼å
¥ãæãå å
¥éåãç»ä¸æ§è¡ï¼ä¹å¯å¨ä¸æ¹ç²è´´ç®å½è·¯å¾ç¹ã导å
¥è¯¥ç®å½ãï¼æä»¶å¤¹éå¤ä¸ªæç Excel 伿æä»¶åèªå¨è¯å«æä»½ãåå«å¯¼å
¥å¯¹åºæ¥è¡¨æ</span> |
| | | </div> |
| | | <el-tabs v-model="activeModule" class="module-tabs"> |
| | | <el-tab-pane v-for="g in groups" :key="g.key" :label="g.name" :name="g.key"> |
| | | <div class="module-sel"> |
| | | <el-select v-model="moduleSel[g.key]" placeholder="鿩坼å
¥ç±»å" style="width:260px" |
| | | @change="onModuleChange(g.key)" clearable> |
| | | <el-option v-for="t in g.types" :key="t.value" :label="t.label" :value="t.value"></el-option> |
| | | </el-select> |
| | | <div v-if="isCityDirType(moduleSel[g.key])" class="module-file"> |
| | | <div class="tip">æ ééæä»¶ï¼èªå¨æ«æ docs/æèµ/è¾å
¥/â¦/{{ period ? Number(period.slice(5)) : 'X' }}æ ç®å½ä¸å
¨é¨å¸å·æ¥è¡¨ï¼éæä»¶å®¹éè§£æå
¥åºï¼é¡¹ç®ä¸»æ¡£+æåº¦ï¼å«æ¬æå®æ/å»ºè®¾é¶æ®µ/形象è¿åº¦ï¼åäºäºé¡¹ç®èªå¨æ è®°ï¼ã</div> |
| | | <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 class="file-btns"> |
| | | <el-upload action="#" :auto-upload="false" :show-file-list="false" accept=".xlsx,.xls" |
| | | :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="success" plain icon="el-icon-plus" :disabled="!files[moduleSel[g.key]]" |
| | | @click="addToQueue(g.key)">å å
¥éå</el-button> |
| | | </div> |
| | | <div class="dir-import-bar"> |
| | | <el-input v-model="dirPath" size="small" placeholder="ç²è´´ç®å½è·¯å¾ï¼å¦ D:\æ°æ®\2026ï¼ï¼ç¹ã导å
¥è¯¥ç®å½ã" style="width:330px" |
| | | @keyup.enter.native="quickDirImport(moduleSel[g.key])" clearable></el-input> |
| | | <el-button size="mini" type="warning" icon="el-icon-folder" :loading="dirImporting" |
| | | @click="quickDirImport(moduleSel[g.key])">导å
¥è¯¥ç®å½</el-button> |
| | | <el-button size="mini" type="text" icon="el-icon-search" @click="openDirBrowser(g.key)">æµè§â¦</el-button> |
| | | <el-checkbox v-model="dirSkipImported" size="mini">è·³è¿å·²å¯¼å
¥æä»¶</el-checkbox> |
| | | </div> |
| | | <div class="tip">ç®å½å¯¼å
¥ï¼æä»¶å¤¹éç Excel 伿æä»¶åèªå¨è¯å«æä»½ï¼æ¯æ 2026å¹´7æ / 2026.7 / 2026-07 / 7æï¼ï¼åå«å¯¼å
¥å¯¹åºæ¥è¡¨æï¼è¯å«ä¸å°æä»½çæä»¶ä¼æ¥éæç¤ºãä¹å¯ç¹ãæµè§â¦ãæ¥æ¾ç®å½ã</div> |
| | | <div v-if="dirImportFiles.length" class="dir-progress"> |
| | | <div class="card-head" style="font-size:13px">ç®å½å¯¼å
¥è¿åº¦</div> |
| | | <div v-for="f in dirImportFiles" :key="f.name" class="dir-progress-row"> |
| | | <i :class="progIcon(f.status)"></i> |
| | | <span class="prog-name" :title="f.name">{{ f.name }}</span> |
| | | <el-tag v-if="f.period" size="mini" type="info">{{ f.period }}</el-tag> |
| | | <el-tag size="mini" :type="progTag(f.status)">{{ progText(f.status) }}</el-tag> |
| | | <span v-if="f.msg" class="queue-msg" :style="f.status === 'fail' ? 'color:#F56C6C' : 'color:#67C23A'">{{ f.msg }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | <div v-if="importType" class="type-info"> |
| | | <div>模æ¿ï¼{{ templateName }} |
| | | <el-link v-if="templateUrl" type="primary" icon="el-icon-download" :underline="false" |
| | | style="margin-left:8px" @click="downloadTemplate">ä¸è½½æ¨¡æ¿</el-link> |
| | | </div> |
| | | <div class="tip">{{ typeDesc }}</div> |
| | | <span v-if="importType === 'cityTaxiAuth'" class="slow-tip">该类å约 4 ä¸+ è¡ï¼é¢è®¡èæ¶ 5 åéå·¦å³</span> |
| | | </div> |
| | | </el-card> |
| | | |
| | | |
| | | <el-card v-if="queue.length" ref="queueCard" shadow="hover" class="import-card" style="margin-bottom:15px"> |
| | | <div class="card-head"> |
| | | å¾
导å
¥éåï¼{{ queue.length }} é¡¹ï¼ |
| | | <el-button type="success" size="small" icon="el-icon-video-play" style="float:right" |
| | | :loading="queueLoading" :disabled="!pendingCount" @click="importQueue">å
¨é¨å¯¼å
¥ï¼{{ pendingCount }}ï¼</el-button> |
| | | </div> |
| | | <el-table :data="queue" border size="small" style="width:100%"> |
| | | <el-table-column label="模å" width="90"> |
| | | <template slot-scope="s">{{ s.row.moduleName }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="导å
¥ç±»å" width="150"> |
| | | <template slot-scope="s">{{ s.row.typeLabel }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="æä»¶" min-width="200" show-overflow-tooltip> |
| | | <template slot-scope="s">{{ s.row.fileName }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¶æ" width="110" align="center"> |
| | | <template slot-scope="s"> |
| | | <el-tag v-if="s.row.status === 'pending'" type="info" size="mini">å¾
导å
¥</el-tag> |
| | | <el-tag v-else-if="s.row.status === 'uploading'" type="warning" size="mini" effect="dark">导å
¥ä¸â¦</el-tag> |
| | | <el-tag v-else-if="s.row.status === 'success'" type="success" size="mini">æå</el-tag> |
| | | <el-tag v-else type="danger" size="mini">失败</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç»æ" min-width="240"> |
| | | <template slot-scope="s"> |
| | | <span class="queue-msg">{{ s.row.message }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="130" align="center"> |
| | | <template slot-scope="s"> |
| | | <el-button v-if="s.row.status === 'pending'" size="mini" type="primary" plain @click="importOne(s.row)">导å
¥</el-button> |
| | | <el-button v-if="s.row.status !== 'uploading'" size="mini" type="danger" plain icon="el-icon-delete" |
| | | @click="removeQueueItem(s.row)"></el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="tip" style="margin-top:8px">导å
¥ä¸ºé个æ§è¡ï¼åæ¥è¡¨æéå¤å¯¼å
¥ä¼å
å 餿§æ°æ®ååå
¥ï¼å¹çï¼ä¸ä¼éå¤å两份ï¼ã</div> |
| | | </el-card> |
| | | |
| | | <el-dialog :title="'æµè§ç®å½ â ' + (dirTypeLabel || '')" :visible.sync="dirDialog" width="640px" top="6vh" append-to-body> |
| | | <div class="dir-toolbar"> |
| | | <el-button size="mini" type="text" icon="el-icon-refresh" :disabled="dirLoading" @click="loadDirs(dirCurrent)">å·æ°</el-button> |
| | | <el-button size="mini" type="text" icon="el-icon-folder-opened" @click="loadDirs('')">æ°æ®ç®å½ docs</el-button> |
| | | <el-button size="mini" type="text" icon="el-icon-back" :disabled="!dirParent" @click="goDirParent">ä¸çº§ç®å½</el-button> |
| | | </div> |
| | | <div class="dir-path" :title="dirCurrent">{{ dirCurrent || 'ï¼å è½½ä¸â¦ï¼' }}</div> |
| | | <div class="dir-body" v-loading="dirLoading"> |
| | | <div v-if="!dirDirs.length" class="dir-empty">å½åç®å½ä¸æ²¡æåæä»¶å¤¹ï¼å¯ç´æ¥éæ©å½åæä»¶å¤¹å¯¼å
¥</div> |
| | | <div v-for="d in dirDirs" :key="d.path" class="dir-row" :title="d.path + 'ï¼åå»ç«å³å¯¼å
¥è¯¥æä»¶å¤¹ï¼'" @click="loadDirs(d.path)" @dblclick="dblImportDir(d)"> |
| | | <i class="el-icon-folder"></i><span>{{ d.name }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="dir-files"> |
| | | <div class="tip">å廿件夹è¿å
¥åæä»¶å¤¹ï¼<b>åå»ç®æ æä»¶å¤¹ç«å³å¯¼å
¥</b>å
¶ä¸å
¨é¨ Excelï¼ä¹å¯è¿å
¥åç¹ä¸æ¹æé®ã导å
¥æ¶ä¼ææä»¶åèªå¨è¯å«æä»½ã</div> |
| | | <div v-if="dirExcelCount === 0" class="tip">å½åæä»¶å¤¹å
没æ Excel æä»¶ï¼.xlsx/.xlsï¼</div> |
| | | <div v-else class="tip">å½åæä»¶å¤¹å
Excelï¼{{ dirExcelCount }} 个 ââ {{ dirFiles.slice(0, 6).join('ã') }}{{ dirFiles.length > 6 ? ' ç' : '' }}ï¼ä»
导å
¥ç´æ¥ä½äºè¯¥æä»¶å¤¹çæä»¶ï¼</div> |
| | | </div> |
| | | <span slot="footer"> |
| | | <el-button size="small" @click="dirDialog = false">åæ¶</el-button> |
| | | <el-button size="small" type="primary" :disabled="dirExcelCount === 0 || dirImporting" :loading="dirImporting" @click="confirmDirImport">导å
¥å½åç®å½</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | |
| | | <el-card shadow="never" style="margin-top:15px" class="batch-card"> |
| | | <div slot="header" class="card-head"> |
| | | 导å
¥è®°å½ï¼{{ period || 'å
¨é¨æ¥è¡¨æ' }}ï¼ |
| | | <el-button type="text" icon="el-icon-refresh" style="float:right" @click="loadBatches">å·æ°</el-button> |
| | | </div> |
| | | <el-table :data="batches" border size="small" v-loading="batchLoading" style="width:100%"> |
| | | <el-table-column prop="importType" label="导å
¥ç±»å" width="150"> |
| | | <template slot-scope="s">{{ typeLabel(s.row.importType) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="reportPeriod" label="æ¥è¡¨æ" width="100"></el-table-column> |
| | | <el-table-column prop="fileName" label="æä»¶å" min-width="220" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="totalRows" label="æ»è¡æ°" width="90"></el-table-column> |
| | | <el-table-column prop="successRows" label="æå" width="80"> |
| | | <template slot-scope="s"> |
| | | <span style="color:#67C23A" v-if="s.row.successRows > 0">{{ s.row.successRows }}</span> |
| | | <span v-else>0</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="failRows" label="失败" width="70"> |
| | | <template slot-scope="s"> |
| | | <el-tag v-if="s.row.failRows > 0" size="mini" type="danger">{{ s.row.failRows }}</el-tag> |
| | | <span v-else style="color:#909399">0</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createdAt" label="导å
¥æ¶é´" width="165"></el-table-column> |
| | | <el-table-column label="ç¶æ" width="90"> |
| | | <template slot-scope="s"> |
| | | <el-tag v-if="s.row.failRows > 0" size="mini" type="warning" effect="plain">é¨å失败</el-tag> |
| | | <el-tag v-else-if="s.row.successRows > 0" size="mini" type="success" effect="plain">æå</el-tag> |
| | | <el-tag v-else size="mini" type="info" effect="plain">æ æ°æ®</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | |
| | | <script> |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | import api from '@/api' |
| | | const TYPE_LABELS = { |
| | | h2032: 'H203-2ææ¥', transportAuth: 'è¿æ¿è½¦è¾', trackMileage: '轨迹éç¨', scaleSplit: 'è§ä¸è§ä¸æå', |
| | | freightTurnover: 'è´§è¿éå¨è½¬é', passengerMonthly: 'H203-1æ
å®¢ææ¥', passengerIndividual: '个ä½å®¢è¿é/å¨è½¬é', |
| | | passengerAuth: 'æ
å®¢è¿æ¿è½¦è¾æ°', energyMonthly: 'H204è½è壿¥', energyAuth: 'è½è车è¾è¿æ¿ä¿¡æ¯', |
| | | investment: 'æèµææ¥ï¼å®¢è¿ç«åºï¼', investmentLogistics: 'æèµææ¥ï¼ç©æµååºï¼', investmentSystem: 'æèµç³»ç»å¯¼åº', |
| | | investCityPassenger: 'å¸å·å表ï¼å®¢è¿ç«åºï¼', investCityLogistics: 'å¸å·å表ï¼ç©æµååºï¼', |
| | | 'INVEST_客è¿ç«åº_CITY': 'å¸å·å表ï¼å®¢è¿ç«åºï¼', 'INVEST_ç©æµååº_CITY': 'å¸å·å表ï¼ç©æµååºï¼', |
| | | cityBus: 'åå¸å
¬äº¤ææ¥', cityTaxi: '巡游åºç§ææ¥', cityTaxiAuth: 'åºç§è½¦è¿æ¿è½¦è¾', |
| | | H2032: 'H203-2ææ¥', H2031: 'H203-1æ
å®¢ææ¥', H204: 'H204è½è壿¥', PASSENGER_AUTH: 'æ
å®¢è¿æ¿è½¦è¾æ°', |
| | | PASSENGER_INDIVIDUAL: '个ä½å®¢è¿é/å¨è½¬é', ENERGY_AUTH: 'è½è车è¾è¿æ¿ä¿¡æ¯', TRANSPORT_AUTH: 'è¿æ¿è½¦è¾', |
| | | TRACK_MILEAGE: '轨迹éç¨', SCALE_SPLIT: 'è§ä¸è§ä¸æå', FREIGHT_TURNOVER: 'è´§è¿éå¨è½¬é', |
| | | INVEST_SYSTEM: 'æèµç³»ç»å¯¼åº', CITY_BUS: 'åå¸å
¬äº¤ææ¥', CITY_TAXI: '巡游åºç§ææ¥', CITY_TAXI_AUTH: 'åºç§è½¦è¿æ¿è½¦è¾' |
| | | } |
| | | const BATCH_TYPE_MAP = { |
| | | H2032: 'h2032', TRANSPORT_AUTH: 'transportAuth', TRACK_MILEAGE: 'trackMileage', |
| | | SCALE_SPLIT: 'scaleSplit', FREIGHT_TURNOVER: 'freightTurnover', |
| | | H2031: 'passengerMonthly', PASSENGER_AUTH: 'passengerAuth', PASSENGER_INDIVIDUAL: 'passengerIndividual', |
| | | H204: 'energyMonthly', ENERGY_AUTH: 'energyAuth', |
| | | CITY_BUS: 'cityBus', CITY_TAXI: 'cityTaxi', CITY_TAXI_AUTH: 'cityTaxiAuth', |
| | | INVEST_SYSTEM: 'investmentSystem' |
| | | } |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'DataImport', |
| | | data() { |
| | | return { |
| | | importType: 'h2032', |
| | | period: '', |
| | | file: null, |
| | | fileList: [], |
| | | loading: false, |
| | | period: (() => { const d = new Date(); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') })(), |
| | | queue: [], |
| | | queueLoading: false, |
| | | cityDirLoading: false, |
| | | dirDialog: false, |
| | | dirKey: '', |
| | | dirCurrent: '', |
| | | dirParent: '', |
| | | dirDirs: [], |
| | | dirExcelCount: 0, |
| | | dirFiles: [], |
| | | dirLoading: false, |
| | | dirImporting: false, |
| | | dirPath: '', |
| | | dirImportFiles: [], |
| | | dirSkipImported: true, |
| | | result: '', |
| | | error: '' |
| | | error: '', |
| | | batches: [], |
| | | batchLoading: false, |
| | | moduleSel: { freight: 'h2032', passenger: '', city: '', energy: '', invest: '' }, |
| | | activeModule: 'freight', |
| | | files: {}, |
| | | fileNames: {}, |
| | | overview: {}, |
| | | groups: [ |
| | | { |
| | | key: 'freight', name: 'è´§è¿', types: [ |
| | | { value: 'h2032', label: 'H203-2ææ¥' }, |
| | | { value: 'transportAuth', label: 'è¿æ¿è½¦è¾' }, |
| | | { value: 'trackMileage', label: '轨迹éç¨' }, |
| | | { value: 'scaleSplit', label: 'è§ä¸è§ä¸æå' }, |
| | | { value: 'freightTurnover', label: 'è´§è¿éå¨è½¬é' } |
| | | ] |
| | | }, |
| | | { |
| | | key: 'passenger', name: 'å
¬è·¯æ
客', types: [ |
| | | { value: 'passengerMonthly', label: 'H203-1æ
å®¢ææ¥' }, |
| | | { value: 'passengerAuth', label: 'è¿æ¿è½¦è¾æ°' }, |
| | | { value: 'passengerIndividual', label: '个ä½å®¢è¿é/å¨è½¬é' } |
| | | ] |
| | | }, |
| | | { |
| | | key: 'city', name: 'åå¸å®¢è¿', types: [ |
| | | { value: 'cityBus', label: 'åå¸å
¬äº¤ææ¥' }, |
| | | { value: 'cityTaxi', label: '巡游åºç§ææ¥' }, |
| | | { value: 'cityTaxiAuth', label: 'åºç§è½¦è¿æ¿è½¦è¾' } |
| | | ] |
| | | }, |
| | | { |
| | | key: 'invest', name: 'æèµ', types: [ |
| | | { value: 'investment', label: 'æèµææ¥ï¼å®¢è¿ç«åºï¼' }, |
| | | { value: 'investmentLogistics', label: 'æèµææ¥ï¼ç©æµååºï¼' }, |
| | | { value: 'investmentSystem', label: 'æèµç³»ç»å¯¼åº' }, |
| | | { value: 'investCityPassenger', label: 'å¸å·å表æ¹é导å
¥ï¼å®¢è¿ç«åºï¼' }, |
| | | { value: 'investCityLogistics', label: 'å¸å·å表æ¹é导å
¥ï¼ç©æµååºï¼' } |
| | | ] |
| | | }, |
| | | { |
| | | key: 'energy', name: 'è½è', types: [ |
| | | { value: 'energyMonthly', label: 'H204è½è壿¥' }, |
| | | { value: 'energyAuth', label: 'è½è车è¾è¿æ¿ä¿¡æ¯' } |
| | | ] |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | computed: { |
| | | pendingCount() { return this.queue.filter(x => x.status === 'pending').length }, |
| | | dirTypeLabel() { |
| | | const t = this.moduleSel[this.dirKey] |
| | | if (!t) return '' |
| | | for (const g of this.groups) { |
| | | const found = g.types.find(x => x.value === t) |
| | | if (found) return g.name + ' / ' + found.label |
| | | } |
| | | return t |
| | | }, |
| | | typeDesc() { |
| | | const map = { |
| | | h2032: 'ä¼ä¸çº§è´§è¿ææ¥ï¼ç¨äºè§ä¸è´§è¿éå®¡æ ¸ä¸åå¸å·æ±æ»ï¼17 å¸å·ä¼ä¸æç»ï¼ã', |
| | | transportAuth: 'è¿æ¿è½¦è¾æ¡£æ¡ï¼ç¨äºè½¦è¾ä¿¡æ¯æ ¸å¯¹ï¼å¨ä½/车å/çæç±»åï¼ã', |
| | | trackMileage: '轨迹éç¨æ°æ®ï¼ç¨äºéç¨ä¸è¿è·æ ¡éªã', |
| | | scaleSplit: 'è§ä¸è§ä¸æåï¼å
¨ç+17 å¸å·è´§è¿é/å¨è½¬éæåç»æã', |
| | | freightTurnover: 'è´§è¿éå¨è½¬éï¼å
¨ç+17 å¸å·æåº¦å计ï¼ä»å¹´ 1-N æ + å»å¹´ï¼ã', |
| | | passengerMonthly: 'å
¬è·¯æ
å®¢ææ¥ï¼ä¼ä¸çº§æç» + è¿æ¿è½¦è¾æ°ä¸¤ä¸ª sheetï¼è¿æ¿è½¦è¾æ°ä¹å¯åç¬å¯¼å
¥ï¼ã', |
| | | passengerAuth: 'æ
å®¢è¿æ¿è½¦è¾æ°ï¼ç¬ç«æ¨¡æ¿ï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼ï¼ç¨äºè¿è¥è½¦è¾æ ¸å¯¹ï¼ä¹å¯é H203-1 ææ¥ç¬¬äºä¸ª sheet ä¸å¹¶å¯¼å
¥ã', |
| | | passengerIndividual: '个ä½å®¢è¿é/å¨è½¬éï¼ä¼ä¸çº¿ä¸æ±æ¥æ°æ®ï¼æå±å°åº/ä¼ä¸ä»£ç /ä¼ä¸åç§°/个ä½å®¢è¿é/ä¸ªä½æ
客å¨è½¬éï¼ï¼å¯¼å
¥åèªå¨å¡«å
¥åå¸å·è¡¨âå
¶ä¸ä¸ªä½âè¡ã', |
| | | energyMonthly: 'è½è壿¥ï¼è½èæç» + è¿æ¿è½¦è¾ä¿¡æ¯ä¸¤ä¸ª sheetï¼è¿æ¿ä¿¡æ¯ä¹å¯åç¬å¯¼å
¥ï¼å£åº¦æ«æçææ±æ»è¡¨ï¼ã', |
| | | energyAuth: 'è½è车è¾è¿æ¿ä¿¡æ¯ï¼ç¬ç«æ¨¡æ¿ï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼ï¼ç¨äºè½èå®¡æ ¸å¯¹æ¯ï¼ä¹å¯é H204 壿¥ç¬¬äºä¸ª sheet ä¸å¹¶å¯¼å
¥ã', |
| | | investment: 'æèµææ¥ï¼å®¢è¿ç«åºï¼ï¼å¸å·æ±æ»å¤§è¡¨ï¼å¯¼å
¥åçæ 4 å¼ é¢å¯¼æ¥è¡¨ã', |
| | | investmentLogistics: 'æèµææ¥ï¼ç©æµååºï¼ï¼åé¡¹ç®æèµå®ææ
åµ sheetã', |
| | | investmentSystem: 'æèµç³»ç»å¯¼åºï¼ç¨äºä¸ææ¥æ°æ®è·¨è¡¨æ ¡éªã', |
| | | investCityPassenger: 'æ ééæä»¶ï¼èªå¨æ«æ docs/æèµ/è¾å
¥/客è¿ï¼å®¢è¿ç«ï¼/Xæ ç®å½å
¨é¨å¸å·æ¥è¡¨ï¼å®¹éè§£æå
¥åºï¼å«æ¬æå®æ/å»ºè®¾é¶æ®µ/形象è¿åº¦ï¼åäºäºé¡¹ç®èªå¨æ è®°ï¼ã', |
| | | investCityLogistics: 'æ ééæä»¶ï¼èªå¨æ«æ docs/æèµ/è¾å
¥/ç©æµï¼ç©æµååºï¼/Xæ ç®å½å
¨é¨å¸å·æ¥è¡¨ï¼å®¹éè§£æå
¥åºï¼å«æ¬æå®æ/å»ºè®¾é¶æ®µ/形象è¿åº¦ï¼åäºäºé¡¹ç®èªå¨æ è®°ï¼ã', |
| | | cityBus: 'åå¸å
¬äº¤ææ¥ï¼ä¼ä¸çº§è¿è¥æ°æ®ï¼å«è½¨é/è½®æ¸¡åæ®µã', |
| | | cityTaxi: '巡游åºç§ææ¥ï¼å¸å·çº§ 17 è¡è¿è¥æ°æ®ã', |
| | | cityTaxiAuth: 'åºç§è½¦è¿æ¿è½¦è¾ï¼è½¦è¾çº§æç»ï¼çº¦ 4 ä¸+ è¡ï¼ï¼ç¨äºè¿è¥è½¦æ°å¯¹æ¯ã' |
| | | } |
| | | return map[this.importType] || '' |
| | | }, |
| | | templateName() { |
| | | const map = { |
| | | h2032: '模æ¿_交ä¼ç»H203-2表_é路货ç©è¿è¾æåº¦ç产æ
åµ.xlsx', |
| | | transportAuth: '模æ¿_è¿æ¿è½¦è¾.xlsx', |
| | | trackMileage: '模æ¿_轨迹éç¨.xlsx', |
| | | scaleSplit: '模æ¿_è§ä¸è§ä¸æåè¿è¾é.xlsx', |
| | | freightTurnover: '模æ¿_è´§è¿éå¨è½¬é.xlsx' |
| | | freightTurnover: '模æ¿_è´§è¿éå¨è½¬é.xlsx', |
| | | passengerMonthly: 'å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsxï¼å«è¿æ¿è½¦è¾æ°sheetï¼', |
| | | passengerAuth: '模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsxï¼åï¼ä¼ä¸ä»£ç /ä¼ä¸åç§°/è½¦è¾æ°/è½½å®¢ä½æ°ï¼', |
| | | passengerIndividual: '个ä½å®¢è¿éæ±æ».xlsxï¼åï¼æå±å°åº/ä¼ä¸ä»£ç /ä¼ä¸åç§°/个ä½å®¢è¿é/ä¸ªä½æ
客å¨è½¬éï¼', |
| | | energyMonthly: 'é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026å¹´ï¼.xlsxï¼è½èæç»+è¿æ¿è½¦è¾ä¿¡æ¯ä¸¤sheetï¼', |
| | | energyAuth: '模æ¿_è½è车è¾è¿æ¿ä¿¡æ¯.xlsxï¼åï¼è½¦çå·/车è¾ç±»å/çæç±»å/æ è®°å¨ä½/åçµå¼è´¨éï¼', |
| | | investment: '7æå®¢è¿ç«æèµæç»è¡¨ï¼å¸å·æ±æ»å¤§è¡¨ï¼ç»é¢å¯¼ï¼.xlsx', |
| | | investmentLogistics: '7æç©æµç«åºæèµæç».xlsï¼åé¡¹ç®æèµå®ææ
åµsheetï¼', |
| | | investmentSystem: 'æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7).xls', |
| | | investCityPassenger: 'âï¼æ¹éæ«æç®å½ï¼æ 鿍¡æ¿ï¼', |
| | | investCityLogistics: 'âï¼æ¹éæ«æç®å½ï¼æ 鿍¡æ¿ï¼', |
| | | cityBus: '导å
¥_åå¸å
Œ
±äº¤éæåº¦è¿è¥æ
åµï¼YYYYå¹´Mæï¼.xls/.xlsxï¼ä¼ä¸çº§æç»ï¼åå
Œ
±æ±½çµè½¦å段ï¼', |
| | | cityTaxi: '导å
¥_巡游åºç§æ±½è½¦è¿è¥æå¡æ
嵿æ¥ï¼YYYYå¹´Mæï¼.xlsï¼å¸å·çº§17è¡ï¼', |
| | | cityTaxiAuth: '导å
¥_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xlsï¼è½¦è¾çº§æç»ï¼ç¨äºè¿è¥è½¦æ°å¯¹æ¯ï¼' |
| | | } |
| | | return map[this.importType] || '' |
| | | }, |
| | | endpoint() { |
| | | templateUrl() { |
| | | const map = { |
| | | h2032: 'è´§è¿/模æ¿_交ä¼ç»H203-2表_é路货ç©è¿è¾æåº¦ç产æ
åµ.xlsx', |
| | | transportAuth: 'è´§è¿/模æ¿_è¿æ¿è½¦è¾.xlsx', |
| | | trackMileage: 'è´§è¿/模æ¿_轨迹éç¨.xlsx', |
| | | scaleSplit: 'è´§è¿/模æ¿_è§ä¸è§ä¸æåè¿è¾é.xlsx', |
| | | freightTurnover: 'è´§è¿/模æ¿_è´§è¿éå¨è½¬é.xlsx', |
| | | passengerMonthly: 'å
¬è·¯æ
客/模æ¿_交ä¼ç»H203-1表_å
¬è·¯æ
客è¿è¾æåº¦ç产æ
åµ.xlsx', |
| | | passengerAuth: 'å
¬è·¯æ
客/模æ¿_å
¬è·¯æ
å®¢è¿æ¿è½¦è¾æ°.xlsx', |
| | | passengerIndividual: '', |
| | | energyMonthly: 'è½è/模æ¿_é路货è¿è½¦è¾è½æºæ¶èæ
åµï¼2026ï¼.xlsx', |
| | | energyAuth: 'è½è/模æ¿_è½è车è¾è¿æ¿ä¿¡æ¯.xlsx', |
| | | cityBus: 'åå¸å®¢è¿/导å
¥æ¨¡æ¿_åå¸å
¬äº¤.xlsx', |
| | | cityTaxi: 'åå¸å®¢è¿/导å
¥æ¨¡æ¿_巡游åºç§.xlsx', |
| | | cityTaxiAuth: 'åå¸å®¢è¿/导å
¥æ¨¡æ¿_åºç§è½¦ä¿¡æ¯(è¿æ¿è½¦è¾ä¿¡æ¯).xls', |
| | | investment: 'æèµ/è¾å
¥/客è¿ï¼å®¢è¿ç«ï¼/æ´æ°_7æå®¢è¿ç«æèµæç»è¡¨ï¼å¸å·æ±æ»å¤§è¡¨ï¼ç»é¢å¯¼ï¼.xlsx', |
| | | investmentLogistics: 'æèµ/è¾å
¥/ç©æµï¼ç©æµååºï¼/æ´æ°_7æç©æµç«åºæèµæç».xls', |
| | | investmentSystem: 'æèµ/模æ¿_æ¥è¯¢ç»æ(æèµç³»ç»-客è¿+ç©æµ 2026.7).xls' |
| | | } |
| | | return map[this.importType] || '' |
| | | } |
| | | }, |
| | | methods: { |
| | | typeLabel(t) { return TYPE_LABELS[t] || t }, |
| | | downloadTemplate() { |
| | | if (!this.templateUrl) return |
| | | window.open('/api/templates/' + this.templateUrl) |
| | | }, |
| | | onModuleChange(key) { |
| | | this.importType = this.moduleSel[key] || '' |
| | | this.dirPath = (this.importType && localStorage.getItem('dirLast_' + this.importType)) || '' |
| | | }, |
| | | onPeriodChange() { this.loadOverview(); this.loadBatches() }, |
| | | endpoint(t) { |
| | | const map = { |
| | | h2032: '/import/h2032', |
| | | transportAuth: '/import/transportAuth', |
| | | trackMileage: '/import/trackMileage', |
| | | scaleSplit: '/import/scaleSplit', |
| | | freightTurnover: '/import/freightTurnover' |
| | | freightTurnover: '/import/freightTurnover', |
| | | passengerMonthly: '/import/passengerMonthly', |
| | | passengerAuth: '/import/passengerAuth', |
| | | passengerIndividual: '/import/passengerIndividual', |
| | | energyMonthly: '/import/energyMonthly', |
| | | energyAuth: '/import/energyAuth', |
| | | investment: '/import/investment', |
| | | investmentLogistics: '/import/investment', |
| | | investmentSystem: '/import/investmentSystem', |
| | | cityBus: '/import/cityBus', |
| | | cityTaxi: '/import/cityTaxi', |
| | | cityTaxiAuth: '/import/cityTaxiAuth' |
| | | } |
| | | return map[this.importType] |
| | | return map[t] || '' |
| | | }, |
| | | openDirBrowser(key) { |
| | | const type = this.moduleSel[key] |
| | | if (!type) { this.$message.warning('请å
鿩坼å
¥ç±»å'); return } |
| | | this.dirKey = key |
| | | this.dirDialog = true |
| | | const last = localStorage.getItem('dirLast_' + type) |
| | | this.loadDirs(last || '') |
| | | }, |
| | | loadDirs(path) { |
| | | this.dirLoading = true |
| | | api.get('/import/dirs', { params: { path: path || '' } }) |
| | | .then(res => { |
| | | const d = (res && res.data) || {} |
| | | this.dirCurrent = d.current || '' |
| | | this.dirParent = d.parent || '' |
| | | this.dirDirs = d.dirs || [] |
| | | this.dirExcelCount = d.excelCount || 0 |
| | | this.dirFiles = d.files || [] |
| | | }) |
| | | .catch(e => { this.$message.error((e.response && e.response.data && e.response.data.message) || 'ç®å½å 载失败') }) |
| | | .finally(() => { this.dirLoading = false }) |
| | | }, |
| | | goDirParent() { if (this.dirParent) this.loadDirs(this.dirParent) }, |
| | | dblImportDir(d) { |
| | | this.dirDialog = false |
| | | this.runDirImport(this.moduleSel[this.dirKey], d.path) |
| | | }, |
| | | confirmDirImport() { |
| | | if (!this.dirCurrent) return |
| | | this.runDirImport(this.moduleSel[this.dirKey], this.dirCurrent) |
| | | }, |
| | | quickDirImport(type) { |
| | | if (!type) { this.$message.warning('请å
鿩坼å
¥ç±»å'); return } |
| | | this.runDirImport(type, this.dirPath) |
| | | }, |
| | | runDirImport(type, dir) { |
| | | if (!type) { this.$message.warning('请å
鿩坼å
¥ç±»å'); return } |
| | | if (!dir || !dir.trim()) { this.$message.warning('请å
å¡«åç®å½è·¯å¾ï¼æç¹ãæµè§â¦ãéæ©ï¼'); return } |
| | | dir = dir.trim() |
| | | this.dirImporting = true |
| | | this.dirImportFiles = [] |
| | | api.get('/import/dirs', { params: { path: dir } }) |
| | | .then(res => { |
| | | const d = (res && res.data) || {} |
| | | const files = (d.files || []).filter(n => /\.(xlsx|xls)$/i.test(n)) |
| | | if (!files.length) { this.$message.warning('该ç®å½æ²¡æ Excel æä»¶ï¼.xlsx/.xlsï¼ï¼' + dir); this.dirImporting = false; return } |
| | | localStorage.setItem('dirLast_' + type, dir) |
| | | this.dirPath = dir |
| | | this.dirImportFiles = files.map(n => ({ name: n, status: 'pending', period: '', msg: '' })) |
| | | const run = (i) => { |
| | | if (i >= files.length) { |
| | | this.dirImporting = false |
| | | const ok = this.dirImportFiles.filter(x => x.status === 'success').length |
| | | const fail = this.dirImportFiles.filter(x => x.status === 'fail').length |
| | | const skip = this.dirImportFiles.filter(x => x.status === 'skipped').length |
| | | if (fail > 0) this.$message.warning('ç®å½å¯¼å
¥å®æï¼æå ' + ok + 'ï¼å¤±è´¥ ' + fail + 'ï¼è·³è¿ ' + skip + 'ï¼å¤±è´¥åå è§å表ï¼') |
| | | else this.$message.success('ç®å½å¯¼å
¥å®æï¼æå ' + ok + 'ï¼è·³è¿ ' + skip) |
| | | this.afterImport() |
| | | return |
| | | } |
| | | const row = this.dirImportFiles[i] |
| | | row.status = 'importing' |
| | | const fd = new FormData() |
| | | fd.append('type', type) |
| | | fd.append('period', this.period || '') |
| | | fd.append('skipImported', this.dirSkipImported ? 'true' : 'false') |
| | | fd.append('path', dir.replace(/[\\/]+$/, '') + '\\' + files[i]) |
| | | api.post('/import/fromDirFile', fd, { timeout: 600000 }) |
| | | .then(res => { |
| | | const r = (res && res.data) || {} |
| | | row.period = r.period || '' |
| | | if (r.skipped) { row.status = 'skipped'; row.msg = r.error || '已跳è¿' } |
| | | else if (r.fail > 0 || r.error) { row.status = 'fail'; row.msg = r.error || ('失败 ' + r.fail + ' æ¡') } |
| | | else { row.status = 'success'; row.msg = 'è§£æ ' + r.success + ' æ¡' } |
| | | }) |
| | | .catch(e => { row.status = 'fail'; row.msg = (e.response && e.response.data && e.response.data.message) || '导å
¥å¤±è´¥' }) |
| | | .finally(() => run(i + 1)) |
| | | } |
| | | run(0) |
| | | }) |
| | | .catch(e => { |
| | | this.dirImporting = false |
| | | this.$message.error((e.response && e.response.data && e.response.data.message) || 'ç®å½ä¸åå¨ææ æ³è®¿é®ï¼' + dir) |
| | | }) |
| | | }, |
| | | progText(s) { return s === 'pending' ? 'å¾
导å
¥' : s === 'importing' ? '导å
¥ä¸â¦' : s === 'success' ? 'æå' : s === 'skipped' ? '已跳è¿' : '失败' }, |
| | | progTag(s) { return s === 'pending' ? 'info' : s === 'importing' ? 'warning' : s === 'success' ? 'success' : s === 'skipped' ? 'info' : 'danger' }, |
| | | progIcon(s) { return s === 'pending' ? 'el-icon-time' : s === 'importing' ? 'el-icon-loading' : s === 'success' ? 'el-icon-success' : s === 'skipped' ? 'el-icon-minus' : 'el-icon-error' }, |
| | | onFilePicked(key, file) { |
| | | this.$set(this.files, this.moduleSel[key], file.raw) |
| | | this.$set(this.fileNames, this.moduleSel[key], file.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)) { |
| | | this.importCityDir(type) |
| | | this.moduleSel[key] = '' |
| | | this.importType = '' |
| | | return |
| | | } |
| | | const file = this.files[type] |
| | | if (!file) { this.$message.warning('请å
为该类åéæ©æä»¶'); return } |
| | | 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) |
| | | const row = { |
| | | id: Date.now() + Math.random(), |
| | | moduleKey: key, |
| | | moduleName: g.name, |
| | | type, |
| | | typeLabel: t.label, |
| | | file, |
| | | fileName: this.fileNames[type] || file.name, |
| | | status: 'pending', |
| | | message: '' |
| | | } |
| | | this.importOne(row).then(() => { |
| | | if ((row.type === 'investment' || row.type === 'investmentLogistics') && row.status === 'success') { |
| | | this.checkInvest(this.period) |
| | | } |
| | | this.afterImport() |
| | | this.moduleSel[key] = '' |
| | | this.importType = '' |
| | | this.files[type] = null |
| | | this.$delete(this.fileNames, type) |
| | | }) |
| | | }, |
| | | isCityDirType(t) { return t === 'investCityPassenger' || t === 'investCityLogistics' }, |
| | | importCityDir(type) { |
| | | if (!this.period) { this.$message.warning('请å
éæ©æ¥è¡¨æ'); return } |
| | | const category = type === 'investCityPassenger' ? '客è¿ç«åº' : 'ç©æµååº' |
| | | const fd = new FormData() |
| | | fd.append('period', this.period) |
| | | fd.append('category', category) |
| | | this.cityDirLoading = true |
| | | api.post('/import/investCityBatch', fd, { timeout: 300000 }) |
| | | .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, 'å¸å·å表æ¹é导å
¥å®æï¼å
± ' + d.total + ' æ¡ï¼æå ' + d.success + ' æ¡' + |
| | | (d.fail > 0 ? 'ï¼å¤±è´¥ ' + d.fail + ' æ¡' : '') + |
| | | 'ï¼åäºäºé¡¹ç®å¹é
' + d.fiveYearMatched + '/' + d.fiveYearTemplateProjects + 'ï¼', { |
| | | dangerouslyUseHTMLString: true, |
| | | type: errCount > 0 ? 'warning' : 'success', |
| | | confirmButtonText: 'ç¥éäº' |
| | | }) |
| | | this.afterImport() |
| | | }) |
| | | .catch(e => { |
| | | this.$message.error((e.response && e.response.data && e.response.data.message) || 'æ¹é导å
¥å¤±è´¥') |
| | | }) |
| | | .finally(() => { this.cityDirLoading = false }) |
| | | }, |
| | | addToQueue(key) { |
| | | const type = this.moduleSel[key] |
| | | if (!type) return |
| | | const file = this.files[type] |
| | | if (!file) { this.$message.warning('请å
为该类åéæ©æä»¶'); return } |
| | | const g = this.groups.find(x => x.key === key) |
| | | const t = g.types.find(x => x.value === type) |
| | | this.queue.push({ |
| | | id: Date.now() + Math.random(), |
| | | moduleKey: key, |
| | | moduleName: g.name, |
| | | type, |
| | | typeLabel: t.label, |
| | | file, |
| | | fileName: this.fileNames[type] || file.name, |
| | | status: 'pending', |
| | | message: '' |
| | | }) |
| | | this.moduleSel[key] = '' |
| | | this.importType = '' |
| | | this.files[type] = null |
| | | this.$delete(this.fileNames, type) |
| | | }, |
| | | removeQueueItem(row) { |
| | | const i = this.queue.indexOf(row) |
| | | if (i >= 0) this.queue.splice(i, 1) |
| | | }, |
| | | importOne(row) { |
| | | if (!this.period) { this.$message.warning('请å
éæ©æ¥è¡¨æ'); return Promise.resolve() } |
| | | row.status = 'uploading' |
| | | row.message = '' |
| | | const fd = new FormData() |
| | | fd.append('file', row.file) |
| | | fd.append('period', this.period) |
| | | if (row.type === 'investment' || row.type === 'investmentLogistics') { |
| | | fd.append('category', row.type === 'investment' ? '客è¿ç«åº' : 'ç©æµååº') |
| | | } |
| | | return api.post(this.endpoint(row.type), fd, { headers: { 'Content-Type': 'multipart/form-data' }, timeout: 600000 }) |
| | | .then(res => { row.status = 'success'; row.message = res.data || '导å
¥æå' }) |
| | | .catch(e => { row.status = 'fail'; row.message = (e.response && e.response.data && e.response.data.message) || '导å
¥å¤±è´¥' }) |
| | | .finally(() => {}) |
| | | }, |
| | | importQueue() { |
| | | if (!this.period) { this.$message.warning('请å
éæ©æ¥è¡¨æ'); return Promise.resolve(false) } |
| | | const pending = this.queue.filter(x => x.status === 'pending') |
| | | if (pending.length === 0) { this.$message.warning('éå䏿²¡æå¾
导å
¥é¡¹'); return Promise.resolve(false) } |
| | | this.queueLoading = true |
| | | return new Promise(resolve => { |
| | | const run = (i) => { |
| | | if (i >= pending.length) { |
| | | this.queueLoading = false |
| | | this.afterImport() |
| | | resolve(true) |
| | | return |
| | | } |
| | | this.importOne(pending[i]).then(() => { |
| | | const t = pending[i].type |
| | | if ((t === 'investment' || t === 'investmentLogistics') && pending[i].status === 'success') { |
| | | this.checkInvest(this.period) |
| | | } |
| | | run(i + 1) |
| | | }) |
| | | } |
| | | run(0) |
| | | }) |
| | | }, |
| | | afterImport() { this.loadOverview(); this.loadBatches() }, |
| | | loadOverview() { |
| | | if (!this.period) return |
| | | api.get('/import/batches', { params: { period: this.period, limit: 200 } }) |
| | | .then(res => { |
| | | const map = {} |
| | | const records = (res.data && res.data.records) || [] |
| | | records.forEach(r => { |
| | | const key = this.batchKeyToType(r.importType) |
| | | if (!key) return |
| | | const cur = map[key] |
| | | if (!cur || (cur.latestId || 0) < (r.id || 0)) { |
| | | map[key] = { totalRows: r.totalRows, successRows: r.successRows, failRows: r.failRows, latestId: r.id } |
| | | } |
| | | }) |
| | | this.overview = map |
| | | }) |
| | | .catch(() => {}) |
| | | }, |
| | | batchKeyToType(k) { |
| | | if (BATCH_TYPE_MAP[k]) return BATCH_TYPE_MAP[k] |
| | | if (k && k.indexOf('INVEST_') === 0) { |
| | | if (k.indexOf('_CITY') >= 0) return k === 'INVEST_ç©æµååº_CITY' ? 'investCityLogistics' : 'investCityPassenger' |
| | | return k === 'INVEST_ç©æµååº' ? 'investmentLogistics' : 'investment' |
| | | } |
| | | return '' |
| | | }, |
| | | overviewState(type) { |
| | | const o = this.overview[type] |
| | | if (!o) return { cls: 'ov-empty', text: 'æªå¯¼å
¥' } |
| | | if (o.failRows > 0) return { cls: 'ov-fail', text: 'é¨å失败ï¼å¤±è´¥ ' + o.failRows + ' è¡ï¼' } |
| | | if (o.successRows > 0) return { cls: 'ov-ok', text: '已导å
¥ ' + o.successRows + ' è¡' } |
| | | return { cls: 'ov-empty', text: 'æ æ°æ®' } |
| | | }, |
| | | loadBatches() { |
| | | this.batchLoading = true |
| | | const params = { limit: 50 } |
| | | if (this.period) params.period = this.period |
| | | api.get('/import/batches', { params }) |
| | | .then(res => { this.batches = (res.data && res.data.records) || [] }) |
| | | .catch(() => {}) |
| | | .finally(() => { this.batchLoading = false }) |
| | | }, |
| | | checkInvest(period) { |
| | | const fd = new FormData() |
| | | fd.append('period', period) |
| | | fd.append('reportType', 'INVEST') |
| | | api.post('/audit/execute', fd, { timeout: 120000 }) |
| | | .then(res => { |
| | | const list = res.data || [] |
| | | const pending = list.filter(x => x.status === 'PENDING') |
| | | if (pending.length > 0) { |
| | | const html = pending.map(x => { |
| | | const name = x.enterpriseName || x.actualValue || '' |
| | | return '<div style="margin:8px 0;line-height:1.6"><b>' + (x.ruleName || '') + '</b>ï¼' + name + |
| | | '<br/>å®é
ï¼' + (x.actualValue || '') + 'ãåºåï¼' + (x.thresholdValue || '') + |
| | | '<br/>å·®å¼ï¼' + (x.deviation || '') + '</div>' |
| | | }).join('') |
| | | this.$alert(html, 'æèµæ°æ®ä¸æèµç³»ç»ä¸ä¸è´ï¼' + pending.length + ' æ¡ï¼', { |
| | | dangerouslyUseHTMLString: true, type: 'warning', confirmButtonText: 'ç¥éäº' |
| | | }) |
| | | } else { |
| | | this.$message.success('已䏿èµç³»ç»æ ¸å¯¹ï¼æ¬ææ°æ®ä¸è´') |
| | | } |
| | | }) |
| | | .catch(() => {}) |
| | | } |
| | | }, |
| | | methods: { |
| | | handleFileChange(file) { this.file = file.raw }, |
| | | importData() { |
| | | if (!this.file) { this.error = 'è¯·éæ©æä»¶'; return } |
| | | if (!this.period) { this.error = 'è¯·éæ©æ¥è¡¨æ'; return } |
| | | this.loading = true; this.error = ''; this.result = '' |
| | | let fd = new FormData() |
| | | fd.append('file', this.file) |
| | | fd.append('period', this.period) |
| | | api.post(this.endpoint, fd, { headers: { 'Content-Type': 'multipart/form-data' } }) |
| | | .then(res => { |
| | | this.result = res.data || '导å
¥æå' |
| | | this.fileList = [] |
| | | this.file = null |
| | | }) |
| | | .catch(e => { this.error = (e.response && e.response.data && e.response.data.message) || '导å
¥å¤±è´¥' }) |
| | | .finally(() => { this.loading = false }) |
| | | } |
| | | mounted() { |
| | | this.loadBatches() |
| | | this.loadOverview() |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .tip { font-size: 12px; color: #909399; margin-top: 6px; } |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | .import-card { margin-bottom: 0; } |
| | | .card-head { font-weight: 600; margin-bottom: 6px; } |
| | | .module-tabs >>> .el-tabs__header { margin-bottom: 12px; } |
| | | .module-sel { max-width: 480px; } |
| | | .group-label { font-size: 13px; color: #606266; font-weight: 600; margin-bottom: 6px; padding-left: 6px; border-left: 3px solid #409EFF; } |
| | | .module-file { margin-top: 8px; } |
| | | .file-name { font-size: 12px; color: #606266; background: #F5F7FA; border: 1px solid #EBEEF5; border-radius: 4px; padding: 4px 8px; margin-bottom: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .file-btns { display: flex; gap: 6px; } |
| | | .type-info { margin-top: 12px; color: #606266; font-size: 13px; background: #F5F7FA; border-radius: 4px; padding: 8px 10px; } |
| | | .tip { font-size: 12px; color: #909399; margin-top: 6px; line-height: 1.5; } |
| | | .slow-tip { font-size: 12px; color: #E6A23C; margin-left: 8px; } |
| | | .overview-grid { display: flex; flex-direction: column; gap: 8px; } |
| | | .ov-row { display: flex; align-items: stretch; gap: 8px; } |
| | | .ov-group { flex: none; width: 90px; display: flex; align-items: center; font-weight: 600; color: #606266; font-size: 13px; } |
| | | .ov-item { flex: 1; min-width: 0; border: 1px solid #EBEEF5; border-radius: 6px; padding: 6px 8px; background: #FAFAFA; } |
| | | .ov-item.ov-ok { background: #F0F9EB; border-color: #E1F3D8; } |
| | | .ov-item.ov-fail { background: #FDF6EC; border-color: #FAECD8; } |
| | | .ov-name { font-size: 12px; color: #303133; } |
| | | .ov-status { font-size: 12px; margin-top: 2px; } |
| | | .ov-ok .ov-status { color: #67C23A; } |
| | | .ov-fail .ov-status { color: #E6A23C; } |
| | | .ov-empty .ov-status { color: #C0C4CC; } |
| | | .queue-msg { font-size: 12px; color: #606266; word-break: break-all; white-space: pre-line; } |
| | | .dir-import-bar { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; flex-wrap: wrap; } |
| | | .dir-progress { margin-top: 8px; border: 1px solid #EBEEF5; border-radius: 4px; padding: 8px; background: #FAFAFA; } |
| | | .dir-progress-row { display: flex; align-items: center; gap: 8px; padding: 4px 2px; font-size: 12px; color: #303133; flex-wrap: wrap; } |
| | | .dir-progress-row i { color: #409EFF; } |
| | | .dir-progress-row .prog-name { min-width: 0; max-width: 380px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| | | .dir-progress-row .queue-msg { width: 100%; padding-left: 22px; } |
| | | .dir-toolbar { margin-bottom: 6px; } |
| | | .dir-path { font-size: 12px; color: #606266; background: #F5F7FA; border: 1px solid #EBEEF5; border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; word-break: break-all; } |
| | | .dir-body { max-height: 320px; overflow-y: auto; border: 1px solid #EBEEF5; border-radius: 4px; } |
| | | .dir-row { display: flex; align-items: center; gap: 6px; padding: 7px 10px; cursor: pointer; font-size: 13px; color: #303133; } |
| | | .dir-row:hover { background: #ECF5FF; color: #409EFF; } |
| | | .dir-row i { color: #E6A23C; } |
| | | .dir-empty { padding: 14px; font-size: 12px; color: #909399; text-align: center; } |
| | | .dir-files { margin-top: 8px; } |
| | | |
| | | </style> |
| | |
| | | <el-radio-button label="trackMileage">轨迹éç¨</el-radio-button> |
| | | <el-radio-button label="scaleSplit">è§ä¸è§ä¸æå</el-radio-button> |
| | | <el-radio-button label="freightTurnover">è´§è¿éå¨è½¬é</el-radio-button> |
| | | <el-radio-button label="h2031">H203-1æ
å®¢ææ¥</el-radio-button> |
| | | <el-radio-button label="passengerAuth">æ
å®¢è¿æ¿è½¦è¾</el-radio-button> |
| | | <el-radio-button label="cityBus">åå¸å
¬äº¤ææ¥</el-radio-button> |
| | | <el-radio-button label="cityTaxi">巡游åºç§ææ¥</el-radio-button> |
| | | <el-radio-button label="cityTaxiAuth">åºç§è½¦è¿æ¿è½¦è¾</el-radio-button> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨æ"> |
| | |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="loadData">æ¥è¯¢</el-button> |
| | | <el-button type="success" icon="el-icon-full-screen" @click="toggleFullscreen">å
¨å±æ¥ç</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div style="color:#909399;font-size:12px;margin-bottom:6px"> |
| | | å
± {{ total }} æ¡è®°å½ï¼å½å第 {{ page }} 页 |
| | | </div> |
| | | <div class="table-area"> |
| | | <div class="table-area" ref="tableArea"> |
| | | <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%"> |
| | | <el-table-column v-for="c in columns" :key="c.prop" :prop="c.prop" :label="c.label" :min-width="c.width" show-overflow-tooltip> |
| | | <template slot-scope="scope"> |
| | |
| | | this.loading = false |
| | | }) |
| | | }, |
| | | toggleFullscreen() { |
| | | const el = this.$refs.tableArea |
| | | if (!document.fullscreenElement) { |
| | | if (el && el.requestFullscreen) el.requestFullscreen() |
| | | else this.$message.warning("æµè§å¨ä¸æ¯æå
¨å±") |
| | | } else if (document.exitFullscreen) { |
| | | document.exitFullscreen() |
| | | } |
| | | }, |
| | | formatCell(v) { |
| | | if (v === null || v === undefined || v === '') return '-' |
| | | return v |
| | | } |
| | | }, |
| | | mounted() { |
| | | const q = this.$route.query |
| | | if (q.type) this.type = q.type |
| | | if (q.period) this.period = q.period |
| | | if (q.keyword) this.keyword = q.keyword |
| | | this.loadData() |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | #app .table-area:fullscreen { |
| | | padding: 20px; |
| | | background: #fff; |
| | | overflow: auto; |
| | | } |
| | | </style> |
| | |
| | | </el-aside> |
| | | <el-main> |
| | | <h3>ä¼ä¸è§£é</h3> |
| | | <el-form :inline="true"> |
| | | <el-form :inline="true" @submit.native.prevent> |
| | | <el-form-item label="æ¥è¡¨ç±»å"> |
| | | <el-select v-model="reportType" style="width:190px" @change="onTypeChange"> |
| | | <el-option label="è´§è¿ï¼H203-2ï¼" value="H2032"></el-option> |
| | | <el-option label="å
¬è·¯æ
客ï¼H203-1ï¼" value="H2031"></el-option> |
| | | <el-option label="åå¸å
¬äº¤ï¼CITY_BUSï¼" value="CITY_BUS"></el-option> |
| | | <el-option label="巡游åºç§ï¼CITY_TAXIï¼" value="CITY_TAXI"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥è¡¨æ"> |
| | | <el-date-picker v-model="period" type="month" placeholder="å
¨é¨" value-format="yyyy-MM" clearable style="width:140px" @change="loadData"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="ä¼ä¸åç§°"> |
| | | <el-input v-model="keyword" placeholder="è¾å
¥ä¼ä¸åç§°å
³é®å" clearable style="width:220px" @keyup.enter.native="loadData"></el-input> |
| | | <el-form-item :label="keywordLabel"> |
| | | <el-input v-model="keyword" :placeholder="keywordPlaceholder" clearable style="width:220px" @keyup.enter.native="loadData"></el-input> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="loadData">æ¥è¯¢</el-button> |
| | | </el-form-item> |
| | | <el-form-item label="ä»
æ¾ç¤ºå·²å¡«å"> |
| | | <el-switch v-model="hasExplanation" @change="onFilterChange"></el-switch> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <span style="color:#909399;font-size:12px">å
± {{ total }} æ¡è§£é</span> |
| | | <span style="color:#909399;font-size:12px">å
± {{ total }} æ¡è®°å½<template v-if="hasExplanation">ï¼å·²å¡«åè§£éï¼</template></span> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table-area"> |
| | | <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%"> |
| | | <el-table-column prop="reportPeriod" label="æ¥è¡¨æ" width="100"></el-table-column> |
| | | <el-table-column prop="regionCode" label="æå±å°åº" width="100"></el-table-column> |
| | | <el-table-column prop="enterpriseCode" label="ä¼ä¸ä»£ç " min-width="140" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="enterpriseName" label="ä¼ä¸åç§°" min-width="200" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="vehicleTotal" label="è½¦è¾æ°" width="90"></el-table-column> |
| | | <el-table-column prop="tonsTotal" label="å¨ä½" width="100"></el-table-column> |
| | | <el-table-column prop="freightTotal" label="è´§è¿é" width="110"></el-table-column> |
| | | <el-table-column prop="turnoverTotal" label="å¨è½¬é" width="120"></el-table-column> |
| | | <el-table-column prop="avgDistance" label="å¹³åè¿è·" width="100"></el-table-column> |
| | | <el-table-column prop="verifyExplanation" label="ä¼ä¸è§£é" min-width="280" show-overflow-tooltip></el-table-column> |
| | | <el-table-column label="æä½" width="100" fixed="right"> |
| | | <el-table :data="records" border v-loading="loading" size="mini" :row-style="{ height: '32px' }" style="width:100%" height="100%"> |
| | | <el-table-column v-for="col in columns" :key="col.prop" v-bind="col"> |
| | | <template slot-scope="scope"> |
| | | <template v-if="col.prop === 'verifyExplanation'"> |
| | | <span v-if="scope.row.verifyExplanation" class="expl-text">{{ scope.row.verifyExplanation }}</span> |
| | | <el-tag v-else size="mini" type="warning" effect="plain">æªå¡«å</el-tag> |
| | | </template> |
| | | <span v-else>{{ scope.row[col.prop] }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="110" fixed="right"> |
| | | <template slot-scope="scope"> |
| | | <el-button size="mini" type="primary" @click="analyze(scope.row)">AI åæ</el-button> |
| | | </template> |
| | |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | import api from '@/api' |
| | | import AiChatDialog from '@/components/AiChatDialog' |
| | | const COLS = { |
| | | H2032: [ |
| | | { prop: 'reportPeriod', label: 'æ¥è¡¨æ', width: 100 }, |
| | | { prop: 'regionCode', label: 'æå±å°åº', width: 100 }, |
| | | { prop: 'enterpriseCode', label: 'ä¼ä¸ä»£ç ', minWidth: 140, showOverflowTooltip: true }, |
| | | { prop: 'enterpriseName', label: 'ä¼ä¸åç§°', minWidth: 200, showOverflowTooltip: true }, |
| | | { prop: 'vehicleTotal', label: 'è½¦è¾æ°', width: 90 }, |
| | | { prop: 'tonsTotal', label: 'å¨ä½', width: 100 }, |
| | | { prop: 'freightTotal', label: 'è´§è¿é(å¨)', width: 110 }, |
| | | { prop: 'turnoverTotal', label: 'å¨è½¬é(å¨å
Ž)', width: 130 }, |
| | | { prop: 'avgDistance', label: 'å¹³åè¿è·(å
Ž)', width: 120 }, |
| | | { prop: 'verifyExplanation', label: 'ä¼ä¸è§£é', minWidth: 260, showOverflowTooltip: true } |
| | | ], |
| | | H2031: [ |
| | | { prop: 'reportPeriod', label: 'æ¥è¡¨æ', width: 100 }, |
| | | { prop: 'regionCode', label: 'æå±å°åº', width: 100 }, |
| | | { prop: 'enterpriseCode', label: 'ä¼ä¸ä»£ç ', minWidth: 140, showOverflowTooltip: true }, |
| | | { prop: 'enterpriseName', label: 'ä¼ä¸åç§°', minWidth: 200, showOverflowTooltip: true }, |
| | | { prop: 'vehicleTotal', label: 'è½¦è¾æ°', width: 90 }, |
| | | { prop: 'seatTotal', label: 'è½½å®¢ä½æ°', width: 100 }, |
| | | { prop: 'passengerTotal', label: '客è¿é(ä¸äºº)', width: 110 }, |
| | | { prop: 'turnoverTotal', label: 'å¨è½¬é(ä¸äººå
Ž)', width: 130 }, |
| | | { prop: 'avgDistance', label: 'å¹³åè¿è·(å
Ž)', width: 120 }, |
| | | { prop: 'verifyExplanation', label: 'ä¼ä¸è§£é', minWidth: 260, showOverflowTooltip: true } |
| | | ], |
| | | CITY_BUS: [ |
| | | { prop: 'reportPeriod', label: 'æ¥è¡¨æ', width: 100 }, |
| | | { prop: 'regionCode', label: 'æå±å°åº', width: 100 }, |
| | | { prop: 'city', label: 'å¸å·', width: 100 }, |
| | | { prop: 'enterpriseName', label: 'ä¼ä¸åç§°', minWidth: 220, showOverflowTooltip: true }, |
| | | { prop: 'opVehicles', label: 'è¿è¥è½¦æ°', width: 90 }, |
| | | { prop: 'passengerVolume', label: '客è¿é(ä¸äººæ¬¡)', width: 120 }, |
| | | { prop: 'turnover', label: 'å¨è½¬é(ä¸äººå
Ž)', width: 130 }, |
| | | { prop: 'avgDistance', label: 'å¹³åè¿è·(å
Ž)', width: 120 }, |
| | | { prop: 'verifyExplanation', label: 'ä¼ä¸è§£é', minWidth: 260, showOverflowTooltip: true } |
| | | ], |
| | | CITY_TAXI: [ |
| | | { prop: 'reportPeriod', label: 'æ¥è¡¨æ', width: 100 }, |
| | | { prop: 'regionCode', label: 'æå±å°åº', width: 100 }, |
| | | { prop: 'city', label: 'å¸å·', width: 110 }, |
| | | { prop: 'tripTotal', label: 'è½½å®¢è½¦æ¬¡æ»æ°', width: 110 }, |
| | | { prop: 'passengerVolume', label: '客è¿é(ä¸äººæ¬¡)', width: 120 }, |
| | | { prop: 'turnover', label: 'å¨è½¬é(ä¸äººå
Ž)', width: 130 }, |
| | | { prop: 'opVehicles', label: 'è¿è¥è½¦è¾æ°', width: 100 }, |
| | | { prop: 'avgDistance', label: 'å¹³åè¿è·(å
Ž)', width: 120 }, |
| | | { prop: 'verifyExplanation', label: 'ä¼ä¸è§£é', minWidth: 260, showOverflowTooltip: true } |
| | | ] |
| | | } |
| | | const TABLE_NAME = { |
| | | H2032: 'h2032_enterprise_monthly', |
| | | H2031: 'h2031_enterprise_monthly', |
| | | CITY_BUS: 'city_bus_monthly', |
| | | CITY_TAXI: 'city_taxi_monthly' |
| | | } |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'ExplainReview', |
| | | components: { AppHeader, AiChatDialog }, |
| | | components: { AiChatDialog }, |
| | | data() { |
| | | return { |
| | | period: '', keyword: '', page: 1, size: 20, total: 0, records: [], loading: false, |
| | | reportType: 'H2032', |
| | | period: '', keyword: '', hasExplanation: false, page: 1, size: 20, total: 0, records: [], loading: false, |
| | | chatVisible: false, chatContext: {} |
| | | } |
| | | }, |
| | | computed: { |
| | | columns() { return COLS[this.reportType] || COLS.H2032 }, |
| | | keywordLabel() { return this.reportType === 'CITY_TAXI' ? 'å¸å·åç§°' : 'ä¼ä¸åç§°' }, |
| | | keywordPlaceholder() { return this.reportType === 'CITY_TAXI' ? 'è¾å
¥å¸å·åç§°å
³é®å' : 'è¾å
¥ä¼ä¸åç§°å
³é®å' } |
| | | }, |
| | | methods: { |
| | | onTypeChange() { this.page = 1; this.loadData() }, |
| | | onFilterChange() { this.page = 1; this.loadData() }, |
| | | loadData() { |
| | | this.loading = true |
| | | api.get('/explain/list', { |
| | | params: { |
| | | reportType: this.reportType, |
| | | period: this.period || undefined, |
| | | keyword: this.keyword || undefined, |
| | | hasExplanation: this.hasExplanation || undefined, |
| | | page: this.page, |
| | | size: this.size |
| | | } |
| | |
| | | this.chatContext = { |
| | | reportId: row.id, |
| | | reportPeriod: row.reportPeriod || this.period, |
| | | enterpriseName: row.enterpriseName || '', |
| | | verifyExplanation: explanation |
| | | enterpriseName: row.enterpriseName || row.city || '', |
| | | verifyExplanation: explanation, |
| | | reportType: this.reportType, |
| | | tableName: TABLE_NAME[this.reportType] || '' |
| | | } |
| | | this.chatVisible = true |
| | | } |
| | | }, |
| | | mounted() { |
| | | const q = this.$route.query |
| | | if (q.reportType && COLS[q.reportType]) this.reportType = q.reportType |
| | | if (q.period) this.period = q.period |
| | | if (q.keyword) this.keyword = q.keyword |
| | | this.loadData() |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | .el-table td { padding: 3px 0; } |
| | | .el-table th { padding: 4px 0; } |
| | | .expl-text { display: inline-block; max-height: 62px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; line-height: 1.4; } |
| | | </style> |
| | |
| | | if (d.code === 200) { |
| | | localStorage.setItem('token', d.data.token) |
| | | localStorage.setItem('username', d.data.username) |
| | | localStorage.setItem('realName', d.data.realName || '') |
| | | localStorage.setItem('modules', d.data.modules || '') |
| | | this.$message.success('ç»å½æå') |
| | | this.$router.push('/dashboard') |
| | | } else if (d.code === 401) { |
| | |
| | | <app-header title="交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - æ¥è¡¨çæ"></app-header> |
| | | <el-container> |
| | | <el-aside width="200px"> |
| | | <el-menu router default-active="/report"> |
| | | <el-menu router :default-active="activeMenu"> |
| | | <el-menu-item index="/dashboard">å·¥ä½å°</el-menu-item> |
| | | <el-menu-item index="/import">æ°æ®å¯¼å
¥</el-menu-item> |
| | | <el-menu-item index="/data">æ°æ®æ¥ç</el-menu-item> |
| | |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | | <!-- 第ä¸è¡ï¼æ é¢ + æ´æ°æ¶é´ --> |
| | | <div class="topbar"> |
| | | <h3>çæç»è®¡æ¥è¡¨</h3> |
| | | <el-card style="max-width:640px"> |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="æ¥è¡¨æ"> |
| | | <el-date-picker v-model="period" type="month" placeholder="éæ©æä»½" value-format="yyyy-MM" style="width:100%"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="exportReport('cityDetail')" :loading="loading === 'cityDetail'">çæ_è´§è¿éåå¸å·æç»</el-button> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="exportReport('freightRank')" :loading="loading === 'freightRank'">çæ_è´§è¿éæå</el-button> |
| | | <el-button type="primary" @click="exportReport('turnoverRank')" :loading="loading === 'turnoverRank'">çæ_å¨è½¬éæå</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-card> |
| | | <el-alert |
| | | title="æç¤ºï¼æ¥è¡¨ç±ç³»ç»æ ¹æ®å·²å¯¼å
¥æ°æ®èªå¨è®¡ç®çæãè´§è¿éï¼å计æ¥èªæ¨¡æ¿_è´§è¿éå¨è½¬éï¼å·¦å1-Næï¼ãè§ä¸æ¥èªH203-2ææ¥ãè§ä¸=å计-è§ä¸ï¼å¨è½¬éæ¥èªè§ä¸è§ä¸æå表ã" |
| | | type="info" style="margin-top:15px" :closable="false"></el-alert> |
| | | <div class="topbar-right"> |
| | | <span v-if="myModules.length" class="focus-user">{{ realName || username }}ï¼è´è´£ {{ myModules.map(moduleTitle).join('ã') }}</span> |
| | | <span class="update-time">æ´æ°æ¶é´ï¼{{ updatedAt }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第äºè¡ï¼æ¥è¡¨ç±»å Tabï¼è´§è¿ / æ
客 / æèµ / è½è / åå¸å®¢è¿ï¼ --> |
| | | <el-tabs v-model="activeKey" class="module-tabs"> |
| | | <el-tab-pane v-for="m in modules" :key="m.key" :name="m.key"> |
| | | <span slot="label">{{ m.title }}<el-tag v-if="m.tag" size="mini" :type="m.tagType || 'info'" style="margin-left:6px">{{ m.tag }}</el-tag></span> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- 第ä¸è¡ï¼æ¥è¯¢æ¡ä»¶åºï¼å¨æ + æä»½ + æ¥è¯¢ï¼ --> |
| | | <section class="query-panel"> |
| | | <span class="ctrl-label">卿</span> |
| | | <el-radio-group v-model="cycle" size="small" @change="onCycleChange"> |
| | | <el-radio-button v-for="opt in cycleOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio-button> |
| | | </el-radio-group> |
| | | <span class="ctrl-label">{{ cycle === 'year' ? '年份' : 'æä»½' }}</span> |
| | | <el-select v-model="period" size="small" style="width:190px" :placeholder="periodPlaceholder" @change="onPeriodChange"> |
| | | <el-option-group v-for="grp in periodGroups" :key="grp.label" :label="grp.label"> |
| | | <el-option v-for="opt in grp.options" :key="opt.value" :label="opt.label" :value="opt.value"></el-option> |
| | | </el-option-group> |
| | | </el-select> |
| | | <el-button type="primary" size="small" icon="el-icon-search" @click="doQuery">æ¥è¯¢</el-button> |
| | | </section> |
| | | |
| | | <!-- 第åè¡ï¼æ¥è¡¨å表 --> |
| | | <section class="module-panel"> |
| | | <div class="panel-head"> |
| | | <div class="panel-title">æ¥è¡¨å表<span class="muted">ï¼{{ curModule.title }}ï¼</span></div> |
| | | <div class="panel-controls"> |
| | | <el-checkbox :value="allChecked" @change="toggleAll">å
¨é</el-checkbox> |
| | | <span class="checked-count">å·²é {{ curModule.checked.length }} å¼ </span> |
| | | <el-button size="small" icon="el-icon-view" :disabled="!curModule.checked.length" @click="previewChecked()">æ¹éé¢è§</el-button> |
| | | <el-button size="small" type="primary" icon="el-icon-download" :disabled="!curModule.checked.length" @click="exportChecked()">æ¹é导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table :data="curModule.items" size="mini" class="report-table"> |
| | | <el-table-column width="44"> |
| | | <template slot-scope="{ row }"> |
| | | <el-checkbox :value="curModule.checked.indexOf(row.key) >= 0" @change="toggleChecked(row)"></el-checkbox> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ¥è¡¨åç§°" prop="label" min-width="220"></el-table-column> |
| | | <el-table-column label="æ°æ®ç¶æ" width="180"> |
| | | <template slot-scope="{ row }"> |
| | | <el-tag v-if="row.status === 'ready'" size="mini" type="success">æ°æ®å°±ç»ª</el-tag> |
| | | <el-tag v-else-if="row.status === 'unaudited'" size="mini" type="warning">æªå®¡æ ¸</el-tag> |
| | | <el-tag v-else-if="row.status === 'missing'" size="mini" type="danger">ç¼ºæ°æ®</el-tag> |
| | | <el-tag v-else size="mini" type="info">â</el-tag> |
| | | <div v-if="row.statusNote" class="status-note">{{ row.statusNote }}</div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" min-width="190"> |
| | | <template slot-scope="{ row }"> |
| | | <span class="note-text">{{ noteOf(row.key) || 'ç¹å»å¾æ æ·»å 夿³¨' }}</span> |
| | | <el-button size="mini" type="text" icon="el-icon-edit" title="ç¼è¾å¤æ³¨" @click="openNoteDialog(row)"></el-button> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="170" align="right"> |
| | | <template slot-scope="{ row }"> |
| | | <el-button size="mini" icon="el-icon-view" @click="previewOne(row)">é¢è§</el-button> |
| | | <el-button size="mini" type="primary" icon="el-icon-download" @click="exportOne(row)">导åº</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="panel-footer">å
± {{ curModule.items.length }} å¼ æ¥è¡¨</div> |
| | | </section> |
| | | |
| | | <el-alert title="ä½¿ç¨æç¤º" type="info" style="margin-top:15px" :closable="false"> |
| | | <div class="tip-text"> |
| | | <p>1. éæ©æ¥è¡¨ç±»å Tabã卿䏿份åç¹å»ãæ¥è¯¢ãï¼åè¡¨ä¼æ¾ç¤ºæ¯å¼ æ¥è¡¨çæ°æ®ç¶æï¼æ°æ®å°±ç»ª / ç¼ºæ°æ® / æªå®¡æ ¸ï¼ã</p> |
| | | <p>2. æ°æ®æ ¡éªä¸å®¡æ ¸è¯·å°ãå®¡æ ¸ç»æã页æ¥çï¼è§åå¯å¨ãè§å管çã页é
ç½®ã</p> |
| | | <p>3. åå¼ å¯ç´æ¥ç¹ãé¢è§ / 导åºãï¼å¤å¼ å¯å¾éåãæ¹éé¢è§ / æ¹é导åºãï¼å¤å¼ 导åºèªå¨æå
为 zipã</p> |
| | | <p>4. ææ¥ï¼é 2026-07 çæ 1~7 ææ°æ®ï¼å¹´æ¥ï¼é年份çæå
¨å¹´ 1~12 æ + ä¸å¹´åæ¯ã</p> |
| | | </div> |
| | | </el-alert> |
| | | </div> |
| | | <el-dialog :title="'é¢è§æ°æ® - ' + previewModule + 'ï¼' + previewPeriod + 'ï¼'" :visible.sync="previewVisible" width="760px"> |
| | | <div class="preview-body"> |
| | | <div v-for="it in previewItems" :key="it.key" class="preview-item"> |
| | | <div class="preview-head"> |
| | | <span class="preview-name">{{ it.label }}</span> |
| | | <span class="preview-rows">å
± {{ it.rowCount || 0 }} è¡</span> |
| | | </div> |
| | | <div class="preview-coverage"> |
| | | <span class="cov-label">æåº¦è¦çï¼</span> |
| | | <span :class="it.ready ? 'cov-ok' : 'cov-bad'">{{ coverageText(it) }}</span> |
| | | <span v-if="!it.ready" class="cov-bad" style="margin-left:10px">{{ it.note }}</span> |
| | | <span v-if="it.ready && it.audited === false" class="cov-bad" style="margin-left:10px">æªå®¡æ ¸</span> |
| | | </div> |
| | | <table class="preview-summary" v-if="it.summary && it.summary.length"> |
| | | <tbody> |
| | | <tr v-for="(s, i) in it.summary" :key="i"> |
| | | <td>{{ s.name }}</td> |
| | | <td><span v-if="s.value === null || s.value === ''" class="cell-empty">ï¼ç©ºï¼</span>{{ fmtVal(s.value) }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | <div class="preview-problems" v-if="it.problems && it.problems.length"> |
| | | <div v-for="(p, i) in it.problems" :key="i" class="prob-item">· {{ p }}</div> |
| | | </div> |
| | | <div class="preview-ok" v-else-if="it.ready">æ°æ®å®æ´ï¼å¯æ£å¸¸çæ</div> |
| | | </div> |
| | | </div> |
| | | <span slot="footer"> |
| | | <el-button size="small" @click="previewVisible = false">å
³é</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | |
| | | <!-- 夿³¨ç¼è¾å¼¹çª --> |
| | | <el-dialog title="ç¼è¾å¤æ³¨" :visible.sync="noteDialogVisible" width="480px"> |
| | | <el-input v-model="noteDraft" type="textarea" :rows="4" placeholder="å¡«å该æ¥è¡¨ç夿³¨è¯´æï¼ä¿å忬å°è®°å¿ï¼"></el-input> |
| | | <span slot="footer"> |
| | | <el-button size="small" @click="noteDialogVisible = false">åæ¶</el-button> |
| | | <el-button size="small" type="primary" @click="saveNote">ä¿å</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | </el-main> |
| | | </el-container> |
| | | </el-container> |
| | | </template> |
| | | |
| | | <script> |
| | | import AppHeader from '@/components/AppHeader.vue' |
| | | import api from '@/api' |
| | | |
| | | export default { |
| | | components: { AppHeader }, |
| | | name: 'ReportExport', |
| | | data() { |
| | | return { period: '', loading: '' } |
| | | return { |
| | | loading: '', |
| | | activeMenu: '/report', |
| | | activeKey: '', |
| | | updatedAt: '', |
| | | // æ¥è¯¢æ¡ä»¶ï¼cycle 卿ï¼month ææ¥ / quarter 壿¥ / year å¹´æ¥ï¼ï¼period æ¥è¡¨æï¼æä»½ yyyy-MM æå¹´ä»½ yyyyï¼ |
| | | cycle: 'month', |
| | | period: '', |
| | | // é¢è§å¼¹çª |
| | | previewVisible: false, |
| | | previewItems: [], |
| | | previewModule: '', |
| | | previewPeriod: '', |
| | | // 夿³¨ç¼è¾å¼¹çª |
| | | noteDialogVisible: false, |
| | | noteEditingKey: '', |
| | | noteDraft: '', |
| | | modules: [ |
| | | { |
| | | key: 'freight', title: 'è´§è¿æ¥è¡¨', items: [ |
| | | { key: 'cityDetail', label: 'è´§è¿éåå¸å·æç»', fileName: 'çæ_è´§è¿éåå¸å·æç».xlsx', status: null, statusNote: '' }, |
| | | { key: 'freightRank', label: 'è´§è¿éæå', fileName: 'çæ_è´§è¿éæå.xlsx', status: null, statusNote: '' }, |
| | | { key: 'turnoverRank', label: 'å¨è½¬éæå', fileName: 'çæ_å¨è½¬éæå.xlsx', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | }, |
| | | methods: { |
| | | exportReport(type) { |
| | | if (!this.period) { this.$message.warning('è¯·éæ©æ¥è¡¨æ'); return } |
| | | this.loading = type |
| | | const token = localStorage.getItem('token') || '' |
| | | api.get('/report/export/' + type, { params: { period: this.period }, responseType: 'blob' }) |
| | | { |
| | | key: 'passenger', title: 'æ
客æ¥è¡¨', items: [ |
| | | { key: 'passengerCityDetail', label: 'å
¬è·¯æ
客åå¸å·', fileName: 'çæ_å
¬è·¯æ
客åå¸å·.xlsx', status: null, statusNote: '' }, |
| | | { key: 'passengerMidDetail', label: 'ä¸å£å¾æç»', fileName: 'çæ_å
¬è·¯æ
客ä¸å£å¾æç».xlsx', status: null, statusNote: '' }, |
| | | { key: 'passengerMidRank', label: 'ä¸å£å¾æå', fileName: 'çæ_å
¬è·¯æ
客ä¸å£å¾æå.xlsx', status: null, statusNote: '' }, |
| | | { key: 'passengerMidAnalysis', label: 'ä¸å£å¾åæ', fileName: 'çæ_å
¬è·¯æ
客ä¸å£å¾åæ.xlsx', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | }, { |
| | | key: 'cityPassenger', title: 'åå¸å®¢è¿æ¥è¡¨', tag: 'å
¬äº¤/åºç§/轨é轮渡/æ±æ»', tagType: 'success', 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: '' } |
| | | ], checked: [] |
| | | }, |
| | | { |
| | | key: 'invest', title: 'æèµæ¥è¡¨', items: [ |
| | | { key: 'investPlan', label: 'å
¨çé¢å®æè®¡åè¿åº¦æ±æ»', fileName: 'çæ_å
¨çé¢å®æè®¡åè¿åº¦æ
嵿±æ».xls', status: null, statusNote: '' }, |
| | | { key: 'investFiveYearLogistics', label: 'åäºäºè§åç©æµé¡¹ç®è¿å±', fileName: 'çæ_åäºäºè§åç©æµé¡¹ç®è¿å±æ
åµ.xls', status: null, statusNote: '' }, |
| | | { key: 'investBillion', label: '亿å
æèµé¡¹ç®', fileName: 'çæ_æ¹åçï¼å®¢è´§ç«åºï¼äº¿å
æèµé¡¹ç®.xls', status: null, statusNote: '' }, |
| | | { key: 'investCounty', label: 'ç»æµå¼ºå¿é¡¹ç®æèµç»è®¡', fileName: 'çæ_ç»æµå¼ºå¿äº¤éç©æµåºç¡è®¾æ½æèµç»è®¡æ¥è¡¨.xls', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | }, |
| | | { |
| | | key: 'energy', title: 'è½èæ¥è¡¨', items: [ |
| | | { key: 'energySummary', label: 'è½è¿æ±æ»è¡¨', fileName: 'çæ_è½è¿æ±æ»è¡¨.xlsx', status: null, statusNote: '' } |
| | | ], checked: [] |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | computed: { |
| | | username() { return localStorage.getItem('username') || '' }, |
| | | realName() { return localStorage.getItem('realName') || '' }, |
| | | myModules() { |
| | | const s = localStorage.getItem('modules') |
| | | if (!s) return [] |
| | | return s.split(',').filter(Boolean) |
| | | }, |
| | | curModule() { |
| | | return this.modules.find(m => m.key === this.activeKey) || this.modules[0] |
| | | }, |
| | | allChecked() { |
| | | const m = this.curModule |
| | | return m.items.length > 0 && m.checked.length === m.items.length |
| | | }, |
| | | // 卿é项ï¼è½èä¸ºå£æ¥ï¼å£åº¦æ«æï¼ï¼å
¶ä½ä¸ºææ¥ / å¹´æ¥ |
| | | cycleOptions() { |
| | | if (this.activeKey === 'energy') return [{ label: '壿¥', value: 'quarter' }] |
| | | return [{ label: 'ææ¥', value: 'month' }, { label: 'å¹´æ¥', value: 'year' }] |
| | | }, |
| | | periodPlaceholder() { |
| | | return this.cycle === 'year' ? '鿩年份' : 'éæ©æä»½' |
| | | }, |
| | | // æ¥è¡¨æä¸æåç»ï¼å¹´æ¥æå¹´ä»½ï¼ææ¥/壿¥æå¹´åç»å 1-12 æï¼è½èåªåå£åº¦æ«æï¼ |
| | | periodGroups() { |
| | | const year = new Date().getFullYear() |
| | | const years = [year - 1, year, year + 1] |
| | | if (this.cycle === 'year') { |
| | | return [{ label: '鿩年份', options: years.map(y => ({ label: y + '年', value: String(y) })) }] |
| | | } |
| | | const months = this.activeKey === 'energy' ? [3, 6, 9, 12] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] |
| | | return years.map(y => ({ |
| | | label: y + 'å¹´' + (this.activeKey === 'energy' ? 'ï¼å£åº¦æ«æï¼' : ''), |
| | | options: months.map(mo => ({ label: y + 'å¹´' + mo + 'æ', value: y + '-' + String(mo).padStart(2, '0') })) |
| | | })) |
| | | }, |
| | | }, |
| | | watch: { |
| | | activeKey() { |
| | | this.ensureCycle() |
| | | this.refreshAll() |
| | | } |
| | | }, |
| | | created() { |
| | | const mine = this.myModules.filter(k => this.modules.some(m => m.key === k)) |
| | | this.activeKey = (mine.length ? mine : this.modules.map(m => m.key))[0] |
| | | this.ensureCycle() |
| | | this.refreshAll() |
| | | }, methods: { |
| | | moduleTitle(key) { |
| | | const m = this.modules.find(x => x.key === key) |
| | | return m ? m.title : key |
| | | }, |
| | | logout() { |
| | | localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('realName'); localStorage.removeItem('modules') |
| | | this.$router.push('/login') |
| | | }, |
| | | // ==================== æ¥è¯¢æ¡ä»¶ä¸ç¶æå·æ° ==================== |
| | | buildParams() { |
| | | const p = { period: this.period } |
| | | p.mode = this.cycle === 'year' ? 'year' : 'month' |
| | | return p |
| | | }, |
| | | // 卿䏿¥è¡¨æå¹é
ï¼åæ¢å¨æåæ¸
æä¸å¹é
çæ§å¼ |
| | | normalizePeriod() { |
| | | const isYear = /^\d{4}$/.test(this.period || '') |
| | | const isMonth = /^\d{4}-\d{2}$/.test(this.period || '') |
| | | if (this.cycle === 'year' && isMonth) this.period = '' |
| | | if (this.cycle !== 'year' && isYear) this.period = '' |
| | | }, |
| | | ensureCycle() { |
| | | const allowed = this.cycleOptions.map(o => o.value) |
| | | if (!allowed.includes(this.cycle)) this.cycle = allowed[0] |
| | | this.normalizePeriod() |
| | | }, |
| | | onCycleChange() { |
| | | this.normalizePeriod() |
| | | this.refreshAll() |
| | | }, |
| | | onPeriodChange() { |
| | | this.refreshAll() |
| | | }, |
| | | doQuery() { |
| | | if (!this.period) { this.$message.warning('请å
鿩卿åæä»½'); return } |
| | | this.refreshAll() |
| | | }, |
| | | refreshAll() { |
| | | this.updatedAt = new Date().toLocaleString('zh-CN', { hour12: false }) |
| | | this.refreshStatus() |
| | | }, |
| | | refreshStatus() { |
| | | const m = this.curModule |
| | | if (!m || !this.period) return |
| | | const params = Object.assign(this.buildParams(), { types: m.items.map(i => i.key).join(',') }) |
| | | api.get('/report/ready', { params }) |
| | | .then(res => { |
| | | const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) |
| | | const list = (res && res.data && res.data.items) || [] |
| | | list.forEach(r => { |
| | | const it = m.items.find(x => x.key === r.key) |
| | | if (!it) return |
| | | if (!r.ready) { it.status = 'missing'; it.statusNote = r.note || 'ç¼ºæ°æ®' } |
| | | else if (r.audited === false) { it.status = 'unaudited'; it.statusNote = 'å°æªå®¡æ ¸éè¿' } |
| | | else { it.status = 'ready'; it.statusNote = '' } |
| | | }) |
| | | }) |
| | | .catch(() => {}) |
| | | }, |
| | | noteOf(key) { |
| | | return localStorage.getItem('report_note_' + key) || '' |
| | | }, |
| | | openNoteDialog(row) { |
| | | this.noteEditingKey = row.key |
| | | this.noteDraft = this.noteOf(row.key) |
| | | this.noteDialogVisible = true |
| | | }, |
| | | saveNote() { |
| | | localStorage.setItem('report_note_' + this.noteEditingKey, this.noteDraft.trim()) |
| | | this.noteDialogVisible = false |
| | | this.$message.success('夿³¨å·²ä¿å') |
| | | }, |
| | | // ==================== å¾é / é¢è§ / å¯¼åº ==================== |
| | | toggleChecked(row) { |
| | | const checked = this.curModule.checked |
| | | const i = checked.indexOf(row.key) |
| | | if (i >= 0) checked.splice(i, 1) |
| | | else checked.push(row.key) |
| | | }, |
| | | toggleAll(v) { |
| | | this.curModule.checked = v ? this.curModule.items.map(i => i.key) : [] |
| | | }, |
| | | exportOne(item) { this.exportChecked([item]) }, |
| | | previewOne(item) { this.previewChecked([item]) }, |
| | | previewChecked(itemList) { |
| | | const m = this.curModule |
| | | if (!this.period) { this.$message.warning('请å
鿩卿åæä»½'); return } |
| | | const items = itemList || m.items.filter(i => m.checked.includes(i.key)) |
| | | if (!items.length) { this.$message.warning('è¯·éæ©è¦é¢è§çæ¥è¡¨'); return } |
| | | this.loading = 'preview-' + m.key |
| | | const params = Object.assign(this.buildParams(), { types: items.map(i => i.key).join(',') }) |
| | | api.get('/report/preview', { params }) |
| | | .then(res => { |
| | | const list = (res && res.data && res.data.items) || [] |
| | | this.previewItems = list.map(it => { |
| | | const item = items.find(x => x.key === it.key) |
| | | return Object.assign({}, it, { label: item ? item.label : it.key }) |
| | | }) |
| | | this.previewModule = m.title |
| | | this.previewPeriod = this.period |
| | | this.previewVisible = true |
| | | this.loading = '' |
| | | }) |
| | | .catch(err => this.failWith(err)) |
| | | }, |
| | | fmtVal(v) { |
| | | if (v === null || v === undefined || v === '') return 'â' |
| | | if (typeof v === 'number') { |
| | | if (Number.isInteger(v)) return String(v) |
| | | return v.toFixed(4).replace(/\.?0+$/, '') |
| | | } |
| | | return String(v) |
| | | }, |
| | | coverageText(it) { |
| | | const cov = it.monthCoverage || [] |
| | | if (!cov.length) return 'æ æ°æ®' |
| | | const continuous = cov.length === cov[cov.length - 1] |
| | | if (continuous) return '1~' + cov[cov.length - 1] + 'æåææ°æ®' |
| | | return cov.map(x => x + 'æ').join('ã') + ' ææ°æ®' |
| | | }, |
| | | exportChecked(itemList) { |
| | | const m = this.curModule |
| | | if (!this.period) { this.$message.warning('请å
鿩卿åæä»½'); return } |
| | | const items = itemList || m.items.filter(i => m.checked.includes(i.key)) |
| | | if (!items.length) { this.$message.warning('è¯·éæ©è¦å¯¼åºçæ¥è¡¨'); return } |
| | | this.loading = 'gen-' + m.key |
| | | const params = this.buildParams() |
| | | const doExport = () => { |
| | | if (items.length === 1) { |
| | | api.get('/report/export/' + items[0].key, { params, responseType: 'blob' }) |
| | | .then(res => { this.downloadBlob(res, items[0].fileName); this.$message.success('æ¥è¡¨å¯¼åºæå'); this.loading = '' }) |
| | | .catch(err => this.failWith(err)) |
| | | return |
| | | } |
| | | const zipParams = Object.assign({}, params, { types: items.map(i => i.key).join(',') }) |
| | | api.get('/report/export/selected', { params: zipParams, responseType: 'blob' }) |
| | | .then(res => { this.downloadBlob(res, 'çæ_æéæ¥è¡¨_' + this.period + '.zip'); this.$message.success('å·²å¯¼åº ' + items.length + ' å¼ æ¥è¡¨ï¼zipï¼'); this.loading = '' }) |
| | | .catch(err => this.failWith(err)) |
| | | } |
| | | const readyParams = Object.assign({}, params, { types: items.map(i => i.key).join(',') }) |
| | | api.get('/report/ready', { params: readyParams }) |
| | | .then(rd => { |
| | | const list = (rd && rd.data && rd.data.items) || [] |
| | | const missing = list.filter(i => !i.ready) |
| | | const unaudited = list.filter(i => i.ready && i.audited === false) |
| | | const lines = [] |
| | | missing.forEach(i => { |
| | | const it = items.find(x => x.key === i.key) |
| | | lines.push('ãç¼ºæ°æ®ã' + (it ? it.label : i.key) + 'ï¼' + i.note + 'ï¼') |
| | | }) |
| | | unaudited.forEach(i => { |
| | | const it = items.find(x => x.key === i.key) |
| | | lines.push('ãæªå®¡æ ¸ã' + (it ? it.label : i.key)) |
| | | }) |
| | | if (!lines.length) { doExport(); return } |
| | | this.$confirm('以䏿¥è¡¨è¯·æ³¨æï¼\n' + lines.join('\n') + '\n\nä»è¦å¯¼åºåï¼', '导åºåæç¤º', { |
| | | confirmButtonText: 'ä»è¦å¯¼åº', cancelButtonText: 'åæ¶', type: 'warning' |
| | | }).then(() => doExport()).catch(() => { this.loading = '' }) |
| | | }) |
| | | .catch(() => doExport()) |
| | | }, |
| | | failWith(err) { |
| | | let msg = '' |
| | | const d = err && err.response && err.response.data |
| | | if (d && typeof d === 'object' && d.message) msg = d.message |
| | | this.$message.error('导åºå¤±è´¥ï¼' + (msg || '请确认已导å
¥å¯¹åºæ°æ®ï¼ææ¥çå端æ¥å¿')) |
| | | this.loading = '' |
| | | }, |
| | | downloadBlob(res, fileName) { |
| | | const isZip = /\.zip$/.test(fileName) |
| | | const isXls = /\.xls$/.test(fileName) |
| | | const blob = new Blob([res], { type: isZip ? 'application/zip' : (isXls ? 'application/vnd.ms-excel' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') }) |
| | | const url = window.URL.createObjectURL(blob) |
| | | const a = document.createElement('a') |
| | | a.href = url |
| | | a.download = this.fileName(type) |
| | | a.download = fileName |
| | | document.body.appendChild(a) |
| | | a.click() |
| | | window.URL.revokeObjectURL(url) |
| | | this.$message.success('æ¥è¡¨çææå') |
| | | }) |
| | | .catch(() => { this.$message.error('æ¥è¡¨çæå¤±è´¥ï¼è¯·ç¡®è®¤å·²å¯¼å
¥å¯¹åºæ°æ®') }) |
| | | .finally(() => { this.loading = '' }) |
| | | }, |
| | | fileName(type) { |
| | | const map = { |
| | | cityDetail: 'çæ_è´§è¿éåå¸å·æç».xlsx', |
| | | freightRank: 'çæ_è´§è¿éæå.xlsx', |
| | | turnoverRank: 'çæ_å¨è½¬éæå.xlsx' |
| | | } |
| | | return map[type] || 'report.xlsx' |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | </script><style scoped> |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | .page-scroll { max-width: 1200px; } |
| | | .topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } |
| | | .topbar h3 { margin: 0; } |
| | | .topbar-right { display: flex; align-items: center; gap: 12px; font-size: 13px; color: #606266; } |
| | | .update-time { font-size: 12px; color: #909399; } |
| | | .module-tabs { margin-bottom: 6px; } |
| | | .query-panel { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 12px 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } |
| | | .data-card { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 14px 16px; margin-bottom: 12px; } |
| | | .card-title { font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; } |
| | | .muted { font-size: 12px; color: #909399; font-weight: 400; } |
| | | .module-panel { background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 14px 16px; } |
| | | .ctrl-label { font-size: 13px; color: #606266; } |
| | | .panel-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; } |
| | | .panel-title { font-size: 16px; font-weight: 600; color: #303133; display: flex; align-items: center; gap: 6px; } |
| | | .panel-controls { display: flex; align-items: center; gap: 10px; } |
| | | .report-table { width: 100%; } |
| | | .checked-count { font-size: 12px; color: #909399; } |
| | | .panel-footer { margin-top: 10px; font-size: 12px; color: #909399; text-align: right; } |
| | | .status-note { font-size: 11px; color: #F56C6C; margin-top: 2px; line-height: 1.4; } |
| | | .note-text { font-size: 12px; color: #606266; margin-right: 4px; } |
| | | .spacer { flex: 1; } |
| | | .preview-body { max-height: 68vh; overflow-y: auto; } |
| | | .preview-item { border: 1px solid #ebeef5; border-radius: 6px; padding: 12px 14px; margin-bottom: 12px; } |
| | | .preview-head { display: flex; align-items: center; margin-bottom: 8px; } |
| | | .preview-name { font-weight: 600; font-size: 15px; margin-right: 10px; } |
| | | .preview-rows { margin-left: auto; color: #909399; font-size: 12px; } |
| | | .preview-coverage { font-size: 13px; margin-bottom: 8px; } |
| | | .cov-ok { color: #67C23A; } |
| | | .cov-bad { color: #F56C6C; } |
| | | .cov-label { color: #909399; } |
| | | .preview-summary { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 8px; } |
| | | .preview-summary th, .preview-summary td { border: 1px solid #ebeef5; padding: 4px 8px; text-align: left; } |
| | | .preview-summary th { background: #f5f7fa; } |
| | | .preview-summary .cell-empty { color: #F56C6C; } |
| | | .preview-problems { font-size: 13px; color: #F56C6C; } |
| | | .preview-problems .prob-item { margin: 2px 0; } |
| | | .preview-ok { font-size: 13px; color: #67C23A; } |
| | | .tip-text { font-size: 13px; line-height: 1.7; } |
| | | .tip-text p { margin: 2px 0; } |
| | | @media (max-width: 900px) { |
| | | .panel-head { flex-direction: column; align-items: flex-start; } |
| | | } |
| | | </style> |
| | |
| | | <template><div>è§å管ç</div></template> |
| | | <script>export default { name: 'RuleManage' }</script> |
| | | <template> |
| | | <el-container> |
| | | <el-header>交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - è§å管ç</el-header> |
| | | <el-container> |
| | | <el-aside width="200px"> |
| | | <el-menu router default-active="/rules"> |
| | | <el-menu-item index="/dashboard">å·¥ä½å°</el-menu-item> |
| | | <el-menu-item index="/import">æ°æ®å¯¼å
¥</el-menu-item> |
| | | <el-menu-item index="/data">æ°æ®æ¥ç</el-menu-item> |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item index="/login" @click="logout">éåºç»å½</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <h3>å®¡æ ¸è§å管ç</h3> |
| | | <el-form :inline="true"> |
| | | <el-form-item label="æ¥è¡¨ç±»å"> |
| | | <el-radio-group v-model="reportType" @change="loadRules"> |
| | | <el-radio label="H2032">è´§è¿ææ¥</el-radio> |
| | | <el-radio label="H2031">æ
å®¢ææ¥</el-radio> |
| | | <el-radio label="CITY_BUS">åå¸å
¬äº¤ææ¥</el-radio> |
| | | <el-radio label="CITY_TAXI">巡游åºç§ææ¥</el-radio> |
| | | <el-radio label="INVEST">æèµææ¥</el-radio> |
| | | <el-radio label="H204">è½è壿¥</el-radio> |
| | | |
| | | |
| | | |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="loadRules" :loading="loading">å·æ°</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table-area"> |
| | | <el-table :data="rules" border v-loading="loading" size="small" style="width:100%" height="100%"> |
| | | <el-table-column prop="ruleCode" label="è§åç¼ç " width="120"></el-table-column> |
| | | <el-table-column prop="ruleName" label="è§ååç§°" min-width="180"></el-table-column> |
| | | <el-table-column prop="description" label="è§å说æ" min-width="260" show-overflow-tooltip></el-table-column> |
| | | <el-table-column label="æ¯è¾æ¹å¼" width="130"> |
| | | <template slot-scope="scope">{{ typeLabel(scope.row.compareType) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="éå¼" width="130"> |
| | | <template slot-scope="scope"> |
| | | <el-input v-model="scope.row.threshold" size="mini" placeholder="éå¼" @change="saveThreshold(scope.row)"></el-input> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç级" width="110"> |
| | | <template slot-scope="scope"> |
| | | <el-select v-model="scope.row.alertLevel" size="mini" @change="saveThreshold(scope.row)"> |
| | | <el-option label="红 (RED)" value="RED"></el-option> |
| | | <el-option label="é» (YELLOW)" value="YELLOW"></el-option> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å¯ç¨" width="80"> |
| | | <template slot-scope="scope"> |
| | | <el-switch v-model="scope.row.isEnabled" :active-value="1" :inactive-value="0" |
| | | @change="toggleRule(scope.row)"></el-switch> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | | </el-container> |
| | | </template> |
| | | <script> |
| | | import api from '@/api' |
| | | const TYPE_CN = { |
| | | DIFF: 'å·®å¼å¯¹æ¯', RATIO: 'æ¯ä¾å¯¹æ¯', MOM: 'ç¯æ¯æ°å¢', NULL_CHECK: 'å¿
å¡«/ä¸è´æ§', |
| | | OUT_RANGE: 'èå´æ£æ¥', MOM_EQ_ZERO: 'ç¯æ¯ä¸º0', MOM_PCT: 'ç¯æ¯ç¾åæ¯', |
| | | MOM_EQ: 'çäºä¸æ', EXISTENCE: 'å卿§', CROSS_DIFF: '跨表对æ¯', |
| | | EXISTENCE_AUTH: 'è¿æ¿ç¼ºå¤±', EXISTENCE_SYS: 'ç³»ç»ç¼ºå¤±', CROSS_DIFF_PCT: '跨表ç¾åæ¯', |
| | | TREND_CONSISTENCY: 'è¶å¿ä¸è´', RATIO_CHANGE: 'å æ¯åå', AVG_DIST_CHANGE: 'å¹³åè¿è·åå', |
| | | PAX_PER_TRIP_CHANGE: 'å车次载客人æ°åå', LOADED_RATIO_DIFF: '载货éç¨å æ¯å¯¹æ¯', |
| | | FUEL_RANGE: 'åèè¶
èå´', FUEL_MOM: 'åèç¯æ¯', FREIGHT_VS_H2032: 'ä¸è´§è¿ææ¥å¯¹æ¯', |
| | | AVG_DIST_VS_H2032: 'å¹³åè¿è·å¯¹æ¯', LOADED_TON_RATIO: '载货å¨ä½æ¯å¼' |
| | | } |
| | | export default { |
| | | name: 'RuleManage', |
| | | data() { |
| | | return { reportType: 'H2032', rules: [], loading: false } |
| | | }, |
| | | methods: { |
| | | typeLabel(t) { return TYPE_CN[t] || t }, |
| | | loadRules() { |
| | | this.loading = true |
| | | api.get('/rules/list', { params: { reportType: this.reportType } }) |
| | | .then(res => { this.rules = res.data || [] }) |
| | | .catch(() => { this.$message.error('è§åå 载失败') }) |
| | | .finally(() => { this.loading = false }) |
| | | }, |
| | | toggleRule(row) { |
| | | api.post('/rules/toggle', null, { params: { id: row.id, enabled: row.isEnabled } }) |
| | | .then(() => { this.$message.success((row.isEnabled ? 'å·²å¯ç¨ï¼' : 'å·²åç¨ï¼') + row.ruleCode) }) |
| | | .catch(() => { row.isEnabled = row.isEnabled ? 0 : 1; this.$message.error('æä½å¤±è´¥') }) |
| | | }, |
| | | saveThreshold(row) { |
| | | api.post('/rules/update', { |
| | | id: row.id, threshold: row.threshold, alertLevel: row.alertLevel |
| | | }).then(() => { this.$message.success('å·²ä¿åï¼' + row.ruleCode) }) |
| | | .catch(() => { this.$message.error('ä¿å失败') }) |
| | | } |
| | | }, |
| | | mounted() { this.loadRules() } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .el-header { background: #409EFF; color: white; line-height: 60px; font-size: 18px; } |
| | | </style> |
| | |
| | | <template><div>ç¨æ·ç®¡ç</div></template> |
| | | <script>export default { name: 'UserManage' }</script> |
| | | <template> |
| | | <el-container> |
| | | <el-header>交éç»è®¡æ¥è¡¨å®¡æ ¸ç³»ç» - ç¨æ·ç®¡ç</el-header> |
| | | <el-container> |
| | | <el-aside width="200px"> |
| | | <el-menu router :default-active="activeMenu"> |
| | | <el-menu-item index="/dashboard">å·¥ä½å°</el-menu-item> |
| | | <el-menu-item index="/import">æ°æ®å¯¼å
¥</el-menu-item> |
| | | <el-menu-item index="/data">æ°æ®æ¥ç</el-menu-item> |
| | | <el-menu-item index="/explain">ä¼ä¸è§£é</el-menu-item> |
| | | <el-menu-item index="/audit">å®¡æ ¸ç»æ</el-menu-item> |
| | | <el-menu-item index="/report">æ¥è¡¨çæ</el-menu-item> |
| | | <el-menu-item index="/rules">è§å管ç</el-menu-item> |
| | | <el-menu-item index="/users">ç¨æ·ç®¡ç</el-menu-item> |
| | | <el-menu-item index="/login" @click="logout">éåºç»å½</el-menu-item> |
| | | </el-menu> |
| | | </el-aside> |
| | | <el-main> |
| | | <div class="page-scroll"> |
| | | <h3>ç¨æ·ç®¡çï¼è´è´£æ¨¡åå³å®æ¥è¡¨çæé¡µé»è®¤æ¾ç¤ºåªäºæ¨¡åï¼</h3> |
| | | <div class="toolbar"> |
| | | <el-input v-model="keyword" placeholder="ç¨æ·å / å§å" clearable style="width:220px" @keyup.enter.native="loadList" @clear="loadList"></el-input> |
| | | <el-button type="primary" icon="el-icon-search" @click="loadList">æ¥è¯¢</el-button> |
| | | <el-button type="success" icon="el-icon-plus" @click="openEdit()">æ°å¢ç¨æ·</el-button> |
| | | </div> |
| | | <div class="table-area"> |
| | | <el-table :data="list" border stripe size="small" v-loading="loading"> |
| | | <el-table-column prop="id" label="ID" width="60" align="center"></el-table-column> |
| | | <el-table-column prop="username" label="ç¨æ·å" width="120"></el-table-column> |
| | | <el-table-column prop="realName" label="å§å" width="100"></el-table-column> |
| | | <el-table-column prop="phone" label="ææºå·" width="130"></el-table-column> |
| | | <el-table-column label="è´è´£æ¨¡å" min-width="240"> |
| | | <template slot-scope="{ row }"> |
| | | <el-tag v-for="mk in moduleKeys(row.modules)" :key="mk" size="mini" style="margin-right:4px">{{ moduleLabel(mk) }}</el-tag> |
| | | <span v-if="!row.modules" style="color:#999">æªé
ç½®ï¼é»è®¤å
¨é¨å¯è§ï¼</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç¶æ" width="80" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <el-tag :type="row.status === 1 ? 'success' : 'info'" size="mini">{{ row.status === 1 ? 'å¯ç¨' : 'åç¨' }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createdAt" label="å建æ¶é´" width="170"></el-table-column> |
| | | <el-table-column label="æä½" width="140" align="center"> |
| | | <template slot-scope="{ row }"> |
| | | <el-button type="text" size="mini" @click="openEdit(row)">ç¼è¾</el-button> |
| | | <el-button type="text" size="mini" style="color:#f56c6c" @click="removeUser(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | | |
| | | <el-dialog :title="form.id ? 'ç¼è¾ç¨æ·' : 'æ°å¢ç¨æ·'" :visible.sync="dialogVisible" width="520px" @closed="resetForm"> |
| | | <el-form ref="userForm" :model="form" :rules="formRules" label-width="90px"> |
| | | <el-form-item label="ç¨æ·å" prop="username"> |
| | | <el-input v-model="form.username" :disabled="!!form.id" placeholder="ç»å½è´¦å·"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å¯ç " :prop="form.id ? '' : 'password'"> |
| | | <el-input v-model="form.password" type="password" :placeholder="form.id ? 'ç空åä¸ä¿®æ¹å¯ç ' : '请è¾å
¥åå§å¯ç '"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="å§å" prop="realName"> |
| | | <el-input v-model="form.realName" placeholder="çå®å§å"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="ææºå·"> |
| | | <el-input v-model="form.phone" placeholder="éå¡«"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="è´è´£æ¨¡å"> |
| | | <el-checkbox-group v-model="form.modules"> |
| | | <el-checkbox v-for="mo in moduleOptions" :key="mo.key" :label="mo.key">{{ mo.label }}</el-checkbox> |
| | | </el-checkbox-group> |
| | | <div class="form-tip">æ¥è¡¨çæé¡µé»è®¤åªæ¾ç¤ºå¾éçæ¨¡åï¼ä¸å¾åå
¨é¨å¯è§</div> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ"> |
| | | <el-radio-group v-model="form.status"> |
| | | <el-radio :label="1">å¯ç¨</el-radio> |
| | | <el-radio :label="0">åç¨</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="saving" @click="saveUser">ä¿å</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </el-container> |
| | | </template> |
| | | <script> |
| | | import api from '@/api' |
| | | const MODULE_OPTIONS = [ |
| | | { key: 'freight', label: 'è´§è¿æ¥è¡¨' }, |
| | | { key: 'passenger', label: 'å
¬è·¯æ
客æ¥è¡¨' }, |
| | | { key: 'invest', label: 'æèµæ¥è¡¨' }, |
| | | { key: 'energy', label: 'è½èæ¥è¡¨' }, |
| | | { key: 'cityPassenger', label: 'åå¸å®¢è¿æ¥è¡¨' } |
| | | ] |
| | | export default { |
| | | name: 'UserManage', |
| | | data() { |
| | | return { |
| | | activeMenu: '/users', |
| | | keyword: '', |
| | | list: [], |
| | | loading: false, |
| | | dialogVisible: false, |
| | | saving: false, |
| | | moduleOptions: MODULE_OPTIONS, |
| | | form: { id: null, username: '', password: '', realName: '', phone: '', modules: [], status: 1 }, |
| | | formRules: { |
| | | username: [{ required: true, message: '请è¾å
¥ç¨æ·å', trigger: 'blur' }], |
| | | password: [{ required: true, message: '请è¾å
¥åå§å¯ç ', trigger: 'blur' }] |
| | | } |
| | | } |
| | | }, |
| | | mounted() { this.loadList() }, |
| | | methods: { |
| | | logout() { |
| | | localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('realName'); localStorage.removeItem('modules') |
| | | this.$router.push('/login') |
| | | }, |
| | | loadList() { |
| | | this.loading = true |
| | | api.get('/user/list', { params: { keyword: this.keyword } }) |
| | | .then(res => { this.list = res.data || [] }) |
| | | .catch(() => this.$message.error('ç¨æ·å表å 载失败')) |
| | | .finally(() => { this.loading = false }) |
| | | }, |
| | | moduleKeys(modules) { |
| | | if (!modules) return [] |
| | | return modules.split(',').filter(Boolean) |
| | | }, |
| | | moduleLabel(key) { |
| | | const mo = MODULE_OPTIONS.find(x => x.key === key) |
| | | return mo ? mo.label : key |
| | | }, |
| | | openEdit(row) { |
| | | this.dialogVisible = true |
| | | this.$nextTick(() => { |
| | | if (row && row.id) { |
| | | this.form = { |
| | | id: row.id, username: row.username, password: '', realName: row.realName || '', |
| | | phone: row.phone || '', modules: this.moduleKeys(row.modules), status: row.status === 0 ? 0 : 1 |
| | | } |
| | | } else { |
| | | this.form = { id: null, username: '', password: '', realName: '', phone: '', modules: [], status: 1 } |
| | | } |
| | | this.$refs.userForm && this.$refs.userForm.clearValidate() |
| | | }) |
| | | }, |
| | | resetForm() { |
| | | this.form = { id: null, username: '', password: '', realName: '', phone: '', modules: [], status: 1 } |
| | | this.$refs.userForm && this.$refs.userForm.clearValidate() |
| | | }, |
| | | saveUser() { |
| | | this.$refs.userForm.validate(valid => { |
| | | if (!valid) return |
| | | this.saving = true |
| | | api.post('/user/save', this.form) |
| | | .then(() => { |
| | | this.$message.success('ä¿åæå') |
| | | this.dialogVisible = false |
| | | this.loadList() |
| | | }) |
| | | .catch(err => { |
| | | const d = err && err.response && err.response.data |
| | | this.$message.error((d && d.message) || 'ä¿å失败') |
| | | }) |
| | | .finally(() => { this.saving = false }) |
| | | }) |
| | | }, |
| | | removeUser(row) { |
| | | this.$confirm('ç¡®å®å é¤ç¨æ·ã' + (row.realName || row.username) + 'ãåï¼', 'å é¤ç¡®è®¤', { type: 'warning' }) |
| | | .then(() => api.post('/user/delete', null, { params: { id: row.id } })) |
| | | .then(() => { this.$message.success('å·²å é¤'); this.loadList() }) |
| | | .catch(() => {}) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | <style scoped> |
| | | .toolbar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; } |
| | | .form-tip { font-size: 12px; color: #909399; line-height: 1.6; margin-top: 4px; } |
| | | </style> |
| New file |
| | |
| | | 交éè´§è¿ç»è®¡å®¡æ ¸ç³»ç» ââ 项ç®äº¤ä»å
|
| | | =================================== |
| | | |
| | | ã项ç®ç»æã |
| | | traffic-audit-server å端ï¼Spring Bootï¼ç«¯å£ 8090ï¼ |
| | | traffic-audit-web å端ï¼Vue2 + Element UIï¼ç«¯å£ 8080ï¼/api 代çå° 8090ï¼ |
| | | docs/ requirement/ éæ±ä¸è®¾è®¡ææ¡£ |
| | | test/ æ ·ä¾æ°æ® |
| | | |
| | | ãç¯å¢è¦æ±ã |
| | | JDK 8+ãMaven 3.xãNode.js 14~18ãMySQL 5.7/8.0 |
| | | |
| | | ãæ°æ®åºåå§åãï¼é¦æ¬¡è¿è¡åå¿
é¡»æ§è¡ï¼ |
| | | 1. 建åºå»ºè¡¨ + åºç¡æ°æ®ï¼æ§è¡ docs/init.sql |
| | | ï¼èªå¨å建 traffic_audit æ°æ®åºãå
¨é¨ä¸å¡è¡¨ãç¨æ·æéä¸åºç¡æ°æ®ï¼ |
| | | ç»å½éªè¯ç 为å
åçï¼æ é Redisï¼ |
| | | 2. åå¸å®¢è¿æ©å±è¡¨ï¼æ§è¡ docs/sql_city_bus.sqlãdocs/sql_city_taxi.sql |
| | | ï¼å«å»ºè¡¨ä¸å®¡æ ¸è§åï¼init.sql å·²å«å
¶ä½å
¨é¨è¡¨ï¼ |
| | | 3. ä¿®æ¹ traffic-audit-server/src/main/resources/application.yml |
| | | ä¸çæ°æ®åºè´¦å·å¯ç ä¸ºæ¬æºå®é
å¼ |
| | | |
| | | ãå¯å¨æ¥éª¤ã |
| | | æ¹å¼ä¸ï¼æ¨èï¼Windows PowerShellï¼é¡¹ç®æ ¹ç®å½æ§è¡ï¼ï¼ |
| | | powershell -ExecutionPolicy Bypass -File start-dev.ps1 |
| | | èæ¬èªå¨å¯å¨å端(8090)ä¸å端(8080)ï¼å¹¶çå¾
就绪ã |
| | | |
| | | æ¹å¼äºï¼æå¨ï¼ï¼ |
| | | å端ï¼cd traffic-audit-server && mvn spring-boot:run |
| | | å端ï¼cd traffic-audit-web && npm install && npm run serve |
| | | |
| | | 访é®ï¼http://localhost:8080 |
| | | é»è®¤è´¦å·ï¼admin / admin123 |
| | | |
| | | ãéè¦é
ç½®ã |
| | | æ°æ®åºè¿æ¥é
ç½®å¨ï¼ |
| | | traffic-audit-server/src/main/resources/application.yml |
| | | ï¼å
å«å¼åç¯å¢è´¦å·å¯ç ï¼è¯·æå®é
ç¯å¢ä¿®æ¹ï¼ |
| | | é
置模æ¿è§åç®å½ application.yml.exampleï¼ |
| | | |
| | | ã夿³¨ã |
| | | åç«¯é¦æ¬¡å¯å¨åé npm installï¼ä¾èµæªéå
æä¾ï¼ã |