rsf-admin/src/App.jsx
@@ -103,19 +103,30 @@ loginPage={Login} dashboard={Dashboard} > {(permissions) => ( <> {Common.extractNavMenus(permissions)?.map((node) => { return ( {(permissions) => { const nav = Common.extractNavMenus(permissions) || []; const components = new Set( nav.map((n) => n.component).filter(Boolean) ); return ( <> {nav.map((node) => ( <Resource key={node.id} name={node.component} {...ResourceContent(node)} /> ); })} </> )} ))} {!components.has("warehouseStock") && ( <Resource key="__warehouseStock" name="warehouseStock" {...ResourceContent({ component: "warehouseStock" })} /> )} </> ); }} {/* CustomRoutes don't trigger checkAuth */} <CustomRoutes> <Route path="/dashboard" element={<Dashboard />} /> rsf-admin/src/config/MyDataProvider.js
@@ -2,7 +2,13 @@ import * as Common from "../utils/common"; // 出库历史单与入库历史单共用 asnOrderLog 接口,仅前端 resource 不同 const getApiResource = (resource) => (resource === "outStockOrderLog" ? "asnOrderLog" : resource); // 即时库存:前端 Resource 名 warehouseStock,后端路径 /warehouse/stock const getApiResource = (resource) => { if (resource === "outStockOrderLog") return "asnOrderLog"; if (resource === "warehouseStock") return "warehouse/stock"; if (resource === "warehouseStockHistories") return "warehouse/stock/histories"; return resource; }; const MyDataProvider = { // *** https://marmelab.com/react-admin/DataProviderWriting.html *** rsf-admin/src/i18n/en.js
@@ -212,6 +212,7 @@ inStockPoces: 'In Stock Pocess', outStockPoces: 'Out Stock Pocess', warehouseStock: 'Instant Inventory', viewStockManage: 'Instant Inventory', deviceBind: 'Device Bind', tasks: 'Tasks', locItem: 'Loc Items', rsf-admin/src/i18n/zh.js
@@ -222,6 +222,7 @@ inStockPoces: '入库管理', outStockPoces: '出库管理', warehouseStock: '即时库存', viewStockManage: '即时库存', deviceBind: '设备绑定', tasks: '任务管理', wave: '波次管理', rsf-admin/src/layout/TabsBar.jsx
@@ -146,8 +146,11 @@ const menuName = findMenuName(tab.path, permissions); if (menuName) return translate(menuName); } return tab.path.replace(/^\//, '').split('/')[0] || tab.path; }, [translate, permissions, findMenuName]); const segment = tab.path.replace(/^\//, '').split('/')[0] || tab.path; const fromMenuKey = t(`menu.${segment}`, null); if (fromMenuKey != null) return fromMenuKey; return segment; }, [translate, permissions, findMenuName, t]); useEffect(() => { const currentPath = location.pathname; rsf-admin/src/page/ResourceContent.js
@@ -102,6 +102,7 @@ case "warehouseAreasItem": return warehouseAreasItem; case "warehouseStock": case "viewStockManage": return warehouseStock; case "loc": return loc; rsf-admin/src/page/components/CardWithIcon.jsx
@@ -66,7 +66,7 @@ </Box> <Box sx={{ display: 'flex', padding: '1em' }}> <Typography color="textSecondary">{type == 'in' ? translate("page.dashboard.header.waitQty") : translate("page.dashboard.header.waitOutQty")}:</Typography> <Typography color="textSecondary">{type == 'in' ? statistic?.taskIn : statistic?.taskOut}</Typography> <Typography color="textSecondary">{type == 'in' ? statistic?.waitIn : statistic?.waitOut}</Typography> </Box> </Box>} </Box> : <Box> rsf-admin/src/page/statistics/stockManage/WarehouseHistories.jsx
@@ -135,7 +135,7 @@ <Grid item sx={24}> <Box display="flex"> <List resource="warehouse/stock/histories" resource="warehouseStockHistories" sx={{ flexGrow: 1, transition: (theme) => rsf-admin/src/page/statistics/stockManage/WarehouseStockList.jsx
@@ -100,7 +100,6 @@ width: '200px' } }} resource="warehouse/stock" title={"common.button.detail"} empty={false} // filter={{aggType: "matnr"}} rsf-admin/src/utils/common.js
@@ -41,10 +41,15 @@ const navMenus = []; const traverse = (nodes) => { nodes.forEach((node) => { if (!node.children) { navMenus.push(node); // 叶子:无子或 children 为空数组;仅收集有 component 的节点(页面资源) const children = node.children; const hasChildren = Array.isArray(children) && children.length > 0; if (!hasChildren) { if (node.component) { navMenus.push(node); } } else { traverse(node.children); traverse(children); } }); }; rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/dto/DashboardDto.java
@@ -22,6 +22,12 @@ @ApiModelProperty("实际出库数量") private Integer taskOut; @ApiModelProperty("未完成入库单数量") private Integer waitIn; @ApiModelProperty("未完成出库单数量") private Integer waitOut; @ApiModelProperty("总入库数量") private Integer totalIn; rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/AsnOrderServiceImpl.java
@@ -448,11 +448,13 @@ DashboardDto dto = new DashboardDto(); //获取入库数量 DashboardDto trandDto = this.baseMapper.getDashbord(OrderType.ORDER_IN.type, TaskType.TASK_TYPE_IN.type + ""); dto.setInAnf(trandDto.getAnfme()).setTaskIn(trandDto.getRealAnfme()).setTotalIn(trandDto.getAnfme() + trandDto.getRealAnfme()); dto.setInAnf(trandDto.getAnfme()).setTaskIn(trandDto.getRealAnfme()).setWaitIn(countPendingInOrders()) .setTotalIn(trandDto.getAnfme() + trandDto.getRealAnfme()); //获取出库单数量 DashboardDto outTrand = this.baseMapper.getDashbord(OrderType.ORDER_OUT.type, TaskType.TASK_TYPE_OUT.type + ""); dto.setOutAnf(outTrand.getAnfme()).setTaskOut(outTrand.getRealAnfme()).setTotalOut(outTrand.getAnfme() + outTrand.getRealAnfme()); dto.setOutAnf(outTrand.getAnfme()).setTaskOut(outTrand.getRealAnfme()).setWaitOut(countPendingOutOrders()) .setTotalOut(outTrand.getAnfme() + outTrand.getRealAnfme()); //获取执行中任务数量 List<Task> tasks = taskService.list(new LambdaQueryWrapper<>()); @@ -462,6 +464,30 @@ return R.ok().add(dto); } /** 未完成入库单:非已完成/取消/关闭 */ private int countPendingInOrders() { return Math.toIntExact(this.count(new LambdaQueryWrapper<WkOrder>() .eq(WkOrder::getType, OrderType.ORDER_IN.type) .eq(WkOrder::getDeleted, 0) .and(w -> w.isNull(WkOrder::getExceStatus).or() .notIn(WkOrder::getExceStatus, AsnExceStatus.ASN_EXCE_STATUS_TASK_DONE.val, AsnExceStatus.ASN_EXCE_STATUS_TASK_CANCEL.val, AsnExceStatus.ASN_EXCE_STATUS_TASK_CLOSE.val)))); } /** 未完成出库单:非已完成/取消/关闭 */ private int countPendingOutOrders() { return Math.toIntExact(this.count(new LambdaQueryWrapper<WkOrder>() .eq(WkOrder::getType, OrderType.ORDER_OUT.type) .eq(WkOrder::getDeleted, 0) .and(w -> w.isNull(WkOrder::getExceStatus).or() .notIn(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_DONE.val, AsnExceStatus.ASN_EXCE_STATUS_TASK_CANCEL.val, AsnExceStatus.ASN_EXCE_STATUS_TASK_CLOSE.val)))); } /** * 获取出入库趋势 * @return rsf-server/src/main/java/com/vincent/rsf/server/system/controller/AuthController.java
@@ -20,12 +20,14 @@ import com.vincent.rsf.server.system.controller.result.LoginResult; import com.vincent.rsf.server.system.controller.result.MenuVo; import com.vincent.rsf.server.system.controller.result.SystemInfoVo; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.vincent.rsf.server.system.entity.Menu; import com.vincent.rsf.server.system.entity.Tenant; import com.vincent.rsf.server.system.entity.User; import com.vincent.rsf.server.system.entity.UserLogin; import com.vincent.rsf.server.system.enums.EmailType; import com.vincent.rsf.server.system.enums.StatusType; import com.vincent.rsf.server.system.service.MenuService; import com.vincent.rsf.server.system.service.RoleMenuService; import com.vincent.rsf.server.system.service.TenantService; import com.vincent.rsf.server.system.service.UserLoginService; @@ -35,7 +37,9 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** @@ -53,6 +57,8 @@ private UserLoginService userLoginService; @Resource private RoleMenuService roleMenuService; @Resource private MenuService menuService; @Resource private TenantService tenantService; @Autowired @@ -164,7 +170,13 @@ @GetMapping("/auth/menu") public R userMenu() { List<Menu> menus = roleMenuService.listMenuByUserId(getLoginUserId(), Menu.TYPE_MENU); Long uid = getLoginUserId(); List<Menu> grantedAll = roleMenuService.listMenuByUserId(uid, null); Set<Long> grantedMenuIds = grantedAll.stream().map(Menu::getId).collect(Collectors.toCollection(HashSet::new)); List<Menu> allMenus = menuService.list(new LambdaQueryWrapper<Menu>().eq(Menu::getDeleted, 0)); List<Menu> menus = roleMenuService.listMenuByUserId(uid, Menu.TYPE_MENU).stream() .filter(m -> menuPageGranted(m, grantedMenuIds, allMenus)) .collect(Collectors.toList()); List<MenuVo> voList = menus.stream().map(this::convertToVo).collect(Collectors.toList()); // exclude tenant if (!configProperties.getSuperUserList().contains(getLoginUser().getUsername())) { @@ -225,6 +237,32 @@ // ---------------------------------------------------- /** * 页面级菜单:若存在带 :list 的按钮权限子节点,则必须同时勾选至少一个该类按钮,才展示该页(与接口 @PreAuthorize 一致) */ private boolean menuPageGranted(Menu m, Set<Long> grantedMenuIds, List<Menu> allMenus) { if (m == null || !Integer.valueOf(Menu.TYPE_MENU).equals(m.getType())) { return true; } if (Cools.isEmpty(m.getComponent())) { return true; } List<Menu> btnChildren = allMenus.stream() .filter(c -> m.getId().equals(c.getParentId()) && Integer.valueOf(Menu.TYPE_BTN).equals(c.getType())) .collect(Collectors.toList()); if (btnChildren.isEmpty()) { return true; } boolean needListBtn = btnChildren.stream().anyMatch(c -> c.getAuthority() != null && c.getAuthority().contains(":list")); if (!needListBtn) { return true; } return btnChildren.stream() .filter(c -> c.getAuthority() != null && c.getAuthority().contains(":list")) .anyMatch(c -> grantedMenuIds.contains(c.getId())); } private MenuVo convertToVo(Menu menu) { if (menu == null) { return null; rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/TenantServiceImpl.java
@@ -122,9 +122,12 @@ throw new CoolException("failed to create userRole"); } // save roleMenu // save roleMenu(HTTP 审计类菜单不默认下发,需在角色里显式勾选) List<Menu> menuList = menuService.list(); for (Menu menu : menuList) { if ("httpAuditLog".equals(menu.getComponent()) || "httpAuditRule".equals(menu.getComponent())) { continue; } RoleMenu roleMenu = new RoleMenu(); roleMenu.setRoleId(role.getId()); roleMenu.setMenuId(menu.getId());