From ce841ddf85c48bae32f9e3068403fdfe98ac8711 Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期三, 07 十月 2026 12:35:09 +0800
Subject: [PATCH] feat: add extension defaults for fault reporting

---
 emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java     |   22 ++
 emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.java                    |   28 ++
 emergency-repair-web/src/views/Login.vue                                                              |    6 
 emergency-repair-web/src/views/fault/FaultReport.vue                                                  |   25 +-
 HANDOFF.md                                                                                            |   54 ++++++
 emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java |    3 
 requirement/交接文件_2026.10.3.md                                                                         |   54 ++++++
 emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java            |    4 
 emergency-repair-web/src/views/system/UserManage.vue                                                  |   65 +++++++
 emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java                  |   12 +
 requirement/改进20261003.txt                                                                            |   10 +
 emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java           |  150 ++++++++++++++++
 emergency-repair-web/src/views/system/OrganizationManage.vue                                          |   60 ++++++
 13 files changed, 474 insertions(+), 19 deletions(-)

diff --git a/HANDOFF.md b/HANDOFF.md
index b08a22f..e2335ac 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -938,3 +938,57 @@
 - 浣跨敤鏂� jar 閲嶅惎鍚庡啀娆℃煡璇紝鍥涘紶涓氬姟琛ㄤ粛鍧囦负 `0` 琛岋紱鐧诲綍鎺ュ彛 `200`锛屽墠绔椤� `200`銆�
 - 褰撳墠鍚庣 PID `21528`锛岀洃鍚� `8091`锛涙棩蹇楋細`.codex-run/backend-20261007-cleared.out.log`銆乣.codex-run/backend-20261007-cleared.err.log`銆�
 - 濡傞渶閲嶆柊鐏屽叆婕旂ず涓氬姟鏁版嵁锛屽皢 `app.seed-demo-data` 涓存椂璁句负 `true` 鍚庨噸鍚��
+
+## 26. 鏀跺熬蹇収锛�2026-10-07 鍛ㄤ笁锛岄渶姹� 28-30 鎵╁睍灞炴�т笌鏂板鏁呴殰榛樿鍊硷級
+
+**闇�姹傚彛寰�**
+- 闇�姹� 30 缂栧彿宸茬‘璁わ紱鏂板鏁呴殰閲岀殑鈥滆矗浠烩�濆氨鏄�滆礋璐d汉鈥濄��
+- 閮ㄩ棬鎵╁睍灞炴�р�滆礋璐k_T鈥濈殑鍊煎氨鏄� `rk_X鍙穈锛屽�煎繀椤绘槸浜у搧缁撴瀯鏍� `side` 绫诲瀷鑺傜偣锛屽崟閫夈��
+- 鏂板鏁呴殰鐨� `rk_X鍙穈 榛樿鍊煎彧鍙栫櫥褰曠敤鎴锋墍灞為儴闂ㄧ殑鎵╁睍灞炴�э紱鍗充娇鐢ㄦ埛鎵╁睍灞炴�т篃璁剧疆浜嗏�滆礋璐k_T鈥濓紝浠嶄互閮ㄩ棬璁剧疆涓哄噯銆�
+- 鈥渞k_TD涓撲笟鈥濅笌鐜版湁鈥渞k_T闃熶笓涓氣�濇槸鍚屼竴瀛楁锛屼唬鐮佸瓧娈典负 `teamMajor`锛屽綋鍓嶇晫闈㈡殏鏃舵部鐢ㄢ�渞k_T闃熶笓涓氣�濄��
+- 澶栭儴 `CSICZB_ZB.SYS_DEPT`銆乣CSICZB_ZB.SYS_USER` 鐢卞叾浠栫郴缁熺淮鎶わ紝鏈」鐩彧璇伙紱鎵╁睍灞炴�т繚瀛樺埌鏈湴鏂拌〃銆�
+
+**鏈疆瀹屾垚鍐呭**
+- `DatabaseBootstrap` 鑷姩鍒涘缓 `LX.T_SYS_DEPT_EXT`锛坄DEPT_ID`銆乣RESPONSIBLE_X_NO` 鍙婂璁″瓧娈碉級鍜� `LX.T_SYS_USER_EXT`锛坄USER_ID`銆乣ENGINEERING_MAJOR`銆乣TEAM_MAJOR`銆乣RESPONSIBLE_X_NO` 鍙婂璁″瓧娈碉級銆�
+- 缁勭粐鏈烘瀯椤甸潰澧炲姞鈥滄墿灞曞睘鎬р�濇寜閽拰寮圭獥锛屽彲璁剧疆閮ㄩ棬鈥滆礋璐k_T鈥濓紱鍒楄〃鏄剧ず宸茶缃�笺��
+- 鐢ㄦ埛绠$悊椤甸潰澧炲姞鈥滄墿灞曞睘鎬р�濇寜閽拰寮圭獥锛屽彲璁剧疆宸ョ▼涓撲笟銆乺k_T闃熶笓涓氥�佽礋璐k_T锛涘垪琛ㄦ樉绀烘墿灞曞睘鎬ф槸鍚﹀凡璁剧疆銆�
+- 鏂板鏁呴殰寮圭獥榛樿甯﹀嚭锛氫笂鎶ュ崟浣嶄负褰撳墠鐧诲綍浜虹殑鍗曚綅銆佽礋璐d汉涓哄綋鍓嶇櫥褰曚汉銆乣rk_X鍙穈涓哄綋鍓嶉儴闂ㄦ墿灞曞睘鎬с�佸伐绋嬩笓涓氬拰 rk_T闃熶笓涓氫负褰撳墠鐢ㄦ埛鎵╁睍灞炴�с��
+- 鐢ㄦ埛鎵╁睍灞炴�т负绌烘椂锛屽伐绋嬩笓涓氬拰 rk_T闃熶笓涓氫笉鍐嶉粯璁ら�夋嫨閰嶇疆鍒楄〃绗竴椤广��
+
+**鎺ュ彛涓庢潈闄�**
+- `GET/PUT /api/system/depts/{deptId}/ext`锛氭煡璇㈠拰淇濆瓨閮ㄩ棬鎵╁睍灞炴�с��
+- `GET/PUT /api/system/users/{userId}/ext`锛氭煡璇㈠拰淇濆瓨鐢ㄦ埛鎵╁睍灞炴�с��
+- `/api/auth/login`銆乣/api/auth/me` 杩斿洖 `userId`銆乣deptId`銆乣engineeringMajor`銆乣teamMajor`銆乣deptResponsibleXNo`銆乣responsibleXNo`銆�
+- 鎸夐挳鏉冮檺鏂板 `system:organization:ext`锛堟墿灞曞睘鎬ц缃級銆乣system:users:ext`锛堟墿灞曞睘鎬ц缃級銆�
+
+**涓昏鍙樻洿鏂囦欢**
+- `emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.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/system/controller/SystemController.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java`
+- `emergency-repair-web/src/views/Login.vue`
+- `emergency-repair-web/src/views/fault/FaultReport.vue`
+- `emergency-repair-web/src/views/system/OrganizationManage.vue`
+- `emergency-repair-web/src/views/system/UserManage.vue`
+- `requirement/鏀硅繘20261003.txt`锛堣ˉ鍏呴渶姹� 28-30锛�
+
+**楠岃瘉缁撴灉**
+- 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃锛屼粎鏈夋棦鏈夊寘浣撶Н鍛婅銆�
+- `node .codex-run/verify-req28-30.js` 杩斿洖 `pass: true`銆�
+- 閮ㄩ棬 `1390477009348739073 / sy鍖哄煙` 璁剧疆 `rk_X鍙�=201`锛岀敤鎴� `123 / yszl` 璁剧疆宸ョ▼涓撲笟 `鍔ㄥ姏涓撲笟`銆乺k_T闃熶笓涓� `娑插帇涓撲笟`銆佽礋璐� `202`銆�
+- 鏂板鏁呴殰瀹為檯榛樿鍊间负锛氫笂鎶ュ崟浣� `sy鍖哄煙`銆佽礋璐d汉 `鍔╃悊鐢ㄦ埛锛堟紨绀猴級`銆乣rk_X鍙�=201`銆乺k_T闃熶笓涓� `娑插帇涓撲笟`銆佸伐绋嬩笓涓� `鍔ㄥ姏涓撲笟`銆�
+- 鎴浘锛歚.codex-run/req28-org-ext.png`銆乣.codex-run/req29-user-ext.png`銆乣.codex-run/req30-fault-defaults.png`銆�
+- 楠岃瘉鍚庡凡娓呴櫎涓存椂閮ㄩ棬銆佺敤鎴锋墿灞曞睘鎬э紝鏌ヨ纭 `configured=false` 涓斿瓧娈典负绌恒��
+
+**褰撳墠杩愯鐘舵�侊紙2026-10-07锛�**
+- 鍚庣 PID `56124`锛岀洃鍚� `http://127.0.0.1:8091`锛涘墠绔� PID `57312`锛岀洃鍚� `http://localhost:8082`銆�
+- 涓� UI 楠岃瘉鍚姩鐨� headless Edge PID `95756`锛孋DP 绔彛 `9222`锛涢厤缃洰褰� `.codex-run/edge-profile-req28-30`銆�
+- 鍚庣鏃ュ織锛歚.codex-run/backend-20261007-req28-30-final.out.log`銆乣.codex-run/backend-20261007-req28-30-final.err.log`銆�
+- 鐧诲綍 `admin / admin123`锛涙暟鎹簱 `SYSDBA/SYSDBA`锛屼笟鍔� schema `LX`銆�
+
+**鎺ユ墜娉ㄦ剰**
+- 寮�鍙戠幆澧冩晱鎰熻瘝閰嶇疆浼氭妸 `rk_T` 鏄剧ず涓衡�滄鈥濓紝鍥犳椤甸潰鎴浘涓彲鑳芥樉绀衡�滆礋璐f鈥濓紱浠g爜鍜屽悗绔瓧娈典粛鏄� `rk_T`銆�
+- 涓嶈鎻愪氦 `.codex-run`銆丱ffice 閿佹枃浠舵垨 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt`銆�
+- 鏈疆浠g爜銆侀渶姹傛枃浠跺拰浜ゆ帴璁板綍涓�骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
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 3da7330..3348937 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
@@ -58,6 +58,12 @@
         data.put("realName", StringUtils.hasText(stringValue(user.get("nickName")))
                 ? stringValue(user.get("nickName")) : username);
         data.put("deptName", stringValue(user.get("deptName")));
+        data.put("userId", String.valueOf(user.get("userId")));
+        data.put("deptId", user.get("deptId") == null ? "" : String.valueOf(user.get("deptId")));
+        data.put("engineeringMajor", stringValue(user.get("engineeringMajor")));
+        data.put("teamMajor", stringValue(user.get("teamMajor")));
+        data.put("deptResponsibleXNo", stringValue(user.get("deptResponsibleXNo")));
+        data.put("responsibleXNo", stringValue(user.get("responsibleXNo")));
         data.put("roles", systemService.roleCodes(username));
         data.put("permissions", systemService.permissionCodes(username));
         return Result.ok(data);
@@ -76,6 +82,12 @@
         data.put("realName", StringUtils.hasText(stringValue(user.get("nickName")))
                 ? stringValue(user.get("nickName")) : username);
         data.put("deptName", stringValue(user.get("deptName")));
+        data.put("userId", String.valueOf(user.get("userId")));
+        data.put("deptId", user.get("deptId") == null ? "" : String.valueOf(user.get("deptId")));
+        data.put("engineeringMajor", stringValue(user.get("engineeringMajor")));
+        data.put("teamMajor", stringValue(user.get("teamMajor")));
+        data.put("deptResponsibleXNo", stringValue(user.get("deptResponsibleXNo")));
+        data.put("responsibleXNo", stringValue(user.get("responsibleXNo")));
         data.put("roles", systemService.roleCodes(username));
         data.put("permissions", systemService.permissionCodes(username));
         return Result.ok(data);
@@ -87,9 +99,13 @@
         }
         try {
             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)",
+                    "SELECT u.USER_ID, u.USER_NAME, u.NICK_NAME, u.PASSWORD, u.STATUS, u.DEPT_ID, d.DEPT_NAME, "
+                            + "ue.ENGINEERING_MAJOR, ue.TEAM_MAJOR, ue.RESPONSIBLE_X_NO AS USER_RESPONSIBLE_X_NO, "
+                            + "de.RESPONSIBLE_X_NO AS DEPT_RESPONSIBLE_X_NO "
+                            + "FROM LX.SYS_USER u LEFT JOIN LX.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID "
+                            + "LEFT JOIN LX.T_SYS_USER_EXT ue ON ue.USER_ID = u.USER_ID AND (ue.DEL_FLAG = 0 OR ue.DEL_FLAG IS NULL) "
+                            + "LEFT JOIN LX.T_SYS_DEPT_EXT de ON de.DEPT_ID = u.DEPT_ID AND (de.DEL_FLAG = 0 OR de.DEL_FLAG IS NULL) "
+                            + "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<>();
@@ -98,6 +114,12 @@
                         row.put("nickName", rs.getString("NICK_NAME"));
                         row.put("password", rs.getString("PASSWORD"));
                         row.put("status", rs.getString("STATUS"));
+                        Object deptId = rs.getObject("DEPT_ID");
+                        row.put("deptId", deptId == null ? null : rs.getLong("DEPT_ID"));
+                        row.put("engineeringMajor", rs.getString("ENGINEERING_MAJOR"));
+                        row.put("teamMajor", rs.getString("TEAM_MAJOR"));
+                        row.put("responsibleXNo", rs.getString("USER_RESPONSIBLE_X_NO"));
+                        row.put("deptResponsibleXNo", rs.getString("DEPT_RESPONSIBLE_X_NO"));
                         row.put("deptName", rs.getString("DEPT_NAME"));
                         return row;
                     });
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 4797f5e..1814e1f 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
@@ -18,7 +18,7 @@
             "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",
-            "T_SYS_ERROR_LOG"
+            "T_SYS_ERROR_LOG", "T_SYS_DEPT_EXT", "T_SYS_USER_EXT"
     };
     private final JdbcTemplate jdbcTemplate;
 
@@ -122,6 +122,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_DEPT_EXT", "CREATE TABLE LX.T_SYS_DEPT_EXT (DEPT_ID BIGINT NOT NULL PRIMARY KEY, RESPONSIBLE_X_NO VARCHAR(40), CREATE_BY VARCHAR(120), UPDATE_BY VARCHAR(120), DEL_FLAG INT DEFAULT 0, CREATE_TIME TIMESTAMP, UPDATE_TIME TIMESTAMP)");
+        ddl.put("T_SYS_USER_EXT", "CREATE TABLE LX.T_SYS_USER_EXT (USER_ID BIGINT NOT NULL PRIMARY KEY, ENGINEERING_MAJOR VARCHAR(100), TEAM_MAJOR VARCHAR(100), RESPONSIBLE_X_NO VARCHAR(40), CREATE_BY VARCHAR(120), UPDATE_BY VARCHAR(120), DEL_FLAG INT DEFAULT 0, CREATE_TIME TIMESTAMP, UPDATE_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)");
         ddl.put("T_SYS_ERROR_LOG", "CREATE TABLE LX.T_SYS_ERROR_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), MODULE VARCHAR(100), REQUEST_METHOD VARCHAR(10), REQUEST_URI VARCHAR(500), EXCEPTION_TYPE VARCHAR(300), MESSAGE VARCHAR(2000), STACK_TRACE CLOB, ERROR_TIME TIMESTAMP)");
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 321c475..ce7c2e6 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
@@ -128,6 +128,18 @@
     }
 
     @Data
+    public static class DeptExtRequest {
+        private String responsibleXNo;
+    }
+
+    @Data
+    public static class UserExtRequest {
+        private String engineeringMajor;
+        private String teamMajor;
+        private String responsibleXNo;
+    }
+
+    @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/system/controller/SystemController.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
index 09f271c..2f1e564 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
@@ -80,6 +80,17 @@
         return Result.ok();
     }
 
+    @GetMapping("/users/{userId}/ext")
+    public Result<Map<String, Object>> userExt(@PathVariable Long userId) {
+        return Result.ok(systemService.userExt(userId));
+    }
+
+    @PutMapping("/users/{userId}/ext")
+    public Result<Void> saveUserExt(@PathVariable Long userId, @RequestBody Requests.UserExtRequest request) {
+        systemService.saveUserExt(userId, request.getEngineeringMajor(), request.getTeamMajor(), request.getResponsibleXNo());
+        return Result.ok();
+    }
+
     @PutMapping("/users/roles/batch")
     public Result<Void> saveBatchUserRoles(@RequestBody Requests.BatchUserRoleRequest request) {
         systemService.saveUserRolesBatch(request.getUserIds(), request.getRoleIds(), request.getMode());
@@ -91,6 +102,17 @@
         return Result.ok(systemService.depts());
     }
 
+    @GetMapping("/depts/{deptId}/ext")
+    public Result<Map<String, Object>> deptExt(@PathVariable Long deptId) {
+        return Result.ok(systemService.deptExt(deptId));
+    }
+
+    @PutMapping("/depts/{deptId}/ext")
+    public Result<Void> saveDeptExt(@PathVariable Long deptId, @RequestBody Requests.DeptExtRequest request) {
+        systemService.saveDeptExt(deptId, request.getResponsibleXNo());
+        return Result.ok();
+    }
+
     @GetMapping("/login-logs")
     public Result<Page<SysLoginLog>> loginLogs(@RequestParam(defaultValue = "1") int current,
                                                 @RequestParam(defaultValue = "20") int size,
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
index 7e6b6fa..bd2bd90 100644
--- 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
@@ -84,10 +84,11 @@
         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/organization", def("system:organization:query", "\u67e5\u8be2"), def("system:organization:ext", "\u6269\u5c55\u5c5e\u6027\u8bbe\u7f6e"));
         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/users", def("system:users:query", "\u67e5\u8be2"), def("system:users:assign", "\u89d2\u8272\u6388\u6743"), def("system:users:ext", "\u6269\u5c55\u5c5e\u6027\u8bbe\u7f6e"));
         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"), def("system:roles:permission", "\u6743\u9650\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/login-logs", def("system:login-logs:query", "\u67e5\u8be2"), def("system:login-logs:export", "\u5bfc\u51fa"));
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 1775f6e..e14467e 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
@@ -13,6 +13,7 @@
 import com.emergencyrepair.business.mapper.ProductNodeMapper;
 import com.emergencyrepair.business.mapper.TeamMajorMapper;
 import com.emergencyrepair.common.BusinessException;
+import com.emergencyrepair.common.audit.CurrentUserContext;
 import com.emergencyrepair.system.entity.SysMenu;
 import com.emergencyrepair.system.entity.SysErrorLog;
 import com.emergencyrepair.system.entity.SysLoginLog;
@@ -601,8 +602,10 @@
     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("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("ue.ENGINEERING_MAJOR, ue.TEAM_MAJOR, ue.RESPONSIBLE_X_NO, CASE WHEN ue.USER_ID IS NULL THEN 0 ELSE 1 END AS EXT_CONFIGURED ");
         sql.append("FROM CSICZB_ZB.SYS_USER u LEFT JOIN CSICZB_ZB.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID ");
+        sql.append("LEFT JOIN LX.T_SYS_USER_EXT ue ON ue.USER_ID = u.USER_ID AND (ue.DEL_FLAG = 0 OR ue.DEL_FLAG IS NULL) ");
         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 ?) ");
@@ -631,6 +634,10 @@
             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("engineeringMajor", rs.getString("ENGINEERING_MAJOR"));
+            row.put("teamMajor", rs.getString("TEAM_MAJOR"));
+            row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+            row.put("extConfigured", rs.getInt("EXT_CONFIGURED") == 1);
             row.put("roles", new ArrayList<Map<String, Object>>());
             return row;
         });
@@ -675,7 +682,11 @@
 
     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",
+                "SELECT d.DEPT_ID, d.DEPT_NAME, d.PARENT_ID, d.ORDER_NUM, d.DEPT_TYPE, d.LEADER, d.PHONE, d.STATUS, d.DEPT_CODE, "
+                        + "e.RESPONSIBLE_X_NO, CASE WHEN e.DEPT_ID IS NULL THEN 0 ELSE 1 END AS EXT_CONFIGURED "
+                        + "FROM CSICZB_ZB.SYS_DEPT d "
+                        + "LEFT JOIN LX.T_SYS_DEPT_EXT e ON e.DEPT_ID = d.DEPT_ID AND (e.DEL_FLAG = 0 OR e.DEL_FLAG IS NULL) "
+                        + "WHERE (d.DEL_FLAG = '0' OR d.DEL_FLAG IS NULL) ORDER BY d.ORDER_NUM, d.DEPT_ID",
                 (rs, rowNum) -> {
                     Map<String, Object> row = new LinkedHashMap<>();
                     row.put("id", String.valueOf(rs.getLong("DEPT_ID")));
@@ -688,10 +699,145 @@
                     row.put("phone", rs.getString("PHONE"));
                     row.put("status", rs.getString("STATUS"));
                     row.put("deptCode", rs.getString("DEPT_CODE"));
+                    row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+                    row.put("extConfigured", rs.getInt("EXT_CONFIGURED") == 1);
                     return row;
                 });
     }
 
+    public Map<String, Object> deptExt(Long deptId) {
+        List<Map<String, Object>> rows = jdbcTemplate.query(
+                "SELECT d.DEPT_NAME, e.RESPONSIBLE_X_NO FROM CSICZB_ZB.SYS_DEPT d "
+                        + "LEFT JOIN LX.T_SYS_DEPT_EXT e ON e.DEPT_ID = d.DEPT_ID AND (e.DEL_FLAG = 0 OR e.DEL_FLAG IS NULL) "
+                        + "WHERE d.DEPT_ID = ? AND (d.DEL_FLAG = '0' OR d.DEL_FLAG IS NULL)",
+                new Object[]{deptId}, (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("deptId", String.valueOf(deptId));
+                    row.put("deptName", rs.getString("DEPT_NAME"));
+                    row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+                    row.put("configured", StringUtils.hasText(rs.getString("RESPONSIBLE_X_NO")));
+                    return row;
+                });
+        if (rows.isEmpty()) {
+            throw new BusinessException("閮ㄩ棬涓嶅瓨鍦�");
+        }
+        return rows.get(0);
+    }
+
+    @Transactional
+    public void saveDeptExt(Long deptId, String responsibleXNo) {
+        assertExternalDept(deptId);
+        String xNo = normalizeExtValue(responsibleXNo);
+        assertKnownXNo(xNo);
+        String username = CurrentUserContext.get();
+        LocalDateTime now = LocalDateTime.now();
+        int delFlag = xNo == null ? 1 : 0;
+        int updated = jdbcTemplate.update(
+                "UPDATE LX.T_SYS_DEPT_EXT SET RESPONSIBLE_X_NO = ?, DEL_FLAG = ?, UPDATE_BY = ?, UPDATE_TIME = ? WHERE DEPT_ID = ?",
+                xNo, delFlag, username, now, deptId);
+        if (updated == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO LX.T_SYS_DEPT_EXT (DEPT_ID, RESPONSIBLE_X_NO, CREATE_BY, UPDATE_BY, DEL_FLAG, CREATE_TIME, UPDATE_TIME) VALUES (?, ?, ?, ?, ?, ?, ?)",
+                    deptId, xNo, username, username, delFlag, now, now);
+        }
+    }
+
+    public Map<String, Object> userExt(Long userId) {
+        List<Map<String, Object>> rows = jdbcTemplate.query(
+                "SELECT u.USER_NAME, u.NICK_NAME, e.ENGINEERING_MAJOR, e.TEAM_MAJOR, e.RESPONSIBLE_X_NO "
+                        + "FROM CSICZB_ZB.SYS_USER u "
+                        + "LEFT JOIN LX.T_SYS_USER_EXT e ON e.USER_ID = u.USER_ID AND (e.DEL_FLAG = 0 OR e.DEL_FLAG IS NULL) "
+                        + "WHERE u.USER_ID = ? AND (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL)",
+                new Object[]{userId}, (rs, rowNum) -> {
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    row.put("userId", String.valueOf(userId));
+                    row.put("userName", rs.getString("USER_NAME"));
+                    row.put("nickName", rs.getString("NICK_NAME"));
+                    row.put("engineeringMajor", rs.getString("ENGINEERING_MAJOR"));
+                    row.put("teamMajor", rs.getString("TEAM_MAJOR"));
+                    row.put("responsibleXNo", rs.getString("RESPONSIBLE_X_NO"));
+                    boolean configured = StringUtils.hasText(rs.getString("ENGINEERING_MAJOR"))
+                            || StringUtils.hasText(rs.getString("TEAM_MAJOR"))
+                            || StringUtils.hasText(rs.getString("RESPONSIBLE_X_NO"));
+                    row.put("configured", configured);
+                    return row;
+                });
+        if (rows.isEmpty()) {
+            throw new BusinessException("鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        return rows.get(0);
+    }
+
+    @Transactional
+    public void saveUserExt(Long userId, String engineeringMajor, String teamMajor, String responsibleXNo) {
+        assertExternalUser(userId);
+        String engineering = normalizeExtValue(engineeringMajor);
+        String team = normalizeExtValue(teamMajor);
+        String xNo = normalizeExtValue(responsibleXNo);
+        assertKnownEngineeringMajor(engineering);
+        assertKnownTeamMajor(team);
+        assertKnownXNo(xNo);
+        boolean configured = engineering != null || team != null || xNo != null;
+        String username = CurrentUserContext.get();
+        LocalDateTime now = LocalDateTime.now();
+        int updated = jdbcTemplate.update(
+                "UPDATE LX.T_SYS_USER_EXT SET ENGINEERING_MAJOR = ?, TEAM_MAJOR = ?, RESPONSIBLE_X_NO = ?, DEL_FLAG = ?, UPDATE_BY = ?, UPDATE_TIME = ? WHERE USER_ID = ?",
+                engineering, team, xNo, configured ? 0 : 1, username, now, userId);
+        if (updated == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO LX.T_SYS_USER_EXT (USER_ID, ENGINEERING_MAJOR, TEAM_MAJOR, RESPONSIBLE_X_NO, CREATE_BY, UPDATE_BY, DEL_FLAG, CREATE_TIME, UPDATE_TIME) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
+                    userId, engineering, team, xNo, username, username, configured ? 0 : 1, now, now);
+        }
+    }
+
+    private void assertExternalDept(Long deptId) {
+        if (deptId == null) {
+            throw new BusinessException("璇烽�夋嫨閮ㄩ棬");
+        }
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM CSICZB_ZB.SYS_DEPT WHERE DEPT_ID = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+                Integer.class, deptId);
+        if (count == null || count == 0) {
+            throw new BusinessException("閮ㄩ棬涓嶅瓨鍦�");
+        }
+    }
+
+    private void assertExternalUser(Long userId) {
+        if (userId == null) {
+            throw new BusinessException("璇烽�夋嫨鐢ㄦ埛");
+        }
+        Integer count = 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 (count == null || count == 0) {
+            throw new BusinessException("鐢ㄦ埛涓嶅瓨鍦�");
+        }
+    }
+
+    private void assertKnownXNo(String xNo) {
+        if (xNo != null && !productXNumbers().contains(xNo)) {
+            throw new BusinessException("rk_X鍙峰繀椤讳粠浜у搧缁撴瀯鏍戠殑side鑺傜偣涓�夋嫨");
+        }
+    }
+
+    private void assertKnownEngineeringMajor(String value) {
+        if (value != null && listEngineeringMajors().stream().map(EngineeringMajor::getName)
+                .filter(StringUtils::hasText).noneMatch(item -> value.equals(item.trim()))) {
+            throw new BusinessException("宸ョ▼涓撲笟涓嶅湪鍙�夎寖鍥村唴");
+        }
+    }
+
+    private void assertKnownTeamMajor(String value) {
+        if (value != null && listTeamMajors().stream().map(TeamMajor::getName)
+                .filter(StringUtils::hasText).noneMatch(item -> value.equals(item.trim()))) {
+            throw new BusinessException("rk_T闃熶笓涓氫笉鍦ㄥ彲閫夎寖鍥村唴");
+        }
+    }
+
+    private String normalizeExtValue(String value) {
+        return StringUtils.hasText(value) ? value.trim() : null;
+    }
+
     public List<String> userRoleIds(Long userId) {
         if (userId == null) {
             return new ArrayList<>();
diff --git a/emergency-repair-web/src/views/Login.vue b/emergency-repair-web/src/views/Login.vue
index a12939a..6e4b37f 100644
--- a/emergency-repair-web/src/views/Login.vue
+++ b/emergency-repair-web/src/views/Login.vue
@@ -48,6 +48,12 @@
           sessionStorage.setItem('username', res.data.username)
           sessionStorage.setItem('realName', res.data.realName)
           sessionStorage.setItem('deptName', res.data.deptName || '')
+          sessionStorage.setItem('userId', res.data.userId || '')
+          sessionStorage.setItem('deptId', res.data.deptId || '')
+          sessionStorage.setItem('engineeringMajor', res.data.engineeringMajor || '')
+          sessionStorage.setItem('teamMajor', res.data.teamMajor || '')
+          sessionStorage.setItem('deptResponsibleXNo', res.data.deptResponsibleXNo || '')
+          sessionStorage.setItem('responsibleXNo', res.data.responsibleXNo || '')
           sessionStorage.setItem('roles', JSON.stringify(res.data.roles || []))
           sessionStorage.setItem('permissions', JSON.stringify(res.data.permissions || []))
           this.$router.push('/dashboard')
diff --git a/emergency-repair-web/src/views/fault/FaultReport.vue b/emergency-repair-web/src/views/fault/FaultReport.vue
index 3b06b6b..778314b 100644
--- a/emergency-repair-web/src/views/fault/FaultReport.vue
+++ b/emergency-repair-web/src/views/fault/FaultReport.vue
@@ -193,22 +193,24 @@
     taskMeta(value) { return statusMeta(taskStatuses, value) },
     yesNo(value) { return Number(value) === 1 ? '\u662f' : '\u5426' },
     loadAccountInfo() {
-      if (sessionStorage.getItem('deptName') !== null) return
-      api.get('/auth/me').then(res => {
+      return api.get('/auth/me').then(res => {
         const info = res.data || {}
         sessionStorage.setItem('realName', info.realName || sessionStorage.getItem('realName') || '')
         sessionStorage.setItem('deptName', info.deptName || '')
-        if (this.form && !this.form.id && !this.form.reportUnit) this.form.reportUnit = info.deptName || ''
+        sessionStorage.setItem('userId', info.userId || '')
+        sessionStorage.setItem('deptId', info.deptId || '')
+        sessionStorage.setItem('engineeringMajor', info.engineeringMajor || '')
+        sessionStorage.setItem('teamMajor', info.teamMajor || '')
+        sessionStorage.setItem('deptResponsibleXNo', info.deptResponsibleXNo || '')
+        sessionStorage.setItem('responsibleXNo', info.responsibleXNo || '')
       })
     },
     loadMajors() {
       api.get('/system/team-majors').then(res => {
         this.teamMajors = res.data || []
-        if (!this.form.teamMajor && this.teamMajors.length) this.form.teamMajor = this.teamMajors[0].name
       })
       api.get('/system/engineering-majors').then(res => {
         this.engineeringMajors = res.data || []
-        if (!this.form.engineeringMajor && this.engineeringMajors.length) this.form.engineeringMajor = this.engineeringMajors[0].name
       })
     },
     loadXNumbers() {
@@ -270,10 +272,10 @@
         faultCode: '',
         reportUnit: sessionStorage.getItem('deptName') || '',
         reportDate: today,
-        xNo: '',
-        owner: sessionStorage.getItem('realName') || 'rk_T鍛�',
-        teamMajor: (this.teamMajors[0] || {}).name || '',
-        engineeringMajor: (this.engineeringMajors[0] || {}).name || '',
+        xNo: sessionStorage.getItem('deptResponsibleXNo') || '',
+        owner: sessionStorage.getItem('realName') || '',
+        teamMajor: sessionStorage.getItem('teamMajor') || '',
+        engineeringMajor: sessionStorage.getItem('engineeringMajor') || '',
         equipmentIds: '',
         equipmentName: '',
         parentSystem: '',
@@ -290,7 +292,10 @@
         remark: ''
       }
     },
-    openCreate() {
+    async openCreate() {
+      try {
+        await this.loadAccountInfo()
+      } catch (e) {}
       this.form = this.blankForm()
       this.dialogVisible = true
     },
diff --git a/emergency-repair-web/src/views/system/OrganizationManage.vue b/emergency-repair-web/src/views/system/OrganizationManage.vue
index 519eb3c..0b455e3 100644
--- a/emergency-repair-web/src/views/system/OrganizationManage.vue
+++ b/emergency-repair-web/src/views/system/OrganizationManage.vue
@@ -39,14 +39,40 @@
         </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="responsibleXNo" label="璐熻矗rk_T" width="110" align="center">
+          <template slot-scope="{ row }">
+            <el-tag v-if="row.responsibleXNo" size="mini" type="success">{{ row.responsibleXNo }}</el-tag>
+            <span v-else class="muted">鏈缃�</span>
+          </template>
+        </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-column label="鎿嶄綔" width="120" align="center" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button type="text" icon="el-icon-setting" v-permission="'system:organization:ext'" @click="openExt(row)">鎵╁睍灞炴��</el-button>
+          </template>
+        </el-table-column>
       </el-table>
     </div>
+
+    <el-dialog :title="'鎵╁睍灞炴�ц缃� - ' + (currentDept.name || '')" :visible.sync="extVisible" width="520px" append-to-body :close-on-click-modal="false">
+      <el-form label-width="105px" v-loading="extLoading">
+        <el-form-item label="璐熻矗rk_T">
+          <el-select v-model="extForm.responsibleXNo" clearable filterable placeholder="璇烽�夋嫨rk_X鍙�" style="width:100%">
+            <el-option v-for="item in xNumbers" :key="item" :label="item" :value="item"></el-option>
+          </el-select>
+          <div class="field-tip">璇ュ�兼柊澧炴晠闅滄椂浣滀负rk_X鍙烽粯璁ゅ�硷紝浠ラ儴闂ㄨ缃负鍑嗐��</div>
+        </el-form-item>
+      </el-form>
+      <template slot="footer">
+        <el-button @click="extVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" :loading="extSaving" @click="saveExt">淇濆瓨</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 <script>
@@ -58,7 +84,13 @@
       loading: false,
       rows: [],
       keyword: '',
-      deptType: ''
+      deptType: '',
+      extVisible: false,
+      extLoading: false,
+      extSaving: false,
+      currentDept: {},
+      extForm: { responsibleXNo: '' },
+      xNumbers: []
     }
   },
   computed: {
@@ -91,13 +123,35 @@
       return walk(this.tree)
     }
   },
-  created() { this.load() },
+  created() { this.load(); this.loadXNumbers() },
   methods: {
     load() {
       this.loading = true
       api.get('/system/depts').then(res => {
         this.rows = res.data || []
       }).finally(() => { this.loading = false })
+    },
+    loadXNumbers() {
+      api.get('/system/tree/product/x-numbers').then(res => { this.xNumbers = res.data || [] })
+    },
+    openExt(row) {
+      this.currentDept = { ...row }
+      this.extForm = { responsibleXNo: row.responsibleXNo || '' }
+      this.extVisible = true
+      this.extLoading = true
+      api.get('/system/depts/' + row.id + '/ext').then(res => {
+        const data = res.data || {}
+        this.extForm = { responsibleXNo: data.responsibleXNo || '' }
+      }).finally(() => { this.extLoading = false })
+    },
+    saveExt() {
+      if (!this.currentDept.id) return
+      this.extSaving = true
+      api.put('/system/depts/' + this.currentDept.id + '/ext', this.extForm).then(() => {
+        this.$message.success('鎵╁睍灞炴�ц缃垚鍔�')
+        this.extVisible = false
+        this.load()
+      }).finally(() => { this.extSaving = false })
     },
     buildTree(rows) {
       const nodes = rows.map(row => ({ ...row, children: [] }))
@@ -124,4 +178,6 @@
 </script>
 <style scoped>
 .summary { color: #6d7f91; font-size: 13px; }
+.muted { color: #a3afbc; font-size: 12px; }
+.field-tip { margin-top: 4px; color: #7b8a99; font-size: 12px; line-height: 18px; }
 </style>
diff --git a/emergency-repair-web/src/views/system/UserManage.vue b/emergency-repair-web/src/views/system/UserManage.vue
index 2e3c9a5..0cc4acf 100644
--- a/emergency-repair-web/src/views/system/UserManage.vue
+++ b/emergency-repair-web/src/views/system/UserManage.vue
@@ -39,6 +39,11 @@
             </template>
           </el-table-column>
           <el-table-column prop="phoneNumber" label="鎵嬫満鍙�" width="130"></el-table-column>
+          <el-table-column label="鎵╁睍灞炴��" width="100" align="center">
+            <template slot-scope="{ row }">
+              <el-button type="text" v-permission="'system:users:ext'" @click="openExt(row)">{{ row.extConfigured ? '缂栬緫' : '璁剧疆' }}</el-button>
+            </template>
+          </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>
@@ -99,6 +104,29 @@
           <el-button type="primary" :loading="batchSaving" v-permission="'system:users:assign'" @click="saveBatchRoles">淇濆瓨</el-button>
         </span>
       </el-dialog>
+      <el-dialog :title="'鐢ㄦ埛鎵╁睍灞炴�ц缃� - ' + ((currentExtUser.nickName || currentExtUser.userName) || '')" :visible.sync="extVisible" width="560px" append-to-body :close-on-click-modal="false">
+        <el-form label-width="105px" v-loading="extLoading">
+          <el-form-item label="宸ョ▼涓撲笟">
+            <el-select v-model="extForm.engineeringMajor" clearable filterable placeholder="璇烽�夋嫨宸ョ▼涓撲笟" 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-form-item label="rk_T闃熶笓涓�">
+            <el-select v-model="extForm.teamMajor" clearable filterable placeholder="璇烽�夋嫨rk_T闃熶笓涓�" 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-form-item label="璐熻矗rk_T">
+            <el-select v-model="extForm.responsibleXNo" clearable 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-form>
+        <template slot="footer">
+          <el-button @click="extVisible = false">鍙栨秷</el-button>
+          <el-button type="primary" :loading="extSaving" @click="saveExt">淇濆瓨</el-button>
+        </template>
+      </el-dialog>
   </div>
 </template>
 <script>
@@ -122,12 +150,21 @@
       roles: [],
       selected: null,
       checkedRoleIds: [],
+      extVisible: false,
+      extLoading: false,
+      extSaving: false,
+      currentExtUser: {},
+      extForm: { engineeringMajor: '', teamMajor: '', responsibleXNo: '' },
+      engineeringMajors: [],
+      teamMajors: [],
+      xNumbers: [],
       filters: { keyword: '', deptId: null, roleId: null }
     }
   },
   created() {
     this.loadRoles()
     this.loadDepts()
+    this.loadExtOptions()
     this.load()
   },
   methods: {
@@ -159,6 +196,34 @@
     loadDepts() {
       api.get('/system/depts').then(res => { this.depts = res.data || [] })
     },
+    loadExtOptions() {
+      api.get('/system/engineering-majors').then(res => { this.engineeringMajors = res.data || [] })
+      api.get('/system/team-majors').then(res => { this.teamMajors = res.data || [] })
+      api.get('/system/tree/product/x-numbers').then(res => { this.xNumbers = res.data || [] })
+    },
+    openExt(row) {
+      this.currentExtUser = { ...row }
+      this.extForm = { engineeringMajor: '', teamMajor: '', responsibleXNo: '' }
+      this.extVisible = true
+      this.extLoading = true
+      api.get('/system/users/' + row.userId + '/ext').then(res => {
+        const data = res.data || {}
+        this.extForm = {
+          engineeringMajor: data.engineeringMajor || '',
+          teamMajor: data.teamMajor || '',
+          responsibleXNo: data.responsibleXNo || ''
+        }
+      }).finally(() => { this.extLoading = false })
+    },
+    saveExt() {
+      if (!this.currentExtUser.userId) return
+      this.extSaving = true
+      api.put('/system/users/' + this.currentExtUser.userId + '/ext', this.extForm).then(() => {
+        this.$message.success('鎵╁睍灞炴�ц缃垚鍔�')
+        this.extVisible = false
+        this.load()
+      }).finally(() => { this.extSaving = false })
+    },
     search() {
       this.current = 1
       this.load()
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"
index fc0995c..b1698db 100644
--- "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"
@@ -735,3 +735,57 @@
 - 娓呯┖鍓嶏細鏁呴殰 `23`銆佹淳宸ュ崟 `8`銆佸畬宸ヤ俊鎭� `6`銆侀獙鏀� `6`锛涙竻绌哄悗鍧� `0`銆�
 - 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涙柊 jar 閲嶅惎鍚庡洓琛ㄤ粛鍧囦负 `0`銆�
 - 褰撳墠鍚庣 PID `21528`锛岀洃鍚� `8091`锛涘墠绔� PID `57312`锛岀洃鍚� `8082`锛涘悗绔敊璇棩蹇椾负绌恒��
+
+## 浜屽崄銆�2026-10-07 鑱旇皟鏀跺熬锛堥渶姹� 28-30锛�
+
+| 搴忓彿 | 闇�姹� | 鐘舵�� | 瀹炵幇涓庨獙璇� |
+| --- | --- | --- | --- |
+| 28 | 缁勭粐鏈烘瀯澧炲姞鎵╁睍灞炴�ц缃紝璁剧疆閮ㄩ棬璐熻矗rk_T | 宸插畬鎴� | 缁勭粐鏈烘瀯鍒楄〃澧炲姞鈥滄墿灞曞睘鎬р�濇寜閽拰寮圭獥锛屼粠浜у搧缁撴瀯鏍� `side` 鑺傜偣涓崟閫� `rk_X鍙穈锛屼繚瀛樺埌 `LX.T_SYS_DEPT_EXT`锛屼笉淇敼澶栭儴 `SYS_DEPT` |
+| 29 | 鐢ㄦ埛绠$悊澧炲姞鎵╁睍灞炴�ц缃� | 宸插畬鎴� | 鍙缃伐绋嬩笓涓氥�乺k_T闃熶笓涓氥�佽礋璐k_T锛屼繚瀛樺埌 `LX.T_SYS_USER_EXT`锛屼笉淇敼澶栭儴 `SYS_USER`锛況k_TD涓撲笟涓庣幇鏈� `teamMajor` 涓哄悓涓�瀛楁 |
+| 30 | 鏂板鏁呴殰榛樿鍊� | 宸插畬鎴� | 涓婃姤鍗曚綅鍙栫櫥褰曠敤鎴峰崟浣嶏紝璐熻矗浜哄彇鐧诲綍鐢ㄦ埛锛宍rk_X鍙穈鍙彇閮ㄩ棬鎵╁睍灞炴�э紝宸ョ▼涓撲笟鍜宺k_T闃熶笓涓氬彇鐢ㄦ埛鎵╁睍灞炴�э紱鏈厤缃椂鐣欑┖锛屼笉鍐嶅彇鍒楄〃绗竴椤� |
+
+### 20.1 纭鍙e緞
+
+- 闇�姹� 30 缂栧彿姝g‘锛涙柊澧炴晠闅滈噷鐨勨�滆矗浠烩�濆氨鏄�滆礋璐d汉鈥濄��
+- 閮ㄩ棬鎵╁睍灞炴�р�滆礋璐k_T鈥濆氨鏄� `rk_X鍙穈锛屽崟閫夛紱鏂板鏁呴殰鐨� `rk_X鍙穈 鍙互閮ㄩ棬璁剧疆涓哄噯锛屽嵆浣跨敤鎴蜂篃璁剧疆浜嗏�滆礋璐k_T鈥濅篃涓嶅弬涓庤榛樿鍊笺��
+- `rk_TD涓撲笟`灏辨槸鐜版湁椤甸潰閲岀殑鈥渞k_T闃熶笓涓氣�濓紝鍚庣画鏆傜粺涓�娌跨敤鐜版湁鏄剧ず锛屽瓧娈典负 `teamMajor`銆�
+- 澶栭儴 `CSICZB_ZB.SYS_DEPT`銆乣CSICZB_ZB.SYS_USER` 鐢卞叾浠栫郴缁熺淮鎶わ紝鎵╁睍灞炴�у潎瀛樻湰鍦版墿灞曡〃銆�
+
+### 20.2 鎺ュ彛涓庢暟鎹〃
+
+- `LX.T_SYS_DEPT_EXT`锛歚DEPT_ID`銆乣RESPONSIBLE_X_NO`銆佸璁″瓧娈点��
+- `LX.T_SYS_USER_EXT`锛歚USER_ID`銆乣ENGINEERING_MAJOR`銆乣TEAM_MAJOR`銆乣RESPONSIBLE_X_NO`銆佸璁″瓧娈点��
+- `GET/PUT /api/system/depts/{deptId}/ext`锛氭煡璇€�佷繚瀛橀儴闂ㄦ墿灞曞睘鎬с��
+- `GET/PUT /api/system/users/{userId}/ext`锛氭煡璇€�佷繚瀛樼敤鎴锋墿灞曞睘鎬с��
+- `/api/auth/login`銆乣/api/auth/me`杩斿洖鐢ㄦ埛銆侀儴闂ㄥ拰涓撲笟鎵╁睍淇℃伅锛屼緵鏂板鏁呴殰榛樿鍊间娇鐢ㄣ��
+- 鏂板鎸夐挳鏉冮檺 `system:organization:ext`銆乣system:users:ext`銆�
+
+### 20.3 涓昏鍙樻洿鏂囦欢
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/auth/AuthController.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/system/controller/SystemController.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java`
+- `emergency-repair-web/src/views/Login.vue`
+- `emergency-repair-web/src/views/fault/FaultReport.vue`
+- `emergency-repair-web/src/views/system/OrganizationManage.vue`
+- `emergency-repair-web/src/views/system/UserManage.vue`
+- `requirement/鏀硅繘20261003.txt`銆乣HANDOFF.md`锛堣ˉ鍏呴渶姹� 28-30 鍜屼氦鎺ヨ褰曪級
+
+### 20.4 楠岃瘉缁撴灉
+
+- 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃锛屼粎鏈夋棦鏈夊寘浣撶Н鍛婅銆�
+- `node .codex-run/verify-req28-30.js` 杩斿洖 `pass: true`銆�
+- 閮ㄩ棬 `sy鍖哄煙` 璁剧疆 `rk_X鍙�=201`锛涚敤鎴� `yszl` 璁剧疆宸ョ▼涓撲笟 `鍔ㄥ姏涓撲笟`銆乺k_T闃熶笓涓� `娑插帇涓撲笟`銆佽礋璐� `202`銆�
+- 鏂板鏁呴殰榛樿鍊煎疄娴嬶細涓婃姤鍗曚綅 `sy鍖哄煙`銆佽礋璐d汉 `鍔╃悊鐢ㄦ埛锛堟紨绀猴級`銆乣rk_X鍙�=201`銆乺k_T闃熶笓涓� `娑插帇涓撲笟`銆佸伐绋嬩笓涓� `鍔ㄥ姏涓撲笟`銆�
+- 鎴浘锛歚.codex-run/req28-org-ext.png`銆乣.codex-run/req29-user-ext.png`銆乣.codex-run/req30-fault-defaults.png`銆�
+- 楠岃瘉鍚庡凡娓呯┖涓存椂鎵╁睍灞炴�э紝鏌ヨ纭 `configured=false` 涓斿瓧娈典负绌恒��
+
+### 20.5 鎺ユ墜娉ㄦ剰
+
+- 褰撳墠鍚庣 PID `56124`锛岀洃鍚� `http://127.0.0.1:8091`锛涘墠绔� PID `57312`锛岀洃鍚� `http://localhost:8082`锛汥M8 `127.0.0.1:5236`锛汦dge CDP `9222`銆傜櫥褰� `admin / admin123`銆�
+- 寮�鍙戠幆澧冩晱鎰熻瘝浼氭妸 `rk_T` 鏄剧ず鎴愨�滄鈥濓紝鎴浘涓殑鈥滆礋璐f鈥濆搴斾唬鐮�/鍚庣瀛楁 `rk_T`銆�
+- `.codex-run`銆丱ffice 閿佹枃浠跺強 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 浠嶄负鏈湴涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
+- 鏈疆浠g爜銆侀渶姹傛枃浠跺拰浜ゆ帴璁板綍涓�骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
diff --git "a/requirement/\346\224\271\350\277\23320261003.txt" "b/requirement/\346\224\271\350\277\23320261003.txt"
index ea44d46..c22cfd3 100644
--- "a/requirement/\346\224\271\350\277\23320261003.txt"
+++ "b/requirement/\346\224\271\350\277\23320261003.txt"
@@ -40,3 +40,13 @@
 26銆佹棩蹇楃鐞嗙殑鏃ユ湡缁勪欢2涓棩鏈熶腑闂撮偅涓�滆嚦鈥濇樉绀轰笉瀹屾暣
 27銆佹柊澧炴晠闅滈偅閲岄�変簡rk_X鍙凤紙瀵瑰簲浜у搧缁撴瀯鏍戠殑side绫诲瀷锛夊悗锛屽啀閫夎澶囩殑鏃跺�欒繕鏄樉绀烘暣涓骇鍝佺粨鏋勬爲锛屽簲璇ユ樉绀洪攣閫夎妭鐐圭殑涓嬬骇鑺傜偣
 
+28锛岀粍缁囨満鏋勶紙閮ㄩ棬鏍戣〃锛夛紝澧炲姞鎵╁睍灞炴�ц缃紝鐩墠涓昏鍖呮嫭璐熻矗rk_T
+29锛岀敤鎴风鐞嗭紝澧炲姞鎵╁睍灞炴�ц缃紝鐩墠涓昏鏈夊伐绋嬩笓涓氥�乺k_TD涓撲笟锛岃礋璐k_T
+30銆佹柊澧炴晠闅滐紝榛樿鍊硷紝鏁呴殰涓婃姤鍗曚綅榛樿濉啓褰撳墠鐧诲綍浜虹殑鍗曚綅锛岃矗浠婚粯璁ゅ~鍐欏綋鍓嶇櫥褰曚汉锛宺k_X鍙烽粯璁ゅ~鍐欏綋鍓嶇櫥褰曚汉鐨勫崟浣嶇殑鎵╁紶灞炴�ц礋璐k_T锛屽伐绋嬩笓涓氥�乺k_TD涓撲笟鍒嗗埆涓哄綋鍓嶇敤鎴风殑鎵╁睍灞炴�у伐绋嬩笓涓氥�乺k_TD涓撲笟
+
+
+
+绗笁鏉″簲璇ョ紪鍙蜂负 30 鍚э紵鍙﹀鈥滆矗浠烩�濇槸鍚﹀氨鏄�滆礋璐d汉鈥濓紵鏄殑
+鈥滆礋璐k_T鈥濆叿浣撴槸閫� rk_X鍙凤紝杩樻槸閫夋煇涓粍缁�/涓撲笟锛熷崟閫夎繕鏄閫夛紵濡傛灉閮ㄩ棬鍜岀敤鎴烽兘璁剧疆浜嗭紝rk_X鍙锋槸鍚﹀彧浠ラ儴闂ㄨ缃负鍑嗭紵閫� rk_X鍙凤紝瑕佹眰鎵╁睍灞炴�у拰鏁呴殰琛ㄩ噷瀛樼殑涓�鑷达紝鍗曢�夛紝閫夐儴闂ㄧ殑
+浣犺鐨勨�渞k_TD涓撲笟鈥濇槸鍚﹀氨鏄幇鏈夐〉闈㈤噷鐨勨�渞k_T闃熶笓涓氣�濓紵鍚庣画缁熶竴鏄剧ず鍝竴涓悕绉帮紵鏄殑
+鎵╁睍灞炴�у缓璁柊寤烘湰鍦版墿灞曡〃淇濆瓨锛屼緥濡傞儴闂ㄦ墿灞曡〃鍜岀敤鎴锋墿灞曡〃锛屼笉鐩存帴淇敼澶栭儴鏉ユ簮鐨� SYS_DEPT銆丼YS_USER 琛ㄣ�傛槸鍚︽寜杩欎釜鏂瑰紡瀹炵幇锛熸槸鐨勶紝鍥犱负閭�2涓〃鏄埆鐨勭郴缁熺淮鎶ょ殑

--
Gitblit v1.9.1