| HANDOFF.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| emergency-repair-server/src/main/java/com/emergencyrepair/business/dto/Requests.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| emergency-repair-web/src/views/system/MenuPermission.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| requirement/交接文件_2026.10.3.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
HANDOFF.md
@@ -726,3 +726,30 @@ - å岿¼ç¤ºæ´¾å·¥åå¯è½ä¿çæ§ `T_CONTRACTOR` åç§°ï¼é管çå cj ç¨æ·å¯è½çä¸å°è¿äºåå²åï¼`admin` ä¸ååä½è¿æ»¤å½±åã - `.codex-run`ãOffice éæä»¶ `requirement\~$åå§éæ±.docx` å `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt` 屿¬å°èè°æä¸´æ¶æä»¶ï¼ä¸åºæäº¤ã - æ¬è½®ä»£ç å·²æäº¤å¹¶æ¨éå ¬å¸ Git ä»åº `main` åæ¯ï¼æäº¤ `757dcb3`ï¼äº¤æ¥æä»¶åå¡«æäº¤è®°å½ãåç»å¯å¨ãæå»ºåæéå½ä»¤æ²¿ç¨ç¬¬ 16 èã ## 18. æ¶å°¾å¿«ç §ï¼2026-10-06 å¨ä¸ï¼éæ± 20 èåæéç®¡çæ¶å°¾ï¼ **æ¬è½®å®æå 容** - éæ± 20ï¼èåæé管çåæ¶ç§æ·æéåè§è²æéçåéææï¼æ¹ä¸ºåæ¥è®¾ç½®è§è²æéãå½åç¨æ·æ¬èº«æ²¡æç§æ·å½å±ï¼å æ¤ä¸åè®¾ç½®ç§æ·æéã - æéæ ç»§ç»å±ç¤ºèåèç¹åæé®èç¹ï¼ä¸æ `menuType` è¿æ»¤ï¼åæ¢è§è²åï¼å·²ä¿åæéå¤éæ¡æ£å¸¸åæ¾ã - èåæéæ¥å£åªä¿ç `roleId` / `roleMenuIds`ï¼å é¤ç§æ·èåææå `/api/system/tenants`ã - ä¿®å¤éªè± ID ç»æµè§å¨ JSON è§£æå精度丢失çé®é¢ï¼å端è¿åå符串 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`ã - 页é¢ç¡®è®¤æé®èç¹ä¿çï¼åæ¢è§è²åæéå¾é坿£å¸¸åæ¾ã **å½åè¿è¡ç¶æï¼2026-10-06ï¼** - å端 `http://localhost:8082`ï¼å端 `http://127.0.0.1:8091`ï¼DM8 `127.0.0.1:5236`ï¼Edge 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`ãOffice éæä»¶æ `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt`ã - æ¬è½®ä»£ç ä¸äº¤æ¥è®°å½ä¸å¹¶æäº¤å¹¶æ¨éå ¬å¸ Git ä»åº `main` 忝ã 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 { 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() { 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; } 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> requirement/½»½ÓÎļþ_2026.10.3.md
@@ -504,3 +504,33 @@ - é管çåå®å·¥å¡«æ¥çåä½å¹é ç®åæ `DEPT_NAME`ï¼ä¸æ¯ `DEPT_ID`ï¼è¥åºç°ååé¨é¨ï¼åºæ¹ä¸º ID 精确å¹é ã - å岿¼ç¤ºæ´¾å·¥åå¯è½ä»ä¿åæ§ `T_CONTRACTOR` åç§°ï¼é管çå cj ç¨æ·å¯è½çä¸å°ï¼`admin` ä¸ååä½è¿æ»¤å½±åã - `.codex-run`ãOffice éæä»¶ä»¥å `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt` æ¯æ¬å°èè°æä¸´æ¶æä»¶ï¼ä¸åºæäº¤ã ## åäºã2026-10-06 èè°æ¶å°¾ï¼éæ± 20ï¼ | åºå· | éæ± | ç¶æ | å®ç°ä¸éªè¯ | | --- | --- | --- | --- | | 20 | èåæé管çåæ¶ç§æ·æéï¼ä» ä¿çè§è²æéï¼æéå¤éæ¡åæ¾ | 已宿 | æéæ ä¿çèååæé®èç¹ï¼è§è²åæ¢åæ£å¸¸åæ¾ï¼æ¥å£ä» å¤ç `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`ã - 页é¢ä¿çæé®èç¹ï¼è§è²åæ¢åæéæ å¤éæ¡åæ¾æ£å¸¸ï¼æªå¾ `.codex-run/menu-permission-verified.png`ã ### 12.3 æ¥ææ³¨æ - æ¬è½®ä»£ç ä¸äº¤æ¥è®°å½ä¸å¹¶æäº¤å¹¶æ¨éå ¬å¸ Git ä»åº `main` 忝ã - `.codex-run`ãOffice éæä»¶å `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt` ä»ä¸ºæ¬å°ä¸´æ¶æä»¶ï¼ä¸åºæäº¤ã