fix(admin): 按角色权限展示后台菜单 (bug #24030)
| | |
| | | id: number |
| | | name: string |
| | | roleCode: string |
| | | permissionCodes?: string[] |
| | | } |
| | | |
| | | export interface AdminLoginResult { |
| | |
| | | { index: 'system-management', label: '系统管理', icon: Setting, items: systemMenus }, |
| | | ] |
| | | |
| | | const routePermissions: Record<string, string> = { |
| | | '/talents': 'admin.talents.view', '/family-card-applications': 'admin.family-cards.view', '/upgrade-applications': 'admin.talents.view', '/family-cards': 'admin.family-cards.view', '/child-cards': 'admin.family-cards.view', |
| | | '/merchants': 'admin.merchants.view', '/products': 'admin.products.view', '/orders': 'admin.orders.view', '/refund-records': 'admin.refunds.view', '/hotel-free-verifications': 'admin.orders.view', |
| | | '/medical-reservations': 'admin.medical-reservations.view', '/medical-applications': 'admin.medical-reservations.view', '/parking-plates': 'admin.parking-plates.view', '/fuel-services': 'admin.parking-plates.view', '/spouse-employment': 'admin.parking-plates.view', '/hotel-reservations': 'admin.medical-reservations.view', |
| | | '/merchant-settlement': 'admin.merchants.view', '/equity-images': 'admin.equity-images.view', '/talent-news-management': 'admin.news.view', '/talent-policy-management': 'admin.news.view', '/movie-tickets': 'admin.news.view', |
| | | '/system-settings': 'admin.settings.view', '/protocol-settings': 'admin.settings.view', '/roles': 'admin.roles.view', '/staff': 'admin.staff.view', |
| | | } |
| | | const canAccess = (path?: string) => { |
| | | if (!path || appStore.user.role === '平台管理') return true |
| | | const required = routePermissions[path] |
| | | return !required || Boolean(appStore.user.permissionCodes?.includes('*') || appStore.user.permissionCodes?.includes(required)) |
| | | } |
| | | const visibleMenuGroups = computed(() => menuGroups.map((group) => ({ ...group, items: group.items.filter((item) => canAccess(item.path)) })).filter((group) => group.items.length > 0)) |
| | | |
| | | const defaultOpeneds = computed(() => { |
| | | const currentGroup = menuGroups.find((item) => findMenuLabel(item.items, activeMenu.value)) |
| | | return currentGroup ? [currentGroup.index] : [] |
| | |
| | | <span>首页</span> |
| | | </el-menu-item> |
| | | |
| | | <el-sub-menu v-for="group in menuGroups" :key="group.index" :index="group.index"> |
| | | <el-sub-menu v-for="group in visibleMenuGroups" :key="group.index" :index="group.index"> |
| | | <template #title> |
| | | <el-icon> |
| | | <component :is="group.icon" /> |
| | |
| | | interface UserProfile { |
| | | name: string |
| | | role: string |
| | | permissionCodes?: string[] |
| | | } |
| | | |
| | | const tokenKey = 'talent-card-admin-token' |
| | |
| | | } |
| | | appStore.login( |
| | | result.accessToken, |
| | | { name: result.operator.name, role: result.operator.roleCode === 'platform_admin' ? '平台管理' : result.operator.roleCode }, |
| | | { name: result.operator.name, role: result.operator.roleCode === 'platform_admin' ? '平台管理' : result.operator.roleCode, permissionCodes: result.operator.permissionCodes || [] }, |
| | | form.remember, |
| | | ) |
| | | await router.replace(String(route.query.redirect || '/dashboard')) |
| | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | |
| | | import com.talentcard.server.business.DatabaseRows; |
| | | import com.talentcard.server.business.merchant.auth.MerchantPasswordHasher; |
| | | import com.talentcard.server.common.BusinessException; |
| | |
| | | public Map<String, Object> login(AdminLoginRequest request, String requestId) { |
| | | String account = request.account().trim(); |
| | | List<Map<String, Object>> rows = rows(""" |
| | | select id operator_id, password_hash, display_name, role_code, status, |
| | | select o.id operator_id, o.password_hash, o.display_name, o.role_code, o.status, |
| | | r.permission_codes, |
| | | login_failed_count, locked_until |
| | | from admin_operator |
| | | where account = ? and deleted = 0 |
| | | from admin_operator o left join admin_role r on r.role_code = o.role_code |
| | | where o.account = ? and o.deleted = 0 |
| | | limit 1 |
| | | """, account); |
| | | if (rows.isEmpty()) { |
| | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("accessToken", accessToken); |
| | | result.put("expiresIn", ACCESS_TOKEN_SECONDS); |
| | | result.put("operator", Map.of( |
| | | "id", number(operator.get("operatorId")), |
| | | "name", String.valueOf(operator.get("displayName")), |
| | | "roleCode", String.valueOf(operator.get("roleCode")))); |
| | | Map<String, Object> principal = new LinkedHashMap<>(); |
| | | principal.put("id", number(operator.get("operatorId"))); |
| | | principal.put("name", String.valueOf(operator.get("displayName"))); |
| | | principal.put("roleCode", String.valueOf(operator.get("roleCode"))); |
| | | principal.put("permissionCodes", permissions(operator)); |
| | | result.put("operator", principal); |
| | | return result; |
| | | } |
| | | |
| | | private List<String> permissions(Map<String, Object> operator) { |
| | | if ("platform_admin".equals(String.valueOf(operator.get("roleCode")))) return List.of("*"); |
| | | String raw = String.valueOf(operator.getOrDefault("permissionCodes", "")); |
| | | try { return JSON.parseArray(raw, String.class); } |
| | | catch (Exception ignored) { return List.of(); } |
| | | } |
| | | |
| | | private void audit(Long operatorId, String account, boolean success, String reason, String requestId) { |
| | | jdbc.update("insert into admin_login_audit(operator_id, account, success, failure_reason, request_id) values (?, ?, ?, ?, ?)", |
| | | operatorId, account, success ? 1 : 0, reason, requestId); |