From 757dcb3fcad665eaef93074a4981c4134f7a027e Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期日, 04 十月 2026 17:33:43 +0800
Subject: [PATCH] feat: complete system management and repair workflow updates

---
 emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserContext.java         |   24 
 emergency-repair-server/src/main/resources/mapper/ReportMapper.xml                                     |  112 
 emergency-repair-web/src/views/system/LoginLogManage.vue                                               |   96 +
 emergency-repair-web/public/logo.png                                                                   |    0 
 emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java               |  316 ++-
 emergency-repair-web/src/views/fault/FaultReport.vue                                                   |   45 
 emergency-repair-web/src/utils/sensitive.js                                                            |   25 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleMenu.java               |    3 
 emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/AuditMetaObjectHandler.java     |   24 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/log/OperationLogInterceptor.java      |  112 +
 emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java                   |   22 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Contractor.java              |    3 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysLoginLog.java               |   29 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/controller/WorkOrderController.java |    5 
 emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserInterceptor.java     |   42 
 emergency-repair-server/src/main/java/com/emergencyrepair/config/WebConfig.java                        |   14 
 emergency-repair-web/src/views/report/ReportPage.vue                                                   |    8 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java            |  880 ++++++++++
 emergency-repair-web/src/views/system/OrganizationManage.vue                                           |  127 +
 emergency-repair-web/src/views/completion/CompletionList.vue                                           |   19 
 emergency-repair-web/public/sensitive-words.json                                                       |   29 
 requirement/界面还原.html                                                                                  |    6 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysOperationLog.java           |   33 
 emergency-repair-web/src/views/system/MenuManage.vue                                                   |  196 ++
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/EngineeringMajor.java        |    3 
 emergency-repair-web/src/views/acceptance/AcceptanceList.vue                                           |    4 
 requirement/工程状态机设计.md                                                                                 |    6 
 requirement/交接文件_2026.10.3.md                                                                          |  506 ++++++
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Acceptance.java              |    3 
 emergency-repair-server/src/main/resources/application.yml                                             |    2 
 emergency-repair-web/src/views/fault/SimpleApproval.vue                                                |   20 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysOperationLogMapper.java     |    7 
 emergency-repair-web/src/components/RepairLogo.vue                                                     |   29 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/WorkOrder.java               |    3 
 emergency-repair-web/src/views/fault/DispatchPage.vue                                                  |   16 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Fault.java                   |    3 
 emergency-repair-web/src/components/MenuNode.vue                                                       |    4 
 requirement/决策表_待确认项.md                                                                                |    8 
 emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java                     |  151 +
 emergency-repair-web/src/views/Login.vue                                                               |    6 
 emergency-repair-web/src/router/index.js                                                               |   16 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/CompletionInfo.java          |    3 
 HANDOFF.md                                                                                             |   41 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/BaseEquipment.java           |    3 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java  |  117 +
 emergency-repair-web/src/main.js                                                                       |   20 
 emergency-repair-server/pom.xml                                                                        |    4 
 emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java             |   23 
 emergency-repair-web/src/views/system/UserManage.vue                                                   |  253 +++
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleUser.java               |    3 
 emergency-repair-web/src/views/system/TeamMajorManage.vue                                              |    8 
 requirement/敏感词字典.json                                                                                 |   16 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java      |  131 +
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/TeamMajor.java               |    3 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRole.java                   |    3 
 emergency-repair-web/src/views/fault/FaultEditDialog.vue                                               |   25 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysMenu.java                   |    3 
 emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java             |  185 ++
 emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysTenantMenu.java             |    3 
 emergency-repair-web/src/views/Dashboard.vue                                                           |   14 
 emergency-repair-web/src/views/fault/XcbReview.vue                                                     |   35 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysLoginLogMapper.java         |    7 
 emergency-repair-web/src/views/system/RoleManage.vue                                                   |  189 ++
 emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java        |  152 +
 emergency-repair-server/src/main/java/com/emergencyrepair/business/service/ReportService.java          |    2 
 emergency-repair-web/src/views/system/MenuPermission.vue                                               |   31 
 emergency-repair-server/src/main/java/com/emergencyrepair/common/entity/BaseAuditEntity.java           |   22 
 requirement/前44页需求梳理.md                                                                                |    6 
 emergency-repair-web/src/views/system/OperationLogManage.vue                                           |  104 +
 emergency-repair-web/src/layout/AppLayout.vue                                                          |   13 
 emergency-repair-web/src/views/system/ContractorManage.vue                                             |  115 
 emergency-repair-web/src/api/index.js                                                                  |    2 
 emergency-repair-web/src/utils/dict.js                                                                 |   10 
 requirement/改进20261003.txt                                                                             |   29 
 emergency-repair-web/src/views/completion/CompletionReport.vue                                         |   12 
 emergency-repair-web/src/views/system/TreeManage.vue                                                   |   54 
 emergency-repair-web/src/components/ProductTreePicker.vue                                              |   89 
 77 files changed, 3,989 insertions(+), 698 deletions(-)

diff --git a/HANDOFF.md b/HANDOFF.md
index 56d211e..9834764 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -289,14 +289,14 @@
 
 ### 0.10 2026-09-28 涓撲笟閰嶇疆涓庢晠闅滅紪鍙疯嚜鍔ㄧ敓鎴愶紙鏈浼氳瘽锛屽凡瀹炵幇骞堕獙璇侊級
 
-鐢ㄦ埛鏈缁欏嚭鐨� 4 鏉℃柊闇�姹傦細鈶� 绯荤粺绠$悊澧炲姞鈥滃伐绋嬩笓涓氶厤缃�濓紙瀛楁浠呭悕绉帮級锛涒憽 绯荤粺绠$悊澧炲姞鈥淭闃熶笓涓氶厤缃�濓紙瀛楁鍚嶇О銆佸垎绫� A/B锛夛紱鈶� 鏁呴殰缂栧彿涓嶅啀鎵嬪伐杈撳叆锛屾敼涓烘寜 `娼�1鍩�-X鍙�/A-YYMMDD-nnn` 鑷姩鐢熸垚锛涒懀 娲惧崟鍙蜂负 `鏁呴殰缂栧彿/nn`锛屾壙淇崟浣嶅簭鍙蜂粠 01 璧枫��
+鐢ㄦ埛鏈缁欏嚭鐨� 4 鏉℃柊闇�姹傦細鈶� 绯荤粺绠$悊澧炲姞鈥滃伐绋嬩笓涓氶厤缃�濓紙瀛楁浠呭悕绉帮級锛涒憽 绯荤粺绠$悊澧炲姞鈥淭闃熶笓涓氶厤缃�濓紙瀛楁鍚嶇О銆佸垎绫� A/B锛夛紱鈶� 鏁呴殰缂栧彿涓嶅啀鎵嬪伐杈撳叆锛屾敼涓烘寜 `鍥哄畾鍓嶇紑-X鍙�/A-YYMMDD-nnn` 鑷姩鐢熸垚锛涒懀 娲惧崟鍙蜂负 `鏁呴殰缂栧彿/nn`锛屾壙淇崟浣嶅簭鍙蜂粠 01 璧枫��
 
 宸插疄鐜帮細
 - 鈥滃伐绋嬩笓涓氶厤缃�濓細鑿滃崟 `/system/engineering-majors`锛屽瓧娈典粎鈥滃悕绉扳�濓紱琛� `LX.T_ENGINEERING_MAJOR(ID, NAME, CREATE_TIME, UPDATE_TIME)`锛涙帴鍙� `/api/system/engineering-majors`锛圙ET/POST/PUT/DELETE锛夈��
 - 鈥淭闃熶笓涓氶厤缃�濓細鑿滃崟 `/system/team-majors`锛屽瓧娈碘�滃悕绉般�佸垎绫伙紙A/B锛夆�濓紱琛� `LX.T_TEAM_MAJOR(ID, NAME, CATEGORY, CREATE_TIME, UPDATE_TIME)`锛涙帴鍙� `/api/system/team-majors`锛圙ET/POST/PUT/DELETE锛夈��
 - 涓ゅ閰嶇疆鍧囨牎楠屽悕绉伴潪绌轰笌閲嶅悕锛汿闃熶笓涓氬垎绫诲彧鍏佽 A/B銆傚惎鍔ㄦ椂 `DataInitializer.ensureMajorConfigMenus` 骞傜瓑琛ヨ彍鍗曪紝`seedMajors` 棰勭疆婕旂ず鏁版嵁锛氬伐绋嬩笓涓� 鍔ㄥ姏/鐢垫皵/娑插帇/杈呮満锛汿闃熶笓涓� 鍔ㄥ姏=A銆佹恫鍘�=A銆佺數姘�=B銆佽緟鏈�=B锛堥粯璁ゆ媶鍒嗭紝鍙湪椤甸潰淇敼锛夈��
-- 鏁呴殰缂栧彿鑷姩鐢熸垚锛歚BusinessService.generateFaultCode`锛屾牸寮� `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`銆�
-  - 鍓嶇紑鍥哄畾 `娼�1鍩�-`锛沊鍙峰彇 3 浣嶆暟瀛楋紙`X01` 鈫� `001`锛夈��
+- 鏁呴殰缂栧彿鑷姩鐢熸垚锛歚BusinessService.generateFaultCode`锛屾牸寮� `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`銆�
+  - 鍓嶇紑鍥哄畾 `鍥哄畾鍓嶇紑-`锛沊鍙峰彇 3 浣嶆暟瀛楋紙`X01` 鈫� `001`锛夈��
   - 鍒嗙被鍙栤�淭闃熶笓涓氶厤缃�濈殑 A/B锛涙湭閰嶇疆鎴栧垎绫婚潪娉曟椂淇濆瓨鎶ラ敊骞舵彁绀哄厛琛岀淮鎶ゃ��
   - YYMMDD 鍙栦笂鎶ユ棩鏈燂紱nnn 涓衡�滃悓涓� X鍙� + 鍚屼竴 T闃熶笓涓� + 鍚屼竴涓婃姤鏃ユ湡鈥濈殑搴忓彿锛屼粠 001 璧枫��
   - 鏂板缓鑷姩鐢熸垚锛涗慨鏀规椂寮哄埗娌跨敤鍘熺紪鍙凤紙鏀� T闃熶笓涓�/鏃ユ湡涔熶笉閲嶆柊鐢熸垚锛夈��
@@ -309,8 +309,8 @@
 楠岃瘉锛�2026-09-28锛夛細
 - 鍚庣 `mvn -q -DskipTests package` 閫氳繃锛涘墠绔� `vue-cli-service build` 閫氳繃锛堜粎鏃㈡湁鍖呬綋绉彁绀猴級銆�
 - 鎺ュ彛锛氬伐绋嬩笓涓氥�乀闃熶笓涓氬悇杩斿洖 4 鏉★紱`/api/system/menu/nav` 绯荤粺绠$悊涓嬫柊澧炰袱椤癸紙鎺掑簭 35/36锛夈��
-- 鑷姩缂栧彿锛歚X02 + 鍔ㄥ姏涓撲笟(A) + 2026-09-28` 鈫� `娼�1鍩�-002/A-260928-001`銆乣娼�1鍩�-002/A-260928-002`锛涙崲 T闃熶笓涓氾紙鐢垫皵 B锛夆啋 `娼�1鍩�-002/B-260928-001`锛涙崲涓婃姤鏃ユ湡 鈫� `娼�1鍩�-001/A-260929-001`锛涗慨鏀规晠闅滄敼 T闃熶笓涓氬悗缂栧彿淇濇寔涓嶅彉銆�
-- 娲惧崟绔埌绔細涓�鏉℃晠闅滃垎閰嶄袱涓壙淇崟浣嶏紝鐢熸垚 `娼�1鍩�-002/B-260928-001/01`銆乣.../02`銆傛祴璇曟暟鎹凡浠� `LX.T_FAULT`銆乣LX.T_WORK_ORDER` 娓呯悊銆�
+- 鑷姩缂栧彿锛歚X02 + 鍔ㄥ姏涓撲笟(A) + 2026-09-28` 鈫� `鍥哄畾鍓嶇紑-002/A-260928-001`銆乣鍥哄畾鍓嶇紑-002/A-260928-002`锛涙崲 T闃熶笓涓氾紙鐢垫皵 B锛夆啋 `鍥哄畾鍓嶇紑-002/B-260928-001`锛涙崲涓婃姤鏃ユ湡 鈫� `鍥哄畾鍓嶇紑-001/A-260929-001`锛涗慨鏀规晠闅滄敼 T闃熶笓涓氬悗缂栧彿淇濇寔涓嶅彉銆�
+- 娲惧崟绔埌绔細涓�鏉℃晠闅滃垎閰嶄袱涓壙淇崟浣嶏紝鐢熸垚 `鍥哄畾鍓嶇紑-002/B-260928-001/01`銆乣.../02`銆傛祴璇曟暟鎹凡浠� `LX.T_FAULT`銆乣LX.T_WORK_ORDER` 娓呯悊銆�
 - 娴忚鍣細涓や釜鏂伴厤缃〉姝e父娓叉煋锛涙晠闅滀笂鎶ユ柊澧炶〃鍗曗�滄晠闅滅紪鍙封�濆彧璇讳笖鍗犱綅鈥滀繚瀛樺悗鑷姩鐢熸垚鈥濓紝T闃熶笓涓�/宸ョ▼涓撲笟涓嬫媺涓洪厤缃」锛涗慨澶嶅悗鎺у埗鍙版棤鏂板鎶ラ敊銆�
 - 鍘嗗彶婕旂ず鏁呴殰浠嶄娇鐢� `GZ-2026-0xx` 鏃х紪鍙凤紙鏈縼绉伙紝閬垮厤褰卞搷宸叉淳宸ュ崟寮曠敤锛夛紱鏂板缓鏁呴殰涓�寰嬩娇鐢ㄦ柊瑙勫垯銆�
 - 鏂囨。宸插悓姝ワ細`requirement\鍐崇瓥琛╛寰呯‘璁ら」.md`銆乣鍓�44椤甸渶姹傛⒊鐞�.md`銆乣宸ョ▼鐘舵�佹満璁捐.md`銆乣鐣岄潰杩樺師.html`锛屼互鍙婇椤靛叕鍛娾�滄淳鍗曡鍒欌�濇枃妗堛��
@@ -646,7 +646,7 @@
 
 **鏈浼氳瘽瀹屾垚锛堢粏鑺傝 0.10 / 0.11锛�**
 - 鍐崇瓥琛� B1鈥揃4 鎸夊缓璁叧闂苟钀藉湴锛氱郴缁熺鐞嗘柊澧炩�滃伐绋嬩笓涓氶厤缃�濓紙瀛楁锛氬悕绉帮級銆佲�淭闃熶笓涓氶厤缃�濓紙瀛楁锛氬悕绉般�佸垎绫� A/B锛夈��
-- 鏁呴殰缂栧彿鏀逛负鑷姩鐢熸垚锛歚娼�1鍩�-X鍙�/A-YYMMDD-nnn`锛圶鍙� 3 浣嶆暟瀛楋紝A锛漈闃熶笓涓� A/B锛宯nn 涓哄悓涓� X鍙凤紜鍚屼竴涓撲笟锛嬪悓涓�涓婃姤鏃ユ湡鍐呬粠 001 閫掑锛夛紱娲惧崟鍙凤紳`鏁呴殰缂栧彿/nn`锛堟壙淇崟浣嶅簭鍙凤紝浠� 01 璧凤級銆�
+- 鏁呴殰缂栧彿鏀逛负鑷姩鐢熸垚锛歚鍥哄畾鍓嶇紑-X鍙�/A-YYMMDD-nnn`锛圶鍙� 3 浣嶆暟瀛楋紝A锛漈闃熶笓涓� A/B锛宯nn 涓哄悓涓� X鍙凤紜鍚屼竴涓撲笟锛嬪悓涓�涓婃姤鏃ユ湡鍐呬粠 001 閫掑锛夛紱娲惧崟鍙凤紳`鏁呴殰缂栧彿/nn`锛堟壙淇崟浣嶅簭鍙凤紝浠� 01 璧凤級銆�
 - 鏁呴殰涓婃姤椤碉細浠诲姟鐘舵�佹爣绛句笌浠诲姟鐘舵�佷笅鎷夋鎷嗘垚涓よ锛堟爣绛炬崲鍒颁笅涓�琛岋級銆�
 - 瀹屽伐淇℃伅涓婃姤椤碉紙AXU15锛夛細淇悗璐ㄩ噺鎯呭喌鐙崰鏁磋锛涢」鐩悕绉颁笌楠屾敹鏂瑰紡鍚屼竴琛岋紱鏁呴殰璇存槑銆佸師鍥犲垎鏋愬悇鐙崰鏁磋锛涒�滃鐞嗚В鍐虫儏鍐碘�濇敼涓哄彲澶氳鐨勮〃鏍硷紙搴忓彿锛忓鐞嗚В鍐虫儏鍐碉級锛涚淮淇櫒鏉愯〃鍒楁敼涓哄簭鍙枫�佸悕绉般�佽鏍煎瀷鍙枫�佹妧鏈弬鏁般�佹暟閲忋�佽閲忓崟浣嶃�佸悎鏍艰瘉鏄庛�佸嚭搴撳崟鍙枫�佹潵婧愩�佸娉ㄣ�傚悗绔柊澧� `LX.T_COMPLETION_INFO.SOLUTIONS_JSON` 涓� `MaterialItem.certificate`锛屽苟缁х画鍏煎鍘� `SOLUTION` 瀛楁銆�
 - 椤圭洰绾冲叆鍏徃 Git 浠撳簱锛歚http://admin@61.183.254.94:3000/r/EmergencyRepair.git`锛屽垎鏀� `main`锛岄鎻愪氦 `5305262`锛�103 涓枃浠讹級锛岄殢鍚� `0bacdcf` 琛ヨ杩滅淇℃伅銆�
@@ -697,3 +697,32 @@
 2. `sensitive.js` 鏀瑰姩宸茶幏纭骞堕殢鏈鎻愪氦鎺ㄩ�侊紱`requirement\~$鍘熷闇�姹�.docx` 鏄� Word 閿佹枃浠讹紝涓嶈鎻愪氦銆�
 3. 闇�姹備晶寰呯‘璁ら」涓嶅彉锛氬喅绛栬〃 B6鈥揃10銆丄XU15 瀛楁鏉ユ簮涓庡彲鏀规�с�佸畬宸ラ獙鏀跺悇鐜妭瑙掕壊涓庢墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾т笌 AXU14 T鍛樿瘎鍒嗗瓧娈电殑鍏崇郴銆�
 4. 鍚庣画鏀瑰姩瀹屾垚鍚庢帹閫佸埌鍏徃 Git 浠撳簱 `main` 鍒嗘敮銆�
+
+## 17. 鏀跺熬蹇収锛�2026-10-04 鍛ㄥ叚锛岄渶姹� 11 / 12 / 17-19 鑱旇皟鏀跺熬锛�
+
+**鏈疆瀹屾垚鍐呭**
+- 闇�姹� 11锛氬畬宸ユ儏鍐靛鐞嗗皢鐘舵�佹敼涓衡�滃畬宸モ�濇椂锛屽彧鏇存柊娲惧伐鍗曠姸鎬侊紝涓嶅啀璺宠浆鈥滃畬宸ヤ俊鎭~鎶モ�濄�傚畬宸ュ鐞嗕笌瀹屽伐濉姤鐢变笉鍚岀敤鎴峰垎鍒搷浣溿��
+- 闇�姹� 12锛氬畬宸ヤ俊鎭~鎶ョ殑娲惧伐鍗曢�夋嫨鍒楄〃鍙睍绀衡�滃凡鍦ㄥ畬宸ユ儏鍐靛鐞嗕腑纭瀹屽伐銆佸睘浜庡綋鍓嶇櫥褰曠敤鎴锋墍灞炲崟浣嶃�佷笖灏氭湭濉姤鈥濈殑娲惧伐鍗曪紝骞跺鍔犲伐绋嬪悕绉帮紙椤圭洰鍚嶇О锛夋樉绀恒��
+- 闇�姹� 17锛氳鑹茬鐞嗙殑鈥滅敤鎴疯缃�濆鍔犫�滃叏閮� / 宸茶缃�濊繃婊ゅ垏鎹€��
+- 闇�姹� 18锛氭柊澧炩�滅粍缁囨満鏋勨�濋儴闂ㄦ爲琛ㄩ〉闈紝鑿滃崟 `/system/organization`锛岄〉闈� `OrganizationManage.vue`锛屾暟鎹潵鑷� `CSICZB_ZB.SYS_DEPT`锛屽悗绔帴鍙� `/api/system/depts`銆傚綋鍓嶇幆澧冭繑鍥� 34 涓儴闂紝鏀寔鍚嶇О銆佺紪鐮併�佽礋璐d汉銆侀儴闂ㄧ被鍨嬬瓫閫夈��
+- 闇�姹� 19锛氭壙淇崟浣嶆敼涓鸿鍙� `SYS_DEPT.DEPT_TYPE='cj'` 鐨勯儴闂ㄨ褰曪紝閮ㄩ棬 ID 浣滀负鎵夸慨鍗曚綅 ID锛涙壙淇崟浣嶆帴鍙d繚鐣� GET锛岄〉闈㈡敼涓哄彧璇汇�傚綋鍓嶇幆澧冭繑鍥� 7 涓� cj 閮ㄩ棬锛孹CB瀹℃牳銆佹淳鍗曞潎浣跨敤 cj 閮ㄩ棬 ID/鍚嶇О銆�
+
+**涓昏鏂囦欢**
+- 鍚庣锛歚emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java`銆乣emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java`銆乣emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java`銆�
+- 鍓嶇锛歚emergency-repair-web/src/views/system/OrganizationManage.vue`銆乣emergency-repair-web/src/views/system/ContractorManage.vue`銆乣emergency-repair-web/src/views/completion/CompletionReport.vue`銆乣emergency-repair-web/src/views/system/RoleManage.vue`銆乣emergency-repair-web/src/router/index.js`銆�
+
+**楠岃瘉缁撴灉**
+- 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃銆�
+- 椤甸潰楠岃瘉鑴氭湰瀹屾暣閫氳繃锛歚node .codex-run/verify-items-11-12-17.js`銆乣node .codex-run/verify-items-18-19.js`锛涙埅鍥捐 `.codex-run/organization-verified.png`銆乣.codex-run/contractor-readonly-verified.png`銆乣.codex-run/completion-report-verified.png`銆乣.codex-run/role-scope-verified.png`銆�
+- 绔埌绔仈璋冧娇鐢ㄤ复鏃舵晠闅� `鍥哄畾鍓嶇紑-088/A-261004-001`锛岃窇閫氫笂鎶ャ�乆CB瀹℃牳鍒嗛厤鈥滃巶瀹�1/鍘傚2鈥濄�丆MB/JCC涓夌骇瀹℃壒銆佹淳鍗曘�傛渶缁堟晠闅滀繚瀛� `contractorIds = 1303144990487465986,1303145150793764866`銆乣contractorNames = 鍘傚1銆佸巶瀹�2`锛岀敓鎴愪袱寮犳淳宸ュ崟锛屽潎淇濆瓨 cj 閮ㄩ棬 ID/鍚嶇О銆�
+- 瀹屽伐濉姤鍙鎬ч獙璇侊細`cjyh1-1` 浠呯湅鍒板巶瀹�1娲惧伐鍗曪紝`cjyh2-1` 浠呯湅鍒板巶瀹�2娲惧伐鍗曪紝`admin` 鍙湅鍒颁袱寮犮�備复鏃� `T_FAULT` / `T_WORK_ORDER` 娴嬭瘯鏁版嵁宸叉竻鐞嗐��
+
+**褰撳墠杩愯鐘舵�侊紙2026-10-04锛�**
+- 鍓嶇锛歚http://localhost:8082`锛涘悗绔細`http://127.0.0.1:8091`锛汥M8锛歚127.0.0.1:5236`锛沨eadless Edge 璋冭瘯瀹炰緥绔彛 `9222`銆�
+- 鐧诲綍锛歚admin / admin123`锛涙暟鎹簱锛歚SYSDBA/SYSDBA`锛屼笟鍔� schema `LX`銆�
+
+**鎺ユ墜娉ㄦ剰浜嬮」**
+- 闈炵鐞嗗憳鐢ㄦ埛鐨勫畬宸ュ~鎶ユ淳宸ュ崟鍗曚綅鍖归厤褰撳墠鎸� `DEPT_NAME`锛屼笉鏄寜 `DEPT_ID`銆傛湰娆¢獙璇侀�氳繃锛涘瀛樺湪鍚屽悕閮ㄩ棬锛屽簲鏀逛负鎸夐儴闂� ID 绮剧‘鍖归厤銆�
+- 鍘嗗彶婕旂ず娲惧伐鍗曞彲鑳戒繚鐣欐棫 `T_CONTRACTOR` 鍚嶇О锛岄潪绠$悊鍛� cj 鐢ㄦ埛鍙兘鐪嬩笉鍒拌繖浜涘巻鍙插崟锛沗admin` 涓嶅彈鍗曚綅杩囨护褰卞搷銆�
+- `.codex-run`銆丱ffice 閿佹枃浠� `requirement\~$鍘熷闇�姹�.docx` 鍜� `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 灞炴湰鍦拌仈璋冩垨涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
+- 鏈疆浠g爜涓庝氦鎺ユ枃妗f彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮锛涘悗缁惎鍔ㄣ�佹瀯寤哄拰鎺掗敊鍛戒护娌跨敤绗� 16 鑺傘��
diff --git a/emergency-repair-server/pom.xml b/emergency-repair-server/pom.xml
index 5510c99..1144a48 100644
--- a/emergency-repair-server/pom.xml
+++ b/emergency-repair-server/pom.xml
@@ -29,6 +29,10 @@
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-crypto</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
             <version>${mybatis-plus.version}</version>
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java b/emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java
index 8b559ad..3da7330 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java
@@ -1,16 +1,23 @@
 package com.emergencyrepair.auth;
 
 import com.emergencyrepair.common.Result;
+import com.emergencyrepair.system.entity.SysLoginLog;
+import com.emergencyrepair.system.mapper.SysLoginLogMapper;
+import com.emergencyrepair.system.service.SystemService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.dao.DataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDateTime;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -20,44 +27,148 @@
 @RequiredArgsConstructor
 public class AuthController {
     private final JdbcTemplate jdbcTemplate;
+    private final SysLoginLogMapper loginLogMapper;
+    private final SystemService systemService;
+    private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
 
     @PostMapping("/login")
-    public Result<Map<String, Object>> login(@RequestBody Map<String, String> body) {
-        String username = body.get("username");
-        String password = body.get("password");
-        if (!"admin".equals(username) || (!"admin".equals(password) && !"admin123".equals(password))) {
-            return Result.error("鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒锛岀涓�鐗堣浣跨敤 admin/admin123");
+    public Result<Map<String, Object>> login(@RequestBody Map<String, String> body, HttpServletRequest request) {
+        String username = body == null || body.get("username") == null ? "" : body.get("username").trim();
+        String password = body == null || body.get("password") == null ? "" : body.get("password");
+        Map<String, Object> user = findUser(username);
+        boolean success = false;
+        String message = "鐢ㄦ埛鍚嶆垨瀵嗙爜閿欒";
+        if (user != null) {
+            if (isDisabled(user)) {
+                message = "璐﹀彿宸插仠鐢�";
+            } else if (matches(password, stringValue(user.get("password")))) {
+                success = true;
+            } else if ("admin".equals(username) && ("admin".equals(password) || "admin123".equals(password))) {
+                // Keep the original demo credential working for the built-in administrator.
+                success = true;
+            }
+        }
+        recordLogin(username, user, request, success, success ? "鐧诲綍鎴愬姛" : message);
+        if (!success) {
+            return Result.error(message);
         }
         Map<String, Object> data = new LinkedHashMap<>();
-        data.put("token", "demo-admin-token");
-        data.put("username", "admin");
-        data.put("realName", "绯荤粺绠$悊鍛�");
-        data.put("deptName", resolveDepartmentName(username));
+        data.put("token", "demo-" + username + "-token");
+        data.put("username", username);
+        data.put("realName", StringUtils.hasText(stringValue(user.get("nickName")))
+                ? stringValue(user.get("nickName")) : username);
+        data.put("deptName", stringValue(user.get("deptName")));
+        data.put("roles", systemService.roleCodes(username));
+        data.put("permissions", systemService.permissionCodes(username));
         return Result.ok(data);
     }
 
     @GetMapping("/me")
-    public Result<Map<String, Object>> me() {
+    public Result<Map<String, Object>> me(@RequestHeader(value = "X-Username", required = false) String headerUser,
+                                          @RequestHeader(value = "Authorization", required = false) String authorization) {
+        String username = currentUsername(headerUser, authorization);
+        Map<String, Object> user = findUser(username);
+        if (user == null) {
+            return Result.error("鐧诲綍鐘舵�佸凡澶辨晥");
+        }
         Map<String, Object> data = new LinkedHashMap<>();
-        data.put("username", "admin");
-        data.put("realName", "绯荤粺绠$悊鍛�");
-        data.put("deptName", resolveDepartmentName("admin"));
-        data.put("roles", new String[]{"SYSTEM_ADMIN"});
+        data.put("username", username);
+        data.put("realName", StringUtils.hasText(stringValue(user.get("nickName")))
+                ? stringValue(user.get("nickName")) : username);
+        data.put("deptName", stringValue(user.get("deptName")));
+        data.put("roles", systemService.roleCodes(username));
+        data.put("permissions", systemService.permissionCodes(username));
         return Result.ok(data);
     }
 
-    private String resolveDepartmentName(String username) {
+    private Map<String, Object> findUser(String username) {
         if (!StringUtils.hasText(username)) {
-            return "";
+            return null;
         }
         try {
-            List<String> names = jdbcTemplate.query(
-                    "SELECT d.DEPT_NAME FROM LX.SYS_USER u LEFT JOIN LX.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID WHERE u.USER_NAME = ? AND u.DEL_FLAG = '0'",
+            List<Map<String, Object>> rows = jdbcTemplate.query(
+                    "SELECT u.USER_ID, u.USER_NAME, u.NICK_NAME, u.PASSWORD, u.STATUS, d.DEPT_NAME " +
+                            "FROM LX.SYS_USER u LEFT JOIN LX.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID " +
+                            "WHERE u.USER_NAME = ? AND (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL)",
                     new Object[]{username},
-                    (rs, rowNum) -> rs.getString(1));
-            return names.isEmpty() || names.get(0) == null ? "" : names.get(0).trim();
+                    (rs, rowNum) -> {
+                        Map<String, Object> row = new LinkedHashMap<>();
+                        row.put("userId", rs.getLong("USER_ID"));
+                        row.put("userName", rs.getString("USER_NAME"));
+                        row.put("nickName", rs.getString("NICK_NAME"));
+                        row.put("password", rs.getString("PASSWORD"));
+                        row.put("status", rs.getString("STATUS"));
+                        row.put("deptName", rs.getString("DEPT_NAME"));
+                        return row;
+                    });
+            return rows.isEmpty() ? null : rows.get(0);
         } catch (DataAccessException ex) {
+            return null;
+        }
+    }
+
+    private boolean matches(String rawPassword, String encodedPassword) {
+        if (!StringUtils.hasText(rawPassword) || !StringUtils.hasText(encodedPassword)) {
+            return false;
+        }
+        try {
+            return passwordEncoder.matches(rawPassword, encodedPassword.trim());
+        } catch (Exception ex) {
+            return false;
+        }
+    }
+
+    private boolean isDisabled(Map<String, Object> user) {
+        return "1".equals(stringValue(user.get("status")));
+    }
+
+    private String stringValue(Object value) {
+        return value == null ? "" : String.valueOf(value).trim();
+    }
+
+    private String currentUsername(String headerUser, String authorization) {
+        if (StringUtils.hasText(headerUser)) {
+            return headerUser.trim();
+        }
+        if (!StringUtils.hasText(authorization)) {
             return "";
         }
+        String token = authorization.trim();
+        if (token.startsWith("Bearer ")) {
+            token = token.substring(7).trim();
+        }
+        if (token.startsWith("demo-") && token.endsWith("-token")) {
+            return token.substring(5, token.length() - 6);
+        }
+        return token;
+    }
+
+    private void recordLogin(String username, Map<String, Object> user, HttpServletRequest request,
+                             boolean success, String message) {
+        try {
+            SysLoginLog log = new SysLoginLog();
+            log.setUserId(user == null ? null : (Long) user.get("userId"));
+            log.setUserName(username);
+            log.setNickName(user == null ? null : stringValue(user.get("nickName")));
+            log.setDeptName(user == null ? "" : stringValue(user.get("deptName")));
+            log.setLoginIp(clientIp(request));
+            log.setStatus(success ? "SUCCESS" : "FAIL");
+            log.setMessage(message);
+            log.setLoginTime(LocalDateTime.now());
+            loginLogMapper.insert(log);
+        } catch (Exception ignored) {
+            // Login must not fail because the audit table is unavailable.
+        }
+    }
+
+    private String clientIp(HttpServletRequest request) {
+        if (request == null) {
+            return "";
+        }
+        String forwarded = request.getHeader("X-Forwarded-For");
+        if (StringUtils.hasText(forwarded)) {
+            return forwarded.split(",")[0].trim();
+        }
+        return request.getRemoteAddr();
     }
 }
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java
index f4ff232..b186d98 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java
@@ -5,7 +5,6 @@
 import com.emergencyrepair.business.entity.Acceptance;
 import com.emergencyrepair.business.entity.BaseEquipment;
 import com.emergencyrepair.business.entity.CompletionInfo;
-import com.emergencyrepair.business.entity.Contractor;
 import com.emergencyrepair.business.entity.EngineeringMajor;
 import com.emergencyrepair.business.entity.Fault;
 import com.emergencyrepair.business.entity.ProductNode;
@@ -14,7 +13,6 @@
 import com.emergencyrepair.business.mapper.AcceptanceMapper;
 import com.emergencyrepair.business.mapper.BaseEquipmentMapper;
 import com.emergencyrepair.business.mapper.CompletionInfoMapper;
-import com.emergencyrepair.business.mapper.ContractorMapper;
 import com.emergencyrepair.business.mapper.EngineeringMajorMapper;
 import com.emergencyrepair.business.mapper.FaultMapper;
 import com.emergencyrepair.business.mapper.ProductNodeMapper;
@@ -34,10 +32,12 @@
 import com.emergencyrepair.system.mapper.SysRoleMenuMapper;
 import com.emergencyrepair.system.mapper.SysRoleUserMapper;
 import com.emergencyrepair.system.mapper.SysTenantMenuMapper;
+import com.emergencyrepair.system.service.ButtonPermissionCatalog;
 import lombok.RequiredArgsConstructor;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 
@@ -60,7 +60,6 @@
     private final SysRoleMenuMapper roleMenuMapper;
     private final SysTenantMenuMapper tenantMenuMapper;
     private final SysRoleUserMapper roleUserMapper;
-    private final ContractorMapper contractorMapper;
     private final EngineeringMajorMapper engineeringMajorMapper;
     private final TeamMajorMapper teamMajorMapper;
     private final CompletionInfoMapper completionInfoMapper;
@@ -71,22 +70,27 @@
     private final AcceptanceMapper acceptanceMapper;
     private final BusinessService businessService;
     private final ObjectMapper objectMapper;
+    private final JdbcTemplate jdbcTemplate;
+    private final ButtonPermissionCatalog buttonPermissionCatalog;
 
     private final Map<String, Long> menuIds = new HashMap<>();
-    private final Map<String, Long> baseIds = new HashMap<>();
     private final Map<String, Long> productDeviceIds = new HashMap<>();
     private final List<Long> contractorIds = new ArrayList<>();
+    private final Map<Long, String> contractorNames = new HashMap<>();
 
     @Override
     @Transactional
     public void run(String... args) {
         seedMenusAndRoles();
+        ensureSystemMenus();
+        normalizeSensitiveTerms();
+        normalizeEquipmentTerms();
+        seedButtonMenus();
+        ensureAdminRoleAssignment();
         ensureMajorConfigMenus();
         seedMajors();
         seedContractors();
         seedTrees();
-        indexProductDevices();
-        ensureDemoProductDevices();
         indexProductDevices();
         backfillFaultXNoFromEquipment();
         linkFaultEquipment();
@@ -103,11 +107,11 @@
             createMenu("dashboard", null, "棣栭〉闂ㄦ埛", "/dashboard", "el-icon-s-home", 1, 10);
             Long fault = createMenu("fault", null, "鏁呴殰绠$悊", null, "el-icon-warning-outline", 0, 20);
             createMenu("fault-report", fault, "鏁呴殰涓婃姤", "/fault/report", null, 1, 10);
-            createMenu("xcb-review", fault, "XCB鍔╃悊瀹℃牳", "/fault/review", null, 1, 20);
-            createMenu("cmb-approval", fault, "CMB棰嗗瀹℃壒", "/fault/approval/cmb", null, 1, 30);
-            createMenu("jcc-assistant", fault, "JCC鍔╃悊瀹℃壒", "/fault/approval/jcc-assistant", null, 1, 40);
-            createMenu("jcc-director", fault, "JCC澶勯暱瀹℃壒", "/fault/approval/jcc-director", null, 1, 50);
-            createMenu("dispatch", fault, "XCB鍔╃悊娲惧崟", "/fault/dispatch", null, 1, 60);
+            createMenu("xcb-review", fault, "rk_XCB鍔╃悊瀹℃牳", "/fault/review", null, 1, 20);
+            createMenu("cmb-approval", fault, "rk_CMB棰嗗瀹℃壒", "/fault/approval/cmb", null, 1, 30);
+            createMenu("jcc-assistant", fault, "rk_JCC鍔╃悊瀹℃壒", "/fault/approval/jcc-assistant", null, 1, 40);
+            createMenu("jcc-director", fault, "rk_JCC澶勯暱瀹℃壒", "/fault/approval/jcc-director", null, 1, 50);
+            createMenu("dispatch", fault, "rk_XCB鍔╃悊娲惧崟", "/fault/dispatch", null, 1, 60);
             createMenu("completion-list", fault, "瀹屽伐鎯呭喌澶勭悊", "/fault/completion", null, 1, 70);
             createMenu("completion-report", fault, "瀹屽伐淇℃伅涓婃姤", "/completion/report", null, 1, 80);
 
@@ -119,18 +123,19 @@
             createMenu("accept-rating", acceptance, "淇悗璐ㄩ噺璇勭骇", "/acceptance/rating", null, 1, 50);
 
             Long reports = createMenu("reports", null, "缁熻鎶ヨ〃", null, "el-icon-data-analysis", 0, 40);
-            createMenu("report-overview", reports, "瑁呭鏁呴殰鎬讳綋鍒嗘瀽", "/reports/overview", null, 1, 10);
+            createMenu("report-overview", reports, "璁惧鏁呴殰鎬讳綋鍒嗘瀽", "/reports/overview", null, 1, 10);
             createMenu("report-top", reports, "鏁呴殰楂樺彂璁惧Top", "/reports/top-equipment", null, 1, 20);
-            createMenu("report-team", reports, "鎸塗缁熻", "/reports/by-team", null, 1, 30);
+            createMenu("report-team", reports, "rk_鎸塗缁熻", "/reports/by-team", null, 1, 30);
             createMenu("report-contractor", reports, "鍘傚缁翠慨鎯呭喌", "/reports/contractors", null, 1, 40);
             createMenu("report-trend", reports, "鏁呴殰鏁�/澶囧搧澶囦欢娑堣�楀勾搴﹁秼鍔�", "/reports/trend", null, 1, 50);
 
             Long system = createMenu("system", null, "绯荤粺绠$悊", null, "el-icon-setting", 0, 50);
+            createMenu("organization", system, "缁勭粐鏈烘瀯", "/system/organization", null, 1, 5);
             createMenu("contractors", system, "鎵夸慨鍗曚綅绠$悊", "/system/contractors", null, 1, 10);
             createMenu("product-tree", system, "浜у搧缁撴瀯鏍戠鐞�", "/system/product-tree", null, 1, 20);
             createMenu("base-equipment", system, "璁惧鍩虹搴撶鐞�", "/system/base-equipment", null, 1, 30);
             createMenu("engineering-majors", system, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", null, 1, 35);
-            createMenu("team-majors", system, "T闃熶笓涓氶厤缃�", "/system/team-majors", null, 1, 36);
+            createMenu("team-majors", system, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", null, 1, 36);
             createMenu("roles", system, "瑙掕壊绠$悊", "/system/roles", null, 1, 40);
             createMenu("menus", system, "鑿滃崟绠$悊", "/system/menus", null, 1, 50);
             createMenu("menu-permissions", system, "鑿滃崟鏉冮檺绠$悊", "/system/menu-permissions", null, 1, 60);
@@ -175,7 +180,7 @@
                 .last("LIMIT 1"));
         Long pid = system == null ? 0L : system.getId();
         ensureMenu(pid, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", 35);
-        ensureMenu(pid, "T闃熶笓涓氶厤缃�", "/system/team-majors", 36);
+        ensureMenu(pid, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", 36);
     }
 
     private void ensureMenu(Long pid, String name, String url, int sort) {
@@ -192,6 +197,134 @@
         menu.setCreateTime(LocalDateTime.now());
         menu.setUpdateTime(LocalDateTime.now());
         menuMapper.insert(menu);
+    }
+
+    private void ensureSystemMenus() {
+        SysMenu system = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+                .eq(SysMenu::getName, "\u7cfb\u7edf\u7ba1\u7406").last("LIMIT 1"));
+        Long pid = system == null ? 0L : system.getId();
+        ensureMenu(pid, "缁勭粐鏈烘瀯", "/system/organization", 5);
+        ensureMenu(pid, "\u7528\u6237\u7ba1\u7406", "/system/users", 37);
+        ensureMenu(pid, "\u767b\u5f55\u65e5\u5fd7", "/system/login-logs", 65);
+        ensureMenu(pid, "\u64cd\u4f5c\u65e5\u5fd7", "/system/operation-logs", 66);
+        grantAdminMenus("/system/organization", "/system/users", "/system/login-logs", "/system/operation-logs");
+    }
+
+    private void grantAdminMenus(String... urls) {
+        SysRole adminRole = roleMapper.selectOne(Wrappers.<SysRole>lambdaQuery()
+                .eq(SysRole::getCode, "SYS_ADMIN").last("LIMIT 1"));
+        if (adminRole == null) {
+            return;
+        }
+        for (String url : urls) {
+            SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+                    .eq(SysMenu::getUrl, url).last("LIMIT 1"));
+            if (menu != null) {
+                ensureRoleMenu(adminRole.getId(), menu.getId());
+                ensureTenantMenu(1L, menu.getId());
+            }
+        }
+    }
+
+    /**
+     * Materializes the declarative button catalog as MENU_TYPE=2 rows.
+     * Existing rows are left untouched so manual menu edits are preserved.
+     */
+    private void seedButtonMenus() {
+        SysRole adminRole = roleMapper.selectOne(Wrappers.<SysRole>lambdaQuery()
+                .eq(SysRole::getCode, "SYS_ADMIN").last("LIMIT 1"));
+        for (Map.Entry<String, List<ButtonPermissionCatalog.ButtonDefinition>> entry : buttonPermissionCatalog.catalog().entrySet()) {
+            SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+                    .eq(SysMenu::getUrl, entry.getKey()).last("LIMIT 1"));
+            if (menu == null || menu.getMenuType() == null || menu.getMenuType() != 1) {
+                continue;
+            }
+            List<ButtonPermissionCatalog.ButtonDefinition> definitions = entry.getValue();
+            for (int index = 0; index < definitions.size(); index++) {
+                ButtonPermissionCatalog.ButtonDefinition definition = definitions.get(index);
+                SysMenu existing = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+                        .eq(SysMenu::getPid, menu.getId())
+                        .eq(SysMenu::getMenuType, 2)
+                        .eq(SysMenu::getPermissions, definition.getCode())
+                        .last("LIMIT 1"));
+                if (existing != null) {
+                    continue;
+                }
+                SysMenu child = new SysMenu();
+                child.setPid(menu.getId());
+                child.setName(definition.getName());
+                child.setPermissions(definition.getCode());
+                child.setMenuType(2);
+                child.setSortNo((index + 1) * 10);
+                child.setVisible(1);
+                child.setCreateTime(LocalDateTime.now());
+                child.setUpdateTime(LocalDateTime.now());
+                menuMapper.insert(child);
+                if (adminRole != null) {
+                    ensureRoleMenu(adminRole.getId(), child.getId());
+                }
+                ensureTenantMenu(1L, child.getId());
+            }
+        }
+    }
+
+    private void ensureRoleMenu(Long roleId, Long menuId) {
+        if (roleId == null || menuId == null) {
+            return;
+        }
+        long count = roleMenuMapper.selectCount(Wrappers.<SysRoleMenu>lambdaQuery()
+                .eq(SysRoleMenu::getRoleId, roleId).eq(SysRoleMenu::getMenuId, menuId));
+        if (count == 0) {
+            SysRoleMenu relation = new SysRoleMenu();
+            relation.setRoleId(roleId);
+            relation.setMenuId(menuId);
+            relation.setCreateTime(LocalDateTime.now());
+            roleMenuMapper.insert(relation);
+        }
+    }
+
+    private void ensureTenantMenu(Long tenantId, Long menuId) {
+        if (tenantId == null || menuId == null) {
+            return;
+        }
+        long count = tenantMenuMapper.selectCount(Wrappers.<SysTenantMenu>lambdaQuery()
+                .eq(SysTenantMenu::getTenantId, tenantId).eq(SysTenantMenu::getMenuId, menuId));
+        if (count == 0) {
+            SysTenantMenu relation = new SysTenantMenu();
+            relation.setTenantId(tenantId);
+            relation.setMenuId(menuId);
+            relation.setCreateTime(LocalDateTime.now());
+            tenantMenuMapper.insert(relation);
+        }
+    }
+
+    private void ensureAdminRoleAssignment() {
+        SysRole adminRole = roleMapper.selectOne(Wrappers.<SysRole>lambdaQuery()
+                .eq(SysRole::getCode, "SYS_ADMIN").last("LIMIT 1"));
+        if (adminRole == null) {
+            return;
+        }
+        List<Long> ids = jdbcTemplate.query(
+                "SELECT USER_ID FROM CSICZB_ZB.SYS_USER WHERE USER_NAME = 'admin' AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                (rs, rowNum) -> rs.getLong(1));
+        if (!ids.isEmpty()) {
+            Long userId = ids.get(0);
+            long count = roleUserMapper.selectCount(Wrappers.<SysRoleUser>lambdaQuery()
+                    .eq(SysRoleUser::getRoleId, adminRole.getId())
+                    .eq(SysRoleUser::getUserId, userId));
+            if (count == 0) {
+                SysRoleUser relation = new SysRoleUser();
+                relation.setRoleId(adminRole.getId());
+                relation.setUserId(userId);
+                relation.setCreateTime(LocalDateTime.now());
+                roleUserMapper.insert(relation);
+            }
+        }
+        Integer placeholder = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM CSICZB_ZB.SYS_USER WHERE USER_ID = 1", Integer.class);
+        if (placeholder == null || placeholder == 0) {
+            roleUserMapper.delete(Wrappers.<SysRoleUser>lambdaQuery().eq(SysRoleUser::getUserId, 1L));
+        }
     }
 
     /**
@@ -228,42 +361,26 @@
     }
 
     private void seedContractors() {
-        if (contractorMapper.selectCount(null) > 0) {
-            return;
-        }
-        String[][] rows = {
-                {"鍖楁柟鏈虹數淇悊鍘�", "鍖楁柟鏈虹數", "鍘傚", "1", "寮犲伐", "0"},
-                {"娴峰煄鍔ㄥ姏璁惧鍘�", "娴峰煄鍔ㄥ姏", "鍘傚", "2", "鏉庡伐", "0"},
-                {"鑱斿悎鐢垫皵缁翠慨涓績", "鑱斿悎鐢垫皵", "鍘傚", "3", "鐜嬪伐", "0"},
-                {"鍗庤繙鏈烘淇�犲巶", "鍗庤繙鏈烘", "鍘傚", "4", "璧靛伐", "0"},
-                {"姹熸捣鑷姩鍖栨湇鍔¢儴", "姹熸捣鑷姩鍖�", "鏈嶅姟鍟�", "5", "鍛ㄥ伐", "0"},
-                {"杩滆埅鐢叉澘璁惧鍏徃", "杩滆埅鐢叉澘", "鍘傚", "6", "闄堝伐", "0"},
-                {"绮惧伐娑插帇缁翠慨鍘�", "绮惧伐娑插帇", "鍘傚", "7", "鍒樺伐", "0"},
-                {"鍚槑鎺у埗璁惧鍘�", "鍚槑鎺у埗", "鍘傚", "8", "瀛欏伐", "0"},
-                {"鎭掍俊绠$郴宸ョ▼閮�", "鎭掍俊绠$郴", "鏈嶅姟鍟�", "9", "鍚村伐", "1"},
-                {"鏂板煄缁煎悎缁翠慨涓績", "鏂板煄缁翠慨", "缁煎悎鍗曚綅", "10", "閮戝伐", "0"}
-        };
-        for (String[] row : rows) {
-            Contractor contractor = new Contractor();
-            contractor.setName(row[0]);
-            contractor.setShortName(row[1]);
-            contractor.setNature(row[2]);
-            contractor.setSortNo(Integer.valueOf(row[3]));
-            contractor.setContact(row[4]);
-            contractor.setDishonest(Integer.valueOf(row[5]));
-            contractor.setCreateTime(LocalDateTime.now());
-            contractor.setUpdateTime(LocalDateTime.now());
-            contractorMapper.insert(contractor);
-            contractorIds.add(contractor.getId());
+        contractorIds.clear();
+        contractorNames.clear();
+        List<Map<String, Object>> rows = jdbcTemplate.query(
+                "SELECT DEPT_ID, DEPT_NAME FROM CSICZB_ZB.SYS_DEPT " +
+                        "WHERE DEPT_TYPE = 'cj' AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL) ORDER BY ORDER_NUM, DEPT_ID",
+                (rs, rowNum) -> {
+                    Map<String, Object> item = new LinkedHashMap<>();
+                    item.put("id", rs.getLong("DEPT_ID"));
+                    item.put("name", rs.getString("DEPT_NAME"));
+                    return item;
+                });
+        for (Map<String, Object> item : rows) {
+            Long id = ((Number) item.get("id")).longValue();
+            contractorIds.add(id);
+            contractorNames.put(id, String.valueOf(item.get("name")));
         }
     }
 
     private void seedTrees() {
-        if (productNodeMapper.selectCount(null) == 0) {
-            ProductNode model = product("LX-A鍨嬬患鍚堜繚闅滃钩鍙�", "LX-A", null, 1, "MODEL", null, 1);
-            ProductNode x01 = product("X01", "X01", model.getId(), 2, "X_NO", null, 1);
-            ProductNode x02 = product("X02", "X02", model.getId(), 2, "X_NO", null, 2);
-            ProductNode x03 = product("X03", "X03", model.getId(), 2, "X_NO", null, 3);
+        if (baseEquipmentMapper.selectCount(null) == 0) {
             Long power = base("鍔ㄥ姏绯荤粺", "BASE-POWER", null, 1, 1);
             Long electric = base("鐢垫皵绯荤粺", "BASE-ELECTRIC", null, 1, 2);
             Long deck = base("鐢叉澘绯荤粺", "BASE-DECK", null, 1, 3);
@@ -279,20 +396,6 @@
             Long controlDevice = base("闆嗕腑鎺у埗鏌�", "DEV-CONTROL", control, 3, 1);
             Long hydraulicDevice = base("娑插帇娉电粍", "DEV-HYD", hydraulic, 3, 1);
             Long compressorDevice = base("绌烘皵鍘嬬缉鏈虹粍", "DEV-AIR", auxiliary, 3, 1);
-
-            addProductDevice(x01, "鍔ㄥ姏绯荤粺", "SYS-POWER", "鎺ㄨ繘瀛愮郴缁�", "SUB-PROP", "涓绘帹杩涜缃�", "DEV-PROP", propulsionDevice, 1);
-            addProductDevice(x01, "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鐢靛姏瀛愮郴缁�", "SUB-GRID", "涓婚厤鐢佃缃�", "DEV-DIST", distributionDevice, 2);
-            addProductDevice(x01, "鐢叉澘绯荤粺", "SYS-DECK", "娑插帇瀛愮郴缁�", "SUB-HYD", "娑插帇娉电粍", "DEV-HYD", hydraulicDevice, 3);
-            addProductDevice(x02, "鍔ㄥ姏绯荤粺", "SYS-POWER", "浼犲姩瀛愮郴缁�", "SUB-TRANS", "鍑忛�熼娇杞", "DEV-GEAR", gearDevice, 1);
-            addProductDevice(x02, "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鎺у埗瀛愮郴缁�", "SUB-CTRL", "闆嗕腑鎺у埗鏌�", "DEV-CONTROL", controlDevice, 2);
-            addProductDevice(x02, "鐢叉澘绯荤粺", "SYS-DECK", "杈呮満瀛愮郴缁�", "SUB-AUX", "绌烘皵鍘嬬缉鏈虹粍", "DEV-AIR", compressorDevice, 3);
-            addProductDevice(x03, "鍔ㄥ姏绯荤粺", "SYS-POWER", "鎺ㄨ繘瀛愮郴缁�", "SUB-PROP", "涓绘帹杩涜缃�", "DEV-PROP", propulsionDevice, 1);
-            addProductDevice(x03, "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鐢靛姏瀛愮郴缁�", "SUB-GRID", "涓婚厤鐢佃缃�", "DEV-DIST", distributionDevice, 2);
-            addProductDevice(x03, "鐢叉澘绯荤粺", "SYS-DECK", "娑插帇瀛愮郴缁�", "SUB-HYD", "娑插帇娉电粍", "DEV-HYD", hydraulicDevice, 3);
-            addProductDevice(x01, "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鎺у埗瀛愮郴缁�", "SUB-CTRL", "闆嗕腑鎺у埗鏌�", "DEV-CONTROL", controlDevice, 4);
-            addProductDevice(x02, "鍔ㄥ姏绯荤粺", "SYS-POWER", "鎺ㄨ繘瀛愮郴缁�", "SUB-PROP", "涓绘帹杩涜缃敼杩涘瀷", "DEV-PROP-X02", propulsionDevice, 4);
-            addProductDevice(x03, "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鎺у埗瀛愮郴缁�", "SUB-CTRL", "闆嗕腑鎺у埗鏌�", "DEV-CONTROL", controlDevice, 4);
-            addProductDevice(x03, "鐢叉澘绯荤粺", "SYS-DECK", "杈呮満瀛愮郴缁�", "SUB-AUX", "绌烘皵鍘嬬缉鏈虹粍", "DEV-AIR", compressorDevice, 4);
         }
     }
 
@@ -336,8 +439,13 @@
         Fault fault = baseFault(code, xNo, equipment, team, nature, date, description);
         fault.setTaskStatus(status);
         if (!StatusConstants.Task.UNREPORTED.equals(status)) {
-            List<Long> ids = Arrays.asList(contractorIds.get(Math.abs(code.hashCode()) % 7), contractorIds.get((Math.abs(code.hashCode()) + 1) % 7));
-            String names = contractorMapper.selectBatchIds(ids).stream().map(Contractor::getName).collect(java.util.stream.Collectors.joining("銆�"));
+            int size = contractorIds.size();
+            List<Long> ids = Arrays.asList(
+                    contractorIds.get(Math.abs(code.hashCode()) % size),
+                    contractorIds.get((Math.abs(code.hashCode()) + 1) % size));
+            String names = ids.stream().map(contractorNames::get)
+                    .filter(java.util.Objects::nonNull)
+                    .collect(java.util.stream.Collectors.joining("銆�"));
             fault.setContractorIds(ids.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(",")));
             fault.setContractorNames(names);
             fault.setEngineeringNo("LX-" + xNo + "-" + date.toString().replace("-", "").substring(2) + "-001");
@@ -350,9 +458,8 @@
         Fault fault = baseFault(code, xNo, equipment, team, nature, date, description);
         fault.setTaskStatus(StatusConstants.Task.JCC_DIRECTOR_APPROVED);
         Long contractorId = contractorIds.get(contractorIndex % contractorIds.size());
-        Contractor contractor = contractorMapper.selectById(contractorId);
         fault.setContractorIds(String.valueOf(contractorId));
-        fault.setContractorNames(contractor.getName());
+        fault.setContractorNames(contractorNames.get(contractorId));
         fault.setEngineeringNo("LX-" + xNo + "-" + date.toString().replace("-", "").substring(2) + "-001");
         fault.setXcbReviewTime(LocalDateTime.now().minusDays(5));
         faultMapper.insert(fault);
@@ -460,45 +567,7 @@
         }
     }
 
-    private void ensureDemoProductDevices() {
-        loadBaseIds();
-        ensureProductDevice("X01", "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鎺у埗瀛愮郴缁�", "SUB-CTRL", "闆嗕腑鎺у埗鏌�", "DEV-CONTROL", "DEV-CONTROL", 4);
-        ensureProductDevice("X02", "鍔ㄥ姏绯荤粺", "SYS-POWER", "鎺ㄨ繘瀛愮郴缁�", "SUB-PROP", "涓绘帹杩涜缃敼杩涘瀷", "DEV-PROP-X02", "DEV-PROP", 4);
-        ensureProductDevice("X03", "鐢垫皵绯荤粺", "SYS-ELECTRIC", "鎺у埗瀛愮郴缁�", "SUB-CTRL", "闆嗕腑鎺у埗鏌�", "DEV-CONTROL", "DEV-CONTROL", 4);
-        ensureProductDevice("X03", "鐢叉澘绯荤粺", "SYS-DECK", "杈呮満瀛愮郴缁�", "SUB-AUX", "绌烘皵鍘嬬缉鏈虹粍", "DEV-AIR", "DEV-AIR", 4);
-    }
 
-    private void ensureProductDevice(String xNo, String system1, String code1, String system2, String code2,
-                                     String device, String deviceCode, String baseCode, int sort) {
-        if (productDeviceIds.containsKey(deviceKey(xNo, device))) {
-            return;
-        }
-        ProductNode xNode = productNode(2, xNo);
-        Long baseId = baseIds.get(baseCode);
-        if (xNode == null || baseId == null) {
-            return;
-        }
-        addProductDevice(xNode, system1, code1, system2, code2, device, deviceCode, baseId, sort);
-    }
-
-    private void loadBaseIds() {
-        if (!baseIds.isEmpty()) {
-            return;
-        }
-        for (BaseEquipment node : baseEquipmentMapper.selectList(null)) {
-            if (StringUtils.hasText(node.getCode())) {
-                baseIds.putIfAbsent(node.getCode(), node.getId());
-            }
-        }
-    }
-
-    private ProductNode productNode(int level, String name) {
-        List<ProductNode> nodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
-                .eq(ProductNode::getNodeLevel, level)
-                .eq(ProductNode::getName, name)
-                .last("LIMIT 1"));
-        return nodes.isEmpty() ? null : nodes.get(0);
-    }
 
     private void backfillFaultXNoFromEquipment() {
         List<ProductNode> nodes = productNodeMapper.selectList(null);
@@ -607,6 +676,20 @@
         }
     }
 
+    private void normalizeSensitiveTerms() {
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, 'XCB', 'rk_XCB') WHERE NAME LIKE '%XCB%' AND NAME NOT LIKE '%rk_XCB%'");
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, 'CMB', 'rk_CMB') WHERE NAME LIKE '%CMB%' AND NAME NOT LIKE '%rk_CMB%'");
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, 'JCC', 'rk_JCC') WHERE NAME LIKE '%JCC%' AND NAME NOT LIKE '%rk_JCC%'");
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, 'T闃�', 'rk_T闃�') WHERE NAME LIKE '%T闃�%' AND NAME NOT LIKE '%rk_T闃�%'");
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, 'T鍛�', 'rk_T鍛�') WHERE NAME LIKE '%T鍛�%' AND NAME NOT LIKE '%rk_T鍛�%'");
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, '鎸塗缁熻', 'rk_鎸塗缁熻') WHERE NAME LIKE '%鎸塗缁熻%' AND NAME NOT LIKE '%rk_鎸塗缁熻%'");
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, '娑塇', 'rk_娑塇') WHERE NAME LIKE '%娑塇%' AND NAME NOT LIKE '%rk_娑塇%'");
+    }
+
+    private void normalizeEquipmentTerms() {
+        jdbcTemplate.update("UPDATE LX.T_SYS_MENU SET NAME = REPLACE(NAME, '\u88c5\u5907', '\u8bbe\u5907') WHERE NAME LIKE '%\u88c5\u5907%'");
+    }
+
     private void normalizeParentSystemSeparators() {
         for (Fault fault : faultMapper.selectList(null)) {
             String normalized = normalizeParentSystem(fault.getParentSystem());
@@ -656,9 +739,9 @@
         fault.setParentSystem(equipment.contains("娑插帇") ? "鐢叉澘绯荤粺" : equipment.contains("鎺у埗") ? "鐢垫皵绯荤粺" : "鍔ㄥ姏绯荤粺");
         fault.setEngineeringMajor(team);
         fault.setTeamMajor(team);
-        fault.setReportUnit(team + "T闃�");
+        fault.setReportUnit(team + "rk_T闃�");
         fault.setReportDate(date);
-        fault.setOwner("T鍛�" + xNo);
+        fault.setOwner("rk_T鍛�" + xNo);
         fault.setFaultImpact("渚涙皵".equals(team) ? "渚涙皵" : "杩愯");
         fault.setFaultNature(nature);
         fault.setEngineeringNameDesc(description);
@@ -747,29 +830,6 @@
         node.setCreateTime(LocalDateTime.now());
         node.setUpdateTime(LocalDateTime.now());
         baseEquipmentMapper.insert(node);
-        baseIds.put(code, node.getId());
         return node.getId();
-    }
-
-    private ProductNode product(String name, String code, Long parentId, int level, String type, Long baseEquipmentId, int sort) {
-        ProductNode node = new ProductNode();
-        node.setParentId(parentId == null ? 0L : parentId);
-        node.setNodeLevel(level);
-        node.setNodeType(type);
-        node.setName(name);
-        node.setCode(code);
-        node.setBaseEquipmentId(baseEquipmentId);
-        node.setSortNo(sort);
-        node.setCreateTime(LocalDateTime.now());
-        node.setUpdateTime(LocalDateTime.now());
-        productNodeMapper.insert(node);
-        return node;
-    }
-
-    private void addProductDevice(ProductNode xNode, String system1, String code1, String system2, String code2,
-                                  String device, String code, Long baseId, int sort) {
-        ProductNode node1 = product(system1, code1 + "-" + xNode.getCode(), xNode.getId(), 3, "SYSTEM1", null, sort);
-        ProductNode node2 = product(system2, code2 + "-" + xNode.getCode(), node1.getId(), 4, "SYSTEM2", null, 1);
-        product(device, code + "-" + xNode.getCode(), node2.getId(), 5, "DEVICE", baseId, 1);
     }
 }
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java
index 085d5cc..d1c6d10 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java
@@ -13,6 +13,12 @@
 @Order(1)
 @RequiredArgsConstructor
 public class DatabaseBootstrap implements CommandLineRunner {
+    private static final String[] AUDIT_TABLES = {
+            "T_FAULT", "T_ENGINEERING_MAJOR", "T_TEAM_MAJOR", "T_CONTRACTOR",
+            "T_BASE_EQUIPMENT", "T_PRODUCT_TREE", "T_WORK_ORDER", "T_COMPLETION_INFO",
+            "T_ACCEPTANCE", "T_SYS_MENU", "T_SYS_ROLE", "T_SYS_ROLE_MENU",
+            "T_SYS_TENANT_MENU", "T_SYS_ROLE_USER", "T_SYS_LOGIN_LOG", "T_SYS_OPERATION_LOG"
+    };
     private final JdbcTemplate jdbcTemplate;
 
     @Override
@@ -23,6 +29,7 @@
                 jdbcTemplate.execute(entry.getValue());
             }
         }
+        ensureAuditColumns();
         renameColumnIfNeeded("T_FAULT", "FAULT_DESCRIPTION", "ENGINEERING_NAME_DESC", "ALTER TABLE LX.T_FAULT RENAME COLUMN FAULT_DESCRIPTION TO ENGINEERING_NAME_DESC");
         ensureColumn("T_FAULT", "EQUIPMENT_IDS", "ALTER TABLE LX.T_FAULT ADD EQUIPMENT_IDS VARCHAR(2000)");
         ensureColumn("T_COMPLETION_INFO", "CONSUMABLE_COUNT", "ALTER TABLE LX.T_COMPLETION_INFO ADD CONSUMABLE_COUNT INT");
@@ -31,8 +38,22 @@
         ensureIndex("UX_T_WORK_ORDER_NO", "CREATE UNIQUE INDEX LX.UX_T_WORK_ORDER_NO ON LX.T_WORK_ORDER(WORK_ORDER_NO)");
         ensureIndex("IX_T_WORK_ORDER_FAULT_ID", "CREATE INDEX LX.IX_T_WORK_ORDER_FAULT_ID ON LX.T_WORK_ORDER(FAULT_ID)");
         ensureIndex("IX_T_COMPLETION_WORK_ORDER", "CREATE INDEX LX.IX_T_COMPLETION_WORK_ORDER ON LX.T_COMPLETION_INFO(WORK_ORDER_ID)");
+        ensureIndex("IX_T_SYS_LOGIN_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_LOGIN_LOG_TIME ON LX.T_SYS_LOGIN_LOG(LOGIN_TIME)");
+        ensureIndex("IX_T_SYS_OPERATION_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_OPERATION_LOG_TIME ON LX.T_SYS_OPERATION_LOG(CREATE_TIME)");
+        ensureIndex("IX_T_SYS_ROLE_USER_USER", "CREATE INDEX LX.IX_T_SYS_ROLE_USER_USER ON LX.T_SYS_ROLE_USER(USER_ID)");
     }
 
+    private void ensureAuditColumns() {
+        for (String tableName : AUDIT_TABLES) {
+            if (!tableExists(tableName)) {
+                continue;
+            }
+            ensureColumn(tableName, "CREATE_BY", "ALTER TABLE LX." + tableName + " ADD CREATE_BY VARCHAR(120)");
+            ensureColumn(tableName, "UPDATE_BY", "ALTER TABLE LX." + tableName + " ADD UPDATE_BY VARCHAR(120)");
+            ensureColumn(tableName, "DEL_FLAG", "ALTER TABLE LX." + tableName + " ADD DEL_FLAG INT DEFAULT 0");
+            jdbcTemplate.update("UPDATE LX." + tableName + " SET DEL_FLAG = 0 WHERE DEL_FLAG IS NULL");
+        }
+    }
     private void ensureSchema() {
         Integer count = jdbcTemplate.queryForObject(
                 "SELECT COUNT(*) FROM ALL_USERS WHERE USERNAME = ?", Integer.class, "LX");
@@ -99,6 +120,8 @@
         ddl.put("T_SYS_ROLE_MENU", "CREATE TABLE LX.T_SYS_ROLE_MENU (ID BIGINT NOT NULL PRIMARY KEY, ROLE_ID BIGINT, MENU_ID BIGINT, CREATE_TIME TIMESTAMP)");
         ddl.put("T_SYS_TENANT_MENU", "CREATE TABLE LX.T_SYS_TENANT_MENU (ID BIGINT NOT NULL PRIMARY KEY, TENANT_ID BIGINT, MENU_ID BIGINT, CREATE_TIME TIMESTAMP)");
         ddl.put("T_SYS_ROLE_USER", "CREATE TABLE LX.T_SYS_ROLE_USER (ID BIGINT NOT NULL PRIMARY KEY, ROLE_ID BIGINT, USER_ID BIGINT, CREATE_TIME TIMESTAMP)");
+        ddl.put("T_SYS_LOGIN_LOG", "CREATE TABLE LX.T_SYS_LOGIN_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), DEPT_NAME VARCHAR(120), LOGIN_IP VARCHAR(200), STATUS VARCHAR(20), MESSAGE VARCHAR(500), LOGIN_TIME TIMESTAMP)");
+        ddl.put("T_SYS_OPERATION_LOG", "CREATE TABLE LX.T_SYS_OPERATION_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), MODULE VARCHAR(100), OPERATION_TYPE VARCHAR(40), TARGET_TYPE VARCHAR(100), TARGET_ID VARCHAR(80), DESCRIPTION VARCHAR(500), REQUEST_METHOD VARCHAR(10), REQUEST_URI VARCHAR(300), RESULT VARCHAR(20), CREATE_TIME TIMESTAMP)");
         return ddl;
     }
 }
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/controller/WorkOrderController.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/controller/WorkOrderController.java
index 4689574..619860e 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/controller/WorkOrderController.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/controller/WorkOrderController.java
@@ -24,8 +24,9 @@
 
     @GetMapping
     public Result<List<WorkOrder>> list(@RequestParam(required = false) String status,
-                                        @RequestParam(required = false) String keyword) {
-        return Result.ok(businessService.listWorkOrders(status, keyword));
+                                        @RequestParam(required = false) String keyword,
+                                        @RequestParam(required = false, defaultValue = "false") boolean completionReportable) {
+        return Result.ok(businessService.listWorkOrders(status, keyword, completionReportable));
     }
 
     @GetMapping("/{id}")
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java
index 52bedd4..b17cfe3 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java
@@ -112,4 +112,26 @@
         private List<Long> roleMenuIds = new ArrayList<>();
         private List<Long> tenantMenuIds = new ArrayList<>();
     }
+    @Data
+    public static class UserRoleRequest {
+        private List<Long> roleIds = new ArrayList<>();
+    }
+
+    @Data
+    public static class BatchUserRoleRequest {
+        private List<Long> userIds = new ArrayList<>();
+        private List<Long> roleIds = new ArrayList<>();
+        private String mode = "append";
+    }
+
+    @Data
+    public static class RoleUserRequest {
+        private List<Long> userIds = new ArrayList<>();
+    }
+
+    @Data
+    public static class ButtonSyncRequest {
+        private Long menuId;
+        private List<String> codes = new ArrayList<>();
+    }
 }
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Acceptance.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Acceptance.java
index 627bd90..1c33777 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Acceptance.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Acceptance.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -13,7 +14,7 @@
 
 @Data
 @TableName("LX.T_ACCEPTANCE")
-public class Acceptance {
+public class Acceptance extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/BaseEquipment.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/BaseEquipment.java
index 8655ed2..425ae61 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/BaseEquipment.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/BaseEquipment.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_BASE_EQUIPMENT")
-public class BaseEquipment {
+public class BaseEquipment extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/CompletionInfo.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/CompletionInfo.java
index 37e3476..ad3b1f6 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/CompletionInfo.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/CompletionInfo.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -14,7 +15,7 @@
 
 @Data
 @TableName("LX.T_COMPLETION_INFO")
-public class CompletionInfo {
+public class CompletionInfo extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Contractor.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Contractor.java
index b6ba178..20d0583 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Contractor.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Contractor.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_CONTRACTOR")
-public class Contractor {
+public class Contractor extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/EngineeringMajor.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/EngineeringMajor.java
index 47965f0..ed6708e 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/EngineeringMajor.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/EngineeringMajor.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_ENGINEERING_MAJOR")
-public class EngineeringMajor {
+public class EngineeringMajor extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Fault.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Fault.java
index 8550893..5cd59e0 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Fault.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/Fault.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -14,7 +15,7 @@
 
 @Data
 @TableName("LX.T_FAULT")
-public class Fault {
+public class Fault extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java
index 0853979..a1e87ff 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java
@@ -9,23 +9,182 @@
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.Locale;
 
 @Data
-@TableName("LX.T_PRODUCT_TREE")
+@TableName("LX.ZT_BOAT_TREE")
 public class ProductNode {
-    @TableId(value = "ID", type = IdType.ASSIGN_ID)
+    /**
+     * Business node id. The external tree uses INFO_ID for the node and
+     * CONNECT_ID for its parent, while ID is the physical relation row id.
+     */
+    @TableId(value = "INFO_ID", type = IdType.INPUT)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
+
+    @TableField("ID")
+    private String recordId;
+
     @JsonSerialize(using = ToStringSerializer.class)
-    @TableField("PARENT_ID") private Long parentId;
-    @TableField("NODE_LEVEL") private Integer nodeLevel;
-    @TableField("NODE_TYPE") private String nodeType;
-    @TableField("NAME") private String name;
-    @TableField("CODE") private String code;
-    @JsonSerialize(using = ToStringSerializer.class)
-    @TableField("BASE_EQUIPMENT_ID") private Long baseEquipmentId;
-    @TableField("SORT_NO") private Integer sortNo;
-    @TableField("REMARK") private String remark;
-    @TableField("CREATE_TIME") private LocalDateTime createTime;
-    @TableField("UPDATE_TIME") private LocalDateTime updateTime;
+    @TableField("CONNECT_ID")
+    private Long parentId;
+
+    @TableField("CONNECT_GROUP")
+    private String connectGroup;
+
+    @TableField("LABEL")
+    private String name;
+
+    @TableField("LAYER")
+    private String layer;
+
+    @TableField("PROJECT")
+    private String project;
+
+    @TableField("SORT")
+    private Integer sortNo;
+
+    @TableField("ISDELETE")
+    private String isDelete;
+
+    @TableField("CREATE_BY")
+    private String createBy;
+
+    @TableField("CREATE_TIME")
+    private LocalDateTime createTime;
+
+    @TableField(exist = false)
+    private Integer nodeLevel;
+
+    @TableField(exist = false)
+    private String nodeType;
+
+    @TableField(exist = false)
+    private String code;
+
+    @TableField(exist = false)
+    private Long baseEquipmentId;
+
+    @TableField(exist = false)
+    private String remark;
+
+    @TableField(exist = false)
+    private LocalDateTime updateTime;
+
+    public Integer getNodeLevel() {
+        if (nodeLevel != null) {
+            return nodeLevel;
+        }
+        return levelForLayer(layer);
+    }
+
+    public void setNodeLevel(Integer nodeLevel) {
+        this.nodeLevel = nodeLevel;
+        if (layer == null && nodeLevel != null) {
+            layer = layerForLevel(nodeLevel);
+        }
+    }
+
+    public String getNodeType() {
+        if (nodeType != null) {
+            return nodeType;
+        }
+        return typeForLayer(layer);
+    }
+
+    public void setNodeType(String nodeType) {
+        this.nodeType = nodeType;
+        if (layer == null && nodeType != null) {
+            layer = layerForType(nodeType);
+        }
+    }
+
+    public static Integer levelForLayer(String value) {
+        String layer = normalize(value);
+        if ("model".equals(layer)) {
+            return 1;
+        }
+        if ("side".equals(layer)) {
+            return 2;
+        }
+        if ("system1".equals(layer)) {
+            return 3;
+        }
+        if ("system2".equals(layer)) {
+            return 4;
+        }
+        if ("equipment".equals(layer)) {
+            return 5;
+        }
+        return layer.isEmpty() ? null : 6;
+    }
+
+    public static String typeForLayer(String value) {
+        Integer level = levelForLayer(value);
+        if (level == null) {
+            return null;
+        }
+        if (level == 1) {
+            return "MODEL";
+        }
+        if (level == 2) {
+            return "X_NO";
+        }
+        if (level == 3) {
+            return "SYSTEM1";
+        }
+        if (level == 4) {
+            return "SYSTEM2";
+        }
+        if (level == 5) {
+            return "DEVICE";
+        }
+        return normalize(value).toUpperCase(Locale.ROOT);
+    }
+
+    public static String layerForLevel(Integer level) {
+        if (level == null) {
+            return null;
+        }
+        if (level == 1) {
+            return "model";
+        }
+        if (level == 2) {
+            return "side";
+        }
+        if (level == 3) {
+            return "system1";
+        }
+        if (level == 4) {
+            return "system2";
+        }
+        if (level == 5) {
+            return "equipment";
+        }
+        return null;
+    }
+
+    public static String layerForType(String type) {
+        String value = normalize(type);
+        if ("MODEL".equals(value)) {
+            return "model";
+        }
+        if ("X_NO".equals(value) || "XNUMBER".equals(value)) {
+            return "side";
+        }
+        if ("SYSTEM1".equals(value)) {
+            return "system1";
+        }
+        if ("SYSTEM2".equals(value)) {
+            return "system2";
+        }
+        if ("DEVICE".equals(value) || "EQUIPMENT".equals(value)) {
+            return "equipment";
+        }
+        return value.isEmpty() ? null : value.toLowerCase(Locale.ROOT);
+    }
+
+    private static String normalize(String value) {
+        return value == null ? "" : value.trim().toLowerCase(Locale.ROOT);
+    }
 }
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/TeamMajor.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/TeamMajor.java
index 077121e..3d2b249 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/TeamMajor.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/TeamMajor.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_TEAM_MAJOR")
-public class TeamMajor {
+public class TeamMajor extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/WorkOrder.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/WorkOrder.java
index 8abe30b..ce927b5 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/WorkOrder.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/WorkOrder.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.business.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -14,7 +15,7 @@
 
 @Data
 @TableName("LX.T_WORK_ORDER")
-public class WorkOrder {
+public class WorkOrder extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java
index 15bdcf8..b36be6c 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java
@@ -6,20 +6,21 @@
 import com.emergencyrepair.business.dto.Requests;
 import com.emergencyrepair.business.entity.Acceptance;
 import com.emergencyrepair.business.entity.CompletionInfo;
-import com.emergencyrepair.business.entity.Contractor;
 import com.emergencyrepair.business.entity.Fault;
 import com.emergencyrepair.business.entity.TeamMajor;
 import com.emergencyrepair.business.entity.WorkOrder;
 import com.emergencyrepair.business.mapper.AcceptanceMapper;
 import com.emergencyrepair.business.mapper.CompletionInfoMapper;
-import com.emergencyrepair.business.mapper.ContractorMapper;
 import com.emergencyrepair.business.mapper.FaultMapper;
 import com.emergencyrepair.business.mapper.TeamMajorMapper;
 import com.emergencyrepair.business.mapper.WorkOrderMapper;
 import com.emergencyrepair.common.BusinessException;
 import com.emergencyrepair.common.StatusConstants;
+import com.emergencyrepair.common.audit.CurrentUserContext;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
@@ -30,24 +31,26 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Collection;
+import java.util.Objects;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
 @Service
 @RequiredArgsConstructor
 public class BusinessService {
     private static final DateTimeFormatter FAULT_DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd");
-    private static final String FAULT_CODE_PREFIX = "\u6f5c1\u57fa-";
     private static final Object FAULT_CODE_LOCK = new Object();
+    @Value("${fault-code.prefix:\u6f5c1\u57fa-}")
+    private String faultCodePrefix;
     private final FaultMapper faultMapper;
+    private final JdbcTemplate jdbcTemplate;
     private final TeamMajorMapper teamMajorMapper;
     private final WorkOrderMapper workOrderMapper;
-    private final ContractorMapper contractorMapper;
     private final CompletionInfoMapper completionInfoMapper;
     private final AcceptanceMapper acceptanceMapper;
     private final ObjectMapper objectMapper;
@@ -168,26 +171,30 @@
     private String generateFaultCode(Fault fault) {
         String xNo = normalizeXNo(fault.getXNo());
         if (xNo == null) {
-            throw new BusinessException("璇烽�夋嫨X鍙凤紝鏍煎紡涓�3浣嶆暟瀛�");
+            throw new BusinessException("璇烽�夋嫨rk_X鍙凤紝鏍煎紡涓�3浣嶆暟瀛�");
         }
         if (!StringUtils.hasText(fault.getTeamMajor())) {
-            throw new BusinessException("璇烽�夋嫨T闃熶笓涓�");
+            throw new BusinessException("璇烽�夋嫨rk_T闃熶笓涓�");
         }
         TeamMajor teamMajor = teamMajorMapper.selectOne(Wrappers.<TeamMajor>lambdaQuery()
                 .eq(TeamMajor::getName, fault.getTeamMajor().trim())
                 .last("LIMIT 1"));
         if (teamMajor == null) {
-            throw new BusinessException("T闃熶笓涓氭湭閰嶇疆锛岃鍏堝湪绯荤粺绠$悊 > T闃熶笓涓氶厤缃腑缁存姢锛�" + fault.getTeamMajor());
+            throw new BusinessException("rk_T闃熶笓涓氭湭閰嶇疆锛岃鍏堝湪绯荤粺绠$悊 > rk_T闃熶笓涓氶厤缃腑缁存姢锛�" + fault.getTeamMajor());
         }
         String category = teamMajor.getCategory() == null ? "" : teamMajor.getCategory().trim().toUpperCase();
         if (!"A".equals(category) && !"B".equals(category)) {
-            throw new BusinessException("T闃熶笓涓氬垎绫诲彧鑳戒负A鎴朆锛�" + fault.getTeamMajor());
+            throw new BusinessException("rk_T闃熶笓涓氬垎绫诲彧鑳戒负A鎴朆锛�" + fault.getTeamMajor());
         }
-        String prefix = FAULT_CODE_PREFIX + xNo + "/" + category + "-" + fault.getReportDate().format(FAULT_DATE_FORMAT) + "-";
+        String prefix = faultCodePrefix + xNo + "/" + category + "-" + fault.getReportDate().format(FAULT_DATE_FORMAT) + "-";
         int next = 1;
-        for (Fault item : faultMapper.selectList(Wrappers.<Fault>lambdaQuery()
-                .likeRight(Fault::getFaultCode, prefix))) {
-            next = Math.max(next, parseFaultSequence(item.getFaultCode(), prefix) + 1);
+        // Include logically deleted rows so a generated code never collides with
+        // the unique index on FAULT_CODE.
+        List<String> existingCodes = jdbcTemplate.query(
+                "SELECT FAULT_CODE FROM LX.T_FAULT WHERE FAULT_CODE LIKE ?",
+                new Object[]{prefix + "%"}, (rs, rowNum) -> rs.getString(1));
+        for (String existingCode : existingCodes) {
+            next = Math.max(next, parseFaultSequence(existingCode, prefix) + 1);
         }
         return prefix + String.format("%03d", next);
     }
@@ -244,7 +251,7 @@
             throw new BusinessException("璇峰~鍐欓攢椤圭悊鐢�");
         }
         String cancelledBy = StringUtils.hasText(request.getCancelledBy())
-                ? request.getCancelledBy().trim() : "XCB鍔╃悊";
+                ? request.getCancelledBy().trim() : "rk_XCB鍔╃悊";
         for (Long id : request.getIds()) {
             Fault fault = requireFault(id);
             if (!StatusConstants.Task.REPORTED_XCB.equals(fault.getTaskStatus())
@@ -281,16 +288,16 @@
         if (request.getContractorIds() == null || request.getContractorIds().isEmpty()) {
             throw new BusinessException("鑷冲皯鍒嗛厤涓�涓壙淇崟浣�");
         }
-        List<Contractor> contractors = contractorMapper.selectBatchIds(request.getContractorIds());
-        Map<Long, Contractor> contractorMap = contractors.stream()
-                .collect(Collectors.toMap(Contractor::getId, Function.identity()));
+        Map<Long, String> contractorMap = contractorNamesByIds(request.getContractorIds());
+        if (contractorMap.isEmpty()) {
+            throw new BusinessException("鎵夸慨鍗曚綅涓嶅瓨鍦ㄦ垨鏈湪閮ㄩ棬琛ㄤ腑閰嶇疆");
+        }
         String contractorIds = request.getContractorIds().stream()
                 .map(String::valueOf)
                 .collect(Collectors.joining(","));
         String contractorNames = request.getContractorIds().stream()
                 .map(contractorMap::get)
-                .filter(item -> item != null)
-                .map(Contractor::getName)
+                .filter(Objects::nonNull)
                 .collect(Collectors.joining("銆�"));
 
         int index = 0;
@@ -298,7 +305,7 @@
             Fault fault = requireFault(id);
             if (!StatusConstants.Task.REPORTED_XCB.equals(fault.getTaskStatus())
                     && !StatusConstants.Task.RETURNED.equals(fault.getTaskStatus())) {
-                throw new BusinessException("鏁呴殰 " + fault.getFaultCode() + " 褰撳墠鐘舵�佷笉鍏佽XCB鍔╃悊瀹℃牳");
+                throw new BusinessException("鏁呴殰 " + fault.getFaultCode() + " 褰撳墠鐘舵�佷笉鍏佽rk_XCB鍔╃悊瀹℃牳");
             }
             index++;
             fault.setContractorIds(contractorIds);
@@ -325,21 +332,21 @@
             LocalDateTime now = LocalDateTime.now();
             switch (request.getStage()) {
                 case "CMB":
-                    requireStatus(fault, StatusConstants.Task.XCB_REVIEWED, "CMB棰嗗瀹℃壒");
+                    requireStatus(fault, StatusConstants.Task.XCB_REVIEWED, "rk_CMB棰嗗瀹℃壒");
                     fault.setCmbOpinion(request.getOpinion());
                     fault.setCmbResult(pass ? "閫氳繃" : "涓嶉�氳繃");
                     fault.setCmbTime(now);
                     fault.setTaskStatus(pass ? StatusConstants.Task.CMB_APPROVED : StatusConstants.Task.RETURNED);
                     break;
                 case "JCC_ASSISTANT":
-                    requireStatus(fault, StatusConstants.Task.CMB_APPROVED, "JCC鍔╃悊瀹℃壒");
+                    requireStatus(fault, StatusConstants.Task.CMB_APPROVED, "rk_JCC鍔╃悊瀹℃壒");
                     fault.setJccAssistantOpinion(request.getOpinion());
                     fault.setJccAssistantResult(pass ? "閫氳繃" : "涓嶉�氳繃");
                     fault.setJccAssistantTime(now);
                     fault.setTaskStatus(pass ? StatusConstants.Task.JCC_ASSISTANT_APPROVED : StatusConstants.Task.RETURNED);
                     break;
                 case "JCC_DIRECTOR":
-                    requireStatus(fault, StatusConstants.Task.JCC_ASSISTANT_APPROVED, "JCC澶勯暱瀹℃壒");
+                    requireStatus(fault, StatusConstants.Task.JCC_ASSISTANT_APPROVED, "rk_JCC澶勯暱瀹℃壒");
                     fault.setJccDirectorOpinion(request.getOpinion());
                     fault.setJccDirectorResult(pass ? "閫氳繃" : "涓嶉�氳繃");
                     fault.setJccDirectorTime(now);
@@ -373,19 +380,17 @@
             if (exists > 0) {
                 throw new BusinessException("鏁呴殰 " + fault.getFaultCode() + " 宸茬粡娲惧崟");
             }
-            List<Contractor> contractors = contractorMapper.selectBatchIds(contractorIds);
-            Map<Long, Contractor> contractorMap = contractors.stream()
-                    .collect(Collectors.toMap(Contractor::getId, Function.identity()));
+            Map<Long, String> contractorMap = contractorNamesByIds(contractorIds);
             int sequence = 1;
             for (Long contractorId : contractorIds) {
-                Contractor contractor = contractorMap.get(contractorId);
-                if (contractor == null) {
+                String contractorName = contractorMap.get(contractorId);
+                if (contractorName == null) {
                     continue;
                 }
                 WorkOrder workOrder = workOrderFromFault(fault);
                 workOrder.setWorkOrderNo(fault.getFaultCode() + "/" + String.format("%02d", sequence++));
-                workOrder.setContractorId(contractor.getId());
-                workOrder.setContractorName(contractor.getName());
+                workOrder.setContractorId(contractorId);
+                workOrder.setContractorName(contractorName);
                 workOrder.setCompletionStatus(StatusConstants.Completion.UNFINISHED);
                 workOrder.setDispatchTime(LocalDateTime.now());
                 workOrder.setCreateTime(LocalDateTime.now());
@@ -401,9 +406,19 @@
         return created;
     }
 
-    public List<WorkOrder> listWorkOrders(String status, String keyword) {
+    public List<WorkOrder> listWorkOrders(String status, String keyword, boolean completionReportable) {
         LambdaQueryWrapper<WorkOrder> query = Wrappers.lambdaQuery();
-        if (StringUtils.hasText(status)) {
+        if (completionReportable) {
+            query.eq(WorkOrder::getCompletionStatus, StatusConstants.Completion.COMPLETED)
+                    .notInSql(WorkOrder::getId, "SELECT WORK_ORDER_ID FROM LX.T_COMPLETION_INFO WHERE DEL_FLAG = 0 OR DEL_FLAG IS NULL");
+            List<String> unitNames = currentUserContractorNames();
+            if (unitNames != null) {
+                if (unitNames.isEmpty()) {
+                    return Collections.emptyList();
+                }
+                query.in(WorkOrder::getContractorName, unitNames);
+            }
+        } else if (StringUtils.hasText(status)) {
             query.in(WorkOrder::getCompletionStatus, Arrays.asList(status.split(",")));
         }
         if (StringUtils.hasText(keyword)) {
@@ -417,6 +432,54 @@
         return workOrderMapper.selectList(query);
     }
 
+    /**
+     * Returns null when the current user sees every unit (system administrator).
+     * Otherwise returns the contractor names matching the logged-in user's department.
+     */
+    private List<String> currentUserContractorNames() {
+        String username = CurrentUserContext.get();
+        if (!StringUtils.hasText(username) || "admin".equalsIgnoreCase(username)) {
+            return null;
+        }
+        List<Map<String, Object>> rows = jdbcTemplate.query(
+                "SELECT d.DEPT_NAME, d.DEPT_TYPE FROM CSICZB_ZB.SYS_USER u "
+                        + "LEFT JOIN CSICZB_ZB.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID "
+                        + "WHERE u.USER_NAME = ? AND (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL)",
+                new Object[]{username}, (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("deptName", rs.getString("DEPT_NAME"));
+                    row.put("deptType", rs.getString("DEPT_TYPE"));
+                    return row;
+                });
+        if (rows.isEmpty() || !StringUtils.hasText((String) rows.get(0).get("deptName"))) {
+            return Collections.emptyList();
+        }
+        Map<String, Object> dept = rows.get(0);
+        if (!"cj".equalsIgnoreCase(String.valueOf(dept.get("deptType")))) {
+            return Collections.emptyList();
+        }
+        return Collections.singletonList(((String) dept.get("deptName")).trim());
+    }
+
+    private Map<Long, String> contractorNamesByIds(Collection<Long> ids) {
+        Map<Long, String> result = new LinkedHashMap<>();
+        if (ids == null || ids.isEmpty()) {
+            return result;
+        }
+        String placeholders = ids.stream().map(item -> "?").collect(Collectors.joining(","));
+        List<Object> args = new ArrayList<>(ids);
+        return jdbcTemplate.query(
+                "SELECT DEPT_ID, DEPT_NAME FROM CSICZB_ZB.SYS_DEPT "
+                        + "WHERE DEPT_TYPE = 'cj' AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL) "
+                        + "AND DEPT_ID IN (" + placeholders + ")",
+                args.toArray(), rs -> {
+                    while (rs.next()) {
+                        result.put(rs.getLong("DEPT_ID"), rs.getString("DEPT_NAME"));
+                    }
+                    return result;
+                });
+    }
+
     public WorkOrder getWorkOrder(Long id) {
         return workOrderMapper.selectById(id);
     }
@@ -424,13 +487,15 @@
     @Transactional
     public void updateCompletionStatus(Requests.StatusRequest request) {
         WorkOrder workOrder = requireWorkOrder(request.getId());
-        if (StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())) {
+        if (StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())
+                && !StatusConstants.Completion.COMPLETED.equals(request.getStatus())) {
             throw new BusinessException("宸插畬宸ユ淳宸ュ崟涓嶈兘閫�鍥炴櫘閫氬畬宸ョ姸鎬�");
         }
         if (!Arrays.asList(
                 StatusConstants.Completion.UNFINISHED,
                 StatusConstants.Completion.WAIT_PARTS,
-                StatusConstants.Completion.WAIT_TEST).contains(request.getStatus())) {
+                StatusConstants.Completion.WAIT_TEST,
+                StatusConstants.Completion.COMPLETED).contains(request.getStatus())) {
             throw new BusinessException("瀹屽伐鐘舵�佷笉鍚堟硶");
         }
         workOrder.setCompletionStatus(request.getStatus());
@@ -453,21 +518,22 @@
     @Transactional
     public CompletionInfo submitCompletion(Requests.CompletionRequest request) {
         WorkOrder workOrder = requireWorkOrder(request.getWorkOrderId());
-        if (StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())) {
+        CompletionInfo info = completionInfoMapper.selectOne(Wrappers.<CompletionInfo>lambdaQuery()
+                .eq(CompletionInfo::getWorkOrderId, workOrder.getId()).last("LIMIT 1"));
+        if (info != null) {
             throw new BusinessException("璇ユ淳宸ュ崟宸插畬宸ヤ笂鎶ワ紝涓嶈兘閲嶅鎻愪氦");
+        }
+        if (!StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())) {
+            throw new BusinessException("璇ユ淳宸ュ崟灏氭湭鍦ㄥ畬宸ユ儏鍐靛鐞嗕腑纭瀹屽伐锛屼笉鑳藉~鎶ュ畬宸ヤ俊鎭�");
         }
         LocalDateTime now = LocalDateTime.now();
         workOrder.setCompletionStatus(StatusConstants.Completion.COMPLETED);
         workOrder.setUpdateTime(now);
         workOrderMapper.updateById(workOrder);
 
-        CompletionInfo info = completionInfoMapper.selectOne(Wrappers.<CompletionInfo>lambdaQuery()
-                .eq(CompletionInfo::getWorkOrderId, workOrder.getId()).last("LIMIT 1"));
-        if (info == null) {
-            info = new CompletionInfo();
-            info.setWorkOrderId(workOrder.getId());
-            info.setCreateTime(now);
-        }
+        info = new CompletionInfo();
+        info.setWorkOrderId(workOrder.getId());
+        info.setCreateTime(now);
         info.setWorkOrderNo(workOrder.getWorkOrderNo());
         info.setXNo(workOrder.getXNo());
         info.setContractorName(workOrder.getContractorName());
@@ -713,7 +779,7 @@
             ids.add(fault.getEquipmentId());
         }
         if (ids.isEmpty()) {
-            throw new BusinessException("璇烽�夋嫨瑁呭鍚嶇О");
+            throw new BusinessException("璇烽�夋嫨璁惧鍚嶇О");
         }
         List<Long> normalized = new ArrayList<>(ids);
         fault.setEquipmentIds(normalized.stream().map(String::valueOf).collect(Collectors.joining(",")));
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/ReportService.java b/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/ReportService.java
index 6edcd9a..c8c40f7 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/ReportService.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/business/service/ReportService.java
@@ -24,7 +24,7 @@
         report.setChart(Arrays.asList(
                 metric("宸ョ▼鎬绘暟", report.getEngineeringTotal()),
                 metric("閲嶅ぇ鏁呴殰", report.getMajorFault()),
-                metric("娑塇缁熻", report.getHRelated()),
+                metric("rk_娑塇缁熻", report.getHRelated()),
                 metric("鐩戞祴椤圭洰", report.getMonitorItem()),
                 metric("鏈畬宸�", report.getUnfinished()),
                 metric("宸插畬宸�", report.getCompleted()),
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/AuditMetaObjectHandler.java b/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/AuditMetaObjectHandler.java
new file mode 100644
index 0000000..17b6307
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/AuditMetaObjectHandler.java
@@ -0,0 +1,24 @@
+package com.emergencyrepair.common.audit;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+/**
+ * Populates create/update actor and logical-delete state automatically.
+ */
+@Component
+public class AuditMetaObjectHandler implements MetaObjectHandler {
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        String username = CurrentUserContext.get();
+        strictInsertFill(metaObject, "createBy", String.class, username);
+        strictInsertFill(metaObject, "updateBy", String.class, username);
+        strictInsertFill(metaObject, "delFlag", Integer.class, 0);
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        strictUpdateFill(metaObject, "updateBy", String.class, CurrentUserContext.get());
+    }
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserContext.java b/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserContext.java
new file mode 100644
index 0000000..3daf07e
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserContext.java
@@ -0,0 +1,24 @@
+package com.emergencyrepair.common.audit;
+
+/**
+ * Carries the request user through the current request thread for MyBatis fills.
+ */
+public final class CurrentUserContext {
+    private static final ThreadLocal<String> USERNAME = new ThreadLocal<>();
+
+    private CurrentUserContext() {
+    }
+
+    public static void set(String username) {
+        USERNAME.set(username);
+    }
+
+    public static String get() {
+        String username = USERNAME.get();
+        return username == null || username.trim().isEmpty() ? "admin" : username.trim();
+    }
+
+    public static void clear() {
+        USERNAME.remove();
+    }
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserInterceptor.java b/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserInterceptor.java
new file mode 100644
index 0000000..9a2c7cb
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/common/audit/CurrentUserInterceptor.java
@@ -0,0 +1,42 @@
+package com.emergencyrepair.common.audit;
+
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class CurrentUserInterceptor implements HandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        CurrentUserContext.set(resolveUsername(request));
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+                                Object handler, Exception exception) {
+        CurrentUserContext.clear();
+    }
+
+    private String resolveUsername(HttpServletRequest request) {
+        String username = request.getHeader("X-Username");
+        if (StringUtils.hasText(username)) {
+            return username.trim();
+        }
+        String authorization = request.getHeader("Authorization");
+        if (!StringUtils.hasText(authorization)) {
+            return "admin";
+        }
+        String token = authorization.trim();
+        if (token.startsWith("Bearer ")) {
+            token = token.substring(7).trim();
+        }
+        if (token.startsWith("demo-") && token.endsWith("-token")) {
+            token = token.substring(5, token.length() - 6);
+        }
+        return StringUtils.hasText(token) ? token : "admin";
+    }
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/common/entity/BaseAuditEntity.java b/emergency-repair-server/src/main/java/com/emergencyrepair/common/entity/BaseAuditEntity.java
new file mode 100644
index 0000000..e95b3cc
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/common/entity/BaseAuditEntity.java
@@ -0,0 +1,22 @@
+package com.emergencyrepair.common.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Data;
+
+/**
+ * Shared audit and logical-delete fields for application-owned tables.
+ */
+@Data
+public abstract class BaseAuditEntity {
+    @TableField(value = "CREATE_BY", fill = FieldFill.INSERT)
+    private String createBy;
+
+    @TableField(value = "UPDATE_BY", fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    @TableLogic(value = "0", delval = "1")
+    @TableField("DEL_FLAG")
+    private Integer delFlag;
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/config/WebConfig.java b/emergency-repair-server/src/main/java/com/emergencyrepair/config/WebConfig.java
index 97f813f..b4cebe1 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/config/WebConfig.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/config/WebConfig.java
@@ -1,11 +1,25 @@
 package com.emergencyrepair.config;
 
+import com.emergencyrepair.common.audit.CurrentUserInterceptor;
 import org.springframework.context.annotation.Configuration;
+import com.emergencyrepair.system.log.OperationLogInterceptor;
+import lombok.RequiredArgsConstructor;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
+@RequiredArgsConstructor
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
+    private final CurrentUserInterceptor currentUserInterceptor;
+    private final OperationLogInterceptor operationLogInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(currentUserInterceptor).addPathPatterns("/api/**").order(0);
+        registry.addInterceptor(operationLogInterceptor).addPathPatterns("/api/**");
+    }
+
     @Override
     public void addCorsMappings(CorsRegistry registry) {
         registry.addMapping("/api/**")
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
index 2f96559..1a9e44f 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
@@ -1,13 +1,15 @@
 package com.emergencyrepair.system.controller;
 
 import com.emergencyrepair.business.dto.Requests;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.emergencyrepair.business.entity.BaseEquipment;
-import com.emergencyrepair.business.entity.Contractor;
 import com.emergencyrepair.business.entity.EngineeringMajor;
 import com.emergencyrepair.business.entity.ProductNode;
 import com.emergencyrepair.business.entity.TeamMajor;
 import com.emergencyrepair.common.Result;
 import com.emergencyrepair.system.entity.SysMenu;
+import com.emergencyrepair.system.entity.SysLoginLog;
+import com.emergencyrepair.system.entity.SysOperationLog;
 import com.emergencyrepair.system.entity.SysRole;
 import com.emergencyrepair.system.service.SystemService;
 import lombok.RequiredArgsConstructor;
@@ -19,6 +21,7 @@
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestHeader;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -31,13 +34,81 @@
     private final SystemService systemService;
 
     @GetMapping("/menu/nav")
-    public Result<List<SysMenu>> nav() {
-        return Result.ok(systemService.menuTree());
+    public Result<List<SysMenu>> nav(@RequestHeader(value = "X-Username", required = false) String username) {
+        return Result.ok(systemService.menuTreeForUser(username));
     }
 
     @GetMapping("/menu/list")
     public Result<List<SysMenu>> menuList() {
         return Result.ok(systemService.listMenus());
+    }
+
+    @GetMapping("/menu/tree")
+    public Result<List<SysMenu>> menuTreeWithButtons() {
+        return Result.ok(systemService.menuTreeWithButtons());
+    }
+
+    @GetMapping("/menu/buttons")
+    public Result<Map<String, Object>> menuButtons(@RequestParam Long menuId) {
+        return Result.ok(systemService.menuButtons(menuId));
+    }
+
+    @PostMapping("/menu/buttons")
+    public Result<Void> syncMenuButtons(@RequestBody Requests.ButtonSyncRequest request) {
+        systemService.syncMenuButtons(request.getMenuId(), request.getCodes());
+        return Result.ok();
+    }
+
+    @GetMapping("/users")
+    public Result<Map<String, Object>> users(@RequestParam(required = false) String keyword,
+                                            @RequestParam(required = false) Long deptId,
+                                            @RequestParam(required = false) Long roleId,
+                                            @RequestParam(defaultValue = "1") int current,
+                                            @RequestParam(defaultValue = "20") int size) {
+        return Result.ok(systemService.userPage(keyword, deptId, roleId, current, size));
+    }
+
+    @GetMapping("/users/{userId}/roles")
+    public Result<List<String>> userRoles(@PathVariable Long userId) {
+        return Result.ok(systemService.userRoleIds(userId));
+    }
+
+    @PutMapping("/users/{userId}/roles")
+    public Result<Void> saveUserRoles(@PathVariable Long userId, @RequestBody Requests.UserRoleRequest request) {
+        systemService.saveUserRoles(userId, request.getRoleIds());
+        return Result.ok();
+    }
+
+    @PutMapping("/users/roles/batch")
+    public Result<Void> saveBatchUserRoles(@RequestBody Requests.BatchUserRoleRequest request) {
+        systemService.saveUserRolesBatch(request.getUserIds(), request.getRoleIds(), request.getMode());
+        return Result.ok();
+    }
+
+    @GetMapping("/depts")
+    public Result<List<Map<String, Object>>> depts() {
+        return Result.ok(systemService.depts());
+    }
+
+    @GetMapping("/login-logs")
+    public Result<Page<SysLoginLog>> loginLogs(@RequestParam(defaultValue = "1") int current,
+                                                @RequestParam(defaultValue = "20") int size,
+                                                @RequestParam(required = false) String keyword,
+                                                @RequestParam(required = false) String status,
+                                                @RequestParam(required = false) String startDate,
+                                                @RequestParam(required = false) String endDate) {
+        return Result.ok(systemService.loginLogPage(current, size, keyword, status, startDate, endDate));
+    }
+
+    @GetMapping("/operation-logs")
+    public Result<Page<SysOperationLog>> operationLogs(@RequestParam(defaultValue = "1") int current,
+                                                    @RequestParam(defaultValue = "20") int size,
+                                                    @RequestParam(required = false) String keyword,
+                                                    @RequestParam(required = false) String module,
+                                                    @RequestParam(required = false) String operationType,
+                                                    @RequestParam(required = false) String startDate,
+                                                    @RequestParam(required = false) String endDate) {
+        return Result.ok(systemService.operationLogPage(current, size, keyword, module, operationType, startDate, endDate));
     }
 
     @PostMapping("/menu")
@@ -81,27 +152,20 @@
         return Result.ok();
     }
 
-    @GetMapping("/contractors")
-    public Result<List<Contractor>> contractors() {
-        return Result.ok(systemService.listContractors());
+    @GetMapping("/roles/{roleId}/users")
+    public Result<Map<String, Object>> roleUsers(@PathVariable Long roleId) {
+        return Result.ok(systemService.roleUsers(roleId));
     }
 
-    @PostMapping("/contractors")
-    public Result<Contractor> createContractor(@RequestBody Contractor contractor) {
-        contractor.setId(null);
-        return Result.ok(systemService.saveContractor(contractor));
-    }
-
-    @PutMapping("/contractors/{id}")
-    public Result<Contractor> updateContractor(@PathVariable Long id, @RequestBody Contractor contractor) {
-        contractor.setId(id);
-        return Result.ok(systemService.saveContractor(contractor));
-    }
-
-    @DeleteMapping("/contractors/{id}")
-    public Result<Void> deleteContractor(@PathVariable Long id) {
-        systemService.deleteContractor(id);
+    @PutMapping("/roles/{roleId}/users")
+    public Result<Void> saveRoleUsers(@PathVariable Long roleId, @RequestBody Requests.RoleUserRequest request) {
+        systemService.saveRoleUsers(roleId, request.getUserIds());
         return Result.ok();
+    }
+
+    @GetMapping("/contractors")
+    public Result<List<Map<String, Object>>> contractors() {
+        return Result.ok(systemService.listContractors());
     }
 
     @GetMapping("/engineering-majors")
@@ -155,6 +219,31 @@
         return Result.ok(systemService.tree(type));
     }
 
+    @GetMapping("/tree/product/roots")
+    public Result<List<Map<String, Object>>> productTreeRoots() {
+        return Result.ok(systemService.productTreeRoots());
+    }
+
+    @GetMapping("/tree/product/children")
+    public Result<List<Map<String, Object>>> productTreeChildren(@RequestParam Long parentId) {
+        return Result.ok(systemService.productTreeChildren(parentId));
+    }
+
+    @GetMapping("/tree/product/side-roots")
+    public Result<List<Map<String, Object>>> productTreeForXNo(@RequestParam(required = false) String xNo) {
+        return Result.ok(systemService.productTreeForXNo(xNo));
+    }
+
+    @GetMapping("/tree/product/x-numbers")
+    public Result<List<String>> productXNumbers() {
+        return Result.ok(systemService.productXNumbers());
+    }
+
+    @GetMapping("/tree/product/selected")
+    public Result<List<Map<String, Object>>> selectedProductNodes(@RequestParam List<Long> ids) {
+        return Result.ok(systemService.selectedProductNodes(ids));
+    }
+
     @PostMapping("/tree/product")
     public Result<ProductNode> createProduct(@RequestBody ProductNode node) {
         node.setId(null);
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysLoginLog.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysLoginLog.java
new file mode 100644
index 0000000..f9bc084
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysLoginLog.java
@@ -0,0 +1,29 @@
+package com.emergencyrepair.system.entity;
+
+import com.emergencyrepair.common.entity.BaseAuditEntity;
+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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("LX.T_SYS_LOGIN_LOG")
+public class SysLoginLog extends BaseAuditEntity {
+    @TableId(value = "ID", type = IdType.ASSIGN_ID)
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+    @JsonSerialize(using = ToStringSerializer.class)
+    @TableField("USER_ID") private Long userId;
+    @TableField("USER_NAME") private String userName;
+    @TableField("NICK_NAME") private String nickName;
+    @TableField("DEPT_NAME") private String deptName;
+    @TableField("LOGIN_IP") private String loginIp;
+    @TableField("STATUS") private String status;
+    @TableField("MESSAGE") private String message;
+    @TableField("LOGIN_TIME") private LocalDateTime loginTime;
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysMenu.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysMenu.java
index 0ec90ed..1db2293 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysMenu.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysMenu.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.system.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -14,7 +15,7 @@
 
 @Data
 @TableName("LX.T_SYS_MENU")
-public class SysMenu {
+public class SysMenu extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysOperationLog.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysOperationLog.java
new file mode 100644
index 0000000..989b95b
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysOperationLog.java
@@ -0,0 +1,33 @@
+package com.emergencyrepair.system.entity;
+
+import com.emergencyrepair.common.entity.BaseAuditEntity;
+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 com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("LX.T_SYS_OPERATION_LOG")
+public class SysOperationLog extends BaseAuditEntity {
+    @TableId(value = "ID", type = IdType.ASSIGN_ID)
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
+    @JsonSerialize(using = ToStringSerializer.class)
+    @TableField("USER_ID") private Long userId;
+    @TableField("USER_NAME") private String userName;
+    @TableField("NICK_NAME") private String nickName;
+    @TableField("MODULE") private String module;
+    @TableField("OPERATION_TYPE") private String operationType;
+    @TableField("TARGET_TYPE") private String targetType;
+    @TableField("TARGET_ID") private String targetId;
+    @TableField("DESCRIPTION") private String description;
+    @TableField("REQUEST_METHOD") private String requestMethod;
+    @TableField("REQUEST_URI") private String requestUri;
+    @TableField("RESULT") private String result;
+    @TableField("CREATE_TIME") private LocalDateTime createTime;
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRole.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRole.java
index f1c5bbd..429c41a 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRole.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRole.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.system.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_SYS_ROLE")
-public class SysRole {
+public class SysRole extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleMenu.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleMenu.java
index d7bd514..95dbdf8 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleMenu.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleMenu.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.system.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_SYS_ROLE_MENU")
-public class SysRoleMenu {
+public class SysRoleMenu extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleUser.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleUser.java
index 7ad218c..70f774e 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleUser.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysRoleUser.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.system.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_SYS_ROLE_USER")
-public class SysRoleUser {
+public class SysRoleUser extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysTenantMenu.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysTenantMenu.java
index da9af4f..991b1d2 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysTenantMenu.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysTenantMenu.java
@@ -1,5 +1,6 @@
 package com.emergencyrepair.system.entity;
 
+import com.emergencyrepair.common.entity.BaseAuditEntity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -12,7 +13,7 @@
 
 @Data
 @TableName("LX.T_SYS_TENANT_MENU")
-public class SysTenantMenu {
+public class SysTenantMenu extends BaseAuditEntity {
     @TableId(value = "ID", type = IdType.ASSIGN_ID)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long id;
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/log/OperationLogInterceptor.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/log/OperationLogInterceptor.java
new file mode 100644
index 0000000..23d822a
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/log/OperationLogInterceptor.java
@@ -0,0 +1,112 @@
+package com.emergencyrepair.system.log;
+
+import com.emergencyrepair.system.entity.SysOperationLog;
+import com.emergencyrepair.common.audit.CurrentUserContext;
+import com.emergencyrepair.system.mapper.SysOperationLogMapper;
+import com.emergencyrepair.system.service.SystemService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Records add/update/delete calls. Login is covered by the login log.
+ */
+@Component
+@RequiredArgsConstructor
+public class OperationLogInterceptor implements HandlerInterceptor {
+    private static final Pattern TARGET_ID = Pattern.compile(".*/(\\d{6,})(?:/.*)?$");
+
+    private final SysOperationLogMapper operationLogMapper;
+    private final SystemService systemService;
+    private final JdbcTemplate jdbcTemplate;
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+                                Object handler, Exception exception) {
+        String method = request.getMethod();
+        if (!"POST".equals(method) && !"PUT".equals(method) && !"DELETE".equals(method)) {
+            return;
+        }
+        String uri = request.getRequestURI();
+        if (!StringUtils.hasText(uri) || !uri.startsWith("/api/") || uri.startsWith("/api/auth/login")) {
+            return;
+        }
+        try {
+            String username = CurrentUserContext.get();
+            SysOperationLog log = new SysOperationLog();
+            log.setUserName(username);
+            Long userId = systemService.resolveUserId(username);
+            log.setUserId(userId);
+            log.setNickName(resolveNickName(userId));
+            log.setModule(resolveModule(uri));
+            log.setOperationType(resolveOperationType(uri, method));
+            log.setTargetType(resolveTargetType(uri));
+            log.setTargetId(resolveTargetId(uri));
+            log.setDescription(method + " " + uri);
+            log.setRequestMethod(method);
+            log.setRequestUri(uri);
+            log.setResult(exception == null && response.getStatus() < 400 ? "\u6210\u529f" : "\u5931\u8d25");
+            log.setCreateTime(LocalDateTime.now());
+            operationLogMapper.insert(log);
+        } catch (Exception ignored) {
+            // Logging must never break the business request.
+        }
+    }
+
+    private String resolveNickName(Long userId) {
+        if (userId == null) {
+            return null;
+        }
+        List<String> names = jdbcTemplate.query(
+                "SELECT NICK_NAME FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ?",
+                new Object[]{userId}, (rs, rowNum) -> rs.getString(1));
+        return names.isEmpty() ? null : names.get(0);
+    }
+
+    private String resolveModule(String uri) {
+        if (uri.startsWith("/api/faults") || uri.startsWith("/api/fault")) return "\u6545\u969c\u7ba1\u7406";
+        if (uri.startsWith("/api/work-orders") || uri.startsWith("/api/completion")) return "\u5b8c\u5de5\u7ba1\u7406";
+        if (uri.startsWith("/api/acceptances")) return "\u5b8c\u5de5\u9a8c\u6536";
+        if (uri.startsWith("/api/reports")) return "\u7edf\u8ba1\u62a5\u8868";
+        if (uri.startsWith("/api/system/users")) return "\u7528\u6237\u7ba1\u7406";
+        if (uri.startsWith("/api/system/roles")) return "\u89d2\u8272\u7ba1\u7406";
+        if (uri.startsWith("/api/system/menu")) return "\u83dc\u5355\u7ba1\u7406";
+        if (uri.startsWith("/api/system/contractors")) return "\u627f\u4fee\u5355\u4f4d\u7ba1\u7406";
+        if (uri.startsWith("/api/system")) return "\u7cfb\u7edf\u7ba1\u7406";
+        return "\u5176\u4ed6";
+    }
+
+    private String resolveOperationType(String uri, String method) {
+        if (uri.contains("/dispatch")) return "\u6d3e\u5355";
+        if (uri.contains("/approve") || uri.contains("/approval") || uri.contains("/review")) return "\u5ba1\u6279";
+        if (uri.contains("/cancel")) return "\u9500\u9879";
+        if (uri.contains("/submit")) return "\u63d0\u4ea4";
+        if (uri.contains("/apply")) return "\u7533\u8bf7";
+        if (uri.contains("/print")) return "\u6253\u5370";
+        if (uri.contains("/rating")) return "\u8bc4\u7ea7";
+        if (uri.contains("/roles") || uri.contains("/menu-permissions")) return "\u6388\u6743";
+        if (uri.contains("/buttons")) return "\u83dc\u5355\u6309\u94ae\u914d\u7f6e";
+        if ("POST".equals(method)) return "\u65b0\u589e";
+        if ("PUT".equals(method)) return "\u4fee\u6539";
+        return "\u5220\u9664";
+    }
+
+    private String resolveTargetType(String uri) {
+        String[] parts = uri.replace("/api/", "").split("/");
+        return parts.length == 0 ? "" : parts[0];
+    }
+
+    private String resolveTargetId(String uri) {
+        Matcher matcher = TARGET_ID.matcher(uri);
+        return matcher.matches() ? matcher.group(1) : null;
+    }
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysLoginLogMapper.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysLoginLogMapper.java
new file mode 100644
index 0000000..15dfcf9
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysLoginLogMapper.java
@@ -0,0 +1,7 @@
+package com.emergencyrepair.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.emergencyrepair.system.entity.SysLoginLog;
+
+public interface SysLoginLogMapper extends BaseMapper<SysLoginLog> {
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysOperationLogMapper.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysOperationLogMapper.java
new file mode 100644
index 0000000..5bab657
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysOperationLogMapper.java
@@ -0,0 +1,7 @@
+package com.emergencyrepair.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.emergencyrepair.system.entity.SysOperationLog;
+
+public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java
new file mode 100644
index 0000000..d530fa1
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java
@@ -0,0 +1,117 @@
+package com.emergencyrepair.system.service;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Declarative catalog of the operation buttons each page exposes.
+ * Menu management uses it to create MENU_TYPE=2 entries automatically.
+ */
+@Component
+public class ButtonPermissionCatalog {
+    @Data
+    @AllArgsConstructor
+    public static class ButtonDefinition {
+        private String code;
+        private String name;
+    }
+
+    private static final Map<String, List<ButtonDefinition>> CATALOG = new LinkedHashMap<>();
+
+    static {
+        put("/dashboard",
+                def("dashboard:query", "\u67e5\u8be2"));
+        put("/fault/report",
+                def("fault:report:query", "\u67e5\u8be2"),
+                def("fault:report:add", "\u65b0\u589e"),
+                def("fault:report:edit", "\u4fee\u6539"),
+                def("fault:report:delete", "\u5220\u9664"),
+                def("fault:report:export", "\u5bfc\u51fa"),
+                def("fault:report:report", "\u4e0a\u62a5"));
+        put("/fault/review",
+                def("fault:review:query", "\u67e5\u8be2"),
+                def("fault:review:audit", "\u5ba1\u6838"),
+                def("fault:review:edit", "\u4fee\u6539"),
+                def("fault:review:cancel", "\u9500\u9879"));
+        put("/fault/approval/cmb",
+                def("fault:approval:query", "\u67e5\u8be2"),
+                def("fault:approval:approve", "\u5ba1\u6279"));
+        put("/fault/approval/jcc-assistant",
+                def("fault:approval:query", "\u67e5\u8be2"),
+                def("fault:approval:approve", "\u5ba1\u6279"));
+        put("/fault/approval/jcc-director",
+                def("fault:approval:query", "\u67e5\u8be2"),
+                def("fault:approval:approve", "\u5ba1\u6279"));
+        put("/fault/dispatch",
+                def("fault:dispatch:query", "\u67e5\u8be2"),
+                def("fault:dispatch:dispatch", "\u4e00\u952e\u6d3e\u5355"));
+        put("/fault/completion",
+                def("fault:completion:query", "\u67e5\u8be2"),
+                def("fault:completion:status", "\u5b8c\u5de5\u72b6\u6001\u8bbe\u7f6e"),
+                def("fault:completion:score", "rk_T\u5458\u8bc4\u5206"));
+        put("/completion/report",
+                def("completion:report:save", "\u4fdd\u5b58"),
+                def("completion:report:submit", "\u63d0\u4ea4"));
+        put("/acceptance/application",
+                def("acceptance:application:query", "\u67e5\u8be2"),
+                def("acceptance:application:apply", "\u7533\u8bf7"));
+        put("/acceptance/inspector",
+                def("acceptance:inspector:query", "\u67e5\u8be2"),
+                def("acceptance:inspector:pass", "\u901a\u8fc7"),
+                def("acceptance:inspector:reject", "\u6253\u56de"));
+        put("/acceptance/leader",
+                def("acceptance:leader:query", "\u67e5\u8be2"),
+                def("acceptance:leader:pass", "\u901a\u8fc7"),
+                def("acceptance:leader:reject", "\u6253\u56de"));
+        put("/acceptance/print",
+                def("acceptance:print:query", "\u67e5\u8be2"),
+                def("acceptance:print:print", "\u6253\u5370"));
+        put("/acceptance/rating",
+                def("acceptance:rating:query", "\u67e5\u8be2"),
+                def("acceptance:rating:grade", "\u8bc4\u7ea7"));
+        put("/reports/overview", def("report:common:query", "\u67e5\u8be2"), def("report:common:export", "\u5bfc\u51fa"));
+        put("/reports/top-equipment", def("report:common:query", "\u67e5\u8be2"), def("report:common:export", "\u5bfc\u51fa"));
+        put("/reports/by-team", def("report:common:query", "\u67e5\u8be2"), def("report:common:export", "\u5bfc\u51fa"));
+        put("/reports/contractors", def("report:common:query", "\u67e5\u8be2"), def("report:common:export", "\u5bfc\u51fa"));
+        put("/reports/trend", def("report:common:query", "\u67e5\u8be2"), def("report:common:export", "\u5bfc\u51fa"));
+        put("/system/product-tree", def("system:tree:add", "\u65b0\u589e"), def("system:tree:edit", "\u7f16\u8f91"), def("system:tree:delete", "\u5220\u9664"));
+        put("/system/base-equipment", def("system:tree:add", "\u65b0\u589e"), def("system:tree:edit", "\u7f16\u8f91"), def("system:tree:delete", "\u5220\u9664"));
+        put("/system/engineering-majors", def("system:major:add", "\u65b0\u589e"), def("system:major:edit", "\u7f16\u8f91"), def("system:major:delete", "\u5220\u9664"));
+        put("/system/team-majors", def("system:major:add", "\u65b0\u589e"), def("system:major:edit", "\u7f16\u8f91"), def("system:major:delete", "\u5220\u9664"));
+        put("/system/users", def("system:users:query", "\u67e5\u8be2"), def("system:users:assign", "\u89d2\u8272\u6388\u6743"));
+        put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"));
+        put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"), def("system:roles:assign", "\u7528\u6237\u8bbe\u7f6e"));
+        put("/system/menus", def("system:menus:add", "\u65b0\u589e"), def("system:menus:edit", "\u7f16\u8f91"), def("system:menus:delete", "\u5220\u9664"), def("system:menus:buttons", "\u83dc\u5355\u6309\u94ae\u540c\u6b65"));
+        put("/system/menu-permissions", def("system:menu-permissions:save", "\u4fdd\u5b58\u6388\u6743"));
+        put("/system/login-logs", def("system:login-logs:query", "\u67e5\u8be2"), def("system:login-logs:export", "\u5bfc\u51fa"));
+        put("/system/operation-logs", def("system:operation-logs:query", "\u67e5\u8be2"), def("system:operation-logs:export", "\u5bfc\u51fa"));
+    }
+
+    private static void put(String url, ButtonDefinition... definitions) {
+        List<ButtonDefinition> list = new ArrayList<>();
+        Collections.addAll(list, definitions);
+        CATALOG.put(url, Collections.unmodifiableList(list));
+    }
+
+    private static ButtonDefinition def(String code, String name) {
+        return new ButtonDefinition(code, name);
+    }
+
+    public List<ButtonDefinition> buttonsFor(String url) {
+        if (url == null) {
+            return Collections.emptyList();
+        }
+        return CATALOG.getOrDefault(url, Collections.emptyList());
+    }
+
+    public Map<String, List<ButtonDefinition>> catalog() {
+        return CATALOG;
+    }
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
index 7d8f9b5..5c5b3e7 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
@@ -1,41 +1,52 @@
 package com.emergencyrepair.system.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.emergencyrepair.business.entity.BaseEquipment;
-import com.emergencyrepair.business.entity.Contractor;
 import com.emergencyrepair.business.entity.EngineeringMajor;
 import com.emergencyrepair.business.entity.TeamMajor;
 import com.emergencyrepair.business.entity.ProductNode;
 import com.emergencyrepair.business.mapper.BaseEquipmentMapper;
-import com.emergencyrepair.business.mapper.ContractorMapper;
 import com.emergencyrepair.business.mapper.EngineeringMajorMapper;
 import com.emergencyrepair.business.mapper.ProductNodeMapper;
 import com.emergencyrepair.business.mapper.TeamMajorMapper;
 import com.emergencyrepair.common.BusinessException;
 import com.emergencyrepair.system.entity.SysMenu;
+import com.emergencyrepair.system.entity.SysLoginLog;
+import com.emergencyrepair.system.entity.SysOperationLog;
 import com.emergencyrepair.system.entity.SysRole;
 import com.emergencyrepair.system.entity.SysRoleMenu;
 import com.emergencyrepair.system.entity.SysRoleUser;
 import com.emergencyrepair.system.entity.SysTenantMenu;
 import com.emergencyrepair.system.mapper.SysMenuMapper;
+import com.emergencyrepair.system.mapper.SysLoginLogMapper;
+import com.emergencyrepair.system.mapper.SysOperationLogMapper;
 import com.emergencyrepair.system.mapper.SysRoleMapper;
 import com.emergencyrepair.system.mapper.SysRoleMenuMapper;
 import com.emergencyrepair.system.mapper.SysRoleUserMapper;
 import com.emergencyrepair.system.mapper.SysTenantMenuMapper;
 import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 
 import java.time.LocalDateTime;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 @Service
@@ -46,14 +57,28 @@
     private final SysRoleMenuMapper roleMenuMapper;
     private final SysTenantMenuMapper tenantMenuMapper;
     private final SysRoleUserMapper roleUserMapper;
-    private final ContractorMapper contractorMapper;
+    private final SysLoginLogMapper loginLogMapper;
+    private final SysOperationLogMapper operationLogMapper;
+    private final ButtonPermissionCatalog buttonPermissionCatalog;
+    private final JdbcTemplate jdbcTemplate;
     private final EngineeringMajorMapper engineeringMajorMapper;
     private final TeamMajorMapper teamMajorMapper;
     private final ProductNodeMapper productNodeMapper;
     private final BaseEquipmentMapper baseEquipmentMapper;
 
     public List<SysMenu> menuTree() {
-        List<SysMenu> menus = listMenus();
+        // Buttons are permission points, not navigable nodes.
+        List<SysMenu> navigable = listMenus().stream()
+                .filter(menu -> menu.getMenuType() == null || menu.getMenuType() != 2)
+                .collect(Collectors.toList());
+        return buildTree(navigable);
+    }
+
+    public List<SysMenu> menuTreeWithButtons() {
+        return buildTree(listMenus());
+    }
+
+    private List<SysMenu> buildTree(List<SysMenu> menus) {
         Map<Long, SysMenu> map = menus.stream().collect(Collectors.toMap(SysMenu::getId, item -> item));
         List<SysMenu> roots = new ArrayList<>();
         for (SysMenu menu : menus) {
@@ -75,6 +100,29 @@
     public SysMenu saveMenu(SysMenu menu) {
         if (!StringUtils.hasText(menu.getName())) {
             throw new BusinessException("鑿滃崟鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (menu.getMenuType() == null) {
+            menu.setMenuType(menu.getId() == null ? 1 : menu.getMenuType());
+        }
+        boolean isRoot = menu.getPid() == null || menu.getPid() == 0L;
+        if (menu.getMenuType() == 2) {
+            if (isRoot) {
+                throw new BusinessException("鎸夐挳绫诲瀷鍙兘寤哄湪鑿滃崟绫诲瀷涓嬬骇");
+            }
+            SysMenu parent = menuMapper.selectById(menu.getPid());
+            if (parent == null || parent.getMenuType() == null || parent.getMenuType() != 1) {
+                throw new BusinessException("鎸夐挳绫诲瀷鍙兘寤哄湪鑿滃崟绫诲瀷涓嬬骇");
+            }
+            if (!StringUtils.hasText(menu.getPermissions())) {
+                throw new BusinessException("鎸夐挳鏉冮檺鏍囪瘑涓嶈兘涓虹┖");
+            }
+            menu.setUrl(null);
+            menu.setIcon(null);
+        } else if (!isRoot) {
+            SysMenu parent = menuMapper.selectById(menu.getPid());
+            if (parent != null && parent.getMenuType() != null && parent.getMenuType() == 2) {
+                throw new BusinessException("鎸夐挳绫诲瀷涓嬬骇涓嶈兘鍐嶅缓鑿滃崟");
+            }
         }
         LocalDateTime now = LocalDateTime.now();
         menu.setUpdateTime(now);
@@ -131,30 +179,27 @@
         roleUserMapper.delete(Wrappers.<SysRoleUser>lambdaQuery().eq(SysRoleUser::getRoleId, id));
     }
 
-    public List<Contractor> listContractors() {
-        return contractorMapper.selectList(Wrappers.<Contractor>lambdaQuery()
-                .orderByAsc(Contractor::getSortNo).orderByAsc(Contractor::getId));
-    }
-
-    @Transactional
-    public Contractor saveContractor(Contractor contractor) {
-        if (!StringUtils.hasText(contractor.getName())) {
-            throw new BusinessException("鎵夸慨鍗曚綅鍚嶇О涓嶈兘涓虹┖");
-        }
-        LocalDateTime now = LocalDateTime.now();
-        contractor.setUpdateTime(now);
-        if (contractor.getId() == null) {
-            contractor.setCreateTime(now);
-            contractorMapper.insert(contractor);
-        } else {
-            contractorMapper.updateById(contractor);
-        }
-        return contractorMapper.selectById(contractor.getId());
-    }
-
-    @Transactional
-    public void deleteContractor(Long id) {
-        contractorMapper.deleteById(id);
+    /**
+     * Contractor units are maintained as department records with DEPT_TYPE='cj'.
+     */
+    public List<Map<String, Object>> listContractors() {
+        return jdbcTemplate.query(
+                "SELECT DEPT_ID, DEPT_NAME, ORDER_NUM, LEADER, PHONE, STATUS, DEPT_CODE FROM CSICZB_ZB.SYS_DEPT "
+                        + "WHERE DEPT_TYPE = 'cj' AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL) ORDER BY ORDER_NUM, DEPT_ID",
+                (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("id", String.valueOf(rs.getLong("DEPT_ID")));
+                    row.put("name", rs.getString("DEPT_NAME"));
+                    row.put("shortName", null);
+                    row.put("nature", "鎵夸慨鍗曚綅");
+                    row.put("sortNo", rs.getObject("ORDER_NUM"));
+                    row.put("contact", rs.getString("LEADER"));
+                    row.put("phone", rs.getString("PHONE"));
+                    row.put("status", rs.getString("STATUS"));
+                    row.put("deptCode", rs.getString("DEPT_CODE"));
+                    row.put("dishonest", 0);
+                    return row;
+                });
     }
 
     public List<EngineeringMajor> listEngineeringMajors() {
@@ -201,12 +246,12 @@
     @Transactional
     public TeamMajor saveTeamMajor(TeamMajor major) {
         if (major == null || !StringUtils.hasText(major.getName())) {
-            throw new BusinessException("T闃熶笓涓氬悕绉颁笉鑳戒负绌�");
+            throw new BusinessException("rk_T闃熶笓涓氬悕绉颁笉鑳戒负绌�");
         }
         String name = major.getName().trim();
         String category = StringUtils.hasText(major.getCategory()) ? major.getCategory().trim().toUpperCase() : "";
         if (!"A".equals(category) && !"B".equals(category)) {
-            throw new BusinessException("T闃熶笓涓氬垎绫诲彧鑳芥槸A鎴朆");
+            throw new BusinessException("rk_T闃熶笓涓氬垎绫诲彧鑳芥槸A鎴朆");
         }
         LambdaQueryWrapper<TeamMajor> duplicate = Wrappers.<TeamMajor>lambdaQuery()
                 .eq(TeamMajor::getName, name);
@@ -214,7 +259,7 @@
             duplicate.ne(TeamMajor::getId, major.getId());
         }
         if (teamMajorMapper.selectCount(duplicate) > 0) {
-            throw new BusinessException("T闃熶笓涓氬悕绉板凡瀛樺湪锛�" + name);
+            throw new BusinessException("rk_T闃熶笓涓氬悕绉板凡瀛樺湪锛�" + name);
         }
         LocalDateTime now = LocalDateTime.now();
         major.setName(name);
@@ -244,9 +289,180 @@
         throw new BusinessException("鏈煡鏍戠被鍨�");
     }
 
+    public List<Map<String, Object>> selectedProductNodes(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<ProductNode> source = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .in(ProductNode::getId, ids));
+        Map<Long, ProductNode> unique = uniqueProductNodes(source);
+        List<Map<String, Object>> result = new ArrayList<>();
+        Set<Long> seen = new HashSet<>();
+        for (Long id : ids) {
+            ProductNode node = unique.get(id);
+            if (node == null || !seen.add(id)) {
+                continue;
+            }
+            List<ProductNode> path = productPath(node);
+            Map<String, Object> row = mapProduct(node, Collections.emptySet());
+            row.put("pathNames", path.stream().map(ProductNode::getName).collect(Collectors.toList()));
+            row.put("parentPathNames", path.stream()
+                    .filter(item -> item != node)
+                    .filter(item -> item.getNodeLevel() != null && item.getNodeLevel() > 2)
+                    .map(ProductNode::getName)
+                    .collect(Collectors.toList()));
+            ProductNode xNode = path.stream()
+                    .filter(item -> item.getNodeLevel() != null && item.getNodeLevel() == 2)
+                    .findFirst().orElse(null);
+            row.put("xNo", xNode == null ? null : xNode.getName());
+            result.add(row);
+        }
+        return result;
+    }
+
+    private List<Map<String, Object>> mapProductTreeNodes(List<ProductNode> source, Set<Long> forcedBranchIds) {
+        Map<Long, ProductNode> unique = uniqueProductNodes(source);
+        List<ProductNode> nodes = sortProductNodes(unique.values());
+        Set<Long> branchIds = new HashSet<>(forcedBranchIds);
+        if (!unique.isEmpty()) {
+            List<ProductNode> childRows = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                    .select(ProductNode::getParentId, ProductNode::getIsDelete)
+                    .in(ProductNode::getParentId, unique.keySet()));
+            for (ProductNode child : childRows) {
+                if (!"1".equals(child.getIsDelete()) && child.getParentId() != null) {
+                    branchIds.add(child.getParentId());
+                }
+            }
+        }
+        List<Map<String, Object>> rows = new ArrayList<>();
+        for (ProductNode node : nodes) {
+            rows.add(mapProduct(node, branchIds));
+        }
+        return rows;
+    }
+
+    private Map<Long, ProductNode> uniqueProductNodes(Collection<ProductNode> source) {
+        Map<Long, ProductNode> unique = new LinkedHashMap<>();
+        if (source == null) {
+            return unique;
+        }
+        for (ProductNode node : source) {
+            if (node.getId() == null || "1".equals(node.getIsDelete())) {
+                continue;
+            }
+            unique.putIfAbsent(node.getId(), node);
+        }
+        return unique;
+    }
+
+    private List<ProductNode> sortProductNodes(Collection<ProductNode> source) {
+        List<ProductNode> nodes = new ArrayList<>(source);
+        nodes.sort(Comparator.comparingInt(this::productNodeLevel)
+                .thenComparing(ProductNode::getSortNo, Comparator.nullsLast(Integer::compareTo))
+                .thenComparing(ProductNode::getId, Comparator.nullsLast(Long::compareTo)));
+        return nodes;
+    }
+
+    private ProductNode findProductRoot(ProductNode node) {
+        ProductNode current = node;
+        Set<Long> visited = new HashSet<>();
+        while (current != null && current.getParentId() != null && current.getParentId() != 0L
+                && visited.add(current.getId())) {
+            current = findProductNode(current.getParentId());
+        }
+        return current;
+    }
+
+    private List<ProductNode> productPath(ProductNode node) {
+        List<ProductNode> reversed = new ArrayList<>();
+        Set<Long> visited = new HashSet<>();
+        ProductNode current = node;
+        while (current != null && visited.add(current.getId())) {
+            reversed.add(current);
+            if (current.getParentId() == null || current.getParentId() == 0L) {
+                break;
+            }
+            current = findProductNode(current.getParentId());
+        }
+        Collections.reverse(reversed);
+        return reversed;
+    }
+
+    public List<Map<String, Object>> productTreeForXNo(String xNo) {
+        if (!StringUtils.hasText(xNo)) {
+            return productTreeRoots();
+        }
+        List<ProductNode> rootNodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .and(wrapper -> wrapper.isNull(ProductNode::getParentId).or().eq(ProductNode::getParentId, 0L))
+                .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
+        List<ProductNode> sideNodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .in(ProductNode::getLayer, Arrays.asList("side", "SIDE"))
+                .eq(ProductNode::getName, xNo.trim())
+                .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
+        Map<Long, ProductNode> roots = uniqueProductNodes(rootNodes);
+        Map<Long, ProductNode> sides = uniqueProductNodes(sideNodes);
+        Set<Long> branchIds = new HashSet<>();
+        branchIds.addAll(roots.keySet());
+        branchIds.addAll(sides.keySet());
+        Map<Long, Map<String, Object>> rootRows = new LinkedHashMap<>();
+        for (ProductNode root : sortProductNodes(roots.values())) {
+            rootRows.put(root.getId(), mapProduct(root, branchIds));
+        }
+        for (ProductNode side : sortProductNodes(sides.values())) {
+            ProductNode root = findProductRoot(side);
+            Map<String, Object> rootRow = root == null ? null : rootRows.get(root.getId());
+            if (rootRow != null) {
+                children(rootRow).add(mapProduct(side, branchIds));
+            }
+        }
+        return new ArrayList<>(rootRows.values());
+    }
+
+    public List<String> productXNumbers() {
+        List<ProductNode> nodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .in(ProductNode::getLayer, Arrays.asList("side", "SIDE"))
+                .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
+        return nodes.stream()
+                .filter(node -> !"1".equals(node.getIsDelete()))
+                .map(ProductNode::getName)
+                .filter(StringUtils::hasText)
+                .map(String::trim)
+                .distinct()
+                .sorted(String.CASE_INSENSITIVE_ORDER)
+                .collect(Collectors.toList());
+    }
+
+    public List<Map<String, Object>> productTreeRoots() {
+        List<ProductNode> nodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .and(wrapper -> wrapper.isNull(ProductNode::getParentId).or().eq(ProductNode::getParentId, 0L))
+                .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
+        return mapProductTreeNodes(nodes, Collections.emptySet());
+    }
+
+    public List<Map<String, Object>> productTreeChildren(Long parentId) {
+        if (parentId == null || parentId == 0L) {
+            return productTreeRoots();
+        }
+        List<ProductNode> nodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .eq(ProductNode::getParentId, parentId)
+                .orderByAsc(ProductNode::getSortNo).orderByAsc(ProductNode::getId));
+        return mapProductTreeNodes(nodes, Collections.emptySet());
+    }
+
     public List<Map<String, Object>> productTree() {
         List<ProductNode> nodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
-                .orderByAsc(ProductNode::getNodeLevel).orderByAsc(ProductNode::getSortNo));
+                .orderByAsc(ProductNode::getId).orderByAsc(ProductNode::getSortNo));
+        Map<Long, ProductNode> unique = new LinkedHashMap<>();
+        for (ProductNode node : nodes) {
+            if (node.getId() == null || "1".equals(node.getIsDelete())) {
+                continue;
+            }
+            unique.putIfAbsent(node.getId(), node);
+        }
+        nodes = new ArrayList<>(unique.values());
+        nodes.sort(Comparator.comparingInt(this::productNodeLevel)
+                .thenComparing(ProductNode::getSortNo, Comparator.nullsLast(Integer::compareTo))
+                .thenComparing(ProductNode::getId, Comparator.nullsLast(Long::compareTo)));
         Map<Long, Map<String, Object>> map = new LinkedHashMap<>();
         for (ProductNode node : nodes) {
             map.put(node.getId(), mapProduct(node));
@@ -284,24 +500,48 @@
 
     @Transactional
     public ProductNode saveProductNode(ProductNode node) {
-        if (!StringUtils.hasText(node.getName()) || node.getNodeLevel() == null) {
-            throw new BusinessException("鑺傜偣鍚嶇О鍜屽眰绾т笉鑳戒负绌�");
+        if (!StringUtils.hasText(node.getName())) {
+            throw new BusinessException("鑺傜偣鍚嶇О涓嶈兘涓虹┖");
         }
         LocalDateTime now = LocalDateTime.now();
-        node.setUpdateTime(now);
+        String layer = resolveProductLayer(node);
+        node.setLayer(layer);
+        node.setConnectGroup(buildConnectGroup(node.getParentId()));
+
         if (node.getId() == null) {
+            node.setId(IdWorker.getId());
+            node.setRecordId(String.valueOf(node.getId()));
+            node.setProject(StringUtils.hasText(node.getProject()) ? node.getProject().trim() : "0");
+            node.setSortNo(node.getSortNo() == null ? 1 : node.getSortNo());
+            node.setIsDelete("0");
+            node.setCreateBy(StringUtils.hasText(node.getCreateBy()) ? node.getCreateBy().trim() : "system");
             node.setCreateTime(now);
             productNodeMapper.insert(node);
         } else {
+            ProductNode current = findProductNode(node.getId());
+            if (current == null) {
+                throw new BusinessException("鑺傜偣涓嶅瓨鍦�");
+            }
+            node.setRecordId(current.getRecordId());
+            node.setProject(StringUtils.hasText(current.getProject()) ? current.getProject() : "0");
+            node.setIsDelete(StringUtils.hasText(current.getIsDelete()) ? current.getIsDelete() : "0");
+            node.setCreateBy(current.getCreateBy());
+            node.setCreateTime(current.getCreateTime());
             productNodeMapper.updateById(node);
         }
-        return productNodeMapper.selectById(node.getId());
+        return findProductNode(node.getId());
     }
 
     @Transactional
     public void deleteProductNode(Long id) {
-        assertNoChildren(productNodeMapper.selectCount(Wrappers.<ProductNode>lambdaQuery().eq(ProductNode::getParentId, id)));
-        productNodeMapper.deleteById(id);
+        assertNoChildren(productNodeMapper.selectCount(Wrappers.<ProductNode>lambdaQuery()
+                .eq(ProductNode::getParentId, id).ne(ProductNode::getIsDelete, "1")));
+        ProductNode current = findProductNode(id);
+        if (current == null) {
+            throw new BusinessException("鑺傜偣涓嶅瓨鍦�");
+        }
+        current.setIsDelete("1");
+        productNodeMapper.updateById(current);
     }
 
     @Transactional
@@ -352,19 +592,529 @@
         }
         roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getRoleId, roleId));
         tenantMenuMapper.delete(Wrappers.<SysTenantMenu>lambdaQuery().eq(SysTenantMenu::getTenantId, tenantId));
-        for (Long menuId : roleMenuIds == null ? Collections.<Long>emptyList() : roleMenuIds) {
+        List<Long> effectiveRoleMenuIds = expandWithAncestors(roleMenuIds);
+        List<Long> effectiveTenantMenuIds = expandWithAncestors(tenantMenuIds);
+        for (Long menuId : effectiveRoleMenuIds) {
             SysRoleMenu relation = new SysRoleMenu();
             relation.setRoleId(roleId);
             relation.setMenuId(menuId);
             relation.setCreateTime(LocalDateTime.now());
             roleMenuMapper.insert(relation);
         }
-        for (Long menuId : tenantMenuIds == null ? Collections.<Long>emptyList() : tenantMenuIds) {
+        for (Long menuId : effectiveTenantMenuIds) {
             SysTenantMenu relation = new SysTenantMenu();
             relation.setTenantId(tenantId);
             relation.setMenuId(menuId);
             relation.setCreateTime(LocalDateTime.now());
             tenantMenuMapper.insert(relation);
+        }
+    }
+
+    public Map<String, Object> userPage(String keyword, Long deptId, Long roleId, int current, int size) {
+        List<Object> args = new ArrayList<>();
+        StringBuilder sql = new StringBuilder();
+        sql.append("SELECT u.USER_ID, u.USER_NAME, u.NICK_NAME, u.DEPT_ID, d.DEPT_NAME, u.PHONENUMBER, u.STATUS, u.LOGIN_DATE ");
+        sql.append("FROM CSICZB_ZB.SYS_USER u LEFT JOIN CSICZB_ZB.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID ");
+        sql.append("WHERE (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL) ");
+        if (StringUtils.hasText(keyword)) {
+            sql.append("AND (u.USER_NAME LIKE ? OR u.NICK_NAME LIKE ?) ");
+            String like = "%" + keyword.trim() + "%";
+            args.add(like);
+            args.add(like);
+        }
+        if (deptId != null) {
+            sql.append("AND u.DEPT_ID = ? ");
+            args.add(deptId);
+        }
+        if (roleId != null) {
+            sql.append("AND EXISTS (SELECT 1 FROM LX.T_SYS_ROLE_USER ru WHERE ru.USER_ID = u.USER_ID AND ru.ROLE_ID = ? AND ru.DEL_FLAG = 0) ");
+            args.add(roleId);
+        }
+        sql.append("ORDER BY u.USER_ID");
+        List<Map<String, Object>> all = jdbcTemplate.query(sql.toString(), args.toArray(), (rs, rowNum) -> {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put("userId", String.valueOf(rs.getLong("USER_ID")));
+            row.put("userName", rs.getString("USER_NAME"));
+            row.put("nickName", rs.getString("NICK_NAME"));
+            Object dept = rs.getObject("DEPT_ID");
+            row.put("deptId", dept == null ? null : String.valueOf(rs.getLong("DEPT_ID")));
+            row.put("deptName", rs.getString("DEPT_NAME"));
+            row.put("phoneNumber", rs.getString("PHONENUMBER"));
+            row.put("status", rs.getString("STATUS"));
+            java.sql.Timestamp loginDate = rs.getTimestamp("LOGIN_DATE");
+            row.put("loginDate", loginDate == null ? null : loginDate.toLocalDateTime().toString().replace('T', ' '));
+            row.put("roles", new ArrayList<Map<String, Object>>());
+            return row;
+        });
+        Map<Long, Map<String, Object>> byId = new HashMap<>();
+        for (Map<String, Object> row : all) {
+            byId.put(Long.valueOf(String.valueOf(row.get("userId"))), row);
+        }
+        if (!byId.isEmpty()) {
+            List<Map<String, Object>> roleRows = jdbcTemplate.query(
+                    "SELECT ru.USER_ID, r.ID, r.NAME, r.CODE, r.ROLE_TYPE FROM LX.T_SYS_ROLE_USER ru JOIN LX.T_SYS_ROLE r ON r.ID = ru.ROLE_ID AND r.DEL_FLAG = 0 WHERE ru.DEL_FLAG = 0 ORDER BY r.SORT_NO",
+                    (rs, rowNum) -> {
+                        Map<String, Object> role = new LinkedHashMap<>();
+                        role.put("userId", String.valueOf(rs.getLong("USER_ID")));
+                        role.put("roleId", String.valueOf(rs.getLong("ID")));
+                        role.put("roleName", rs.getString("NAME"));
+                        role.put("roleCode", rs.getString("CODE"));
+                        role.put("roleType", rs.getString("ROLE_TYPE"));
+                        return role;
+                    });
+            for (Map<String, Object> role : roleRows) {
+                Map<String, Object> user = byId.get(Long.valueOf(String.valueOf(role.get("userId"))));
+                if (user != null) {
+                    @SuppressWarnings("unchecked")
+                    List<Map<String, Object>> roles = (List<Map<String, Object>>) user.get("roles");
+                    roles.add(role);
+                }
+            }
+        }
+        int safeSize = size <= 0 ? 20 : size;
+        int safeCurrent = current <= 0 ? 1 : current;
+        int total = all.size();
+        int from = Math.min(total, (safeCurrent - 1) * safeSize);
+        int to = Math.min(total, from + safeSize);
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("records", new ArrayList<>(all.subList(from, to)));
+        result.put("total", total);
+        result.put("current", safeCurrent);
+        result.put("size", safeSize);
+        result.put("pages", (total + safeSize - 1) / safeSize);
+        return result;
+    }
+
+    public List<Map<String, Object>> depts() {
+        return jdbcTemplate.query(
+                "SELECT DEPT_ID, DEPT_NAME, PARENT_ID, ORDER_NUM, DEPT_TYPE, LEADER, PHONE, STATUS, DEPT_CODE FROM CSICZB_ZB.SYS_DEPT WHERE (DEL_FLAG = '0' OR DEL_FLAG IS NULL) ORDER BY ORDER_NUM, DEPT_ID",
+                (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("id", String.valueOf(rs.getLong("DEPT_ID")));
+                    row.put("name", rs.getString("DEPT_NAME"));
+                    Object parentId = rs.getObject("PARENT_ID");
+                    row.put("parentId", parentId == null ? null : String.valueOf(rs.getLong("PARENT_ID")));
+                    row.put("orderNum", rs.getObject("ORDER_NUM"));
+                    row.put("deptType", rs.getString("DEPT_TYPE"));
+                    row.put("leader", rs.getString("LEADER"));
+                    row.put("phone", rs.getString("PHONE"));
+                    row.put("status", rs.getString("STATUS"));
+                    row.put("deptCode", rs.getString("DEPT_CODE"));
+                    return row;
+                });
+    }
+
+    public List<String> userRoleIds(Long userId) {
+        if (userId == null) {
+            return new ArrayList<>();
+        }
+        return roleUserMapper.selectList(Wrappers.<SysRoleUser>lambdaQuery()
+                        .eq(SysRoleUser::getUserId, userId))
+                .stream().map(item -> String.valueOf(item.getRoleId())).collect(Collectors.toList());
+    }
+
+    public Map<String, Object> roleUsers(Long roleId) {
+        if (roleId == null || roleMapper.selectById(roleId) == null) {
+            throw new BusinessException("瑙掕壊涓嶅瓨鍦�");
+        }
+        Set<Long> assignedUserIds = roleUserMapper.selectList(Wrappers.<SysRoleUser>lambdaQuery()
+                        .eq(SysRoleUser::getRoleId, roleId))
+                .stream().map(SysRoleUser::getUserId).collect(Collectors.toCollection(LinkedHashSet::new));
+        List<Map<String, Object>> users = jdbcTemplate.query(
+                "SELECT u.USER_ID, u.USER_NAME, u.NICK_NAME, u.DEPT_ID, d.DEPT_NAME, u.STATUS " +
+                        "FROM CSICZB_ZB.SYS_USER u LEFT JOIN CSICZB_ZB.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID " +
+                        "WHERE (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL) ORDER BY u.USER_ID",
+                (rs, rowNum) -> {
+                    Long userId = rs.getLong("USER_ID");
+                    Map<String, Object> user = new LinkedHashMap<>();
+                    user.put("userId", String.valueOf(userId));
+                    user.put("userName", rs.getString("USER_NAME"));
+                    user.put("nickName", rs.getString("NICK_NAME"));
+                    Object deptId = rs.getObject("DEPT_ID");
+                    user.put("deptId", deptId == null ? null : String.valueOf(rs.getLong("DEPT_ID")));
+                    user.put("deptName", rs.getString("DEPT_NAME"));
+                    user.put("status", rs.getString("STATUS"));
+                    user.put("assigned", assignedUserIds.contains(userId));
+                    return user;
+                });
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("records", users);
+        result.put("userIds", assignedUserIds.stream().map(String::valueOf).collect(Collectors.toList()));
+        result.put("total", users.size());
+        return result;
+    }
+
+    @Transactional
+    public void saveRoleUsers(Long roleId, List<Long> userIds) {
+        if (roleId == null || roleMapper.selectById(roleId) == null) {
+            throw new BusinessException("瑙掕壊涓嶅瓨鍦�");
+        }
+        Set<Long> distinct = new LinkedHashSet<>();
+        if (userIds != null) {
+            distinct.addAll(userIds);
+        }
+        distinct.remove(null);
+        for (Long userId : distinct) {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                    Integer.class, userId);
+            if (exists == null || exists == 0) {
+                throw new BusinessException("鐢ㄦ埛涓嶅瓨鍦�");
+            }
+        }
+        roleUserMapper.delete(Wrappers.<SysRoleUser>lambdaQuery().eq(SysRoleUser::getRoleId, roleId));
+        for (Long userId : distinct) {
+            SysRoleUser relation = new SysRoleUser();
+            relation.setRoleId(roleId);
+            relation.setUserId(userId);
+            relation.setCreateTime(LocalDateTime.now());
+            roleUserMapper.insert(relation);
+        }
+    }
+
+    @Transactional
+    public void saveUserRoles(Long userId, List<Long> roleIds) {
+        if (userId == null) {
+            throw new BusinessException("璇烽�夋嫨鐢ㄦ埛");
+        }
+        Integer exists = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                Integer.class, userId);
+        if (exists == null || exists == 0) {
+            throw new BusinessException("鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        Set<Long> distinct = new LinkedHashSet<>();
+        if (roleIds != null) {
+            distinct.addAll(roleIds);
+        }
+        for (Long roleId : distinct) {
+            if (roleId != null && roleMapper.selectById(roleId) == null) {
+                throw new BusinessException("瑙掕壊涓嶅瓨鍦�");
+            }
+        }
+        roleUserMapper.delete(Wrappers.<SysRoleUser>lambdaQuery().eq(SysRoleUser::getUserId, userId));
+        for (Long roleId : distinct) {
+            if (roleId == null) {
+                continue;
+            }
+            SysRoleUser relation = new SysRoleUser();
+            relation.setRoleId(roleId);
+            relation.setUserId(userId);
+            relation.setCreateTime(LocalDateTime.now());
+            roleUserMapper.insert(relation);
+        }
+    }
+
+    @Transactional
+    public void saveUserRolesBatch(List<Long> userIds, List<Long> roleIds, String mode) {
+        Set<Long> distinctUsers = new LinkedHashSet<>();
+        if (userIds != null) {
+            distinctUsers.addAll(userIds);
+        }
+        distinctUsers.remove(null);
+        if (distinctUsers.isEmpty()) {
+            throw new BusinessException("\u8bf7\u9009\u62e9\u7528\u6237");
+        }
+        Set<Long> distinctRoles = new LinkedHashSet<>();
+        if (roleIds != null) {
+            distinctRoles.addAll(roleIds);
+        }
+        distinctRoles.remove(null);
+        for (Long roleId : distinctRoles) {
+            if (roleMapper.selectById(roleId) == null) {
+                throw new BusinessException("\u89d2\u8272\u4e0d\u5b58\u5728");
+            }
+        }
+        boolean append = !"replace".equalsIgnoreCase(mode == null ? "" : mode.trim());
+        for (Long userId : distinctUsers) {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                    Integer.class, userId);
+            if (exists == null || exists == 0) {
+                throw new BusinessException("\u7528\u6237\u4e0d\u5b58\u5728");
+            }
+            Set<Long> targetRoleIds = new LinkedHashSet<>();
+            if (append) {
+                targetRoleIds.addAll(roleUserMapper.selectList(Wrappers.<SysRoleUser>lambdaQuery()
+                                .eq(SysRoleUser::getUserId, userId))
+                        .stream().map(SysRoleUser::getRoleId).collect(Collectors.toList()));
+            }
+            targetRoleIds.addAll(distinctRoles);
+            roleUserMapper.delete(Wrappers.<SysRoleUser>lambdaQuery().eq(SysRoleUser::getUserId, userId));
+            for (Long roleId : targetRoleIds) {
+                SysRoleUser relation = new SysRoleUser();
+                relation.setRoleId(roleId);
+                relation.setUserId(userId);
+                relation.setCreateTime(LocalDateTime.now());
+                roleUserMapper.insert(relation);
+            }
+        }
+    }
+
+    public List<String> roleCodes(String username) {
+        Long userId = resolveUserId(username);
+        if (userId == null) {
+            return new ArrayList<>();
+        }
+        List<Long> roleIds = roleUserMapper.selectList(Wrappers.<SysRoleUser>lambdaQuery()
+                        .eq(SysRoleUser::getUserId, userId))
+                .stream().map(SysRoleUser::getRoleId).collect(Collectors.toList());
+        if (roleIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+        return roleMapper.selectBatchIds(roleIds).stream()
+                .map(SysRole::getCode).filter(StringUtils::hasText).sorted().collect(Collectors.toList());
+    }
+
+    public Set<Long> effectiveMenuIdsForUser(String username) {
+        Set<Long> result = new LinkedHashSet<>();
+        if (!StringUtils.hasText(username)) {
+            return result;
+        }
+        if ("admin".equals(username)) {
+            listMenus().forEach(menu -> result.add(menu.getId()));
+            return result;
+        }
+        Long userId = resolveUserId(username);
+        if (userId == null) {
+            return result;
+        }
+        List<Long> roleIds = roleUserMapper.selectList(Wrappers.<SysRoleUser>lambdaQuery()
+                        .eq(SysRoleUser::getUserId, userId))
+                .stream().map(SysRoleUser::getRoleId).collect(Collectors.toList());
+        if (roleIds.isEmpty()) {
+            return result;
+        }
+        Set<Long> roleMenuIds = new LinkedHashSet<>(roleMenuMapper.selectList(Wrappers.<SysRoleMenu>lambdaQuery()
+                        .in(SysRoleMenu::getRoleId, roleIds))
+                .stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList()));
+        Set<Long> tenantMenuIds = new LinkedHashSet<>(tenantMenuMapper.selectList(Wrappers.<SysTenantMenu>lambdaQuery()
+                        .eq(SysTenantMenu::getTenantId, 1L))
+                .stream().map(SysTenantMenu::getMenuId).collect(Collectors.toList()));
+        if (tenantMenuIds.isEmpty()) {
+            result.addAll(roleMenuIds);
+        } else {
+            for (Long menuId : roleMenuIds) {
+                if (tenantMenuIds.contains(menuId)) {
+                    result.add(menuId);
+                }
+            }
+        }
+        return result;
+    }
+
+    public List<SysMenu> menuTreeForUser(String username) {
+        Set<Long> allowed = new LinkedHashSet<>(effectiveMenuIdsForUser(username));
+        if (allowed.isEmpty() && !"admin".equals(username)) {
+            SysMenu dashboard = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+                    .eq(SysMenu::getUrl, "/dashboard").last("LIMIT 1"));
+            if (dashboard != null) {
+                allowed.add(dashboard.getId());
+            }
+        }
+        final Set<Long> allowedIds = allowed;
+        List<SysMenu> navigable = listMenus().stream()
+                .filter(menu -> menu.getMenuType() == null || menu.getMenuType() != 2)
+                .filter(menu -> allowedIds.contains(menu.getId()))
+                .collect(Collectors.toList());
+        return buildTree(navigable);
+    }
+
+    public List<String> permissionCodes(String username) {
+        Set<Long> menuIds = effectiveMenuIdsForUser(username);
+        return listMenus().stream()
+                .filter(menu -> menuIds.contains(menu.getId()))
+                .map(SysMenu::getPermissions)
+                .filter(StringUtils::hasText)
+                .distinct()
+                .sorted()
+                .collect(Collectors.toList());
+    }
+
+    public Map<String, Object> menuButtons(Long menuId) {
+        SysMenu menu = menuMapper.selectById(menuId);
+        if (menu == null) {
+            throw new BusinessException("鑿滃崟涓嶅瓨鍦�");
+        }
+        List<Map<String, Object>> available = new ArrayList<>();
+        for (ButtonPermissionCatalog.ButtonDefinition definition : buttonPermissionCatalog.buttonsFor(menu.getUrl())) {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put("code", definition.getCode());
+            row.put("name", definition.getName());
+            available.add(row);
+        }
+        List<String> selected = menuMapper.selectList(Wrappers.<SysMenu>lambdaQuery()
+                        .eq(SysMenu::getPid, menuId)
+                        .eq(SysMenu::getMenuType, 2))
+                .stream().map(SysMenu::getPermissions).filter(StringUtils::hasText).collect(Collectors.toList());
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("menuId", String.valueOf(menuId));
+        result.put("menuName", menu.getName());
+        result.put("available", available);
+        result.put("selected", selected);
+        return result;
+    }
+
+    @Transactional
+    public void syncMenuButtons(Long menuId, List<String> codes) {
+        SysMenu menu = menuMapper.selectById(menuId);
+        if (menu == null) {
+            throw new BusinessException("鑿滃崟涓嶅瓨鍦�");
+        }
+        if (menu.getMenuType() == null || menu.getMenuType() != 1) {
+            throw new BusinessException("鍙湁鑿滃崟绫诲瀷鎵嶈兘閰嶇疆鎸夐挳");
+        }
+        List<ButtonPermissionCatalog.ButtonDefinition> definitions = buttonPermissionCatalog.buttonsFor(menu.getUrl());
+        Map<String, ButtonPermissionCatalog.ButtonDefinition> definitionMap = new LinkedHashMap<>();
+        for (ButtonPermissionCatalog.ButtonDefinition definition : definitions) {
+            definitionMap.put(definition.getCode(), definition);
+        }
+        Set<String> wanted = new LinkedHashSet<>();
+        if (codes != null) {
+            for (String code : codes) {
+                if (StringUtils.hasText(code) && definitionMap.containsKey(code)) {
+                    wanted.add(code);
+                }
+            }
+        }
+        List<SysMenu> existing = menuMapper.selectList(Wrappers.<SysMenu>lambdaQuery()
+                .eq(SysMenu::getPid, menuId).eq(SysMenu::getMenuType, 2));
+        Map<String, SysMenu> existingByCode = new HashMap<>();
+        for (SysMenu child : existing) {
+            existingByCode.put(child.getPermissions(), child);
+            if (!wanted.contains(child.getPermissions())) {
+                menuMapper.deleteById(child.getId());
+                roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getMenuId, child.getId()));
+                tenantMenuMapper.delete(Wrappers.<SysTenantMenu>lambdaQuery().eq(SysTenantMenu::getMenuId, child.getId()));
+            }
+        }
+        LocalDateTime now = LocalDateTime.now();
+        for (int index = 0; index < definitions.size(); index++) {
+            ButtonPermissionCatalog.ButtonDefinition definition = definitions.get(index);
+            if (!wanted.contains(definition.getCode())) {
+                continue;
+            }
+            SysMenu child = existingByCode.get(definition.getCode());
+            if (child == null) {
+                child = new SysMenu();
+                child.setPid(menuId);
+                child.setName(definition.getName());
+                child.setPermissions(definition.getCode());
+                child.setMenuType(2);
+                child.setSortNo((index + 1) * 10);
+                child.setVisible(1);
+                child.setCreateTime(now);
+                child.setUpdateTime(now);
+                menuMapper.insert(child);
+            } else {
+                child.setName(definition.getName());
+                child.setSortNo((index + 1) * 10);
+                child.setUpdateTime(now);
+                menuMapper.updateById(child);
+            }
+        }
+    }
+
+    public Page<SysLoginLog> loginLogPage(int current, int size, String keyword, String status, String startDate, String endDate) {
+        Page<SysLoginLog> page = new Page<>(current <= 0 ? 1 : current, size <= 0 ? 20 : size);
+        LambdaQueryWrapper<SysLoginLog> wrapper = Wrappers.lambdaQuery();
+        if (StringUtils.hasText(keyword)) {
+            String value = keyword.trim();
+            wrapper.and(item -> item.like(SysLoginLog::getUserName, value).or().like(SysLoginLog::getNickName, value));
+        }
+        if (StringUtils.hasText(status)) {
+            wrapper.eq(SysLoginLog::getStatus, status.trim());
+        }
+        LocalDateTime start = parseStart(startDate);
+        LocalDateTime end = parseEnd(endDate);
+        if (start != null) {
+            wrapper.ge(SysLoginLog::getLoginTime, start);
+        }
+        if (end != null) {
+            wrapper.le(SysLoginLog::getLoginTime, end);
+        }
+        wrapper.orderByDesc(SysLoginLog::getLoginTime);
+        return loginLogMapper.selectPage(page, wrapper);
+    }
+
+    public Page<SysOperationLog> operationLogPage(int current, int size, String keyword, String module, String operationType, String startDate, String endDate) {
+        Page<SysOperationLog> page = new Page<>(current <= 0 ? 1 : current, size <= 0 ? 20 : size);
+        LambdaQueryWrapper<SysOperationLog> wrapper = Wrappers.lambdaQuery();
+        if (StringUtils.hasText(keyword)) {
+            String value = keyword.trim();
+            wrapper.and(item -> item.like(SysOperationLog::getUserName, value)
+                    .or().like(SysOperationLog::getNickName, value)
+                    .or().like(SysOperationLog::getRequestUri, value));
+        }
+        if (StringUtils.hasText(module)) {
+            wrapper.eq(SysOperationLog::getModule, module.trim());
+        }
+        if (StringUtils.hasText(operationType)) {
+            wrapper.eq(SysOperationLog::getOperationType, operationType.trim());
+        }
+        LocalDateTime start = parseStart(startDate);
+        LocalDateTime end = parseEnd(endDate);
+        if (start != null) {
+            wrapper.ge(SysOperationLog::getCreateTime, start);
+        }
+        if (end != null) {
+            wrapper.le(SysOperationLog::getCreateTime, end);
+        }
+        wrapper.orderByDesc(SysOperationLog::getCreateTime);
+        return operationLogMapper.selectPage(page, wrapper);
+    }
+
+    public Long resolveUserId(String username) {
+        if (!StringUtils.hasText(username)) {
+            return null;
+        }
+        List<Long> ids = jdbcTemplate.query(
+                "SELECT USER_ID FROM CSICZB_ZB.SYS_USER WHERE USER_NAME = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                new Object[]{username}, (rs, rowNum) -> rs.getLong(1));
+        return ids.isEmpty() ? null : ids.get(0);
+    }
+
+    private List<Long> expandWithAncestors(List<Long> ids) {
+        Set<Long> result = new LinkedHashSet<>();
+        if (ids == null) {
+            return new ArrayList<>(result);
+        }
+        for (Long id : ids) {
+            Long cursor = id;
+            int guard = 0;
+            while (cursor != null && cursor != 0L && guard++ < 32) {
+                if (!result.add(cursor)) {
+                    break;
+                }
+                SysMenu menu = menuMapper.selectById(cursor);
+                if (menu == null) {
+                    break;
+                }
+                cursor = menu.getPid();
+            }
+        }
+        return new ArrayList<>(result);
+    }
+
+    private LocalDateTime parseStart(String value) {
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        try {
+            return LocalDate.parse(value.trim()).atStartOfDay();
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    private LocalDateTime parseEnd(String value) {
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        try {
+            return LocalDate.parse(value.trim()).atTime(23, 59, 59);
+        } catch (Exception ex) {
+            return null;
         }
     }
 
@@ -394,20 +1144,70 @@
     }
 
     private Map<String, Object> mapProduct(ProductNode node) {
+        return mapProduct(node, Collections.emptySet());
+    }
+
+    private Map<String, Object> mapProduct(ProductNode node, Set<Long> branchIds) {
         Map<String, Object> row = new LinkedHashMap<>();
         row.put("id", String.valueOf(node.getId()));
         row.put("parentId", node.getParentId() == null ? null : String.valueOf(node.getParentId()));
         row.put("nodeLevel", node.getNodeLevel());
         row.put("nodeType", node.getNodeType());
+        row.put("layer", node.getLayer());
+        row.put("connectGroup", node.getConnectGroup());
         row.put("name", node.getName());
         row.put("code", node.getCode());
         row.put("baseEquipmentId", node.getBaseEquipmentId() == null ? null : String.valueOf(node.getBaseEquipmentId()));
+        row.put("project", node.getProject());
         row.put("sortNo", node.getSortNo());
         row.put("remark", node.getRemark());
+        row.put("isLeaf", !branchIds.contains(node.getId()));
         row.put("children", new ArrayList<>());
         return row;
     }
 
+    private int productNodeLevel(ProductNode node) {
+        Integer level = node.getNodeLevel();
+        return level == null ? Integer.MAX_VALUE : level;
+    }
+
+    private String resolveProductLayer(ProductNode node) {
+        String layer = ProductNode.layerForType(node.getNodeType());
+        if (!StringUtils.hasText(layer)) {
+            layer = ProductNode.layerForLevel(node.getNodeLevel());
+        }
+        if (!StringUtils.hasText(layer)) {
+            layer = StringUtils.hasText(node.getLayer()) ? node.getLayer().trim().toLowerCase() : null;
+        }
+        if (!StringUtils.hasText(layer)) {
+            throw new BusinessException("鑺傜偣灞傜骇涓嶈兘涓虹┖");
+        }
+        return layer;
+    }
+
+    private String buildConnectGroup(Long parentId) {
+        if (parentId == null || parentId == 0L) {
+            return "0";
+        }
+        ProductNode parent = findProductNode(parentId);
+        if (parent == null) {
+            throw new BusinessException("鐖惰妭鐐逛笉瀛樺湪");
+        }
+        String prefix = StringUtils.hasText(parent.getConnectGroup()) ? parent.getConnectGroup() : "0";
+        return prefix + "," + parent.getId();
+    }
+
+    private ProductNode findProductNode(Long id) {
+        if (id == null) {
+            return null;
+        }
+        List<ProductNode> nodes = productNodeMapper.selectList(Wrappers.<ProductNode>lambdaQuery()
+                .eq(ProductNode::getId, id)
+                .orderByAsc(ProductNode::getRecordId)
+                .last("LIMIT 1"));
+        return nodes.isEmpty() ? null : nodes.get(0);
+    }
+
     private Map<String, Object> mapBase(BaseEquipment node) {
         Map<String, Object> row = new LinkedHashMap<>();
         row.put("id", String.valueOf(node.getId()));
diff --git a/emergency-repair-server/src/main/resources/application.yml b/emergency-repair-server/src/main/resources/application.yml
index 3141a2e..f7a6b89 100644
--- a/emergency-repair-server/src/main/resources/application.yml
+++ b/emergency-repair-server/src/main/resources/application.yml
@@ -1,3 +1,5 @@
+fault-code:
+  prefix: "鍥哄畾鍓嶇紑-"
 server:
   port: 8091
   servlet:
diff --git a/emergency-repair-server/src/main/resources/mapper/ReportMapper.xml b/emergency-repair-server/src/main/resources/mapper/ReportMapper.xml
index 893c1ec..ace6e62 100644
--- a/emergency-repair-server/src/main/resources/mapper/ReportMapper.xml
+++ b/emergency-repair-server/src/main/resources/mapper/ReportMapper.xml
@@ -52,14 +52,16 @@
                    NVL(SUM(CASE WHEN IS_H_RELATED = 1 THEN 1 ELSE 0 END), 0) AS H_RELATED,
                    NVL(SUM(CASE WHEN IS_MONITOR_ITEM = 1 THEN 1 ELSE 0 END), 0) AS MONITOR_ITEM,
                    NVL(SUM(CASE WHEN TASK_STATUS = 'CANCELLED' THEN 1 ELSE 0 END), 0) AS CANCELLED
-            FROM LX.T_FAULT
-        ) f
-        CROSS JOIN (
-            SELECT NVL(SUM(CASE WHEN COMPLETION_STATUS IS NULL OR COMPLETION_STATUS &lt;&gt; 'COMPLETED' THEN 1 ELSE 0 END), 0) AS UNFINISHED,
-                   NVL(SUM(CASE WHEN COMPLETION_STATUS = 'COMPLETED' THEN 1 ELSE 0 END), 0) AS COMPLETED,
-                   NVL(SUM(CASE WHEN COMPLETION_STATUS = 'WAIT_TEST' THEN 1 ELSE 0 END), 0) AS WAIT_TEST
-            FROM LX.T_WORK_ORDER
-        ) w
+             FROM LX.T_FAULT
+             WHERE DEL_FLAG = 0
+         ) f
+         CROSS JOIN (
+             SELECT NVL(SUM(CASE WHEN COMPLETION_STATUS IS NULL OR COMPLETION_STATUS &lt;&gt; 'COMPLETED' THEN 1 ELSE 0 END), 0) AS UNFINISHED,
+                    NVL(SUM(CASE WHEN COMPLETION_STATUS = 'COMPLETED' THEN 1 ELSE 0 END), 0) AS COMPLETED,
+                    NVL(SUM(CASE WHEN COMPLETION_STATUS = 'WAIT_TEST' THEN 1 ELSE 0 END), 0) AS WAIT_TEST
+             FROM LX.T_WORK_ORDER
+             WHERE DEL_FLAG = 0
+         ) w
     </select>
 
     <select id="selectTopEquipment" resultMap="metricResultMap">
@@ -76,9 +78,10 @@
                         ',' || CAST(p.ID AS VARCHAR(40)) || ',') &gt; 0
                   OR (NVL(f.EQUIPMENT_IDS, '') = '' AND f.EQUIPMENT_ID = p.ID)
              )
-            JOIN LX.T_BASE_EQUIPMENT b ON b.ID = p.BASE_EQUIPMENT_ID
-        ),
-        splitter AS (
+             JOIN LX.T_BASE_EQUIPMENT b ON b.ID = p.BASE_EQUIPMENT_ID
+             WHERE f.DEL_FLAG = 0 AND p.DEL_FLAG = 0 AND b.DEL_FLAG = 0
+         ),
+         splitter AS (
             SELECT LEVEL AS N
             FROM DUAL
             CONNECT BY LEVEL &lt;= (
@@ -86,10 +89,11 @@
                     REPLACE(REPLACE(REPLACE(NVL(EQUIPMENT_NAME, ''), '锛�', '銆�'), ',', '銆�'), CHR(10), '銆�'),
                     '銆�'
                 )), 0) + 1
-                FROM LX.T_FAULT
-            )
-        ),
-        fallback_names AS (
+                 FROM LX.T_FAULT
+                 WHERE DEL_FLAG = 0
+             )
+         ),
+         fallback_names AS (
             SELECT f.ID AS FAULT_ID,
                    NVL(TRIM(REGEXP_SUBSTR(
                        REPLACE(REPLACE(REPLACE(NVL(f.EQUIPMENT_NAME, ''), '锛�', '銆�'), ',', '銆�'), CHR(10), '銆�'),
@@ -102,26 +106,29 @@
               ON sp.N &lt;= REGEXP_COUNT(
                   REPLACE(REPLACE(REPLACE(NVL(f.EQUIPMENT_NAME, ''), '锛�', '銆�'), ',', '銆�'), CHR(10), '銆�'),
                   '銆�'
-              ) + 1
-        ),
-        fallback_link AS (
+               ) + 1
+             WHERE f.DEL_FLAG = 0
+         ),
+         fallback_link AS (
             SELECT DISTINCT fn.FAULT_ID,
                             b.ID AS BASE_ID,
                             b.NAME AS BASE_NAME
             FROM fallback_names fn
-            JOIN LX.T_FAULT f ON f.ID = fn.FAULT_ID
-            JOIN LX.T_PRODUCT_TREE p5
-              ON p5.NODE_LEVEL = 5
-             AND p5.NAME = fn.EQUIPMENT_NAME
-             AND p5.BASE_EQUIPMENT_ID IS NOT NULL
-            JOIN LX.T_PRODUCT_TREE p4 ON p4.ID = p5.PARENT_ID
-            JOIN LX.T_PRODUCT_TREE p3 ON p3.ID = p4.PARENT_ID
-            JOIN LX.T_PRODUCT_TREE x
-              ON x.ID = p3.PARENT_ID
-             AND x.NODE_LEVEL = 2
-             AND x.NAME = f.X_NO
-            JOIN LX.T_BASE_EQUIPMENT b ON b.ID = p5.BASE_EQUIPMENT_ID
-            WHERE NOT EXISTS (
+             JOIN LX.T_FAULT f ON f.ID = fn.FAULT_ID
+             JOIN LX.T_PRODUCT_TREE p5
+               ON p5.NODE_LEVEL = 5
+              AND p5.NAME = fn.EQUIPMENT_NAME
+              AND p5.BASE_EQUIPMENT_ID IS NOT NULL
+              AND p5.DEL_FLAG = 0
+             JOIN LX.T_PRODUCT_TREE p4 ON p4.ID = p5.PARENT_ID AND p4.DEL_FLAG = 0
+             JOIN LX.T_PRODUCT_TREE p3 ON p3.ID = p4.PARENT_ID AND p3.DEL_FLAG = 0
+             JOIN LX.T_PRODUCT_TREE x
+               ON x.ID = p3.PARENT_ID
+              AND x.NODE_LEVEL = 2
+              AND x.NAME = f.X_NO
+              AND x.DEL_FLAG = 0
+             JOIN LX.T_BASE_EQUIPMENT b ON b.ID = p5.BASE_EQUIPMENT_ID AND b.DEL_FLAG = 0
+             WHERE NOT EXISTS (
                 SELECT 1
                 FROM explicit_link el
                 WHERE el.FAULT_ID = fn.FAULT_ID
@@ -166,27 +173,29 @@
     <select id="selectByTeam" resultMap="teamResultMap">
         WITH fault_agg AS (
             SELECT CASE
-                       WHEN TRIM(TEAM_MAJOR) IS NULL OR TRIM(TEAM_MAJOR) = '' THEN '鏈~鍐橳闃熶笓涓�'
+                       WHEN TRIM(TEAM_MAJOR) IS NULL OR TRIM(TEAM_MAJOR) = '' THEN '鏈~鍐檙k_T闃熶笓涓�'
                        ELSE TRIM(TEAM_MAJOR)
                    END AS NAME,
                    COUNT(*) AS FAULT_COUNT
-            FROM LX.T_FAULT
-            GROUP BY CASE
-                         WHEN TRIM(TEAM_MAJOR) IS NULL OR TRIM(TEAM_MAJOR) = '' THEN '鏈~鍐橳闃熶笓涓�'
+             FROM LX.T_FAULT
+             WHERE DEL_FLAG = 0
+             GROUP BY CASE
+                         WHEN TRIM(TEAM_MAJOR) IS NULL OR TRIM(TEAM_MAJOR) = '' THEN '鏈~鍐檙k_T闃熶笓涓�'
                          ELSE TRIM(TEAM_MAJOR)
                      END
         ),
         parts_agg AS (
             SELECT CASE
-                       WHEN f.TEAM_MAJOR IS NULL OR TRIM(f.TEAM_MAJOR) = '' THEN '鏈~鍐橳闃熶笓涓�'
+                       WHEN f.TEAM_MAJOR IS NULL OR TRIM(f.TEAM_MAJOR) = '' THEN '鏈~鍐檙k_T闃熶笓涓�'
                        ELSE TRIM(f.TEAM_MAJOR)
                    END AS NAME,
                    NVL(SUM(NVL(ci.CONSUMABLE_COUNT, 0)), 0) AS PARTS_COUNT
-            FROM LX.T_COMPLETION_INFO ci
-            JOIN LX.T_WORK_ORDER w ON w.ID = ci.WORK_ORDER_ID
-            LEFT JOIN LX.T_FAULT f ON f.ID = w.FAULT_ID
-            GROUP BY CASE
-                         WHEN f.TEAM_MAJOR IS NULL OR TRIM(f.TEAM_MAJOR) = '' THEN '鏈~鍐橳闃熶笓涓�'
+             FROM LX.T_COMPLETION_INFO ci
+             JOIN LX.T_WORK_ORDER w ON w.ID = ci.WORK_ORDER_ID AND w.DEL_FLAG = 0
+             LEFT JOIN LX.T_FAULT f ON f.ID = w.FAULT_ID AND f.DEL_FLAG = 0
+             WHERE ci.DEL_FLAG = 0
+             GROUP BY CASE
+                         WHEN f.TEAM_MAJOR IS NULL OR TRIM(f.TEAM_MAJOR) = '' THEN '鏈~鍐檙k_T闃熶笓涓�'
                          ELSE TRIM(f.TEAM_MAJOR)
                      END
         ),
@@ -210,8 +219,9 @@
                NVL(SUM(CASE WHEN COMPLETION_STATUS IS NULL OR COMPLETION_STATUS &lt;&gt; 'COMPLETED' THEN 1 ELSE 0 END), 0) AS UNFINISHED_COUNT,
                NVL(SUM(CASE WHEN COMPLETION_STATUS = 'COMPLETED' THEN 1 ELSE 0 END), 0) AS COMPLETED_COUNT,
                NVL(ROUND(AVG(SCORE), 1), 0) AS AVERAGE_SCORE
-        FROM LX.T_WORK_ORDER
-        GROUP BY CONTRACTOR_NAME
+         FROM LX.T_WORK_ORDER
+         WHERE DEL_FLAG = 0
+         GROUP BY CONTRACTOR_NAME
         ORDER BY TOTAL_COUNT DESC, CONTRACTOR_NAME ASC
     </select>
 
@@ -219,18 +229,18 @@
         WITH fault_agg AS (
             SELECT EXTRACT(YEAR FROM REPORT_DATE) AS REPORT_YEAR,
                    COUNT(*) AS FAULT_COUNT
-            FROM LX.T_FAULT
-            WHERE REPORT_DATE IS NOT NULL
-            GROUP BY EXTRACT(YEAR FROM REPORT_DATE)
+             FROM LX.T_FAULT
+             WHERE DEL_FLAG = 0 AND REPORT_DATE IS NOT NULL
+             GROUP BY EXTRACT(YEAR FROM REPORT_DATE)
         ),
         parts_agg AS (
             SELECT EXTRACT(YEAR FROM f.REPORT_DATE) AS REPORT_YEAR,
                    NVL(SUM(NVL(ci.CONSUMABLE_COUNT, 0)), 0) AS PARTS_COUNT
-            FROM LX.T_COMPLETION_INFO ci
-            JOIN LX.T_WORK_ORDER w ON w.ID = ci.WORK_ORDER_ID
-            JOIN LX.T_FAULT f ON f.ID = w.FAULT_ID
-            WHERE f.REPORT_DATE IS NOT NULL
-            GROUP BY EXTRACT(YEAR FROM f.REPORT_DATE)
+             FROM LX.T_COMPLETION_INFO ci
+             JOIN LX.T_WORK_ORDER w ON w.ID = ci.WORK_ORDER_ID AND w.DEL_FLAG = 0
+             JOIN LX.T_FAULT f ON f.ID = w.FAULT_ID AND f.DEL_FLAG = 0
+             WHERE ci.DEL_FLAG = 0 AND f.REPORT_DATE IS NOT NULL
+             GROUP BY EXTRACT(YEAR FROM f.REPORT_DATE)
         ),
         report_years AS (
             SELECT REPORT_YEAR FROM fault_agg
diff --git a/emergency-repair-web/public/logo.png b/emergency-repair-web/public/logo.png
new file mode 100644
index 0000000..096aa68
--- /dev/null
+++ b/emergency-repair-web/public/logo.png
Binary files differ
diff --git a/emergency-repair-web/public/sensitive-words.json b/emergency-repair-web/public/sensitive-words.json
index e3565ce..1c84cef 100644
--- a/emergency-repair-web/public/sensitive-words.json
+++ b/emergency-repair-web/public/sensitive-words.json
@@ -1,49 +1,49 @@
 {
   "sensitive_words": [
     {
-      "codename": "X鍙�",
+      "codename": "rk_X鍙�",
       "real_name": "鍘垮彿",
       "scope": "ui",
       "note": "Development mode displays the codename. Replace real_name only in the deployed production dictionary."
     },
     {
-      "codename": "JC",
+      "codename": "rk_JC",
       "real_name": "JC",
       "scope": "ui",
       "note": "JC淇℃伅 = JC information."
     },
     {
-      "codename": "JCC",
+      "codename": "rk_JCC",
       "real_name": "JCC",
       "scope": "ui",
       "note": "JCC assistant, director, and acceptance roles."
     },
     {
-      "codename": "CMB",
+      "codename": "rk_CMB",
       "real_name": "CMB",
       "scope": "ui",
       "note": "CMB leader approval role."
     },
     {
-      "codename": "T",
-      "real_name": "T",
-      "scope": "ui",
-      "note": "Word root. T闃熶笓涓�, T鍐呮柦宸�, and T鍛� all use the codename in development."
-    },
-    {
-      "codename": "TD",
+      "codename": "rk_TD",
       "real_name": "TD",
       "scope": "ui",
       "note": "Home page statistic column."
     },
     {
-      "codename": "XCB",
+      "codename": "rk_T",
+      "real_name": "T",
+      "scope": "ui",
+      "note": "Word root. T闃熶笓涓�, T鍐呮柦宸�, and T鍛� all use the codename in development."
+    },
+    {
+      "codename": "rk_XCB",
       "real_name": "淇埞鐗�",
       "scope": "ui",
       "note": "XCB assistant review and dispatch."
     },
     {
-      "codename": "H",
+      "codename": "rk_H",
       "real_name": "H",
       "scope": "ui",
       "note": "Development mode displays H only. Replace real_name after production deployment."
@@ -51,6 +51,7 @@
   ],
   "config": {
     "environment": "development",
-    "rule": "Build and deploy the normal development package. After deployment, manually replace the real_name values in the deployed dist/sensitive-words.json file in the production environment. Do not rebuild the frontend after that replacement."
+    "rule": "Build and deploy the normal development package. After deployment, manually replace the real_name values in the deployed dist/sensitive-words.json file in the production environment. Do not rebuild the frontend after that replacement.",
+    "code_pre": "ABC"
   }
 }
diff --git a/emergency-repair-web/src/api/index.js b/emergency-repair-web/src/api/index.js
index 25c25d1..3dc7b8d 100644
--- a/emergency-repair-web/src/api/index.js
+++ b/emergency-repair-web/src/api/index.js
@@ -9,6 +9,8 @@
 api.interceptors.request.use(config => {
   const token = sessionStorage.getItem('token')
   if (token) config.headers.Authorization = 'Bearer ' + token
+  const username = sessionStorage.getItem('username')
+  if (username) config.headers['X-Username'] = username
   return config
 })
 
diff --git a/emergency-repair-web/src/components/MenuNode.vue b/emergency-repair-web/src/components/MenuNode.vue
index 705cc29..3543002 100644
--- a/emergency-repair-web/src/components/MenuNode.vue
+++ b/emergency-repair-web/src/components/MenuNode.vue
@@ -1,12 +1,12 @@
 <template>
-  <el-submenu v-if="item.children && item.children.length" :index="String(item.id)">
+  <el-submenu v-if="item.menuType !== 2 && item.children && item.children.length" :index="String(item.id)">
     <template slot="title">
       <i :class="item.icon || 'el-icon-menu'"></i>
       <span slot="title">{{ item.name }}</span>
     </template>
     <menu-node v-for="child in item.children" :key="child.id" :item="child"></menu-node>
   </el-submenu>
-  <el-menu-item v-else-if="item.url" :index="item.url">
+  <el-menu-item v-else-if="item.menuType !== 2 && item.url" :index="item.url">
     <i :class="item.icon || 'el-icon-tickets'"></i>
     <span slot="title">{{ item.name }}</span>
   </el-menu-item>
diff --git a/emergency-repair-web/src/components/ProductTreePicker.vue b/emergency-repair-web/src/components/ProductTreePicker.vue
index 2e82ab6..e578ed2 100644
--- a/emergency-repair-web/src/components/ProductTreePicker.vue
+++ b/emergency-repair-web/src/components/ProductTreePicker.vue
@@ -7,7 +7,9 @@
           ref="tree"
           :data="tree"
           node-key="id"
-          default-expand-all
+          v-loading="loading"
+          lazy
+          :load="loadNode"
           show-checkbox
           check-strictly
           :filter-node-method="filterNode"
@@ -51,7 +53,7 @@
     selectedIds: { type: Array, default: () => [] }
   },
   data() {
-    return { localVisible: false, keyword: '', tree: [], checkedCount: 0, selectedDevices: [] }
+    return { localVisible: false, keyword: '', tree: [], checkedCount: 0, selectedDevices: [], selectedMap: {}, loading: false }
   },
   computed: {
     treeProps() {
@@ -74,52 +76,71 @@
   methods: {
     load() {
       const lockedXNo = String(this.xNo || '').trim()
-      api.get('/system/tree', { params: { type: 'product' } }).then(res => {
-        const source = res.data || []
-        this.tree = lockedXNo ? this.filterTreeByXNo(source, lockedXNo) : source
+      this.loading = true
+      const ids = (this.selectedIds || []).map(String).filter(Boolean)
+      const treeRequest = lockedXNo
+        ? api.get('/system/tree/product/side-roots', { params: { xNo: lockedXNo } })
+        : api.get('/system/tree/product/roots')
+      const selectedRequest = ids.length
+        ? api.get('/system/tree/product/selected', { params: { ids: ids.join(',') } })
+        : Promise.resolve({ data: [] })
+      Promise.all([treeRequest, selectedRequest]).then(([treeRes, selectedRes]) => {
+        this.tree = treeRes.data || []
+        this.selectedMap = (selectedRes.data || []).reduce((map, node) => {
+          map[String(node.id)] = node
+          return map
+        }, {})
         this.keyword = ''
-        this.$nextTick(this.restoreChecked)
-      })
+        this.$nextTick(() => { this.restoreChecked(); this.syncSelected() })
+      }).finally(() => { this.loading = false })
     },
-    filterTreeByXNo(nodes, xNo) {
-      const result = []
-      ;(nodes || []).forEach(node => {
-        if (Number(node.nodeLevel) === 2) {
-          if (String(node.name || '').trim() === xNo) result.push(node)
-          return
-        }
-        const children = this.filterTreeByXNo(node.children || [], xNo)
-        if (children.length || node.nodeType === 'DEVICE') {
-          result.push(children.length ? { ...node, children } : node)
-        }
-      })
-      return result
+    loadNode(node, resolve) {
+      if (!node || !node.data || !node.data.id) {
+        resolve([])
+        return
+      }
+      api.get('/system/tree/product/children', { params: { parentId: node.data.id } })
+        .then(res => {
+          resolve(res.data || [])
+          this.$nextTick(this.restoreChecked)
+        })
+        .catch(() => resolve([]))
     },
     restoreChecked() {
       if (!this.$refs.tree) return
-      const ids = (this.selectedIds || []).map(String).filter(Boolean)
+      const ids = Object.keys(this.selectedMap).filter(id => Boolean(this.$refs.tree.getNode(id)))
       this.$refs.tree.setCheckedKeys(ids)
       this.syncSelected()
     },
     handleCheck(node) {
       const tree = this.$refs.tree
       const treeNode = tree && tree.getNode(node.id)
+      this.syncSelected()
       if (treeNode && treeNode.checked) {
-        const selected = this.deviceNodes().map(item => this.decorateNode(item))
-        const xNos = Array.from(new Set(selected.map(item => item.xNo).filter(Boolean)))
+        const xNos = Array.from(new Set(this.selectedDevices.map(item => item.xNo).filter(Boolean)))
         if (xNos.length > 1) {
           tree.setChecked(node.id, false, true)
-          this.$message.warning('鍚屼竴鏁呴殰鍙兘閫夋嫨鍚屼竴涓猉鍙蜂笅鐨勮澶�')
+          this.syncSelected()
+          this.$message.warning('鍚屼竴鏁呴殰鍙兘閫夋嫨鍚屼竴涓猺k_X鍙蜂笅鐨勮澶�')
         }
       }
-      this.syncSelected()
     },
     syncSelected() {
-      const selected = this.deviceNodes().map(node => this.decorateNode(node))
-      this.selectedDevices = selected
-      this.checkedCount = selected.length
+      const tree = this.$refs.tree
+      if (!tree) return
+      const selectedMap = { ...this.selectedMap }
+      const checked = tree.getCheckedNodes().filter(node => node.nodeType === 'DEVICE')
+      const checkedIds = new Set(checked.map(node => String(node.id)))
+      Object.keys(selectedMap).forEach(id => {
+        if (tree.getNode(id) && !checkedIds.has(id)) delete selectedMap[id]
+      })
+      checked.forEach(node => { selectedMap[String(node.id)] = this.decorateNode(node) })
+      this.selectedMap = selectedMap
+      this.selectedDevices = Object.values(selectedMap)
+      this.checkedCount = this.selectedDevices.length
     },
     decorateNode(node) {
+      if (!this.$refs.tree) return { ...node }
       const pathNodes = []
       let current = this.$refs.tree.getNode(node.id)
       while (current && current.data) {
@@ -134,8 +155,10 @@
       return { ...node, pathNames: pathNodes.map(item => item.name), parentPathNames, xNo: xNode ? xNode.name : '' }
     },
     removeSelected(device) {
-      if (!this.$refs.tree) return
-      this.$refs.tree.setChecked(device.id, false, true)
+      const selectedMap = { ...this.selectedMap }
+      delete selectedMap[String(device.id)]
+      this.selectedMap = selectedMap
+      if (this.$refs.tree && this.$refs.tree.getNode(device.id)) this.$refs.tree.setChecked(device.id, false, true)
       this.syncSelected()
     },
     deviceNodes() {
@@ -147,12 +170,12 @@
       return (data.name || '').includes(value) || (data.code || '').includes(value)
     },
     confirm() {
-      const nodes = this.deviceNodes()
+      const nodes = this.selectedDevices
       if (!nodes.length) return this.$message.warning('璇烽�夋嫨璁惧')
-      const selected = nodes.map(node => this.decorateNode(node))
+      const selected = nodes
       const xNos = Array.from(new Set(selected.map(item => item.xNo).filter(Boolean)))
       if (xNos.length > 1) {
-        return this.$message.warning('鍚屼竴鏁呴殰鍙兘閫夋嫨鍚屼竴涓猉鍙蜂笅鐨勮澶�')
+        return this.$message.warning('鍚屼竴鏁呴殰鍙兘閫夋嫨鍚屼竴涓猺k_X鍙蜂笅鐨勮澶�')
       }
       this.$emit('select', selected)
       this.localVisible = false
diff --git a/emergency-repair-web/src/components/RepairLogo.vue b/emergency-repair-web/src/components/RepairLogo.vue
index c10deb5..39c40ef 100644
--- a/emergency-repair-web/src/components/RepairLogo.vue
+++ b/emergency-repair-web/src/components/RepairLogo.vue
@@ -1,32 +1,23 @@
 <template>
-  <svg class="repair-logo" viewBox="0 0 30 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
-    <rect class="screwdriver-handle" x="5" y="2" width="3.2" height="6" rx="1.2"/>
-    <path d="M5.7 4.2h1.8"/>
-    <path d="M5.7 6.2h1.8"/>
-    <path d="M6.6 8v7"/>
-    <path d="M5.6 15h2"/>
-    <path d="M5.6 17.5h2"/>
-    <g transform="translate(5 -2) scale(.82)">
-      <path vector-effect="non-scaling-stroke" d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
-    </g>
-  </svg>
+  <img class="repair-logo" :src="logoSrc" alt="涓存姠淇鐞嗙郴缁�">
 </template>
 
 <script>
 export default {
-  name: 'RepairLogo'
+  name: 'RepairLogo',
+  computed: {
+    logoSrc() {
+      return (process.env.BASE_URL || '/') + 'logo.png'
+    }
+  }
 }
 </script>
 
 <style scoped>
 .repair-logo {
   display: block;
-  width: 1.30em;
-  height: 1.03em;
-  overflow: visible;
-}
-.screwdriver-handle {
-  fill: currentColor;
-  fill-opacity: .16;
+  width: 1.2em;
+  height: 1.2em;
+  object-fit: contain;
 }
 </style>
diff --git a/emergency-repair-web/src/layout/AppLayout.vue b/emergency-repair-web/src/layout/AppLayout.vue
index 68af8d7..891bb52 100644
--- a/emergency-repair-web/src/layout/AppLayout.vue
+++ b/emergency-repair-web/src/layout/AppLayout.vue
@@ -65,9 +65,9 @@
 }
 
 const REPORT_TITLES = {
-  overview: '瑁呭鏁呴殰鎬讳綋鍒嗘瀽',
+  overview: '璁惧鏁呴殰鎬讳綋鍒嗘瀽',
   'top-equipment': '鏁呴殰楂樺彂璁惧Top',
-  'by-team': '鎸塗缁熻',
+  'by-team': 'rk_鎸塗缁熻',
   contractors: '鍘傚缁翠慨鎯呭喌',
   trend: '骞村害瓒嬪娍'
 }
@@ -259,9 +259,12 @@
   overflow-wrap: anywhere;
   word-break: break-word;
 }
-.mdi-tabbar { display: flex; flex: 0 0 40px; align-items: stretch; height: 40px; padding-top: 5px; border-bottom: 1px solid #cfdae5; background: #e3eaf1; }
-.mdi-tabs-scroll { flex: 1; min-width: 0; overflow-x: auto; overflow-y: hidden; }
-.mdi-tabs { display: inline-flex; align-items: flex-end; min-width: 100%; height: 100%; padding: 0 4px; }
+.mdi-tabbar { display: flex; flex: 0 0 50px; align-items: stretch; height: 50px; padding-top: 5px; border-bottom: 1px solid #cfdae5; background: #e3eaf1; }
+.mdi-tabs-scroll { flex: 1; min-width: 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; scrollbar-color: #9fb1c1 transparent; }
+.mdi-tabs-scroll::-webkit-scrollbar { height: 6px; }
+.mdi-tabs-scroll::-webkit-scrollbar-track { background: transparent; }
+.mdi-tabs-scroll::-webkit-scrollbar-thumb { border-radius: 6px; background: #9fb1c1; }
+.mdi-tabs { display: inline-flex; align-items: flex-end; min-width: 100%; height: 35px; padding: 0 4px; }
 .mdi-tab { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 6px; max-width: 220px; height: 34px; margin-right: 2px; padding: 0 9px; border: 1px solid transparent; border-bottom: 0; border-radius: 4px 4px 0 0; color: #5c6f82; background: rgba(255, 255, 255, .42); cursor: pointer; font-size: 13px; user-select: none; }
 .mdi-tab:hover { color: #245275; background: rgba(255, 255, 255, .72); }
 .mdi-tab.is-active { height: 35px; color: #1f3348; background: #eef2f6; border-color: #cfdae5; box-shadow: inset 0 2px 0 #2c7fb8; }
diff --git a/emergency-repair-web/src/main.js b/emergency-repair-web/src/main.js
index 36c8336..381a415 100644
--- a/emergency-repair-web/src/main.js
+++ b/emergency-repair-web/src/main.js
@@ -11,6 +11,26 @@
 
 Vue.prototype.$appName = '涓存姠淇鐞嗙郴缁�'
 
+Vue.prototype.$hasPermission = function(code) {
+  const raw = sessionStorage.getItem('permissions')
+  if (raw === null) return true
+  try {
+    const list = JSON.parse(raw)
+    if (!Array.isArray(list)) return true
+    return list.indexOf(code) >= 0
+  } catch (e) {
+    return true
+  }
+}
+
+Vue.directive('permission', {
+  inserted(el, binding) {
+    if (binding.value && !Vue.prototype.$hasPermission(binding.value) && el.parentNode) {
+      el.parentNode.removeChild(el)
+    }
+  }
+})
+
 new Vue({
   router,
   render: h => h(App)
diff --git a/emergency-repair-web/src/router/index.js b/emergency-repair-web/src/router/index.js
index 35442f5..3339885 100644
--- a/emergency-repair-web/src/router/index.js
+++ b/emergency-repair-web/src/router/index.js
@@ -13,11 +13,11 @@
     children: [
       { path: 'dashboard', component: () => import('@/views/Dashboard.vue'), meta: { title: '棣栭〉闂ㄦ埛', cacheName: 'Dashboard' } },
       { path: 'fault/report', component: () => import('@/views/fault/FaultReport.vue'), meta: { title: '鏁呴殰涓婃姤', cacheName: 'FaultReport' } },
-      { path: 'fault/review', component: () => import('@/views/fault/XcbReview.vue'), meta: { title: 'XCB鍔╃悊瀹℃牳', cacheName: 'XcbReview' } },
-      { path: 'fault/approval/cmb', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'CMB' }, meta: { title: 'CMB棰嗗瀹℃壒', stage: 'CMB', cacheName: 'SimpleApproval' } },
-      { path: 'fault/approval/jcc-assistant', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'JCC_ASSISTANT' }, meta: { title: 'JCC鍔╃悊瀹℃壒', stage: 'JCC_ASSISTANT', cacheName: 'SimpleApproval' } },
-      { path: 'fault/approval/jcc-director', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'JCC_DIRECTOR' }, meta: { title: 'JCC澶勯暱瀹℃壒', stage: 'JCC_DIRECTOR', cacheName: 'SimpleApproval' } },
-      { path: 'fault/dispatch', component: () => import('@/views/fault/DispatchPage.vue'), meta: { title: 'XCB鍔╃悊娲惧崟', cacheName: 'DispatchPage' } },
+      { path: 'fault/review', component: () => import('@/views/fault/XcbReview.vue'), meta: { title: 'rk_XCB鍔╃悊瀹℃牳', cacheName: 'XcbReview' } },
+      { path: 'fault/approval/cmb', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'CMB' }, meta: { title: 'rk_CMB棰嗗瀹℃壒', stage: 'CMB', cacheName: 'SimpleApproval' } },
+      { path: 'fault/approval/jcc-assistant', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'JCC_ASSISTANT' }, meta: { title: 'rk_JCC鍔╃悊瀹℃壒', stage: 'JCC_ASSISTANT', cacheName: 'SimpleApproval' } },
+      { path: 'fault/approval/jcc-director', component: () => import('@/views/fault/SimpleApproval.vue'), props: { stage: 'JCC_DIRECTOR' }, meta: { title: 'rk_JCC澶勯暱瀹℃壒', stage: 'JCC_DIRECTOR', cacheName: 'SimpleApproval' } },
+      { path: 'fault/dispatch', component: () => import('@/views/fault/DispatchPage.vue'), meta: { title: 'rk_XCB鍔╃悊娲惧崟', cacheName: 'DispatchPage' } },
       { path: 'fault/completion', component: () => import('@/views/completion/CompletionList.vue'), meta: { title: '瀹屽伐鎯呭喌澶勭悊', cacheName: 'CompletionList' } },
       { path: 'completion/report', component: () => import('@/views/completion/CompletionReport.vue'), props: route => ({ workOrderId: route.query.workOrderId || null }), meta: { title: '瀹屽伐淇℃伅涓婃姤', cacheName: 'CompletionReport' } },
       { path: 'acceptance/application', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'APPLICATION' }, meta: { title: '楠屾敹鐢宠', stage: 'APPLICATION', cacheName: 'AcceptanceList' } },
@@ -27,11 +27,15 @@
       { path: 'acceptance/rating', component: () => import('@/views/acceptance/AcceptanceList.vue'), props: { stage: 'RATING' }, meta: { title: '淇悗璐ㄩ噺璇勭骇', stage: 'RATING', cacheName: 'AcceptanceList' } },
       { path: 'reports/:type', component: () => import('@/views/report/ReportPage.vue'), props: route => ({ type: route.params.type }), meta: { title: '缁熻鎶ヨ〃', cacheName: 'ReportPage' } },
       { path: 'system/contractors', component: () => import('@/views/system/ContractorManage.vue'), meta: { title: '鎵夸慨鍗曚綅绠$悊', cacheName: 'ContractorManage' } },
+      { path: 'system/organization', component: () => import('@/views/system/OrganizationManage.vue'), meta: { title: '缁勭粐鏈烘瀯', cacheName: 'OrganizationManage' } },
       { path: 'system/product-tree', component: () => import('@/views/system/TreeManage.vue'), props: { treeType: 'product' }, meta: { title: '浜у搧缁撴瀯鏍戠鐞�', treeType: 'product', cacheName: 'TreeManage' } },
       { path: 'system/base-equipment', component: () => import('@/views/system/TreeManage.vue'), props: { treeType: 'base' }, meta: { title: '璁惧鍩虹搴撶鐞�', treeType: 'base', cacheName: 'TreeManage' } },
       { path: 'system/engineering-majors', component: () => import('@/views/system/EngineeringMajorManage.vue'), meta: { title: '宸ョ▼涓撲笟閰嶇疆', cacheName: 'EngineeringMajorManage' } },
-      { path: 'system/team-majors', component: () => import('@/views/system/TeamMajorManage.vue'), meta: { title: 'T闃熶笓涓氶厤缃�', cacheName: 'TeamMajorManage' } },
+      { path: 'system/team-majors', component: () => import('@/views/system/TeamMajorManage.vue'), meta: { title: 'rk_T闃熶笓涓氶厤缃�', cacheName: 'TeamMajorManage' } },
+      { path: 'system/users', component: () => import('@/views/system/UserManage.vue'), meta: { title: '鐢ㄦ埛绠$悊', cacheName: 'UserManage' } },
       { path: 'system/roles', component: () => import('@/views/system/RoleManage.vue'), meta: { title: '瑙掕壊绠$悊', cacheName: 'RoleManage' } },
+      { path: 'system/login-logs', component: () => import('@/views/system/LoginLogManage.vue'), meta: { title: '鐧诲綍鏃ュ織', cacheName: 'LoginLogManage' } },
+      { path: 'system/operation-logs', component: () => import('@/views/system/OperationLogManage.vue'), meta: { title: '鎿嶄綔鏃ュ織', cacheName: 'OperationLogManage' } },
       { path: 'system/menus', component: () => import('@/views/system/MenuManage.vue'), meta: { title: '鑿滃崟绠$悊', cacheName: 'MenuManage' } },
       { path: 'system/menu-permissions', component: () => import('@/views/system/MenuPermission.vue'), meta: { title: '鑿滃崟鏉冮檺', cacheName: 'MenuPermission' } },
       { path: 'system/three-member', component: () => import('@/views/system/ThreeMember.vue'), meta: { title: '涓夊憳绠$悊', cacheName: 'ThreeMember' } }
diff --git a/emergency-repair-web/src/utils/dict.js b/emergency-repair-web/src/utils/dict.js
index 2d5ea8f..e758711 100644
--- a/emergency-repair-web/src/utils/dict.js
+++ b/emergency-repair-web/src/utils/dict.js
@@ -1,11 +1,11 @@
 export const taskStatuses = {
   UNREPORTED: { text: '鏈笂鎶�', type: 'info' },
-  REPORTED_XCB: { text: '宸蹭笂鎶CB', type: 'warning' },
+  REPORTED_XCB: { text: '宸蹭笂鎶k_XCB', type: 'warning' },
   RETURNED: { text: '涓婄骇鎵撳洖', type: 'danger' },
-  XCB_REVIEWED: { text: 'XCB鍔╃悊瀹℃牳瀹屾垚', type: 'primary' },
-  CMB_APPROVED: { text: 'CMB棰嗗瀹℃壒瀹屾垚', type: 'success' },
-  JCC_ASSISTANT_APPROVED: { text: 'JCC鍔╃悊瀹℃壒瀹屾垚', type: 'success' },
-  JCC_DIRECTOR_APPROVED: { text: 'JCC澶勯暱宸查槄', type: 'success' },
+  XCB_REVIEWED: { text: 'rk_XCB鍔╃悊瀹℃牳瀹屾垚', type: 'primary' },
+  CMB_APPROVED: { text: 'rk_CMB棰嗗瀹℃壒瀹屾垚', type: 'success' },
+  JCC_ASSISTANT_APPROVED: { text: 'rk_JCC鍔╃悊瀹℃壒瀹屾垚', type: 'success' },
+  JCC_DIRECTOR_APPROVED: { text: 'rk_JCC澶勯暱宸查槄', type: 'success' },
   DISPATCHED: { text: '宸叉淳鍗�', type: 'primary' },
   CANCELLED: { text: '宸查攢椤�', type: 'info' }
 }
diff --git a/emergency-repair-web/src/utils/sensitive.js b/emergency-repair-web/src/utils/sensitive.js
index c4bba53..d2bee8b 100644
--- a/emergency-repair-web/src/utils/sensitive.js
+++ b/emergency-repair-web/src/utils/sensitive.js
@@ -143,6 +143,24 @@
   })
 }
 
+function expandPrefixedAliases(items) {
+  const expanded = []
+  const seen = {}
+  items.forEach(function (item) {
+    const names = [item.codename]
+    if (item.codename.indexOf('rk_') === 0 && item.codename.length > 3) {
+      names.push(item.codename.substring(3))
+    }
+    names.forEach(function (codename) {
+      if (!codename || seen[codename]) {
+        return
+      }
+      seen[codename] = true
+      expanded.push({ codename: codename, real_name: item.real_name })
+    })
+  })
+  return expanded
+}
 export async function installSensitiveWords() {
   try {
     const baseUrl = process.env.BASE_URL || '/'
@@ -158,9 +176,10 @@
       .filter(function (item) {
         return item && item.codename && isConfigured(item.real_name, item.codename)
       })
-      .sort(function (left, right) {
-        return right.codename.length - left.codename.length
-      })
+
+    replacements = expandPrefixedAliases(replacements).sort(function (left, right) {
+      return right.codename.length - left.codename.length
+    })
 
     replaceDom(document.body)
     watchDom()
diff --git a/emergency-repair-web/src/views/Dashboard.vue b/emergency-repair-web/src/views/Dashboard.vue
index 7a88109..4b0c3f9 100644
--- a/emergency-repair-web/src/views/Dashboard.vue
+++ b/emergency-repair-web/src/views/Dashboard.vue
@@ -19,7 +19,7 @@
     <div class="dashboard-grid">
       <section class="dashboard-panel chart-panel">
         <div class="panel-head">
-          <h3>瑁呭鏁呴殰鎬讳綋鍒嗘瀽</h3>
+          <h3>璁惧鏁呴殰鎬讳綋鍒嗘瀽</h3>
           <span>涓婃柟鏌辩姸鍥� / 涓嬫柟缁熻</span>
         </div>
         <div ref="overviewChart" class="chart-box"></div>
@@ -28,7 +28,7 @@
         <div class="panel-head"><h3>甯哥敤鍔熻兘</h3></div>
         <div class="quick-list">
           <button @click="$router.push('/fault/report')"><i class="el-icon-edit-outline"></i><span>鏁呴殰涓婃姤</span></button>
-          <button @click="$router.push('/fault/review')"><i class="el-icon-s-check"></i><span>XCB鍔╃悊瀹℃牳</span></button>
+          <button @click="$router.push('/fault/review')"><i class="el-icon-s-check"></i><span>rk_XCB鍔╃悊瀹℃牳</span></button>
           <button @click="$router.push('/fault/dispatch')"><i class="el-icon-s-promotion"></i><span>涓�閿淳鍗�</span></button>
           <button @click="$router.push('/fault/completion')"><i class="el-icon-finished"></i><span>瀹屽伐澶勭悊</span></button>
           <button @click="$router.push('/acceptance/application')"><i class="el-icon-document-checked"></i><span>楠屾敹鐢宠</span></button>
@@ -39,16 +39,16 @@
 
     <section class="dashboard-panel">
       <div class="panel-head">
-        <h3>鎸塜鍙风粺璁�</h3>
-        <span>鎸塜鍙锋眹鎬诲綋鍓嶄换鍔″拰瀹屽伐鐘舵��</span>
+        <h3>鎸塺k_X鍙风粺璁�</h3>
+        <span>鎸塺k_X鍙锋眹鎬诲綋鍓嶄换鍔″拰瀹屽伐鐘舵��</span>
       </div>
       <el-table :data="xStats" border size="small">
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
         <el-table-column prop="currentStatus" label="褰撳墠鐘舵��" width="110">
           <template slot-scope="{ row }"><el-tag size="mini" type="info">{{ row.currentStatus }}</el-tag></template>
         </el-table-column>
         <el-table-column prop="berth" label="鍋滈潬娉婁綅" width="120"></el-table-column>
-        <el-table-column prop="team" label="TD" min-width="130"></el-table-column>
+        <el-table-column prop="team" label="rk_TD" min-width="130"></el-table-column>
         <el-table-column prop="total" label="宸ョ▼鎬绘暟" align="center"></el-table-column>
         <el-table-column prop="cancelled" label="閿�椤规�绘暟" align="center"></el-table-column>
         <el-table-column prop="major" label="閲嶅ぇ鏁呴殰" align="center"></el-table-column>
@@ -111,7 +111,7 @@
       return [
         { label: '宸ョ▼鎬绘暟', value: this.overview.engineeringTotal || 0, note: '褰撳墠鏁呴殰浠诲姟' },
         { label: '閲嶅ぇ鏁呴殰', value: this.overview.majorFault || 0, note: '闇�閲嶇偣鍏虫敞' },
-        { label: '娑塇缁熻', value: this.overview.hRelated || 0, note: '鏁忔劅鏍囪瘑浠诲姟' },
+        { label: '娑塺k_H缁熻', value: this.overview.hRelated || 0, note: '鏁忔劅鏍囪瘑浠诲姟' },
         { label: '鏈畬宸�', value: this.overview.unfinished || 0, note: '娲惧伐鍗曞彛寰�' },
         { label: '瀹屽伐寰呰瘯楠�', value: this.overview.waitTest || 0, note: '寰呰瘯楠屽伐鍗�' },
         { label: '宸插畬宸�', value: this.overview.completed || 0, note: '鍙繘鍏ラ獙鏀�' }
diff --git a/emergency-repair-web/src/views/Login.vue b/emergency-repair-web/src/views/Login.vue
index 35f75ec..bbe197e 100644
--- a/emergency-repair-web/src/views/Login.vue
+++ b/emergency-repair-web/src/views/Login.vue
@@ -16,7 +16,7 @@
         </el-form-item>
         <el-button type="primary" class="login-btn" :loading="loading" @click="submit">鐧诲綍绯荤粺</el-button>
       </el-form>
-      <div class="login-tip">绗竴鐗堟殏涓嶅惎鐢ㄨ鑹叉帶鍒讹紝浣跨敤 admin / admin123 浣撻獙鍏ㄩ儴鍔熻兘銆�</div>
+      <div class="login-tip">璇蜂娇鐢ㄥ凡鍒嗛厤鐨勭郴缁熻处鍙风櫥褰曘��</div>
     </div>
   </div>
 </template>
@@ -31,7 +31,7 @@
   data() {
     return {
       loading: false,
-      form: { username: 'admin', password: 'admin123' },
+      form: { username: '', password: '' },
       rules: {
         username: [{ required: true, message: '璇疯緭鍏ョ敤鎴峰悕', trigger: 'blur' }],
         password: [{ required: true, message: '璇疯緭鍏ュ瘑鐮�', trigger: 'blur' }]
@@ -48,6 +48,8 @@
           sessionStorage.setItem('username', res.data.username)
           sessionStorage.setItem('realName', res.data.realName)
           sessionStorage.setItem('deptName', res.data.deptName || '')
+          sessionStorage.setItem('roles', JSON.stringify(res.data.roles || []))
+          sessionStorage.setItem('permissions', JSON.stringify(res.data.permissions || []))
           this.$router.push('/dashboard')
         }).finally(() => { this.loading = false })
       })
diff --git a/emergency-repair-web/src/views/acceptance/AcceptanceList.vue b/emergency-repair-web/src/views/acceptance/AcceptanceList.vue
index 0ecce6b..4059e5c 100644
--- a/emergency-repair-web/src/views/acceptance/AcceptanceList.vue
+++ b/emergency-repair-web/src/views/acceptance/AcceptanceList.vue
@@ -9,7 +9,7 @@
     </div>
 
     <div class="toolbar">
-      <el-input v-model="keyword" placeholder="娲惧伐鍗曠紪鍙� / X鍙� / 鎵夸慨鍗曚綅" clearable style="width:280px" @keyup.enter.native="load"></el-input>
+      <el-input v-model="keyword" placeholder="娲惧伐鍗曠紪鍙� / rk_X鍙� / 鎵夸慨鍗曚綅" clearable style="width:280px" @keyup.enter.native="load"></el-input>
       <el-button type="primary" icon="el-icon-search" @click="load">鏌ヨ</el-button>
       <div class="spacer"></div>
       <el-tag type="info" size="small">浠呭畬宸ョ姸鎬佺殑娲惧伐鍗曞彲杩涘叆楠屾敹娴佺▼</el-tag>
@@ -21,7 +21,7 @@
         <el-table-column prop="status" label="楠屾敹鐘舵��" width="155" fixed>
           <template slot-scope="{ row }"><el-tag size="mini" :type="acceptanceMeta(row.status).type">{{ acceptanceMeta(row.status).text }}</el-tag></template>
         </el-table-column>
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
         <el-table-column prop="contractorName" label="鎵夸慨鍗曚綅" min-width="170"></el-table-column>
         <el-table-column prop="stage" label="褰撳墠鐜妭" width="150">
           <template slot-scope="{ row }">{{ stageText(row.stage) }}</template>
diff --git a/emergency-repair-web/src/views/completion/CompletionList.vue b/emergency-repair-web/src/views/completion/CompletionList.vue
index 483b4e5..687ab32 100644
--- a/emergency-repair-web/src/views/completion/CompletionList.vue
+++ b/emergency-repair-web/src/views/completion/CompletionList.vue
@@ -3,7 +3,7 @@
     <div class="page-title">
       <div>
         <h2>瀹屽伐鎯呭喌澶勭悊</h2>
-        <p>姣忎竴琛屼唬琛ㄤ竴寮犳淳宸ュ崟锛屽彧鍏佽鍕鹃�変竴琛岋紱瀹屽伐鐘舵�佺敱涓氬姟鎸夐挳缁存姢</p>
+        <p>姣忎竴琛屼唬琛ㄤ竴寮犳淳宸ュ崟锛屽彧鍏佽鍕鹃�変竴琛岋紱鐐光�滃畬宸モ�濆彧鏇存柊瀹屽伐鐘舵�侊紝瀹屽伐淇℃伅鐢辨壙淇崟浣嶇敤鎴峰彟琛屽~鎶�</p>
       </div>
       <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
     </div>
@@ -19,11 +19,11 @@
     </div>
 
     <div class="toolbar action-toolbar">
-      <el-button type="success" icon="el-icon-finished" :disabled="!selectedRow || selectedRow.completionStatus === 'COMPLETED'" @click="goCompletion">瀹屽伐</el-button>
+      <el-button type="success" icon="el-icon-finished" :disabled="!selectedRow || selectedRow.completionStatus === 'COMPLETED'" @click="setStatus('COMPLETED')">瀹屽伐</el-button>
       <el-button type="primary" icon="el-icon-time" :disabled="!canSetStatus" @click="setStatus('WAIT_TEST')">瀹屽伐寰呰瘯楠�</el-button>
       <el-button icon="el-icon-refresh-left" :disabled="!canSetStatus" @click="setStatus('UNFINISHED')">鏈畬宸�</el-button>
       <el-button type="warning" icon="el-icon-box" :disabled="!canSetStatus" @click="setStatus('WAIT_PARTS')">寰呭浠�</el-button>
-      <el-button type="primary" plain icon="el-icon-star-on" :disabled="!selectedRow" @click="openScore">T鍛樿瘎鍒�</el-button>
+      <el-button type="primary" plain icon="el-icon-star-on" :disabled="!selectedRow" @click="openScore">rk_T鍛樿瘎鍒�</el-button>
     </div>
 
     <div class="table-panel">
@@ -35,15 +35,15 @@
         <el-table-column prop="completionStatus" label="瀹屽伐鐘舵��" width="115" fixed>
           <template slot-scope="{ row }"><el-tag size="mini" :type="completionMeta(row.completionStatus).type">{{ completionMeta(row.completionStatus).text }}</el-tag></template>
         </el-table-column>
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
-        <el-table-column prop="equipmentName" label="瑁呭鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
+        <el-table-column prop="equipmentName" label="璁惧鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="contractorName" label="娲惧崟鍗曚綅" min-width="160"></el-table-column>
         <el-table-column prop="engineeringMajor" label="宸ョ▼涓撲笟" width="100"></el-table-column>
-        <el-table-column prop="teamMajor" label="T闃熶笓涓�" width="100"></el-table-column>
+        <el-table-column prop="teamMajor" label="rk_T闃熶笓涓�" width="100"></el-table-column>
         <el-table-column prop="parentSystem" label="鎵�灞炰笂绾х郴缁�" min-width="130"></el-table-column>
         <el-table-column prop="reportUnit" label="涓婃姤鍗曚綅" min-width="120"></el-table-column>
         <el-table-column prop="reportDate" label="涓婃姤鏃堕棿" width="110"></el-table-column>
-        <el-table-column prop="reviewTime" label="XCB瀹℃牳鏃堕棿" width="160">
+        <el-table-column prop="reviewTime" label="rk_XCB瀹℃牳鏃堕棿" width="160">
           <template slot-scope="{ row }">{{ formatTime(row.reviewTime) }}</template>
         </el-table-column>
         <el-table-column prop="owner" label="璐熻矗浜�" width="90"></el-table-column>
@@ -59,7 +59,7 @@
       </el-table>
     </div>
 
-    <el-dialog title="T鍛樿瘎鍒�" :visible.sync="scoreVisible" width="520px" append-to-body>
+    <el-dialog title="rk_T鍛樿瘎鍒�" :visible.sync="scoreVisible" width="520px" append-to-body>
       <el-form :model="scoreForm" label-width="100px">
         <el-form-item label="娲惧伐鍗�"><b>{{ scoreForm.workOrderNo }}</b></el-form-item>
         <el-form-item label="淇悗璐ㄩ噺璇勫垎"><el-rate v-model="scoreForm.score" show-text :texts="['寰堝樊','杈冨樊','涓�鑸�','鑹ソ','浼樼']"></el-rate></el-form-item>
@@ -117,9 +117,6 @@
         this.$message.success('瀹屽伐鐘舵�佸凡鏇存柊')
         this.load()
       }).catch(() => {})
-    },
-    goCompletion() {
-      this.$router.push({ path: '/completion/report', query: { workOrderId: this.selectedRow.id } })
     },
     openScore() {
       this.scoreForm = {
diff --git a/emergency-repair-web/src/views/completion/CompletionReport.vue b/emergency-repair-web/src/views/completion/CompletionReport.vue
index e7f9336..70fbba0 100644
--- a/emergency-repair-web/src/views/completion/CompletionReport.vue
+++ b/emergency-repair-web/src/views/completion/CompletionReport.vue
@@ -3,15 +3,15 @@
     <div class="page-title">
       <div>
         <h2>瀹屽伐淇℃伅涓婃姤</h2>
-        <p>浠庡畬宸ユ儏鍐靛鐞嗛〉鐐光�滃畬宸モ�濊繘鍏ワ紱鎻愪氦鎴愬姛鍚庢淳宸ュ崟瀹屽伐鐘舵�佸啓涓衡�滃畬宸モ�濓紝骞惰繘鍏ラ獙鏀剁敵璇�</p>
+        <p>浠呮樉绀哄凡鍦ㄥ畬宸ユ儏鍐靛鐞嗕腑纭瀹屽伐銆佸垎閰嶇粰鏈崟浣嶄笖灏氭湭濉姤鐨勬淳宸ュ崟锛涙彁浜ゅ悗杩涘叆瀹屽伐楠屾敹娴佺▼</p>
       </div>
       <el-button icon="el-icon-back" @click="$router.push('/fault/completion')">杩斿洖瀹屽伐澶勭悊</el-button>
     </div>
 
     <div class="toolbar">
       <span>娲惧伐鍗�</span>
-      <el-select v-model="form.workOrderId" filterable placeholder="璇烽�夋嫨娲惧伐鍗�" style="width:360px" :disabled="!!routeWorkOrderId" @change="loadSelected">
-        <el-option v-for="item in selectableOrders" :key="item.id" :label="item.workOrderNo + ' - ' + item.contractorName" :value="item.id"></el-option>
+      <el-select v-model="form.workOrderId" filterable placeholder="璇烽�夋嫨娲惧伐鍗�" style="width:560px" :disabled="!!routeWorkOrderId" no-data-text="娌℃湁鍙~鎶ョ殑娲惧伐鍗曪紙闇�宸茬‘璁ゅ畬宸ャ�佸垎閰嶇粰鏈崟浣嶄笖灏氭湭濉姤锛�" @change="loadSelected">
+        <el-option v-for="item in selectableOrders" :key="item.id" :label="item.workOrderNo + ' - ' + (item.engineeringNameDesc || '鏃犲伐绋嬪悕绉�') + ' - ' + item.contractorName" :value="item.id"></el-option>
       </el-select>
       <div class="spacer"></div>
       <el-tag v-if="workOrder" size="small" type="info">{{ workOrder.xNo }} 路 {{ workOrder.equipmentName }}</el-tag>
@@ -21,13 +21,13 @@
       <section class="form-section">
         <h3>鍩虹淇℃伅</h3>
         <el-row :gutter="16">
-          <el-col :span="8"><el-form-item label="X鍙�"><el-input v-model="form.xNo" readonly></el-input></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="rk_X鍙�"><el-input v-model="form.xNo" readonly></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="鎵夸慨鍗曚綅"><el-input v-model="form.contractorName" readonly></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="宸ョ▼缂栧彿"><el-input v-model="form.engineeringNo"></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="寮�宸ユ椂闂�"><el-date-picker v-model="form.startDate" type="date" value-format="yyyy-MM-dd" style="width:100%"></el-date-picker></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="瀹屽伐鏃堕棿"><el-date-picker v-model="form.finishDate" type="date" value-format="yyyy-MM-dd" style="width:100%"></el-date-picker></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="鎵夸慨浜�"><el-input v-model="form.contractorPerson"></el-input></el-form-item></el-col>
-          <el-col :span="8"><el-form-item label="T鍐呮柦宸�"><el-input v-model="form.tInternalConstruction"></el-input></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="rk_T鍐呮柦宸�"><el-input v-model="form.tInternalConstruction"></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="鐗佃繛宸ョ▼"><el-input v-model="form.relatedProject"></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="瀹屽伐鑰楁椂(澶�)"><el-input-number v-model="form.durationDays" :min="0" style="width:100%"></el-input-number></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="鎵夸慨浜烘暟"><el-input-number v-model="form.personCount" :min="0" style="width:100%"></el-input-number></el-form-item></el-col>
@@ -141,7 +141,7 @@
       }
     },
     loadSelectable() {
-      api.get('/work-orders', { params: { status: 'UNFINISHED,WAIT_PARTS,WAIT_TEST' } }).then(res => { this.selectableOrders = res.data || [] })
+      api.get('/work-orders', { params: { completionReportable: true } }).then(res => { this.selectableOrders = res.data || [] })
     },
     loadSelected() {
       if (!this.form.workOrderId) return
diff --git a/emergency-repair-web/src/views/fault/DispatchPage.vue b/emergency-repair-web/src/views/fault/DispatchPage.vue
index 4592b29..f9408f4 100644
--- a/emergency-repair-web/src/views/fault/DispatchPage.vue
+++ b/emergency-repair-web/src/views/fault/DispatchPage.vue
@@ -2,14 +2,14 @@
   <div class="page">
     <div class="page-title">
       <div>
-        <h2>XCB鍔╃悊娲惧崟</h2>
-        <p>鏄剧ずJCC澶勯暱瀹℃壒瀹屾垚涓旀湭娲惧崟鐨勬晠闅滐紝鎸夋壙淇崟浣嶄竴閿敓鎴愭淳宸ュ崟</p>
+        <h2>rk_XCB鍔╃悊娲惧崟</h2>
+        <p>鏄剧ずrk_JCC澶勯暱瀹℃壒瀹屾垚涓旀湭娲惧崟鐨勬晠闅滐紝鎸夋壙淇崟浣嶄竴閿敓鎴愭淳宸ュ崟</p>
       </div>
       <el-button type="primary" icon="el-icon-s-promotion" :disabled="selection.length === 0" @click="dispatch">涓�閿淳鍗�</el-button>
     </div>
 
     <div class="toolbar">
-      <el-input v-model="keyword" placeholder="鏁呴殰缂栧彿 / 瑁呭鍚嶇О / 宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="load"></el-input>
+      <el-input v-model="keyword" placeholder="鏁呴殰缂栧彿 / 璁惧鍚嶇О / 宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="load"></el-input>
       <el-button type="primary" icon="el-icon-search" @click="load">鏌ヨ</el-button>
       <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
       <div class="spacer"></div>
@@ -22,17 +22,17 @@
       <el-table :data="rows" border stripe height="calc(100vh - 325px)" @selection-change="selection = $event">
         <el-table-column type="selection" width="46"></el-table-column>
         <el-table-column prop="faultCode" label="鏁呴殰缂栧彿" width="180" fixed></el-table-column>
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
-        <el-table-column prop="equipmentName" label="瑁呭鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
+        <el-table-column prop="equipmentName" label="璁惧鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="engineeringMajor" label="宸ョ▼涓撲笟" width="100"></el-table-column>
         <el-table-column prop="parentSystem" label="鎵�灞炰笂绾х郴缁�" min-width="130"></el-table-column>
         <el-table-column prop="contractorNames" label="鎵夸慨鍗曚綅" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="reportUnit" label="涓婃姤鍗曚綅" min-width="120"></el-table-column>
         <el-table-column prop="reportDate" label="涓婃姤鏃堕棿" width="110"></el-table-column>
-        <el-table-column prop="xcbReviewTime" label="XCB瀹℃牳鏃堕棿" width="160">
+        <el-table-column prop="xcbReviewTime" label="rk_XCB瀹℃牳鏃堕棿" width="160">
           <template slot-scope="{ row }">{{ formatTime(row.xcbReviewTime) }}</template>
         </el-table-column>
-        <el-table-column prop="teamMajor" label="T闃熶笓涓�" width="100"></el-table-column>
+        <el-table-column prop="teamMajor" label="rk_T闃熶笓涓�" width="100"></el-table-column>
         <el-table-column prop="owner" label="璐熻矗浜�" width="90"></el-table-column>
         <el-table-column prop="requestedFinishDate" label="瑕佹眰瀹屽伐鏃ユ湡" width="120"></el-table-column>
         <el-table-column prop="faultImpact" label="鏁呴殰褰卞搷" width="100"></el-table-column>
@@ -47,7 +47,7 @@
         <el-table-column prop="workOrderNo" label="娲惧伐鍗曠紪鍙�" width="160"></el-table-column>
         <el-table-column prop="faultCode" label="鏁呴殰缂栧彿" width="180"></el-table-column>
         <el-table-column prop="contractorName" label="鎵夸慨鍗曚綅" min-width="180"></el-table-column>
-        <el-table-column prop="equipmentName" label="瑁呭鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="equipmentName" label="璁惧鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="completionStatus" label="瀹屽伐鐘舵��" width="100">
           <template slot-scope="{ row }"><el-tag size="mini" type="info">{{ completionMeta(row.completionStatus).text }}</el-tag></template>
         </el-table-column>
diff --git a/emergency-repair-web/src/views/fault/FaultEditDialog.vue b/emergency-repair-web/src/views/fault/FaultEditDialog.vue
index 2218c0f..6e5fce2 100644
--- a/emergency-repair-web/src/views/fault/FaultEditDialog.vue
+++ b/emergency-repair-web/src/views/fault/FaultEditDialog.vue
@@ -5,14 +5,14 @@
         <el-col :span="8"><el-form-item label="鏁呴殰缂栧彿"><el-input v-model="form.faultCode" readonly placeholder="淇濆瓨鍚庤嚜鍔ㄧ敓鎴�"></el-input></el-form-item></el-col>
         <el-col :span="8"><el-form-item label="涓婃姤鍗曚綅"><el-input v-model="form.reportUnit"></el-input></el-form-item></el-col>
         <el-col :span="8"><el-form-item label="涓婃姤鏃ユ湡"><el-date-picker v-model="form.reportDate" type="date" value-format="yyyy-MM-dd" style="width:100%"></el-date-picker></el-form-item></el-col>
-        <el-col :span="8"><el-form-item label="X鍙�"><el-select v-model="form.xNo" filterable placeholder="璇烽�夋嫨X鍙�" style="width:100%"><el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option></el-select></el-form-item></el-col>
+        <el-col :span="8"><el-form-item label="rk_X鍙�"><el-select v-model="form.xNo" filterable placeholder="璇烽�夋嫨rk_X鍙�" style="width:100%"><el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option></el-select></el-form-item></el-col>
         <el-col :span="8"><el-form-item label="璐熻矗浜�"><el-input v-model="form.owner"></el-input></el-form-item></el-col>
-        <el-col :span="8"><el-form-item label="T闃熶笓涓�"><el-select v-model="form.teamMajor" style="width:100%"><el-option v-for="item in teamMajors" :key="item.id" :label="item.name" :value="item.name"></el-option></el-select></el-form-item></el-col>
+        <el-col :span="8"><el-form-item label="rk_T闃熶笓涓�"><el-select v-model="form.teamMajor" style="width:100%"><el-option v-for="item in teamMajors" :key="item.id" :label="item.name" :value="item.name"></el-option></el-select></el-form-item></el-col>
         <el-col :span="8"><el-form-item label="宸ョ▼涓撲笟"><el-select v-model="form.engineeringMajor" style="width:100%"><el-option v-for="item in engineeringMajors" :key="item.id" :label="item.name" :value="item.name"></el-option></el-select></el-form-item></el-col>
         <el-col :span="16">
-          <el-form-item label="瑁呭鍚嶇О" prop="equipmentName">
+          <el-form-item label="璁惧鍚嶇О" prop="equipmentName">
             <el-input v-model="form.equipmentName" readonly :title="form.equipmentName" placeholder="鐐瑰嚮鍙充晶鎸夐挳浠庝骇鍝佺粨鏋勬爲閫夎澶囷紝鍙閫�">
-              <el-button slot="append" icon="el-icon-s-grid" @click="pickerVisible = true">閫夋嫨瑁呭</el-button>
+              <el-button slot="append" icon="el-icon-s-grid" @click="pickerVisible = true">閫夋嫨璁惧</el-button>
             </el-input>
             <div v-if="selectedEquipmentIds.length" class="field-tip">宸查�� {{ selectedEquipmentIds.length }} 鍙拌澶�</div>
           </el-form-item>
@@ -25,7 +25,7 @@
         <el-col :span="8"><el-form-item label="鏄惁鑷慨"><el-switch v-model="form.isSelfRepair" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
       </el-row>
       <el-row :gutter="16">
-        <el-col :span="8"><el-form-item label="鏄惁娑塇"><el-switch v-model="form.isHRelated" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
+        <el-col :span="8"><el-form-item label="鏄惁娑塺k_H"><el-switch v-model="form.isHRelated" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
         <el-col :span="8"><el-form-item label="鏄惁鐩戞祴椤圭洰"><el-switch v-model="form.isMonitorItem" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
         <el-col :span="8"><el-form-item label="鐩戞祴鏁板��"><el-input v-model="form.monitorValue" :disabled="form.isMonitorItem !== 1"></el-input></el-form-item></el-col>
         <el-col :span="24"><el-form-item label="閲嶅ぇ鏁呴殰鎻忚堪"><el-input v-model="form.majorFaultDesc" type="textarea" :rows="2"></el-input></el-form-item></el-col>
@@ -63,7 +63,7 @@
       natures: ['涓�鑸�', '閲嶅ぇ'],
       form: {},
       rules: {
-        equipmentName: [{ required: true, message: '璇烽�夋嫨瑁呭鍚嶇О', trigger: 'change' }],
+        equipmentName: [{ required: true, message: '璇烽�夋嫨璁惧鍚嶇О', trigger: 'change' }],
         engineeringNameDesc: [{ required: true, message: '璇峰~鍐欏伐绋嬪悕绉板強鎹熷潖鎯呭喌', trigger: 'blur' }]
       }
     }
@@ -98,18 +98,7 @@
       api.get('/system/engineering-majors').then(res => { this.engineeringMajors = res.data || [] })
     },
     loadXNumbers() {
-      api.get('/system/tree', { params: { type: 'product' } }).then(res => {
-        const values = []
-        const walk = nodes => {
-          if (!nodes) return
-          nodes.forEach(node => {
-            if (node.nodeType === 'X_NO' || node.nodeLevel === 2) values.push(node.name)
-            walk(node.children)
-          })
-        }
-        walk(res.data)
-        this.xNumbers = Array.from(new Set(values.filter(Boolean))).sort((a, b) => a.localeCompare(b, 'zh-CN'))
-      })
+      api.get('/system/tree/product/x-numbers').then(res => { this.xNumbers = res.data || [] })
     },
     onEquipmentSelected(nodes) {
       const selected = Array.isArray(nodes) ? nodes : [nodes]
diff --git a/emergency-repair-web/src/views/fault/FaultReport.vue b/emergency-repair-web/src/views/fault/FaultReport.vue
index 4c13110..3b06b6b 100644
--- a/emergency-repair-web/src/views/fault/FaultReport.vue
+++ b/emergency-repair-web/src/views/fault/FaultReport.vue
@@ -3,17 +3,17 @@
     <div class="page-title">
       <div>
         <h2>鏁呴殰涓婃姤</h2>
-        <p>T鍛樺綍鍏ヨ澶囦俊鎭拰淇悊瑕佹眰锛屾晠闅滅紪鍙蜂繚瀛樺悗鎸夎鍒欒嚜鍔ㄧ敓鎴�</p>
+        <p>rk_T鍛樺綍鍏ヨ澶囦俊鎭拰淇悊瑕佹眰锛屾晠闅滅紪鍙蜂繚瀛樺悗鎸夎鍒欒嚜鍔ㄧ敓鎴�</p>
       </div>
       <el-button icon="el-icon-plus" type="primary" @click="openCreate">鏂板鏁呴殰</el-button>
     </div>
 
     <div class="toolbar query-toolbar">
-      <span class="filter-label">X鍙�</span>
+      <span class="filter-label">rk_X鍙�</span>
       <el-select v-model="query.xNo" placeholder="鍏ㄩ儴" clearable style="width:100px">
         <el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option>
       </el-select>
-      <span class="filter-label">T闃熶笓涓�</span>
+      <span class="filter-label">rk_T闃熶笓涓�</span>
       <el-select v-model="query.teamMajor" placeholder="鍏ㄩ儴" clearable style="width:130px">
         <el-option v-for="item in teamMajors" :key="item.id" :label="item.name" :value="item.name"></el-option>
       </el-select>
@@ -40,7 +40,7 @@
       <el-select v-model="query.status" placeholder="鍏ㄩ儴" clearable style="width:160px">
         <el-option v-for="(meta, key) in taskStatuses" :key="key" :label="meta.text" :value="key"></el-option>
       </el-select>
-      <el-input v-model="query.keyword" placeholder="鏁呴殰缂栧彿/瑁呭鍚嶇О/宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="search"></el-input>
+      <el-input v-model="query.keyword" placeholder="鏁呴殰缂栧彿/璁惧鍚嶇О/宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="search"></el-input>
       <el-button type="primary" icon="el-icon-search" @click="search">鏌ヨ</el-button>
       <el-button icon="el-icon-refresh-left" @click="resetQuery">閲嶇疆</el-button>
       <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
@@ -62,13 +62,13 @@
           <template slot-scope="{ row }"><el-tag size="mini" :type="taskMeta(row.taskStatus).type">{{ taskMeta(row.taskStatus).text }}</el-tag></template>
         </el-table-column>
         <el-table-column prop="faultCode" label="鏁呴殰缂栧彿" width="180" fixed></el-table-column>
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
-        <el-table-column prop="equipmentName" label="瑁呭鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
+        <el-table-column prop="equipmentName" label="璁惧鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="parentSystem" label="鎵�灞炰笂绾х郴缁�" min-width="120"></el-table-column>
         <el-table-column prop="engineeringNameDesc" label="宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="repairRequirement" label="淇悊鎶�鏈姹�" min-width="200" show-overflow-tooltip></el-table-column>
         <el-table-column prop="engineeringMajor" label="宸ョ▼涓撲笟" width="100"></el-table-column>
-        <el-table-column prop="teamMajor" label="T闃熶笓涓�" width="100"></el-table-column>
+        <el-table-column prop="teamMajor" label="rk_T闃熶笓涓�" width="100"></el-table-column>
         <el-table-column prop="owner" label="璐熻矗浜�" width="90"></el-table-column>
         <el-table-column prop="reportUnit" label="涓婃姤鍗曚綅" min-width="130"></el-table-column>
         <el-table-column prop="reportDate" label="涓婃姤鏃ユ湡" width="110" align="center"></el-table-column>
@@ -78,7 +78,7 @@
         <el-table-column label="鏄惁鑷慨" width="90" align="center">
           <template slot-scope="{ row }">{{ yesNo(row.isSelfRepair) }}</template>
         </el-table-column>
-        <el-table-column label="鏄惁娑塇" width="90" align="center">
+        <el-table-column label="鏄惁娑塺k_H" width="90" align="center">
           <template slot-scope="{ row }">{{ yesNo(row.isHRelated) }}</template>
         </el-table-column>
         <el-table-column label="鐩戞祴椤圭洰" width="90" align="center">
@@ -109,14 +109,14 @@
           <el-col :span="8"><el-form-item label="鏁呴殰缂栧彿"><el-input v-model="form.faultCode" readonly placeholder="淇濆瓨鍚庤嚜鍔ㄧ敓鎴�"></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="涓婃姤鍗曚綅"><el-input v-model="form.reportUnit"></el-input></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="涓婃姤鏃ユ湡"><el-date-picker v-model="form.reportDate" type="date" value-format="yyyy-MM-dd" style="width:100%"></el-date-picker></el-form-item></el-col>
-          <el-col :span="8"><el-form-item label="X鍙�"><el-select v-model="form.xNo" filterable placeholder="璇烽�夋嫨X鍙�" style="width:100%"><el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="rk_X鍙�"><el-select v-model="form.xNo" filterable placeholder="璇烽�夋嫨rk_X鍙�" style="width:100%"><el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option></el-select></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="璐熻矗浜�"><el-input v-model="form.owner"></el-input></el-form-item></el-col>
-          <el-col :span="8"><el-form-item label="T闃熶笓涓�"><el-select v-model="form.teamMajor" style="width:100%"><el-option v-for="item in teamMajors" :key="item.id" :label="item.name" :value="item.name"></el-option></el-select></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="rk_T闃熶笓涓�"><el-select v-model="form.teamMajor" style="width:100%"><el-option v-for="item in teamMajors" :key="item.id" :label="item.name" :value="item.name"></el-option></el-select></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="宸ョ▼涓撲笟"><el-select v-model="form.engineeringMajor" style="width:100%"><el-option v-for="item in engineeringMajors" :key="item.id" :label="item.name" :value="item.name"></el-option></el-select></el-form-item></el-col>
           <el-col :span="16">
-            <el-form-item label="瑁呭鍚嶇О" prop="equipmentName">
+            <el-form-item label="璁惧鍚嶇О" prop="equipmentName">
               <el-input v-model="form.equipmentName" readonly :title="form.equipmentName" placeholder="鐐瑰嚮鍙充晶鎸夐挳浠庝骇鍝佺粨鏋勬爲閫夎澶囷紝鍙閫�">
-                <el-button slot="append" icon="el-icon-s-grid" @click="pickerVisible = true">閫夋嫨瑁呭</el-button>
+                <el-button slot="append" icon="el-icon-s-grid" @click="pickerVisible = true">閫夋嫨璁惧</el-button>
               </el-input>
               <div v-if="selectedEquipmentIds.length" class="field-tip">宸查�� {{ selectedEquipmentIds.length }} 鍙拌澶�</div>
             </el-form-item>
@@ -129,7 +129,7 @@
           <el-col :span="8"><el-form-item label="鏄惁鑷慨"><el-switch v-model="form.isSelfRepair" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
         </el-row>
         <el-row :gutter="16">
-          <el-col :span="8"><el-form-item label="鏄惁娑塇"><el-switch v-model="form.isHRelated" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="鏄惁娑塺k_H"><el-switch v-model="form.isHRelated" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="鏄惁鐩戞祴椤圭洰"><el-switch v-model="form.isMonitorItem" :active-value="1" :inactive-value="0"></el-switch></el-form-item></el-col>
           <el-col :span="8"><el-form-item label="鐩戞祴鏁板��"><el-input v-model="form.monitorValue" :disabled="form.isMonitorItem !== 1"></el-input></el-form-item></el-col>
           <el-col :span="24"><el-form-item label="閲嶅ぇ鏁呴殰鎻忚堪"><el-input v-model="form.majorFaultDesc" type="textarea" :rows="2"></el-input></el-form-item></el-col>
@@ -171,7 +171,7 @@
       natures: ['涓�鑸�', '閲嶅ぇ'],
       form: {},
       rules: {
-        equipmentName: [{ required: true, message: '璇烽�夋嫨瑁呭鍚嶇О', trigger: 'change' }],
+        equipmentName: [{ required: true, message: '璇烽�夋嫨璁惧鍚嶇О', trigger: 'change' }],
         engineeringNameDesc: [{ required: true, message: '璇峰~鍐欏伐绋嬪悕绉板強鎹熷潖鎯呭喌', trigger: 'blur' }]
       }
     }
@@ -212,18 +212,7 @@
       })
     },
     loadXNumbers() {
-      api.get('/system/tree', { params: { type: 'product' } }).then(res => {
-        const values = []
-        const walk = nodes => {
-          if (!nodes) return
-          nodes.forEach(node => {
-            if (node.nodeType === 'X_NO' || node.nodeLevel === 2) values.push(node.name)
-            walk(node.children)
-          })
-        }
-        walk(res.data)
-        this.xNumbers = Array.from(new Set(values.filter(Boolean))).sort((a, b) => a.localeCompare(b, 'zh-CN'))
-      })
+      api.get('/system/tree/product/x-numbers').then(res => { this.xNumbers = res.data || [] })
     },
     queryParams() {
       const range = this.query.reportDateRange || []
@@ -282,7 +271,7 @@
         reportUnit: sessionStorage.getItem('deptName') || '',
         reportDate: today,
         xNo: '',
-        owner: sessionStorage.getItem('realName') || 'T鍛�',
+        owner: sessionStorage.getItem('realName') || 'rk_T鍛�',
         teamMajor: (this.teamMajors[0] || {}).name || '',
         engineeringMajor: (this.engineeringMajors[0] || {}).name || '',
         equipmentIds: '',
@@ -361,7 +350,7 @@
     exportCsv() {
       api.get('/faults', { params: this.queryParams() }).then(res => {
         const rows = res.data || []
-        const headers = ['鏁呴殰缂栧彿', 'X鍙�', '瑁呭鍚嶇О', '浠诲姟鐘舵��', '宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�', '涓婃姤鍗曚綅', '涓婃姤鏃ユ湡']
+        const headers = ['鏁呴殰缂栧彿', 'rk_X鍙�', '璁惧鍚嶇О', '浠诲姟鐘舵��', '宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�', '涓婃姤鍗曚綅', '涓婃姤鏃ユ湡']
         const lines = [headers.join(',')].concat(rows.map(row => [
           row.faultCode, row.xNo, row.equipmentName, this.taskMeta(row.taskStatus).text,
           row.engineeringNameDesc, row.reportUnit, row.reportDate
diff --git a/emergency-repair-web/src/views/fault/SimpleApproval.vue b/emergency-repair-web/src/views/fault/SimpleApproval.vue
index aa303c8..b4e8421 100644
--- a/emergency-repair-web/src/views/fault/SimpleApproval.vue
+++ b/emergency-repair-web/src/views/fault/SimpleApproval.vue
@@ -9,7 +9,7 @@
     </div>
 
     <div class="toolbar">
-      <el-input v-model="keyword" placeholder="鏁呴殰缂栧彿 / 瑁呭鍚嶇О / 宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="load"></el-input>
+      <el-input v-model="keyword" placeholder="鏁呴殰缂栧彿 / 璁惧鍚嶇О / 宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="load"></el-input>
       <el-button icon="el-icon-search" type="primary" @click="load">鏌ヨ</el-button>
       <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
       <div class="spacer"></div>
@@ -23,13 +23,13 @@
           <template slot-scope="{ row }"><el-tag size="mini" :type="taskMeta(row.taskStatus).type">{{ taskMeta(row.taskStatus).text }}</el-tag></template>
         </el-table-column>
         <el-table-column prop="faultCode" label="鏁呴殰缂栧彿" width="180" fixed></el-table-column>
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
-        <el-table-column prop="equipmentName" label="瑁呭鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
+        <el-table-column prop="equipmentName" label="璁惧鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="parentSystem" label="鎵�灞炰笂绾х郴缁�" min-width="120"></el-table-column>
         <el-table-column prop="engineeringNameDesc" label="宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="repairRequirement" label="淇悊鎶�鏈姹�" min-width="200" show-overflow-tooltip></el-table-column>
         <el-table-column prop="engineeringMajor" label="宸ョ▼涓撲笟" width="100"></el-table-column>
-        <el-table-column prop="teamMajor" label="T闃熶笓涓�" width="100"></el-table-column>
+        <el-table-column prop="teamMajor" label="rk_T闃熶笓涓�" width="100"></el-table-column>
         <el-table-column prop="owner" label="璐熻矗浜�" width="90"></el-table-column>
         <el-table-column prop="reportUnit" label="涓婃姤鍗曚綅" min-width="130"></el-table-column>
         <el-table-column prop="reportDate" label="涓婃姤鏃ユ湡" width="110" align="center"></el-table-column>
@@ -39,7 +39,7 @@
         <el-table-column label="鏄惁鑷慨" width="90" align="center">
           <template slot-scope="{ row }">{{ yesNo(row.isSelfRepair) }}</template>
         </el-table-column>
-        <el-table-column label="鏄惁娑塇" width="90" align="center">
+        <el-table-column label="鏄惁娑塺k_H" width="90" align="center">
           <template slot-scope="{ row }">{{ yesNo(row.isHRelated) }}</template>
         </el-table-column>
         <el-table-column label="鐩戞祴椤圭洰" width="90" align="center">
@@ -66,7 +66,7 @@
         <el-form-item label="瀹℃壒鎰忚">
           <el-input v-model="form.opinion" type="textarea" :rows="3" placeholder="璇峰~鍐欏鎵规剰瑙�"></el-input>
         </el-form-item>
-        <el-alert v-if="form.result === 'REJECT'" :closable="false" type="warning" show-icon title="瀹℃壒涓嶉�氳繃鏃剁洿鎺ラ��鍥瀀CB鍔╃悊锛屼换鍔$姸鎬佸彉涓衡�滀笂绾ф墦鍥炩�濄��"></el-alert>
+        <el-alert v-if="form.result === 'REJECT'" :closable="false" type="warning" show-icon title="瀹℃壒涓嶉�氳繃鏃剁洿鎺ラ��鍥瀝k_XCB鍔╃悊锛屼换鍔$姸鎬佸彉涓衡�滀笂绾ф墦鍥炩�濄��"></el-alert>
       </el-form>
       <template slot="footer">
         <el-button @click="dialogVisible = false">鍙栨秷</el-button>
@@ -89,13 +89,13 @@
   },
   computed: {
     pageTitle() {
-      return { CMB: 'CMB棰嗗瀹℃壒', JCC_ASSISTANT: 'JCC鍔╃悊瀹℃壒', JCC_DIRECTOR: 'JCC澶勯暱瀹℃壒' }[this.stage]
+      return { CMB: 'rk_CMB棰嗗瀹℃壒', JCC_ASSISTANT: 'rk_JCC鍔╃悊瀹℃壒', JCC_DIRECTOR: 'rk_JCC澶勯暱瀹℃壒' }[this.stage]
     },
     subtitle() {
       return {
-        CMB: '瀵筙CB鍔╃悊瀹℃牳瀹屾垚鐨勬晠闅滆繘琛屾壒閲忛�氳繃鎴栭��鍥�',
-        JCC_ASSISTANT: '瀵笴MB棰嗗瀹℃壒瀹屾垚鐨勬晠闅滆繘琛屾壒閲忛�氳繃鎴栭��鍥�',
-        JCC_DIRECTOR: '瀵笿CC鍔╃悊瀹℃壒瀹屾垚鐨勬晠闅滆繘琛屾渶缁堝鎵癸紝閫氳繃鍚庤繘鍏ユ淳鍗�'
+        CMB: '瀵箁k_XCB鍔╃悊瀹℃牳瀹屾垚鐨勬晠闅滆繘琛屾壒閲忛�氳繃鎴栭��鍥�',
+        JCC_ASSISTANT: '瀵箁k_CMB棰嗗瀹℃壒瀹屾垚鐨勬晠闅滆繘琛屾壒閲忛�氳繃鎴栭��鍥�',
+        JCC_DIRECTOR: '瀵箁k_JCC鍔╃悊瀹℃壒瀹屾垚鐨勬晠闅滆繘琛屾渶缁堝鎵癸紝閫氳繃鍚庤繘鍏ユ淳鍗�'
       }[this.stage]
     },
     status() {
diff --git a/emergency-repair-web/src/views/fault/XcbReview.vue b/emergency-repair-web/src/views/fault/XcbReview.vue
index 7fd8603..9210a9c 100644
--- a/emergency-repair-web/src/views/fault/XcbReview.vue
+++ b/emergency-repair-web/src/views/fault/XcbReview.vue
@@ -2,17 +2,17 @@
   <div class="page">
     <div class="page-title">
       <div>
-        <h2>XCB鍔╃悊瀹℃牳</h2>
-        <p>鍒嗛厤鎵夸慨鍗曚綅銆佽窡韪妧鏈槦鍜岃姹傚畬宸ユ棩鏈燂紝瀹℃牳鍚庤繘鍏MB棰嗗瀹℃壒</p>
+        <h2>rk_XCB鍔╃悊瀹℃牳</h2>
+        <p>鍒嗛厤鎵夸慨鍗曚綅銆佽窡韪妧鏈槦鍜岃姹傚畬宸ユ棩鏈燂紝瀹℃牳鍚庤繘鍏k_CMB棰嗗瀹℃壒</p>
       </div>
     </div>
 
     <div class="toolbar query-toolbar">
-      <span class="filter-label">X鍙�</span>
+      <span class="filter-label">rk_X鍙�</span>
       <el-select v-model="query.xNo" placeholder="鍏ㄩ儴" clearable style="width:100px">
         <el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option>
       </el-select>
-      <span class="filter-label">T闃熶笓涓�</span>
+      <span class="filter-label">rk_T闃熶笓涓�</span>
       <el-select v-model="query.teamMajor" placeholder="鍏ㄩ儴" clearable style="width:130px">
         <el-option v-for="item in teamMajors" :key="item.id" :label="item.name" :value="item.name"></el-option>
       </el-select>
@@ -46,7 +46,7 @@
       <el-select v-model="query.status" placeholder="鍏ㄩ儴" clearable style="width:180px">
         <el-option v-for="(meta, key) in taskStatuses" :key="key" :label="meta.text" :value="key"></el-option>
       </el-select>
-      <el-input v-model="query.keyword" placeholder="鏁呴殰缂栧彿/瑁呭鍚嶇О/宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="load"></el-input>
+      <el-input v-model="query.keyword" placeholder="鏁呴殰缂栧彿/璁惧鍚嶇О/宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" clearable style="width:320px" @keyup.enter.native="load"></el-input>
       <el-button type="primary" icon="el-icon-search" @click="load">鏌ヨ</el-button>
       <el-button icon="el-icon-refresh-left" @click="resetQuery">閲嶇疆</el-button>
       <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
@@ -67,13 +67,13 @@
           <template slot-scope="{ row }"><el-tag size="mini" :type="taskMeta(row.taskStatus).type">{{ taskMeta(row.taskStatus).text }}</el-tag></template>
         </el-table-column>
         <el-table-column prop="faultCode" label="鏁呴殰缂栧彿" width="180" fixed></el-table-column>
-        <el-table-column prop="xNo" label="X鍙�" width="64"></el-table-column>
-        <el-table-column prop="equipmentName" label="瑁呭鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="xNo" label="rk_X鍙�" width="64"></el-table-column>
+        <el-table-column prop="equipmentName" label="璁惧鍚嶇О" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="parentSystem" label="鎵�灞炰笂绾х郴缁�" min-width="120"></el-table-column>
         <el-table-column prop="engineeringNameDesc" label="宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�" min-width="210" show-overflow-tooltip></el-table-column>
         <el-table-column prop="repairRequirement" label="淇悊鎶�鏈姹�" min-width="200" show-overflow-tooltip></el-table-column>
         <el-table-column prop="engineeringMajor" label="宸ョ▼涓撲笟" width="100"></el-table-column>
-        <el-table-column prop="teamMajor" label="T闃熶笓涓�" width="100"></el-table-column>
+        <el-table-column prop="teamMajor" label="rk_T闃熶笓涓�" width="100"></el-table-column>
         <el-table-column prop="owner" label="璐熻矗浜�" width="90"></el-table-column>
         <el-table-column prop="reportUnit" label="涓婃姤鍗曚綅" min-width="130"></el-table-column>
         <el-table-column prop="reportDate" label="涓婃姤鏃ユ湡" width="110" align="center"></el-table-column>
@@ -83,7 +83,7 @@
         <el-table-column label="鏄惁鑷慨" width="90" align="center">
           <template slot-scope="{ row }">{{ yesNo(row.isSelfRepair) }}</template>
         </el-table-column>
-        <el-table-column label="鏄惁娑塇" width="90" align="center">
+        <el-table-column label="鏄惁娑塺k_H" width="90" align="center">
           <template slot-scope="{ row }">{{ yesNo(row.isHRelated) }}</template>
         </el-table-column>
         <el-table-column label="鐩戞祴椤圭洰" width="90" align="center">
@@ -97,7 +97,7 @@
 
     <fault-edit-dialog :visible.sync="editVisible" :fault="editingFault" @saved="onFaultSaved"></fault-edit-dialog>
 
-    <el-dialog title="XCB鍔╃悊瀹℃牳" :visible.sync="dialogVisible" width="820px" append-to-body>
+    <el-dialog title="rk_XCB鍔╃悊瀹℃牳" :visible.sync="dialogVisible" width="820px" append-to-body>
       <el-alert :closable="false" type="info" show-icon title="鎵夸慨鍗曚綅鍙閫夈�傛帓搴忓墠7涓綔涓哄父鐢ㄦ壙淇崟浣嶄紭鍏堟樉绀猴紝澶变俊鍗曚綅鏍囪鎻愰啋銆�" style="margin-bottom:14px"></el-alert>
       <el-form :model="form" label-width="105px">
         <el-form-item label="宸查�夋晠闅�">
@@ -252,18 +252,7 @@
       api.get('/system/engineering-majors').then(res => { this.engineeringMajors = res.data || [] })
     },
     loadXNumbers() {
-      api.get('/system/tree', { params: { type: 'product' } }).then(res => {
-        const values = []
-        const walk = nodes => {
-          if (!nodes) return
-          nodes.forEach(node => {
-            if (node.nodeType === 'X_NO' || node.nodeLevel === 2) values.push(node.name)
-            walk(node.children)
-          })
-        }
-        walk(res.data)
-        this.xNumbers = Array.from(new Set(values.filter(Boolean))).sort((a, b) => a.localeCompare(b, 'zh-CN'))
-      })
+      api.get('/system/tree/product/x-numbers').then(res => { this.xNumbers = res.data || [] })
     },
     editSelected() {
       if (this.selection.length !== 1) return
@@ -303,7 +292,7 @@
       api.post('/faults/cancel', {
         ids: this.selection.map(item => item.id),
         reason: this.cancelReason.trim(),
-        cancelledBy: sessionStorage.getItem('realName') || 'XCB鍔╃悊'
+        cancelledBy: sessionStorage.getItem('realName') || 'rk_XCB鍔╃悊'
       }).then(() => {
         this.$message.success('閿�椤瑰畬鎴�')
         this.cancelVisible = false
diff --git a/emergency-repair-web/src/views/report/ReportPage.vue b/emergency-repair-web/src/views/report/ReportPage.vue
index 4f1e476..8a3c474 100644
--- a/emergency-repair-web/src/views/report/ReportPage.vue
+++ b/emergency-repair-web/src/views/report/ReportPage.vue
@@ -11,7 +11,7 @@
     <div v-if="type === 'overview'" class="stat-grid">
       <div class="stat-card"><div class="label">宸ョ▼鎬绘暟</div><div class="value">{{ stats.engineeringTotal || 0 }}</div></div>
       <div class="stat-card"><div class="label">閲嶅ぇ鏁呴殰</div><div class="value">{{ stats.majorFault || 0 }}</div></div>
-      <div class="stat-card"><div class="label">娑塇缁熻</div><div class="value">{{ stats.hRelated || 0 }}</div></div>
+      <div class="stat-card"><div class="label">娑塺k_H缁熻</div><div class="value">{{ stats.hRelated || 0 }}</div></div>
       <div class="stat-card"><div class="label">鐩戞祴椤圭洰</div><div class="value">{{ stats.monitorItem || 0 }}</div></div>
       <div class="stat-card"><div class="label">鏈畬宸�</div><div class="value">{{ stats.unfinished || 0 }}</div></div>
       <div class="stat-card"><div class="label">宸插畬宸�</div><div class="value">{{ stats.completed || 0 }}</div></div>
@@ -47,9 +47,9 @@
   computed: {
     title() {
       return {
-        overview: '瑁呭鏁呴殰鎬讳綋鍒嗘瀽',
+        overview: '璁惧鏁呴殰鎬讳綋鍒嗘瀽',
         'top-equipment': '鏁呴殰楂樺彂璁惧Top',
-        'by-team': '鎸塗缁熻',
+        'by-team': 'rk_鎸塗缁熻',
         contractors: '鍘傚缁翠慨鎯呭喌',
         trend: '鏁呴殰鏁� / 澶囧搧澶囦欢娑堣�楀勾搴﹁秼鍔�'
       }[this.type] || '缁熻鎶ヨ〃'
@@ -76,7 +76,7 @@
         { prop: 'value', label: '鏁呴殰鏁�', width: 100 }
       ]
       if (this.type === 'by-team') return [
-        { prop: 'name', label: 'T闃熶笓涓�', width: 150 },
+        { prop: 'name', label: 'rk_T闃熶笓涓�', width: 150 },
         { prop: 'faultCount', label: '鏁呴殰鏁�' },
         { prop: 'partsCount', label: '澶囧搧澶囦欢娑堣��' }
       ]
diff --git a/emergency-repair-web/src/views/system/ContractorManage.vue b/emergency-repair-web/src/views/system/ContractorManage.vue
index 1fa140f..89d381e 100644
--- a/emergency-repair-web/src/views/system/ContractorManage.vue
+++ b/emergency-repair-web/src/views/system/ContractorManage.vue
@@ -3,52 +3,52 @@
     <div class="page-title">
       <div>
         <h2>鎵夸慨鍗曚綅绠$悊</h2>
-        <p>鎺掑簭鐢ㄤ簬XCB鍔╃悊瀹℃牳鏃舵樉绀哄墠7涓父鐢ㄦ壙淇崟浣�</p>
+        <p>鏁版嵁鏉ユ簮锛氱粍缁囨満鏋勯儴闂ㄨ〃锛圖EPT_TYPE = cj锛�</p>
       </div>
-      <el-button type="primary" icon="el-icon-plus" @click="openCreate">鏂板鎵夸慨鍗曚綅</el-button>
-    </div>
-
-    <div class="toolbar">
-      <el-input v-model="keyword" placeholder="鍚嶇О / 绠�绉� / 鑱旂郴浜�" clearable style="width:260px"></el-input>
-      <el-button type="primary" icon="el-icon-search" @click="load">鏌ヨ</el-button>
       <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
     </div>
 
+    <div class="toolbar">
+      <el-input
+        v-model="keyword"
+        placeholder="鍚嶇О / 閮ㄩ棬缂栫爜 / 璐熻矗浜�"
+        clearable
+        style="width:280px"
+        @keyup.enter.native="load"
+      ></el-input>
+      <el-button type="primary" icon="el-icon-search" @click="load">鏌ヨ</el-button>
+      <div class="spacer"></div>
+      <span class="summary">鍏� {{ filteredRows.length }} 涓壙淇崟浣�</span>
+    </div>
+
     <div class="table-panel">
-      <el-table :data="filteredRows" border stripe height="calc(100vh - 245px)">
+      <el-table
+        v-loading="loading"
+        :data="filteredRows"
+        border
+        stripe
+        height="calc(100vh - 245px)"
+      >
         <el-table-column prop="sortNo" label="鎺掑簭" width="80" align="center"></el-table-column>
         <el-table-column label="甯哥敤" width="80" align="center">
-          <template slot-scope="{ $index }"><el-tag v-if="$index < 7" size="mini" type="success">鍓�7</el-tag><span v-else>-</span></template>
+          <template slot-scope="{ $index }">
+            <el-tag v-if="$index < 7" size="mini" type="success">鍓�7</el-tag>
+            <span v-else>-</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="name" label="鍚嶇О" min-width="220"></el-table-column>
-        <el-table-column prop="shortName" label="绠�绉�" width="150"></el-table-column>
-        <el-table-column prop="nature" label="鍗曚綅鎬ц川" width="130"></el-table-column>
-        <el-table-column prop="contact" label="鑱旂郴浜�" width="120"></el-table-column>
-        <el-table-column prop="dishonest" label="鏄惁澶变俊鍗曚綅" width="130">
-          <template slot-scope="{ row }"><el-tag size="mini" :type="row.dishonest === 1 ? 'danger' : 'success'">{{ row.dishonest === 1 ? '鏄�' : '鍚�' }}</el-tag></template>
-        </el-table-column>
-        <el-table-column prop="remark" label="澶囨敞" min-width="180" show-overflow-tooltip></el-table-column>
-        <el-table-column label="鎿嶄綔" width="130" fixed="right">
+        <el-table-column prop="name" label="鍚嶇О" min-width="240" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="deptCode" label="閮ㄩ棬缂栫爜" width="160"></el-table-column>
+        <el-table-column prop="contact" label="璐熻矗浜�" width="120"></el-table-column>
+        <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" width="160"></el-table-column>
+        <el-table-column label="鐘舵��" width="100" align="center">
           <template slot-scope="{ row }">
-            <el-button type="text" @click="openEdit(row)">缂栬緫</el-button>
-            <el-button type="text" class="danger-link" @click="remove(row)">鍒犻櫎</el-button>
+            <el-tag size="mini" :type="row.status === '1' ? 'info' : 'success'">
+              {{ row.status === '1' ? '鍋滅敤' : '姝e父' }}
+            </el-tag>
           </template>
         </el-table-column>
       </el-table>
     </div>
-
-    <el-dialog :title="form.id ? '缂栬緫鎵夸慨鍗曚綅' : '鏂板鎵夸慨鍗曚綅'" :visible.sync="dialogVisible" width="620px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="110px">
-        <el-form-item label="鍚嶇О" prop="name"><el-input v-model="form.name"></el-input></el-form-item>
-        <el-form-item label="绠�绉�" prop="shortName"><el-input v-model="form.shortName"></el-input></el-form-item>
-        <el-form-item label="鍗曚綅鎬ц川"><el-select v-model="form.nature" style="width:100%"><el-option v-for="item in natures" :key="item" :label="item" :value="item"></el-option></el-select></el-form-item>
-        <el-form-item label="鎺掑簭"><el-input-number v-model="form.sortNo" :min="1" style="width:180px"></el-input-number></el-form-item>
-        <el-form-item label="鑱旂郴浜�"><el-input v-model="form.contact"></el-input></el-form-item>
-        <el-form-item label="鏄惁澶变俊鍗曚綅"><el-switch v-model="form.dishonest" :active-value="1" :inactive-value="0"></el-switch></el-form-item>
-        <el-form-item label="澶囨敞"><el-input v-model="form.remark" type="textarea" :rows="3"></el-input></el-form-item>
-      </el-form>
-      <template slot="footer"><el-button @click="dialogVisible = false">鍙栨秷</el-button><el-button type="primary" :loading="saving" @click="save">淇濆瓨</el-button></template>
-    </el-dialog>
   </div>
 </template>
 <script>
@@ -57,42 +57,37 @@
   name: 'ContractorManage',
   data() {
     return {
-      rows: [], keyword: '', dialogVisible: false, saving: false,
-      natures: ['鍘傚', '鏈嶅姟鍟�', '缁煎悎鍗曚綅', 'T闃�', '鍏朵粬'],
-      form: {},
-      rules: {
-        name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }],
-        shortName: [{ required: true, message: '璇疯緭鍏ョ畝绉�', trigger: 'blur' }]
-      }
+      loading: false,
+      rows: [],
+      keyword: ''
     }
   },
   computed: {
     filteredRows() {
-      if (!this.keyword) return this.rows
-      const text = this.keyword.toLowerCase()
-      return this.rows.filter(item => [item.name, item.shortName, item.contact].some(value => String(value || '').toLowerCase().includes(text)))
+      const keyword = this.keyword.trim().toLowerCase()
+      if (!keyword) return this.rows
+      return this.rows.filter(item => [item.name, item.deptCode, item.contact]
+        .some(value => String(value || '').toLowerCase().includes(keyword)))
     }
   },
-  created() { this.load() },
+  created() {
+    this.load()
+  },
   methods: {
-    load() { api.get('/system/contractors').then(res => { this.rows = res.data || [] }) },
-    blank() { return { name: '', shortName: '', nature: '鍘傚', sortNo: this.rows.length + 1, contact: '', dishonest: 0, remark: '' } },
-    openCreate() { this.form = this.blank(); this.dialogVisible = true },
-    openEdit(row) { this.form = { ...row }; this.dialogVisible = true },
-    save() {
-      this.$refs.form.validate(valid => {
-        if (!valid) return
-        this.saving = true
-        const request = this.form.id ? api.put('/system/contractors/' + this.form.id, this.form) : api.post('/system/contractors', this.form)
-        request.then(() => { this.$message.success('淇濆瓨鎴愬姛'); this.dialogVisible = false; this.load() }).finally(() => { this.saving = false })
+    load() {
+      this.loading = true
+      api.get('/system/contractors').then(res => {
+        this.rows = res.data || []
+      }).finally(() => {
+        this.loading = false
       })
-    },
-    remove(row) {
-      this.$confirm(`纭畾鍒犻櫎鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' }).then(() => api.delete('/system/contractors/' + row.id)).then(() => {
-        this.$message.success('鍒犻櫎鎴愬姛'); this.load()
-      }).catch(() => {})
     }
   }
 }
 </script>
-<style scoped>.danger-link { color: #d9534f; }</style>
+<style scoped>
+.summary {
+  color: #6d7f91;
+  font-size: 13px;
+}
+</style>
diff --git a/emergency-repair-web/src/views/system/LoginLogManage.vue b/emergency-repair-web/src/views/system/LoginLogManage.vue
new file mode 100644
index 0000000..cbbd963
--- /dev/null
+++ b/emergency-repair-web/src/views/system/LoginLogManage.vue
@@ -0,0 +1,96 @@
+<template>
+  <div class="page">
+    <div class="page-title">
+      <div><h2>鐧诲綍鏃ュ織</h2><p>璁板綍姣忔鐧诲綍灏濊瘯鐨勭敤鎴枫�佹潵婧愬湴鍧�銆佺粨鏋滃拰鏃堕棿</p></div>
+    </div>
+    <div class="table-panel">
+      <div class="toolbar">
+        <el-input v-model="filters.keyword" clearable placeholder="璐︽埛鍚� / 濮撳悕" style="width:200px" @keyup.enter.native="search"></el-input>
+        <el-select v-model="filters.status" clearable placeholder="鐧诲綍缁撴灉" style="width:140px">
+          <el-option label="鎴愬姛" value="SUCCESS"></el-option>
+          <el-option label="澶辫触" value="FAIL"></el-option>
+        </el-select>
+        <el-date-picker
+          v-model="filters.dates"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+          style="width:260px"
+        ></el-date-picker>
+        <el-button type="primary" icon="el-icon-search" v-permission="'system:login-logs:query'" @click="search">鏌ヨ</el-button>
+        <el-button icon="el-icon-refresh-left" @click="reset">閲嶇疆</el-button>
+      </div>
+      <el-table v-loading="loading" :data="rows" border stripe height="calc(100vh - 250px)">
+        <el-table-column prop="loginTime" label="鐧诲綍鏃堕棿" width="180"></el-table-column>
+        <el-table-column prop="userName" label="璐︽埛鍚�" min-width="140"></el-table-column>
+        <el-table-column prop="nickName" label="濮撳悕" min-width="140"></el-table-column>
+        <el-table-column prop="deptName" label="鎵�灞為儴闂�" min-width="180" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="loginIp" label="鐧诲綍IP" min-width="150"></el-table-column>
+        <el-table-column label="缁撴灉" width="90" align="center">
+          <template slot-scope="{ row }">
+            <el-tag size="mini" :type="row.status === 'SUCCESS' ? 'success' : 'danger'">{{ row.status === 'SUCCESS' ? '鎴愬姛' : '澶辫触' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="message" label="璇存槑" min-width="180" show-overflow-tooltip></el-table-column>
+      </el-table>
+      <el-pagination
+        class="pager"
+        background
+        layout="total, sizes, prev, pager, next"
+        :total="total"
+        :current-page="current"
+        :page-size="size"
+        :page-sizes="[20, 50, 100]"
+        @current-change="handleCurrentChange"
+        @size-change="handleSizeChange"
+      ></el-pagination>
+    </div>
+  </div>
+</template>
+<script>
+import api from '@/api'
+export default {
+  name: 'LoginLogManage',
+  data() {
+    return {
+      rows: [],
+      total: 0,
+      current: 1,
+      size: 20,
+      loading: false,
+      filters: { keyword: '', status: '', dates: [] }
+    }
+  },
+  created() { this.load() },
+  methods: {
+    load() {
+      this.loading = true
+      const dates = this.filters.dates || []
+      api.get('/system/login-logs', {
+        params: {
+          keyword: this.filters.keyword || undefined,
+          status: this.filters.status || undefined,
+          startDate: dates[0] || undefined,
+          endDate: dates[1] || undefined,
+          current: this.current,
+          size: this.size
+        }
+      }).then(res => {
+        const data = res.data || {}
+        this.rows = data.records || []
+        this.total = data.total || 0
+      }).finally(() => { this.loading = false })
+    },
+    search() { this.current = 1; this.load() },
+    reset() { this.filters = { keyword: '', status: '', dates: [] }; this.search() },
+    handleCurrentChange(page) { this.current = page; this.load() },
+    handleSizeChange(size) { this.size = size; this.current = 1; this.load() }
+  }
+}
+</script>
+<style scoped>
+.toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
+.pager { margin-top: 12px; text-align: right; }
+</style>
diff --git a/emergency-repair-web/src/views/system/MenuManage.vue b/emergency-repair-web/src/views/system/MenuManage.vue
index 0849396..9d1f4af 100644
--- a/emergency-repair-web/src/views/system/MenuManage.vue
+++ b/emergency-repair-web/src/views/system/MenuManage.vue
@@ -1,33 +1,80 @@
 <template>
   <div class="page">
     <div class="page-title">
-      <div><h2>鑿滃崟绠$悊</h2><p>鑿滃崟涓哄姩鎬侀厤缃紝宸︿晶瀵艰埅鐩存帴璇诲彇杩欓噷鐨勬暟鎹�</p></div>
-      <el-button type="primary" icon="el-icon-plus" @click="openCreate(null)">鏂板涓�绾ц彍鍗�</el-button>
+      <div><h2>鑿滃崟绠$悊</h2><p>鏀寔鐩綍銆佽彍鍗曘�佹寜閽笁绉嶇被鍨嬶紱鎸夐挳鍙兘鎸傚湪鑿滃崟涓嬬骇锛岀敤浜庨厤缃〉闈㈡搷浣滄潈闄�</p></div>
+      <el-button type="primary" icon="el-icon-plus" v-permission="'system:menus:add'" @click="openCreate(null)">鏂板涓�绾ц彍鍗�</el-button>
     </div>
     <div class="table-panel">
       <el-table :data="tree" row-key="id" border default-expand-all :tree-props="{ children: 'children' }">
         <el-table-column prop="name" label="鑿滃崟鍚嶇О" min-width="220"></el-table-column>
-        <el-table-column prop="icon" label="鍥炬爣" width="200"><template slot-scope="{ row }"><span class="icon-cell"><i :class="row.icon"></i><span>{{ row.icon }}</span></span></template></el-table-column>
-        <el-table-column prop="url" label="璺敱鍦板潃" min-width="240"></el-table-column>
-        <el-table-column prop="permissions" label="鏉冮檺鏍囪瘑" min-width="170"></el-table-column>
-        <el-table-column prop="menuType" label="绫诲瀷" width="90"><template slot-scope="{ row }"><el-tag size="mini" :type="row.menuType === 0 ? 'info' : 'primary'">{{ row.menuType === 0 ? '鐩綍' : '鑿滃崟' }}</el-tag></template></el-table-column>
+        <el-table-column prop="icon" label="鍥炬爣" width="180">
+          <template slot-scope="{ row }">
+            <span v-if="row.menuType !== 2" class="icon-cell"><i :class="row.icon"></i><span>{{ row.icon }}</span></span>
+            <span v-else class="muted">-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="url" label="璺敱鍦板潃" min-width="220">
+          <template slot-scope="{ row }">
+            <span v-if="row.menuType !== 2">{{ row.url }}</span>
+            <span v-else class="muted">-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="permissions" label="鏉冮檺鏍囪瘑" min-width="200"></el-table-column>
+        <el-table-column label="绫诲瀷" width="90">
+          <template slot-scope="{ row }">
+            <el-tag size="mini" :type="typeTag(row.menuType)">{{ typeName(row.menuType) }}</el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="sortNo" label="鎺掑簭" width="80" align="center"></el-table-column>
-        <el-table-column prop="visible" label="鏄剧ず" width="80" align="center"><template slot-scope="{ row }"><el-tag size="mini" :type="row.visible === 0 ? 'info' : 'success'">{{ row.visible === 0 ? '闅愯棌' : '鏄剧ず' }}</el-tag></template></el-table-column>
-        <el-table-column label="鎿嶄綔" width="200" fixed="right"><template slot-scope="{ row }"><el-button type="text" @click="openCreate(row)">鏂板涓嬬骇</el-button><el-button type="text" @click="openEdit(row)">缂栬緫</el-button><el-button type="text" class="danger-link" @click="remove(row)">鍒犻櫎</el-button></template></el-table-column>
+        <el-table-column label="鏄剧ず" width="80" align="center">
+          <template slot-scope="{ row }">
+            <el-tag size="mini" :type="row.visible === 0 ? 'info' : 'success'">{{ row.visible === 0 ? '闅愯棌' : '鏄剧ず' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" width="270" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button v-if="row.menuType !== 2" type="text" v-permission="'system:menus:add'" @click="openCreate(row)">鏂板涓嬬骇</el-button>
+            <el-button v-if="row.menuType === 1" type="text" v-permission="'system:menus:buttons'" @click="openButtons(row)">椤甸潰鎸夐挳</el-button>
+            <el-button type="text" v-permission="'system:menus:edit'" @click="openEdit(row)">缂栬緫</el-button>
+            <el-button type="text" class="danger-link" v-permission="'system:menus:delete'" @click="remove(row)">鍒犻櫎</el-button>
+          </template>
+        </el-table-column>
       </el-table>
     </div>
+
     <el-dialog :title="form.id ? '缂栬緫鑿滃崟' : '鏂板鑿滃崟'" :visible.sync="dialogVisible" width="640px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="105px">
         <el-form-item label="涓婄骇鑿滃崟"><el-input :value="parentName" readonly></el-input></el-form-item>
+        <el-form-item label="鑿滃崟绫诲瀷">
+          <el-radio-group v-model="form.menuType">
+            <el-radio-button v-for="item in typeOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="鑿滃崟鍚嶇О" prop="name"><el-input v-model="form.name"></el-input></el-form-item>
-        <el-form-item label="鑿滃崟绫诲瀷"><el-radio-group v-model="form.menuType"><el-radio-button :label="0">鐩綍</el-radio-button><el-radio-button :label="1">鑿滃崟</el-radio-button></el-radio-group></el-form-item>
-        <el-form-item label="璺敱鍦板潃"><el-input v-model="form.url" placeholder="/fault/report"></el-input></el-form-item>
-        <el-form-item label="鍥炬爣"><el-input v-model="form.icon" placeholder="el-icon-tickets"></el-input></el-form-item>
-        <el-form-item label="鏉冮檺鏍囪瘑"><el-input v-model="form.permissions"></el-input></el-form-item>
+        <el-form-item v-if="form.menuType !== 2" label="璺敱鍦板潃"><el-input v-model="form.url" placeholder="/fault/report"></el-input></el-form-item>
+        <el-form-item v-if="form.menuType !== 2" label="鍥炬爣"><el-input v-model="form.icon" placeholder="el-icon-tickets"></el-input></el-form-item>
+        <el-form-item label="鏉冮檺鏍囪瘑" :prop="form.menuType === 2 ? 'permissions' : ''">
+          <el-input v-model="form.permissions" :placeholder="form.menuType === 2 ? '渚嬪 fault:report:add' : '鍙�夛紝渚嬪 fault-report'"></el-input>
+        </el-form-item>
         <el-form-item label="鎺掑簭"><el-input-number v-model="form.sortNo" :min="1" style="width:160px"></el-input-number></el-form-item>
         <el-form-item label="鏄惁鏄剧ず"><el-switch v-model="form.visible" :active-value="1" :inactive-value="0"></el-switch></el-form-item>
       </el-form>
       <template slot="footer"><el-button @click="dialogVisible = false">鍙栨秷</el-button><el-button type="primary" :loading="saving" @click="save">淇濆瓨</el-button></template>
+    </el-dialog>
+
+    <el-dialog :title="'椤甸潰鎸夐挳閰嶇疆 - ' + (buttonMenu.name || '')" :visible.sync="buttonDialogVisible" width="620px" append-to-body>
+      <p class="dialog-tip">涓嬪垪鎸夐挳鏉ヨ嚜鍚庣鎸夐挳鐩綍锛屽嬀閫夊悗鑷姩鐢熸垚鎸夐挳绫诲瀷鑿滃崟锛屽彲鍦ㄨ彍鍗曟潈闄愮鐞嗕腑鎺堟潈缁欒鑹层��</p>
+      <el-checkbox-group v-model="buttonSelected" class="button-list">
+        <el-checkbox v-for="item in buttonAvailable" :key="item.code" :label="item.code" class="button-item">
+          <span class="button-name">{{ item.name }}</span>
+          <span class="button-code">{{ item.code }}</span>
+        </el-checkbox>
+      </el-checkbox-group>
+      <div v-if="!buttonAvailable.length" class="empty-tip">璇ラ〉闈㈡殏鏈櫥璁板彲鐢ㄦ寜閽�</div>
+      <template slot="footer">
+        <el-button @click="buttonDialogVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" :loading="buttonSaving" @click="saveButtons">淇濆瓨鎸夐挳</el-button>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -35,19 +82,130 @@
 import api from '@/api'
 export default {
   name: 'MenuManage',
-  data() { return { tree: [], flat: [], dialogVisible: false, saving: false, form: {}, rules: { name: [{ required: true, message: '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' }] } } },
-  computed: { parentName() { const parent = this.flat.find(item => item.id === this.form.pid); return parent ? parent.name : '鏃狅紙涓�绾ц彍鍗曪級' } },
+  data() {
+    return {
+      tree: [],
+      flat: [],
+      dialogVisible: false,
+      saving: false,
+      form: {},
+      parent: null,
+      rules: { name: [{ required: true, message: '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' }] },
+      buttonDialogVisible: false,
+      buttonSaving: false,
+      buttonMenu: {},
+      buttonAvailable: [],
+      buttonSelected: []
+    }
+  },
+  computed: {
+    parentName() {
+      if (!this.parent) return this.form.pid && this.form.pid !== 0 ? '涓婄骇鑿滃崟' : '鏃狅紙涓�绾ц彍鍗曪級'
+      return this.parent.name
+    },
+    typeOptions() {
+      if (this.parent && this.parent.menuType === 1) {
+        return [{ label: '鎸夐挳', value: 2 }]
+      }
+      if (this.parent && this.parent.menuType === 0) {
+        return [{ label: '鐩綍', value: 0 }, { label: '鑿滃崟', value: 1 }]
+      }
+      if (this.form.id && this.form.menuType === 2) {
+        return [{ label: '鎸夐挳', value: 2 }]
+      }
+      return [{ label: '鐩綍', value: 0 }, { label: '鑿滃崟', value: 1 }]
+    }
+  },
   created() { this.load() },
   methods: {
-    load() { Promise.all([api.get('/system/menu/nav'), api.get('/system/menu/list')]).then(([tree, list]) => { this.tree = tree.data || []; this.flat = list.data || [] }) },
-    openCreate(parent) { this.form = { pid: parent ? parent.id : 0, name: '', url: '', icon: 'el-icon-tickets', menuType: parent ? 1 : 0, permissions: '', sortNo: 10, visible: 1 }; this.dialogVisible = true },
-    openEdit(row) { this.form = { ...row }; this.dialogVisible = true },
-    save() { this.$refs.form.validate(valid => { if (!valid) return; this.saving = true; const request = this.form.id ? api.put('/system/menu/' + this.form.id, this.form) : api.post('/system/menu', this.form); request.then(() => { this.$message.success('淇濆瓨鎴愬姛'); this.dialogVisible = false; this.load() }).finally(() => { this.saving = false }) }) },
-    remove(row) { this.$confirm(`纭畾鍒犻櫎鑿滃崟鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' }).then(() => api.delete('/system/menu/' + row.id)).then(() => { this.$message.success('鍒犻櫎鎴愬姛'); this.load() }).catch(() => {}) }
+    load() {
+      Promise.all([api.get('/system/menu/tree'), api.get('/system/menu/list')]).then(([tree, list]) => {
+        this.tree = tree.data || []
+        this.flat = list.data || []
+      })
+    },
+    typeName(type) {
+      return type === 2 ? '鎸夐挳' : type === 0 ? '鐩綍' : '鑿滃崟'
+    },
+    typeTag(type) {
+      return type === 2 ? 'warning' : type === 0 ? 'info' : 'primary'
+    },
+    openCreate(parent) {
+      this.parent = parent || null
+      const menuType = parent && parent.menuType === 1 ? 2 : parent ? 1 : 0
+      this.form = {
+        pid: parent ? parent.id : 0,
+        name: '',
+        url: '',
+        icon: menuType === 2 ? '' : 'el-icon-tickets',
+        menuType,
+        permissions: '',
+        sortNo: 10,
+        visible: 1
+      }
+      this.dialogVisible = true
+    },
+    openEdit(row) {
+      this.parent = this.flat.find(item => item.id === row.pid) || null
+      this.form = { ...row }
+      this.dialogVisible = true
+    },
+    save() {
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+        if (this.form.menuType === 2 && !this.form.permissions) {
+          this.$message.error('鎸夐挳鏉冮檺鏍囪瘑涓嶈兘涓虹┖')
+          return
+        }
+        const payload = { ...this.form }
+        if (payload.menuType === 2) {
+          payload.url = null
+          payload.icon = null
+        }
+        delete payload.children
+        this.saving = true
+        const request = payload.id ? api.put('/system/menu/' + payload.id, payload) : api.post('/system/menu', payload)
+        request.then(() => {
+          this.$message.success('淇濆瓨鎴愬姛')
+          this.dialogVisible = false
+          this.load()
+        }).finally(() => { this.saving = false })
+      })
+    },
+    remove(row) {
+      this.$confirm(`纭畾鍒犻櫎鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' })
+        .then(() => api.delete('/system/menu/' + row.id))
+        .then(() => { this.$message.success('鍒犻櫎鎴愬姛'); this.load() })
+        .catch(() => {})
+    },
+    openButtons(row) {
+      this.buttonMenu = row
+      api.get('/system/menu/buttons', { params: { menuId: row.id } }).then(res => {
+        const data = res.data || {}
+        this.buttonAvailable = data.available || []
+        this.buttonSelected = data.selected || []
+        this.buttonDialogVisible = true
+      })
+    },
+    saveButtons() {
+      this.buttonSaving = true
+      api.post('/system/menu/buttons', { menuId: this.buttonMenu.id, codes: this.buttonSelected }).then(() => {
+        this.$message.success('椤甸潰鎸夐挳宸插悓姝�')
+        this.buttonDialogVisible = false
+        this.load()
+      }).finally(() => { this.buttonSaving = false })
+    }
   }
 }
 </script>
 <style scoped>
 .danger-link { color: #d9534f; }
 .icon-cell { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
+.muted { color: #a3afbc; }
+.dialog-tip { margin: 0 0 12px; color: #738296; font-size: 13px; }
+.button-list { display: flex; flex-direction: column; gap: 2px; }
+.button-item { margin-right: 0; }
+.button-name { color: #31465d; }
+.button-code { margin-left: 8px; color: #93a2b1; font-size: 12px; }
+.empty-tip { padding: 30px 0; color: #93a2b1; text-align: center; }
 </style>
diff --git a/emergency-repair-web/src/views/system/MenuPermission.vue b/emergency-repair-web/src/views/system/MenuPermission.vue
index f712cad..347bd0a 100644
--- a/emergency-repair-web/src/views/system/MenuPermission.vue
+++ b/emergency-repair-web/src/views/system/MenuPermission.vue
@@ -15,11 +15,11 @@
     <div class="permission-grid">
       <section class="permission-panel">
         <div class="panel-head"><h3>绉熸埛鑿滃崟鎺堟潈</h3><el-checkbox v-model="tenantCheckAll" @change="checkAll('tenant')">鍏ㄩ��</el-checkbox></div>
-        <el-tree ref="tenantTree" :data="menus" node-key="id" show-checkbox check-strictly default-expand-all :props="{ label: 'name', children: 'children' }"></el-tree>
+        <el-tree ref="tenantTree" :data="menus" node-key="id" show-checkbox default-expand-all :render-content="renderContent" :props="{ label: 'name', children: 'children' }" @check="syncCheckAllState('tenant')"></el-tree>
       </section>
       <section class="permission-panel">
         <div class="panel-head"><h3>瑙掕壊鑿滃崟鎺堟潈</h3><el-checkbox v-model="roleCheckAll" @change="checkAll('role')">鍏ㄩ��</el-checkbox></div>
-        <el-tree ref="roleTree" :data="menus" node-key="id" show-checkbox check-strictly default-expand-all :props="{ label: 'name', children: 'children' }"></el-tree>
+        <el-tree ref="roleTree" :data="menus" node-key="id" show-checkbox default-expand-all :render-content="renderContent" :props="{ label: 'name', children: 'children' }" @check="syncCheckAllState('role')"></el-tree>
       </section>
       <section class="permission-panel intersection-panel">
         <div class="panel-head"><h3>浜ら泦棰勮</h3></div>
@@ -34,14 +34,16 @@
   name: 'MenuPermission',
   data() { return { menus: [], flatMenus: [], roles: [], tenants: [], tenantId: 1, roleId: null, saving: false, tenantCheckAll: false, roleCheckAll: false, selectedTenantIds: [], selectedRoleIds: [] } },
   computed: {
-    allMenuIds() { return this.flatMenus.map(item => item.id) },
+    allMenuIds() { return this.flatMenus.map(item => String(item.id)) },
+    parentMenuIds() { const ids = new Set(); this.flatMenus.forEach(item => ids.add(String(item.pid))); return ids },
+    leafMenuIds() { return this.flatMenus.filter(item => !this.parentMenuIds.has(String(item.id))).map(item => String(item.id)) },
     effectiveCount() { return this.effectiveMenus.length },
-    effectiveMenus() { return this.flatMenus.filter(item => this.selectedTenantIds.includes(item.id) && this.selectedRoleIds.includes(item.id)) }
+    effectiveMenus() { return this.flatMenus.filter(item => this.selectedTenantIds.map(String).includes(String(item.id)) && this.selectedRoleIds.map(String).includes(String(item.id))) }
   },
   created() { this.loadBase() },
   methods: {
     loadBase() {
-      Promise.all([api.get('/system/menu/nav'), api.get('/system/menu/list'), api.get('/system/roles'), api.get('/system/tenants')]).then(([tree, flat, roles, tenants]) => {
+      Promise.all([api.get('/system/menu/tree'), api.get('/system/menu/list'), api.get('/system/roles'), api.get('/system/tenants')]).then(([tree, flat, roles, tenants]) => {
         this.menus = tree.data || []; this.flatMenus = flat.data || []; this.roles = roles.data || []; this.tenants = tenants.data || []
         if (!this.roleId && this.roles.length) this.roleId = this.roles[0].id
         this.$nextTick(this.loadPermissions)
@@ -54,19 +56,28 @@
         this.selectedTenantIds = data.tenantMenuIds || []
         this.selectedRoleIds = data.roleMenuIds || []
         this.$nextTick(() => {
-          this.$refs.tenantTree.setCheckedKeys(this.selectedTenantIds)
-          this.$refs.roleTree.setCheckedKeys(this.selectedRoleIds)
+          this.$refs.tenantTree.setCheckedKeys(this.leafKeys(this.selectedTenantIds))
+          this.$refs.roleTree.setCheckedKeys(this.leafKeys(this.selectedRoleIds))
+          this.syncCheckAllState('tenant'); this.syncCheckAllState('role')
         })
       })
     },
+    renderContent(h, { data }) {
+      const label = data.menuType === 2 ? data.name + ' [鎸夐挳 ' + (data.permissions || '') + ']' : data.name
+      return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label)
+    },
+    leafKeys(ids) { const leafIds = new Set(this.leafMenuIds); return (ids || []).map(String).filter(id => leafIds.has(id)) },
+    checkedMenuIds(ref) { return [...new Set([...(ref.getCheckedKeys(false) || []), ...(ref.getHalfCheckedKeys() || [])].map(String))] },
+    syncCheckAllState(target) { const ref = target === 'tenant' ? this.$refs.tenantTree : this.$refs.roleTree; if (!ref) return; const checked = this.checkedMenuIds(ref).length === this.allMenuIds.length; if (target === 'tenant') this.tenantCheckAll = checked; if (target === 'role') this.roleCheckAll = checked },
     checkAll(target) {
       const checked = target === 'tenant' ? this.tenantCheckAll : this.roleCheckAll
       const ref = target === 'tenant' ? this.$refs.tenantTree : this.$refs.roleTree
       ref.setCheckedKeys(checked ? this.allMenuIds : [])
+      this.$nextTick(() => this.syncCheckAllState(target))
     },
     save() {
-      const tenantMenuIds = this.$refs.tenantTree.getCheckedKeys()
-      const roleMenuIds = this.$refs.roleTree.getCheckedKeys()
+      const tenantMenuIds = this.checkedMenuIds(this.$refs.tenantTree)
+      const roleMenuIds = this.checkedMenuIds(this.$refs.roleTree)
       this.saving = true
       api.post('/system/menu-permissions', { tenantId: this.tenantId, roleId: this.roleId, tenantMenuIds, roleMenuIds }).then(() => {
         this.selectedTenantIds = tenantMenuIds; this.selectedRoleIds = roleMenuIds; this.$message.success('鑿滃崟鏉冮檺宸蹭繚瀛�')
@@ -76,6 +87,8 @@
 }
 </script>
 <style scoped>
+::v-deep .perm-button-node { color: #b8791f; }
+
 .permission-grid { display: grid; grid-template-columns: 1fr 1fr 320px; gap: 12px; }
 .permission-panel { min-height: calc(100vh - 220px); padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; overflow: auto; }
 .panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
diff --git a/emergency-repair-web/src/views/system/OperationLogManage.vue b/emergency-repair-web/src/views/system/OperationLogManage.vue
new file mode 100644
index 0000000..51e337a
--- /dev/null
+++ b/emergency-repair-web/src/views/system/OperationLogManage.vue
@@ -0,0 +1,104 @@
+<template>
+  <div class="page">
+    <div class="page-title">
+      <div><h2>鎿嶄綔鏃ュ織</h2><p>鑷姩璁板綍鏂板銆佷慨鏀广�佸垹闄ょ瓑鍐欐搷浣滐紝鐧诲綍琛屼负鍗曠嫭璁板叆鐧诲綍鏃ュ織</p></div>
+    </div>
+    <div class="table-panel">
+      <div class="toolbar">
+        <el-input v-model="filters.keyword" clearable placeholder="璐︽埛鍚� / 濮撳悕 / 璇锋眰鍦板潃" style="width:230px" @keyup.enter.native="search"></el-input>
+        <el-select v-model="filters.module" clearable placeholder="妯″潡" style="width:160px">
+          <el-option v-for="item in modules" :key="item" :label="item" :value="item"></el-option>
+        </el-select>
+        <el-select v-model="filters.operationType" clearable placeholder="鎿嶄綔绫诲瀷" style="width:150px">
+          <el-option v-for="item in operationTypes" :key="item" :label="item" :value="item"></el-option>
+        </el-select>
+        <el-date-picker
+          v-model="filters.dates"
+          type="daterange"
+          value-format="yyyy-MM-dd"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+          style="width:260px"
+        ></el-date-picker>
+        <el-button type="primary" icon="el-icon-search" v-permission="'system:operation-logs:query'" @click="search">鏌ヨ</el-button>
+        <el-button icon="el-icon-refresh-left" @click="reset">閲嶇疆</el-button>
+      </div>
+      <el-table v-loading="loading" :data="rows" border stripe height="calc(100vh - 250px)">
+        <el-table-column prop="createTime" label="鎿嶄綔鏃堕棿" width="180"></el-table-column>
+        <el-table-column prop="userName" label="璐︽埛鍚�" width="130"></el-table-column>
+        <el-table-column prop="nickName" label="濮撳悕" width="120"></el-table-column>
+        <el-table-column prop="module" label="妯″潡" width="130"></el-table-column>
+        <el-table-column prop="operationType" label="鎿嶄綔绫诲瀷" width="110" align="center"></el-table-column>
+        <el-table-column prop="targetType" label="鐩爣绫诲瀷" width="120"></el-table-column>
+        <el-table-column prop="targetId" label="鐩爣ID" width="180" show-overflow-tooltip></el-table-column>
+        <el-table-column label="缁撴灉" width="80" align="center">
+          <template slot-scope="{ row }">
+            <el-tag size="mini" :type="row.result === '鎴愬姛' ? 'success' : 'danger'">{{ row.result }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="requestMethod" label="鏂瑰紡" width="80" align="center"></el-table-column>
+        <el-table-column prop="requestUri" label="璇锋眰鍦板潃" min-width="240" show-overflow-tooltip></el-table-column>
+      </el-table>
+      <el-pagination
+        class="pager"
+        background
+        layout="total, sizes, prev, pager, next"
+        :total="total"
+        :current-page="current"
+        :page-size="size"
+        :page-sizes="[20, 50, 100]"
+        @current-change="handleCurrentChange"
+        @size-change="handleSizeChange"
+      ></el-pagination>
+    </div>
+  </div>
+</template>
+<script>
+import api from '@/api'
+export default {
+  name: 'OperationLogManage',
+  data() {
+    return {
+      rows: [],
+      total: 0,
+      current: 1,
+      size: 20,
+      loading: false,
+      modules: ['鏁呴殰绠$悊', '瀹屽伐绠$悊', '瀹屽伐楠屾敹', '缁熻鎶ヨ〃', '鐢ㄦ埛绠$悊', '瑙掕壊绠$悊', '鑿滃崟绠$悊', '鎵夸慨鍗曚綅绠$悊', '绯荤粺绠$悊', '鍏朵粬'],
+      operationTypes: ['鏂板', '淇敼', '鍒犻櫎', '鎺堟潈', '瀹℃壒', '娲惧崟', '閿�椤�', '鎻愪氦', '鐢宠', '鎵撳嵃', '璇勭骇', '鑿滃崟鎸夐挳閰嶇疆'],
+      filters: { keyword: '', module: '', operationType: '', dates: [] }
+    }
+  },
+  created() { this.load() },
+  methods: {
+    load() {
+      this.loading = true
+      const dates = this.filters.dates || []
+      api.get('/system/operation-logs', {
+        params: {
+          keyword: this.filters.keyword || undefined,
+          module: this.filters.module || undefined,
+          operationType: this.filters.operationType || undefined,
+          startDate: dates[0] || undefined,
+          endDate: dates[1] || undefined,
+          current: this.current,
+          size: this.size
+        }
+      }).then(res => {
+        const data = res.data || {}
+        this.rows = data.records || []
+        this.total = data.total || 0
+      }).finally(() => { this.loading = false })
+    },
+    search() { this.current = 1; this.load() },
+    reset() { this.filters = { keyword: '', module: '', operationType: '', dates: [] }; this.search() },
+    handleCurrentChange(page) { this.current = page; this.load() },
+    handleSizeChange(size) { this.size = size; this.current = 1; this.load() }
+  }
+}
+</script>
+<style scoped>
+.toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
+.pager { margin-top: 12px; text-align: right; }
+</style>
diff --git a/emergency-repair-web/src/views/system/OrganizationManage.vue b/emergency-repair-web/src/views/system/OrganizationManage.vue
new file mode 100644
index 0000000..519eb3c
--- /dev/null
+++ b/emergency-repair-web/src/views/system/OrganizationManage.vue
@@ -0,0 +1,127 @@
+<template>
+  <div class="page">
+    <div class="page-title">
+      <div>
+        <h2>缁勭粐鏈烘瀯</h2>
+        <p>閮ㄩ棬鏍戣〃鏉ユ簮浜� SYS_DEPT锛汥EPT_TYPE 涓� cj 鐨勯儴闂ㄥ嵆鎵夸慨鍗曚綅</p>
+      </div>
+      <el-button icon="el-icon-refresh" @click="load">鍒锋柊</el-button>
+    </div>
+
+    <div class="toolbar">
+      <el-input v-model="keyword" placeholder="閮ㄩ棬鍚嶇О / 閮ㄩ棬缂栫爜 / 璐熻矗浜�" clearable style="width:280px"></el-input>
+      <el-select v-model="deptType" clearable placeholder="閮ㄩ棬绫诲瀷" style="width:160px">
+        <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+      </el-select>
+      <el-button type="primary" icon="el-icon-search" @click="load">鏌ヨ</el-button>
+      <div class="spacer"></div>
+      <span class="summary">鍏� {{ rows.length }} 涓儴闂紝鍏朵腑鎵夸慨鍗曚綅 {{ contractorCount }} 涓�</span>
+    </div>
+
+    <div class="table-panel">
+      <el-table
+        v-loading="loading"
+        :data="filteredTree"
+        row-key="id"
+        border
+        stripe
+        default-expand-all
+        height="calc(100vh - 245px)"
+        :tree-props="{ children: 'children' }"
+      >
+        <el-table-column prop="name" label="閮ㄩ棬鍚嶇О" min-width="260"></el-table-column>
+        <el-table-column prop="deptCode" label="閮ㄩ棬缂栫爜" width="150"></el-table-column>
+        <el-table-column label="閮ㄩ棬绫诲瀷" width="130" align="center">
+          <template slot-scope="{ row }">
+            <el-tag v-if="row.deptType === 'cj'" size="mini" type="success">鎵夸慨鍗曚綅</el-tag>
+            <el-tag v-else size="mini" type="info">{{ row.deptType || '-' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="leader" label="璐熻矗浜�" width="120"></el-table-column>
+        <el-table-column prop="phone" label="鑱旂郴鐢佃瘽" width="150"></el-table-column>
+        <el-table-column prop="orderNum" label="鎺掑簭" width="80" align="center"></el-table-column>
+        <el-table-column label="鐘舵��" width="90" align="center">
+          <template slot-scope="{ row }">
+            <el-tag size="mini" :type="row.status === '1' ? 'info' : 'success'">{{ row.status === '1' ? '鍋滅敤' : '姝e父' }}</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+<script>
+import api from '@/api'
+export default {
+  name: 'OrganizationManage',
+  data() {
+    return {
+      loading: false,
+      rows: [],
+      keyword: '',
+      deptType: ''
+    }
+  },
+  computed: {
+    typeOptions() {
+      const values = Array.from(new Set(this.rows.map(item => item.deptType).filter(Boolean))).sort()
+      return values.map(value => ({ value, label: value === 'cj' ? '鎵夸慨鍗曚綅' : value }))
+    },
+    contractorCount() {
+      return this.rows.filter(item => item.deptType === 'cj').length
+    },
+    tree() {
+      return this.buildTree(this.rows)
+    },
+    filteredTree() {
+      const keyword = this.keyword.trim().toLowerCase()
+      const type = this.deptType
+      const hit = node => {
+        const textOk = !keyword || [node.name, node.deptCode, node.leader]
+          .some(value => String(value || '').toLowerCase().includes(keyword))
+        const typeOk = !type || node.deptType === type
+        return textOk && typeOk
+      }
+      const walk = nodes => nodes.reduce((result, node) => {
+        const children = walk(node.children || [])
+        if (hit(node) || children.length) {
+          result.push({ ...node, children })
+        }
+        return result
+      }, [])
+      return walk(this.tree)
+    }
+  },
+  created() { this.load() },
+  methods: {
+    load() {
+      this.loading = true
+      api.get('/system/depts').then(res => {
+        this.rows = res.data || []
+      }).finally(() => { this.loading = false })
+    },
+    buildTree(rows) {
+      const nodes = rows.map(row => ({ ...row, children: [] }))
+      const map = new Map(nodes.map(node => [String(node.id), node]))
+      const roots = []
+      nodes.forEach(node => {
+        const parentId = node.parentId == null ? null : String(node.parentId)
+        if (parentId && parentId !== String(node.id) && map.has(parentId)) {
+          map.get(parentId).children.push(node)
+        } else {
+          roots.push(node)
+        }
+      })
+      const sortNodes = list => {
+        list.sort((left, right) => (Number(left.orderNum) || 0) - (Number(right.orderNum) || 0)
+          || String(left.name || '').localeCompare(String(right.name || ''), 'zh'))
+        list.forEach(item => sortNodes(item.children || []))
+      }
+      sortNodes(roots)
+      return roots
+    }
+  }
+}
+</script>
+<style scoped>
+.summary { color: #6d7f91; font-size: 13px; }
+</style>
diff --git a/emergency-repair-web/src/views/system/RoleManage.vue b/emergency-repair-web/src/views/system/RoleManage.vue
index fbaec3e..16dd95f 100644
--- a/emergency-repair-web/src/views/system/RoleManage.vue
+++ b/emergency-repair-web/src/views/system/RoleManage.vue
@@ -1,8 +1,8 @@
 <template>
   <div class="page">
     <div class="page-title">
-      <div><h2>瑙掕壊绠$悊</h2><p>绗竴鐗堟殏涓嶅惎鐢ㄨ鑹叉帶鍒讹紝椤甸潰鍜屾暟鎹粨鏋勪负鍚庣画鏉冮檺鍚敤棰勭暀</p></div>
-      <el-button type="primary" icon="el-icon-plus" @click="openCreate">鏂板瑙掕壊</el-button>
+      <div><h2>瑙掕壊绠$悊</h2><p>缁存姢瑙掕壊淇℃伅锛屽苟鍙寜瑙掕壊鎵归噺璁剧疆鎵�灞炵敤鎴�</p></div>
+      <el-button type="primary" icon="el-icon-plus" v-permission="'system:roles:add'" @click="openCreate">鏂板瑙掕壊</el-button>
     </div>
     <div class="table-panel">
       <el-table :data="rows" border stripe height="calc(100vh - 190px)">
@@ -10,19 +10,75 @@
         <el-table-column prop="name" label="瑙掕壊鍚嶇О" min-width="180"></el-table-column>
         <el-table-column prop="code" label="瑙掕壊缂栫爜" width="170"></el-table-column>
         <el-table-column prop="roleType" label="瑙掕壊绫诲瀷" width="170"></el-table-column>
-        <el-table-column prop="remark" label="澶囨敞" min-width="260" show-overflow-tooltip></el-table-column>
-        <el-table-column label="鎿嶄綔" width="130" fixed="right"><template slot-scope="{ row }"><el-button type="text" @click="openEdit(row)">缂栬緫</el-button><el-button type="text" class="danger-link" @click="remove(row)">鍒犻櫎</el-button></template></el-table-column>
+        <el-table-column prop="remark" label="澶囨敞" min-width="220" show-overflow-tooltip></el-table-column>
+        <el-table-column label="鎿嶄綔" width="220" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button type="text" v-permission="'system:roles:assign'" @click="openUserSetting(row)">鐢ㄦ埛璁剧疆</el-button>
+            <el-button type="text" v-permission="'system:roles:edit'" @click="openEdit(row)">缂栬緫</el-button>
+            <el-button type="text" class="danger-link" v-permission="'system:roles:delete'" @click="remove(row)">鍒犻櫎</el-button>
+          </template>
+        </el-table-column>
       </el-table>
     </div>
+
     <el-dialog :title="form.id ? '缂栬緫瑙掕壊' : '鏂板瑙掕壊'" :visible.sync="dialogVisible" width="560px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="95px">
         <el-form-item label="瑙掕壊鍚嶇О" prop="name"><el-input v-model="form.name"></el-input></el-form-item>
         <el-form-item label="瑙掕壊缂栫爜" prop="code"><el-input v-model="form.code"></el-input></el-form-item>
-        <el-form-item label="瑙掕壊绫诲瀷"><el-select v-model="form.roleType" style="width:100%"><el-option label="涓氬姟瑙掕壊" value="BUSINESS"></el-option><el-option label="绯荤粺绠$悊鍛�" value="SYSTEM_ADMIN"></el-option><el-option label="瀹夊叏淇濆瘑绠$悊鍛�" value="SECURITY_ADMIN"></el-option><el-option label="瀹夊叏瀹¤绠$悊鍛�" value="AUDIT_ADMIN"></el-option></el-select></el-form-item>
+        <el-form-item label="瑙掕壊绫诲瀷">
+          <el-select v-model="form.roleType" style="width:100%">
+            <el-option label="涓氬姟瑙掕壊" value="BUSINESS"></el-option>
+            <el-option label="绯荤粺绠$悊鍛�" value="SYSTEM_ADMIN"></el-option>
+            <el-option label="瀹夊叏淇濆瘑绠$悊鍛�" value="SECURITY_ADMIN"></el-option>
+            <el-option label="瀹夊叏瀹¤绠$悊鍛�" value="AUDIT_ADMIN"></el-option>
+          </el-select>
+        </el-form-item>
         <el-form-item label="鎺掑簭"><el-input-number v-model="form.sortNo" :min="1" style="width:160px"></el-input-number></el-form-item>
         <el-form-item label="澶囨敞"><el-input v-model="form.remark" type="textarea" :rows="3"></el-input></el-form-item>
       </el-form>
       <template slot="footer"><el-button @click="dialogVisible = false">鍙栨秷</el-button><el-button type="primary" :loading="saving" @click="save">淇濆瓨</el-button></template>
+    </el-dialog>
+
+    <el-dialog title="瑙掕壊鐢ㄦ埛璁剧疆" :visible.sync="userDialogVisible" width="820px" append-to-body :close-on-click-modal="false">
+      <div class="user-setting-head">
+        <div>
+          <span class="role-label">褰撳墠瑙掕壊</span>
+          <strong>{{ currentRole.name }}</strong>
+          <span class="role-code">{{ currentRole.code }}</span>
+        </div>
+        <span class="selected-total">宸查�夋嫨 {{ selectedUserIds.length }} 浜�</span>
+      </div>
+      <div class="user-setting-toolbar">
+        <el-radio-group v-model="userScope" size="small">
+          <el-radio-button label="all">鍏ㄩ儴</el-radio-button>
+          <el-radio-button label="assigned">宸茶缃�</el-radio-button>
+        </el-radio-group>
+        <el-input v-model="userKeyword" clearable prefix-icon="el-icon-search" placeholder="鐢ㄦ埛鍚� / 璐︽埛鍚� / 閮ㄩ棬" style="width: 280px"></el-input>
+      </div>
+      <el-table
+        ref="roleUserTable"
+        v-loading="userLoading"
+        :data="filteredUsers"
+        row-key="userId"
+        height="430"
+        border
+        stripe
+        @selection-change="handleUserSelectionChange"
+      >
+        <el-table-column type="selection" reserve-selection width="48" align="center"></el-table-column>
+        <el-table-column prop="nickName" label="鐢ㄦ埛鍚�" min-width="120"></el-table-column>
+        <el-table-column prop="userName" label="璐︽埛鍚�" min-width="140"></el-table-column>
+        <el-table-column prop="deptName" label="鎵�灞為儴闂�" min-width="180" show-overflow-tooltip></el-table-column>
+        <el-table-column label="鐘舵��" width="80" align="center">
+          <template slot-scope="{ row }">
+            <el-tag size="mini" :type="row.status === '1' ? 'info' : 'success'">{{ row.status === '1' ? '鍋滅敤' : '姝e父' }}</el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+      <template slot="footer">
+        <el-button @click="userDialogVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" :loading="userSaving" v-permission="'system:roles:assign'" @click="saveRoleUsers">淇濆瓨</el-button>
+      </template>
     </el-dialog>
   </div>
 </template>
@@ -30,15 +86,124 @@
 import api from '@/api'
 export default {
   name: 'RoleManage',
-  data() { return { rows: [], dialogVisible: false, saving: false, form: {}, rules: { name: [{ required: true, message: '璇疯緭鍏ヨ鑹插悕绉�', trigger: 'blur' }], code: [{ required: true, message: '璇疯緭鍏ヨ鑹茬紪鐮�', trigger: 'blur' }] } } },
+  data() {
+    return {
+      rows: [],
+      dialogVisible: false,
+      saving: false,
+      userDialogVisible: false,
+      userLoading: false,
+      userSaving: false,
+      userKeyword: '',
+      userScope: 'all',
+      roleUsers: [],
+      selectedUserIds: [],
+      currentRole: {},
+      form: {},
+      rules: {
+        name: [{ required: true, message: '璇疯緭鍏ヨ鑹插悕绉�', trigger: 'blur' }],
+        code: [{ required: true, message: '璇疯緭鍏ヨ鑹茬紪鐮�', trigger: 'blur' }]
+      }
+    }
+  },
+  computed: {
+    filteredUsers() {
+      const assigned = new Set(this.selectedUserIds.map(String))
+      const users = this.userScope === 'assigned'
+        ? this.roleUsers.filter(item => assigned.has(String(item.userId)))
+        : this.roleUsers
+      const keyword = this.userKeyword.trim().toLowerCase()
+      if (!keyword) return users
+      return users.filter(item => [item.nickName, item.userName, item.deptName]
+        .some(value => String(value || '').toLowerCase().includes(keyword)))
+    }
+  },
   created() { this.load() },
   methods: {
-    load() { api.get('/system/roles').then(res => { this.rows = res.data || [] }) },
-    openCreate() { this.form = { name: '', code: '', roleType: 'BUSINESS', sortNo: this.rows.length + 1, remark: '' }; this.dialogVisible = true },
-    openEdit(row) { this.form = { ...row }; this.dialogVisible = true },
-    save() { this.$refs.form.validate(valid => { if (!valid) return; this.saving = true; const request = this.form.id ? api.put('/system/roles/' + this.form.id, this.form) : api.post('/system/roles', this.form); request.then(() => { this.$message.success('淇濆瓨鎴愬姛'); this.dialogVisible = false; this.load() }).finally(() => { this.saving = false }) }) },
-    remove(row) { this.$confirm(`纭畾鍒犻櫎瑙掕壊鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' }).then(() => api.delete('/system/roles/' + row.id)).then(() => { this.$message.success('鍒犻櫎鎴愬姛'); this.load() }).catch(() => {}) }
+    load() {
+      api.get('/system/roles').then(res => { this.rows = res.data || [] })
+    },
+    openCreate() {
+      this.form = { name: '', code: '', roleType: 'BUSINESS', sortNo: this.rows.length + 1, remark: '' }
+      this.dialogVisible = true
+    },
+    openEdit(row) {
+      this.form = { ...row }
+      this.dialogVisible = true
+    },
+    save() {
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+        this.saving = true
+        const request = this.form.id ? api.put('/system/roles/' + this.form.id, this.form) : api.post('/system/roles', this.form)
+        request.then(() => {
+          this.$message.success('淇濆瓨鎴愬姛')
+          this.dialogVisible = false
+          this.load()
+        }).finally(() => { this.saving = false })
+      })
+    },
+    remove(row) {
+      this.$confirm(`纭畾鍒犻櫎瑙掕壊鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' })
+        .then(() => api.delete('/system/roles/' + row.id))
+        .then(() => { this.$message.success('鍒犻櫎鎴愬姛'); this.load() })
+        .catch(() => {})
+    },
+    openUserSetting(row) {
+      this.currentRole = { ...row }
+      this.userKeyword = ''
+      this.userScope = 'all'
+      this.selectedUserIds = []
+      this.roleUsers = []
+      this.userDialogVisible = true
+      this.userLoading = true
+      api.get('/system/roles/' + row.id + '/users').then(res => {
+        const data = res.data || {}
+        this.roleUsers = data.records || []
+        this.selectedUserIds = (data.userIds || []).map(String)
+        this.$nextTick(this.restoreUserSelection)
+      }).finally(() => { this.userLoading = false })
+    },
+    restoreUserSelection() {
+      const table = this.$refs.roleUserTable
+      if (!table) return
+      table.clearSelection()
+      const selected = new Set(this.selectedUserIds)
+      this.roleUsers.forEach(row => {
+        if (selected.has(String(row.userId))) table.toggleRowSelection(row, true)
+      })
+      this.$nextTick(() => {
+        table.clearSelection()
+        this.roleUsers.forEach(row => {
+          if (selected.has(String(row.userId))) table.toggleRowSelection(row, true)
+        })
+        this.selectedUserIds = Array.from(selected)
+      })
+    },
+    handleUserSelectionChange(selection) {
+      this.selectedUserIds = (selection || []).map(item => String(item.userId))
+    },
+    saveRoleUsers() {
+      const userIds = this.selectedUserIds.slice()
+      const message = userIds.length
+        ? `纭畾灏嗏��${this.currentRole.name}鈥濊鑹茶缃负閫変腑鐨� ${userIds.length} 涓敤鎴峰悧锛焋
+        : `鏈�夋嫨鐢ㄦ埛锛屽皢娓呯┖鈥�${this.currentRole.name}鈥濊鑹茬殑鍏ㄩ儴鐢ㄦ埛锛屾槸鍚︾户缁紵`
+      this.$confirm(message, '瑙掕壊鐢ㄦ埛璁剧疆', { type: 'warning' }).then(() => {
+        this.userSaving = true
+        api.put('/system/roles/' + this.currentRole.id + '/users', { userIds }).then(() => {
+          this.$message.success('瑙掕壊鐢ㄦ埛璁剧疆宸蹭繚瀛�')
+          this.userDialogVisible = false
+        }).finally(() => { this.userSaving = false })
+      }).catch(() => {})
+    }
   }
 }
 </script>
-<style scoped>.danger-link { color: #d9534f; }</style>
+<style scoped>
+.danger-link { color: #d9534f; }
+.user-setting-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; color: #31465d; }
+.role-label { margin-right: 10px; color: #7b8b9c; font-size: 13px; }
+.role-code { margin-left: 8px; color: #93a2b1; font-size: 12px; }
+.selected-total { color: #2f658f; font-size: 13px; }
+.user-setting-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
+</style>
diff --git a/emergency-repair-web/src/views/system/TeamMajorManage.vue b/emergency-repair-web/src/views/system/TeamMajorManage.vue
index e56d9c1..396eef7 100644
--- a/emergency-repair-web/src/views/system/TeamMajorManage.vue
+++ b/emergency-repair-web/src/views/system/TeamMajorManage.vue
@@ -2,10 +2,10 @@
   <div class="page">
     <div class="page-title">
       <div>
-        <h2>T闃熶笓涓氶厤缃�</h2>
+        <h2>rk_T闃熶笓涓氶厤缃�</h2>
         <p>鍒嗙被鍙栧�糀銆丅锛岀敤浜庤嚜鍔ㄧ敓鎴愭晠闅滅紪鍙�</p>
       </div>
-      <el-button type="primary" icon="el-icon-plus" @click="openCreate">鏂板T闃熶笓涓�</el-button>
+      <el-button type="primary" icon="el-icon-plus" @click="openCreate">鏂板rk_T闃熶笓涓�</el-button>
     </div>
 
     <div class="toolbar">
@@ -30,9 +30,9 @@
       </el-table>
     </div>
 
-    <el-dialog :title="form.id ? '缂栬緫T闃熶笓涓�' : '鏂板T闃熶笓涓�'" :visible.sync="dialogVisible" width="520px" append-to-body>
+    <el-dialog :title="form.id ? '缂栬緫rk_T闃熶笓涓�' : '鏂板rk_T闃熶笓涓�'" :visible.sync="dialogVisible" width="520px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="鍚嶇О" prop="name"><el-input v-model="form.name" placeholder="璇疯緭鍏闃熶笓涓氬悕绉�"></el-input></el-form-item>
+        <el-form-item label="鍚嶇О" prop="name"><el-input v-model="form.name" placeholder="璇疯緭鍏k_T闃熶笓涓氬悕绉�"></el-input></el-form-item>
         <el-form-item label="鍒嗙被" prop="category">
           <el-radio-group v-model="form.category">
             <el-radio-button label="A">A</el-radio-button>
diff --git a/emergency-repair-web/src/views/system/TreeManage.vue b/emergency-repair-web/src/views/system/TreeManage.vue
index efba783..8cbaf53 100644
--- a/emergency-repair-web/src/views/system/TreeManage.vue
+++ b/emergency-repair-web/src/views/system/TreeManage.vue
@@ -3,7 +3,7 @@
     <div class="page-title">
       <div>
         <h2>{{ isProduct ? '浜у搧缁撴瀯鏍戠鐞�' : '璁惧鍩虹搴撶鐞�' }}</h2>
-        <p>{{ isProduct ? '鍨嬪彿 鈫� X鍙� 鈫� 涓�绾х郴缁� 鈫� 浜岀骇绯荤粺 鈫� 璁惧' : '涓�绾х郴缁� 鈫� 浜岀骇绯荤粺 鈫� 璁惧' }}</p>
+        <p>{{ isProduct ? '鍨嬪彿 鈫� rk_X鍙� 鈫� 涓�绾х郴缁� 鈫� 浜岀骇绯荤粺 鈫� 璁惧' : '涓�绾х郴缁� 鈫� 浜岀骇绯荤粺 鈫� 璁惧' }}</p>
       </div>
       <div>
         <el-button icon="el-icon-plus" @click="addRoot">鏂板涓�绾ц妭鐐�</el-button>
@@ -18,7 +18,10 @@
           ref="tree"
           :data="tree"
           node-key="id"
-          default-expand-all
+          v-loading="loading"
+          :lazy="isProduct"
+          :load="loadNode"
+          :default-expand-all="!isProduct"
           highlight-current
           :filter-node-method="filterNode"
           :props="{ label: 'name', children: 'children' }"
@@ -27,7 +30,7 @@
           <span slot-scope="{ data }" class="tree-node">
             <span class="level-badge">{{ levelText(data.nodeLevel) }}</span>
             <span>{{ data.name }}</span>
-            <small>{{ data.code }}</small>
+            <small v-if="!isProduct && data.code">{{ data.code }}</small>
           </span>
         </el-tree>
       </section>
@@ -44,19 +47,14 @@
           <el-form :model="form" label-width="100px">
             <el-form-item label="鑺傜偣灞傜骇"><el-tag type="info">{{ levelText(form.nodeLevel) }}</el-tag></el-form-item>
             <el-form-item label="鍚嶇О"><el-input v-model="form.name"></el-input></el-form-item>
-            <el-form-item label="缂栫爜"><el-input v-model="form.code"></el-input></el-form-item>
+            <el-form-item v-if="!isProduct" label="缂栫爜"><el-input v-model="form.code"></el-input></el-form-item>
             <el-form-item v-if="isProduct" label="鑺傜偣绫诲瀷">
               <el-select v-model="form.nodeType" :disabled="form.nodeLevel < 5" style="width:100%">
                 <el-option label="鍨嬪彿" value="MODEL"></el-option>
-                <el-option label="X鍙�" value="X_NO"></el-option>
+                <el-option label="rk_X鍙�" value="X_NO"></el-option>
                 <el-option label="涓�绾х郴缁�" value="SYSTEM1"></el-option>
                 <el-option label="浜岀骇绯荤粺" value="SYSTEM2"></el-option>
                 <el-option label="璁惧" value="DEVICE"></el-option>
-              </el-select>
-            </el-form-item>
-            <el-form-item v-if="isProduct && form.nodeLevel === 5" label="鍩虹璁惧">
-              <el-select v-model="form.baseEquipmentId" filterable placeholder="鍏宠仈璁惧鍩虹搴撹澶�" style="width:100%">
-                <el-option v-for="item in baseDevices" :key="item.id" :label="item.path + ' / ' + item.name" :value="item.id"></el-option>
               </el-select>
             </el-form-item>
             <el-form-item label="鎺掑簭"><el-input-number v-model="form.sortNo" :min="1" style="width:180px"></el-input-number></el-form-item>
@@ -77,12 +75,12 @@
     treeType: { type: String, required: true }
   },
   data() {
-    return { tree: [], keyword: '', form: {}, saving: false, baseDevices: [] }
+    return { tree: [], keyword: '', form: {}, saving: false, loading: false }
   },
   computed: {
     isProduct() { return this.treeType === 'product' },
     maxLevel() { return this.isProduct ? 5 : 3 },
-    levelNames() { return this.isProduct ? ['鍨嬪彿', 'X鍙�', '涓�绾х郴缁�', '浜岀骇绯荤粺', '璁惧'] : ['涓�绾х郴缁�', '浜岀骇绯荤粺', '璁惧'] }
+    levelNames() { return this.isProduct ? ['鍨嬪彿', 'rk_X鍙�', '涓�绾х郴缁�', '浜岀骇绯荤粺', '璁惧'] : ['涓�绾х郴缁�', '浜岀骇绯荤粺', '璁惧'] }
   },
   watch: {
     keyword(value) { this.$refs.tree && this.$refs.tree.filter(value) },
@@ -91,7 +89,20 @@
   created() { this.load() },
   methods: {
     load() {
-      api.get('/system/tree', { params: { type: this.treeType } }).then(res => { this.tree = res.data || [] })
+      this.loading = true
+      const request = this.isProduct
+        ? api.get('/system/tree/product/roots')
+        : api.get('/system/tree', { params: { type: this.treeType } })
+      request.then(res => { this.tree = res.data || [] }).finally(() => { this.loading = false })
+    },
+    loadNode(node, resolve) {
+      if (!this.isProduct || !node || !node.data || !node.data.id) {
+        resolve([])
+        return
+      }
+      api.get('/system/tree/product/children', { params: { parentId: node.data.id } })
+        .then(res => resolve(res.data || []))
+        .catch(() => resolve([]))
     },
     levelText(level) { return this.levelNames[(level || 1) - 1] || ('绗�' + level + '绾�') },
     filterNode(value, data) {
@@ -100,31 +111,16 @@
     },
     selectNode(node) {
       this.form = { ...node }
-      if (this.isProduct && node.nodeLevel === 5) this.loadBaseDevices()
     },
     nodeTypeForLevel(level) {
       return this.isProduct ? ['MODEL', 'X_NO', 'SYSTEM1', 'SYSTEM2', 'DEVICE'][level - 1] : 'BASE_' + level
     },
     addRoot() {
       this.form = { parentId: 0, nodeLevel: 1, nodeType: this.nodeTypeForLevel(1), name: '', code: '', sortNo: 1, remark: '' }
-      if (this.isProduct) this.form.baseEquipmentId = null
     },
     addChild() {
       const level = (this.form.nodeLevel || 1) + 1
-      this.form = { parentId: this.form.id, nodeLevel: level, nodeType: this.nodeTypeForLevel(level), name: '', code: '', sortNo: 1, remark: '', baseEquipmentId: null }
-      if (this.isProduct && level === 5) this.loadBaseDevices()
-    },
-    loadBaseDevices() {
-      api.get('/system/tree', { params: { type: 'base' } }).then(res => { this.baseDevices = this.flattenBase(res.data || [], []) })
-    },
-    flattenBase(nodes, parents) {
-      const result = []
-      nodes.forEach(node => {
-        const path = parents.concat(node.name)
-        if (node.nodeLevel === 3) result.push({ ...node, path: path.slice(0, -1).join(' / ') })
-        if (node.children) result.push(...this.flattenBase(node.children, path))
-      })
-      return result
+      this.form = { parentId: this.form.id, nodeLevel: level, nodeType: this.nodeTypeForLevel(level), name: '', code: '', sortNo: 1, remark: '' }
     },
     save() {
       if (!this.form.name) return this.$message.warning('璇疯緭鍏ヨ妭鐐瑰悕绉�')
diff --git a/emergency-repair-web/src/views/system/UserManage.vue b/emergency-repair-web/src/views/system/UserManage.vue
new file mode 100644
index 0000000..2e3c9a5
--- /dev/null
+++ b/emergency-repair-web/src/views/system/UserManage.vue
@@ -0,0 +1,253 @@
+<template>
+  <div class="page">
+    <div class="page-title">
+      <div><h2>鐢ㄦ埛绠$悊</h2><p>宸︿晶鏌ヨ鐢ㄦ埛锛屽彸渚ц缃鐢ㄦ埛鎷ユ湁鐨勮鑹诧紝涓�涓敤鎴峰彲璁剧疆澶氫釜瑙掕壊</p></div>
+    </div>
+    <div class="user-layout">
+      <section class="user-left">
+        <div class="toolbar">
+          <el-button type="primary" plain icon="el-icon-s-check" :disabled="!selectedRows.length" v-permission="'system:users:assign'" @click="openBatchDialog">鎵归噺璁剧疆瑙掕壊</el-button>
+          <span v-if="selectedRows.length" class="selected-count">宸查�� {{ selectedRows.length }} 浜�</span>
+          <el-input v-model="filters.keyword" clearable placeholder="鐢ㄦ埛鍚� / 璐︽埛鍚�" style="width:190px" @keyup.enter.native="search"></el-input>
+          <el-select v-model="filters.deptId" clearable filterable placeholder="鎵�灞為儴闂�" style="width:200px">
+            <el-option v-for="item in depts" :key="item.id" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+          <el-select v-model="filters.roleId" clearable placeholder="瑙掕壊" style="width:170px">
+            <el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+          <el-button type="primary" icon="el-icon-search" v-permission="'system:users:query'" @click="search">鏌ヨ</el-button>
+          <el-button icon="el-icon-refresh-left" @click="reset">閲嶇疆</el-button>
+        </div>
+        <el-table ref="userTable" row-key="userId"
+          v-loading="loading"
+          :data="rows"
+          border
+          stripe
+          highlight-current-row
+          height="calc(100vh - 250px)"
+          @selection-change="selectedRows = $event"
+          @current-change="selectUser"
+        >
+          <el-table-column type="selection" width="46" align="center"></el-table-column>
+          <el-table-column prop="nickName" label="鐢ㄦ埛鍚�" min-width="110"></el-table-column>
+          <el-table-column prop="userName" label="璐︽埛鍚�" min-width="140"></el-table-column>
+          <el-table-column prop="deptName" label="鎵�灞為儴闂�" min-width="180" show-overflow-tooltip></el-table-column>
+          <el-table-column label="瑙掕壊鏉冮檺" min-width="220">
+            <template slot-scope="{ row }">
+              <el-tag v-for="role in row.roles" :key="role.roleId" size="mini" class="role-tag">{{ role.roleName }}</el-tag>
+              <span v-if="!row.roles || !row.roles.length" class="muted">鏈垎閰�</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="phoneNumber" label="鎵嬫満鍙�" width="130"></el-table-column>
+          <el-table-column label="鐘舵��" width="80" align="center">
+            <template slot-scope="{ row }">
+              <el-tag size="mini" :type="isDisabled(row) ? 'info' : 'success'">{{ isDisabled(row) ? '鍋滅敤' : '姝e父' }}</el-tag>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-pagination
+          class="pager"
+          background
+          layout="total, sizes, prev, pager, next"
+          :total="total"
+          :current-page="current"
+          :page-size="size"
+          :page-sizes="[20, 50, 100]"
+          @current-change="handleCurrentChange"
+          @size-change="handleSizeChange"
+        ></el-pagination>
+      </section>
+      <section class="user-right">
+        <div class="panel-head">
+          <h3>瑙掕壊鎺堟潈</h3>
+          <span v-if="selected" class="selected-user">{{ selected.nickName || selected.userName }}锛坽{ selected.userName }}锛�</span>
+        </div>
+        <div v-if="!selected" class="empty-tip">璇烽�夋嫨宸︿晶鐢ㄦ埛</div>
+        <template v-else>
+          <el-checkbox-group v-model="checkedRoleIds" class="role-list">
+            <el-checkbox v-for="item in roles" :key="item.id" :label="item.id" class="role-item">
+              <span class="role-name">{{ item.name }}</span>
+              <span class="role-code">{{ item.code }}</span>
+            </el-checkbox>
+          </el-checkbox-group>
+          <el-button
+            type="primary"
+            icon="el-icon-check"
+            class="save-button"
+            v-permission="'system:users:assign'"
+            :loading="saving"
+            @click="saveRoles"
+          >淇濆瓨鎺堟潈</el-button>
+        </template>
+      </section>
+    </div>
+      <el-dialog title="鎵归噺璁剧疆瑙掕壊" :visible.sync="batchVisible" width="520px" append-to-body :close-on-click-modal="false">
+        <div class="batch-summary">宸查�夋嫨 {{ selectedRows.length }} 涓敤鎴�</div>
+        <el-radio-group v-model="batchMode" class="batch-mode">
+          <el-radio label="append">杩藉姞瑙掕壊</el-radio>
+          <el-radio label="replace">瑕嗙洊瑙掕壊</el-radio>
+        </el-radio-group>
+        <el-alert :title="batchMode === 'append' ? '淇濈暀鎵�閫夌敤鎴峰師鏈夎鑹诧紝骞惰拷鍔犲嬀閫夎鑹�' : '浣跨敤鍕鹃�夎鑹茶鐩栨墍閫夌敤鎴风殑鍘熸湁瑙掕壊'" type="warning" :closable="false" show-icon></el-alert>
+        <el-checkbox-group v-model="batchRoleIds" class="batch-role-list">
+          <el-checkbox v-for="item in roles" :key="item.id" :label="item.id" class="role-item">
+            <span class="role-name">{{ item.name }}</span>
+            <span class="role-code">{{ item.code }}</span>
+          </el-checkbox>
+        </el-checkbox-group>
+        <span slot="footer">
+          <el-button @click="batchVisible = false">鍙栨秷</el-button>
+          <el-button type="primary" :loading="batchSaving" v-permission="'system:users:assign'" @click="saveBatchRoles">淇濆瓨</el-button>
+        </span>
+      </el-dialog>
+  </div>
+</template>
+<script>
+import api from '@/api'
+export default {
+  name: 'UserManage',
+  data() {
+    return {
+      rows: [],
+      total: 0,
+      current: 1,
+      size: 20,
+      loading: false,
+      saving: false,
+      batchSaving: false,
+      batchVisible: false,
+      batchRoleIds: [],
+      batchMode: 'append',
+      selectedRows: [],
+      depts: [],
+      roles: [],
+      selected: null,
+      checkedRoleIds: [],
+      filters: { keyword: '', deptId: null, roleId: null }
+    }
+  },
+  created() {
+    this.loadRoles()
+    this.loadDepts()
+    this.load()
+  },
+  methods: {
+    load() {
+      this.clearSelection()
+      this.loading = true
+      api.get('/system/users', {
+        params: {
+          keyword: this.filters.keyword || undefined,
+          deptId: this.filters.deptId || undefined,
+          roleId: this.filters.roleId || undefined,
+          current: this.current,
+          size: this.size
+        }
+      }).then(res => {
+        const data = res.data || {}
+        this.rows = data.records || []
+        this.total = data.total || 0
+        if (this.selected) {
+          const matched = this.rows.find(item => item.userId === this.selected.userId)
+          this.selected = matched || null
+          if (!matched) this.checkedRoleIds = []
+        }
+      }).finally(() => { this.loading = false })
+    },
+    loadRoles() {
+      api.get('/system/roles').then(res => { this.roles = res.data || [] })
+    },
+    loadDepts() {
+      api.get('/system/depts').then(res => { this.depts = res.data || [] })
+    },
+    search() {
+      this.current = 1
+      this.load()
+    },
+    reset() {
+      this.filters = { keyword: '', deptId: null, roleId: null }
+      this.search()
+    },
+    handleCurrentChange(page) {
+      this.current = page
+      this.load()
+    },
+    handleSizeChange(size) {
+      this.size = size
+      this.current = 1
+      this.load()
+    },
+    clearSelection() {
+      this.selectedRows = []
+      this.$nextTick(() => { if (this.$refs.userTable) this.$refs.userTable.clearSelection() })
+    },
+    openBatchDialog() {
+      if (!this.selectedRows.length) return
+      this.batchRoleIds = []
+      this.batchMode = 'append'
+      this.batchVisible = true
+    },
+    saveBatchRoles() {
+      if (!this.selectedRows.length) return
+      const userIds = this.selectedRows.map(item => item.userId)
+      const roleIds = this.batchRoleIds.slice()
+      if (this.batchMode === 'append' && !roleIds.length) {
+        this.$message.warning('璇烽�夋嫨瑕佽拷鍔犵殑瑙掕壊')
+        return
+      }
+      const message = this.batchMode === 'append'
+        ? '灏嗗悜 ' + userIds.length + ' 涓敤鎴疯拷鍔犳墍閫夎鑹诧紝鏄惁缁х画锛�'
+        : (roleIds.length ? '灏嗕娇鐢ㄦ墍閫夎鑹茶鐩� ' + userIds.length + ' 涓敤鎴风殑鐜版湁瑙掕壊锛屾槸鍚︾户缁紵' : '鏈�夋嫨瑙掕壊锛屽皢娓呯┖ ' + userIds.length + ' 涓敤鎴风殑鍏ㄩ儴瑙掕壊锛屾槸鍚︾户缁紵')
+      this.$confirm(message, '鎵归噺璁剧疆瑙掕壊', { type: 'warning' }).then(() => {
+        this.batchSaving = true
+        api.put('/system/users/roles/batch', { userIds, roleIds, mode: this.batchMode }).then(() => {
+          this.$message.success('鎵归噺瑙掕壊璁剧疆鎴愬姛')
+          this.batchVisible = false
+          this.load()
+          if (this.selected) this.selectUser(this.selected)
+        }).finally(() => { this.batchSaving = false })
+      }).catch(() => {})
+    },
+    selectUser(row) {
+      this.selected = row || null
+      this.checkedRoleIds = []
+      if (!row) return
+      api.get('/system/users/' + row.userId + '/roles').then(res => {
+        this.checkedRoleIds = res.data || []
+      })
+    },
+    saveRoles() {
+      if (!this.selected) return
+      this.saving = true
+      api.put('/system/users/' + this.selected.userId + '/roles', { roleIds: this.checkedRoleIds }).then(() => {
+        this.$message.success('瑙掕壊鎺堟潈宸蹭繚瀛�')
+        this.load()
+      }).finally(() => { this.saving = false })
+    },
+    isDisabled(row) {
+      return row.status === '1'
+    }
+  }
+}
+</script>
+<style scoped>
+.user-layout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 12px; }
+.user-left, .user-right { min-width: 0; padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; }
+.user-right { min-height: calc(100vh - 190px); }
+.toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
+.selected-count { align-self: center; color: #5c6f82; font-size: 12px; }
+.batch-summary { margin-bottom: 10px; color: #31465d; font-size: 14px; }
+.batch-role-list { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 12px; max-height: 320px; margin-top: 14px; overflow: auto; }
+.pager { margin-top: 12px; text-align: right; }
+.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #edf1f5; }
+.panel-head h3 { margin: 0; color: #31465d; font-size: 15px; }
+.selected-user { color: #5c6f82; font-size: 13px; }
+.empty-tip { padding: 60px 0; color: #93a2b1; text-align: center; }
+.role-list { display: flex; flex-direction: column; gap: 4px; max-height: calc(100vh - 320px); overflow: auto; }
+.role-item { display: flex; align-items: center; height: 34px; margin-right: 0; }
+.role-name { color: #31465d; }
+.role-code { margin-left: 8px; color: #93a2b1; font-size: 12px; }
+.save-button { width: 100%; margin-top: 16px; }
+.role-tag { margin: 2px 4px 2px 0; }
+.muted { color: #a3afbc; }
+@media (max-width: 1100px) { .user-layout { grid-template-columns: 1fr; } }
+</style>
diff --git "a/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md" "b/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md"
new file mode 100644
index 0000000..e42a7a5
--- /dev/null
+++ "b/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md"
@@ -0,0 +1,506 @@
+# 涓存姠淇鐞嗙郴缁熶氦鎺ユ枃浠讹紙2026.10.3锛�
+
+## 鍩烘湰淇℃伅
+
+- 浠撳簱鐩綍锛歚D:\projectJava\lqx`
+- Git 鍒嗘敮锛歚main`
+- 鍩虹嚎鎻愪氦锛歚f17c76c fix: load only active report data`
+- 鍘熷闇�姹傛枃浠讹細`requirement/鏀硅繘20261003.txt`
+- 浜ゆ帴鏁寸悊鏃堕棿锛�2026-10-04
+- 鏈疆宸ヤ綔鏃ユ湡锛�2026-10-03
+- 鎻愪氦鐘舵�侊細闇�姹� 11銆�12銆�17-19 宸插畬鎴愬苟瀹屾垚娴忚鍣ㄣ�佹帴鍙e強绔埌绔仈璋冿紝鏈疆浠g爜涓庢湰鏂囨。灏嗙粺涓�鎻愪氦骞舵帹閫� `main` 鍒嗘敮銆�
+- 娉ㄦ剰浜嬮」锛歚requirement/鏀硅繘20261003.txt` 鏄敤鎴峰師濮嬮渶姹傝褰曪紝鏈鏈慨鏀广��
+
+> 2026-10-04 鏇存柊锛氶渶姹� 11銆�12銆�17-19 宸插畬鎴愬苟瀹屾垚娴忚鍣ㄣ�佹帴鍙e強绔埌绔仈璋冿紱鏈疆浠g爜涓庝氦鎺ユ枃妗e皢缁熶竴鎻愪氦骞舵帹閫� `main` 鍒嗘敮銆俙DEPT_NAME` 鍖归厤绛夊墿浣欓闄╄绗崄涓�鑺傘��
+
+## 涓�銆侀渶姹傚畬鎴愭儏鍐�
+
+| 搴忓彿 | 闇�姹� | 鐘舵�� | 涓昏瀹炵幇 |
+| --- | --- | --- | --- |
+| 1 | logo 浣跨敤 `public/logo.png` | 宸插畬鎴� | `RepairLogo.vue` 璇诲彇 `${BASE_URL}logo.png`锛屾枃浠朵綅浜� `emergency-repair-web/public/logo.png` |
+| 2 | 鐢ㄦ埛绠$悊锛屽乏鍙崇粨鏋勶紝宸︿晶鐢ㄦ埛琛ㄦ牸銆佸彸渚ц鑹插閫夊強鍙嶆樉 | 宸插畬鎴� | 鏂板 `UserManage.vue`锛涙敮鎸佺敤鎴峰悕銆佽处鎴峰悕銆侀儴闂ㄣ�佽鑹茶繃婊わ紱鏀寔涓�浜哄瑙掕壊 |
+| 3 | 鐧诲綍鏃ュ織銆佹搷浣滄棩蹇� | 宸插畬鎴� | 鏂板鐧诲綍鏃ュ織鍜屾搷浣滄棩蹇楅〉闈€�佸疄浣撱�丮apper锛涙搷浣滄棩蹇楄褰曟柊澧炪�佷慨鏀广�佸垹闄よ姹� |
+| 4 | 鑿滃崟绠$悊澧炲姞鎸夐挳绫诲瀷锛屾寜閽彧鑳芥寕鍦ㄨ彍鍗曚笅绾� | 宸插畬鎴� | `MenuManage.vue`銆乣ButtonPermissionCatalog.java`锛涢〉闈㈡寜閽彲鑷姩鑾峰彇骞跺悓姝ヤ负 `MENU_TYPE=2` |
+| 5 | 浠g爜涓殑鈥滆澶団�濇敼鎴愨�滆澶団�� | 宸插畬鎴� | 鍓嶇銆佸悗绔簮鐮佹鏌ユ棤鈥滆澶団�濆尮閰嶏紱鍒濆鍖栬彍鍗曚篃鏈夋浛鎹㈤�昏緫 |
+| 6 | 澶氭枃妗f爣绛炬í鍚戞粴鍔ㄦ椂楂樺害涓嶈冻銆佹枃瀛楁樉绀轰笉鍏� | 宸插畬鎴� | `AppLayout.vue` 璋冩暣澶氭枃妗f爣绛炬爮銆佹粴鍔ㄦ潯鍜屾爣绛鹃珮搴� |
+| 7 | 鏀惧紑鏉冮檺锛屽叾浠栫敤鎴蜂篃鍙互鐧诲綍 | 宸插畬鎴� | `AuthController` 鏌ヨ澶栭儴鐢ㄦ埛琛紝涓嶅啀鍙厑璁� admin锛涘叾浠栨湁鏁堢敤鎴峰彲鐧诲綍 |
+| 8 | 鑿滃崟鏉冮檺绠$悊鐖跺瓙鑺傜偣澶嶉�夎仈鍔� | 宸插畬鎴� | `MenuPermission.vue` 浣跨敤 Element UI 榛樿绾ц仈澶嶉�夐�昏緫锛屽苟鍦ㄤ繚瀛樻椂鍖呭惈鍗婇�夌埗鑺傜偣 |
+| 9 | 鐢ㄦ埛绠$悊鎵归噺璁剧疆瑙掕壊锛涜鑹茬鐞嗗鍔犺鑹茬敤鎴疯缃� | 宸插畬鎴� | 鐢ㄦ埛绠$悊鏀寔鈥滆拷鍔犺鑹测�濆拰鈥滆鐩栬鑹测�濓紱瑙掕壊绠$悊鏀寔鈥滅敤鎴疯缃�濆拰瑙掕壊鐢ㄦ埛鍙嶆樉 |
+| 10 | 浜у搧缁撴瀯鏍戞敼涓哄悓涔夎瘝琛� `ZT_BOAT_TREE` | 宸插畬鎴愶紝浠嶆湁寰呯‘璁や簨椤� | 宸叉槧灏� `LABEL`銆乣INFO_ID`銆乣CONNECT_ID`銆乣CONNECT_GROUP`銆乣LAYER`锛涘凡鏀逛负鎳掑姞杞� |
+
+## 浜屻�佺敤鎴蜂笌瑙掕壊鎺堟潈
+
+### 2.1 鐢ㄦ埛绠$悊
+
+椤甸潰鏂囦欢锛�
+
+- `emergency-repair-web/src/views/system/UserManage.vue`
+
+鍔熻兘锛�
+
+- 宸︿晶鐢ㄦ埛琛ㄦ牸鏄剧ず锛�
+  - 鐢ㄦ埛鍚�
+  - 璐︽埛鍚�
+  - 鎵�灞為儴闂�
+  - 瑙掕壊鏉冮檺
+  - 鎵嬫満鍙�
+  - 鐘舵��
+- 鏀寔鎸夌敤鎴峰悕銆佽处鎴峰悕銆侀儴闂ㄣ�佽鑹茶繃婊ゃ��
+- 閫夋嫨宸︿晶鐢ㄦ埛鍚庯紝鍙充晶澶嶉�夋樉绀鸿鐢ㄦ埛鎷ユ湁鐨勮鑹层��
+- 涓�涓敤鎴峰彲浠ュ悓鏃舵嫢鏈夊涓鑹层��
+- 鏀寔澶氶�夌敤鎴峰悗鈥滄壒閲忚缃鑹测�濓細
+  - `append`锛氫繚鐣欏師瑙掕壊锛岃拷鍔犲嬀閫夎鑹层��
+  - `replace`锛氫娇鐢ㄥ嬀閫夎鑹茶鐩栧師瑙掕壊銆�
+- `replace` 涓旀湭閫夋嫨瑙掕壊鏃讹紝浼氭竻绌烘墍閫夌敤鎴风殑鍏ㄩ儴瑙掕壊銆�
+
+鐩稿叧鎺ュ彛锛�
+
+- `GET /api/system/users`
+- `GET /api/system/users/{userId}/roles`
+- `PUT /api/system/users/{userId}/roles`
+- `PUT /api/system/users/roles/batch`
+
+### 2.2 瑙掕壊绠$悊
+
+椤甸潰鏂囦欢锛�
+
+- `emergency-repair-web/src/views/system/RoleManage.vue`
+
+鍔熻兘锛�
+
+- 姣忚瑙掕壊澧炲姞鈥滅敤鎴疯缃�濄��
+- 寮圭獥涓彲杩囨护鐢ㄦ埛锛屽閫夊苟鍙嶆樉褰撳墠瑙掕壊鎵�灞炵敤鎴枫��
+- 淇濆瓨瑙掕壊鐢ㄦ埛鍏崇郴鏃跺彧鏇挎崲璇ヨ鑹茬殑鐢ㄦ埛鍏崇郴锛屼笉鍒犻櫎杩欎簺鐢ㄦ埛鎷ユ湁鐨勫叾浠栬鑹层��
+
+鐩稿叧鎺ュ彛锛�
+
+- `GET /api/system/roles/{roleId}/users`
+- `PUT /api/system/roles/{roleId}/users`
+
+鏈嶅姟瀹炵幇锛�
+
+- `SystemService.roleUsers(...)`
+- `SystemService.saveRoleUsers(...)`
+
+鎸夐挳鏉冮檺锛�
+
+- `system:roles:assign`
+
+## 涓夈�佹棩蹇楀姛鑳�
+
+### 3.1 鐧诲綍鏃ュ織
+
+瀹炵幇浣嶇疆锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysLoginLog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysLoginLogMapper.java`
+- `emergency-repair-web/src/views/system/LoginLogManage.vue`
+
+璁板綍鍐呭锛�
+
+- 鐢ㄦ埛 ID
+- 璐︽埛鍚�
+- 鐢ㄦ埛鍚�
+- 閮ㄩ棬
+- 鐧诲綍 IP
+- 鐧诲綍鐘舵��
+- 鐧诲綍缁撴灉璇存槑
+- 鐧诲綍鏃堕棿
+
+鎺ュ彛锛�
+
+- `GET /api/system/login-logs`
+
+### 3.2 鎿嶄綔鏃ュ織
+
+瀹炵幇浣嶇疆锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/log/OperationLogInterceptor.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysOperationLog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysOperationLogMapper.java`
+- `emergency-repair-web/src/views/system/OperationLogManage.vue`
+
+璁板綍瑙勫垯锛�
+
+- 鎷︽埅 `/api/**` 涓嬬殑 `POST`銆乣PUT`銆乣DELETE` 璇锋眰銆�
+- 鐧诲綍鎺ュ彛鎺掗櫎锛岀敱鐧诲綍鏃ュ織璐熻矗銆�
+- 璁板綍妯″潡銆佹搷浣滅被鍨嬨�佺洰鏍囩被鍨嬨�佺洰鏍� ID銆佽姹傛柟寮忋�佽姹� URI銆佹垚鍔�/澶辫触銆佹搷浣滀汉鍜屾椂闂淬��
+- 涓氬姟璇锋眰澶辫触鏃讹紝鏃ュ織鍐欏叆寮傚父涓嶄細褰卞搷鍘熶笟鍔¤姹傘��
+- 褰撳墠鎿嶄綔鏃ュ織鐢ㄦ埛鍙栬姹傚ご `X-Username`锛涘鏋滆皟鐢ㄦ柟娌℃湁浼犺璇锋眰澶达紝浼氬洖閫�璁板綍涓� `admin`銆傛墜鏈虹鎴栫涓夋柟璋冪敤鏃堕渶瑕佹敞鎰忚ˉ榻愯璇锋眰澶淬��
+
+鎺ュ彛锛�
+
+- `GET /api/system/operation-logs`
+
+### 3.3 鏃ュ織琛�
+
+鐢� `DatabaseBootstrap` 鍒涘缓锛�
+
+- `LX.T_SYS_LOGIN_LOG`
+- `LX.T_SYS_OPERATION_LOG`
+
+绱㈠紩锛�
+
+- `IX_T_SYS_LOGIN_LOG_TIME`
+- `IX_T_SYS_OPERATION_LOG_TIME`
+- `IX_T_SYS_ROLE_USER_USER`
+
+## 鍥涖�佽彍鍗曚笌鎸夐挳鏉冮檺
+
+椤甸潰鏂囦欢锛�
+
+- `emergency-repair-web/src/views/system/MenuManage.vue`
+- `emergency-repair-web/src/views/system/MenuPermission.vue`
+
+鎸夐挳鐩綍锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java`
+
+瑙勫垯锛�
+
+- 鑿滃崟绫诲瀷锛�
+  - `0`锛氱洰褰�
+  - `1`锛氳彍鍗�
+  - `2`锛氭寜閽�
+- 鎸夐挳鍙兘鎸傚湪鑿滃崟绫诲瀷 `1` 鐨勪笅绾с��
+- 鑿滃崟椤甸潰鐐瑰嚮鈥滈〉闈㈡寜閽�濆悗锛屽悗绔粠鎸夐挳鐩綍鑷姩鍒楀嚭璇ラ〉闈㈠彲閰嶇疆鎸夐挳銆�
+- 鍕鹃�夊苟淇濆瓨鍚庯紝鑷姩鐢熸垚鎸夐挳绫诲瀷鑿滃崟銆�
+- 鎸夐挳绫诲瀷鑿滃崟鍙户缁湪鑿滃崟鏉冮檺绠$悊涓巿鏉冪粰瑙掕壊銆�
+- 鑿滃崟鏉冮檺鏍戜娇鐢ㄧ埗瀛愯妭鐐硅仈鍔紝淇濆瓨鏃跺悓鏃舵敹闆嗗凡閫変腑鑺傜偣鍜屽崐閫夌埗鑺傜偣銆�
+
+鐩稿叧鎺ュ彛锛�
+
+- `GET /api/system/menu/buttons`
+- `POST /api/system/menu/buttons`
+- `GET /api/system/menu-permissions`
+- `POST /api/system/menu-permissions`
+
+## 浜斻�佷骇鍝佺粨鏋勬爲
+
+### 5.1 鏁版嵁婧�
+
+瀹炰綋鏂囦欢锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java`
+
+鏄犲皠琛細
+
+```text
+LX.ZT_BOAT_TREE
+```
+
+鐗╃悊琛細
+
+```text
+CSICZB_ZB.ZT_BOAT_TREE
+```
+
+宸茬‘璁� `CSICZB` 鍜� `LX` 涓嬪潎鏈夊悓涔夎瘝銆�
+
+瀛楁鏄犲皠锛�
+
+| 澶栭儴琛ㄥ瓧娈� | Java 瀛楁 | 璇存槑 |
+| --- | --- | --- |
+| `INFO_ID` | `id` | 涓氬姟鑺傜偣涓婚敭 |
+| `CONNECT_ID` | `parentId` | 鐖惰妭鐐� ID |
+| `CONNECT_GROUP` | `connectGroup` | 绁栬緢璺緞 |
+| `LABEL` | `name` | 璁惧鍚嶇О |
+| `LAYER` | `layer` | 鑺傜偣绫诲瀷 |
+| `SORT` | `sortNo` | 鎺掑簭 |
+| `ISDELETE` | `isDelete` | 鍒犻櫎鏍囪 |
+| `ID` | `recordId` | 鐗╃悊鍏崇郴琛� ID |
+
+宸茬煡灞傜骇鍏煎鏄犲皠锛�
+
+| `LAYER` | 鍓嶇灞傜骇 | 鍓嶇绫诲瀷 |
+| --- | --- | --- |
+| `model` | 1 | `MODEL` |
+| `side` | 2 | `X_NO` |
+| `system1` | 3 | `SYSTEM1` |
+| `system2` | 4 | `SYSTEM2` |
+| `equipment` | 5 | `DEVICE` |
+| 鍏朵粬鍊� | 6 | 鍘熷鍊煎ぇ鍐� |
+
+### 5.2 瀹為檯鏁版嵁鎯呭喌
+
+2026-10-03 DM8 瀹炴祴锛�
+
+- 鐗╃悊琛屾暟锛�12974
+- 鍘婚噸鍚� `INFO_ID`锛�12972
+- 閲嶅 `INFO_ID`锛�2 涓�
+- 鏍硅妭鐐癸細2 涓�
+- `equipment`锛�12798
+- `system1`锛�161
+- `side`锛�9
+- `model`锛�2
+- 鏈煡灞傜骇锛歚ttt`銆乣sss`銆乣rrrr`锛屽悇 1 涓�
+
+绀轰緥鏍硅妭鐐癸細
+
+- `INFO_ID=1280000000000006486`
+- 鍚嶇О锛歚鍨嬪彿s44444`
+- `LAYER=model`
+
+### 5.3 鎳掑姞杞芥敼閫�
+
+鐢变簬 12972 涓妭鐐逛竴娆℃�у姞杞藉拰娓叉煋浼氬鑷存祻瑙堝櫒鍗℃锛屼骇鍝佹爲宸茬粡鏀逛负鎸夊睍寮�灞傜骇鎳掑姞杞姐��
+
+鏂板鎺ュ彛锛�
+
+- `GET /api/system/tree/product/roots`
+- `GET /api/system/tree/product/children?parentId={id}`
+- `GET /api/system/tree/product/side-roots?xNo={xNo}`
+- `GET /api/system/tree/product/x-numbers`
+- `GET /api/system/tree/product/selected?ids={id1,id2}`
+
+鍓嶇浣跨敤浣嶇疆锛�
+
+- `TreeManage.vue`锛氫骇鍝佹爲绠$悊
+- `ProductTreePicker.vue`锛氭晠闅滀笂鎶ュ拰鏁呴殰缂栬緫涓殑璁惧閫夋嫨
+- `FaultReport.vue`
+- `FaultEditDialog.vue`
+- `XcbReview.vue`
+
+澶勭悊缁撴灉锛�
+
+- 浜у搧鏍戠鐞嗛灞忓彧杩斿洖 2 涓牴鑺傜偣銆�
+- 灞曞紑鏍硅妭鐐规椂鍙姹傜洿鎺ュ瓙鑺傜偣銆�
+- 鏁呴殰椤甸潰 X 鍙蜂笉鍐嶉亶鍘嗗畬鏁存爲锛岀洿鎺ヨ姹� X 鍙峰垪琛ㄣ��
+- 缂栬緫鏁呴殰鏃堕�氳繃鈥滃凡閫夎澶団�濇帴鍙e弽鏄捐澶囧悕绉板拰璺緞銆�
+- 浜у搧鏍戠鐞嗛殣钘忎簡澶栭儴琛ㄤ笉瀛樺湪鐨勨�滅紪鐮佲�濆瓧娈点��
+- 浜у搧鑺傜偣鍒犻櫎閲囩敤杞垹闄わ紝璁剧疆 `ISDELETE=1`銆�
+
+### 5.4 浜у搧鏍戝緟纭浜嬮」
+
+鐢ㄦ埛宸茶鏄庯細瀹為檯缁撴瀯骞堕潪涓ユ牸鎸� `model -> side -> system1 -> system2 -> equipment` 鎺掑垪锛屽彲鑳界己灏� `system2`锛屼笖 `system2` 涓嬩篃鍙兘鎸傞潪 `equipment` 绫诲瀷鑺傜偣銆�
+
+鐩墠瀹炵幇浠嶆湁浠ヤ笅绾︽潫锛�
+
+- 鏈煡 `LAYER` 鐩墠缁熶竴鏄犲皠涓虹 6 绾с��
+- 鍓嶇褰撳墠鏈�澶х紪杈戝眰绾у浐瀹氫负 5锛岀 6 绾ц妭鐐归�氬父鍙兘鏌ョ湅锛岀偣鍑烩�滄柊澧炰笅绾р�濈殑鏉′欢涓嶆弧瓒炽��
+- 闇�瑕佺敤鎴峰悗缁‘璁ゆ湭鐭ュ眰绾ф槸鍙灞曠ず銆佸綊鍏モ�滃叾浠栤�濓紝杩樻槸鍏佽缁х画閰嶇疆涓嬬骇銆�
+- 浜у搧鏍戝啓鍏ョ殑鏄閮ㄨ〃鍚屼箟璇嶏紝姝e紡鐜闇�纭鏁版嵁搴撹处鍙峰 `CSICZB_ZB.ZT_BOAT_TREE` 鐨勫鍒犳敼鏉冮檺銆�
+- `DatabaseBootstrap` 涓粛淇濈暀鏃� `LX.T_PRODUCT_TREE` 寤鸿〃瀹氫箟锛屼絾 `ProductNode` 宸蹭笉鍐嶄娇鐢ㄦ棫琛紝`DataInitializer` 涔熷凡鍋滄鍒濆鍖栦骇鍝佹爲婕旂ず鏁版嵁銆�
+
+## 鍏�佹瀯寤轰笌杩愯
+
+### 6.1 鐜
+
+- Java锛歚C:\Program Files\Java\jdk1.8.0_172`
+- Maven锛歚D:\solidDir\apache-maven-3.6.1\bin\mvn.cmd`
+- Node/npm锛歚C:\Program Files\nodejs\npm.cmd`
+- 鍚庣绔彛锛歚8091`
+- 鍓嶇绔彛锛歚8082`
+- DM8锛歚127.0.0.1:5236`
+- DM8 鏁版嵁搴撹处鍙凤細`SYSDBA / SYSDBA`
+- 鐧诲綍绀轰緥锛歚admin / admin` 鎴� `admin / admin123`
+
+### 6.2 鍚庣鏋勫缓
+
+```powershell
+$env:JAVA_HOME='C:\Program Files\Java\jdk1.8.0_172'
+Set-Location 'D:\projectJava\lqx\emergency-repair-server'
+& 'D:\solidDir\apache-maven-3.6.1\bin\mvn.cmd' -B -DskipTests clean package
+```
+
+鏋勫缓缁撴灉锛氭垚鍔熴��
+
+JAR 璺緞锛�
+
+```text
+D:\projectJava\lqx\emergency-repair-server\target\emergency-repair-server-1.0.0-SNAPSHOT.jar
+```
+
+鍚姩绀轰緥锛�
+
+```powershell
+Start-Process `
+  -FilePath 'C:\Program Files\Java\jdk1.8.0_172\bin\java.exe' `
+  -ArgumentList '-jar','D:\projectJava\lqx\emergency-repair-server\target\emergency-repair-server-1.0.0-SNAPSHOT.jar' `
+  -WorkingDirectory 'D:\projectJava\lqx\emergency-repair-server' `
+  -WindowStyle Hidden `
+  -PassThru
+```
+
+### 6.3 鍓嶇鏋勫缓
+
+```powershell
+Set-Location 'D:\projectJava\lqx\emergency-repair-web'
+& 'C:\Program Files\nodejs\npm.cmd' run build
+```
+
+鏋勫缓缁撴灉锛氭垚鍔熴��
+
+鏋勫缓鍛婅锛�
+
+- `chunk-5be386f4` 绾� 1.01 MiB銆�
+- `chunk-vendors` 绾� 946 KiB銆�
+- 灞炰簬鍖呬綋绉憡璀︼紝涓嶉樆濉炶繍琛屻��
+
+鍚姩寮�鍙戞湇鍔★細
+
+```powershell
+Set-Location 'D:\projectJava\lqx\emergency-repair-web'
+& 'C:\Program Files\nodejs\npm.cmd' run serve
+```
+
+鍓嶇浠g悊閰嶇疆锛�
+
+- `/api` 浠g悊鍒� `http://127.0.0.1:8091`
+- 鍓嶇鍦板潃锛歚http://localhost:8082`
+
+## 涓冦�佸凡瀹屾垚楠岃瘉
+
+### 7.1 缂栬瘧楠岃瘉
+
+- 鍚庣锛歚mvn -B -DskipTests clean package` 閫氳繃銆�
+- 鍓嶇锛歚npm run build` 閫氳繃銆�
+
+### 7.2 娴忚鍣ㄩ獙璇�
+
+瑙掕壊绠$悊锛�
+
+- 鈥滅敤鎴疯缃�濇寜閽瓨鍦ㄣ��
+- 寮圭獥鍙嶆樉 44 涓敤鎴枫��
+- 棣栦釜瑙掕壊閫変腑 6 浜恒��
+
+鐢ㄦ埛绠$悊锛�
+
+- 鈥滄壒閲忚缃鑹测�濇寜閽瓨鍦ㄣ��
+- 瑙掕壊鎺堟潈闈㈡澘瀛樺湪銆�
+- 琛ㄦ牸杩斿洖 20 琛屻��
+
+浜у搧鏍戯細
+
+- 棣栧睆 2 涓牴鑺傜偣銆�
+- 灞曞紑绗竴涓牴鑺傜偣鍚庢樉绀� 5 涓洿鎺ュ瓙鑺傜偣銆�
+- X 鍙锋暟閲� 9 涓��
+- 宸查�夎澶囨帴鍙h兘杩斿洖鍚嶇О鍜屽畬鏁磋矾寰勩��
+- 浜у搧鏍戠鐞嗗拰鏁呴殰閫夋嫨璁惧寮圭獥鍧囧彲姝e父鍔犺浇锛屾湭鍑虹幇鏁存爲娓叉煋鍗℃銆�
+
+楠岃瘉鎴浘鍜岃剼鏈細
+
+- `.codex-run/role-users-verified.png`
+- `.codex-run/user-manage-verified.png`
+- `.codex-run/product-tree-verified.png`
+- `.codex-run/product-tree-lazy-verified.png`
+- `.codex-run/verify-ui.js`
+- `.codex-run/verify-lazy-tree.js`
+- `.codex-run/verify-picker.js`
+
+### 7.3 浜ゆ帴鏃惰繘绋嬬姸鎬�
+
+2026-10-04 12:28 閲嶆柊鏋勫缓骞跺惎鍔ㄥ悗锛屽綋鍓嶈繍琛岀姸鎬侊細`8082` 鍓嶇寮�鍙戞湇鍔$洃鍚腑锛宍8091` 鍚庣鐩戝惉涓�傚惎鍔ㄦ棩蹇椾綅浜� `.codex-run/backend.out.log` 鍜� `.codex-run/backend.err.log`銆�
+
+2026-10-03 鏈�鍚庨獙璇佹椂鏇捐繍琛岋細
+
+- 鍚庣绔彛锛歚8091`
+- 鍓嶇绔彛锛歚8082`
+- Edge 璋冭瘯绔彛锛歚9222`
+
+閲嶆柊鑱旇皟鏃舵寜绗叚鑺傚懡浠ゅ惎鍔ㄥ悗绔拰鍓嶇銆�
+
+## 鍏�佷富瑕佸彉鏇存枃浠�
+
+鍚庣锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/business/entity/ProductNode.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/log/OperationLogInterceptor.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysLoginLog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysOperationLog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysLoginLogMapper.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysOperationLogMapper.java`
+
+鍓嶇锛�
+
+- `emergency-repair-web/public/logo.png`
+- `emergency-repair-web/src/components/RepairLogo.vue`
+- `emergency-repair-web/src/components/ProductTreePicker.vue`
+- `emergency-repair-web/src/layout/AppLayout.vue`
+- `emergency-repair-web/src/router/index.js`
+- `emergency-repair-web/src/views/system/UserManage.vue`
+- `emergency-repair-web/src/views/system/RoleManage.vue`
+- `emergency-repair-web/src/views/system/MenuManage.vue`
+- `emergency-repair-web/src/views/system/MenuPermission.vue`
+- `emergency-repair-web/src/views/system/TreeManage.vue`
+- `emergency-repair-web/src/views/system/LoginLogManage.vue`
+- `emergency-repair-web/src/views/system/OperationLogManage.vue`
+- `emergency-repair-web/src/views/fault/FaultReport.vue`
+- `emergency-repair-web/src/views/fault/FaultEditDialog.vue`
+- `emergency-repair-web/src/views/fault/XcbReview.vue`
+
+## 涔濄�佹帴鎵嬪缓璁�
+
+1. 鍏堢‘璁� `git status`锛屼笉瑕佸洖閫�褰撳墠鏈彁浜や慨鏀广��
+2. 鍏堝惎鍔ㄥ悗绔紝鍐嶅惎鍔ㄥ墠绔紝纭 `8091` 鍜� `8082` 鍙闂��
+3. 浜у搧鏍戝悗缁紭鍏堢‘璁ゆ湭鐭� `LAYER` 鐨勫睍绀哄拰缂栬緫瑙勫垯銆�
+4. 姝e紡鑱旇皟鏃舵鏌ユ搷浣滄棩蹇楁槸鍚﹁兘鎷垮埌鐪熷疄 `X-Username`锛岄伩鍏嶅叏閮ㄥ洖閫�涓� `admin`銆�
+5. 濡傛灉瑕佹妸浜у搧鏍戝啓鍏ュ閮ㄥ悓涔夎瘝琛紝鍏堢‘璁ゆ暟鎹簱璐﹀彿鏉冮檺鍜屽閮ㄧ郴缁熷 `ISDELETE` 杞垹闄ょ殑绾﹀畾銆�
+
+## 鍗併��2026-10-04 琛ュ厖闇�姹傦紙13-16锛�
+
+| 搴忓彿 | 闇�姹� | 鐘舵�� | 瀹炵幇涓庨獙璇� |
+| --- | --- | --- | --- |
+| 13 | 鏁忔劅璇� codename 澧炲姞 `rk_` 鍓嶇紑 | 宸插畬鎴� | `public/sensitive-words.json`銆乣requirement/鏁忔劅璇嶅瓧鍏�.json` 宸叉洿鏂帮紱鑿滃崟鍙婇〉闈㈡棫鏂囨宸茶縼绉讳负 `rk_`锛涘墠绔繚鐣欐棤鍓嶇紑鍒悕鍏煎 |
+| 14 | 鏁呴殰缂栧彿鍓嶇紑鍚庡彴鍙厤缃� | 宸插畬鎴� | `application.yml` 鏂板 `fault-code.prefix`锛岄粯璁� `鍥哄畾鍓嶇紑-`锛涘疄娴嬬紪鍙� `鍥哄畾鍓嶇紑-001/A-261004-001` |
+| 15 | 鎵�鏈夎〃鑷姩璁板綍鍒涘缓浜恒�佷慨鏀逛汉 | 宸插畬鎴� | 鏂板 `BaseAuditEntity`銆乣CurrentUserInterceptor`銆乣AuditMetaObjectHandler`锛涘疄娴嬪垱寤哄啓鍏� `codex-create-user`锛屼慨鏀瑰啓鍏� `codex-update-user` |
+| 16 | 鎵�鏈夎〃閫昏緫鍒犻櫎骞惰嚜鍔ㄨ繃婊� | 宸插畬鎴� | 鑷缓琛ㄦ柊澧� `DEL_FLAG`锛屽疄浣撶户鎵� `@TableLogic`锛涙姤琛� SQL 宸插鍔犺繃婊わ紱瀹炴祴閫昏緫鍒犻櫎鍚庡垪琛ㄦ煡璇㈣繑鍥� 0 鏉� |
+
+琛ュ厖璇存槑锛�
+
+- 浜у搧缁撴瀯鏍戝閮ㄥ悓涔夎瘝琛� `LX.ZT_BOAT_TREE` 娌跨敤鑷繁鐨� `ISDELETE`锛屼笉棰濆澧炲姞 `UPDATE_BY`銆乣DEL_FLAG`銆�
+- 鏁呴殰缂栧彿搴忓彿鏌ヨ浼氬寘鍚�昏緫鍒犻櫎璁板綍锛岄伩鍏嶅敮涓�绱㈠紩鍐茬獊銆�
+- 鎿嶄綔鏃ュ織鏂板銆佷慨鏀广�佸垹闄ゅ潎宸插疄娴嬭惤搴撱��
+
+## 鍗佷竴銆�2026-10-04 鑱旇皟鏀跺熬锛�11銆�12銆�17-19锛�
+
+| 搴忓彿 | 闇�姹� | 鐘舵�� | 瀹炵幇涓庨獙璇� |
+| --- | --- | --- | --- |
+| 11 | 瀹屽伐鎯呭喌澶勭悊鏀逛负鈥滃畬宸モ�濇椂涓嶈烦杞畬宸ヤ俊鎭~鎶� | 宸插畬鎴� | 瀹屽伐澶勭悊鍙洿鏂版淳宸ュ崟鐘舵�侊紱瀹屽伐澶勭悊涓庡畬宸ュ~鎶ョ敱涓嶅悓鐢ㄦ埛鍒嗗埆鎿嶄綔 |
+| 12 | 瀹屽伐淇℃伅濉姤鍙樉绀哄凡瀹屽伐銆佹湰鍗曚綅銆佹湭濉姤娲惧伐鍗曪紝骞舵樉绀哄伐绋�/椤圭洰鍚嶇О | 宸插畬鎴� | 閫夋嫨鍒楄〃鎸夆�滃畬宸ョ姸鎬� + 鐧诲綍鐢ㄦ埛鎵�灞炲崟浣� + 灏氭湭濉姤鈥濋檺鍒讹紝骞舵樉绀哄伐绋嬪悕绉� |
+| 17 | 瑙掕壊绠$悊鐢ㄦ埛璁剧疆澧炲姞鈥滃叏閮� / 宸茶缃�濆垏鎹� | 宸插畬鎴� | `RoleManage.vue` 澧炲姞鑼冨洿杩囨护锛屽凡璁剧疆鍜屽叏閮ㄥ垪琛ㄥ彲鍒囨崲 |
+| 18 | 澧炲姞缁勭粐鏈烘瀯锛堥儴闂ㄦ爲琛級鍔熻兘 | 宸插畬鎴� | 鑿滃崟 `/system/organization`锛岄〉闈� `OrganizationManage.vue`锛屾暟鎹潵鑷� `CSICZB_ZB.SYS_DEPT`锛屾帴鍙� `/api/system/depts`锛涘綋鍓� 34 涓儴闂� |
+| 19 | 鎵夸慨鍗曚綅鏀逛负鏌ヨ閮ㄩ棬琛ㄤ腑 `DEPT_TYPE='cj'` 鐨勮褰� | 宸插畬鎴� | `SystemService.listContractors()` 鏌ヨ cj 閮ㄩ棬锛岄儴闂� ID/鍚嶇О鐢ㄤ簬瀹℃牳鍜屾淳鍗曪紱鎵夸慨鍗曚綅椤靛彧璇伙紱褰撳墠 7 涓� cj 閮ㄩ棬 |
+
+### 11.1 涓昏鍙樻洿鏂囦欢
+
+鍚庣锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java`
+
+鍓嶇锛�
+
+- `emergency-repair-web/src/views/system/OrganizationManage.vue`
+- `emergency-repair-web/src/views/system/ContractorManage.vue`
+- `emergency-repair-web/src/views/completion/CompletionReport.vue`
+- `emergency-repair-web/src/views/system/RoleManage.vue`
+- `emergency-repair-web/src/router/index.js`
+
+### 11.2 楠岃瘉缁撴灉
+
+- 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃銆�
+- `node .codex-run/verify-items-11-12-17.js`銆乣node .codex-run/verify-items-18-19.js` 閫氳繃銆�
+- 鎴浘锛歚.codex-run/organization-verified.png`銆乣.codex-run/contractor-readonly-verified.png`銆乣.codex-run/completion-report-verified.png`銆乣.codex-run/role-scope-verified.png`銆�
+- 绔埌绔复鏃舵晠闅� `鍥哄畾鍓嶇紑-088/A-261004-001` 宸茶窇閫氫笂鎶ャ�乆CB瀹℃牳鍒嗛厤鈥滃巶瀹�1/鍘傚2鈥濄�丆MB/JCC涓夌骇瀹℃壒鍜屾淳鍗曪紱鐢熸垚涓ゅ紶娲惧伐鍗曪紝鎵夸慨鍗曚綅涓� cj 閮ㄩ棬 ID/鍚嶇О銆�
+- 瀹屽伐濉姤鍙鎬э細`cjyh1-1` 浠呰鍘傚1娲惧伐鍗曪紝`cjyh2-1` 浠呰鍘傚2娲惧伐鍗曪紝`admin` 瑙佷袱寮狅紱涓存椂 `T_FAULT`銆乣T_WORK_ORDER` 鏁版嵁宸叉竻鐞嗐��
+
+### 11.3 褰撳墠鏈嶅姟
+
+- 鍓嶇 `http://localhost:8082`锛涘悗绔� `http://127.0.0.1:8091`锛汥M8 `127.0.0.1:5236`锛汦dge 璋冭瘯绔彛 `9222`銆�
+- 鐧诲綍 `admin / admin123`锛涙暟鎹簱 `SYSDBA/SYSDBA`锛屼笟鍔� schema `LX`銆�
+
+### 11.4 鎺ユ墜娉ㄦ剰
+
+- 闈炵鐞嗗憳瀹屽伐濉姤鐨勫崟浣嶅尮閰嶇洰鍓嶆寜 `DEPT_NAME`锛屼笉鏄� `DEPT_ID`锛涜嫢鍑虹幇鍚屽悕閮ㄩ棬锛屽簲鏀逛负 ID 绮剧‘鍖归厤銆�
+- 鍘嗗彶婕旂ず娲惧伐鍗曞彲鑳戒粛淇濆瓨鏃� `T_CONTRACTOR` 鍚嶇О锛岄潪绠$悊鍛� cj 鐢ㄦ埛鍙兘鐪嬩笉鍒帮紱`admin` 涓嶅彈鍗曚綅杩囨护褰卞搷銆�
+- `.codex-run`銆丱ffice 閿佹枃浠朵互鍙� `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 鏄湰鍦拌仈璋冩垨涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
diff --git "a/requirement/\345\206\263\347\255\226\350\241\250_\345\276\205\347\241\256\350\256\244\351\241\271.md" "b/requirement/\345\206\263\347\255\226\350\241\250_\345\276\205\347\241\256\350\256\244\351\241\271.md"
index a49edfb..eadb725 100644
--- "a/requirement/\345\206\263\347\255\226\350\241\250_\345\276\205\347\241\256\350\256\244\351\241\271.md"
+++ "b/requirement/\345\206\263\347\255\226\350\241\250_\345\276\205\347\241\256\350\256\244\351\241\271.md"
@@ -17,7 +17,7 @@
 >
 > **2026-09-28 鐢ㄦ埛纭锛圔1-B4锛�**锛氭淳宸ュ崟鈥滃畬宸モ�濅笌棣栭〉鈥滃凡瀹屽伐鈥濅负鍚屼竴缁熻鐘舵�侊紱棣栭〉涓庢淳宸ュ崟鐨勨�滃畬宸ュ緟璇曢獙鈥濆彛寰勪竴鑷达紱鈥滃緟澶囦欢鈥濆厑璁哥洿鎺モ�滃畬宸モ�濓紝鐢� AXU15 鎻愪氦鎴愬姛鍚庡啓鍏ワ紱鈥滃畬宸ュ緟璇曢獙鈥濊瘯楠屼笉閫氳繃鏃跺洖鍒扳�滄湭瀹屽伐鈥濓紝涓嶅崟璁锯�滆繑宸モ�濈姸鎬侊紝鍘熷洜鍐欏叆淇悗璐ㄩ噺鎰忚鎴栧娉ㄣ��
 >
-> **2026-09-28 鐢ㄦ埛纭锛圔5 涔嬪悗鐨勭 4 鏉℃柊闇�姹傦級**锛氭晠闅滅紪鍙蜂笉鍐嶄汉宸ュ~鍐欙紝鏀逛负绯荤粺鑷姩鐢熸垚锛屾牸寮� `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛圶鍙蜂负3浣嶆暟瀛楋紱鍒嗙被鍙朤闃熶笓涓氶厤缃殑 A/B锛沋YMMDD 涓轰笂鎶ユ棩鏈燂紱nnn 涓哄悓涓� X鍙� + 鍚屼竴 T闃熶笓涓� + 鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紝浠� 001 璧凤級銆傜郴缁熺鐞嗘柊澧炩�滃伐绋嬩笓涓氶厤缃�濓紙瀛楁浠呭悕绉帮級涓庘�淭闃熶笓涓氶厤缃�濓紙瀛楁涓哄悕绉般�佸垎绫籄/B锛夛紱T闃熶笓涓氫笌宸ョ▼涓撲笟鐨勫彇鍊煎潎鏀逛负浠庨厤缃鍙栵紝涓嶅啀鍓嶇纭紪鐮併�傛淳鍗曞彿淇濇寔鈥滄晠闅滅紪鍙�/nn鈥濓紙鎵夸慨鍗曚綅搴忓彿浠� 01 璧凤級銆�
+> **2026-09-28 鐢ㄦ埛纭锛圔5 涔嬪悗鐨勭 4 鏉℃柊闇�姹傦級**锛氭晠闅滅紪鍙蜂笉鍐嶄汉宸ュ~鍐欙紝鏀逛负绯荤粺鑷姩鐢熸垚锛屾牸寮� `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛圶鍙蜂负3浣嶆暟瀛楋紱鍒嗙被鍙朤闃熶笓涓氶厤缃殑 A/B锛沋YMMDD 涓轰笂鎶ユ棩鏈燂紱nnn 涓哄悓涓� X鍙� + 鍚屼竴 T闃熶笓涓� + 鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紝浠� 001 璧凤級銆傜郴缁熺鐞嗘柊澧炩�滃伐绋嬩笓涓氶厤缃�濓紙瀛楁浠呭悕绉帮級涓庘�淭闃熶笓涓氶厤缃�濓紙瀛楁涓哄悕绉般�佸垎绫籄/B锛夛紱T闃熶笓涓氫笌宸ョ▼涓撲笟鐨勫彇鍊煎潎鏀逛负浠庨厤缃鍙栵紝涓嶅啀鍓嶇纭紪鐮併�傛淳鍗曞彿淇濇寔鈥滄晠闅滅紪鍙�/nn鈥濓紙鎵夸慨鍗曚綅搴忓彿浠� 01 璧凤級銆�
 > 
 > **2026-09-17 鐢ㄦ埛纭**锛欰XU5/6/7 涓夊紶瀹℃壒椤靛叡閫氾紝淇濈暀鍒楄〃銆佸埛鏂般�佹壒閲忓鎵癸紱鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱鐣岄潰銆佽彍鍗曞拰鎸夐挳缁熶竴浣跨敤鈥滃鎵光�濓紝JCC澶勯暱椤甸潰鍙婅彍鍗曠粺涓�鍐欌�淛CC澶勯暱瀹℃壒鈥濓紱鏁呴殰淇℃伅澧炲姞 CMB棰嗗銆丣CC鍔╃悊銆丣CC澶勯暱涓夌粍瀹℃壒鎰忚銆佸鎵圭粨鏋滐紙閫氳繃/涓嶉�氳繃锛夈�佸鎵规椂闂村瓧娈碉紱澧炲姞閿�椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂村瓧娈碉紝鏁呴殰涓婃姤闃舵鐨勯攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛涢攢椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�
 >
@@ -32,12 +32,12 @@
 | AXU0 棣栭〉闂ㄦ埛 | 宸叉牳瀵� 2026-09-09锛氶《閮ㄦ寜閽尯锛堝彸渚э細鍣ㄦ潗瀹℃牳/JC淇℃伅锛�+ 涓儴鎸塜鍙风粺璁¤〃锛�11鍒楋紝绀轰緥2琛岋紝棣栬褰撳墠鐘舵��=灏忎慨锛�+ 搴曢儴涓夋爮锛堝叕鍛�/鍏变韩璧勬簮涓嬭浇/甯哥敤鍔熻兘锛夛紱鈥滆嚜绛瑰櫒鏉愬鏍糕�濃�淴鍙峰綋鍓嶄俊鎭�濅笉灞炴湰椤靛凡鍒狅紱鑿滃崟鏍戝彟琛屽崟鐙牳瀵癸紱TD=鑹囬槦宸茬‘璁わ紱鍏憡/鍏变韩璧勬簮涓嬭浇/甯哥敤鍔熻兘=鍗犱綅鏍囩锛涢《閮ㄦ寜閽紙鍣ㄦ潗瀹℃牳/JC淇℃伅锛夊唴瀹瑰悗缁啀璁� | 宸叉牳瀵� 2026-09-09 |
 | AXU1 娴佺▼鎬昏 | **宸叉牳瀵� 2026-09-11锛�2026-09-20 鏇存柊楠屾敹娴佺▼**锛氱涓�鐗堜笉鎸夊師濮嬮渶姹傦紝鏀逛负銆岀畝鍖栨祦绋� + 鍙仛缃戠粶鐗堛�嶏紱纭娴佺▼ = T鍛樻晠闅滀笂鎶� 鈫� XCB鍔╃悊瀹℃牳锛堝垎閰嶅涓壙淇崟浣嶃�佹媶鍗曘�佺敓鎴愬伐绋嬪崟鍙凤級鈫� CMB棰嗗瀹℃壒 鈫� JCC鍔╃悊瀹℃壒 鈫� JCC澶勯暱瀹℃壒 鈫� XCB鍔╃悊娲惧崟锛堟湭娲惧崟鏁呴殰鍕鹃�夊悗涓�閿淳鍗曪紝鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛夆啋 鍘傚绾夸笅淇悊 鈫� 鍘傚涓婃姤瀹屽伐淇℃伅 鈫� 楠屾敹鐢宠 鈫� 璐ㄦ鍛橀獙鏀� 鈫� 璐ㄦ璐熻矗浜洪獙鏀� 鈫� 瀹屽伐楠屾敹鎵撳嵃杈撳嚭 鈫� 淇悗璐ㄩ噺璇勭骇 鈫� 楠屾敹瀹屾垚 | 宸叉牳瀵� 2026-09-11 / 楠屾敹閾� 2026-09-20 |
 | AXU2 鏁版嵁鍖呭鍏� | 銆愬凡瑙e喅 2026-09-14銆戝彧鍋氱綉缁滅増銆佹棤瀵煎叆鍔熻兘 鈫� 鏈〉鏁翠綋鍙栨秷銆佽彍鍗曢」鈥滄暟鎹寘瀵煎叆鈥濆垹闄わ紝鏃犻渶鏍稿 | 宸插彇娑� 2026-09-14 |
-| AXU3 鏁呴殰涓婃姤 | **宸叉牳瀵� 2026-09-16锛�2026-09-28 鏁呴殰缂栧彿鏀逛负绯荤粺鑷姩鐢熸垚**锛氭晠闅滅紪鍙风敱绯荤粺鎸� `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛屼笉鍐嶄汉宸ュ~鍐欙紙鍘� 2026-09-20 鐨勨�滀汉宸ュ~鍐欌�濆彛寰勪綔搴燂級锛涘瓧娈�/鎸夐挳閫氳繃锛涘垪琛ㄥ鍔犫�滀笂鎶ユ棩鏈熲�濃�滀笂鎶ュ崟浣嶁�濓紱浠诲姟鐘舵��=鍏ㄥ眬涓冧釜鍊硷紙鏈笂鎶� / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄锛夛紱鏁呴殰褰卞搷=鍙厤缃瓧鍏革紙绀轰緥鈥滀緵姘斺�濓級锛屾晠闅滄�ц川=鍙厤缃瓧鍏革紙绀轰緥鈥滈噸澶р�濓級锛涒�滃~鍐欐晠闅滃垎鏋愭姤鍛娾�濈涓�鐗堟殏涓嶅疄鐜帮紱鏂板鏀硅〃鍗曞紡锛屽甫榛樿鍊笺�佷笅鎷夊瓧娈靛拰浜у搧缁撴瀯鏍戦�夋嫨锛涘仠娉婂湴瀛楁鍙栨秷锛涘垹闄ら檺鍒�=鏈笂鎶ュ彲鍒犻櫎锛涘凡涓婃姤涓嶅彲鍒犻櫎锛屼换鍔$姸鎬佷负鈥滀笂绾ф墦鍥炩�濇椂闄ゅ | 宸叉牳瀵� 2026-09-16 |
+| AXU3 鏁呴殰涓婃姤 | **宸叉牳瀵� 2026-09-16锛�2026-09-28 鏁呴殰缂栧彿鏀逛负绯荤粺鑷姩鐢熸垚**锛氭晠闅滅紪鍙风敱绯荤粺鎸� `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛屼笉鍐嶄汉宸ュ~鍐欙紙鍘� 2026-09-20 鐨勨�滀汉宸ュ~鍐欌�濆彛寰勪綔搴燂級锛涘瓧娈�/鎸夐挳閫氳繃锛涘垪琛ㄥ鍔犫�滀笂鎶ユ棩鏈熲�濃�滀笂鎶ュ崟浣嶁�濓紱浠诲姟鐘舵��=鍏ㄥ眬涓冧釜鍊硷紙鏈笂鎶� / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄锛夛紱鏁呴殰褰卞搷=鍙厤缃瓧鍏革紙绀轰緥鈥滀緵姘斺�濓級锛屾晠闅滄�ц川=鍙厤缃瓧鍏革紙绀轰緥鈥滈噸澶р�濓級锛涒�滃~鍐欐晠闅滃垎鏋愭姤鍛娾�濈涓�鐗堟殏涓嶅疄鐜帮紱鏂板鏀硅〃鍗曞紡锛屽甫榛樿鍊笺�佷笅鎷夊瓧娈靛拰浜у搧缁撴瀯鏍戦�夋嫨锛涘仠娉婂湴瀛楁鍙栨秷锛涘垹闄ら檺鍒�=鏈笂鎶ュ彲鍒犻櫎锛涘凡涓婃姤涓嶅彲鍒犻櫎锛屼换鍔$姸鎬佷负鈥滀笂绾ф墦鍥炩�濇椂闄ゅ | 宸叉牳瀵� 2026-09-16 |
 | AXU4 XCB鍔╃悊瀹℃牳 | **宸叉牳瀵� 2026-09-16锛�2026-09-17 琛ュ厖**锛氭湰椤靛嵆绗� 2 姝� XCB鍔╃悊瀹℃牳锛涢〉闈㈠強鑿滃崟鍚嶇О缁熶竴鍐欌�淴CB鍔╃悊瀹℃牳鈥濓紝椤甸潰鍐呪�滃鎵光�濇枃妗堢粺涓�鏀逛负鈥滃鏍糕�濓紱鍒嗛厤澶氫釜鎵夸慨鍗曚綅銆佹寜鎵夸慨鍗曚綅鎷嗗崟銆佺敓鎴愬伐绋嬪崟鍙峰潎鍦ㄦ湰鐜妭锛涘垎閰嶆壙淇崟浣嶆椂锛屽�欓�夊垪琛ㄦ潵鑷�滅郴缁熺鐞� > 鎵夸慨鍗曚綅绠$悊鈥濓紝鎸夆�滄帓搴忊�濆彇鍓�7涓父鐢ㄦ壙淇崟浣嶆樉绀猴紱閫氳繃鍚庡嵆鍙鍣ㄦ潗锛涘悗缁淳鍗曢〉鏄剧ず鏈淳鍗曟晠闅滐紝鍕鹃�夊悗涓�閿淳鍗曞苟鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛涙煡璇�=宸ョ▼閫氱敤鏉′欢+鎵夸慨鍗曚綅/鏁呴殰鎬ц川/浠诲姟鐘舵�侊紱鍘烩�滄坊鍔犺繘搴﹀娉ㄢ�濃�淐MB宸查槄鈥濇寜閽紱璺熻釜鎶�鏈槦鍔犲叆鏁呴殰淇℃伅涓斿崟閫夛紱鈥滆姹傚畬宸ユ椂闂粹�濈粺涓�涓衡�滆姹傚畬宸ユ棩鏈熲�濓紱浠诲姟鐘舵��=鍏ㄥ眬涓冧釜鍊硷紱鈥滀笂绾ф墦鍥炩�濅负鐙珛鐘舵�佸�硷紝涓庘�滃凡涓婃姤XCB鈥濅笟鍔¤涔夌瓑浠蜂絾娴佽浆杞ㄨ抗鍒嗗紑锛沊CB鏈幆鑺傚鏍镐笉閫氳繃鏃朵笉杞�滀笂绾ф墦鍥炩�濓細鍐呭涓嶅悎鏍艰蛋鈥滀慨鏀光�濓紝涓嶄綔涓烘晠闅滃鐞嗚蛋鈥滈攢椤光�濓紱鏁呴殰涓婃姤闃舵鐨勯攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛岃褰曢攢椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂达紱閿�椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿 | 宸叉牳瀵� 2026-09-16/17 |
 | AXU5/6/7 涓変釜瀹℃壒椤� | **宸叉牳瀵� 2026-09-17**锛氫笁椤靛叡鐢ㄥ悓涓�濂楃晫闈㈠拰鍔熻兘锛涗繚鐣欏垪琛ㄣ�佸埛鏂般�佹壒閲忓鎵癸紱鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱鐣岄潰銆佽彍鍗曞拰鎸夐挳缁熶竴浣跨敤鈥滃鎵光�濓紝AXU7 缁熶竴鍐欌�淛CC澶勯暱瀹℃壒鈥濓紱涓夐〉涓嶉�氳繃鍧囩洿鎺ラ��鍥瀀CB鍔╃悊銆佷换鍔$姸鎬佷负鈥滀笂绾ф墦鍥炩�濓紱鏁呴殰淇℃伅澧炲姞涓夌粍瀹℃壒鎰忚銆佸鎵圭粨鏋溿�佸鎵规椂闂村瓧娈� | 宸叉牳瀵� 2026-09-17 |
 | AXU8 宸ョ▼绠$悊鎬昏 | 鐢ㄦ埛瑕佹眰鏆傜紦鏍稿锛�2026-09-20锛夛紱鐘舵�佺瓫閫夊舰寮忓悗缁鐞� | 鏆傜紦 2026-09-20 |
 | AXU9 CMB棰嗗宸查槄 | 銆愭殏涓嶅鐞� 2026-09-20銆戜笉绾冲叆褰撳墠闇�姹傦紝鐘舵�佸垪鍚箟鏃犻渶缁х画纭 | 鏆備笉澶勭悊 |
-| AXU10 娲惧崟绠$悊 | **宸茬‘璁� 2026-09-20**锛氬叆鍙e湪 JCC澶勯暱瀹℃壒鍚庯紱鏄剧ず鏈淳鍗曟晠闅滐紱鍕鹃�夊悗涓�閿淳鍗曪紱鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛涚紪鍙蜂负鏁呴殰缂栧彿 + `/01`銆乣/02` 绛夛紱闄も�滀竴閿淳鍗曗�濆鍘熸埅鍥惧叾浠栦笟鍔℃寜閽殏涓嶉渶瑕侊紱鏁呴殰缂栧彿鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紙`娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛夈�� | 宸茬‘璁� 2026-09-20 |
+| AXU10 娲惧崟绠$悊 | **宸茬‘璁� 2026-09-20**锛氬叆鍙e湪 JCC澶勯暱瀹℃壒鍚庯紱鏄剧ず鏈淳鍗曟晠闅滐紱鍕鹃�夊悗涓�閿淳鍗曪紱鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛涚紪鍙蜂负鏁呴殰缂栧彿 + `/01`銆乣/02` 绛夛紱闄も�滀竴閿淳鍗曗�濆鍘熸埅鍥惧叾浠栦笟鍔℃寜閽殏涓嶉渶瑕侊紱鏁呴殰缂栧彿鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紙`鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛夈�� | 宸茬‘璁� 2026-09-20 |
 | AXU11/12/13 | 銆愭殏涓嶅鐞� 2026-09-20銆戜笉绾冲叆褰撳墠闇�姹� | 鏆備笉澶勭悊 |
 | AXU14 瀹屽伐鎯呭喌澶勭悊 | **宸茬‘璁� 2026-09-20**锛氭淳鍗曞悗杩涘叆鏈〉锛涙瘡涓�琛�=1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛岋紱鍘绘帀鈥滃伐绋嬪崟鍗曞彿鈥濓紝澧炲姞鈥滃畬宸ョ姸鎬佲�濓紱瀹屽伐鐘舵�佸浐瀹氫负鈥滄湭瀹屽伐锛堥粯璁わ級/寰呭浠�/瀹屽伐寰呰瘯楠�/瀹屽伐鈥濓紱鍘绘帀鈥滃鍑衡�濃�滄坊鍔犺繘搴﹀娉ㄢ�濃�滈攢椤光�濓紱鍕鹃��1琛屽悗鍙敤鈥滄湭瀹屽伐/寰呭浠�/瀹屽伐寰呰瘯楠屸�濇寜閽缃姸鎬侊紱T鍛樿瘎鍒嗘妸淇悗璐ㄩ噺璇勫垎鍜屼慨鍚庤川閲忔剰瑙佸啓鍥炴墍閫夋淳宸ュ崟锛涒�滃畬宸モ�濇寜閽粎璺宠浆 AXU15鈥滃畬宸ヤ俊鎭笂鎶モ�濓紱AXU15 鎴愬姛鎻愪氦鍚庯紝灏嗗叧鑱旀淳宸ュ崟瀹屽伐鐘舵�佸啓涓衡�滃畬宸モ�濄��<br>銆愬緟纭銆戞搷浣滆鑹层�佸悇鐘舵�佹寜閽惎鐢ㄦ潯浠躲�佽烦杞� AXU15 鏃舵槸鍚︿紶鍏ラ�変腑鐨勬淳宸ュ崟 | 宸茬‘璁� 2026-09-20锛堥儴鍒嗙粏鑺傚緟纭锛� |
 | 瀹屽伐楠屾敹娴佺▼锛�5鑿滃崟锛屾棤鐙珛鎴浘锛� | **宸茬‘璁� 2026-09-20**锛欰XU15 鎴愬姛鎻愪氦鍚庝緷娆¤繘鍏ラ獙鏀剁敵璇� 鈫� 璐ㄦ鍛橀獙鏀� 鈫� 璐ㄦ璐熻矗浜洪獙鏀� 鈫� 瀹屽伐楠屾敹鎵撳嵃杈撳嚭 鈫� 淇悗璐ㄩ噺璇勭骇锛涗粎璐ㄦ鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀舵彁渚涒�滈�氳繃/鎵撳洖鈥濓紱璐ㄦ鍛橀獙鏀舵墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜洪獙鏀舵墦鍥炶川妫�鍛橀獙鏀讹紱楠屾敹鐢宠銆佸畬宸ラ獙鏀舵墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾т负绠�鍗曞姩浣溿��<br>銆愬緟纭銆戝悇鐜妭鎿嶄綔瑙掕壊銆佹墦鍗拌緭鍑鸿涓恒�佷慨鍚庤川閲忚瘎绾т笌 AXU14 璇勫垎瀛楁鐨勫叧绯� | 宸茬‘璁� 2026-09-20锛堢粏鑺傚緟纭锛� |
@@ -78,7 +78,7 @@
 16. 銆愬凡瑙e喅 2026-09-16銆慩CB鍔╃悊瀹℃牳涓嶉�氳繃鍚庣殑澶勭悊锛氫笉杞�滀笂绾ф墦鍥炩�濓紱鍐呭涓嶅悎鏍兼椂鐢盭CB鍔╃悊閫氳繃鈥滀慨鏀光�濇寜閽慨姝e唴瀹癸紝涓嶄綔涓烘晠闅滃鐞嗘椂鐐光�滈攢椤光�濄��
 17. 銆愬凡瑙e喅 2026-09-17銆慉XU5/6/7 涓夊紶瀹℃壒椤靛叡閫氭寜閽�佸鎵圭敤璇�佸鎵瑰瓧娈电粍锛屼互鍙娾�淛CC澶勯暱瀹℃壒鈥濆悕绉扮粺涓�銆�
 18. 銆愬凡瑙e喅 2026-09-17銆戞晠闅滀笂鎶ラ樁娈甸攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛涙晠闅滀俊鎭鍔犻攢椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂村瓧娈碉紱閿�椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�
-19. 銆愭洿鏂� 2026-09-28銆戞晠闅滅紪鍙锋敼涓虹郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛圶鍙�3浣嶆暟瀛椼�佸垎绫诲彇T闃熶笓涓氶厤缃瓵/B銆乶nn 鎸夊悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡浠� 001 閫掑锛夛紱2026-09-20 鐨勨�滀汉宸ュ~鍐欍�佺郴缁熶笉鑷姩鐢熸垚鈥濆彛寰勪綔搴熴��
+19. 銆愭洿鏂� 2026-09-28銆戞晠闅滅紪鍙锋敼涓虹郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛圶鍙�3浣嶆暟瀛椼�佸垎绫诲彇T闃熶笓涓氶厤缃瓵/B銆乶nn 鎸夊悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡浠� 001 閫掑锛夛紱2026-09-20 鐨勨�滀汉宸ュ~鍐欍�佺郴缁熶笉鑷姩鐢熸垚鈥濆彛寰勪綔搴熴��
 20. 銆愬凡瑙e喅 2026-09-20銆慉XU14 瀹屽伐鎯呭喌澶勭悊锛氫綅浜庢淳鍗曚箣鍚庯紱姣忎竴琛�=1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛岋紱鍘绘帀鈥滃伐绋嬪崟鍗曞彿鈥濓紝澧炲姞鈥滃畬宸ョ姸鎬佲�濓紱瀹屽伐鐘舵��4绉嶅��=鏈畬宸ワ紙榛樿锛�/寰呭浠�/瀹屽伐寰呰瘯楠�/瀹屽伐锛涘幓鎺夊鍑恒�佹坊鍔犺繘搴﹀娉ㄣ�侀攢椤癸紱鍕鹃��1琛屽悗鍙敤鈥滄湭瀹屽伐/寰呭浠�/瀹屽伐寰呰瘯楠屸�濇寜閽缃姸鎬侊紱T鍛樿瘎鍒嗗啓鍥炰慨鍚庤川閲忚瘎鍒嗗拰鎰忚锛涒�滃畬宸モ�濇寜閽粎璺宠浆 AXU15锛汚XU15 鎴愬姛鎻愪氦鍚庯紝灏嗗叧鑱旀淳宸ュ崟瀹屽伐鐘舵�佸啓涓衡�滃畬宸モ�濄��
 21. 銆怉XU14 寰呯‘璁ゃ�戞搷浣滆鑹层�佸悇鐘舵�佽缃寜閽殑鍚敤鏉′欢銆佽烦杞� AXU15 鏃舵槸鍚︿紶鍏ラ�変腑鐨勬淳宸ュ崟銆�
 22. 銆愬凡瑙e喅 2026-09-20銆戝畬宸ラ獙鏀舵祦绋嬩负5涓彍鍗曪細楠屾敹鐢宠銆佽川妫�鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀躲�佸畬宸ラ獙鏀舵墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾э紱鍙湁璐ㄦ鍛橀獙鏀跺拰璐ㄦ璐熻矗浜洪獙鏀舵彁渚涢�氳繃/鎵撳洖锛岃川妫�鍛樻墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜烘墦鍥炶川妫�鍛橀獙鏀讹紱鍏朵綑3涓彍鍗曚负绠�鍗曞姩浣溿��
diff --git "a/requirement/\345\211\21544\351\241\265\351\234\200\346\261\202\346\242\263\347\220\206.md" "b/requirement/\345\211\21544\351\241\265\351\234\200\346\261\202\346\242\263\347\220\206.md"
index 30fba92..49c0dc1 100644
--- "a/requirement/\345\211\21544\351\241\265\351\234\200\346\261\202\346\242\263\347\220\206.md"
+++ "b/requirement/\345\211\21544\351\241\265\351\234\200\346\261\202\346\242\263\347\220\206.md"
@@ -5,7 +5,7 @@
 > 绯荤粺璁捐鍘熷垯锛堢敤鎴风‘璁� 2026-08-25锛夛細涓嶆悶涓撻棬鐨勬祦绋嬪紩鎿庯紱瀹℃壒閾句笌宸ョ▼鐘舵�佸潎鐢ㄤ笟鍔¤〃涓婄殑**鐘舵�佸瓧娈�**绠$悊娴佽浆锛岀敱涓氬姟鎿嶄綔瑙﹀彂骞跺湪浠g爜涓牎楠屽悎娉曟�с��
 > 鎶�鏈矾绾匡紙鐢ㄦ埛纭 2026-08-28 / 2026-09-20锛夛細鍓嶇 **Vue 2 + Element UI**锛屽悗绔� **Spring Boot + JDK 8 + MyBatis-Plus 3.5.3**锛屽弬鑰� `MyProject\trafficAudit`銆傛暟鎹簱浠呭紑鍙�**杈炬ⅵ8锛圖M8锛�**锛屼笉寮�鍙� MySQL锛涘湴鍧� `127.0.0.1:5236`锛岀敤鎴峰悕 `SYSDBA`锛屽瘑鐮� `SYSDBA`锛屾暟鎹簱澶у皬鍐欐晱鎰燂紱琛ㄥ悕鍜屽瓧娈靛悕缁熶竴浣跨敤澶у啓銆備复鎶慨妯″紡 `LX` 闇�瑕佹柊寤猴紱`SYS_DEPT`銆乣SYS_USER` 浣跨敤 `CSICZB_ZB` 妯″紡涓嬬殑鍚屽悕琛ㄥ苟閫氳繃鍚屼箟璇嶈闂�傝缁嗙害鏉熻 `requirement\鎶�鏈矾绾�.md`銆�
 > 鏇存柊璇存槑锛�2026-09-16锛夛細AXU3 鏁呴殰涓婃姤銆丄XU4 XCB鍔╃悊瀹℃牳宸叉牳瀵瑰苟鍥哄寲锛涗换鍔$姸鎬佺粺涓�涓轰竷涓�硷紙鍘熷叚鎬� + 涓婄骇鎵撳洖锛夛紱H 宸茬撼鍏ユ晱鎰熻瘝瀛楀吀锛岄潪瀵嗘潗鏂欑粺涓�鍐欌�滄槸鍚︽秹H鈥濄��
-> 鏇存柊璇存槑锛�2026-09-20锛夛細JCC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗鐐瑰嚮鈥滀竴閿淳鍗曗�濓紱鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛屾淳宸ュ崟缂栧彿涓烘晠闅滅紪鍙� + `/01`銆乣/02` 绛夛紱闄も�滀竴閿淳鍗曗�濆锛屽師 AXU10 鍏朵粬涓氬姟鎸夐挳鏆備笉闇�瑕侊紱AXU8 鏆傜紦鏍稿锛涙晠闅滅紪鍙风敱绯荤粺鎸� `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛�2026-09-28 鍙e緞鏇存柊锛屾浛浠e師鈥滀汉宸ュ~鍐欌�濓級锛屾晠闅滀俊鎭繚鐣欌�滄晠闅滅紪鍙封�濆瓧娈点�侫XU14鈥滃畬宸ユ儏鍐靛鐞嗏�濅綅浜庢淳鍗曚箣鍚庯紱姣忎竴琛屼负1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛岋紱鍒楄〃鍘绘帀鈥滃伐绋嬪崟鍗曞彿鈥濄�佸鍔犫�滃畬宸ョ姸鎬佲�濓紱鍘绘帀鈥滃鍑衡�濃�滄坊鍔犺繘搴﹀娉ㄢ�濃�滈攢椤光�濇寜閽紱娲惧伐鍗曞畬宸ョ姸鎬佸浐瀹氫负鈥滄湭瀹屽伐锛堥粯璁わ級/寰呭浠�/瀹屽伐寰呰瘯楠�/瀹屽伐鈥濆洓绉嶏紱鍕鹃�夊悗鍙敤涓変釜鐘舵�佹寜閽缃�滄湭瀹屽伐/寰呭浠�/瀹屽伐寰呰瘯楠屸�濓紝鈥滃畬宸モ�濇寜閽彧璺宠浆AXU15锛屼笉绔嬪嵆鏀圭姸鎬侊紱AXU15鎴愬姛鎻愪氦鍚庡皢鍏宠仈娲惧伐鍗曞畬宸ョ姸鎬佸啓涓衡�滃畬宸モ�濓紱闅忓悗杩涘叆瀹屽伐楠屾敹娴佺▼锛氶獙鏀剁敵璇� 鈫� 璐ㄦ鍛橀獙鏀� 鈫� 璐ㄦ璐熻矗浜洪獙鏀� 鈫� 瀹屽伐楠屾敹鎵撳嵃杈撳嚭 鈫� 淇悗璐ㄩ噺璇勭骇锛涗粎璐ㄦ鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀舵彁渚涢�氳繃/鎵撳洖锛岃川妫�鍛樻墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜烘墦鍥炶川妫�鍛橀獙鏀讹紝鍏朵綑3涓彍鍗曚负绠�鍗曞姩浣滐紱T鍛樿瘎鍒嗗啓鍥炰慨鍚庤川閲忚瘎鍒嗗拰鎰忚銆傚彟纭锛氱郴缁熺鐞嗕竴绾ц彍鍗曞寘鍚壙淇崟浣嶇鐞嗐�佷骇鍝佺粨鏋勬爲绠$悊銆佽澶囧熀纭�搴撶鐞嗭紱鎵夸慨鍗曚綅瀛楁涓哄悕绉般�佺畝绉般�佸崟浣嶆�ц川銆佹帓搴忋�佽仈绯讳汉銆佹槸鍚﹀け淇″崟浣嶏紝鎺掑簭鐢ㄤ簬XCB鍔╃悊瀹℃牳鏃舵樉绀哄墠7涓父鐢ㄦ壙淇崟浣嶏紱浜у搧缁撴瀯鏍戜负鍨嬪彿鈫扻鍙封啋涓�绾х郴缁熲啋浜岀骇绯荤粺鈫掕澶囩殑5绾ф爲锛岃澶囧熀纭�搴撲负涓�绾х郴缁熲啋浜岀骇绯荤粺鈫掕澶囩殑3绾ф爲锛涗骇鍝佺粨鏋勬爲璁惧涓哄疄渚嬶紝璁惧鍩虹搴撹澶囦负绫汇��
+> 鏇存柊璇存槑锛�2026-09-20锛夛細JCC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗鐐瑰嚮鈥滀竴閿淳鍗曗�濓紱鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛屾淳宸ュ崟缂栧彿涓烘晠闅滅紪鍙� + `/01`銆乣/02` 绛夛紱闄も�滀竴閿淳鍗曗�濆锛屽師 AXU10 鍏朵粬涓氬姟鎸夐挳鏆備笉闇�瑕侊紱AXU8 鏆傜紦鏍稿锛涙晠闅滅紪鍙风敱绯荤粺鎸� `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛�2026-09-28 鍙e緞鏇存柊锛屾浛浠e師鈥滀汉宸ュ~鍐欌�濓級锛屾晠闅滀俊鎭繚鐣欌�滄晠闅滅紪鍙封�濆瓧娈点�侫XU14鈥滃畬宸ユ儏鍐靛鐞嗏�濅綅浜庢淳鍗曚箣鍚庯紱姣忎竴琛屼负1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛岋紱鍒楄〃鍘绘帀鈥滃伐绋嬪崟鍗曞彿鈥濄�佸鍔犫�滃畬宸ョ姸鎬佲�濓紱鍘绘帀鈥滃鍑衡�濃�滄坊鍔犺繘搴﹀娉ㄢ�濃�滈攢椤光�濇寜閽紱娲惧伐鍗曞畬宸ョ姸鎬佸浐瀹氫负鈥滄湭瀹屽伐锛堥粯璁わ級/寰呭浠�/瀹屽伐寰呰瘯楠�/瀹屽伐鈥濆洓绉嶏紱鍕鹃�夊悗鍙敤涓変釜鐘舵�佹寜閽缃�滄湭瀹屽伐/寰呭浠�/瀹屽伐寰呰瘯楠屸�濓紝鈥滃畬宸モ�濇寜閽彧璺宠浆AXU15锛屼笉绔嬪嵆鏀圭姸鎬侊紱AXU15鎴愬姛鎻愪氦鍚庡皢鍏宠仈娲惧伐鍗曞畬宸ョ姸鎬佸啓涓衡�滃畬宸モ�濓紱闅忓悗杩涘叆瀹屽伐楠屾敹娴佺▼锛氶獙鏀剁敵璇� 鈫� 璐ㄦ鍛橀獙鏀� 鈫� 璐ㄦ璐熻矗浜洪獙鏀� 鈫� 瀹屽伐楠屾敹鎵撳嵃杈撳嚭 鈫� 淇悗璐ㄩ噺璇勭骇锛涗粎璐ㄦ鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀舵彁渚涢�氳繃/鎵撳洖锛岃川妫�鍛樻墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜烘墦鍥炶川妫�鍛橀獙鏀讹紝鍏朵綑3涓彍鍗曚负绠�鍗曞姩浣滐紱T鍛樿瘎鍒嗗啓鍥炰慨鍚庤川閲忚瘎鍒嗗拰鎰忚銆傚彟纭锛氱郴缁熺鐞嗕竴绾ц彍鍗曞寘鍚壙淇崟浣嶇鐞嗐�佷骇鍝佺粨鏋勬爲绠$悊銆佽澶囧熀纭�搴撶鐞嗭紱鎵夸慨鍗曚綅瀛楁涓哄悕绉般�佺畝绉般�佸崟浣嶆�ц川銆佹帓搴忋�佽仈绯讳汉銆佹槸鍚﹀け淇″崟浣嶏紝鎺掑簭鐢ㄤ簬XCB鍔╃悊瀹℃牳鏃舵樉绀哄墠7涓父鐢ㄦ壙淇崟浣嶏紱浜у搧缁撴瀯鏍戜负鍨嬪彿鈫扻鍙封啋涓�绾х郴缁熲啋浜岀骇绯荤粺鈫掕澶囩殑5绾ф爲锛岃澶囧熀纭�搴撲负涓�绾х郴缁熲啋浜岀骇绯荤粺鈫掕澶囩殑3绾ф爲锛涗骇鍝佺粨鏋勬爲璁惧涓哄疄渚嬶紝璁惧鍩虹搴撹澶囦负绫汇��
 > 鏇存柊璇存槑锛�2026-09-17锛夛細AXU5/6/7 涓夊紶瀹℃壒椤垫寜鍏遍�氳鍒欐牳瀵瑰畬鎴愶紱淇濈暀鍒楄〃銆佸埛鏂般�佹壒閲忓鎵癸紝鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱缁熶竴鈥滃鎵光�濈敤璇拰鈥淛CC澶勯暱瀹℃壒鈥濆悕绉帮紱鏁呴殰淇℃伅澧炲姞涓夌粍瀹℃壒鎰忚銆佸鎵圭粨鏋溿�佸鎵规椂闂村瓧娈碉紝浠ュ強閿�椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂村瓧娈点�傛晠闅滀笂鎶ラ樁娈电殑閿�椤圭粺涓�鐢盭CB鍔╃悊鎵ц銆�
 > 鏇存柊璇存槑锛�2026-09-20 缁熻鎶ヨ〃锛夛細鏂板涓�绾ц彍鍗曗�滅粺璁℃姤琛ㄢ�濓紝璇嗗埆瑁呭鏁呴殰鎬讳綋鍒嗘瀽銆佹晠闅滈珮鍙戣澶� Top銆佹寜T缁熻銆佹晠闅滅被鍨嬪垎甯冦�佸巶瀹剁淮淇儏鍐点�佹晠闅滄暟/澶囧搧澶囦欢娑堣�楀勾搴﹁秼鍔�6椤瑰姛鑳斤紝鍏朵腑鈥滄晠闅滅被鍨嬪垎甯冣�濆洜闇�姹傛殏涓嶆槑纭�屾殏缂擄紱瑁呭鏁呴殰鎬讳綋鍒嗘瀽浣跨敤鈥滅洃娴嬮」鐩�濓紱鍘傚涓庢壙淇崟浣嶅悓涔夛紱绯荤粺绠$悊涓殑鈥滃熀纭�璁惧搴撯�濈粺涓�鏇村悕涓衡�滆澶囧熀纭�搴撶鐞嗏�濄��
 > 鏇存柊璇存槑锛�2026-09-28 B1-B4锛夛細娲惧伐鍗曗�滃畬宸モ�濅笌棣栭〉鈥滃凡瀹屽伐鈥濅负鍚屼竴缁熻鐘舵�侊紱棣栭〉涓庢淳宸ュ崟鐨勨�滃畬宸ュ緟璇曢獙鈥濆彛寰勪竴鑷达紱鈥滃緟澶囦欢鈥濆彲鐩存帴鈥滃畬宸モ�濓紱鈥滃畬宸ュ緟璇曢獙鈥濊瘯楠屼笉閫氳繃鍥炩�滄湭瀹屽伐鈥濓紝涓嶆柊澧炩�滆繑宸モ�濈姸鎬併��
@@ -125,7 +125,7 @@
 - 鍒犻櫎闄愬埗锛�2026-09-16 纭锛夛細鏈笂鎶ョ殑鏁呴殰鍙互鍒犻櫎锛涘凡涓婃姤鐨勬晠闅滀笉鑳藉垹闄わ紱浠诲姟鐘舵�佷负鈥滀笂绾ф墦鍥炩�濇椂浠嶅厑璁稿垹闄ゃ��
 - 銆愬凡纭 2026-09-16銆戠涓�鐗堟殏涓嶅疄鐜扳�滃~鍐欐晠闅滃垎鏋愭姤鍛娾�濓紝涓氬姟閫昏緫寰呭悗缁槑纭��
 - 鍒楄〃锛氬伐绋嬮�氱敤鍒� + 涓婃姤鏃ユ湡銆佷笂鎶ュ崟浣� + 浠诲姟鐘舵�併��
-- 鏁呴殰缂栧彿锛�2026-09-28 纭锛屾浛浠� 2026-09-20 鍙e緞锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛屽叾涓� X鍙蜂负3浣嶆暟瀛椼�佸垎绫诲彇鈥淭闃熶笓涓氶厤缃�濈殑 A/B銆乊YMMDD 涓轰笂鎶ユ棩鏈熴�乶nn 涓哄悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紙浠� 001 璧凤級锛涚敓鎴愬悗涓嶅彲淇敼锛岀敤浜庣敓鎴愭淳宸ュ崟缂栧彿銆�
+- 鏁呴殰缂栧彿锛�2026-09-28 纭锛屾浛浠� 2026-09-20 鍙e緞锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛屽叾涓� X鍙蜂负3浣嶆暟瀛椼�佸垎绫诲彇鈥淭闃熶笓涓氶厤缃�濈殑 A/B銆乊YMMDD 涓轰笂鎶ユ棩鏈熴�乶nn 涓哄悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紙浠� 001 璧凤級锛涚敓鎴愬悗涓嶅彲淇敼锛岀敤浜庣敓鎴愭淳宸ュ崟缂栧彿銆�
 - 浠诲姟鐘舵�佺粺涓�鍙栧�硷紙2026-09-16 纭锛夛細鏈笂鎶� / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄銆�
 - 鏂板/缂栬緫琛ㄥ崟瀛楁锛氭晠闅滅紪鍙凤紙鍙锛屼繚瀛樺悗鑷姩鐢熸垚锛夈�乆鍙枫�佷笂鎶ュ崟浣嶃�乀闃熶笓涓氥�佽礋璐d汉銆佷笂鎶ユ棩鏈熴�佸伐绋嬩笓涓氥�佽澶囧悕绉般�佸垎鏈�/閮ㄤ綅銆佹墍灞炰笂绾х郴缁熴�佸伐绋嬪悕绉般�佷慨鐞嗘妧鏈姹傘�佽窡韪妧鏈槦锛堝崟閫夛細涓�闃�/浜岄槦/涓夐槦锛夈�佹晠闅滃奖鍝嶃�佹晠闅滄�ц川銆侀噸澶ф晠闅滄弿杩般�佹槸鍚﹁嚜淇�佹槸鍚︽秹H銆佺洃娴嬮」鐩�佺洃娴嬫暟鍊笺�佽姹傚畬宸ユ棩鏈熴�佸娉ㄣ��
 - 銆愬凡纭 2026-09-16銆戞柊澧炴晠闅滄敼涓鸿〃鍗曞紡褰曞叆锛屼笉浣跨敤鍘熼渶姹傜殑琛ㄦ牸寮忓綍鍏ャ��
@@ -302,7 +302,7 @@
 18. 銆愬凡瑙e喅 2026-09-16銆慩CB鍔╃悊瀹℃牳涓嶉�氳繃鍚庣殑澶勭悊锛氫笉杞�滀笂绾ф墦鍥炩�濓紱鍐呭涓嶅悎鏍兼椂鐢盭CB鍔╃悊閫氳繃鈥滀慨鏀光�濇寜閽慨姝e唴瀹癸紝涓嶄綔涓烘晠闅滃鐞嗘椂鐐光�滈攢椤光�濄��
 19. 銆愬凡瑙e喅 2026-09-17銆慉XU5/6/7 涓夊紶瀹℃壒椤靛叡閫氭寜閽�佸鎵圭敤璇強涓夌粍瀹℃壒瀛楁銆�
 20. 銆愬凡瑙e喅 2026-09-17銆戞晠闅滀笂鎶ラ樁娈甸攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛涙晠闅滀俊鎭鍔犻攢椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂村瓧娈碉紱閿�椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�
-21. 銆愭洿鏂� 2026-09-28銆戞晠闅滅紪鍙风敱绯荤粺鑷姩鐢熸垚锛坄娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛夛紱绯荤粺绠$悊鏂板鈥滃伐绋嬩笓涓氶厤缃�濃�淭闃熶笓涓氶厤缃�濓紝涓ゅ涓嬫媺鏀圭敱閰嶇疆璇诲彇銆�
+21. 銆愭洿鏂� 2026-09-28銆戞晠闅滅紪鍙风敱绯荤粺鑷姩鐢熸垚锛坄鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛夛紱绯荤粺绠$悊鏂板鈥滃伐绋嬩笓涓氶厤缃�濃�淭闃熶笓涓氶厤缃�濓紝涓ゅ涓嬫媺鏀圭敱閰嶇疆璇诲彇銆�
 22. 銆怉XU14 寰呯‘璁ゃ�戞湰椤垫搷浣滆鑹诧紱鍚勭姸鎬佽缃寜閽殑鍚敤鏉′欢锛涜烦杞� AXU15 鏃舵槸鍚︿紶鍏ラ�変腑鐨勬淳宸ュ崟銆�
 23. 銆愬凡瑙e喅 2026-09-20銆戝畬宸ラ獙鏀舵祦绋嬩负5涓彍鍗曪細楠屾敹鐢宠銆佽川妫�鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀躲�佸畬宸ラ獙鏀舵墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾с�傚彧鏈夎川妫�鍛橀獙鏀跺拰璐ㄦ璐熻矗浜洪獙鏀舵彁渚涢�氳繃/鎵撳洖锛岃川妫�鍛樻墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜烘墦鍥炶川妫�鍛橀獙鏀讹紱楠屾敹鐢宠銆佸畬宸ラ獙鏀舵墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾т负绠�鍗曞姩浣溿��
 24. 銆愬畬宸ラ獙鏀跺緟纭銆戝悇鐜妭鎿嶄綔瑙掕壊锛涘畬宸ラ獙鏀舵墦鍗拌緭鍑虹殑瀹為檯鎵撳嵃琛屼负锛涒�滀慨鍚庤川閲忚瘎绾р�濅笌 AXU14鈥淭鍛樿瘎鍒嗏�濆瓧娈电殑鍏崇郴銆�
diff --git "a/requirement/\345\267\245\347\250\213\347\212\266\346\200\201\346\234\272\350\256\276\350\256\241.md" "b/requirement/\345\267\245\347\250\213\347\212\266\346\200\201\346\234\272\350\256\276\350\256\241.md"
index 710a45c..057a958 100644
--- "a/requirement/\345\267\245\347\250\213\347\212\266\346\200\201\346\234\272\350\256\276\350\256\241.md"
+++ "b/requirement/\345\267\245\347\250\213\347\212\266\346\200\201\346\234\272\350\256\276\350\256\241.md"
@@ -6,7 +6,7 @@
 > 2026-09-16 鏇存柊锛氫换鍔$姸鎬佺粺涓�涓轰竷涓�硷細鈥滄湭涓婃姤 / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄鈥濓紱AXU4 鎵夸慨鍗曚綅鍒嗛厤銆佹媶鍗曘�佸伐绋嬪崟鍙风敓鎴愬潎鍦� XCB鍔╃悊瀹℃牳鐜妭瀹屾垚銆�
 > 2026-09-17 鏇存柊锛欰XU5/6/7 涓夊紶瀹℃壒椤电粺涓�淇濈暀鍒楄〃銆佸埛鏂般�佹壒閲忓鎵癸紝鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱缁熶竴鈥滃鎵光�濈敤璇拰鈥淛CC澶勯暱瀹℃壒鈥濆悕绉帮紱鏁呴殰淇℃伅澧炲姞涓夌粍瀹℃壒瀛楁銆�
 > 2026-09-20 鑼冨洿鏀跺彛锛欰XU9銆丄XU11鈥揂XU13銆丄XU16 鏆備笉澶勭悊锛屼笉绾冲叆褰撳墠闇�姹傦紱AXU8 缁х画鏆傜紦銆�
-> 2026-09-20 鏇存柊锛欽CC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏� XCB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗涓�閿淳鍗曪紝鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛涚紪鍙蜂负鏁呴殰缂栧彿 + `/01`銆乣/02` 绛夛紱闄も�滀竴閿淳鍗曗�濆鍘熸埅鍥惧叾浠栦笟鍔℃寜閽殏涓嶉渶瑕侊紱鏁呴殰缂栧彿鐢辩郴缁熸寜 `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛�2026-09-28 鏇存柊锛屾浛浠e師鈥滀汉宸ュ~鍐欌�濓級銆侫XU8 鏆傜紦鏍稿銆侫XU14 浣嶄簬娲惧崟涔嬪悗锛屾瘡涓�琛�=1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛岋紱瀹屽伐鐘舵�佸浐瀹氫负鈥滄湭瀹屽伐锛堥粯璁わ級/寰呭浠�/瀹屽伐寰呰瘯楠�/瀹屽伐鈥濓紱鍘绘帀瀵煎嚭銆佹坊鍔犺繘搴﹀娉ㄣ�侀攢椤规寜閽紱T鍛樿瘎鍒嗗啓鍥炰慨鍚庤川閲忚瘎鍒嗗拰鎰忚锛涒�滃畬宸モ�濇寜閽粎璺宠浆 AXU15锛汚XU15 鎴愬姛鎻愪氦鍚庯紝灏嗗叧鑱旀淳宸ュ崟瀹屽伐鐘舵�佸啓涓衡�滃畬宸モ�濓紝闅忓悗杩涘叆5鑿滃崟瀹屽伐楠屾敹娴佺▼锛氶獙鏀剁敵璇� 鈫� 璐ㄦ鍛橀獙鏀� 鈫� 璐ㄦ璐熻矗浜洪獙鏀� 鈫� 瀹屽伐楠屾敹鎵撳嵃杈撳嚭 鈫� 淇悗璐ㄩ噺璇勭骇锛涗粎璐ㄦ鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀舵彁渚涢�氳繃/鎵撳洖锛岃川妫�鍛樻墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜烘墦鍥炶川妫�鍛橀獙鏀讹紱楠屾敹鐢宠銆佸畬宸ラ獙鏀舵墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾т负绠�鍗曞姩浣溿��
+> 2026-09-20 鏇存柊锛欽CC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏� XCB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗涓�閿淳鍗曪紝鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛涚紪鍙蜂负鏁呴殰缂栧彿 + `/01`銆乣/02` 绛夛紱闄も�滀竴閿淳鍗曗�濆鍘熸埅鍥惧叾浠栦笟鍔℃寜閽殏涓嶉渶瑕侊紱鏁呴殰缂栧彿鐢辩郴缁熸寜 `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛�2026-09-28 鏇存柊锛屾浛浠e師鈥滀汉宸ュ~鍐欌�濓級銆侫XU8 鏆傜紦鏍稿銆侫XU14 浣嶄簬娲惧崟涔嬪悗锛屾瘡涓�琛�=1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛岋紱瀹屽伐鐘舵�佸浐瀹氫负鈥滄湭瀹屽伐锛堥粯璁わ級/寰呭浠�/瀹屽伐寰呰瘯楠�/瀹屽伐鈥濓紱鍘绘帀瀵煎嚭銆佹坊鍔犺繘搴﹀娉ㄣ�侀攢椤规寜閽紱T鍛樿瘎鍒嗗啓鍥炰慨鍚庤川閲忚瘎鍒嗗拰鎰忚锛涒�滃畬宸モ�濇寜閽粎璺宠浆 AXU15锛汚XU15 鎴愬姛鎻愪氦鍚庯紝灏嗗叧鑱旀淳宸ュ崟瀹屽伐鐘舵�佸啓涓衡�滃畬宸モ�濓紝闅忓悗杩涘叆5鑿滃崟瀹屽伐楠屾敹娴佺▼锛氶獙鏀剁敵璇� 鈫� 璐ㄦ鍛橀獙鏀� 鈫� 璐ㄦ璐熻矗浜洪獙鏀� 鈫� 瀹屽伐楠屾敹鎵撳嵃杈撳嚭 鈫� 淇悗璐ㄩ噺璇勭骇锛涗粎璐ㄦ鍛橀獙鏀躲�佽川妫�璐熻矗浜洪獙鏀舵彁渚涢�氳繃/鎵撳洖锛岃川妫�鍛樻墦鍥為獙鏀剁敵璇凤紝璐ㄦ璐熻矗浜烘墦鍥炶川妫�鍛橀獙鏀讹紱楠屾敹鐢宠銆佸畬宸ラ獙鏀舵墦鍗拌緭鍑恒�佷慨鍚庤川閲忚瘎绾т负绠�鍗曞姩浣溿��
 > 2026-09-28 鏇存柊锛圔1-B4锛夛細娲惧伐鍗曗�滃畬宸モ�濅笌棣栭〉鈥滃凡瀹屽伐鈥濅负鍚屼竴缁熻鐘舵�侊紱涓ゅ鈥滃畬宸ュ緟璇曢獙鈥濆彛寰勪竴鑷达紱鈥滃緟澶囦欢鈥濆厑璁哥洿鎺モ�滃畬宸モ�濓紱鈥滃畬宸ュ緟璇曢獙鈥濊瘯楠屼笉閫氳繃鍥炩�滄湭瀹屽伐鈥濓紝涓嶆柊澧炩�滆繑宸モ�濈姸鎬併��
 > 鎬ц川锛氶渶姹傛⒊鐞嗛樁娈电殑**璁捐鑽夋**锛屽緟鐢ㄦ埛纭鍚庡浐鍖栬繘闇�姹傛竻鍗曘��
 
@@ -106,7 +106,7 @@
 - **浠诲姟鐘舵��**锛氭淳鍗曚笉鏀瑰彉浠诲姟鐘舵�侊紝浠嶇劧淇濇寔鈥淛CC澶勯暱宸查槄鈥濓紱鈥滄湭娲惧崟/宸叉淳鍗曗�濇槸娲惧崟鐜妭鐨勭嫭绔嬫煡璇㈠彛寰勩��
 - **鍏宠仈瑙勫垯**锛歑CB鍔╃悊瀹℃牳浠嶈礋璐e垎閰嶆壙淇崟浣嶃�佹媶鍗曞苟鐢熸垚宸ョ▼鍗曞彿锛涙淳鍗曟椂涓嶅啀閲嶆柊鍒嗛厤鎴栦慨鏀规壙淇崟浣嶃��
 - **甯哥敤鎵夸慨鍗曚綅鏄剧ず**锛歑CB鍔╃悊瀹℃牳鍒嗛厤鎵夸慨鍗曚綅鏃讹紝鍊欓�夊垪琛ㄦ潵鑷�滅郴缁熺鐞� > 鎵夸慨鍗曚綅绠$悊鈥濓紝鎸夆�滄帓搴忊�濇帓搴忓悗鏄剧ず鍓�7涓父鐢ㄦ壙淇崟浣嶃��
-- **鏁呴殰缂栧彿鏉ユ簮**锛�2026-09-28 鏇存柊锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛圶鍙�3浣嶆暟瀛椼�佸垎绫诲彇鈥淭闃熶笓涓氶厤缃�濈殑 A/B銆乊YMMDD 涓轰笂鎶ユ棩鏈熴�乶nn 涓哄悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紝浠� 001 璧凤級锛涙晠闅滀俊鎭繚鐣欌�滄晠闅滅紪鍙封�濆瓧娈碉紝鐢熸垚鍚庝笉鍙慨鏀广��
+- **鏁呴殰缂栧彿鏉ユ簮**锛�2026-09-28 鏇存柊锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛圶鍙�3浣嶆暟瀛椼�佸垎绫诲彇鈥淭闃熶笓涓氶厤缃�濈殑 A/B銆乊YMMDD 涓轰笂鎶ユ棩鏈熴�乶nn 涓哄悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紝浠� 001 璧凤級锛涙晠闅滀俊鎭繚鐣欌�滄晠闅滅紪鍙封�濆瓧娈碉紝鐢熸垚鍚庝笉鍙慨鏀广��
 
 ## 3. 宸ョ▼鐘舵�佹満锛堟墽琛岄摼璺級
 
@@ -228,7 +228,7 @@
 13. 銆愬凡瑙e喅 2026-09-16銆慩CB鍔╃悊瀹℃牳涓嶉�氳繃鍚庣殑澶勭悊锛氫笉杞�滀笂绾ф墦鍥炩�濓紱鍐呭涓嶅悎鏍兼椂鐢盭CB鍔╃悊閫氳繃鈥滀慨鏀光�濇寜閽慨姝e唴瀹癸紝涓嶄綔涓烘晠闅滃鐞嗘椂鐐光�滈攢椤光�濄�傗�滀笂绾ф墦鍥炩�濅粎閫傜敤浜嶤MB棰嗗銆丣CC鍔╃悊銆丣CC澶勯暱閫�鍥瀀CB鍔╃悊銆�
 14. 銆愬凡瑙e喅 2026-09-17銆慉XU5/6/7 鍏遍�氭搷浣溿�佸鎵圭敤璇拰瀹℃壒瀛楁缁勶細淇濈暀鍒楄〃銆佸埛鏂般�佹壒閲忓鎵癸紱鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱缁熶竴鈥滃鎵光�濈敤璇拰鈥淛CC澶勯暱瀹℃壒鈥濆悕绉帮紱澧炲姞 CMB棰嗗/JCC鍔╃悊/JCC澶勯暱涓夌粍瀹℃壒鎰忚銆佸鎵圭粨鏋溿�佸鎵规椂闂村瓧娈点��
 15. 銆愬凡瑙e喅 2026-09-17銆戞晠闅滀笂鎶ラ樁娈甸攢椤癸細缁熶竴鐢盭CB鍔╃悊鎵ц锛涙晠闅滀俊鎭鍔犻攢椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂村瓧娈碉紱閿�椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�
-16. 銆愭洿鏂� 2026-09-28銆戞晠闅滅紪鍙风敱绯荤粺鑷姩鐢熸垚锛坄娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn`锛夛紱绯荤粺绠$悊鏂板鈥滃伐绋嬩笓涓氶厤缃�濃�淭闃熶笓涓氶厤缃�濓紝T闃熶笓涓�/宸ョ▼涓撲笟涓嬫媺鏀圭敱閰嶇疆璇诲彇銆�
+16. 銆愭洿鏂� 2026-09-28銆戞晠闅滅紪鍙风敱绯荤粺鑷姩鐢熸垚锛坄鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn`锛夛紱绯荤粺绠$悊鏂板鈥滃伐绋嬩笓涓氶厤缃�濃�淭闃熶笓涓氶厤缃�濓紝T闃熶笓涓�/宸ョ▼涓撲笟涓嬫媺鏀圭敱閰嶇疆璇诲彇銆�
 17. AXU14 鎿嶄綔瑙掕壊銆佸悇鐘舵�佽缃寜閽殑鍚敤鏉′欢锛屼互鍙婅烦杞� AXU15 鏃舵槸鍚︿紶鍏ラ�変腑鐨勬淳宸ュ崟銆�
 18. 瀹屽伐楠屾敹娴佺▼鍚勭幆鑺傛搷浣滆鑹诧紱瀹屽伐楠屾敹鎵撳嵃杈撳嚭鐨勫疄闄呮墦鍗拌涓猴紱鈥滀慨鍚庤川閲忚瘎绾р�濅笌 AXU14鈥淭鍛樿瘎鍒嗏�濆瓧娈电殑鍏崇郴銆�
 
diff --git "a/requirement/\346\224\271\350\277\23320261003.txt" "b/requirement/\346\224\271\350\277\23320261003.txt"
new file mode 100644
index 0000000..9959a47
--- /dev/null
+++ "b/requirement/\346\224\271\350\277\23320261003.txt"
@@ -0,0 +1,29 @@
+1銆乴ogo 鐢╬ublic鐩綍涓殑logo.png鏂囦欢
+2 銆佹坊鍔犵敤鎴风鐞嗗姛鑳斤紝鍒嗗乏鍙崇粨鏋勶紝宸﹁竟琛ㄦ牸鏄剧ず鎵�鏈夌敤鎴蜂俊鎭紙鐢ㄦ埛鍚嶏紝璐︽埛鍚嶏紝鎵�灞為儴闂紝瑙掕壊鏉冮檺绛変俊鎭級锛屽彲杩囨护锛涘彸杈规樉绀哄緟妫�鏌ユ鐨勮鑹插垪琛紝鍙互璁剧疆鍜屽弽鏄剧敤鎴锋嫢鏈夌殑瑙掕壊锛屼竴涓敤鎴峰彲浠ヨ缃涓鑹�
+3 銆佺郴缁熻褰曠敤鎴风櫥褰曟棩蹇椼�佹搷浣滄棩蹇楋紱鎿嶄綔鏃ュ織涓昏璁板綍娣诲姞璁板綍鎴栦慨鏀硅褰曠殑鏃ュ織
+4銆佽彍鍗曠鐞嗙殑绫诲瀷闄や簡鐩綍鍜岃彍鍗曞锛屽鍔犱竴涓寜閽被鍨嬶紝杩欎釜鍙兘鍦ㄨ彍鍗曠被鍨嬬殑涓嬬骇锛岀敤浜庤缃敤鎴锋嫢鏈変笂绾ц彍鍗曢〉闈㈢殑鎸夐挳鎿嶄綔鏉冮檺锛屾湁鍝簺鎸夐挳鏄惁鍙互鑷姩鑾峰彇
+
+
+
+5銆佷唬鐮佷腑鐨勨�滆澶団�濆瓧鏍锋敼鎴愨�滆澶団��
+6銆佸伐浣滃尯鐨勫鏂囨。鍒囨崲鏍囩锛屽綋鎵撳紑鏂囨。澶氱殑鏃跺�欏嚭鐜版í鍚戞粴鍔ㄦ潯鐨勬椂鍊欐爣绛鹃珮搴︿笉澶燂紝鏂囧瓧鏄剧ず涓嶅叏
+7銆佹斁寮�鏉冮檺鍒殑鐢ㄦ埛涔熷彲浠ョ櫥褰�
+8銆佽彍鍗曟潈闄愮鐞嗭紝妫�鏌ユ鐖跺瓙鑺傜偣鏀规垚鑱斿姩鐨�
+
+9銆佺敤鎴风鐞嗗鍔犳壒閲忚缃鑹诧紱瑙掕壊绠$悊閭i噷澧炲姞瑙掕壊鐢ㄦ埛璁剧疆
+10銆佷骇鍝佺粨鏋勬爲琛ㄦ敼鎴愬悓涔夎瘝琛╖T_BOAT_TREE锛宭abel鏄澶囧悕绉帮紝info_id鏄澶囦富閿甶d锛孋ONNECT_ID鏄埗鑺傜偣id锛孋ONNECT_GROUP鏄杈堣妭鐐硅矾寰勶紝LAYER鏄妭鐐圭被鍨嬶紙model銆乻ide銆乻ystem1銆乻ystem2銆乪quipment绛夛級 涓嶆槸涓ユ牸鎸夌収杩欎釜椤哄簭鏉ョ殑锛� 鍙兘娌℃湁system2灞傦紝system2涓嬫寕鐨勫彲鑳戒笉鏄痚quipment绫诲瀷锛屽彲鑳芥槸鍒殑绫诲瀷锛屽洖澶存垜鍐嶆暣鐞嗕笅
+
+
+11銆佸畬宸ユ儏鍐靛鐞嗛偅閲岋紝鐘舵�佷慨鏀瑰畬宸ョ殑鏃跺�欙紝涓嶈璺宠浆鍒板畬宸ヤ俊鎭~鎶ワ紝鍥犱负涓嶆槸鍚屼竴涓敤鎴锋搷浣滅殑
+      鐐瑰畬宸ュ彧鏄妸鐘舵�佸畬宸ワ紝瀹屽伐淇℃伅濉姤鏄敱鍒殑鐢ㄦ埛濉啓鐨�
+12 銆佸畬宸ヤ俊鎭~鎶ラ偅閲岋紝娲惧伐鍗曢�夋嫨鍒楄〃锛屼竴鏄湪瀹屽伐澶勭悊鎯呭喌閭i噷宸茬粡纭畾瀹屽伐鐘舵�佺殑锛屼簩灏辨槸瑕佹眰鍒嗙粰鐧诲綍鐢ㄦ埛鎵�灞炲崟浣嶆淳宸ュ崟锛屽彟澶栧鏄剧ず涓�涓伐绋嬪悕绉帮紙鎴栭」鐩悕绉帮級淇℃伅
+17銆佽鑹茬鐞嗙敤鎴疯缃鍔犱竴涓叏閮�/宸茶缃垏鎹㈠姛鑳�
+
+13銆乻ensitive_words鐨刢odename鐨勫�煎墠闈㈠姞"rk_"鍓嶇紑锛屽苟淇敼鐜板湪浠g爜鐨勭浉搴旂殑鏂囨湰
+14銆佺敓鎴愭晠闅滅紪鍙蜂腑鐨勫浐瀹氬墠缂�鍦ㄥ悗鍙伴厤缃枃浠朵腑閰嶇疆
+15銆佹墍鏈夎〃璁板綍鍔犲垱寤轰汉銆佷慨鏀逛汉锛屾坊鍔犱慨鏀规椂绯荤粺鑷姩濉啓
+16銆佹墍鏈夎〃鍔犻�昏緫鍒犻櫎瀛楁锛屽垹闄ら兘鏄�昏緫鍒犻櫎锛屾煡璇㈢殑鏃跺�欒繃婊ゆ帀閫昏緫鍒犻櫎鐨勫瓧娈�
+
+17銆佽鑹茬鐞嗙敤鎴疯缃鍔犱竴涓叏閮�/宸茶缃垏鎹㈠姛鑳�
+18銆佸鍔犲姛鑳芥樉绀虹粍缁囨満鏋勶紙閮ㄩ棬鏍戣〃锛�
+19銆佹壙淇崟浣嶅簲璇ュ埌閮ㄩ棬琛ㄩ噷鎵綝EPT_TYPE鍒椾负cj鐨勮褰�
\ No newline at end of file
diff --git "a/requirement/\346\225\217\346\204\237\350\257\215\345\255\227\345\205\270.json" "b/requirement/\346\225\217\346\204\237\350\257\215\345\255\227\345\205\270.json"
index c2763eb..b879053 100644
--- "a/requirement/\346\225\217\346\204\237\350\257\215\345\255\227\345\205\270.json"
+++ "b/requirement/\346\225\217\346\204\237\350\257\215\345\255\227\345\205\270.json"
@@ -1,49 +1,49 @@
 {
   "sensitive_words": [
     {
-      "codename": "X鍙�",
+      "codename": "rk_X鍙�",
       "real_name": "鑸峰彿",
       "scope": "documents,ui,code",
       "note": "鑸拌埞缂栧彿"
     },
     {
-      "codename": "JC",
+      "codename": "rk_JC",
       "real_name": "鑸拌埞",
       "scope": "documents,ui,code",
       "note": "濡� JC淇℃伅=鑸拌埞淇℃伅"
     },
     {
-      "codename": "JCC",
+      "codename": "rk_JCC",
       "real_name": "鑸拌埞澶�",
       "scope": "documents,ui,code",
       "note": "瀹℃壒瑙掕壊锛欽CC鍔╃悊/JCC澶勯暱/JCC楠屾敹"
     },
     {
-      "codename": "CMB",
+      "codename": "rk_CMB",
       "real_name": "鍙傝皨閮�",
       "scope": "documents,ui,code",
       "note": "瀹℃壒瑙掕壊锛欳MB棰嗗"
     },
     {
-      "codename": "T",
+      "codename": "rk_T",
       "real_name": "鑹�",
       "scope": "documents,ui,code",
       "note": "璇嶆牴浠gО锛岀粍鍚堣瘝鑷姩鏇挎崲锛圱闃熶笓涓氥�乀鍐呮柦宸ョ瓑锛�"
     },
     {
-      "codename": "TD",
+      "codename": "rk_TD",
       "real_name": "鑹囬槦",
       "scope": "documents,ui,code",
       "note": "濡傞椤电粺璁¤〃TD鍒楋紙X鍙锋墍灞炶墖闃燂級"
     },
     {
-      "codename": "XCB",
+      "codename": "rk_XCB",
       "real_name": "淇埞鍔�",
       "scope": "documents,ui,code",
       "note": "鍘熷ぇ闃熷姪鐞嗗鏍歌鑹诧紝鐜伴〉闈㈠強鑿滃崟缁熶竴绉癤CB鍔╃悊瀹℃牳"
     },
     {
-      "codename": "H",
+      "codename": "rk_H",
       "real_name": "[production-only sensitive value]",
       "scope": "documents,ui,code",
       "note": "AXU3 鍙婂悗缁瓧娈碘�滄槸鍚︽秹H鈥濓紱寮�鍙戠幆澧冨彧杈撳嚭浠gО"
diff --git "a/requirement/\347\225\214\351\235\242\350\277\230\345\216\237.html" "b/requirement/\347\225\214\351\235\242\350\277\230\345\216\237.html"
index b24e683..57734ba 100644
--- "a/requirement/\347\225\214\351\235\242\350\277\230\345\216\237.html"
+++ "b/requirement/\347\225\214\351\235\242\350\277\230\345\216\237.html"
@@ -18,7 +18,7 @@
 </style></head><body>
 <h1>涓存姠淇蒋浠� 路 鐣岄潰璇嗗埆杩樺師锛堝師濮嬮渶姹�.docx 鍓� 44 椤碉級</h1>
 <p style="font-size:13px;color:#555">鐢ㄩ�旓細涓庡師濮嬫枃妗i�愬紶鏍稿 OCR/璇嗗埆缁撴灉銆傞粍鑹查珮浜� = 寰呯‘璁�/OCR鐤戣銆傝〃鏍煎垪涓烘í鍚戞帓鍒楋紝涓嶄唬琛ㄥ師鍥惧疄闄呭垪椤哄簭銆�</p>
-<div class="note">鍏ㄥ眬鍙e緞锛�2026-09-11 鐢ㄦ埛纭锛夛細绗竴鐗�<mark>涓嶆寜鍘熷闇�姹�</mark>瀹炵幇锛屾敼涓� 鈶� <b>绠�鍖栦笟鍔℃祦绋�</b> + 鈶� <b>鍙彁渚涚綉缁滅増銆佷笉鎻愪緵绂荤嚎鍗曟満鐗�</b>锛涚畝鍖栨祦绋嬭 AXU1 鍗$墖銆傛墍鏈夆�滃崟鏈虹増濉啓/绂荤嚎濉啓/鏁版嵁鍖呭鍏ュ鍑衡�濈浉鍏冲姛鑳藉潎涓嶈繘鍏ョ涓�鐗堛��<mark>2026-09-20 鐢ㄦ埛纭锛欽CC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗鐐瑰嚮鈥滀竴閿淳鍗曗�濓紱鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛岀紪鍙蜂负鏁呴殰缂栧彿 + `/01`銆乣/02` 绛夈�傞櫎鈥滀竴閿淳鍗曗�濆锛屽師 AXU10 鍏朵粬涓氬姟鎸夐挳鏆備笉闇�瑕併�傛晠闅滅紪鍙风敱绯荤粺鎸� `娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛屾晠闅滀俊鎭繚鐣欌�滄晠闅滅紪鍙封�濆瓧娈碉紙2026-09-28 鏇存柊锛夈��</mark><mark>2026-09-14 鐢ㄦ埛纭锛氱绾�/鏁版嵁鍖呯浉鍏冲姛鑳芥暣浣撳彇娑堛�佸搴旇彍鍗曢」涓�骞跺垹闄わ紙C-12 宸茶В鍐筹級锛涒�滄姤浠风鐞嗏�濊彍鍗曚笌妯″潡鏁翠綋涓嶅仛锛堟姤浠峰鐞嗙Щ鍑轰富娴佺▼锛夛紱鑹囬暱鐜妭涓嶇撼鍏ョ涓�鐗堬紱JCC 鍔╃悊瀹℃壒涓哄繀缁忕幆鑺傦紱AXU2 鏁版嵁鍖呭鍏ユ棤闇�鏍稿銆�</mark></div>
+<div class="note">鍏ㄥ眬鍙e緞锛�2026-09-11 鐢ㄦ埛纭锛夛細绗竴鐗�<mark>涓嶆寜鍘熷闇�姹�</mark>瀹炵幇锛屾敼涓� 鈶� <b>绠�鍖栦笟鍔℃祦绋�</b> + 鈶� <b>鍙彁渚涚綉缁滅増銆佷笉鎻愪緵绂荤嚎鍗曟満鐗�</b>锛涚畝鍖栨祦绋嬭 AXU1 鍗$墖銆傛墍鏈夆�滃崟鏈虹増濉啓/绂荤嚎濉啓/鏁版嵁鍖呭鍏ュ鍑衡�濈浉鍏冲姛鑳藉潎涓嶈繘鍏ョ涓�鐗堛��<mark>2026-09-20 鐢ㄦ埛纭锛欽CC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗鐐瑰嚮鈥滀竴閿淳鍗曗�濓紱鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛岀紪鍙蜂负鏁呴殰缂栧彿 + `/01`銆乣/02` 绛夈�傞櫎鈥滀竴閿淳鍗曗�濆锛屽師 AXU10 鍏朵粬涓氬姟鎸夐挳鏆備笉闇�瑕併�傛晠闅滅紪鍙风敱绯荤粺鎸� `鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn` 鑷姩鐢熸垚锛屾晠闅滀俊鎭繚鐣欌�滄晠闅滅紪鍙封�濆瓧娈碉紙2026-09-28 鏇存柊锛夈��</mark><mark>2026-09-14 鐢ㄦ埛纭锛氱绾�/鏁版嵁鍖呯浉鍏冲姛鑳芥暣浣撳彇娑堛�佸搴旇彍鍗曢」涓�骞跺垹闄わ紙C-12 宸茶В鍐筹級锛涒�滄姤浠风鐞嗏�濊彍鍗曚笌妯″潡鏁翠綋涓嶅仛锛堟姤浠峰鐞嗙Щ鍑轰富娴佺▼锛夛紱鑹囬暱鐜妭涓嶇撼鍏ョ涓�鐗堬紱JCC 鍔╃悊瀹℃壒涓哄繀缁忕幆鑺傦紱AXU2 鏁版嵁鍖呭鍏ユ棤闇�鏍稿銆�</mark></div>
 <div class="note"><b>鑼冨洿鏀跺彛锛�2026-09-20锛夛細</b><mark>AXU9銆丄XU11銆丄XU12銆丄XU13銆丄XU16 鏆備笉澶勭悊锛屼笉绾冲叆褰撳墠闇�姹備笌鍚庣画鏍稿锛汚XU8 缁х画鏆傜紦銆�</mark> 鍘熷鎴浘鏂囧瓧浠嶄繚鐣欙紝渚涜祫鏂欒拷婧��</div>
 <h2>AXUnn 鈫� 鑿滃崟鍚� 鈫� 鏂囨。椤� 瀵圭収琛�</h2>
 <table><tr><th>鎴浘缂栧彿</th><th>鑿滃崟鍚�/椤甸潰鍚�</th><th>鏂囨。椤�</th></tr><tr><td><b>AXU0</b></td><td>棣栭〉闂ㄦ埛</td><td>鏂囨。椤� 5</td></tr>
@@ -59,7 +59,7 @@
 <div class="note2">鈶� 涓嶅啀鍖呭惈鈥滆墖闀挎壒閲忓鎵�/鑹囬暱瀹℃壒鈥濓紝鐩存帴浠� T鍛樻晠闅滀笂鎶ュ紑濮嬶紱鈶� 鍙栨秷鈥滃崟鏈虹増濉啓/绂荤嚎濉啓鏁版嵁鍖� + 鍐呯綉瀵煎叆/瀵煎嚭鏁版嵁鍖呪�濓紱鈶� 鍙栨秷鈥淴CB鍔╃悊瀹屽伐澶勭悊锛堝鍑哄畬宸ラ」鐩�/鍗曟満鐗堝~鍐�/鍘傚绂荤嚎濉啓锛夆�濈幆鑺傦紝鏀逛负鍘傚鐩存帴涓婃姤瀹屽伐淇℃伅锛涒懀 鎶ヤ环澶勭悊涓嶅啀浣滀负涓绘祦绋嬬幆鑺傦紙鈥滄姤浠风鐞嗏�濊彍鍗曟槸鍚︿繚鐣欏緟纭锛夛紱鈶� JCC鍔╃悊瀹℃壒纭涓烘祦绋嬪繀缁忕幆鑺傦紙鍘熸埅鍥炬祦绋嬪浘鏈崟鍒楋級銆�</div>
 <div class="note2">鍘熷鎴浘娴佺▼锛堝瓨妗e弬鑰冿紝宸蹭笉閲囩敤锛夛細鏁呴殰涓婃姤鏁版嵁鍖� 鈫� XCB鍔╃悊瀹℃牳锛堝彲鐩存帴閿�椤癸級鈫� CMB棰嗗瀹℃壒锛堝彲鎵撳洖涓婁竴姝ワ級鈫� JCC澶勯暱瀹℃壒 鈫� XCB鍔╃悊娲惧崟锛堟墦鍗版淳宸ュ崟锛氬悓X鍙�+鍚屼笂鎶ユ棩鏈�+鍚屾壙淇崟浣嶅悎鎴愪竴涓淳宸ュ崟锛夆啋 鍘傚绾夸笅淇悊 鈫� XCB鍔╃悊瀹屽伐澶勭悊锛堝鍑哄畬宸ラ」鐩�/鍗曟満鐗堝~鍐�/鍘傚绂荤嚎濉啓/鍐呯綉涓婃姤锛夆啋 鎵撳嵃瀹屽伐楠屾敹鍗曪紱T鍛橀獙鏀� 鈫� 鎶ヤ环澶勭悊锛堝鍑哄緟鎶ヤ环椤圭洰 鈫� 鍗曟満鐗堝~鍐�/瀵煎叆 鈫� 淇埞鍔炲姪鐞嗗浠� 鈫� JCC瀹′环锛夈��</div>
 <div class="note2">鏈锛�2026-09-14 鐢ㄦ埛纭锛夛細缁熶竴鍐欌�淭鍛樷�濓紙T=鑹囦唬绉帮紝T鍛�=鑹囧憳锛夛紱鍘熸枃妗h彍鍗曠殑鏃у啓娉曞凡浣滃簾锛岃瘎鍒嗕笌瑙掕壊鍚嶄竴骞剁粺涓�涓衡�淭鍛樷�濄��</div>
-</section><section class="card"><h3>AXU2 路 鏁版嵁鍖呭鍏ワ紙鏂囨。椤� 8锛�</h3><div class="note">瀵煎叆鏁呴殰涓婃姤鏁版嵁鍖呮枃浠�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">閫夋嫨鏂囦欢</span><span class="td">瀵煎叆鏁版嵁鍖�</span></div><div class="note2">瀵煎叆缁撴灉缁熻</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">缁熻鍒嗙被</span><span class="td">鏁伴噺</span><span class="td">鏄庣粏</span></div><div class="note2">缁熻鍒嗙被鏄庣粏锛氶」鐩暟閲� / 娑夊強宸ョ▼涓撲笟 / 娑夊強T闃熶笓涓� / 娑夊強瑁呭</div><div class="note2">銆愬凡纭 2026-09-14銆戝彧鍋氱綉缁滅増銆佹棤瀵煎叆鍔熻兘 鈫� 鏈〉鏁翠綋鍙栨秷锛岃彍鍗曢」鈥滄暟鎹寘瀵煎叆鈥濆垹闄わ紝鏃犻渶鏍稿銆�</div></section><section class="card"><h3>AXU3 路 鏁呴殰涓婃姤锛堟枃妗i〉 9锛�</h3><div class="note">椤甸潰鏍囬锛氬垪琛�</div><div class="label">鏌ヨ鏉′欢</div><div class="chips"><span class="td">X鍙�</span><span class="td">T闃熶笓涓�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">涓婃姤鍗曚綅</span><span class="td">璐熻矗浜�</span><span class="td">涓婃姤鏃ユ湡锛堣捣锛夆�旓紙鑷筹級</span></div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鏌ヨ</span><span class="td">鍒锋柊</span><span class="td">鏂板</span><span class="td">涓婃姤</span><span class="td">淇敼</span><span class="td">鍒犻櫎</span><span class="td">瀵煎嚭</span></div><div class="note2">銆愬凡纭 2026-09-16銆戝瓧娈典笌鎸夐挳閫氳鏃犺锛涒�滃~鍐欐晠闅滃垎鏋愭姤鍛娾�濈涓�鐗堟殏涓嶅疄鐜帮紝涓氬姟閫昏緫寰呭悗缁槑纭��</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鏁呴殰缂栧彿</span><span class="td">浠诲姟鐘舵��</span><span class="td">涓婃姤鏃ユ湡</span><span class="td">涓婃姤鍗曚綅</span><span class="td">瑁呭鍚嶇О</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="note2">浠诲姟鐘舵�侊紙2026-09-16 纭锛屽叏灞�涓冧釜鍊硷級锛氭湭涓婃姤 / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄銆�</div><div class="note2">鍒楄〃琛ㄥご澧炲姞鈥滀笂鎶ユ棩鏈熲�濃�滀笂鎶ュ崟浣嶁�濓紙2026-09-16 纭锛夈��</div><div class="note2">鍒犻櫎闄愬埗锛�2026-09-16 纭锛夛細鏈笂鎶ョ殑鏁呴殰鍙互鍒犻櫎锛涘凡涓婃姤鐨勬晠闅滀笉鑳藉垹闄わ紱浠诲姟鐘舵�佷负鈥滀笂绾ф墦鍥炩�濇椂浠嶅厑璁稿垹闄ゃ��</div><div class="note2">瀹℃壒瀛楁缁勶紙2026-09-17 纭锛夛細CMB棰嗗瀹℃壒鎰忚銆丆MB棰嗗瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丆MB棰嗗瀹℃壒鏃堕棿锛汮CC鍔╃悊瀹℃壒鎰忚銆丣CC鍔╃悊瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC鍔╃悊瀹℃壒鏃堕棿锛汮CC澶勯暱瀹℃壒鎰忚銆丣CC澶勯暱瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC澶勯暱瀹℃壒鏃堕棿銆�</div><div class="note2">閿�椤瑰瓧娈电粍锛�2026-09-17 纭锛夛細閿�椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂达紱鏁呴殰涓婃姤闃舵鐨勯攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛孴闃熷拰鍏朵粬瀹℃壒瑙掕壊涓嶆彁渚涢攢椤规搷浣溿�傞攢椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�</div><div class="label">鏂板/缂栬緫琛ㄥ崟瀛楁锛�2026-09-16 纭锛氭柊澧炴敼涓鸿〃鍗曞紡锛屽師闇�姹傝〃鏍煎紡宸茶皟鏁达級锛�</div><div class="chips"><span class="td">鏁呴殰缂栧彿锛堝彧璇伙紝鑷姩鐢熸垚锛�</span><span class="td">X鍙�</span><span class="td">涓婃姤鍗曚綅</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">涓婃姤鏃ユ湡</span><span class="td">宸ョ▼涓撲笟</span><span class="td">瑁呭鍚嶇О</span><span class="td">鍒嗘満/閮ㄤ綅</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">宸ョ▼鍚嶇О</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">鏁呴殰褰卞搷<mark>鍙厤缃瓧鍏革紱绀轰緥鍊硷細渚涙皵锛涘叾浠栧�煎緟纭</mark></span><span class="td">鏁呴殰鎬ц川<mark>鍙厤缃瓧鍏革紱绀轰緥鍊硷細閲嶅ぇ锛涘叾浠栧�煎緟纭</mark></span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="note2">瀛楁璋冩暣锛�2026-09-16 纭锛夛細鍋滄硦鍦板瓧娈典笉淇濈暀銆�</div><div class="note2">鏁呴殰缂栧彿锛�2026-09-28 纭锛屾浛浠� 2026-09-20 鍙e緞锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 <b>娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn</b>锛沊鍙峰彇3浣嶆暟瀛楋紝鍒嗙被鍙栤�淭闃熶笓涓氶厤缃�濈殑 A/B锛孻YMMDD 涓轰笂鎶ユ棩鏈燂紝nnn 涓哄悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紙浠� 001 璧凤級銆傝〃鍗曚腑璇ュ瓧娈靛彧璇伙紝鏄剧ず鈥滀繚瀛樺悗鑷姩鐢熸垚鈥濄��</div><div class="label">鏂板琛ㄥ崟浜や簰锛�2026-09-16 纭锛夛細</div><div class="note2">鐐瑰嚮鈥滄柊澧炩�濆脊鍑鸿〃鍗曪紝涓嶄娇鐢ㄥ師闇�姹傜殑琛ㄦ牸寮忓綍鍏ャ��</div><div class="note2">榛樿鍊硷細涓婃姤鍗曚綅=鐧诲綍鐢ㄦ埛鎵�灞炲崟浣嶏紱T闃熶笓涓�=鐧诲綍鐢ㄦ埛鎵�灞炰笓涓氾紱璐熻矗浜�=鐧诲綍鐢ㄦ埛锛涗笂鎶ユ棩鏈�=褰撳墠鏃ユ湡銆�</div><div class="note2">涓嬫媺瀛楁锛歍闃熶笓涓氥�佸伐绋嬩笓涓氥�佹晠闅滃奖鍝嶃�佹晠闅滄�ц川銆�</div><div class="note2">鐐瑰嚮鈥滆澶囧悕绉扳�濇墦寮�浜у搧缁撴瀯鏍戯紱閫夋嫨瑁呭锛堣澶囷級鍚庯紝鑷姩濉叆鈥滆澶囧悕绉扳�濆拰鈥滄墍灞炰笂绾х郴缁熲�濄��</div><div class="note2">H 涓烘晱鎰熻瘝瀛楀吀浠gО锛屽瓧娈电粺涓�鍐欌�滄槸鍚︽秹H鈥濄��</div></section><section class="card"><h3>AXU4 路 XCB鍔╃悊瀹℃牳锛堟枃妗i〉 10锛�</h3><div class="note">椤甸潰瀹氫綅锛氱畝鍖栨祦绋嬬 2 姝モ�淴CB鍔╃悊瀹℃牳鈥濄��</div><div class="note">鍛藉悕鍙e緞锛�2026-09-16 纭锛夛細椤甸潰鍙婅彍鍗曞悕绉扮粺涓�鍐欌�淴CB鍔╃悊瀹℃牳鈥濓紝椤甸潰鍐呭師鈥滃鎵光�濇枃妗堢粺涓�鏀逛负鈥滃鏍糕�濄��</div><div class="note2">銆愬凡纭 2026-09-16銆戞湰鐜妭瀹屾垚锛氬垎閰嶆壙淇崟浣嶃�佹寜鎵夸慨鍗曚綅鎷嗗崟銆佺敓鎴愬伐绋嬪崟鍙凤紱XCB鍔╃悊瀹℃牳瀹屾垚鍚庡嵆鍙鍣ㄦ潗銆侸CC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗涓�閿淳鍗曞苟鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛屼笉鍐嶅垎閰嶆垨淇敼鎵夸慨鍗曚綅銆�</div><div class="label">涓氬姟瑙勫垯</div><div class="note2">涓�涓晠闅滃崟鍙互鍒嗛厤澶氫釜鎵夸慨鍗曚綅锛涘鏍稿畬鎴愬悗鎸夋壙淇崟浣嶆媶鎴愬涓伐绋嬪崟璧板悗缁祦绋嬨��</div><div class="note2">宸ョ▼鍗曞彿鍦ㄦ湰鐜妭鐢熸垚锛岃鍒欙細鍥哄畾-X鍙�/鐝粍绫诲埆-YYMMDD-鍘傚浠e彿/搴忓彿锛屽叾涓� YYMMDD 涓轰笂鎶ユ棩鏈熴��</div><div class="note2">鏈纭畾鏄惁鐩戞祴椤圭洰銆佺洃娴嬫暟鍊肩瓑銆�</div><div class="label">鏌ヨ鏉′欢</div><div class="chips"><span class="td">宸ョ▼閫氱敤鏉′欢</span><span class="td">鎵夸慨鍗曚綅</span><span class="td">鏁呴殰鎬ц川</span><span class="td">浠诲姟鐘舵��</span></div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鏌ヨ</span><span class="td">鍒锋柊</span><span class="td">淇敼</span><span class="td">閿�椤癸紙浠匵CB鍔╃悊锛�</span><span class="td">瀹℃牳閫氳繃/涓嶉�氳繃</span><span class="td"><mark>宸插幓鎺夛細娣诲姞杩涘害澶囨敞銆丆MB宸查槄</mark></span></div><div class="note2">浠诲姟鐘舵�佺粺涓�閲囩敤鍏ㄥ眬涓冧釜鍊硷細鏈笂鎶� / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄銆�</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="label">瀹℃牳琛ㄥ崟瀛楁锛�2026-09-16 纭锛夛細</div><div class="chips"><span class="td">T闃熶笓涓�</span><span class="td">涓婃姤鍗曚綅</span><span class="td">璐熻矗浜�</span><span class="td">瑁呭鍚嶇О</span><span class="td">涓婃姤鏃ユ湡</span><span class="td">宸ョ▼鍚嶇О</span><span class="td">鎵夸慨鍗曚綅锛堝彲鍒嗛厤澶氫釜锛涘�欓�夋潵鑷�滅郴缁熺鐞� > 鎵夸慨鍗曚綅绠$悊鈥濓紝鎸夆�滄帓搴忊�濆彇鍓�7涓父鐢ㄦ壙淇崟浣嶆樉绀猴級</span><span class="td">璺熻釜鎶�鏈槦锛堜粠鏁呴殰淇℃伅甯﹀嚭锛屽崟閫夛細涓�闃�/浜岄槦/涓夐槦锛�</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">鏁呴殰鎬ц川</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鏄惁鐩戞祴椤圭洰</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鐩戞祴鏁版嵁鍊�</span><span class="td">澶囨敞</span></div><div class="note2">瀹℃牳琛ㄥ崟鍖呭惈閫氳繃銆佷笉閫氳繃銆佸叧闂�俋CB鍔╃悊瀹℃牳涓嶉�氳繃鏃朵笉杞�滀笂绾ф墦鍥炩�濓細鍐呭涓嶅悎鏍肩敱XCB鍔╃悊閫氳繃鈥滀慨鏀光�濇寜閽嚜琛屼慨姝o紱涓嶄綔涓烘晠闅滃鐞嗘椂鐐光�滈攢椤光�濄��</div><div class="note2">閿�椤规潈闄愪笌瀛楁锛�2026-09-17 纭锛夛細鏁呴殰涓婃姤闃舵鐨勯攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛涜褰曢攢椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂淬�傞攢椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�</div></section><section class="card"><h3>AXU5 路 CMB棰嗗瀹℃壒锛堟枃妗i〉 11锛�</h3><div class="note">鍒楄〃 + 鎵归噺瀹℃壒</div><div class="note2">AXU5/6/7 鍏遍�氾紙2026-09-17 纭锛夛細淇濈暀鍒楄〃銆佸埛鏂般�佹壒閲忓鎵癸紱鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱涓夐〉缁熶竴浣跨敤鈥滃鎵光�濈敤璇��</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鍒锋柊</span><span class="td">鎵归噺瀹℃壒</span><span class="td"><mark>宸插幓鎺夛細淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉�</mark></span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="note2">浠诲姟鐘舵�侊細缁熶竴閲囩敤鍏ㄥ眬涓冧釜鍊硷紱鏈〉瀹℃壒涓嶉�氳繃鏃剁洿鎺ヨ浆涓衡�滀笂绾ф墦鍥炩�濆苟閫�鍥瀀CB鍔╃悊銆�</div><div class="note2">鏁呴殰淇℃伅瀹℃壒瀛楁缁勶紙2026-09-17 纭锛夛細CMB棰嗗瀹℃壒鎰忚銆丆MB棰嗗瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丆MB棰嗗瀹℃壒鏃堕棿锛汮CC鍔╃悊瀹℃壒鎰忚銆丣CC鍔╃悊瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC鍔╃悊瀹℃壒鏃堕棿锛汮CC澶勯暱瀹℃壒鎰忚銆丣CC澶勯暱瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC澶勯暱瀹℃壒鏃堕棿銆�</div><div class="label">瀹℃壒琛ㄥ崟锛�</div><div class="chips"><span class="td">瀹℃壒鎰忚</span><span class="td">鍏抽棴 / 閫氳繃 / 涓嶉�氳繃</span></div></section><section class="card"><h3>AXU6 路 JCC鍔╃悊瀹℃壒锛堟枃妗i〉 12锛�</h3><div class="note">涓� AXU5 鍏辩敤鍚屼竴濂楃晫闈㈠拰鍔熻兘锛�2026-09-17 纭锛�</div><div class="note2">涓婄骇鎵撳洖瑙勫垯锛欽CC鍔╃悊瀹℃壒涓嶉�氳繃鏃剁洿鎺ラ��鍥瀀CB鍔╃悊锛屼换鍔$姸鎬佽浆涓衡�滀笂绾ф墦鍥炩�濓紝涓嶉�愮骇鎵撳洖銆�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鍒锋柊</span><span class="td">鎵归噺瀹℃壒</span><span class="td"><mark>宸插幓鎺夛細淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉�</mark></span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div></section><section class="card"><h3>AXU7 路 JCC澶勯暱瀹℃壒锛堟枃妗i〉 13锛�</h3><div class="note">涓� AXU5/6 鍏辩敤鍚屼竴濂楃晫闈㈠拰鍔熻兘锛�2026-09-17 纭锛夛紱椤甸潰鍙婅彍鍗曞悕绉扮粺涓�涓衡�淛CC澶勯暱瀹℃壒鈥�</div><div class="note2">涓婄骇鎵撳洖瑙勫垯锛欽CC澶勯暱瀹℃壒涓嶉�氳繃鏃剁洿鎺ラ��鍥瀀CB鍔╃悊锛屼换鍔$姸鎬佽浆涓衡�滀笂绾ф墦鍥炩�濓紝涓嶉�愮骇鎵撳洖銆�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鍒锋柊</span><span class="td">鎵归噺瀹℃壒</span><span class="td"><mark>宸插幓鎺夛細淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉�</mark></span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div></section><section class="card"><h3>AXU8 路 涓存姠淇伐绋嬬鐞嗭紙鎬昏锛夛紙鏂囨。椤� 14锛�</h3><div class="note">椤甸潰璇存槑锛氣�滅被浼煎凡娲惧伐绋嬪崟锛岃鑹蹭笉涓�鏍凤紝鎵嬪伐鎿嶄綔鈥�</div><div class="label">鐘舵�佸叆鍙� / 缁熻</div><div class="chips"><span class="td">瀹屽伐</span><span class="td">鏈畬宸�</span><span class="td">寰呰瘯楠�</span><span class="td">寰呭浠�</span><span class="td"><mark>宸茬‘璁や负宸ョ▼鐘舵�佺瓫閫夊叆鍙o紙鎸夐挳/椤电锛夛紝褰㈠紡寰呭畾</mark></span></div><div class="note2">涓嬫柟绀烘剰鎬ц〃鏍硷細琛ㄥご浠� Column1/Column2/Column3 + 2 琛岀┖琛岋紙浣滆�呮湭濉瓧娈碉級鈥斺�旀寜鐘舵�佺瓫閫夊睍绀哄伐绋嬪垪琛紝瀛楁鍚庣画鑷璁捐</div></section><section class="card"><h3>AXU9 路 CMB棰嗗宸查槄锛堟枃妗i〉 15锛�</h3><div class="note"><mark>鏆備笉澶勭悊 2026-09-20锛氫笉绾冲叆褰撳墠闇�姹備笌鍚庣画鏍稿銆�</mark></div><div class="note">椤甸潰璇存槑锛氣�滄煡鐪婥MB棰嗗宸叉壒闃呯殑鈥濆伐绋�</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span><span class="td">JCC鍔╃悊瀹℃壒锛堝惁锛�</span><span class="td">JCC澶勯暱瀹℃壒</span><span class="td">瀹℃壒瀹屾垚</span></div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">閿�椤�</span></div><div class="note2"><mark>JCC鍔╃悊瀹℃壒/JCC澶勯暱瀹℃壒/瀹℃壒瀹屾垚 涓夊垪鍚箟寰呯‘璁�</mark></div><div class="note2">娴佺▼浣嶇疆锛�2026-09-20 纭锛夛細JCC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟锛汚XU9涓嶄綔涓哄繀缁忔祦绋嬭妭鐐广��</div></section><section class="card"><h3>AXU10 路 娲惧崟绠$悊锛堟枃妗i〉 16锛�2026-09-20 鎸夌敤鎴峰彛寰勯噸瀹氫箟锛�</h3>
+</section><section class="card"><h3>AXU2 路 鏁版嵁鍖呭鍏ワ紙鏂囨。椤� 8锛�</h3><div class="note">瀵煎叆鏁呴殰涓婃姤鏁版嵁鍖呮枃浠�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">閫夋嫨鏂囦欢</span><span class="td">瀵煎叆鏁版嵁鍖�</span></div><div class="note2">瀵煎叆缁撴灉缁熻</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">缁熻鍒嗙被</span><span class="td">鏁伴噺</span><span class="td">鏄庣粏</span></div><div class="note2">缁熻鍒嗙被鏄庣粏锛氶」鐩暟閲� / 娑夊強宸ョ▼涓撲笟 / 娑夊強T闃熶笓涓� / 娑夊強瑁呭</div><div class="note2">銆愬凡纭 2026-09-14銆戝彧鍋氱綉缁滅増銆佹棤瀵煎叆鍔熻兘 鈫� 鏈〉鏁翠綋鍙栨秷锛岃彍鍗曢」鈥滄暟鎹寘瀵煎叆鈥濆垹闄わ紝鏃犻渶鏍稿銆�</div></section><section class="card"><h3>AXU3 路 鏁呴殰涓婃姤锛堟枃妗i〉 9锛�</h3><div class="note">椤甸潰鏍囬锛氬垪琛�</div><div class="label">鏌ヨ鏉′欢</div><div class="chips"><span class="td">X鍙�</span><span class="td">T闃熶笓涓�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">涓婃姤鍗曚綅</span><span class="td">璐熻矗浜�</span><span class="td">涓婃姤鏃ユ湡锛堣捣锛夆�旓紙鑷筹級</span></div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鏌ヨ</span><span class="td">鍒锋柊</span><span class="td">鏂板</span><span class="td">涓婃姤</span><span class="td">淇敼</span><span class="td">鍒犻櫎</span><span class="td">瀵煎嚭</span></div><div class="note2">銆愬凡纭 2026-09-16銆戝瓧娈典笌鎸夐挳閫氳鏃犺锛涒�滃~鍐欐晠闅滃垎鏋愭姤鍛娾�濈涓�鐗堟殏涓嶅疄鐜帮紝涓氬姟閫昏緫寰呭悗缁槑纭��</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鏁呴殰缂栧彿</span><span class="td">浠诲姟鐘舵��</span><span class="td">涓婃姤鏃ユ湡</span><span class="td">涓婃姤鍗曚綅</span><span class="td">瑁呭鍚嶇О</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="note2">浠诲姟鐘舵�侊紙2026-09-16 纭锛屽叏灞�涓冧釜鍊硷級锛氭湭涓婃姤 / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄銆�</div><div class="note2">鍒楄〃琛ㄥご澧炲姞鈥滀笂鎶ユ棩鏈熲�濃�滀笂鎶ュ崟浣嶁�濓紙2026-09-16 纭锛夈��</div><div class="note2">鍒犻櫎闄愬埗锛�2026-09-16 纭锛夛細鏈笂鎶ョ殑鏁呴殰鍙互鍒犻櫎锛涘凡涓婃姤鐨勬晠闅滀笉鑳藉垹闄わ紱浠诲姟鐘舵�佷负鈥滀笂绾ф墦鍥炩�濇椂浠嶅厑璁稿垹闄ゃ��</div><div class="note2">瀹℃壒瀛楁缁勶紙2026-09-17 纭锛夛細CMB棰嗗瀹℃壒鎰忚銆丆MB棰嗗瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丆MB棰嗗瀹℃壒鏃堕棿锛汮CC鍔╃悊瀹℃壒鎰忚銆丣CC鍔╃悊瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC鍔╃悊瀹℃壒鏃堕棿锛汮CC澶勯暱瀹℃壒鎰忚銆丣CC澶勯暱瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC澶勯暱瀹℃壒鏃堕棿銆�</div><div class="note2">閿�椤瑰瓧娈电粍锛�2026-09-17 纭锛夛細閿�椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂达紱鏁呴殰涓婃姤闃舵鐨勯攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛孴闃熷拰鍏朵粬瀹℃壒瑙掕壊涓嶆彁渚涢攢椤规搷浣溿�傞攢椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�</div><div class="label">鏂板/缂栬緫琛ㄥ崟瀛楁锛�2026-09-16 纭锛氭柊澧炴敼涓鸿〃鍗曞紡锛屽師闇�姹傝〃鏍煎紡宸茶皟鏁达級锛�</div><div class="chips"><span class="td">鏁呴殰缂栧彿锛堝彧璇伙紝鑷姩鐢熸垚锛�</span><span class="td">X鍙�</span><span class="td">涓婃姤鍗曚綅</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">涓婃姤鏃ユ湡</span><span class="td">宸ョ▼涓撲笟</span><span class="td">瑁呭鍚嶇О</span><span class="td">鍒嗘満/閮ㄤ綅</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">宸ョ▼鍚嶇О</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">鏁呴殰褰卞搷<mark>鍙厤缃瓧鍏革紱绀轰緥鍊硷細渚涙皵锛涘叾浠栧�煎緟纭</mark></span><span class="td">鏁呴殰鎬ц川<mark>鍙厤缃瓧鍏革紱绀轰緥鍊硷細閲嶅ぇ锛涘叾浠栧�煎緟纭</mark></span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="note2">瀛楁璋冩暣锛�2026-09-16 纭锛夛細鍋滄硦鍦板瓧娈典笉淇濈暀銆�</div><div class="note2">鏁呴殰缂栧彿锛�2026-09-28 纭锛屾浛浠� 2026-09-20 鍙e緞锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紝瑙勫垯 <b>鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn</b>锛沊鍙峰彇3浣嶆暟瀛楋紝鍒嗙被鍙栤�淭闃熶笓涓氶厤缃�濈殑 A/B锛孻YMMDD 涓轰笂鎶ユ棩鏈燂紝nnn 涓哄悓涓� X鍙�+鍚屼竴T闃熶笓涓�+鍚屼竴涓婃姤鏃ユ湡鐨勫簭鍙凤紙浠� 001 璧凤級銆傝〃鍗曚腑璇ュ瓧娈靛彧璇伙紝鏄剧ず鈥滀繚瀛樺悗鑷姩鐢熸垚鈥濄��</div><div class="label">鏂板琛ㄥ崟浜や簰锛�2026-09-16 纭锛夛細</div><div class="note2">鐐瑰嚮鈥滄柊澧炩�濆脊鍑鸿〃鍗曪紝涓嶄娇鐢ㄥ師闇�姹傜殑琛ㄦ牸寮忓綍鍏ャ��</div><div class="note2">榛樿鍊硷細涓婃姤鍗曚綅=鐧诲綍鐢ㄦ埛鎵�灞炲崟浣嶏紱T闃熶笓涓�=鐧诲綍鐢ㄦ埛鎵�灞炰笓涓氾紱璐熻矗浜�=鐧诲綍鐢ㄦ埛锛涗笂鎶ユ棩鏈�=褰撳墠鏃ユ湡銆�</div><div class="note2">涓嬫媺瀛楁锛歍闃熶笓涓氥�佸伐绋嬩笓涓氥�佹晠闅滃奖鍝嶃�佹晠闅滄�ц川銆�</div><div class="note2">鐐瑰嚮鈥滆澶囧悕绉扳�濇墦寮�浜у搧缁撴瀯鏍戯紱閫夋嫨瑁呭锛堣澶囷級鍚庯紝鑷姩濉叆鈥滆澶囧悕绉扳�濆拰鈥滄墍灞炰笂绾х郴缁熲�濄��</div><div class="note2">H 涓烘晱鎰熻瘝瀛楀吀浠gО锛屽瓧娈电粺涓�鍐欌�滄槸鍚︽秹H鈥濄��</div></section><section class="card"><h3>AXU4 路 XCB鍔╃悊瀹℃牳锛堟枃妗i〉 10锛�</h3><div class="note">椤甸潰瀹氫綅锛氱畝鍖栨祦绋嬬 2 姝モ�淴CB鍔╃悊瀹℃牳鈥濄��</div><div class="note">鍛藉悕鍙e緞锛�2026-09-16 纭锛夛細椤甸潰鍙婅彍鍗曞悕绉扮粺涓�鍐欌�淴CB鍔╃悊瀹℃牳鈥濓紝椤甸潰鍐呭師鈥滃鎵光�濇枃妗堢粺涓�鏀逛负鈥滃鏍糕�濄��</div><div class="note2">銆愬凡纭 2026-09-16銆戞湰鐜妭瀹屾垚锛氬垎閰嶆壙淇崟浣嶃�佹寜鎵夸慨鍗曚綅鎷嗗崟銆佺敓鎴愬伐绋嬪崟鍙凤紱XCB鍔╃悊瀹℃牳瀹屾垚鍚庡嵆鍙鍣ㄦ潗銆侸CC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟椤甸潰锛涢〉闈㈡樉绀烘湭娲惧崟鏁呴殰锛屽嬀閫夊悗涓�閿淳鍗曞苟鎸夋壙淇崟浣嶇敓鎴愭淳宸ュ崟锛屼笉鍐嶅垎閰嶆垨淇敼鎵夸慨鍗曚綅銆�</div><div class="label">涓氬姟瑙勫垯</div><div class="note2">涓�涓晠闅滃崟鍙互鍒嗛厤澶氫釜鎵夸慨鍗曚綅锛涘鏍稿畬鎴愬悗鎸夋壙淇崟浣嶆媶鎴愬涓伐绋嬪崟璧板悗缁祦绋嬨��</div><div class="note2">宸ョ▼鍗曞彿鍦ㄦ湰鐜妭鐢熸垚锛岃鍒欙細鍥哄畾-X鍙�/鐝粍绫诲埆-YYMMDD-鍘傚浠e彿/搴忓彿锛屽叾涓� YYMMDD 涓轰笂鎶ユ棩鏈熴��</div><div class="note2">鏈纭畾鏄惁鐩戞祴椤圭洰銆佺洃娴嬫暟鍊肩瓑銆�</div><div class="label">鏌ヨ鏉′欢</div><div class="chips"><span class="td">宸ョ▼閫氱敤鏉′欢</span><span class="td">鎵夸慨鍗曚綅</span><span class="td">鏁呴殰鎬ц川</span><span class="td">浠诲姟鐘舵��</span></div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鏌ヨ</span><span class="td">鍒锋柊</span><span class="td">淇敼</span><span class="td">閿�椤癸紙浠匵CB鍔╃悊锛�</span><span class="td">瀹℃牳閫氳繃/涓嶉�氳繃</span><span class="td"><mark>宸插幓鎺夛細娣诲姞杩涘害澶囨敞銆丆MB宸查槄</mark></span></div><div class="note2">浠诲姟鐘舵�佺粺涓�閲囩敤鍏ㄥ眬涓冧釜鍊硷細鏈笂鎶� / 宸蹭笂鎶CB / 涓婄骇鎵撳洖 / XCB鍔╃悊瀹℃牳瀹屾垚 / CMB棰嗗瀹℃壒瀹屾垚 / JCC鍔╃悊瀹℃壒瀹屾垚 / JCC澶勯暱宸查槄銆�</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="label">瀹℃牳琛ㄥ崟瀛楁锛�2026-09-16 纭锛夛細</div><div class="chips"><span class="td">T闃熶笓涓�</span><span class="td">涓婃姤鍗曚綅</span><span class="td">璐熻矗浜�</span><span class="td">瑁呭鍚嶇О</span><span class="td">涓婃姤鏃ユ湡</span><span class="td">宸ョ▼鍚嶇О</span><span class="td">鎵夸慨鍗曚綅锛堝彲鍒嗛厤澶氫釜锛涘�欓�夋潵鑷�滅郴缁熺鐞� > 鎵夸慨鍗曚綅绠$悊鈥濓紝鎸夆�滄帓搴忊�濆彇鍓�7涓父鐢ㄦ壙淇崟浣嶆樉绀猴級</span><span class="td">璺熻釜鎶�鏈槦锛堜粠鏁呴殰淇℃伅甯﹀嚭锛屽崟閫夛細涓�闃�/浜岄槦/涓夐槦锛�</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">鏁呴殰鎬ц川</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鏄惁鐩戞祴椤圭洰</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鐩戞祴鏁版嵁鍊�</span><span class="td">澶囨敞</span></div><div class="note2">瀹℃牳琛ㄥ崟鍖呭惈閫氳繃銆佷笉閫氳繃銆佸叧闂�俋CB鍔╃悊瀹℃牳涓嶉�氳繃鏃朵笉杞�滀笂绾ф墦鍥炩�濓細鍐呭涓嶅悎鏍肩敱XCB鍔╃悊閫氳繃鈥滀慨鏀光�濇寜閽嚜琛屼慨姝o紱涓嶄綔涓烘晠闅滃鐞嗘椂鐐光�滈攢椤光�濄��</div><div class="note2">閿�椤规潈闄愪笌瀛楁锛�2026-09-17 纭锛夛細鏁呴殰涓婃姤闃舵鐨勯攢椤圭粺涓�鐢盭CB鍔╃悊鎵ц锛涜褰曢攢椤圭悊鐢便�侀攢椤逛汉銆侀攢椤规椂闂淬�傞攢椤逛汉鑷姩鍙栧綋鍓嶇櫥褰昘CB鍔╃悊锛岄攢椤规椂闂磋嚜鍔ㄥ彇绯荤粺褰撳墠鏃堕棿銆�</div></section><section class="card"><h3>AXU5 路 CMB棰嗗瀹℃壒锛堟枃妗i〉 11锛�</h3><div class="note">鍒楄〃 + 鎵归噺瀹℃壒</div><div class="note2">AXU5/6/7 鍏遍�氾紙2026-09-17 纭锛夛細淇濈暀鍒楄〃銆佸埛鏂般�佹壒閲忓鎵癸紱鍘绘帀淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉紱涓夐〉缁熶竴浣跨敤鈥滃鎵光�濈敤璇��</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鍒锋柊</span><span class="td">鎵归噺瀹℃壒</span><span class="td"><mark>宸插幓鎺夛細淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉�</mark></span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div><div class="note2">浠诲姟鐘舵�侊細缁熶竴閲囩敤鍏ㄥ眬涓冧釜鍊硷紱鏈〉瀹℃壒涓嶉�氳繃鏃剁洿鎺ヨ浆涓衡�滀笂绾ф墦鍥炩�濆苟閫�鍥瀀CB鍔╃悊銆�</div><div class="note2">鏁呴殰淇℃伅瀹℃壒瀛楁缁勶紙2026-09-17 纭锛夛細CMB棰嗗瀹℃壒鎰忚銆丆MB棰嗗瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丆MB棰嗗瀹℃壒鏃堕棿锛汮CC鍔╃悊瀹℃壒鎰忚銆丣CC鍔╃悊瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC鍔╃悊瀹℃壒鏃堕棿锛汮CC澶勯暱瀹℃壒鎰忚銆丣CC澶勯暱瀹℃壒缁撴灉锛堥�氳繃/涓嶉�氳繃锛夈�丣CC澶勯暱瀹℃壒鏃堕棿銆�</div><div class="label">瀹℃壒琛ㄥ崟锛�</div><div class="chips"><span class="td">瀹℃壒鎰忚</span><span class="td">鍏抽棴 / 閫氳繃 / 涓嶉�氳繃</span></div></section><section class="card"><h3>AXU6 路 JCC鍔╃悊瀹℃壒锛堟枃妗i〉 12锛�</h3><div class="note">涓� AXU5 鍏辩敤鍚屼竴濂楃晫闈㈠拰鍔熻兘锛�2026-09-17 纭锛�</div><div class="note2">涓婄骇鎵撳洖瑙勫垯锛欽CC鍔╃悊瀹℃壒涓嶉�氳繃鏃剁洿鎺ラ��鍥瀀CB鍔╃悊锛屼换鍔$姸鎬佽浆涓衡�滀笂绾ф墦鍥炩�濓紝涓嶉�愮骇鎵撳洖銆�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鍒锋柊</span><span class="td">鎵归噺瀹℃壒</span><span class="td"><mark>宸插幓鎺夛細淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉�</mark></span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div></section><section class="card"><h3>AXU7 路 JCC澶勯暱瀹℃壒锛堟枃妗i〉 13锛�</h3><div class="note">涓� AXU5/6 鍏辩敤鍚屼竴濂楃晫闈㈠拰鍔熻兘锛�2026-09-17 纭锛夛紱椤甸潰鍙婅彍鍗曞悕绉扮粺涓�涓衡�淛CC澶勯暱瀹℃壒鈥�</div><div class="note2">涓婄骇鎵撳洖瑙勫垯锛欽CC澶勯暱瀹℃壒涓嶉�氳繃鏃剁洿鎺ラ��鍥瀀CB鍔╃悊锛屼换鍔$姸鎬佽浆涓衡�滀笂绾ф墦鍥炩�濓紝涓嶉�愮骇鎵撳洖銆�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鍒锋柊</span><span class="td">鎵归噺瀹℃壒</span><span class="td"><mark>宸插幓鎺夛細淇敼銆丆MB宸查槄銆侀攢椤广�佹坊鍔犺繘搴﹀娉�</mark></span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">鎿嶄綔</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div></section><section class="card"><h3>AXU8 路 涓存姠淇伐绋嬬鐞嗭紙鎬昏锛夛紙鏂囨。椤� 14锛�</h3><div class="note">椤甸潰璇存槑锛氣�滅被浼煎凡娲惧伐绋嬪崟锛岃鑹蹭笉涓�鏍凤紝鎵嬪伐鎿嶄綔鈥�</div><div class="label">鐘舵�佸叆鍙� / 缁熻</div><div class="chips"><span class="td">瀹屽伐</span><span class="td">鏈畬宸�</span><span class="td">寰呰瘯楠�</span><span class="td">寰呭浠�</span><span class="td"><mark>宸茬‘璁や负宸ョ▼鐘舵�佺瓫閫夊叆鍙o紙鎸夐挳/椤电锛夛紝褰㈠紡寰呭畾</mark></span></div><div class="note2">涓嬫柟绀烘剰鎬ц〃鏍硷細琛ㄥご浠� Column1/Column2/Column3 + 2 琛岀┖琛岋紙浣滆�呮湭濉瓧娈碉級鈥斺�旀寜鐘舵�佺瓫閫夊睍绀哄伐绋嬪垪琛紝瀛楁鍚庣画鑷璁捐</div></section><section class="card"><h3>AXU9 路 CMB棰嗗宸查槄锛堟枃妗i〉 15锛�</h3><div class="note"><mark>鏆備笉澶勭悊 2026-09-20锛氫笉绾冲叆褰撳墠闇�姹備笌鍚庣画鏍稿銆�</mark></div><div class="note">椤甸潰璇存槑锛氣�滄煡鐪婥MB棰嗗宸叉壒闃呯殑鈥濆伐绋�</div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">浠诲姟鐘舵��</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span><span class="td">JCC鍔╃悊瀹℃壒锛堝惁锛�</span><span class="td">JCC澶勯暱瀹℃壒</span><span class="td">瀹℃壒瀹屾垚</span></div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">閿�椤�</span></div><div class="note2"><mark>JCC鍔╃悊瀹℃壒/JCC澶勯暱瀹℃壒/瀹℃壒瀹屾垚 涓夊垪鍚箟寰呯‘璁�</mark></div><div class="note2">娴佺▼浣嶇疆锛�2026-09-20 纭锛夛細JCC澶勯暱瀹℃壒瀹屾垚鍚庣洿鎺ヨ繘鍏CB鍔╃悊娲惧崟锛汚XU9涓嶄綔涓哄繀缁忔祦绋嬭妭鐐广��</div></section><section class="card"><h3>AXU10 路 娲惧崟绠$悊锛堟枃妗i〉 16锛�2026-09-20 鎸夌敤鎴峰彛寰勯噸瀹氫箟锛�</h3>
 <div class="note2">椤甸潰鍏ュ彛锛欽CC澶勯暱瀹℃壒瀹屾垚鍚庯紝娴佺▼鐩存帴杩涘叆 XCB鍔╃悊娲惧崟椤甸潰銆�</div>
 <div class="note2">椤甸潰鍐呭锛氭樉绀衡�滄湭娲惧崟鈥濈殑鏁呴殰涓�瑙堛��</div>
 <div class="note2">鎿嶄綔锛氬嬀閫変竴涓垨澶氫釜鏁呴殰锛岀偣鍑烩�滀竴閿淳鍗曗�濄�傞櫎鈥滀竴閿淳鍗曗�濆锛屽師鎴浘涓殑淇敼銆佺敓鎴愪复淇伐绋嬪崟銆佸垹闄や簹澶嶏紙鐤戜负娓呴櫎閲嶅锛夌瓑涓氬姟鎸夐挳鏆備笉闇�瑕併��</div>
@@ -72,7 +72,7 @@
 <div class="chips"><span class="td">涓�閿淳鍗曪紙鍘烵CR鈥滈敭娲惧崟鈥濓級</span><span class="td"><mark>淇敼銆佺敓鎴愪复淇伐绋嬪崟銆佸垹闄や簹澶嶏紙鐤戜负鈥滄竻闄ら噸澶嶁�濓級鏆備笉闇�瑕�</mark></span></div>
 <div class="label">鍘熸埅鍥惧垪琛ㄥ垪锛堝瓨妗e弬鑰冿級</div>
 <div class="chips"><span class="td">搴忓彿</span><span class="td">宸ョ▼涓撲笟</span><span class="td">涓婃姤鍗曚綅</span><span class="td">涓婃姤鏃堕棿</span><span class="td">淇埞鍔炲鏍告椂闂�</span><span class="td">鎵夸慨鍗曚綅</span><span class="td">X鍙�</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div>
-<div class="note2">鏁呴殰缂栧彿鏉ユ簮锛�2026-09-28 鏇存柊锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紙娼�1鍩�-X鍙�/鍒嗙被-YYMMDD-nnn锛夛紝娲惧伐鍗曠紪鍙蜂负鈥滄晠闅滅紪鍙�/01銆�/02鈥︹�濄�傜涓�鐗堝疄闄呴〉闈互鈥滄湭娲惧崟鏁呴殰鍒楄〃 + 鍕鹃�� + 涓�閿淳鍗曗�濅负鍑嗐��</div>
+<div class="note2">鏁呴殰缂栧彿鏉ユ簮锛�2026-09-28 鏇存柊锛夛細鐢辩郴缁熻嚜鍔ㄧ敓鎴愶紙鍥哄畾鍓嶇紑-X鍙�/鍒嗙被-YYMMDD-nnn锛夛紝娲惧伐鍗曠紪鍙蜂负鈥滄晠闅滅紪鍙�/01銆�/02鈥︹�濄�傜涓�鐗堝疄闄呴〉闈互鈥滄湭娲惧崟鏁呴殰鍒楄〃 + 鍕鹃�� + 涓�閿淳鍗曗�濅负鍑嗐��</div>
 </section><section class="card"><h3>AXU11 路 宸叉淳宸ョ▼锛堟枃妗i〉 17锛�</h3><div class="note"><mark>鏆備笉澶勭悊 2026-09-20锛氫笉绾冲叆褰撳墠闇�姹備笌鍚庣画鏍稿銆�</mark></div><div class="note">鎾ら攢绛夌鐞�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒楄〃</span><span class="td">鎵归噺鎾ら攢</span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">宸ョ▼涓撲笟</span><span class="td">涓婃姤鍗曚綅</span><span class="td">涓婃姤鏃堕棿</span><span class="td">淇埞鍔炲鏍告椂闂�</span><span class="td">娲惧崟鍗曚綅</span><span class="td">椤圭洰鏁伴噺</span><span class="td">鎾ら攢</span><span class="td">璇︽儏</span></div><div class="note2">琛屾搷浣滐細鎾ら攢娲惧崟 / 鏌ョ湅璇︽儏</div></section><section class="card"><h3>AXU12 路 宸叉淳宸ョ▼鍗曪紙鏂囨。椤� 18锛�</h3><div class="note"><mark>鏆備笉澶勭悊 2026-09-20锛氫笉绾冲叆褰撳墠闇�姹備笌鍚庣画鏍稿銆�</mark></div><div class="note">瀹屽伐绠$悊</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒锋柊</span><span class="td">瀹屽伐</span><span class="td">瀹屽伐寰呰瘯楠�</span><span class="td">鏈畬宸�</span><span class="td">寰呭浠�</span><span class="td">瀵煎嚭宸ョ▼鍗�</span><span class="td">娣诲姞杩涘害澶囨敞</span><span class="td">閿�椤�</span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">宸ョ▼鍗曞崟鍙�</span><span class="td">娲惧崟鍗曞彿</span><span class="td">宸ョ▼涓撲笟</span><span class="td">涓婃姤鍗曚綅</span><span class="td">涓婃姤鏃堕棿</span><span class="td">淇埞鍔炲鏍告椂闂�</span><span class="td">娲惧崟鍗曚綅</span><span class="td">X鍙�</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span></div></section><section class="card"><h3>AXU13 路 宸ョ▼鍗曟墦鍗拌緭鍑猴紙鏂囨。椤� 19锛�</h3><div class="note"><mark>鏆備笉澶勭悊 2026-09-20锛氫笉绾冲叆褰撳墠闇�姹備笌鍚庣画鏍稿銆�</mark></div><div class="note">椤甸潰澶囨敞锛�</div><div class="note2">1. 鎵归噺鎵撳嵃杈撳嚭锛岃緭鍑虹畝鍗曢」鐩俊鎭紝渚涙姤浠蜂娇鐢�</div><div class="note2">2. 鍗曟潯瀵煎嚭涔熷彲鑳芥湁澶氭潯淇℃伅锛岃繖閲屾槸鎸夋淳宸ュ崟鍒楄〃鐨勮〃鏍�</div><div class="label">鎸夐挳/鎿嶄綔</div><div class="chips"><span class="td">鍒锋柊</span><span class="td">鎵归噺瀵煎嚭瀹屽伐楠屾敹鍗�</span><span class="td">鎵归噺瀵煎嚭瀹屽伐鍗�</span><span class="td">瀵煎嚭寰呭畬宸ユ暟鎹寘<mark>2026-09-14锛氬凡纭鍙栨秷锛堢绾挎暟鎹寘锛孋-12锛�</mark></span><span class="td">璐㈠姟鍥涜仈鍗�</span><span class="td">瀹屽伐楠屾敹鍗�</span><span class="td">宸ョ▼鍗�</span></div><div class="label">鍒楄〃鍒楋紙琛ㄥご锛�</div><div class="chips"><span class="td">搴忓彿</span><span class="td">璇︽儏</span><span class="td">宸ョ▼鍗曞崟鍙�</span><span class="td">娲惧崟鍗曞彿</span><span class="td">宸ョ▼涓撲笟</span><span class="td">涓婃姤鍗曚綅</span><span class="td">涓婃姤鏃堕棿</span><span class="td">淇埞鍔炲鏍告椂闂�</span><span class="td">娲惧崟鍗曚綅</span><span class="td">X鍙�</span><span class="td">鎵�灞炰笂绾х郴缁�</span><span class="td">瑁呭鍚嶇О</span><span class="td">宸ョ▼鍚嶇О鍙婃崯鍧忔儏鍐�</span><span class="td">淇悊鎶�鏈姹�</span><span class="td">宸ョ▼涓撲笟</span><span class="td">T闃熶笓涓�</span><span class="td">璺熻釜鎶�鏈槦</span><span class="td">璐熻矗浜�</span><span class="td">鏁呴殰褰卞搷</span><span class="td">鏁呴殰鎬ц川</span><span class="td">閲嶅ぇ鏁呴殰鎻忚堪</span><span class="td">鏄惁鑷慨</span><span class="td">鏄惁娑塇</span><span class="td">鐩戞祴椤圭洰</span><span class="td">鐩戞祴鏁板��</span><span class="td">瑕佹眰瀹屽伐鏃ユ湡</span><span class="td">澶囨敞</span><span class="td">鎿嶄綔锛氭煡鐪� / 瀵煎嚭</span></div></section><section class="card"><h3>AXU14 路 瀹屽伐鎯呭喌澶勭悊锛堟枃妗i〉 20锛�2026-09-20 鎸夌敤鎴峰彛寰勯噸瀹氫箟锛�</h3>
 <div class="note2">椤甸潰浣嶇疆锛氭淳鍗曞悗杩涘叆鏈〉銆傛瘡涓�琛屼唬琛�1涓淳宸ュ崟锛屽彧鍏佽鍕鹃��1琛屻��</div>
 <div class="note2">鍒楄〃瀛楁璋冩暣锛氬幓鎺夆�滃伐绋嬪崟鍗曞彿鈥濓紝澧炲姞鈥滃畬宸ョ姸鎬佲�濄�傛淳鐢熷伐鍗曟椂锛屽畬宸ョ姸鎬侀粯璁ゅ�间负鈥滄湭瀹屽伐鈥濄��</div>

--
Gitblit v1.9.1