From 3e069207503d4685a20ab5c695b1a17993f115ea Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期三, 09 九月 2026 15:56:45 +0800
Subject: [PATCH] fix: 09-09下午第三轮——审核结果默认按企业合并与问题范围语义收敛/导出企业合并问题列按用户4条例文话术、数据查看工具栏整理、报表整本预览分组表格化 + 测试报告与工作日结

---
 traffic-audit-web/src/views/ReportExport.vue                                                    |   86 +++++++++
 docs/工作日结/工作日结_2026-09-09.md                                                                    |   21 ++
 docs/功能测试报告/功能测试报告_审核界面交互与问题话术导出_2026-09-09.md                                                  |   38 ++++
 traffic-audit-web/src/views/DataView.vue                                                        |   91 +++-------
 traffic-audit-web/src/views/AuditResult.vue                                                     |   24 +
 traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java |  247 ++++++++++++++++-----------
 6 files changed, 335 insertions(+), 172 deletions(-)

diff --git "a/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\345\256\241\346\240\270\347\225\214\351\235\242\344\272\244\344\272\222\344\270\216\351\227\256\351\242\230\350\257\235\346\234\257\345\257\274\345\207\272_2026-09-09.md" "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\345\256\241\346\240\270\347\225\214\351\235\242\344\272\244\344\272\222\344\270\216\351\227\256\351\242\230\350\257\235\346\234\257\345\257\274\345\207\272_2026-09-09.md"
new file mode 100644
index 0000000..8d55d82
--- /dev/null
+++ "b/docs/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212/\345\212\237\350\203\275\346\265\213\350\257\225\346\212\245\345\221\212_\345\256\241\346\240\270\347\225\214\351\235\242\344\272\244\344\272\222\344\270\216\351\227\256\351\242\230\350\257\235\346\234\257\345\257\274\345\207\272_2026-09-09.md"
@@ -0,0 +1,38 @@
+# 鍔熻兘娴嬭瘯鎶ュ憡锛氬鏍哥粨鏋滅晫闈氦浜掍紭鍖栦笌瀵煎嚭闂璇濇湳钀藉湴
+
+鏃ユ湡锛�2026-09-09銆�妯″潡锛氬鏍哥粨鏋� / 鏁版嵁鏌ョ湅 / 鎶ヨ〃鐢熸垚锛堝墠绔級+ 瀹℃牳缁撴灉瀵煎嚭涓庢煡璇紙鍚庣锛夈��娴嬭瘯浜猴細Codex锛堜笟鍔$偣楠屽悗琛ュ厖锛�
+
+## 涓�銆佸姛鑳借鏄�
+1. 瀹℃牳缁撴灉椤垫寜涓氬姟鍙嶉鏀舵暃浜や簰璇箟锛氶棶棰樿寖鍥翠袱涓�夐」鏀逛负銆屼粎闇�鏍稿疄 / 鍚彁閱掑瀷銆嶅苟闄勮鏄庛�屾彁閱掑瀷浠呮彁绀哄彉鍖栥�佷笉闇�澶勭悊锛屽鍑洪粯璁や笉鍚�嶏紱瑙嗗浘榛樿銆屾寜浼佷笟鍚堝苟銆嶏紝鍚屾椂淇濈暀銆屾寜鏉℃槑缁嗐�嶏紱绛涢�夊尯鏀逛负娴呯伆鍗$墖鏍忥紝骞跺睍绀恒�屽綋鍓嶇瓫閫� N 鏉� / M 瀹朵紒涓氥�嶅疄鏃跺懡涓暟锛涢《閮ㄧ孩/榛�/寰呭鐞�/宸插鐞嗙粺璁″崱鏀逛负璺熼殢褰撳墠绛涢�夌粨鏋滆仈鍔ㄣ��
+2. 瀵煎嚭銆屼紒涓氬悎骞躲�峉heet 鐨勯棶棰樺垪璇濇湳鎸夌敤鎴风粰瀹� 4 鏉′緥鏂囨牸寮忕敓鎴愶紝鍏朵綑瑙勫垯缁熶竴涓恒�屾牳瀹炲紓甯�/宸紓璇存槑锛堝惈鍏抽敭鏁板�硷級锛嬭鏍稿疄銆嶉鏍硷紱queryResults 鏀逛负鎸夋姤琛ㄧ被鍨嬪厛鍙栬鍒欍�佸啀鍙栬鏈熺粨鏋滐紝閬垮厤鍏ㄨ〃瑙勫垯姣斿銆�
+3. 鏁版嵁鏌ョ湅椤靛伐鍏锋爮鏁寸悊涓轰笌瀵煎叆椤典竴鑷寸殑銆屽垎缁勬ā鍧楋紜绫诲瀷涓嬫媺銆嶏紝鎶ヨ〃鏈�/鍏抽敭瀛�/鏌ヨ/鍏ㄥ睆骞舵帓浜庝竴琛岋紝搴曢儴鎻愮ず鏀逛负銆屽叡 N 鏉¤褰曪紝宸插姞杞� N 鏉°�嶃��
+4. 鎶ヨ〃鐢熸垚椤点�岄亾璺繍杈撻噺姹囨�昏〃锛堟暣鏈級銆嶉瑙堟敼涓哄垎缁勮〃鏍硷紙閬撹矾璐ц繍/鍩庡競瀹㈣繍/涓彛寰� 脳 褰撴湀/绱/鍚屾瘮锛夛紝鍚屾瘮缂轰笂骞村熀鏈熸椂閫愰」缁欍�屸�斻�嶄笌鎻愮ず銆�
+
+## 浜屻�佹祴璇曠幆澧�
+- 鍚庣 8090锛�2026-09-09 15:45 浠ユ渶鏂颁唬鐮侀噸鍚氨缁級锛屽墠绔� 8080锛坉ev 缂栬瘧鎴愬姛銆丠MR 鐢熸晥锛夈��
+- 鏁版嵁搴擄細鏈満 traffic_audit锛�2026-08 璐ц繍/鏃呭/鍏氦/鍑虹鏁版嵁鍧囧湪搴擄級銆�
+
+## 涓夈�佺敤渚嬩笌缁撴灉
+| 缂栧彿 | 鐢ㄤ緥 | 杈撳叆 | 棰勬湡 | 瀹為檯 | 缁撹 |
+|---|---|---|---|---|---|
+| 1 | 2026-08 璐ц繍鏌ヨ涓嶈浆鍦� | 椤甸潰閫� 2026-08锛嬭揣杩愭湀鎶モ啋鏌ヨ缁撴灉 | 鏁扮鍐呰繑鍥炲苟鍋滄 loading | 绾� 2~3s 杩斿洖 487 鏉� / 403 瀹讹紝鏃犲崱杞湀 | 閫氳繃 |
+| 2 | 闂鑼冨洿鎸夐挳鍗虫椂鍝嶅簲 | 鐐瑰嚮銆屽惈鎻愰啋鍨嬨�嶅啀鐐广�屼粎闇�鏍稿疄銆� | 閫変腑鎬佷笌鍛戒腑鏁板嵆鏃跺彉鍖栵紝鏃犻渶閲嶆柊鏌ヨ | 閫変腑鎬佺鍒囷紱浠呴渶鏍稿疄涓嬭嚜鍔ㄥ墧闄� RULE_004 鎻愰啋鍨嬶紙487鈫�474 鏉★級 | 閫氳繃 |
+| 3 | 瑙嗗浘榛樿涓庡垏鎹� | 杩涘叆椤甸潰鈫掕鍥惧崟閫� | 榛樿銆屾寜浼佷笟鍚堝苟銆嶏紝鍙垏銆屾寜鏉℃槑缁嗐�� | 榛樿鎸変紒涓氬悎骞讹紙AX 閫変腑鎬� Value=1锛夛紝鎸夋潯鏄庣粏鍙甯稿垏鎹负 10 鍒楅�愭潯琛� | 閫氳繃 |
+| 4 | 鏈堟姤鏁版嵁寮圭獥锛堟暟鎹級鎺ュ彛 | /api/data/list?type=h2032/cityBus/h2031&period=2026-08 | 蹇�熻繑鍥炶浼佷笟璇ユ湡濉姤 | 23~561ms 杩斿洖锛坔2032 鍏抽敭瀛楀懡涓� 1 鏉°�乧ityBus 10 鏉°�乭2031 50 鏉�/鎬绘暟119锛� | 閫氳繃 |
+| 5 | H2031 瀵煎嚭闂鍒椾笌鐢ㄦ埛 4 鏉℃ā鏉夸竴鑷� | /api/audit/export 2026-08 H2031 | 1)~4) 璇濇湳涓庣敤鎴锋彁渚涙牸寮忓畬鍏ㄤ竴鑷� | 瀹炴祴 G7锛氣憼瀹㈣繍閲忕幆姣�=0鏍稿疄寮傚父锛堜笂鏈堝杩愰噺=3784锛屾湰鏈堝寘杞﹀杩愰噺=3784锛夎鏍稿疄 鈶℃梾瀹㈠懆杞噺鐜瘮=0鏍稿疄寮傚父锛堚�︼級璇锋牳瀹� 鈶㈠寘杞﹀钩鍧囪繍璺濈瓑浜庝笂鏈堬紙涓婃湀杩愯窛=25.9锛屾湰鏈堣繍璺�=25.9锛夎鏍稿疄浼佷笟鏄惁鏄浐瀹氱嚎璺梾娓告垨鑰呴�氬嫟涓氬姟锛汫10 鐝嚎涓恒�屸�︾幆姣旇秴卤20%鈥︼紙涓夌被鐝嚎骞冲潎杩愯窛=0锛屼笂鏈�=17.6锛岀幆姣�100%锛夆�﹁鏍稿疄銆� | 閫氳繃 |
+| 6 | H2032 瀵煎嚭闂鍒楀惈鏄庣粏 | 鍚屼笂 H2032 | 姣忕被鏍稿疄鎰忚甯﹀叧閿暟鍊� | 瀹炴祴銆屼紒涓氫笂鎶ヨ溅杈�/鍚ㄤ綅涓庤繍鏀挎暟鎹姣斿瓨鍦ㄥ樊寮傦紙鏁磋溅鍚ㄤ綅锛氫笂鎶�=鈥︼紝杩愭斂=鈥︼紝宸�︼紙闄�50 鍚級锛涙�诲惃浣嶏細鈥︼級璇锋牳瀹炪�嶃�屼笂鎶ュ懆杞噺涓庤建杩规祴绠楀懆杞噺宸紓鏄庢樉锛堜笂鎶ュ懆杞噺=鈥︼紝杞ㄨ抗娴嬬畻=鈥︼級璇峰弬鑰冭建杩规牳瀹炰紒涓氬懆杞噺銆嶇瓑 | 閫氳繃 |
+| 7 | 瀹℃牳缁撴灉瀵煎嚭缁撴瀯涓庡悎骞跺尯 | openpyxl 鎵撳紑涓や唤瀵煎嚭 | 鍙� Sheet銆丅/G 璺ㄨ鍚堝苟銆侀棶棰樺垪缂栧彿 1銆�2銆�3鈥� | H2031 84 琛屻�丠2032 475 琛岋紱浼佷笟鍚堝苟 B锛堜紒涓氾級/G锛堥棶棰橈級鍚堝苟鍚屾瀯 | 閫氳繃 |
+| 8 | 鏁存湰棰勮 2026-08 鍒嗙粍琛ㄩ敭鍚� | /api/report/preview?period=2026-08&types=summaryWorkbook | summary 閿悕涓庢柊鍒嗙粍琛ㄦ牸鍙栧�间竴鑷� | 杩斿洖閿惈銆屽叏鐪佽揣鐗╁懆杞噺路绱 / 路绱鍚屾瘮銆嶃�屽煄甯傚杩愰噺/鍛ㄨ浆閲徛峰綋鏈埪风疮璁°�嶃�屼腑鍙e緞瀹㈣繍閲�/鍛ㄨ浆閲徛峰綋鏈埪风疮璁°�嶇瓑锛屽墠绔� wbRows 鍒嗙粍鍙彇鏁帮紝缂哄綋鏈堟簮鏁版嵁涓庝笂骞村熀鏈熺殑琛屾樉绀恒�屸�斻�嶅苟缁欐彁绀� | 閫氳繃 |
+| 9 | 缂栬瘧涓庤繍琛� | mvn compile锛嬪墠绔� dev | 鏃犵紪璇戦敊璇�侀〉闈㈠彲浜や簰 | 鍚庣 15:45 閲嶅惎鎴愬姛锛圫tarted 鈥� port 8090锛夛紱鍓嶇 15:42 缂栬瘧鎴愬姛锛涢〉闈㈠疄娴嬬偣鍑�/鍒囨崲/鏌ヨ鍧囨甯� | 閫氳繃 |
+
+## 鍥涖�佸彂鐜伴棶棰樹笌澶勭疆
+- 鍘熴�屾牳瀹炲瀷/鍏ㄩ儴銆嶆枃妗堣涔変笉娓呭鑷寸敤鎴峰洶鎯� 鈫� 鏀逛负銆屼粎闇�鏍稿疄/鍚彁閱掑瀷銆嶏紝骞跺姞鐏拌壊璇存槑鏂囧瓧锛涘師銆屾寜鏉℃槑缁嗐�嶉粯璁よ鍥� 鈫� 鏀逛负榛樿銆屾寜浼佷笟鍚堝苟銆嶏紙涓庢棩甯告寜浼佷笟涓嬪彂鏍稿鐨勫伐浣滄祦涓�鑷达級銆�
+- 鐢ㄦ埛鍙嶉鈥滈�� 8 鏈堣揣杩�/鍩庡競鍏氦涓�鐩磋浆鍦堚�濓細澶嶆祴 2026-08 鍚勭被鍨嬬粨鏋滄煡璇㈠潎 <1s銆�/data/list 寮圭獥鏁版嵁 <600ms锛屾湭澶嶇幇杞湀锛涘垽瀹氫负鏃х増鎸夐挳浠呮洿鏂伴�夋嫨鐘舵�佷笉鍒锋柊鍒楄〃鎵�鑷达紝鐜板凡鏀逛负鍓嶇鍝嶅簲寮忚繃婊わ紙鍒囪寖鍥�/鐘舵�佸嵆鏃剁敓鏁堬級锛屾棤闇�鏁撮〉閲嶆柊璇锋眰銆�
+- 瀵煎嚭闂鍒� H2032 鏃ц瘽鏈彧鍐欌�滃紓甯革紙瀹為檯/鍩哄噯/鍋忓樊锛夆�濊緝鏅︽订 鈫� 鎸夎鍒欒ˉ榻愬叧閿暟鍊间笌涓氬姟鍙e緞锛堣繍鏀垮垎椤广�佽建杩广�侀泦瑁呯銆佹暣杞﹀惃浣嶇瓑锛夛紝璐ц繍/瀹㈣繍涓ょ被璇濇湳鍧囧凡鎸夋柊鏍煎紡瀹炴祴鏍稿銆�
+
+## 浜斻�佸緟涓氬姟鐐归獙 / 閬楃暀
+- 璇蜂笟鍔″湪娴忚鍣ㄧ偣楠岋細闂鑼冨洿/瑙嗗浘/鐘舵�佷笁涓帶浠跺垏鎹€�佽鍐呫�屾暟鎹�嶅脊绐椼�佸鍑� Excel 鎵撳紑鍚庝笌 docs/瀹℃牳鐣岄潰鏈堟姤妯℃澘/妯℃澘_瀹℃牳缁撴灉.xlsx 鍙婂洓鏉′緥鏂囬�愭潯姣斿銆�
+- 涓庢棦鏈夐仐鐣欎竴鑷达細姹囨�诲ぇ琛� 8 鏈堣嚜鍔ㄥ~鍊硷紙M2/M3 SummaryFiller锛夋湭鍚姩锛�8 鏈堢己 freight_turnover_import 绛夋簮鏁版嵁琛岋紱瀵煎叆寮圭獥 UI 涓� RULE_001 闃堝�硷紙5 鍙�/50 鍚級寰呬笟鍔$‘璁ゃ��
+
+## 鍏�佺粨璁�
+鏈疆鐣岄潰浜や簰鏀舵暃銆佸鍑洪棶棰樿瘽鏈惤鍦颁笌棰勮琛ㄦ牸鍖栧潎閫氳繃鎺ュ彛绾т笌椤甸潰绾ч獙璇侊紱鏈彂鐜伴樆濉炴�ч棶棰橈紝鍙殢浠g爜涓�骞朵氦浠橈紝绛夊緟涓氬姟缁堥獙銆�
\ No newline at end of file
diff --git "a/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md" "b/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md"
index 705edab..d2dc73d 100644
--- "a/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md"
+++ "b/docs/\345\267\245\344\275\234\346\227\245\347\273\223/\345\267\245\344\275\234\346\227\245\347\273\223_2026-09-09.md"
@@ -38,3 +38,24 @@
 - /api/audit/export 2026-08 H2032锛氬鏍哥粨鏋� 475 琛� 10 鍒� + 浼佷笟鍚堝苟 150 缁勶紙B/G 鍚堝苟锛夛紝浠呮牳瀹炲瀷锛泂tatus=pending/handled 杩囨护鐢熸晥銆�
 - /api/report/preview summaryWorkbook锛�2026-07 鏄剧ず鍏ㄧ渷璐ц繍閲� 16285.71 涓囧惃/绱 113423.13锛屽懆杞噺褰撴湀/绱+鍚屾瘮锛屽煄甯傚杩愰噺褰撴湀 30612.85 涓囦汉娆$瓑锛涘悓姣旂己 2025-07 鍩烘湡鏁版嵁鐨勪笁椤瑰潎鏈夋槑纭彁绀恒��
 - 鏁版嵁鏌ョ湅鍒楃粨鏋勬牳瀵癸細鏈嶅姟绔惈 enterpriseCode/unifiedCreditCode/reportUnit 涓夊垪锛堝墠绔殣钘忕敓鏁堬級銆�
+
+
+---
+
+## 涓嬪崍绗笁杞紙2026-09-09 16:xx锛夌晫闈㈣涔夋敹鏁� / 瀵煎嚭闂璇濇湳 / 棰勮琛ㄦ牸鍖栵紙宸插湪鏈湴澶嶉獙锛�
+
+### 瀹屾垚鍐呭
+- 瀹℃牳缁撴灉椤碉細瑙嗗浘鏀逛负榛樿銆屾寜浼佷笟鍚堝苟銆嶏紙淇濈暀銆屾寜鏉℃槑缁嗐�嶏級锛涖�岄棶棰樿寖鍥淬�嶆敼鏂囨銆屼粎闇�鏍稿疄/鍚彁閱掑瀷銆嶅苟闄勮鏄庛�屾彁閱掑瀷浠呮彁绀哄彉鍖栥�佷笉闇�澶勭悊锛屽鍑洪粯璁や笉鍚�嶏紱绛涢�夊尯鍗$墖鍖栵紱缁熻鍗′笌銆屽綋鍓嶇瓫閫� N 鏉�/M 瀹朵紒涓氥�嶉殢绛涢�夊疄鏃惰仈鍔紙鍒囨崲涓嶅啀闇�瑕侀噸鏂版煡璇級銆�
+- 瀹℃牳缁撴灉瀵煎嚭锛氫紒涓氬悎骞躲�岄棶棰樸�嶅垪鎸夌敤鎴� 4 鏉′緥鏂囨牸寮忚惤鍦帮紙RULE_P003/04 瀹㈣繍閲忋�佹梾瀹㈠懆杞噺鐜瘮=0 鏍稿疄寮傚父锛堜笂鏈堚��=鈥︼紝鏈湀鈥�=鈥︼級璇锋牳瀹烇紱RULE_P005 鐝嚎骞冲潎杩愯窛鐜瘮瓒呴檺闄勪笂涓嬫湀涓庣幆姣旓紱RULE_P006 鍖呰溅骞冲潎杩愯窛绛変簬涓婃湀锛堜笂鏈堣繍璺�=鈥︼紝鏈湀杩愯窛=鈥︼級璇锋牳瀹炴槸鍚﹀浐瀹氱嚎璺梾娓告垨閫氬嫟锛夛紱H2032 璐ц繍璇濇湳琛ュ垎椤规槑缁嗭紙杞﹁締/鍚ㄤ綅杩愭斂宸�佽建杩瑰懆杞噺銆侀泦瑁呯銆佹暣杞﹀惃浣嶈寖鍥寸瓑锛夈�傚悗绔� queryResults 鏀逛负鍏堟寜鎶ヨ〃绫诲瀷鍙栬鍒欏啀鏌ヨ鏈熺粨鏋溿��
+- 鏁版嵁鏌ョ湅锛氶《閮ㄥ伐鍏锋爮鏁寸悊锛堝垎缁勬ā鍧�+绫诲瀷涓嬫媺/鎶ヨ〃鏈�/鍏抽敭瀛�/鏌ヨ/鍏ㄥ睆涓�琛屽寲锛夛紝搴曢儴璁℃暟鏀广�屽叡 N 鏉★紝宸插姞杞� N 鏉°�嶃��
+- 鎶ヨ〃鐢熸垚锛氭眹鎬诲ぇ琛紙鏁存湰锛夐瑙堟敼涓哄垎缁勮〃锛堥亾璺揣杩�/鍩庡競瀹㈣繍/涓彛寰� 脳 褰撴湀/绱/鍚屾瘮锛夛紝缂� 2025 鍩烘湡鎴栧綋鏈堟簮鏁版嵁鏃剁粰銆屸�斻�嶄笌鎻愮ず銆�
+
+### 娴嬭瘯缁撴灉鎽樿
+- 椤甸潰瀹炴祴锛�8080锛夛細2026-08 璐ц繍鏌ヨ 2~3s 杩斿洖 487 鏉�/403 瀹舵棤杞湀锛涖�屼粎闇�鏍稿疄/鍚彁閱掑瀷銆嶇偣鍑诲嵆鏃剁敓鏁堬紙487鈫�474 鏉¤嚜鍔ㄥ墧闄� RULE_004锛夛紱瑙嗗浘榛樿鎸変紒涓氬悎骞跺彲鍒囨槑缁嗭紱琛屽唴銆屾暟鎹�嶆帴鍙� 23~561ms 杩斿洖銆�
+- 瀵煎嚭瀹炴祴锛欻2031 2026-08 闂鍒椾笌 4 鏉′緥鏂囬�愬瓧姣斿涓�鑷达紱H2032 2026-08 鍙� Sheet 475 琛屻�丅/G 鍚堝苟姝e父銆�
+- 鍓嶇 dev 缂栬瘧鎴愬姛锛�15:42锛夛紱鍚庣 15:45 閲嶅惎灏辩华锛涗袱涓繙绔紙gitea main / 涓汉 gitee master锛夊潎鏃犳柊鎻愪氦銆�
+- 娴嬭瘯鎶ュ憡锛歞ocs/鍔熻兘娴嬭瘯鎶ュ憡/鍔熻兘娴嬭瘯鎶ュ憡_瀹℃牳鐣岄潰浜や簰涓庨棶棰樿瘽鏈鍑篲2026-09-09.md銆�
+
+### 閬楃暀闂涓庡緟鍔烇紙涓庝笂鍗堜竴鑷达級
+- 姹囨�诲ぇ琛� 8 鏈堣嚜鍔ㄥ~鍊硷紙M2/M3 SummaryFiller锛夋湭鍚姩锛�8 鏈堢己 freight_turnover_import銆乻cale_split_transport銆乸assenger_individual_monthly銆亀yc_total/order 婧愭暟鎹��
+- 寰呬笟鍔$粓楠岋細闂鑼冨洿/瑙嗗浘/鐘舵�佹帶浠躲�佽鍐呮暟鎹脊绐椼�佸鍑� Excel 涓庢ā鏉�/鍥涙潯渚嬫枃閫愭潯鏍稿锛涘鍏ュ脊绐� UI銆丷ULE_001 闃堝�硷紙5 鍙�/50 鍚級鏄惁绗﹀悎涓氬姟棰勬湡銆�
\ No newline at end of file
diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
index 75c05b2..63dfe69 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/auditengine/service/AuditEngineService.java
@@ -1259,19 +1259,17 @@
 
     public List<AuditResult> queryResults(String reportPeriod, String reportType) {
         String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
+        List<AuditRule> rules = ruleMapper.selectList(new LambdaQueryWrapper<AuditRule>()
+            .eq(AuditRule::getReportType, type));
+        if (rules.isEmpty()) return new java.util.ArrayList<>();
+        List<Long> ruleIds = new ArrayList<>();
+        for (AuditRule rule : rules) ruleIds.add(rule.getId());
         List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>()
             .eq(AuditResult::getReportPeriod, reportPeriod)
+            .in(AuditResult::getRuleId, ruleIds)
             .orderByAsc(AuditResult::getId));
-        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;
+        attachInfo(results, type, reportPeriod);
+        return results;
     }
 
     public void reviewResult(Long id, String status, String comment) {
@@ -1283,46 +1281,56 @@
         resultMapper.updateById(result);
     }
 
-    private void attachInfo(List<AuditResult> results) {
+    private void attachInfo(List<AuditResult> results, String type, String period) {
         if (results.isEmpty()) return;
         Map<Long, AuditRule> ruleMap = new HashMap<>();
-        for (AuditRule rule : ruleMapper.selectList(null)) ruleMap.put(rule.getId(), rule);
-        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)) {
-            h2032Name.putIfAbsent(r.getId(), r.getEnterpriseName());
-            h2032Explain.putIfAbsent(r.getId(), r.getVerifyExplanation());
+        for (AuditRule rule : ruleMapper.selectList(new LambdaQueryWrapper<AuditRule>()
+                .eq(AuditRule::getReportType, type))) {
+            ruleMap.put(rule.getId(), rule);
         }
-        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());
+        Map<Long, String> nameMap = new HashMap<>();
+        Map<Long, String> explainMap = new HashMap<>();
+        if ("INVEST".equals(type)) {
+            Map<Long, Long> monthlyProject = new HashMap<>();
+            for (InvestmentMonthly m : investMonthlyMapper.selectList(new LambdaQueryWrapper<InvestmentMonthly>()
+                    .eq(InvestmentMonthly::getReportPeriod, period))) {
+                monthlyProject.put(m.getId(), m.getProjectId());
+            }
+            for (InvestmentProject p : investProjectMapper.selectList(null)) {
+                for (Map.Entry<Long, Long> e : monthlyProject.entrySet()) {
+                    if (e.getValue() != null && e.getValue().equals(p.getId())) {
+                        nameMap.putIfAbsent(e.getKey(), p.getProjectName());
+                    }
+                }
+            }
+        } else if ("H204".equals(type)) {
+            for (EnergyVehicleQuarterly m : energyMapper.selectList(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                    .eq(EnergyVehicleQuarterly::getReportPeriod, period))) {
+                nameMap.putIfAbsent(m.getId(), m.getEnterpriseName());
+            }
+        } else if ("H2031".equals(type)) {
+            for (PassengerEnterpriseMonthly m : passengerMapper.selectList(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                    .eq(PassengerEnterpriseMonthly::getReportPeriod, period))) {
+                nameMap.putIfAbsent(m.getId(), m.getEnterpriseName());
+                explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation());
+            }
+        } else if ("CITY_BUS".equals(type)) {
+            for (CityBusMonthly m : cityBusMapper.selectList(new LambdaQueryWrapper<CityBusMonthly>()
+                    .eq(CityBusMonthly::getReportPeriod, period))) {
+                nameMap.putIfAbsent(m.getId(), m.getEnterpriseName());
+                explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation());
+            }
+        } else if ("CITY_TAXI".equals(type)) {
+            for (CityTaxiMonthly m : cityTaxiMapper.selectList(new LambdaQueryWrapper<CityTaxiMonthly>()
+                    .eq(CityTaxiMonthly::getReportPeriod, period))) {
+                nameMap.putIfAbsent(m.getId(), m.getCity());
+                explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation());
+            }
+        } else {
+            for (H2032EnterpriseMonthly m : h2032Mapper.selectList(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                    .eq(H2032EnterpriseMonthly::getReportPeriod, period))) {
+                nameMap.putIfAbsent(m.getId(), m.getEnterpriseName());
+                explainMap.putIfAbsent(m.getId(), m.getVerifyExplanation());
             }
         }
         for (AuditResult result : results) {
@@ -1332,69 +1340,49 @@
                 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) {
-                // 涓婃姤鏁版嵁琚噸鏂板鍏ュ悗鏃у鏍哥粨鏋滃凡澶辨晥锛岀粰鍑烘彁绀鸿�岄潪绌虹櫧
-                enterpriseName = "\uFF08\u539F\u6570\u636E\u5DF2\u91CD\u65B0\u5BFC\u5165\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C\u5BA1\u6838\uFF09";
+                enterpriseName = "锛堝師鏁版嵁宸查噸鏂板鍏ワ紝璇烽噸鏂版墽琛屽鏍革級";
             }
             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;
+                return firstOf(passengerMapper.selectObjs(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
+                    .select(PassengerEnterpriseMonthly::getReportPeriod)
+                    .orderByDesc(PassengerEnterpriseMonthly::getReportPeriod).last("LIMIT 1")));
             case "H204":
-                for (EnergyVehicleQuarterly m : energyMapper.selectList(
-                        new LambdaQueryWrapper<EnergyVehicleQuarterly>().select(EnergyVehicleQuarterly::getReportPeriod))) {
-                    max = maxPeriod(max, m.getReportPeriod());
-                }
-                break;
+                return firstOf(energyMapper.selectObjs(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
+                    .select(EnergyVehicleQuarterly::getReportPeriod)
+                    .orderByDesc(EnergyVehicleQuarterly::getReportPeriod).last("LIMIT 1")));
             case "INVEST":
-                for (InvestmentMonthly m : investMonthlyMapper.selectList(
-                        new LambdaQueryWrapper<InvestmentMonthly>().select(InvestmentMonthly::getReportPeriod))) {
-                    max = maxPeriod(max, m.getReportPeriod());
-                }
-                break;
+                return firstOf(investMonthlyMapper.selectObjs(new LambdaQueryWrapper<InvestmentMonthly>()
+                    .select(InvestmentMonthly::getReportPeriod)
+                    .orderByDesc(InvestmentMonthly::getReportPeriod).last("LIMIT 1")));
             case "CITY_BUS":
-                for (CityBusMonthly m : cityBusMapper.selectList(
-                        new LambdaQueryWrapper<CityBusMonthly>().select(CityBusMonthly::getReportPeriod))) {
-                    max = maxPeriod(max, m.getReportPeriod());
-                }
-                break;
+                return firstOf(cityBusMapper.selectObjs(new LambdaQueryWrapper<CityBusMonthly>()
+                    .select(CityBusMonthly::getReportPeriod)
+                    .orderByDesc(CityBusMonthly::getReportPeriod).last("LIMIT 1")));
             case "CITY_TAXI":
-                for (CityTaxiMonthly m : cityTaxiMapper.selectList(
-                        new LambdaQueryWrapper<CityTaxiMonthly>().select(CityTaxiMonthly::getReportPeriod))) {
-                    max = maxPeriod(max, m.getReportPeriod());
-                }
-                break;
+                return firstOf(cityTaxiMapper.selectObjs(new LambdaQueryWrapper<CityTaxiMonthly>()
+                    .select(CityTaxiMonthly::getReportPeriod)
+                    .orderByDesc(CityTaxiMonthly::getReportPeriod).last("LIMIT 1")));
             default:
-                for (H2032EnterpriseMonthly m : h2032Mapper.selectList(
-                        new LambdaQueryWrapper<H2032EnterpriseMonthly>().select(H2032EnterpriseMonthly::getReportPeriod))) {
-                    max = maxPeriod(max, m.getReportPeriod());
-                }
-                break;
+                return firstOf(h2032Mapper.selectObjs(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
+                    .select(H2032EnterpriseMonthly::getReportPeriod)
+                    .orderByDesc(H2032EnterpriseMonthly::getReportPeriod).last("LIMIT 1")));
         }
-        return max;
+    }
+
+    private String firstOf(List<Object> objs) {
+        if (objs == null || objs.isEmpty() || objs.get(0) == null) return null;
+        String v = String.valueOf(objs.get(0));
+        return v.trim().isEmpty() ? null : v.trim();
     }
 
     private String maxPeriod(String a, String b) {
@@ -1512,25 +1500,84 @@
     /** 浼佷笟鍚堝苟鈥滈棶棰樷�濆垪璇濇湳锛氭寜瑙勫垯绫诲瀷鐢熸垚鍙鐨勬牳瀹炴剰瑙� */
     private String issueSentence(AuditResult res) {
         String code = res.getRuleCode();
-        String dev = valueOf(res.getDeviation());
+        String name = valueOf(res.getRuleName());
         String act = valueOf(res.getActualValue());
-        String thr = valueOf(res.getThresholdValue());
         if ("RULE_001".equals(code) || "RULE_002".equals(code)) {
-            return "浼佷笟涓婃姤杞﹁締/鍚ㄤ綅涓庤繍鏀挎暟鎹姣斿瓨鍦ㄥ樊寮傦紙" + dev + "锛夛紝璇锋牳瀹�";
+            return "浼佷笟涓婃姤杞﹁締/鍚ㄤ綅涓庤繍鏀挎暟鎹姣斿瓨鍦ㄥ樊寮傦紙" + act + "锛夛紝璇锋牳瀹�";
         }
         if ("RULE_003".equals(code)) {
-            return "涓婃姤鍛ㄨ浆閲忎笌杞ㄨ抗娴嬬畻鍛ㄨ浆閲忓樊寮傛槑鏄撅紙" + dev + "锛夛紝璇峰弬鑰冭建杩规牳瀹炰紒涓氬懆杞噺";
+            return "涓婃姤鍛ㄨ浆閲忎笌杞ㄨ抗娴嬬畻鍛ㄨ浆閲忓樊寮傛槑鏄撅紙" + act + "锛夛紝璇峰弬鑰冭建杩规牳瀹炰紒涓氬懆杞噺";
         }
         if ("RULE_004".equals(code)) {
-            return "璐ц繍绫诲瀷鐜瘮鍙戠敓鍙樺寲锛岃鏍稿疄";
+            return "璐ц繍绫诲瀷鐜瘮鍙戠敓鍙樺寲" + (act.isEmpty() ? "" : "锛�" + act + "锛�") + "锛岃鍏虫敞鏄惁濉姤姝e父";
         }
         if ("RULE_005".equals(code)) {
-            return "鏃犻泦瑁呯杞︿絾瀛樺湪闆嗚绠辫揣杩愰噺锛岃鏍稿疄杞﹁締绫诲瀷涓庤揣杩愰噺濉姤";
+            return "鏃犻泦瑁呯杞︿絾瀛樺湪闆嗚绠辫揣杩愰噺锛�" + act + "锛夛紝璇锋牳瀹炶溅杈嗙被鍨嬩笌璐ц繍閲忓~鎶�";
         }
         if ("RULE_006".equals(code)) {
-            return "鏁磋溅鍚ㄤ綅瓒呭嚭 4~40 鍚ㄥ悎鐞嗚寖鍥达紝璇锋牳瀹炲钩鍧囧惃浣嶅~鎶�";
+            return "鏁磋溅骞冲潎鍚ㄤ綅瓒呭嚭鍚堢悊鑼冨洿锛�" + act + "锛夛紝璇锋牳瀹炲钩鍧囧惃浣嶅~鎶�";
         }
-        return valueOf(res.getRuleName()) + "寮傚父锛堝疄闄咃細" + act + "锛涘熀鍑嗭細" + thr + "锛�" + dev + "锛夛紝璇锋牳瀹�";
+        if (code != null && code.startsWith("RULE_P")) {
+            String special = passengerIssueSentence(res);
+            if (special != null) return special;
+        }
+        return name + "寮傚父锛�" + act + "锛夛紝璇锋牳瀹�";
+    }
+
+    /** 瀹㈣繍锛圚2031锛夎鍒欓棶棰樿瘽鏈細璐村悎妯℃澘绀轰緥鈥滄牳瀹炲紓甯革紙涓婃湡鎸囨爣=锛屾湰鏈熸寚鏍�=锛夎鏍稿疄鈥� */
+    private String passengerIssueSentence(AuditResult res) {
+        String code = res.getRuleCode();
+        String act = valueOf(res.getActualValue());
+        if ("RULE_P003".equals(code) || "RULE_P004".equals(code)) {
+            boolean turnover = "RULE_P004".equals(code);
+            String inner = act;
+            if (inner.startsWith("鏈湀涓庝笂鏈堢浉鍚�: ")) {
+                inner = inner.substring("鏈湀涓庝笂鏈堢浉鍚�: ".length());
+            }
+            java.util.List<String> clauses = new java.util.ArrayList<>();
+            for (String item : inner.split("銆�")) {
+                int eq = item.indexOf('=');
+                if (eq <= 0) { clauses.add(item); continue; }
+                String label = item.substring(0, eq).trim();
+                String v = item.substring(eq + 1).trim();
+                clauses.add("涓婃湀" + (turnover ? "鍛ㄨ浆閲�" : "瀹㈣繍閲�") + "=" + v + "锛屾湰鏈�" + label + "=" + v);
+            }
+            return valueOf(res.getRuleName()) + "寮傚父锛�" + String.join("锛�", clauses) + "锛夎鏍稿疄";
+        }
+        if ("RULE_P005".equals(code)) {
+            String pct = extractPct(valueOf(res.getThresholdValue()));
+            String prefix = pct.isEmpty() ? "鐝嚎骞冲潎杩愯窛鐜瘮瓒呴檺" : "鐝嚎骞冲潎杩愯窛鐜瘮瓒吢�" + pct + "%";
+            return prefix + "锛屾甯哥彮绾胯繍璺濆湪绾胯矾鏈彂鐢熼噸澶у彉鍖栧墠杈冧负绋冲畾锛�" + act + "锛夛紝璇锋牳瀹�";
+        }
+        if ("RULE_P006".equals(code)) {
+            String inner = act;
+            if (inner.startsWith("绛変簬涓婃湀: ")) {
+                inner = inner.substring("绛変簬涓婃湀: ".length());
+            }
+            java.util.List<String> clauses = new java.util.ArrayList<>();
+            for (String item : inner.split("銆�")) {
+                int eq = item.indexOf('=');
+                if (eq <= 0) { clauses.add(item); continue; }
+                String v = item.substring(eq + 1).trim();
+                clauses.add("涓婃湀杩愯窛=" + v + "锛屾湰鏈堣繍璺�=" + v);
+            }
+            return "鍖呰溅骞冲潎杩愯窛绛変簬涓婃湀锛�" + String.join("锛�", clauses)
+                + "锛夎鏍稿疄浼佷笟鏄惁鏄浐瀹氱嚎璺梾娓告垨鑰呴�氬嫟涓氬姟";
+        }
+        return null;
+    }
+
+    private String extractPct(String thresholdText) {
+        if (thresholdText == null || thresholdText.isEmpty()) return "";
+        int idx = thresholdText.indexOf('\u00b1');
+        if (idx < 0) return "";
+        StringBuilder num = new StringBuilder();
+        for (int i = idx + 1; i < thresholdText.length(); i++) {
+            char c = thresholdText.charAt(i);
+            if (Character.isDigit(c) || c == '.') num.append(c);
+            else if (!Character.isWhitespace(c)) break;
+        }
+        return num.toString();
     }
 
     private String auditStatusCn(String status) {
diff --git a/traffic-audit-web/src/views/AuditResult.vue b/traffic-audit-web/src/views/AuditResult.vue
index f4b7278..9a78adf 100644
--- a/traffic-audit-web/src/views/AuditResult.vue
+++ b/traffic-audit-web/src/views/AuditResult.vue
@@ -50,7 +50,9 @@
           <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">
+        <div v-if="!focusMode" class="audit-filter-bar">
+        <el-form :inline="true">
+          <el-form-item label="绛涢��"></el-form-item>
           <el-form-item label="浼佷笟鍚嶇О">
             <el-input v-model="enterpriseFilter" placeholder="杈撳叆浼佷笟鍚嶇О鍏抽敭瀛�" clearable style="width:200px" @keyup.enter.native="queryResults"></el-input>
           </el-form-item>
@@ -59,11 +61,12 @@
               <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-form-item label="闂鑼冨洿">
             <el-radio-group v-model="issueScope" size="small" @change="updateBatchSelection">
-              <el-radio-button label="verify">鏍稿疄鍨�</el-radio-button>
-              <el-radio-button label="all">鍏ㄩ儴</el-radio-button>
+              <el-radio-button label="verify">浠呴渶鏍稿疄</el-radio-button>
+              <el-radio-button label="all">鍚彁閱掑瀷</el-radio-button>
             </el-radio-group>
+            <span class="hint">鎻愰啋鍨嬩粎鎻愮ず鍙樺寲銆佷笉闇�澶勭悊锛屽鍑洪粯璁や笉鍚�</span>
           </el-form-item>
           <el-form-item label="鐘舵��">
             <el-select v-model="statusScope" style="width:110px" @change="updateBatchSelection">
@@ -87,9 +90,11 @@
             <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 }} 鏉¢棶棰橈紝{{ mergedResults.length }} 瀹朵紒涓�</span>
+            <span style="color:#303133;font-size:13px;font-weight:600">褰撳墠绛涢�� {{ filteredResults.length }} 鏉� / {{ mergedResults.length }} 瀹朵紒涓�</span>
+            <span style="color:#909399;font-size:12px;margin-left:8px">锛堝叡 {{ results.length }} 鏉¢棶棰橈級</span>
           </el-form-item>
         </el-form>
+        </div>
         <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"
@@ -354,7 +359,7 @@
   data() {
     return {
       period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false,
-      enterpriseFilter: '', ruleFilter: '', viewMode: 'detail', issueScope: 'verify', statusScope: 'all', batchSelection: [], focusMode: false,
+      enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', issueScope: 'verify', statusScope: 'all', batchSelection: [], focusMode: false,
       explainDrafts: {}, savingExplainId: null, expandedExplain: null,
       monthVisible: false, monthLoading: false, monthType: '', monthPeriod: '', monthKeyword: '', monthRecords: [], monthColumns: []
     }
@@ -367,7 +372,7 @@
     },
     severityStats() {
       const s = { red: 0, yellow: 0, pending: 0, done: 0 }
-      this.results.forEach(r => {
+      this.filteredResults.forEach(r => {
         if (r.alertLevel === 'RED') s.red++
         else if (r.alertLevel === 'YELLOW') s.yellow++
         if (r.status === 'PENDING') s.pending++
@@ -769,4 +774,9 @@
 .mr-sec-tab th { background: #F5F7FA; font-weight: 600; }
 .mr-note { margin: 6px 12px 12px; background: #FDF6EC; border: 1px solid #FAECD8; border-radius: 4px; padding: 8px 10px; font-size: 12px; color: #8a6d3b; white-space: pre-wrap; word-break: break-all; }
 .mr-note-line { margin: 2px 0; }
+
+.audit-filter-bar { background:#FAFAFA; border:1px solid #EBEEF5; border-radius:8px; padding:2px 12px 0; margin-top:8px; flex:none; }
+.audit-filter-bar .el-form-item { margin-bottom:6px; }
+.audit-filter-bar .el-form-item__label { color:#606266; font-weight:600; }
+.hint { color:#909399; font-size:12px; margin-left:6px; white-space:nowrap; }
 </style>
diff --git a/traffic-audit-web/src/views/DataView.vue b/traffic-audit-web/src/views/DataView.vue
index 7ab9413..3fafdd5 100644
--- a/traffic-audit-web/src/views/DataView.vue
+++ b/traffic-audit-web/src/views/DataView.vue
@@ -16,32 +16,23 @@
       </el-aside>
       <el-main>
         <h3>瀵煎叆鏁版嵁鏌ョ湅</h3>
-        <div class="type-row">
-          <span class="type-label">鏁版嵁绫诲瀷</span>
+        <div class="toolbar">
+          <span class="toolbar-label">鏁版嵁绫诲瀷</span>
           <el-radio-group v-model="activeModule" size="small" @change="onModuleChange">
             <el-radio-button v-for="g in modules" :key="g.key" :label="g.key">{{ g.name }}</el-radio-button>
           </el-radio-group>
-          <el-select v-model="type" size="small" style="width:300px" placeholder="閫夋嫨鏁版嵁绫诲瀷" @change="onTypeSelect">
+          <el-select v-model="type" size="small" style="width:250px" placeholder="閫夋嫨鏁版嵁绫诲瀷" @change="onTypeSelect">
             <el-option-group :label="activeTypes.name">
               <el-option v-for="t in activeTypes.types" :key="t.value" :label="t.label" :value="t.value"></el-option>
             </el-option-group>
           </el-select>
+          <span class="toolbar-sep"></span>
+          <el-date-picker v-model="period" type="month" placeholder="鎶ヨ〃鏈燂紙鍏ㄩ儴锛�" value-format="yyyy-MM" clearable style="width:150px" @change="doQuery"></el-date-picker>
+          <el-input v-model="keyword" placeholder="浼佷笟/甯傚窞鍚嶇О" clearable style="width:180px" @keyup.enter.native="doQuery"></el-input>
+          <el-button type="primary" size="small" @click="doQuery">鏌ヨ</el-button>
+          <el-button size="small" icon="el-icon-full-screen" @click="toggleFullscreen">鍏ㄥ睆鏌ョ湅</el-button>
         </div>
-        <el-form :inline="true" class="filter-row">
-          <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:200px" @keyup.enter.native="loadData"></el-input>
-          </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="load-hint">鍏� {{ total }} 鏉¤褰曪紝宸插姞杞� {{ records.length }} 鏉�</div>
         <div class="table-wrap" ref="tableWrap">
           <div class="table-area">
             <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%">
@@ -52,12 +43,8 @@
               </el-table-column>
             </el-table>
           </div>
-          <el-pagination style="margin-top:15px;text-align:right"
-            background layout="total, prev, pager, next, sizes"
-            :total="total" :current-page.sync="page" :page-size.sync="size"
-            :page-sizes="[20, 50, 100]" @current-change="loadData" @size-change="loadData">
-          </el-pagination>
         </div>
+
       </el-main>
     </el-container>
   </el-container>
@@ -99,7 +86,7 @@
       period: '',
       keyword: '',
       page: 1,
-      size: 20,
+      size: 50,
       total: 0,
       records: [],
       columns: [],
@@ -126,9 +113,14 @@
       if (!g.types.some(t => t.value === this.type)) {
         this.type = g.types[0].value
       }
+      this.doQuery()
+    },
+    onTypeSelect() { this.doQuery() },
+    doQuery() {
+      this.page = 1
+      this.records = []
       this.loadData()
     },
-    onTypeSelect() { this.loadData() },
     loadData() {
       this.loading = true
       this.loadingMore = false
@@ -262,43 +254,14 @@
 .type-row { margin-bottom: 10px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
 .type-label { color: #606266; font-weight: 600; }
 .filter-row { margin-bottom: 4px; }
-#app .table-wrap {
-  display: flex;
-  flex-direction: column;
-  height: calc(100vh - 330px);
-  min-height: 300px;
-}
-#app .table-area {
-  flex: 1 1 auto;
-  min-height: 0;
-  overflow: hidden;
-}
-#app .table-area .el-table {
-  width: 100%;
-  height: 100%;
-}
-#app .table-wrap .el-pagination {
-  flex: none;
-  margin-top: 8px;
-}
-#app .table-area:fullscreen {
-  padding: 20px;
-  background: #fff;
-  overflow: auto;
-}
-#app .table-wrap:fullscreen {
-  padding: 20px;
-  background: #fff;
-  overflow: auto;
-  display: flex;
-  flex-direction: column;
-  height: 100vh;
-}
-#app .table-wrap:fullscreen .table-area {
-  flex: 1 1 auto;
-  min-height: 0;
-}
-#app .table-wrap:fullscreen .el-pagination {
-  flex: none;
-}
+#app .toolbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; padding:8px 12px; background:#FAFAFA; border:1px solid #EBEEF5; border-radius:8px; margin-bottom:8px; }
+#app .toolbar-label { color:#606266; font-weight:600; white-space:nowrap; }
+#app .toolbar-sep { width:1px; height:22px; background:#DCDFE6; margin:0 2px; }
+#app .load-hint { color:#909399; font-size:12px; margin-bottom:6px; }
+#app .table-wrap { display:flex; flex-direction:column; height:calc(100vh - 250px); min-height:300px; }
+#app .table-area { flex:1 1 auto; min-height:0; overflow:hidden; }
+#app .table-area .el-table { width:100%; height:100%; }
+#app .table-area:fullscreen { padding:20px; background:#fff; overflow:auto; }
+#app .table-wrap:fullscreen { padding:20px; background:#fff; overflow:auto; display:flex; flex-direction:column; height:100vh; }
+#app .table-wrap:fullscreen .table-area { flex:1 1 auto; min-height:0; }
 </style>
\ No newline at end of file
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index 2cbe51b..54f5513 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -112,7 +112,25 @@
                 <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">
+              <table class="preview-summary wb-table" v-if="it.key === 'summaryWorkbook' && it.summary && it.summary.length">
+                <thead>
+                  <tr><th style="width:44%">鎸囨爣</th><th style="width:30%">鏁版嵁</th><th>鍚屾瘮</th></tr>
+                </thead>
+                <tbody>
+                  <template v-for="(sec, si) in wbRows(it.summary)">
+                    <tr v-for="(row, ri) in sec.rows" :key="si + '-' + ri">
+                      <td v-if="ri === 0" :rowspan="sec.rows.length" class="wb-sec">{{ sec.name }}</td>
+                      <td class="wb-label">{{ row.label }}</td>
+                      <td class="wb-value">{{ row.value === null ? '鈥�' : fmtVal(row.value) }}</td>
+                      <td class="wb-yoy" :class="{ 'yoy-empty': row.yoy === null }">{{ row.yoy === null ? '鈥�' : row.yoy }}</td>
+                    </tr>
+                  </template>
+                </tbody>
+              </table>
+              <div v-if="it.key === 'summaryWorkbook' && wbMissing(it.summary)" class="wb-note">
+                閮ㄥ垎鎸囨爣缂哄皯涓婂勾鍚屾湡锛坽{ wbPrevYear() }}锛夊熀鏈熸暟鎹紝鍚屾瘮鏆傛湭璁$畻
+              </div>
+              <table class="preview-summary" v-else-if="it.summary && it.summary.length">
                 <tbody>
                   <tr v-for="(s, i) in it.summary" :key="i">
                     <td>{{ s.label }}</td>
@@ -380,6 +398,65 @@
       }
       return String(v)
     },
+    wbRows(summary) {
+      const get = (key) => {
+        const hit = (summary || []).find(x => x.label === key)
+        return hit && hit.value !== null && hit.value !== undefined && hit.value !== '' ? hit.value : null
+      }
+      const pct = (key) => {
+        const v = get(key)
+        return v === null ? null : this.fmtVal(v) + '%'
+      }
+      const mk = (defs) => defs.map(d => ({
+        label: d[0],
+        value: get(d[1]),
+        yoy: d[2] ? pct(d[2]) : null
+      }))
+      return [
+        {
+          name: '閬撹矾璐ц繍',
+          rows: mk([
+            ['褰撴湀璐ц繍閲�(涓囧惃)', '鍏ㄧ渷璐ц繍閲徛峰綋鏈�'],
+            ['褰撴湀璐х墿鍛ㄨ浆閲�(涓囧惃)', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛峰綋鏈�', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛峰綋鏈堝悓姣�'],
+            ['绱璐ц繍閲�(涓囧惃)', '鍏ㄧ渷璐ц繍閲徛风疮璁�'],
+            ['绱璐х墿鍛ㄨ浆閲�(涓囧惃鍏噷)', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛风疮璁�', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛风疮璁″悓姣�']
+          ])
+        },
+        {
+          name: '鍩庡競瀹㈣繍',
+          rows: mk([
+            ['褰撴湀鍩庡競瀹㈣繍閲�:', '鍩庡競瀹㈣繍閲徛峰綋鏈�'],
+            ['褰撴湀鍩庡競瀹㈣繍鍛ㄨ浆閲�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛峰綋鏈�'],
+            ['绱鍩庡競瀹㈣繍閲�:', '鍩庡競瀹㈣繍閲徛风疮璁�'],
+            ['绱鍩庡競瀹㈣繍鍛ㄨ浆閲�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛风疮璁�']
+          ])
+        },
+        {
+          name: '涓彛寰�',
+          rows: mk([
+            ['褰撴湀涓彛寰勫杩愰噺', '涓彛寰勫杩愰噺路褰撴湀'],
+            ['褰撴湀涓彛寰勫懆杞噺', '涓彛寰勫懆杞噺路褰撴湀'],
+            ['绱涓彛寰勫杩愰噺', '涓彛寰勫杩愰噺路绱'],
+            ['绱涓彛寰勫懆杞噺', '涓彛寰勫懆杞噺路绱']
+          ])
+        }
+      ]
+    },
+    wbMissing(summary) {
+      let total = 0
+      let have = 0
+      ;(summary || []).forEach(x => {
+        if (String(x.label).indexOf('鍚屾瘮') >= 0) return
+        total++
+        if (x.value !== null && x.value !== undefined && x.value !== '') have++
+      })
+      return total > 0 && have < total
+    },
+    wbPrevYear() {
+      const m = /^(\d{4})-(\d{2})$/.exec(this.previewPeriod || '')
+      if (!m) return '涓婂勾鍚屾湡'
+      return (Number(m[1]) - 1) + '-' + m[2]
+    },
     coverageText(it) {
       const cov = it.monthCoverage || []
       if (!cov.length) return '鏃犳暟鎹�'
@@ -494,4 +571,11 @@
 @media (max-width: 900px) {
   .panel-head { flex-direction: column; align-items: flex-start; }
 }
+.wb-table th { background:#f5f7fa; }
+.wb-sec { background:#F7F9FC; font-weight:600; text-align:center; color:#303133; }
+.wb-label { color:#303133; }
+.wb-value { text-align:right; font-variant-numeric: tabular-nums; }
+.wb-yoy { text-align:right; color:#606266; }
+.wb-yoy.yoy-empty { color:#C0C4CC; }
+.wb-note { color:#E6A23C; font-size:12px; margin:2px 0 8px; }
 </style>

--
Gitblit v1.9.1