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 <> '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 <> '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)) || ',') > 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 <= (
@@ -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 <= 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 <> '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