From f3b1f046c906850c000d4db51ed58c1cfdc4006e Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sun, 19 Apr 2026 15:13:27 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Models/AdminUser/CreateAdminUserRequest.cs | 9 +++++++-- .../Models/AdminUser/UpdateAdminUserRequest.cs | 9 +++++++-- uniapp/manifest.json | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs index c546a5c..b03945d 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs @@ -36,11 +36,16 @@ public class CreateAdminUserRequest public string? Avatar { get; set; } /// - /// 邮箱 + /// 邮箱(为空时跳过格式验证) /// [MaxLength(100, ErrorMessage = "邮箱最多100个字符")] [EmailAddress(ErrorMessage = "邮箱格式不正确")] - public string? Email { get; set; } + public string? Email + { + get => _email; + set => _email = string.IsNullOrWhiteSpace(value) ? null : value; + } + private string? _email; /// /// 手机号 diff --git a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs index 04895bc..b6e4c42 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs @@ -20,11 +20,16 @@ public class UpdateAdminUserRequest public string? Avatar { get; set; } /// - /// 邮箱 + /// 邮箱(为空时跳过格式验证) /// [MaxLength(100, ErrorMessage = "邮箱最多100个字符")] [EmailAddress(ErrorMessage = "邮箱格式不正确")] - public string? Email { get; set; } + public string? Email + { + get => _email; + set => _email = string.IsNullOrWhiteSpace(value) ? null : value; + } + private string? _email; /// /// 手机号 diff --git a/uniapp/manifest.json b/uniapp/manifest.json index b56f978..58eb3ae 100644 --- a/uniapp/manifest.json +++ b/uniapp/manifest.json @@ -1,6 +1,6 @@ { "name" : "学业邑规划", - "appid" : "__UNI__1BAACAB", + "appid" : "__UNI__A612028", "description" : "", "versionName" : "1.0.0", "versionCode" : "100", From 111c6bf466858ea573161bd1912ecf8833ae7c45 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sun, 19 Apr 2026 15:28:45 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=96=B0=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Models/AdminUser/CreateAdminUserRequest.cs | 18 ++++++++++++++---- .../Models/AdminUser/UpdateAdminUserRequest.cs | 18 ++++++++++++++---- .../admin-web/src/views/system/user/index.vue | 11 +++++++++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs index b03945d..9da5769 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/CreateAdminUserRequest.cs @@ -24,10 +24,15 @@ public class CreateAdminUserRequest public string Password { get; set; } = null!; /// - /// 真实姓名 + /// 真实姓名(为空时自动转 null) /// [MaxLength(50, ErrorMessage = "真实姓名最多50个字符")] - public string? RealName { get; set; } + public string? RealName + { + get => _realName; + set => _realName = string.IsNullOrWhiteSpace(value) ? null : value; + } + private string? _realName; /// /// 头像URL @@ -48,10 +53,15 @@ public class CreateAdminUserRequest private string? _email; /// - /// 手机号 + /// 手机号(为空时自动转 null) /// [MaxLength(20, ErrorMessage = "手机号最多20个字符")] - public string? Phone { get; set; } + public string? Phone + { + get => _phone; + set => _phone = string.IsNullOrWhiteSpace(value) ? null : value; + } + private string? _phone; /// /// 部门ID diff --git a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs index b6e4c42..5b56211 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Models/AdminUser/UpdateAdminUserRequest.cs @@ -8,10 +8,15 @@ namespace MiAssessment.Admin.Models.AdminUser; public class UpdateAdminUserRequest { /// - /// 真实姓名 + /// 真实姓名(为空时自动转 null) /// [MaxLength(50, ErrorMessage = "真实姓名最多50个字符")] - public string? RealName { get; set; } + public string? RealName + { + get => _realName; + set => _realName = string.IsNullOrWhiteSpace(value) ? null : value; + } + private string? _realName; /// /// 头像URL @@ -32,10 +37,15 @@ public class UpdateAdminUserRequest private string? _email; /// - /// 手机号 + /// 手机号(为空时自动转 null) /// [MaxLength(20, ErrorMessage = "手机号最多20个字符")] - public string? Phone { get; set; } + public string? Phone + { + get => _phone; + set => _phone = string.IsNullOrWhiteSpace(value) ? null : value; + } + private string? _phone; /// /// 部门ID diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue index 7ecb73b..c763294 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue @@ -372,11 +372,18 @@ const handleSubmit = async () => { submitLoading.value = true try { + // 非必填字段:空字符串转 null,避免后端格式验证失败 + const submitData = { + ...formData, + realName: formData.realName || null, + email: formData.email || null, + phone: formData.phone || null + } if (isEdit.value) { - await updateAdminUser(formData.id, formData) + await updateAdminUser(submitData.id, submitData) ElMessage.success('更新成功') } else { - await createAdminUser(formData) + await createAdminUser(submitData) ElMessage.success('创建成功') } dialogVisible.value = false From b693a032643b14e92bd0c475947a3d02c56c3776 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sun, 19 Apr 2026 15:33:16 +0800 Subject: [PATCH 3/5] . --- uniapp/pages/assessment/info/index.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/uniapp/pages/assessment/info/index.vue b/uniapp/pages/assessment/info/index.vue index 8ddd357..b0a983e 100644 --- a/uniapp/pages/assessment/info/index.vue +++ b/uniapp/pages/assessment/info/index.vue @@ -653,6 +653,7 @@ + From 36b08997bdd2420a84ae3b2a5ed7636c02591b95 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sun, 19 Apr 2026 15:44:57 +0800 Subject: [PATCH 4/5] 12 --- .../admin-web/src/api/operationLog.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/api/operationLog.ts b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/api/operationLog.ts index 729b33a..c63cd05 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/api/operationLog.ts +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/api/operationLog.ts @@ -2,28 +2,29 @@ import { request, type ApiResponse, type PagedResult } from '@/utils/request' export interface OperationLog { id: number - userId: number - username: string - module: string - action: string - method: string - url: string - params: string | null + adminUserId: number | null + username: string | null + module: string | null + action: string | null + method: string | null + url: string | null ip: string | null - userAgent: string | null - duration: number + requestData: string | null + responseData: string | null status: number - errorMessage: string | null + errorMsg: string | null + duration: number createdAt: string } export interface OperationLogQuery { - keyword?: string - module?: string - startDate?: string - endDate?: string page: number pageSize: number + username?: string + module?: string + status?: number + startDate?: string + endDate?: string } // 获取操作日志列表 @@ -35,7 +36,7 @@ export function getOperationLogList(params: OperationLogQuery): Promise> { return request({ url: `/admin/logs/${id}`, From b82c43e513fabe3bd6f906ad42694e7b184c07b0 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sun, 19 Apr 2026 20:38:08 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=9D=83=E9=99=90=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/AdminUserService.cs | 9 ++- .../Services/PermissionService.cs | 63 ++++++++++++++++++- .../admin-web/src/router/index.ts | 24 ++++++- .../admin-web/src/utils/request.ts | 5 ++ .../admin-web/src/views/system/user/index.vue | 30 ++++++++- temp_menu29.txt | 1 + temp_menus.json | 1 + temp_test_menus.txt | 1 + 8 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 temp_menu29.txt create mode 100644 temp_menus.json create mode 100644 temp_test_menus.txt diff --git a/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs b/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs index 6c88de6..0a5a54a 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs @@ -15,15 +15,17 @@ public class AdminUserService : IAdminUserService private readonly AdminDbContext _dbContext; private readonly ILogger _logger; private readonly IAuthService _authService; + private readonly IPermissionService _permissionService; // 超级管理员角色编码 private const string SuperAdminRoleCode = "super_admin"; - public AdminUserService(AdminDbContext dbContext, ILogger logger, IAuthService authService) + public AdminUserService(AdminDbContext dbContext, ILogger logger, IAuthService authService, IPermissionService permissionService) { _dbContext = dbContext; _logger = logger; _authService = authService; + _permissionService = permissionService; } /// @@ -310,6 +312,7 @@ public class AdminUserService : IAdminUserService } await _dbContext.SaveChangesAsync(); + _permissionService.InvalidateCache(userId); _logger.LogInformation("管理员 {UserId} 分配角色成功,角色数量: {Count}", userId, roleIds.Count); } @@ -353,6 +356,10 @@ public class AdminUserService : IAdminUserService } await _dbContext.SaveChangesAsync(); + + // 清除用户权限缓存,使新菜单权限立即生效 + _permissionService.InvalidateCache(userId); + _logger.LogInformation("管理员 {UserId} 分配用户专属菜单成功,菜单数量: {Count}", userId, menuIds.Count); } diff --git a/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs b/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs index 9dd2a03..e5d941e 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs @@ -212,14 +212,71 @@ public class PermissionService : IPermissionService var roleIds = userRoles.Select(ur => ur.RoleId).ToList(); - // 获取角色关联的权限 - var permissions = await _dbContext.RolePermissions + // 1. 获取角色关联的权限(permissions 表) + var rolePermissions = await _dbContext.RolePermissions .Where(rp => roleIds.Contains(rp.RoleId)) .Include(rp => rp.Permission) .Select(rp => rp.Permission.Code) .Distinct() .ToListAsync(); - return permissions; + // 2. 收集角色关联的菜单 ID + var roleMenuIds = await _dbContext.RoleMenus + .Where(rm => roleIds.Contains(rm.RoleId)) + .Select(rm => rm.MenuId) + .Distinct() + .ToListAsync(); + + // 3. 收集用户专属菜单 ID + var userMenuIds = await _dbContext.AdminUserMenus + .Where(um => um.AdminUserId == adminUserId) + .Select(um => um.MenuId) + .Distinct() + .ToListAsync(); + + // 4. 合并所有菜单 ID,并递归查找所有子菜单 + var allMenuIds = roleMenuIds.Concat(userMenuIds).Distinct().ToHashSet(); + var allChildMenuIds = await GetAllChildMenuIdsAsync(allMenuIds); + allMenuIds.UnionWith(allChildMenuIds); + + // 5. 从所有菜单中提取权限标识 + var menuPermissions = await _dbContext.Set() + .Where(m => allMenuIds.Contains(m.Id) && m.Permission != null && m.Permission != "") + .Select(m => m.Permission!) + .Distinct() + .ToListAsync(); + + // 合并所有权限 + var allPermissions = rolePermissions + .Concat(menuPermissions) + .Distinct() + .ToList(); + + return allPermissions; + } + + /// + /// 递归获取所有子菜单 ID + /// + private async Task> GetAllChildMenuIdsAsync(HashSet parentIds) + { + var result = new HashSet(); + var currentParentIds = parentIds; + + while (currentParentIds.Count > 0) + { + var childIds = await _dbContext.Set() + .Where(m => currentParentIds.Contains(m.ParentId)) + .Select(m => m.Id) + .ToListAsync(); + + var newIds = childIds.Where(id => !result.Contains(id) && !parentIds.Contains(id)).ToHashSet(); + if (newIds.Count == 0) break; + + result.UnionWith(newIds); + currentParentIds = newIds; + } + + return result; } } diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts index 78078a7..ab7f8ec 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts @@ -10,7 +10,7 @@ export { businessRoutes, type RouteMeta, getBusinessPermissions, filterRoutesByP const constantRoutes: RouteRecordRaw[] = [ { path: '/', - redirect: '/dashboard' + redirect: '/dashboard' // 默认值,登录后会被动态路由覆盖 }, { path: '/login', @@ -60,6 +60,23 @@ const router = createRouter({ // 白名单路由 const whiteList = ['/login', '/404'] +/** + * 递归获取用户有权限的第一个可访问页面路径 + */ +function getFirstAccessiblePath(routes: RouteRecordRaw[], parentPath = ''): string | null { + for (const route of routes) { + const fullPath = parentPath ? `${parentPath}/${route.path}`.replace(/\/+/g, '/') : route.path + // 有 component 且不是 Layout 的就是叶子页面 + if (route.children && route.children.length > 0) { + const childPath = getFirstAccessiblePath(route.children, fullPath) + if (childPath) return childPath + } else if (route.component) { + return fullPath + } + } + return null +} + // 标记动态路由是否已加载 let dynamicRoutesLoaded = false @@ -94,6 +111,11 @@ router.beforeEach(async (to, _from, next) => { accessRoutes.forEach(route => { router.addRoute(route) }) + // 动态设置首页重定向到用户有权限的第一个页面 + const firstRoute = getFirstAccessiblePath(accessRoutes) + if (firstRoute) { + router.addRoute({ path: '/', redirect: firstRoute }) + } // 添加 404 兜底路由 router.addRoute({ path: '/:pathMatch(.*)*', redirect: '/404' }) // 标记动态路由已加载 diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts index 70fba6b..82b609d 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts @@ -123,6 +123,11 @@ async function handle401Error(error: any): Promise { // Don't retry if already retried or if it's a refresh request if (originalConfig._retry || isWhiteListUrl(originalConfig.url)) { + // 白名单接口(如登录)的 401 错误,直接显示后端返回的错误信息 + const msg = error.response?.data?.message + if (msg) { + ElMessage.error(msg) + } return Promise.reject(error) } diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue index c763294..2ddc4c0 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue @@ -161,7 +161,6 @@ show-checkbox node-key="id" :default-checked-keys="checkedMenuIds" - :check-strictly="true" />