From 0642bc9c44d1c8277ada33059c4a3e6156335b30 Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期二, 06 十月 2026 14:48:37 +0800
Subject: [PATCH] feat: use role-only menu permissions

---
 HANDOFF.md                                                                                        |   27 +++++
 emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java |   12 -
 requirement/交接文件_2026.10.3.md                                                                     |   30 ++++++
 emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java              |    2 
 emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java       |   50 ++-------
 emergency-repair-web/src/views/system/MenuPermission.vue                                          |  144 ++++++++++++++++++----------
 6 files changed, 165 insertions(+), 100 deletions(-)

diff --git a/HANDOFF.md b/HANDOFF.md
index 99d1ccf..d7093f3 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -726,3 +726,30 @@
 - 鍘嗗彶婕旂ず娲惧伐鍗曞彲鑳戒繚鐣欐棫 `T_CONTRACTOR` 鍚嶇О锛岄潪绠$悊鍛� cj 鐢ㄦ埛鍙兘鐪嬩笉鍒拌繖浜涘巻鍙插崟锛沗admin` 涓嶅彈鍗曚綅杩囨护褰卞搷銆�
 - `.codex-run`銆丱ffice 閿佹枃浠� `requirement\~$鍘熷闇�姹�.docx` 鍜� `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 灞炴湰鍦拌仈璋冩垨涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
 - 鏈疆浠g爜宸叉彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮锛屾彁浜� `757dcb3`锛涗氦鎺ユ枃浠跺洖濉彁浜よ褰曘�傚悗缁惎鍔ㄣ�佹瀯寤哄拰鎺掗敊鍛戒护娌跨敤绗� 16 鑺傘��
+
+## 18. 鏀跺熬蹇収锛�2026-10-06 鍛ㄤ竴锛岄渶姹� 20 鑿滃崟鏉冮檺绠$悊鏀跺熬锛�
+
+**鏈疆瀹屾垚鍐呭**
+- 闇�姹� 20锛氳彍鍗曟潈闄愮鐞嗗彇娑堢鎴锋潈闄愬拰瑙掕壊鏉冮檺鐨勫弻閲嶆巿鏉冿紝鏀逛负鍗曟璁剧疆瑙掕壊鏉冮檺銆傚綋鍓嶇敤鎴锋湰韬病鏈夌鎴峰綊灞烇紝鍥犳涓嶅啀璁剧疆绉熸埛鏉冮檺銆�
+- 鏉冮檺鏍戠户缁睍绀鸿彍鍗曡妭鐐瑰拰鎸夐挳鑺傜偣锛屼笉鎸� `menuType` 杩囨护锛涘垏鎹㈣鑹插悗锛屽凡淇濆瓨鏉冮檺澶嶉�夋姝e父鍙嶆樉銆�
+- 鑿滃崟鏉冮檺鎺ュ彛鍙繚鐣� `roleId` / `roleMenuIds`锛屽垹闄ょ鎴疯彍鍗曟巿鏉冨強 `/api/system/tenants`銆�
+- 淇闆姳 ID 缁忔祻瑙堝櫒 JSON 瑙f瀽鍚庣簿搴︿涪澶辩殑闂锛氬悗绔繑鍥炲瓧绗︿覆 ID锛屽墠绔�変腑鎬佹寜瀛楃涓插鐞嗐��
+
+**涓昏鏂囦欢**
+- 鍚庣锛歚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/SystemService.java`銆�
+- 鍓嶇锛歚emergency-repair-web/src/views/system/MenuPermission.vue`銆�
+
+**楠岃瘉缁撴灉**
+- 鍚庣 `mvn -B -DskipTests compile` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃銆�
+- 鎺ュ彛锛歚GET /api/system/menu-permissions?roleId=2106221661022490625` 杩斿洖瀛楃涓插瀷 `roleMenuIds` 鍏� 14 鏉°��
+- 娴忚鍣細`node .codex-run/verify-menu-permission.js` 杈撳嚭 `hasTenantPanel: false`銆乣buttonNodeCount: 76`銆乣checkedMatchesSaved: true`銆乣hasError: false`锛涙埅鍥� `.codex-run/menu-permission-verified.png`銆�
+- 椤甸潰纭鎸夐挳鑺傜偣淇濈暀锛屽垏鎹㈣鑹插悗鏉冮檺鍕鹃�夊彲姝e父鍙嶆樉銆�
+
+**褰撳墠杩愯鐘舵�侊紙2026-10-06锛�**
+- 鍓嶇 `http://localhost:8082`锛屽悗绔� `http://127.0.0.1:8091`锛孌M8 `127.0.0.1:5236`锛孍dge CDP `9222`銆�
+- 鐧诲綍 `admin / admin123`锛涙暟鎹簱 `SYSDBA/SYSDBA`锛屼笟鍔� schema `LX`銆�
+- 鍚庣鏃ュ織锛歚.codex-run/backend-20261006-menu-permission-fixed.out.log`銆乣.codex-run/backend-20261006-menu-permission-fixed.err.log`銆�
+
+**鎺ユ墜娉ㄦ剰**
+- 涓嶈鎻愪氦 `.codex-run`銆丱ffice 閿佹枃浠舵垨 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt`銆�
+- 鏈疆浠g爜涓庝氦鎺ヨ褰曚竴骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
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 b17cfe3..321c475 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
@@ -108,9 +108,7 @@
     @Data
     public static class MenuPermissionRequest {
         private Long roleId;
-        private Long tenantId;
         private List<Long> roleMenuIds = new ArrayList<>();
-        private List<Long> tenantMenuIds = new ArrayList<>();
     }
     @Data
     public static class UserRoleRequest {
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 1a9e44f..c167be9 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
@@ -281,22 +281,16 @@
     }
 
     @GetMapping("/menu-permissions")
-    public Result<Map<String, Object>> menuPermissions(@RequestParam(required = false) Long roleId,
-                                                       @RequestParam(required = false) Long tenantId) {
-        return Result.ok(systemService.menuPermissions(roleId, tenantId));
+    public Result<Map<String, Object>> menuPermissions(@RequestParam(required = false) Long roleId) {
+        return Result.ok(systemService.menuPermissions(roleId));
     }
 
     @PostMapping("/menu-permissions")
     public Result<Void> saveMenuPermissions(@RequestBody Requests.MenuPermissionRequest request) {
-        systemService.saveMenuPermissions(request.getRoleId(), request.getTenantId(),
-                request.getRoleMenuIds(), request.getTenantMenuIds());
+        systemService.saveMenuPermissions(request.getRoleId(), request.getRoleMenuIds());
         return Result.ok();
     }
 
-    @GetMapping("/tenants")
-    public Result<List<Map<String, Object>>> tenants() {
-        return Result.ok(systemService.tenants());
-    }
 
     @GetMapping("/three-member")
     public Result<Map<String, Object>> threeMember() {
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 5c5b3e7..c2b4797 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
@@ -566,47 +566,32 @@
         baseEquipmentMapper.deleteById(id);
     }
 
-    public List<Map<String, Object>> tenants() {
-        return Collections.singletonList(row("id", 1L, "name", "涓存姠淇鎴�"));
-    }
-
-    public Map<String, Object> menuPermissions(Long roleId, Long tenantId) {
-        List<Long> roleMenuIds = roleMenuMapper.selectList(Wrappers.<SysRoleMenu>lambdaQuery()
-                        .eq(roleId != null, SysRoleMenu::getRoleId, roleId))
-                .stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
-        List<Long> tenantMenuIds = tenantMenuMapper.selectList(Wrappers.<SysTenantMenu>lambdaQuery()
-                        .eq(tenantId != null, SysTenantMenu::getTenantId, tenantId))
-                .stream().map(SysTenantMenu::getMenuId).collect(Collectors.toList());
-        List<Long> effective = roleMenuIds.stream().filter(tenantMenuIds::contains).collect(Collectors.toList());
+    public Map<String, Object> menuPermissions(Long roleId) {
+        if (roleId == null) {
+            throw new BusinessException("璇烽�夋嫨瑙掕壊");
+        }
+        List<String> roleMenuIds = roleMenuMapper.selectList(Wrappers.<SysRoleMenu>lambdaQuery()
+                        .eq(SysRoleMenu::getRoleId, roleId))
+                .stream().map(SysRoleMenu::getMenuId).map(String::valueOf).collect(Collectors.toList());
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("roleMenuIds", roleMenuIds);
-        result.put("tenantMenuIds", tenantMenuIds);
-        result.put("effectiveMenuIds", effective);
+        result.put("effectiveMenuIds", roleMenuIds);
         return result;
     }
 
     @Transactional
-    public void saveMenuPermissions(Long roleId, Long tenantId, List<Long> roleMenuIds, List<Long> tenantMenuIds) {
-        if (roleId == null || tenantId == null) {
-            throw new BusinessException("璇烽�夋嫨瑙掕壊鍜岀鎴�");
+    public void saveMenuPermissions(Long roleId, List<Long> roleMenuIds) {
+        if (roleId == null) {
+            throw new BusinessException("璇烽�夋嫨瑙掕壊");
         }
         roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getRoleId, roleId));
-        tenantMenuMapper.delete(Wrappers.<SysTenantMenu>lambdaQuery().eq(SysTenantMenu::getTenantId, tenantId));
         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 : effectiveTenantMenuIds) {
-            SysTenantMenu relation = new SysTenantMenu();
-            relation.setTenantId(tenantId);
-            relation.setMenuId(menuId);
-            relation.setCreateTime(LocalDateTime.now());
-            tenantMenuMapper.insert(relation);
         }
     }
 
@@ -888,18 +873,7 @@
         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);
-                }
-            }
-        }
+        result.addAll(roleMenuIds);
         return result;
     }
 
diff --git a/emergency-repair-web/src/views/system/MenuPermission.vue b/emergency-repair-web/src/views/system/MenuPermission.vue
index 347bd0a..e69de15 100644
--- a/emergency-repair-web/src/views/system/MenuPermission.vue
+++ b/emergency-repair-web/src/views/system/MenuPermission.vue
@@ -1,64 +1,63 @@
 <template>
   <div class="page">
     <div class="page-title">
-      <div><h2>鑿滃崟鏉冮檺绠$悊</h2><p>鍚屼竴鑿滃崟蹇呴』鍚屾椂鑾峰緱绉熸埛鎺堟潈鍜岃鑹叉巿鏉冿紝鐢ㄦ埛鎵嶈兘璁块棶锛涚涓�鐗� admin 鐩存帴鏄剧ず鍏ㄩ儴鑿滃崟</p></div>
+      <div><h2>鑿滃崟鏉冮檺绠$悊</h2><p>鎸夎鑹茶缃彍鍗曞拰鎸夐挳鏉冮檺锛屼繚瀛樺悗鍒囨崲瑙掕壊浼氳嚜鍔ㄥ弽鏄俱��</p></div>
     </div>
     <div class="toolbar">
-      <span>绉熸埛</span>
-      <el-select v-model="tenantId" style="width:220px" @change="loadPermissions"><el-option v-for="item in tenants" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
       <span>瑙掕壊</span>
-      <el-select v-model="roleId" style="width:220px" @change="loadPermissions"><el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
+      <el-select v-model="roleId" style="width:260px" @change="loadPermissions"><el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
       <div class="spacer"></div>
-      <el-tag type="success">鏈�缁堝彲鐢ㄨ彍鍗曪細{{ effectiveCount }} 涓�</el-tag>
-      <el-button type="primary" :loading="saving" @click="save">淇濆瓨鍙屾巿鏉�</el-button>
+      <el-tag type="success">宸查�夋潈闄愶細{{ selectedRoleIds.length }} 涓�</el-tag>
+      <el-button type="primary" :loading="saving" @click="save">淇濆瓨瑙掕壊鏉冮檺</el-button>
     </div>
-    <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 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 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>
-        <div class="intersection-list"><el-tag v-for="item in effectiveMenus" :key="item.id" size="small">{{ item.name }}</el-tag></div>
-      </section>
-    </div>
+    <section class="permission-panel">
+      <div class="panel-head"><h3>瑙掕壊鑿滃崟鍙婃寜閽潈闄�</h3><el-checkbox v-model="roleCheckAll" @change="checkAll">鍏ㄩ��</el-checkbox></div>
+      <el-tree ref="roleTree" :data="menus" node-key="id" show-checkbox default-expand-all :render-content="renderContent" :props="{ label: 'name', children: 'children' }" @check="syncCheckAllState"></el-tree>
+    </section>
   </div>
 </template>
 <script>
 import api from '@/api'
 export default {
   name: 'MenuPermission',
-  data() { return { menus: [], flatMenus: [], roles: [], tenants: [], tenantId: 1, roleId: null, saving: false, tenantCheckAll: false, roleCheckAll: false, selectedTenantIds: [], selectedRoleIds: [] } },
+  data() {
+    return {
+      menus: [],
+      flatMenus: [],
+      roles: [],
+      roleId: null,
+      saving: false,
+      roleCheckAll: false,
+      selectedRoleIds: []
+    }
+  },
   computed: {
-    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.map(String).includes(String(item.id)) && this.selectedRoleIds.map(String).includes(String(item.id))) }
+    allMenuIds() { return this.flatMenus.map(item => String(item.id)) }
   },
   created() { this.loadBase() },
   methods: {
     loadBase() {
-      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 || []
+      Promise.all([api.get('/system/menu/tree'), api.get('/system/menu/list'), api.get('/system/roles')]).then(([tree, flat, roles]) => {
+        this.menus = tree.data || []
+        this.flatMenus = flat.data || []
+        this.roles = roles.data || []
         if (!this.roleId && this.roles.length) this.roleId = this.roles[0].id
         this.$nextTick(this.loadPermissions)
       })
     },
     loadPermissions() {
-      if (!this.roleId || !this.tenantId) return
-      api.get('/system/menu-permissions', { params: { roleId: this.roleId, tenantId: this.tenantId } }).then(res => {
+      if (!this.roleId) {
+        this.selectedRoleIds = []
+        this.roleCheckAll = false
+        if (this.$refs.roleTree) this.$refs.roleTree.setCheckedKeys([])
+        return
+      }
+      api.get('/system/menu-permissions', { params: { roleId: this.roleId } }).then(res => {
         const data = res.data || {}
-        this.selectedTenantIds = data.tenantMenuIds || []
-        this.selectedRoleIds = data.roleMenuIds || []
+        this.selectedRoleIds = (data.roleMenuIds || []).map(String)
         this.$nextTick(() => {
-          this.$refs.tenantTree.setCheckedKeys(this.leafKeys(this.selectedTenantIds))
-          this.$refs.roleTree.setCheckedKeys(this.leafKeys(this.selectedRoleIds))
-          this.syncCheckAllState('tenant'); this.syncCheckAllState('role')
+          this.$refs.roleTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
+          this.syncCheckAllState()
         })
       })
     },
@@ -66,21 +65,51 @@
       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)) },
+    displayCheckedKeys(ids) {
+      const selected = new Set((ids || []).map(String))
+      const children = {}
+      const menuIds = new Set(this.flatMenus.map(item => String(item.id)))
+      this.flatMenus.forEach(item => {
+        const id = String(item.id)
+        const pid = item.pid == null ? '0' : String(item.pid)
+        if (!children[pid]) children[pid] = []
+        children[pid].push(id)
+      })
+      const result = []
+      const collectFullySelected = id => {
+        if (!selected.has(id)) return false
+        const childIds = children[id] || []
+        if (!childIds.length) {
+          result.push(id)
+          return true
+        }
+        const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean)
+        if (allChildrenSelected) result.push(id)
+        return allChildrenSelected
+      }
+      this.flatMenus.filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid))).forEach(item => collectFullySelected(String(item.id)))
+      return result
+    },
     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))
+    syncCheckAllState() {
+      const ref = this.$refs.roleTree
+      if (!ref) return
+      this.roleCheckAll = this.checkedMenuIds(ref).length === this.allMenuIds.length
+    },
+    checkAll() {
+      this.$refs.roleTree.setCheckedKeys(this.roleCheckAll ? this.allMenuIds : [])
+      this.$nextTick(this.syncCheckAllState)
     },
     save() {
-      const tenantMenuIds = this.checkedMenuIds(this.$refs.tenantTree)
+      if (!this.roleId) {
+        this.$message.warning('璇峰厛閫夋嫨瑙掕壊')
+        return
+      }
       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('鑿滃崟鏉冮檺宸蹭繚瀛�')
+      api.post('/system/menu-permissions', { roleId: this.roleId, roleMenuIds }).then(() => {
+        this.selectedRoleIds = roleMenuIds
+        this.$message.success('瑙掕壊鑿滃崟鏉冮檺宸蹭繚瀛�')
       }).finally(() => { this.saving = false })
     }
   }
@@ -89,10 +118,23 @@
 <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; }
-.panel-head h3 { margin: 0; color: #31465d; font-size: 15px; }
-.intersection-list { display: flex; flex-wrap: wrap; gap: 6px; }
-@media (max-width: 1100px) { .permission-grid { grid-template-columns: 1fr; } }
+.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;
+}
+.panel-head h3 {
+  margin: 0;
+  color: #31465d;
+  font-size: 15px;
+}
 </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"
index b150cee..443f293 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"
@@ -504,3 +504,33 @@
 - 闈炵鐞嗗憳瀹屽伐濉姤鐨勫崟浣嶅尮閰嶇洰鍓嶆寜 `DEPT_NAME`锛屼笉鏄� `DEPT_ID`锛涜嫢鍑虹幇鍚屽悕閮ㄩ棬锛屽簲鏀逛负 ID 绮剧‘鍖归厤銆�
 - 鍘嗗彶婕旂ず娲惧伐鍗曞彲鑳戒粛淇濆瓨鏃� `T_CONTRACTOR` 鍚嶇О锛岄潪绠$悊鍛� cj 鐢ㄦ埛鍙兘鐪嬩笉鍒帮紱`admin` 涓嶅彈鍗曚綅杩囨护褰卞搷銆�
 - `.codex-run`銆丱ffice 閿佹枃浠朵互鍙� `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 鏄湰鍦拌仈璋冩垨涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
+
+## 鍗佷簩銆�2026-10-06 鑱旇皟鏀跺熬锛堥渶姹� 20锛�
+
+| 搴忓彿 | 闇�姹� | 鐘舵�� | 瀹炵幇涓庨獙璇� |
+| --- | --- | --- | --- |
+| 20 | 鑿滃崟鏉冮檺绠$悊鍙栨秷绉熸埛鏉冮檺锛屼粎淇濈暀瑙掕壊鏉冮檺锛涙潈闄愬閫夋鍙嶆樉 | 宸插畬鎴� | 鏉冮檺鏍戜繚鐣欒彍鍗曞拰鎸夐挳鑺傜偣锛涜鑹插垏鎹㈠悗姝e父鍙嶆樉锛屾帴鍙d粎澶勭悊 `roleId` / `roleMenuIds` |
+
+### 12.1 涓昏鍙樻洿鏂囦欢
+
+鍚庣锛�
+
+- `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/SystemService.java`
+
+鍓嶇锛�
+
+- `emergency-repair-web/src/views/system/MenuPermission.vue`
+
+### 12.2 楠岃瘉缁撴灉
+
+- 鍚庣 `mvn -B -DskipTests compile` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃銆�
+- `GET /api/system/menu-permissions?roleId=2106221661022490625` 杩斿洖瀛楃涓插瀷 `roleMenuIds` 鍏� 14 鏉★紝閬垮厤娴忚鍣ㄨВ鏋愰洩鑺� ID 鏃朵涪澶辩簿搴︺��
+- `node .codex-run/verify-menu-permission.js` 杈撳嚭 `hasTenantPanel: false`銆乣buttonNodeCount: 76`銆乣checkedMatchesSaved: true`銆乣hasError: false`銆�
+- 椤甸潰淇濈暀鎸夐挳鑺傜偣锛岃鑹插垏鎹㈠悗鏉冮檺鏍戝閫夋鍙嶆樉姝e父锛涙埅鍥� `.codex-run/menu-permission-verified.png`銆�
+
+### 12.3 鎺ユ墜娉ㄦ剰
+
+- 鏈疆浠g爜涓庝氦鎺ヨ褰曚竴骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
+- `.codex-run`銆丱ffice 閿佹枃浠跺強 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 浠嶄负鏈湴涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��

--
Gitblit v1.9.1